update --css

This commit is contained in:
Talon Poole 2021-02-15 20:56:41 +00:00
parent 74e84b1ab4
commit 77e4ecb2fb
8 changed files with 158 additions and 118 deletions

122
cli.js
View file

@ -1,24 +1,24 @@
#!/usr/bin/env node
import { readFileSync } from "fs";
import { readFileSync, existsSync } from "fs";
import path from "path";
import fs from "vinyl-fs";
import map from "map-stream";
import yargs from "yargs";
import CleanCSS from "clean-css";
import toHTML, { BASE_CSS, html } from "./html.js";
import * as CSS from "./css.js";
import toHTML, { html } from "./html.js";
const cli = yargs(process.argv.slice(2))
.config("config", function (path) {
return JSON.parse(readFileSync(path));
return JSON.parse(readFileSync(path), "utf-8");
})
.scriptName("gmi-web")
.command("$0 [files..]", "Convert text/gemini to text/html.", (yargs) =>
yargs
.example("$0 --body < ~/my-capsule/index.gmi")
.example("$0 --html en $(find ~/my-capsule -name '*.gmi')")
.example(
"$0 --foreground '#000000' --background '#EEEEEE' --html en < doc.gmi"
)
.example("$0 --body < ~/my-capsule/index.gmi")
.example("$0 --image jpg --audio mp3 --image png --body < doc.gmi")
.epilog("See the gmi-web(1) man page for more information")
)
@ -44,8 +44,7 @@ const cli = yargs(process.argv.slice(2))
type: "boolean",
},
css: {
choices: ["gmi.css", "base", "none"],
default: "gmi.css",
default: "full",
requiresArg: true,
},
charset: {
@ -54,99 +53,70 @@ const cli = yargs(process.argv.slice(2))
default: "utf-8",
requiresArg: true,
},
image: {
type: "array",
requiresArg: true,
},
audio: {
type: "array",
requiresArg: true,
},
video: {
type: "array",
requiresArg: true,
},
});
cli.options({
image: {
type: "array",
requiresArg: true,
},
audio: {
type: "array",
requiresArg: true,
},
video: {
type: "array",
requiresArg: true,
},
});
// TODO: automatically pull these in from gmi.css (also for gmi.css(5))
const GMI_CSS_VARS = [
"body-width",
"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",
].map((key) => {
cli.option(key);
const CSS_VARS = CSS.rootVariables(CSS.FULL);
Object.keys(CSS_VARS).map((key) => {
cli.option(key, { default: CSS_VARS[key] });
cli.conflicts(key, "body");
return key;
});
const argv = cli
.conflicts("author", "body")
.conflicts("description", "body")
.conflicts("descriptions", "body")
.conflicts("html", "body")
.group(["html", "body"], "Core:")
.group(["author", "description", "css", "dir"], "HTML:")
.group(["author", "descriptions", "css", "dir"], "HTML:")
.group(["image", "audio", "video"], "Inline Media:")
.group(GMI_CSS_VARS, "gmi.css:")
.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;
let styles = "";
if (argv.css === "gmi.css") {
styles = GMI_CSS_VARS.reduce((style, key) => {
if (argv[key]) {
style += `--${key}: ${argv[key]};`;
}
return style;
}, styles);
argv.css = new CleanCSS().minify(
readFileSync(
// TODO import.meta.resolve is supposed to accomplish this without "path"
path.resolve(
path.dirname(new URL(import.meta.url).pathname),
"./gmi.css"
),
"utf-8"
)
).styles;
} else if (argv.css === "base") {
argv.css = BASE_CSS;
} else {
argv.css = "";
}
if (!argv.lang && !argv.body) {
if (!argv.html && !argv.body) {
cli.showHelp();
console.error(`\nMissing required argument: --html or --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 {
gemtext = readFileSync(process.stdin.fd);
gemtext = readFileSync(process.stdin.fd, "utf-8");
} catch (e) {
cli.showHelp();
console.error("\nMissing files: pipe from stdin or provide [files..]");

48
css.js Normal file
View file

@ -0,0 +1,48 @@
import { readFileSync } from "fs";
import path from "path";
import { stringify, parse } from "css";
export { stringify, parse };
// TODO import.meta.resolve is supposed to accomplish this without "path"
const GMI_CSS = path.resolve(
path.dirname(new URL(import.meta.url).pathname),
"./gmi.css"
);
export const CORE = parse(`p,
a,
pre,
h1,
h2,
h3,
ul,
blockquote,
img,
audio,
video {
display: block;
margin: 0;
padding: 0;
overflow-wrap: anywhere;
max-width: 100%;
}`);
export const FULL = parse(readFileSync(GMI_CSS, "utf-8"));
export function rootVariables({ stylesheet }) {
return stylesheet.rules
.find(
({ type, selectors }) => type === "rule" && selectors.includes(":root")
)
.declarations.reduce(
(obj, { property, value }) =>
!/^\-\-/.test(property)
? obj
: Object.assign(obj, { [property.replace("--", "")]: value }),
{}
);
}
export function resolve(arg, options) {
return stringify(parse(readFileSync(path.resolve(arg), "utf-8")), options);
}

View file

@ -48,16 +48,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
\fB--css\fR [\fIMODE\fR|\fIFILE\fR|\fICSS\fR]
.RS 4
By default this will be set to \fBgmi.css\fR. Choosing \fBbase\fR will use just what
is needed to fix issues with CSS 2.1's Normal Flow. Choosing \fBnone\fR will not
include any style information and inline elements will be wrapped in <p>.
.P
.RE
\fBgmi.css\fR
.RS 4
See --help or gmi.css(5) for the complete list of customizable styles.
By default this will be set to \fBfull\fR enabling a handful of customizable
variables to be set. See --help for the complete list.
.P
.RE
.nf
@ -68,7 +62,17 @@ gmi-web --html en \\
.fi
.RE
.P
\fB[--image|--audio|--video]\fR \fIEXENSIONS\fR
.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.
.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.
.P
.RE
\fB[--image|--audio|--video]\fR \fIEXTENSIONS\fR
.RS 4
Include media extensions inline. You can provide multiple extensions per flag
or multiple flags per extension.
@ -89,10 +93,6 @@ All the options documented here and by \fB--help\fR may be captured in a .json
file and passed to \fB--config\fR instead of as flags on the command-line.
.P
.RE
.SH SEE ALSO
.P
\fBgmi.css\fR(5)
.P
.SH AUTHORS
.P
Maintained by Talon Poole <code@talon.computer>. Up-to-date sources can be

View file

@ -20,10 +20,6 @@ mobile-friendly fashion!
*--body*
Generate only the HTML for the lines of the Gemini document.
```
gmi-web --body < doc.gmi
```
*--html* _LANG_
Generate a full HTML5 document with the provided _LANG_. *--dir* can be used
to adjust the document text direction from "ltr" to "rtl".
@ -35,13 +31,9 @@ gmi-web --body < doc.gmi
Use *--author* _NAME_ to set the author <meta> tag on every file.
*--css* _MODE_
By default this will be set to *gmi.css*. Choosing *base* will use just what
is needed to fix issues with CSS 2.1's Normal Flow. Choosing *none* will not
include any style information and inline elements will be wrapped in <p>.
*gmi.css*
See --help or gmi.css(5) for the complete list of customizable styles.
*--css* [_MODE_|_FILE_|_CSS_]
By default this will be set to *full* enabling a handful of customizable
variables to be set. See --help for the complete list.
```
gmi-web --html en \\
@ -49,7 +41,15 @@ gmi-web --html en \\
--background "#9EEBCF" < doc.gmi
```
*[--image|--audio|--video]* _EXENSIONS_
Choosing *core* will use just what is needed to fix vertical layout issues
with CSS 2.1's Normal Flow and inline elements.
Pointing to a .css _FILE_ or providing a valid _CSS_ string will use those
styles.
Choosing *none* will not include any style information.
*[--image|--audio|--video]* _EXTENSIONS_
Include media extensions inline. You can provide multiple extensions per flag
or multiple flags per extension.
@ -64,10 +64,6 @@ gmi-web --html en \\
All the options documented here and by *--help* may be captured in a .json
file and passed to *--config* instead of as flags on the command-line.
# SEE ALSO
*gmi.css*(5)
# AUTHORS
Maintained by Talon Poole <code@talon.computer>. Up-to-date sources can be

17
html.js
View file

@ -33,15 +33,14 @@ ${body(tokens, options)}
`;
}
export const BASE_CSS =
"p,a,pre,h1,h2,h3,ul,blockquote,img,audio,video{display:block;max-width:100%;margin:0;padding:0;overflow-wrap:anywhere;}";
export function head(options) {
return `
<meta charset="${options.charset}">
<meta name="viewport" content="width=device-width,initial-scale=1">
${options.css !== "" ? `<meta name="color-scheme" content="dark light">` : ""}
${options.css !== "" ? `<style>${options.css}</style>` : ""}
<meta name="viewport" content="width=device-width,initial-scale=1">${
options.css && options.css !== ""
? `<meta name="color-scheme" content="dark light">`
: ""
}${options.css && options.css !== "" ? `<style>${options.css}</style>` : ""}
<title>${options.title}</title>${
!options.author ? "" : `<meta name="author" content="${options.author}">`
}${
@ -70,17 +69,11 @@ function line(
return `<img src="${href}"${titleProp}/>`;
}
if (audio && matchesExt(href, audio)) {
if (css === "") {
return `<p><audio controls src="${href}"${titleProp}></audio><p>`;
}
return `<audio controls src="${href}"${titleProp}></audio>`;
}
if (video && matchesExt(href, video))
return `<video controls src="${href}"${titleProp}/></video>`;
if (css === "") {
return `<p><a href="${href}">${title ? escape(title) : href}</a></p>`;
}
return `<a href="${href}">${title ? escape(title) : href}</a>`;
}

29
package-lock.json generated
View file

@ -25,6 +25,11 @@
"buffer-equal": "^1.0.0"
}
},
"atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -127,6 +132,21 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"css": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
"integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==",
"requires": {
"inherits": "^2.0.4",
"source-map": "^0.6.1",
"source-map-resolve": "^0.6.0"
}
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
},
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@ -549,6 +569,15 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-resolve": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
"integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==",
"requires": {
"atob": "^2.1.2",
"decode-uri-component": "^0.2.0"
}
},
"stream-shift": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",

View file

@ -25,6 +25,7 @@
},
"dependencies": {
"clean-css": "^5.0.1",
"css": "^3.0.0",
"escape-html": "^1.0.3",
"map-stream": "0.0.7",
"vinyl-fs": "^3.0.3",

3
test.css Normal file
View file

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