refactor
This commit is contained in:
parent
b1d2f7140b
commit
e5bb1fa77c
57
cli.js
57
cli.js
|
@ -2,10 +2,9 @@
|
||||||
import { readFileSync, existsSync } from "fs";
|
import { readFileSync, existsSync } from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "vinyl-fs";
|
import fs from "vinyl-fs";
|
||||||
import map from "map-stream";
|
|
||||||
import yargs from "yargs";
|
import yargs from "yargs";
|
||||||
import * as CSS from "./css.js";
|
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))
|
const cli = yargs(process.argv.slice(2))
|
||||||
.config("config", function (path) {
|
.config("config", function (path) {
|
||||||
|
@ -47,12 +46,6 @@ const cli = yargs(process.argv.slice(2))
|
||||||
default: "full",
|
default: "full",
|
||||||
requiresArg: true,
|
requiresArg: true,
|
||||||
},
|
},
|
||||||
charset: {
|
|
||||||
type: "string",
|
|
||||||
hidden: true,
|
|
||||||
default: "utf-8",
|
|
||||||
requiresArg: true,
|
|
||||||
},
|
|
||||||
image: {
|
image: {
|
||||||
type: "array",
|
type: "array",
|
||||||
requiresArg: true,
|
requiresArg: true,
|
||||||
|
@ -65,37 +58,37 @@ const cli = yargs(process.argv.slice(2))
|
||||||
type: "array",
|
type: "array",
|
||||||
requiresArg: true,
|
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);
|
const CSS_VARS = CSS.rootVariables(CSS.FULL);
|
||||||
Object.keys(CSS_VARS).map((key) => {
|
Object.keys(CSS_VARS).map((key) => {
|
||||||
cli.option(key, { default: CSS_VARS[key] });
|
cli.option(key, { default: CSS_VARS[key] });
|
||||||
|
cli.option(key, "core");
|
||||||
|
cli.option(key, "none");
|
||||||
return key;
|
return key;
|
||||||
});
|
});
|
||||||
|
cli.group(Object.keys(CSS_VARS), "CSS:");
|
||||||
|
|
||||||
const argv = cli
|
const argv = cli
|
||||||
.conflicts("author", "body")
|
.conflicts("author", "body")
|
||||||
.conflicts("descriptions", "body")
|
.conflicts("descriptions", "body")
|
||||||
.conflicts("html", "body")
|
.conflicts("html", "body")
|
||||||
.group(["html", "body"], "Core:")
|
.group(["html", "body"], "Core:")
|
||||||
.group(["author", "descriptions", "css", "dir"], "HTML:")
|
.group(["author", "descriptions", "css", "mode", "dir"], "HTML:")
|
||||||
.group(["image", "audio", "video"], "Inline Media:")
|
.group(["image", "audio", "video"], "Inline Media:")
|
||||||
.group(Object.keys(CSS_VARS), "CSS:")
|
|
||||||
.alias("html", "language")
|
.alias("html", "language")
|
||||||
.alias("html", "lang")
|
.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)
|
.showHelpOnFail(true)
|
||||||
.help().argv;
|
.help().argv;
|
||||||
|
|
||||||
|
@ -105,13 +98,6 @@ if (!argv.html && !argv.body) {
|
||||||
cli.exit(1);
|
cli.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let styles = Object.keys(CSS_VARS).reduce((style, key) => {
|
|
||||||
if (argv[key]) {
|
|
||||||
style += `--${key}: ${argv[key]};`;
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
}, "");
|
|
||||||
|
|
||||||
if (!argv.files) {
|
if (!argv.files) {
|
||||||
let gemtext;
|
let gemtext;
|
||||||
try {
|
try {
|
||||||
|
@ -121,16 +107,9 @@ if (!argv.files) {
|
||||||
console.error("\nMissing files: pipe from stdin or provide [files..]");
|
console.error("\nMissing files: pipe from stdin or provide [files..]");
|
||||||
cli.exit(1);
|
cli.exit(1);
|
||||||
}
|
}
|
||||||
console.log(html({ contents: gemtext }, argv));
|
console.log(toHTML(gemtext, argv));
|
||||||
} else {
|
} else {
|
||||||
fs.src(argv.files)
|
fs.src(argv.files)
|
||||||
.pipe(
|
.pipe(streamHTML(argv))
|
||||||
map(
|
|
||||||
toHTML({
|
|
||||||
...argv,
|
|
||||||
styles,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.pipe(fs.dest((file) => path.dirname(file.path)));
|
.pipe(fs.dest((file) => path.dirname(file.path)));
|
||||||
}
|
}
|
||||||
|
|
21
gmi-web.1
21
gmi-web.1
|
@ -26,15 +26,9 @@ mobile-friendly fashion!
|
||||||
.P
|
.P
|
||||||
\fB--body\fR
|
\fB--body\fR
|
||||||
.RS 4
|
.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
|
.P
|
||||||
.RE
|
.RE
|
||||||
.nf
|
|
||||||
.RS 4
|
|
||||||
gmi-web --body < doc\&.gmi
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
.P
|
|
||||||
\fB--html\fR \fILANG\fR
|
\fB--html\fR \fILANG\fR
|
||||||
.RS 4
|
.RS 4
|
||||||
Generate a full HTML5 document with the provided \fILANG\fR. \fB--dir\fR can be used
|
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.
|
Use \fB--author\fR \fINAME\fR to set the author <meta> tag on every file.
|
||||||
.P
|
.P
|
||||||
.RE
|
.RE
|
||||||
\fB--css\fR [\fIMODE\fR|\fIFILE\fR|\fICSS\fR]
|
\fB--css\fR [\fIMODE\fR|\fIFILE\fR]
|
||||||
.RS 4
|
.RS 4
|
||||||
By default this will be set to \fBfull\fR enabling a handful of customizable
|
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
|
.P
|
||||||
.RE
|
.RE
|
||||||
.nf
|
.nf
|
||||||
|
@ -64,12 +58,11 @@ gmi-web --html en \\
|
||||||
.P
|
.P
|
||||||
.RS 4
|
.RS 4
|
||||||
Choosing \fBcore\fR will use just what is needed to fix vertical layout issues
|
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
|
.P
|
||||||
Pointing to a .css \fIFILE\fR or providing a valid \fICSS\fR string will use those
|
Choosing \fBnone\fR will not include any style information including when paired
|
||||||
styles.
|
with \fB--body\fR where it will NOT apply the core inline styles.
|
||||||
.P
|
|
||||||
Choosing \fBnone\fR will not include any style information.
|
|
||||||
.P
|
.P
|
||||||
.RE
|
.RE
|
||||||
\fB[--image|--audio|--video]\fR \fIEXTENSIONS\fR
|
\fB[--image|--audio|--video]\fR \fIEXTENSIONS\fR
|
||||||
|
|
|
@ -18,8 +18,7 @@ mobile-friendly fashion!
|
||||||
# OPTIONS
|
# OPTIONS
|
||||||
|
|
||||||
*--body*
|
*--body*
|
||||||
Generate only the HTML for the lines of the Gemini document. Use *--css* none
|
Generate just the HTML for the lines of the Gemini document.
|
||||||
to turn off the core inline styles.
|
|
||||||
|
|
||||||
*--html* _LANG_
|
*--html* _LANG_
|
||||||
Generate a full HTML5 document with the provided _LANG_. *--dir* can be used
|
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.
|
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
|
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 \\
|
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
|
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
|
Choosing *none* will not include any style information including when paired
|
||||||
styles.
|
with *--body* where it will NOT apply the core inline styles.
|
||||||
|
|
||||||
Choosing *none* will not include any style information.
|
|
||||||
|
|
||||||
*[--image|--audio|--video]* _EXTENSIONS_
|
*[--image|--audio|--video]* _EXTENSIONS_
|
||||||
Include media extensions inline. You can provide multiple extensions per flag
|
Include media extensions inline. You can provide multiple extensions per flag
|
||||||
|
|
50
html.js
50
html.js
|
@ -1,14 +1,15 @@
|
||||||
|
import fs from "fs";
|
||||||
|
import map from "map-stream";
|
||||||
import escape from "escape-html";
|
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>(.+)?))$/;
|
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) =>
|
const truncate = (text, limit) =>
|
||||||
text.length > limit ? `${text.substring(0, limit)}...` : text;
|
text.length > limit ? `${text.substring(0, limit)}...` : text;
|
||||||
export function html(file, options) {
|
|
||||||
const tokens = file.contents
|
export function toHTML(gemtext, options) {
|
||||||
.toString("utf8")
|
const tokens = gemtext.split("\n").map((line) => GMI_REGEX.exec(line).groups);
|
||||||
.split("\n")
|
|
||||||
.map((line) => GMI_REGEX.exec(line).groups);
|
|
||||||
|
|
||||||
let description = options.descriptions
|
let description = options.descriptions
|
||||||
? tokens.find((token) => {
|
? tokens.find((token) => {
|
||||||
|
@ -36,11 +37,12 @@ ${body(tokens, options)}
|
||||||
export function head(options) {
|
export function head(options) {
|
||||||
return `
|
return `
|
||||||
<meta charset="${options.charset}">
|
<meta charset="${options.charset}">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">${
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
options.css && options.css !== ""
|
${
|
||||||
? `<meta name="color-scheme" content="dark light">`
|
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>${
|
<title>${options.title}</title>${
|
||||||
!options.author ? "" : `<meta name="author" content="${options.author}">`
|
!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(
|
function line(
|
||||||
{ text, href, title, pre, alt, h1, h2, h3, li, quote },
|
{ text, href, title, pre, alt, h1, h2, h3, li, quote },
|
||||||
{ image, audio, video, css, body } = {}
|
{ image, audio, video, css, body } = {}
|
||||||
|
@ -63,7 +78,8 @@ function line(
|
||||||
|
|
||||||
if (href) {
|
if (href) {
|
||||||
const titleProp = title ? ` title="${title}"` : "";
|
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) =>
|
const matchesExt = (url, exts) =>
|
||||||
exts.some((ext) => new RegExp(`\.${ext}$`).test(url));
|
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 `<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>`;
|
if (h1) return `<h1>${escape(h1)}</h1>`;
|
||||||
|
@ -120,10 +138,14 @@ export function body(tokens, options) {
|
||||||
|
|
||||||
export const GMI_EXT = /\.gmi$/;
|
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);
|
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");
|
file.path = file.path.replace(GMI_EXT, ".html");
|
||||||
if (options.verbose) console.log(file.path);
|
if (options.verbose) console.log(file.path);
|
||||||
return cb(null, file);
|
return cb(null, file);
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue