This commit is contained in:
Talon Poole 2021-02-15 22:31:08 +00:00
parent b1d2f7140b
commit e5bb1fa77c
5 changed files with 73 additions and 84 deletions

57
cli.js
View file

@ -2,10 +2,9 @@
import { readFileSync, existsSync } from "fs";
import path from "path";
import fs from "vinyl-fs";
import map from "map-stream";
import yargs from "yargs";
import * as CSS from "./css.js";
import toHTML, { html } from "./html.js";
import { streamHTML, toHTML } from "./html.js";
const cli = yargs(process.argv.slice(2))
.config("config", function (path) {
@ -47,12 +46,6 @@ const cli = yargs(process.argv.slice(2))
default: "full",
requiresArg: true,
},
charset: {
type: "string",
hidden: true,
default: "utf-8",
requiresArg: true,
},
image: {
type: "array",
requiresArg: true,
@ -65,37 +58,37 @@ const cli = yargs(process.argv.slice(2))
type: "array",
requiresArg: true,
},
charset: {
type: "string",
hidden: true,
default: "utf-8",
requiresArg: true,
},
modes: {
type: "boolean",
hidden: true,
default: false,
},
});
const CSS_VARS = CSS.rootVariables(CSS.FULL);
Object.keys(CSS_VARS).map((key) => {
cli.option(key, { default: CSS_VARS[key] });
cli.option(key, "core");
cli.option(key, "none");
return key;
});
cli.group(Object.keys(CSS_VARS), "CSS:");
const argv = cli
.conflicts("author", "body")
.conflicts("descriptions", "body")
.conflicts("html", "body")
.group(["html", "body"], "Core:")
.group(["author", "descriptions", "css", "dir"], "HTML:")
.group(["author", "descriptions", "css", "mode", "dir"], "HTML:")
.group(["image", "audio", "video"], "Inline Media:")
.group(Object.keys(CSS_VARS), "CSS:")
.alias("html", "language")
.alias("html", "lang")
.coerce("css", (arg) => {
if (arg === "full") {
return CSS.stringify(CSS.FULL, { compress: true });
} else if (arg === "core") {
return CSS.stringify(CSS.CORE, { compress: true });
} else if (arg !== "none" && arg !== "") {
if (existsSync(path.resolve(arg))) {
return CSS.resolve(arg, { compress: true });
} else {
return CSS.parse(arg);
}
}
})
.showHelpOnFail(true)
.help().argv;
@ -105,13 +98,6 @@ if (!argv.html && !argv.body) {
cli.exit(1);
}
let styles = Object.keys(CSS_VARS).reduce((style, key) => {
if (argv[key]) {
style += `--${key}: ${argv[key]};`;
}
return style;
}, "");
if (!argv.files) {
let gemtext;
try {
@ -121,16 +107,9 @@ if (!argv.files) {
console.error("\nMissing files: pipe from stdin or provide [files..]");
cli.exit(1);
}
console.log(html({ contents: gemtext }, argv));
console.log(toHTML(gemtext, argv));
} else {
fs.src(argv.files)
.pipe(
map(
toHTML({
...argv,
styles,
})
)
)
.pipe(streamHTML(argv))
.pipe(fs.dest((file) => path.dirname(file.path)));
}

View file

@ -26,15 +26,9 @@ mobile-friendly fashion!
.P
\fB--body\fR
.RS 4
Generate only the HTML for the lines of the Gemini document.
Generate just the HTML for the lines of the Gemini document.
.P
.RE
.nf
.RS 4
gmi-web --body < doc\&.gmi
.fi
.RE
.P
\fB--html\fR \fILANG\fR
.RS 4
Generate a full HTML5 document with the provided \fILANG\fR. \fB--dir\fR can be used
@ -48,10 +42,10 @@ be used.
Use \fB--author\fR \fINAME\fR to set the author <meta> tag on every file.
.P
.RE
\fB--css\fR [\fIMODE\fR|\fIFILE\fR|\fICSS\fR]
\fB--css\fR [\fIMODE\fR|\fIFILE\fR]
.RS 4
By default this will be set to \fBfull\fR enabling a handful of customizable
variables to be set. See --help for the complete list.
variables. See \fB--help\fR for the complete list.
.P
.RE
.nf
@ -64,12 +58,11 @@ gmi-web --html en \\
.P
.RS 4
Choosing \fBcore\fR will use just what is needed to fix vertical layout issues
with CSS 2.1's Normal Flow and inline elements.
with CSS 2.1's Normal Flow and inline elements. Pointing to a .css \fIFILE\fR
will use those styles.
.P
Pointing to a .css \fIFILE\fR or providing a valid \fICSS\fR string will use those
styles.
.P
Choosing \fBnone\fR will not include any style information.
Choosing \fBnone\fR will not include any style information including when paired
with \fB--body\fR where it will NOT apply the core inline styles.
.P
.RE
\fB[--image|--audio|--video]\fR \fIEXTENSIONS\fR

View file

@ -18,8 +18,7 @@ mobile-friendly fashion!
# OPTIONS
*--body*
Generate only the HTML for the lines of the Gemini document. Use *--css* none
to turn off the core inline styles.
Generate just the HTML for the lines of the Gemini document.
*--html* _LANG_
Generate a full HTML5 document with the provided _LANG_. *--dir* can be used
@ -32,9 +31,9 @@ mobile-friendly fashion!
Use *--author* _NAME_ to set the author <meta> tag on every file.
*--css* [_MODE_|_FILE_|_CSS_]
*--css* [_MODE_|_FILE_]
By default this will be set to *full* enabling a handful of customizable
variables to be set. See --help for the complete list.
variables. See *--help* for the complete list.
```
gmi-web --html en \\
@ -43,12 +42,11 @@ gmi-web --html en \\
```
Choosing *core* will use just what is needed to fix vertical layout issues
with CSS 2.1's Normal Flow and inline elements.
with CSS 2.1's Normal Flow and inline elements. Pointing to a .css _FILE_
will use those styles.
Pointing to a .css _FILE_ or providing a valid _CSS_ string will use those
styles.
Choosing *none* will not include any style information.
Choosing *none* will not include any style information including when paired
with *--body* where it will NOT apply the core inline styles.
*[--image|--audio|--video]* _EXTENSIONS_
Include media extensions inline. You can provide multiple extensions per flag

50
html.js
View file

@ -1,14 +1,15 @@
import fs from "fs";
import map from "map-stream";
import escape from "escape-html";
import * as CSS from "./css.js";
export const GMI_REGEX = /^((=>\s?(?<href>[^\s]+)(\s(?<title>.+))?)|(?<pre>```\s?(?<alt>.+)?)|(###\s?(?<h3>.+))|(##\s?(?<h2>.+))|(#\s?(?<h1>.+))|(\*\s?(?<li>.+))|(>\s?(?<quote>.+))|(?<text>(.+)?))$/;
const truncate = (text, limit) =>
text.length > limit ? `${text.substring(0, limit)}...` : text;
export function html(file, options) {
const tokens = file.contents
.toString("utf8")
.split("\n")
.map((line) => GMI_REGEX.exec(line).groups);
export function toHTML(gemtext, options) {
const tokens = gemtext.split("\n").map((line) => GMI_REGEX.exec(line).groups);
let description = options.descriptions
? tokens.find((token) => {
@ -36,11 +37,12 @@ ${body(tokens, options)}
export function head(options) {
return `
<meta charset="${options.charset}">
<meta name="viewport" content="width=device-width,initial-scale=1">${
options.css && options.css !== ""
? `<meta name="color-scheme" content="dark light">`
<meta name="viewport" content="width=device-width,initial-scale=1">
${
options.modes || options.css === "full"
? `<meta name="color-scheme" content="dark light">\n`
: ""
}${options.css && options.css !== "" ? `<style>${options.css}</style>` : ""}
}${style(options.css)}
<title>${options.title}</title>${
!options.author ? "" : `<meta name="author" content="${options.author}">`
}${
@ -55,6 +57,19 @@ export function head(options) {
`;
}
export function style(mode) {
if (mode === "none") return "";
if (mode === "core")
return `<style>${CSS.stringify(CSS.CORE, { compress: true })}</style>`;
if (mode === "full")
return `<style>${CSS.stringify(CSS.FULL, { compress: true })}</style>`;
if (fs.existsSync(mode)) {
return `<style>${CSS.resolve(mode, { compress: true })}</style>`;
} else {
throw new Error(`Cannot find file ${mode}.`);
}
}
function line(
{ text, href, title, pre, alt, h1, h2, h3, li, quote },
{ image, audio, video, css, body } = {}
@ -63,7 +78,8 @@ function line(
if (href) {
const titleProp = title ? ` title="${title}"` : "";
const FIX_NORMAL_FLOW = body && css ? ` style="display: block; max-width: 100%;"` : ""
const FIX_NORMAL_FLOW =
body && css !== "none" ? ` style="display: block; max-width: 100%;"` : "";
const matchesExt = (url, exts) =>
exts.some((ext) => new RegExp(`\.${ext}$`).test(url));
@ -77,7 +93,9 @@ function line(
return `<video controls src="${href}"${titleProp}${FIX_NORMAL_FLOW}/></video>`;
}
return `<a href="${href}"${FIX_NORMAL_FLOW}>${title ? escape(title) : href}</a>`;
return `<a href="${href}"${FIX_NORMAL_FLOW}>${
title ? escape(title) : href
}</a>`;
}
if (h1) return `<h1>${escape(h1)}</h1>`;
@ -120,10 +138,14 @@ export function body(tokens, options) {
export const GMI_EXT = /\.gmi$/;
export default (options) => (file, cb) => {
export function streamHTML(options) {
return map((file, cb) => {
if (!GMI_EXT.test(file.path)) return cb(null);
file.contents = Buffer.from(html(file, options));
file.contents = Buffer.from(
toHTML(file.contents.toString("utf-8"), options)
);
file.path = file.path.replace(GMI_EXT, ".html");
if (options.verbose) console.log(file.path);
return cb(null, file);
};
});
}

View file

@ -1,3 +0,0 @@
p {
color: blue;
}