refactor
This commit is contained in:
parent
54280ba8d3
commit
cce9b22502
2
Makefile
2
Makefile
|
@ -18,6 +18,6 @@ example/test.html: example/test.gmi
|
|||
./test.sh
|
||||
|
||||
clean:
|
||||
rm -rf gmi.min.* gmi-web.1 node_modules
|
||||
rm -rf gmi.min.* gmi-web.1 gmi.css.5 node_modules
|
||||
|
||||
.PHONY: clean format
|
||||
|
|
8
cli.js
8
cli.js
|
@ -2,8 +2,7 @@
|
|||
import path from "path";
|
||||
import fs from "vinyl-fs";
|
||||
import map from "map-stream";
|
||||
import tokenize from "./tokenize.js";
|
||||
import toHTML from "./to-html.js";
|
||||
import toHTML from "./gmi.html.js";
|
||||
import yargs from "yargs";
|
||||
|
||||
// TODO: automatically pull these in from gmi.css (also for gmi.css(5))
|
||||
|
@ -69,19 +68,18 @@ yargs(process.argv.slice(2))
|
|||
"gmi.css is included by default. Use --no-css for the bare minimum <style>",
|
||||
}),
|
||||
(argv) => {
|
||||
const override = GMI_CSS_VARS.reduce((style, key) => {
|
||||
const styles = GMI_CSS_VARS.reduce((style, key) => {
|
||||
if (argv[key]) {
|
||||
style += `--${key}: ${argv[key]};`;
|
||||
}
|
||||
return style;
|
||||
}, "");
|
||||
fs.src(argv.files)
|
||||
.pipe(map(tokenize))
|
||||
.pipe(
|
||||
map(
|
||||
toHTML({
|
||||
...argv,
|
||||
override,
|
||||
styles,
|
||||
})
|
||||
)
|
||||
)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "gmi-web" "1" "2021-02-02" "1.0.7-rc.1"
|
||||
.TH "gmi-web" "1" "2021-02-10" "1.0.7-rc.1"
|
||||
.P
|
||||
.SH NAME
|
||||
.P
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "gmi.css" "5" "2021-02-02" "1.0.7-rc.1"
|
||||
.TH "gmi.css" "5" "2021-02-10" "1.0.7-rc.1"
|
||||
.P
|
||||
.SH NAME
|
||||
.P
|
||||
|
|
|
@ -2,84 +2,94 @@ import fs from "fs";
|
|||
import path from "path";
|
||||
import escape from "escape-html";
|
||||
|
||||
const TOKENS_EXT = /\.tokens\.json$/;
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats
|
||||
export const IMG_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)$/;
|
||||
export const BASE_STYLE =
|
||||
"<style>p,a,pre,h1,h2,h3,ul,blockquote,img,audio,video{display:block;max-width:100%;margin:0;padding:0;overflow-wrap:anywhere;}</style>";
|
||||
export const GMI_REGEX = /^((=>\s?(?<href>[^\s]+)(\s(?<title>.+))?)|(?<pre>```\s?(?<alt>.+)?)|(###\s?(?<h3>.+))|(##\s?(?<h2>.+))|(#\s?(?<h1>.+))|(\*\s?(?<li>.+))|(>\s?(?<quote>.+))|(?<text>(.+)?))$/;
|
||||
|
||||
export function html(file, options) {
|
||||
const tokens = file.contents
|
||||
.toString("utf8")
|
||||
.split("\n")
|
||||
.map((line) => GMI_REGEX.exec(line).groups);
|
||||
|
||||
if (options.body) return body(tokens, options);
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="${options.language}" style="${options.styles}">
|
||||
<head>${head(
|
||||
Object.assign(options, {
|
||||
title: tokens[0].h1,
|
||||
charset: "utf-8",
|
||||
})
|
||||
)}</head>
|
||||
<body>${body(tokens, options)}</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
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 const GMI_CSS = fs.readFileSync(
|
||||
path.resolve(path.dirname(new URL(import.meta.url).pathname), "./gmi.min.css"),
|
||||
// TODO import.meta.resolve is supposed to accomplish this without "path"
|
||||
path.resolve(
|
||||
path.dirname(new URL(import.meta.url).pathname),
|
||||
"./gmi.min.css"
|
||||
),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
export default (options) => (file, cb) => {
|
||||
if (!TOKENS_EXT.test(file.path)) return cb(null);
|
||||
file.contents = Buffer.from(toHTML(file, options));
|
||||
file.path = file.path.replace(TOKENS_EXT, ".html");
|
||||
if (options.verbose) console.log(file.path);
|
||||
return cb(null, file);
|
||||
};
|
||||
|
||||
export function toHTML (file, options) {
|
||||
const tokens = JSON.parse(file.contents.toString("utf8"));
|
||||
const title = tokens[0].h1;
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="${options.language}" style="${options.override}">
|
||||
${head(file.path, title, options)}<body>
|
||||
${gemtext(tokens, options)}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
};
|
||||
|
||||
export function head(file, title, options) {
|
||||
return `<head>
|
||||
<meta charset="utf-8">
|
||||
export function head(options) {
|
||||
return `
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta charset="${options.charset}">
|
||||
<meta language="${options.language}">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">${
|
||||
<title>${options.title}</title>${
|
||||
!options.description
|
||||
? ""
|
||||
: `<meta name="description" content="${options.description}}">`
|
||||
}${
|
||||
!options.css
|
||||
? BASE_STYLE
|
||||
? `<style>${BASE_CSS}</style>`
|
||||
: `\n<meta name="color-scheme" content="dark light">\n<style>${GMI_CSS}</style>`
|
||||
}${
|
||||
!options.canonical
|
||||
? ""
|
||||
: `<link rel="canonical" href="${options.canonical}}">`
|
||||
}
|
||||
<title>${title}</title>
|
||||
</head>
|
||||
`;
|
||||
}
|
||||
|
||||
export function gemtext(tokens, options) {
|
||||
let body = [];
|
||||
export function body(tokens, options) {
|
||||
let lines = [];
|
||||
|
||||
let cursor = tokens.shift();
|
||||
while (tokens.length) {
|
||||
if (cursor.pre) {
|
||||
body.push(`<pre${cursor.alt ? ` title="${cursor.alt}"` : ""}>`);
|
||||
lines.push(`<pre${cursor.alt ? ` title="${cursor.alt}"` : ""}>`);
|
||||
const closing = tokens.findIndex((token) => token.pre);
|
||||
body = body.concat(tokens.slice(0, closing).map(({ text }) => text));
|
||||
body.push("</pre>");
|
||||
lines = lines.concat(tokens.slice(0, closing).map(({ text }) => text));
|
||||
lines.push("</pre>");
|
||||
tokens = tokens.slice(closing + 1);
|
||||
} else if (cursor.li) {
|
||||
body.push(`<ul>`);
|
||||
lines.push(`<ul>`);
|
||||
const closing = tokens.findIndex((token) => !token.li);
|
||||
body = body
|
||||
lines = lines
|
||||
.concat([line(cursor)])
|
||||
.concat(tokens.slice(0, closing).map(line));
|
||||
body.push("</ul>");
|
||||
lines.push("</ul>");
|
||||
tokens = tokens.slice(closing);
|
||||
} else {
|
||||
body.push(line(cursor, options));
|
||||
lines.push(line(cursor, options));
|
||||
}
|
||||
cursor = tokens.shift();
|
||||
}
|
||||
|
||||
return body.join("\n");
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
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(
|
||||
{ text, href, title, pre, alt, h1, h2, h3, li, quote },
|
||||
{ images, audio, video } = {}
|
||||
|
@ -87,7 +97,7 @@ function line(
|
|||
if (text) return `<p>${escape(text)}</p>`;
|
||||
if (href) {
|
||||
const titleProp = title ? ` title="${title}"` : "";
|
||||
if (images && IMG_EXT.test(href)) return `<img src="${href}"${titleProp}/>`;
|
||||
if (images && IMAGE_EXT.test(href)) return `<img src="${href}"${titleProp}/>`;
|
||||
if (audio && AUDIO_EXT.test(href))
|
||||
return `<audio controls src="${href}"${titleProp}></audio>`;
|
||||
if (video && VIDEO_EXT.test(href))
|
||||
|
@ -102,3 +112,13 @@ function line(
|
|||
if (quote) return `<blockquote>${escape(quote)}</blockquote>`;
|
||||
return `<p><br></p>`;
|
||||
}
|
||||
|
||||
export const GMI_EXT = /\.gmi$/;
|
||||
|
||||
export default (options) => (file, cb) => {
|
||||
if (!GMI_EXT.test(file.path)) return cb(null);
|
||||
file.contents = Buffer.from(html(file, options));
|
||||
file.path = file.path.replace(GMI_EXT, ".html");
|
||||
if (options.verbose) console.log(file.path);
|
||||
return cb(null, file);
|
||||
};
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "gmi-web-cli",
|
||||
"version": "1.0.5-rc.1",
|
||||
"version": "1.0.7-rc.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
14
tokenize.js
14
tokenize.js
|
@ -1,14 +0,0 @@
|
|||
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_EXT = /\.gmi$/;
|
||||
export const tokenize = (gemtext) =>
|
||||
JSON.stringify(
|
||||
gemtext.split("\n").map((line) => GMI_REGEX.exec(line).groups),
|
||||
null,
|
||||
2
|
||||
);
|
||||
export default (file, cb) => {
|
||||
if (!GMI_EXT.test(file.path)) return cb(null, file);
|
||||
file.contents = Buffer.from(tokenize(file.contents.toString("utf8")));
|
||||
file.path = file.path.replace(GMI_EXT, ".tokens.json");
|
||||
cb(null, file);
|
||||
};
|
Loading…
Reference in a new issue