three --css modes: gmi.css, base and none

when "none" inline tags will be wrapped in <p>

--image, --audio and --video now accept the extensions to inline
  so what's possible to load isn't maintained here

--lang is now referred to as --html
  --html and --body are mutually exclusive
This commit is contained in:
Talon Poole 2021-02-11 23:26:29 +00:00
parent 9184e2c792
commit 2ade53f268
7 changed files with 71 additions and 48 deletions

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
example/

View file

@ -1,4 +1,5 @@
build: format gmi-web.1 gmi.css.5 build: gmi-web.1 gmi.css.5
npm install
gmi-web.1: gmi-web.1.scd gmi-web.1: gmi-web.1.scd
scdoc < $< > $@ scdoc < $< > $@
@ -6,15 +7,10 @@ gmi-web.1: gmi-web.1.scd
gmi.css.5: gmi.css.5.scd gmi.css.5: gmi.css.5.scd
scdoc < $< > $@ scdoc < $< > $@
format:
./node_modules/prettier/bin-prettier.js --write README.md
./node_modules/prettier/bin-prettier.js --write gmi.css
./node_modules/prettier/bin-prettier.js --write *.js !*.min.js
example/test.html: example/test.gmi example/test.html: example/test.gmi
./test.sh ./test.sh
clean: clean:
rm -rf gmi.min.* gmi-web.1 gmi.css.5 node_modules rm -rf gmi-web.1 gmi.css.5 node_modules
.PHONY: clean format .PHONY: clean

62
cli.js
View file

