use cli to pass in CSS vars

This commit is contained in:
Talon Poole 2021-01-29 22:32:30 +00:00
parent 13a885ee11
commit 990667c2a7
4 changed files with 104 additions and 81 deletions

58
cli.js
View file

@ -5,10 +5,33 @@ const map = require("map-stream");
const tokenize = require("./tokenize"); const tokenize = require("./tokenize");
const toHTML = require("./to-html"); const toHTML = require("./to-html");
// TODO: automatically pull these in from gmi.css (also for gmi.css(5))
const GMI_CSS_VARS = [
"foreground",
"background",
"p-size",
"p-indent",
"p-line-height",
"a-size",
"pre-size",
"pre-line-height",
"h1-size",
"h2-size",
"h3-size",
"heading-line-height",
"ul-size",
"ul-line-height",
"blockquote-size",
"blockquote-line-height",
"mono",
"serif",
"sans-serif",
];
require("yargs") require("yargs")
.scriptName("gmi-web") .scriptName("gmi-web")
.command( .command(
"$0 <files..>", "$0 [files..]",
"Convert .gmi to .html. See gmi-web(1) for more details.", "Convert .gmi to .html. See gmi-web(1) for more details.",
(yargs) => (yargs) =>
yargs yargs
@ -19,52 +42,49 @@ require("yargs")
.option("images", { .option("images", {
type: "boolean", type: "boolean",
default: false, default: false,
description: "Include images", describe: "Include images",
}) })
.option("audio", { .option("audio", {
type: "boolean", type: "boolean",
default: false, default: false,
description: "Include audio", describe: "Include audio",
}) })
.option("video", { .option("video", {
type: "boolean", type: "boolean",
default: false, default: false,
description: "Include video", describe: "Include video",
})
.option("verbose", {
alias: "v",
type: "boolean",
default: false,
description: "No logging to stdout",
}) })
.option("css", { .option("css", {
type: "boolean", type: "boolean",
default: true, default: true,
description: "Include gmi.css", describe:
"gmi.css is included by default. Use --no-css to for the bare-minimum <style>",
}), }),
(argv) => { (argv) => {
if (argv.verbose) console.log(argv); const override = GMI_CSS_VARS.reduce((style, key) => {
if (argv[key]) {
style += `--${key}: ${argv[key]};`;
}
return style;
}, "");
fs.src(argv.files) fs.src(argv.files)
.pipe(map(tokenize)) .pipe(map(tokenize))
.pipe( .pipe(
map( map(
toHTML({ toHTML({
css: !argv.noCss, css: argv.css,
inline: { inline: {
images: argv.images, images: argv.images,
audio: argv.audio, audio: argv.audio,
video: argv.video, video: argv.video,
}, },
silent: argv.silent, verbose: argv.verbose,
override,
}) })
) )
) )
.pipe(fs.dest((file) => path.dirname(file.path))); .pipe(fs.dest((file) => path.dirname(file.path)));
} }
) )
.showHelpOnFail(true, "Specify --help for available options")
.help().argv; .help().argv;
function log(file, cb) {
console.log(file.path, file.contents ? file.contents.toString("utf8") : "");
cb(null, file);
}

View file

@ -13,9 +13,7 @@ gmi-web - A bridge between Gemini and HTML.
.P .P
.SH SYNOPSIS .SH SYNOPSIS
.P .P
\fBgmi-web\fR [\fIOPTIONS\fR] <\fIFILES\fR> \fBgmi-web\fR <\fIOPTIONS\fR> [\fIFILES\fR]
.P
\fBgmi-web\fR --recursive [\fIOPTIONS\fR] <\fIDIR\fR> [\fIOUT\fR]
.P .P
.SH DESCRIPTION .SH DESCRIPTION
.P .P
@ -24,24 +22,12 @@ and mobile-friendly fashion!
.P .P
.SH OPTIONS .SH OPTIONS
.P .P
\fBCSS variables\fR \fBCSS Variables\fR
.RS 4 .RS 4
Any variable documented in \fBgmi.css(5)\fR can be set via \fIOPTIONS\fR of the same name. Any variable documented in \fBgmi.css\fR(5) can be set via \fIOPTIONS\fR of the same
name.
.P .P
\fBgmi-web\fR --foreground "#FFFFF" --background "#00000" *.gmi \fBgmi-web\fR --foreground "#FFFFF" --background "#00000" $(find . -name *.gmi)
.P
.RE
\fB--recursive\fR <\fIDIR\fR> [\fIOUT\fR]
.RS 4
Recursively generate HTML for a \fIDIR\fR. Use \fIOUT\fR to put the .html somewhere
other than \fIDIR\fR.
.P
\fBgmi-web\fR --recursive capsule /srv/html/
.P
.RE
\fB--no-css\fR
.RS 4
Use only the bare-minimum <style>
.P .P
.RE .RE
\fB--images\fR \fB--audio\fR \fB--video\fR \fB--images\fR \fB--audio\fR \fB--video\fR
@ -49,11 +35,21 @@ Use only the bare-minimum <style>
Include the respective media inline. Include the respective media inline.
.P .P
.RE .RE
\fB--verbose\fR \fB--no-css\fR
.RS 4 .RS 4
Enable internal logs for debugging mostly. Use only the bare-minimum <style>
.P .P
.RE .RE
.SH EXAMPLES
.P
Render all the .gmi files in the current directory to .html
.P
.nf
.RS 4
gmi-web $(find \&. -name *\&.gmi)
.fi
.RE
.P
.SH SEE ALSO .SH SEE ALSO
.P .P
\fBgmi.css\fR(5) \fBgmi.css\fR(5)

