UNPKG

hackmd-to-html-cli

Version:

A node.js CLI tool for converting HackMD markdown to HTML.

236 lines 10.2 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = __importDefault(require("commander")); const fs_1 = __importDefault(require("fs")); const converter_1 = require("./converter"); const path_1 = __importDefault(require("path")); const https = __importStar(require("https")); const http = __importStar(require("http")); const glob_1 = require("glob"); const node_crypto_1 = require("node:crypto"); const utils_1 = require("./markdown/utils"); const package_json_1 = require("../package.json"); const hash = (0, node_crypto_1.createHash)('sha256'); commander_1.default.program.version(package_json_1.version, '-v, --version', 'output the current version'); commander_1.default.program .requiredOption('-i, --input <files_or_urls...>', 'the path/url of input markdown files') .addOption(new commander_1.default.Option('-d, --dest <dir>', 'the path of output directory (filename is generated automatically)').default('', './output')) .addOption(new commander_1.default.Option('-o, --output <files...>', 'the path of output file (ignored if the flag -d is set)').default('', '""')) .addOption(new commander_1.default.Option('-l, --layout <html_file>', 'specify the layout file').default('', '""')) .addOption(new commander_1.default.Option('-b, --hardBreak', 'use hard break instead of soft break')) .addOption(new commander_1.default.Option('-k, --dark', 'use the dark mode layout (activate only if the -l option is not set)')) .parse(process.argv); const options = commander_1.default.program.opts(); const inputs = options.input; const dest = options.dest === '' ? './output' : options.dest; const outputs = options.dest === '' && options.output !== '' ? options.output : null; const inputLayout = options.layout !== '' ? fs_1.default.readFileSync(options.layout, { encoding: 'utf-8' }) : null; const hardBreak = options.hardBreak; const darkMode = options.dark; function main() { const converter = new converter_1.Converter({ html: true, breaks: !hardBreak, linkify: true, typographer: true }); let errorCounter = 0; let outputsIndex = 0; const outputFilenameSet = new Set(); // load layout const layout = inputLayout !== null && inputLayout !== void 0 ? inputLayout : defaultLayout(darkMode); const isURL = (s) => { try { const url = new URL(s); return url; } catch (_a) { return null; } }; const printError = (fn, e) => { console.error(`❌ #${errorCounter} ${fn}`); console.error(`${e}`); errorCounter++; }; const convertURL = (inputURL, output, res) => { let data = ""; res.on('data', (d) => { data += d; }); res.on('end', () => { const res = converter.render(data); const converted = renderToLayout(res, layout); try { fs_1.default.writeFileSync(output, converted); console.log(`✅ ${inputURL} ➡️ ${output}`); } catch (e) { printError(inputURL, e); return; } }); }; const generateOutputFilename = (inputFilename) => { // if `outputs` is non-null, use `outputs` as output file name let ret; if (outputs !== null) { if (outputsIndex < outputs.length) { ret = outputs[outputsIndex].toString(); outputsIndex++; } else { throw ('the number of --output is smaller than the number of --input'); } } else { ret = path_1.default.join(dest.toString(), path_1.default.basename(inputFilename.toString()).replace(/\.md$/, '') + '.html'); } // if `ret` is repeated, use a hash function to generate a new filename let hashIn = inputFilename.toString(); const retExtname = path_1.default.extname(ret); // including leading dot '.' const tmpRet = ret.replace(new RegExp(retExtname + "$"), ''); while (outputFilenameSet.has(ret)) { hashIn = hash.update(hashIn).digest('hex').toString().substring(0, 5); ret = tmpRet + '.' + hashIn + retExtname; } outputFilenameSet.add(ret); return ret; }; // if `outputs` is null, generate the output file in the directory `dest` // if `dest` existed, check `dest` is directory // otherwise create the new directory `dest` if (outputs === null) { if (fs_1.default.existsSync(dest)) { const stats = fs_1.default.statSync(dest); if (!stats.isDirectory()) { printError(dest, `${dest} is not directory`); return; } } if (!fs_1.default.existsSync(dest)) { fs_1.default.mkdirSync(dest); } } inputs.forEach((fn) => { // 1. http/https mode const url = isURL(fn.toString()); if (url != null) { if (outputs !== null && outputsIndex >= (outputs === null || outputs === void 0 ? void 0 : outputs.length)) { return; } if (url.protocol === 'https:') { https.get(url, (res) => { convertURL(url, generateOutputFilename(url), res); }).on('error', (e) => { printError(fn, e); }); } else if (url.protocol === 'http:') { http.get(url, (res) => { convertURL(url, generateOutputFilename(url), res); }).on('error', (e) => { printError(fn, e); }); } else { printError(url, "protocol not supported"); } } else { // 2. File mode (0, glob_1.glob)(fn.toString()).then((fileList) => { fileList.forEach((f) => { try { const stats = fs_1.default.statSync(f); if (stats.isDirectory()) { return; } } catch (e) { printError(fn, e); return; } const markdown = fs_1.default.readFileSync(f, { encoding: 'utf-8' }); const res = converter.render(markdown); const converted = renderToLayout(res, layout); const o = generateOutputFilename(f); try { fs_1.default.writeFileSync(o, converted); console.log(`✅ ${f} ➡️ ${o}`); } catch (e) { printError(f, e); } }); }).catch((e) => { printError(fn, e); }); } }); } function renderToLayout(res, layout) { let metas = ''; if (res.metadata.title !== '') { metas += '<title>' + (0, utils_1.escapeHtml)(res.metadata.title) + '</title>\n'; metas += '<meta name="twitter:title" content="' + (0, utils_1.escapeHtml)(res.metadata.title) + '" />\n'; metas += '<meta property="og:title" content="' + (0, utils_1.escapeHtml)(res.metadata.title) + '" />\n'; } if (res.metadata.robots !== '') { metas += '<meta name="robots" content="' + (0, utils_1.escapeHtml)(res.metadata.robots) + '">\n'; } if (res.metadata.description !== '') { metas += '<meta name="description" content="' + (0, utils_1.escapeHtml)(res.metadata.description) + '">\n'; metas += '<meta name="twitter:description" content="' + (0, utils_1.escapeHtml)(res.metadata.description) + '">\n'; metas += '<meta property="og:description" content="' + (0, utils_1.escapeHtml)(res.metadata.description) + '">\n'; } if (res.metadata.image !== '') { metas += '<meta name="twitter:image:src" content="' + (0, utils_1.escapeHtml)(res.metadata.image) + '" />\n'; metas += '<meta property="og:image" content="' + (0, utils_1.escapeHtml)(res.metadata.image) + '" />\n'; } let lang = ''; if (res.metadata.lang !== '') { lang = ' lang="' + (0, utils_1.escapeHtml)(res.metadata.lang) + '"'; } let dir = ''; if (res.metadata.dir !== '') { dir = ' dir="' + (0, utils_1.escapeHtml)(res.metadata.dir) + '"'; } return layout .replace('{{lang}}', lang) .replace('{{dir}}', dir) .replace('{{metas}}', metas) .replace('{{main}}', res.main); } function defaultLayout(dark = false) { return fs_1.default.readFileSync(path_1.default.join(__dirname, !dark ? '../layouts/layout.html' : '../layouts/layout.dark.html'), { encoding: 'utf-8' }); } main(); //# sourceMappingURL=cli.js.map