@ -5,33 +5,38 @@ import fs from "vinyl-fs";
import map from "map-stream"; import map from "map-stream";
import yargs from "yargs"; import yargs from "yargs";
import CleanCSS from "clean-css"; import CleanCSS from "clean-css";
import toHTML, { BASE_CSS, html } from "./gmi.html.js"; import toHTML, { BASE_CSS, html } from "./html.js";
const cli = yargs(process.argv.slice(2)) const cli = yargs(process.argv.slice(2))
.scriptName("gmi-web") .scriptName("gmi-web")
.command("$0 [files..]", "Convert text/gemini to text/html.", (yargs) => yargs .command("$0 [files..]", "Convert text/gemini to text/html.", (yargs) =>
//yargs.positional("files") yargs
.example("$0 --html en $(find ~/my-capsule -name '*.gmi')")
.example("$0 --body < ~/my-capsule/index.gmi")
) )
.options({ .options({
language: { html: {
alias: "lang", type: "string",
required: true, requiresArg: true,
}, },
css: { css: {
choices: ["gmi.css", "base", "none"],
default: "gmi.css",
},
body: {
type: "boolean", type: "boolean",
default: true,
}, },
}); });
cli.options({ cli.options({
images: { image: {
type: "boolean", type: "array",
}, },
audio: { audio: {
type: "boolean", type: "array",
}, },
video: { video: {
type: "boolean", type: "array",
}, },
}); });
@ -59,24 +64,24 @@ const GMI_CSS_VARS = [
"sans-serif", "sans-serif",
].map((key) => { ].map((key) => {
cli.option(key); cli.option(key);
cli.conflicts(key, "body");
return key; return key;
}); });
const argv = cli const argv = cli
.group(["language", "css"], "Core:") .conflicts("html", "body")
.group(["html", "css", "body"], "Core:")
.group(GMI_CSS_VARS, "CSS Variables:") .group(GMI_CSS_VARS, "CSS Variables:")
.group(["images", "audio", "video"], "Inline Media:") .group(["images", "audio", "video"], "Inline Media:")
.alias("html", "language")
.alias("html", "lang")
.config() .config()
.example("$0 --lang en $(find ~/my-capsule -name '*.gmi')")
.example("cat ~/my-capsule/index.gmi | $0 --lang en")
.epilog("See the gmi-web(1) man page for more information!") .epilog("See the gmi-web(1) man page for more information!")
.showHelpOnFail(true) .showHelpOnFail(true)
.help().argv; .help().argv;
argv.css = argv.css || BASE_CSS;
let styles = ""; let styles = "";
if (argv.css) { if (argv.css === "gmi.css") {
styles = GMI_CSS_VARS.reduce((style, key) => { styles = GMI_CSS_VARS.reduce((style, key) => {
if (argv[key]) { if (argv[key]) {
style += `--${key}: ${argv[key]};`; style += `--${key}: ${argv[key]};`;
@ -93,19 +98,28 @@ if (argv.css) {
"utf8" "utf8"
) )
).styles; ).styles;
} else if (argv.css === "base") {
argv.css = BASE_CSS;
} else {
argv.css = "";
}
if (!argv.lang && !argv.body) {
cli.showHelp();
console.error(`\nMissing required argument: --html or --body`);
cli.exit(1);
} }
if (!argv.files) { if (!argv.files) {
let gemtext let gemtext;
try { try {
gemtext = readFileSync(process.stdin.fd) gemtext = readFileSync(process.stdin.fd);
} catch (e) { } catch (e) {
console.error("Either send a file to this program from stdin") console.error(`Either send a file to this program from stdin or provide [files..]
console.error("or provide [files..]\n") See --help or read gmi-web(1) for usage details.`);
console.error("See --help or read gmi-web(1) for usage details.") cli.exit(1);
process.exit(1)
} }
console.log(html({ contents: gemtext }, argv)) console.log(html({ contents: gemtext }, argv));
} else { } else {
fs.src(argv.files) fs.src(argv.files)
.pipe( .pipe(

View file

@ -5,7 +5,7 @@
.nh .nh
.ad l .ad l
.\" Begin generated content: .\" Begin generated content:
.TH "gmi-web" "1" "2021-02-10" "1.0.7-rc.1" .TH "gmi-web" "1" "2021-02-11" "1.0.7-rc.1"
.P .P
.SH NAME .SH NAME
.P .P

View file

@ -29,8 +29,12 @@ 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;}"; "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) { export function head(options) {
return ` return `
<meta name="color-scheme" content="dark light"> ${
<style>${options.css}</style> 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"> <meta name="viewport" content="width=device-width,initial-scale=1">
<meta charset="${options.charset}"> <meta charset="${options.charset}">
<meta language="${options.language}"> <meta language="${options.language}">
@ -46,31 +50,41 @@ export function head(options) {
`; `;
} }
export const IMAGE_EXT = /\.(apng|avif|gif|jpg|jpeg|jfif|pjpeg|pjp|png|svg|webp)$/;
export const AUDIO_EXT = /\.(mp3|wav|aac|aacp|mpeg|off|flac)$/;
export const VIDEO_EXT = /\.(mp4|webm)$/;
function line( function line(
{ text, href, title, pre, alt, h1, h2, h3, li, quote }, { text, href, title, pre, alt, h1, h2, h3, li, quote },
{ images, audio, video } = {} { image, audio, video, css } = {}
) { ) {
if (text) return `<p>${escape(text)}</p>`; if (text) return `<p>${escape(text)}</p>`;
const matchesExt = (url, exts) =>
exts.some((ext) => new RegExp(`\.${ext}$`).test(url));
if (href) { if (href) {
const titleProp = title ? ` title="${title}"` : ""; const titleProp = title ? ` title="${title}"` : "";
if (images && IMAGE_EXT.test(href)) if (image && matchesExt(href, image)) {
return `<img src="${href}"${titleProp}/>`; return `<img src="${href}"${titleProp}/>`;
if (audio && AUDIO_EXT.test(href)) }
if (audio && matchesExt(href, audio)) {
if (css === "") {
return `<p><audio controls src="${href}"${titleProp}></audio><p>`;
}
return `<audio controls src="${href}"${titleProp}></audio>`; return `<audio controls src="${href}"${titleProp}></audio>`;
if (video && VIDEO_EXT.test(href)) }
if (video && matchesExt(href, video))
return `<video controls src="${href}"${titleProp}/></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>`; return `<a href="${href}">${title ? escape(title) : href}</a>`;
} }
if (h1) return `<h1>${escape(h1)}</h1>`; if (h1) return `<h1>${escape(h1)}</h1>`;
if (h2) return `<h2>${escape(h2)}</h2>`; if (h2) return `<h2>${escape(h2)}</h2>`;
if (h3) return `<h3>${escape(h3)}</h3>`; if (h3) return `<h3>${escape(h3)}</h3>`;
if (li) return `<li>${escape(li)}</li>`; if (li) return `<li>${escape(li)}</li>`;
if (quote) return `<blockquote>${escape(quote)}</blockquote>`; if (quote) return `<blockquote>${escape(quote)}</blockquote>`;
return `<p><br></p>`; return `<p><br></p>`;
} }

View file

@ -2,7 +2,7 @@
"name": "gmi-web-cli", "name": "gmi-web-cli",
"version": "1.0.7-rc.1", "version": "1.0.7-rc.1",
"description": "A bridge between HTML and Gemini", "description": "A bridge between HTML and Gemini",
"main": "cli.js", "main": "html.js",
"type": "module", "type": "module",
"bin": { "bin": {
"gmi-web": "cli.js" "gmi-web": "cli.js"
@ -23,9 +23,7 @@
"gmi.css.5" "gmi.css.5"
], ],
"scripts": { "scripts": {
"build": "npm install && make", "prepare": "prettier --write ."
"prepare": "make",
"clean": "make clean"
}, },
"dependencies": { "dependencies": {
"clean-css": "^5.0.1", "clean-css": "^5.0.1",

View file

@ -1,5 +1,5 @@
rm -rf "example/test.html" rm -rf "example/test.html"
./cli.js --lang en --images --audio --video "example/*.gmi" ./cli.js --image jpg --audio mp3 --video mp4 --html en "example/*.gmi"
if cmp -s "example/test.html" "example/expected.html"; then if cmp -s "example/test.html" "example/expected.html"; then
printf "PASS: test.html matches expected.html!\n" printf "PASS: test.html matches expected.html!\n"
else else