View file

@ -6,9 +6,7 @@ gmi-web - A bridge between Gemini and HTML.
# SYNOPSIS # SYNOPSIS
*gmi-web* [_OPTIONS_] <_FILES_> *gmi-web* <_OPTIONS_> [_FILES_]
*gmi-web* --recursive [_OPTIONS_] <_DIR_> [_OUT_]
# DESCRIPTION # DESCRIPTION
@ -17,26 +15,25 @@ and mobile-friendly fashion!
# OPTIONS # OPTIONS
*CSS variables* *CSS Variables*
Any variable documented in *gmi.css*(5) can be set via _OPTIONS_ of the same Any variable documented in *gmi.css*(5) can be set via _OPTIONS_ of the same
name. name.
*gmi-web* --foreground "#FFFFF" --background "#00000" \*.gmi *gmi-web* --foreground "#FFFFF" --background "#00000" $(find . -name \*.gmi)
*--recursive* <_DIR_> [_OUT_]
Recursively generate HTML for a _DIR_. Use _OUT_ to put the .html somewhere
other than _DIR_.
*gmi-web* --recursive capsule /srv/html/
*--no-css*
Use only the bare-minimum <style>
*--images* *--audio* *--video* *--images* *--audio* *--video*
Include the respective media inline. Include the respective media inline.
*--verbose* *--no-css*
Enable internal logs for debugging mostly. Use only the bare-minimum <style>
# EXAMPLES
Render all the .gmi files in the current directory to .html
```
gmi-web $(find . -name \*.gmi)
```
# SEE ALSO # SEE ALSO

View file

@ -2,40 +2,45 @@ const fs = require("fs");
const path = require("path"); const path = require("path");
const TOKENS_EXT = /\.tokens\.json$/; const TOKENS_EXT = /\.tokens\.json$/;
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats // https://developer.mozilla.org/en-US/docs/Web/Media/Formats
const IMG_EXT = /\.(apng|avif|gif|jpg|jpeg|jfif|pjpeg|pjp|png|svg|webp)$/; const IMG_EXT = (exports.IMG_EXT = /\.(apng|avif|gif|jpg|jpeg|jfif|pjpeg|pjp|png|svg|webp)$/);
const AUDIO_EXT = /\.(mp3|wav|aac|aacp|mpeg|off|flac)$/; const AUDIO_EXT = (exports.AUDIO_EXT = /\.(mp3|wav|aac|aacp|mpeg|off|flac)$/);
const VIDEO_EXT = /\.(mp4|webm)$/; const VIDEO_EXT = (exports.VIDEO_EXT = /\.(mp4|webm)$/);
const BASE_STYLE = (exports.baseStyle =
"<style>p,a,pre,h1,h2,h3,ul,blockquote,img,audio,video{display:block;max-width:100%;margin:0;padding:0;overflow-wrap:anywhere;}</style>");
module.exports = (options) => (file, cb) => { const toHTML = (exports.toHTML = (file, options) => `<!DOCTYPE html>
if (!TOKENS_EXT.test(file.path)) return cb(null); <html style="${options.override}">
${head(file.path, options)}
// TODO: meta: language, description, canonical
file.contents = Buffer.from(`<!DOCTYPE html>
<html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">${
options.css
? `\n<meta name="color-scheme" content="dark light">\n<style>${fs.readFileSync(
path.resolve(__dirname, "./gmi.min.css"),
"utf8"
)}</style>\n<style>${fs.readFileSync(
path.resolve(path.dirname(file.path), "./gmi.css"),
"utf8"
)}</style>`
: "<style>p,a,pre,h1,h2,h3,ul,blockquote,img,audio,video{display:block;max-width:100%;margin:0;padding:0;overflow-wrap:anywhere;}</style>"
}
<body> <body>
${toHTML(JSON.parse(file.contents.toString("utf8")), options)} ${gemtext(JSON.parse(file.contents.toString("utf8")), options)}
</body> </body>
</html> </html>
`); `);
module.exports = (options) => (file, cb) => {
if (!TOKENS_EXT.test(file.path)) return cb(null);
file.contents = Buffer.from(toHTML(file, options));
file.path = file.path.replace(TOKENS_EXT, ".html"); file.path = file.path.replace(TOKENS_EXT, ".html");
if (!options.silent) console.log(file.path); if (options.verbose) console.log(file.path);
return cb(null, file); return cb(null, file);
}; };
function toHTML(tokens, options) { // TODO: meta: language, description, canonical
function head(file, options) {
return `<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">${
!options.css
? BASE_STYLE
: `\n<meta name="color-scheme" content="dark light">\n<style>${fs.readFileSync(
path.resolve(__dirname, "./gmi.min.css"),
"utf8"
)}</style>`
}
</head>
`;
}
function gemtext(tokens, options) {
let body = []; let body = [];
let cursor = tokens.shift(); let cursor = tokens.shift();
@ -83,3 +88,8 @@ function line(
if (quote) return `<blockquote>${quote}</blockquote>`; if (quote) return `<blockquote>${quote}</blockquote>`;
return `<p><br></p>`; return `<p><br></p>`;
} }
function omit(key, obj) {
const { [key]: omitted, ...rest } = obj;
return rest;
}