Ejdjdososs/fable-ai
0
1#!/usr/bin/env node2 3/**4 * Marked CLI5 * Copyright (c) 2011-2013, Christopher Jeffrey (MIT License)6 */7 8import { promises } from 'node:fs';9import { dirname, resolve } from 'node:path';10import { homedir } from 'node:os';11import { createRequire } from 'node:module';12import { marked } from '../lib/marked.esm.js';13 14const { access, readFile, writeFile } = promises;15const require = createRequire(import.meta.url);16 17/**18 * @param {Process} nodeProcess inject process so it can be mocked in tests.19 */20export async function main(nodeProcess) {21 /**22 * Man Page23 */24 async function help() {25 const { spawn } = await import('child_process');26 const { fileURLToPath } = await import('url');27 28 const options = {29 cwd: nodeProcess.cwd(),30 env: nodeProcess.env,31 stdio: 'inherit',32 };33 34 const __dirname = dirname(fileURLToPath(import.meta.url));35 const helpText = await readFile(resolve(__dirname, '../man/marked.1.md'), 'utf8');36 37 await new Promise(res => {38 spawn('man', [resolve(__dirname, '../man/marked.1')], options)39 .on('error', () => {40 console.log(helpText);41 })42 .on('close', res);43 });44 }45 46 async function version() {47 const pkg = require('../package.json');48 console.log(pkg.version);49 }50 51 /**52 * Main53 */54 async function start(argv) {55 const files = [];56 const options = {};57 let input;58 let output;59 let string;60 let arg;61 let tokens;62 let config;63 let opt;64 let noclobber;65 66 function getArg() {67 let arg = argv.shift();68 69 if (arg.indexOf('--') === 0) {70 // e.g. --opt71 arg = arg.split('=');72 if (arg.length > 1) {73 // e.g. --opt=val74 argv.unshift(arg.slice(1).join('='));75 }76 arg = arg[0];77 } else if (arg[0] === '-') {78 if (arg.length > 2) {79 // e.g. -abc80 argv = arg.substring(1).split('').map(function(ch) {81 return '-' + ch;82 }).concat(argv);83 arg = argv.shift();84 } else {85 // e.g. -a86 }87 } else {88 // e.g. foo89 }90 91 return arg;92 }93 94 while (argv.length) {95 arg = getArg();96 switch (arg) {97 case '-o':98 case '--output':99 output = argv.shift();100 break;101 case '-i':102 case '--input':103 input = argv.shift();104 break;105 case '-s':106 case '--string':107 string = argv.shift();108 break;109 case '-t':110 case '--tokens':111 tokens = true;112 break;113 case '-c':114 case '--config':115 config = argv.shift();116 break;117 case '-n':118 case '--no-clobber':119 noclobber = true;120 break;121 case '-h':122 case '--help':123 return await help();124 case '-v':125 case '--version':126 return await version();127 default:128 if (arg.indexOf('--') === 0) {129 opt = camelize(arg.replace(/^--(no-)?/, ''));130 if (!(opt in marked.defaults)) {131 continue;132 }133 if (arg.indexOf('--no-') === 0) {134 options[opt] = typeof marked.defaults[opt] !== 'boolean'135 ? null136 : false;137 } else {138 options[opt] = typeof marked.defaults[opt] !== 'boolean'139 ? argv.shift()140 : true;141 }142 } else {143 files.push(arg);144 }145 break;146 }147 }148 149 async function getData() {150 if (!input) {151 if (files.length <= 2) {152 if (string) {153 return string;154 }155 return await getStdin();156 }157 input = files.pop();158 }159 return await readFile(input, 'utf8');160 }161 162 function resolveFile(file) {163 return resolve(file.replace(/^~/, homedir));164 }165 166 function fileExists(file) {167 return access(resolveFile(file)).then(() => true, () => false);168 }169 170 async function runConfig(file) {171 const configFile = resolveFile(file);172 let markedConfig;173 try {174 // try require for json175 markedConfig = require(configFile);176 } catch (err) {177 if (err.code !== 'ERR_REQUIRE_ESM') {178 throw err;179 }180 // must import esm181 markedConfig = await import('file:///' + configFile);182 }183 184 if (markedConfig.default) {185 markedConfig = markedConfig.default;186 }187 188 if (typeof markedConfig === 'function') {189 markedConfig(marked);190 } else {191 marked.use(markedConfig);192 }193 }194 195 const data = await getData();196 197 if (config) {198 if (!await fileExists(config)) {199 throw Error(`Cannot load config file '${config}'`);200 }201 202 await runConfig(config);203 } else {204 const defaultConfig = [205 '~/.marked.json',206 '~/.marked.js',207 '~/.marked/index.js',208 ];209 210 for (const configFile of defaultConfig) {211 if (await fileExists(configFile)) {212 await runConfig(configFile);213 break;214 }215 }216 }217 218 const html = tokens219 ? JSON.stringify(marked.lexer(data, options), null, 2)220 : await marked.parse(data, options);221 222 if (output) {223 if (noclobber && await fileExists(output)) {224 throw Error('marked: output file \'' + output + '\' already exists, disable the \'-n\' / \'--no-clobber\' flag to overwrite\n');225 }226 return await writeFile(output, html);227 }228 229 nodeProcess.stdout.write(html + '\n');230 }231 232 /**233 * Helpers234 */235 function getStdin() {236 return new Promise((resolve, reject) => {237 const stdin = nodeProcess.stdin;238 let buff = '';239 240 stdin.setEncoding('utf8');241 242 stdin.on('data', function(data) {243 buff += data;244 });245 246 stdin.on('error', function(err) {247 reject(err);248 });249 250 stdin.on('end', function() {251 resolve(buff);252 });253 254 stdin.resume();255 });256 }257 258 /**259 * @param {string} text260 */261 function camelize(text) {262 return text.replace(/(\w)-(\w)/g, function(_, a, b) {263 return a + b.toUpperCase();264 });265 }266 267 try {268 await start(nodeProcess.argv.slice());269 nodeProcess.exit(0);270 } catch (err) {271 if (err.code === 'ENOENT') {272 nodeProcess.stderr.write('marked: ' + err.path + ': No such file or directory');273 } else {274 nodeProcess.stderr.write(err.message);275 }276 return nodeProcess.exit(1);277 }278}279 