From 77e4ecb2fbb2b1d3ab77976b6403b86dc721508b Mon Sep 17 00:00:00 2001 From: Talon Poole Date: Mon, 15 Feb 2021 20:56:41 +0000 Subject: [PATCH] update --css --- cli.js | 122 +++++++++++++++++----------------------------- css.js | 48 ++++++++++++++++++ gmi-web.1 | 28 +++++------ gmi-web.1.scd | 28 +++++------ html.js | 17 ++----- package-lock.json | 29 +++++++++++ package.json | 1 + test.css | 3 ++ 8 files changed, 158 insertions(+), 118 deletions(-) create mode 100644 css.js create mode 100644 test.css diff --git a/cli.js b/cli.js index 0152225..f848ec4 100755 --- a/cli.js +++ b/cli.js @@ -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..]"); diff --git a/css.js b/css.js new file mode 100644 index 0000000..7f701d0 --- /dev/null +++ b/css.js @@ -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); +} diff --git a/gmi-web.1 b/gmi-web.1 index 8fa960c..2dabe5a 100644 --- a/gmi-web.1 +++ b/gmi-web.1 @@ -48,16 +48,10 @@ be used. Use \fB--author\fR \fINAME\fR to set the author 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 -.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 . Up-to-date sources can be diff --git a/gmi-web.1.scd b/gmi-web.1.scd index a8d2191..b6beacb 100644 --- a/gmi-web.1.scd +++ b/gmi-web.1.scd @@ -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 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

. - -*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 . Up-to-date sources can be diff --git a/html.js b/html.js index 80b3e0c..3603b4b 100644 --- a/html.js +++ b/html.js @@ -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 ` - -${options.css !== "" ? `` : ""} -${options.css !== "" ? `` : ""} +${ + options.css && options.css !== "" + ? `` + : "" + }${options.css && options.css !== "" ? `` : ""} ${options.title}${ !options.author ? "" : `` }${ @@ -70,17 +69,11 @@ function line( return ``; } if (audio && matchesExt(href, audio)) { - if (css === "") { - return `

`; - } return ``; } if (video && matchesExt(href, video)) return ``; - if (css === "") { - return `

${title ? escape(title) : href}

`; - } return `${title ? escape(title) : href}`; } diff --git a/package-lock.json b/package-lock.json index e9ca3e1..bba6095 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 3cf379f..3d771e4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test.css b/test.css new file mode 100644 index 0000000..ce7da04 --- /dev/null +++ b/test.css @@ -0,0 +1,3 @@ +p { + color: blue; +}