|
#!/usr/bin/env node |
|
|
|
|
|
|
|
|
|
|
|
|
|
import { promises } from 'node:fs'; |
|
import { dirname, resolve } from 'node:path'; |
|
import { homedir } from 'node:os'; |
|
import { createRequire } from 'node:module'; |
|
import { marked } from '../lib/marked.esm.js'; |
|
|
|
const { access, readFile, writeFile } = promises; |
|
const require = createRequire(import.meta.url); |
|
|
|
|
|
|
|
|
|
export async function main(nodeProcess) { |
|
|
|
|
|
|
|
async function help() { |
|
const { spawn } = await import('child_process'); |
|
const { fileURLToPath } = await import('url'); |
|
|
|
const options = { |
|
cwd: nodeProcess.cwd(), |
|
env: nodeProcess.env, |
|
stdio: 'inherit' |
|
}; |
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url)); |
|
const helpText = await readFile(resolve(__dirname, '../man/marked.1.md'), 'utf8'); |
|
|
|
|
|
await new Promise(res => { |
|
spawn('man', [resolve(__dirname, '../man/marked.1')], options) |
|
.on('error', () => { |
|
console.log(helpText); |
|
}) |
|
.on('close', res); |
|
}); |
|
} |
|
|
|
async function version() { |
|
const pkg = require('../package.json'); |
|
console.log(pkg.version); |
|
} |
|
|
|
|
|
|
|
|
|
async function start(argv) { |
|
const files = []; |
|
const options = {}; |
|
let input; |
|
let output; |
|
let string; |
|
let arg; |
|
let tokens; |
|
let config; |
|
let opt; |
|
let noclobber; |
|
|
|
function getArg() { |
|
let arg = argv.shift(); |
|
|
|
if (arg.indexOf('--') === 0) { |
|
|
|
arg = arg.split('='); |
|
if (arg.length > 1) { |
|
|
|
argv.unshift(arg.slice(1).join('=')); |
|
} |
|
arg = arg[0]; |
|
} else if (arg[0] === '-') { |
|
if (arg.length > 2) { |
|
|
|
argv = arg.substring(1).split('').map(function(ch) { |
|
return '-' + ch; |
|
}).concat(argv); |
|
arg = argv.shift(); |
|
} else { |
|
|
|
} |
|
} else { |
|
|
|
} |
|
|
|
return arg; |
|
} |
|
|
|
while (argv.length) { |
|
arg = getArg(); |
|
switch (arg) { |
|
case '-o': |
|
case '--output': |
|
output = argv.shift(); |
|
break; |
|
case '-i': |
|
case '--input': |
|
input = argv.shift(); |
|
break; |
|
case '-s': |
|
case '--string': |
|
string = argv.shift(); |
|
break; |
|
case '-t': |
|
case '--tokens': |
|
tokens = true; |
|
break; |
|
case '-c': |
|
case '--config': |
|
config = argv.shift(); |
|
break; |
|
case '-n': |
|
case '--no-clobber': |
|
noclobber = true; |
|
break; |
|
case '-h': |
|
case '--help': |
|
return await help(); |
|
case '-v': |
|
case '--version': |
|
return await version(); |
|
default: |
|
if (arg.indexOf('--') === 0) { |
|
opt = camelize(arg.replace(/^--(no-)?/, '')); |
|
if (!marked.defaults.hasOwnProperty(opt)) { |
|
continue; |
|
} |
|
if (arg.indexOf('--no-') === 0) { |
|
options[opt] = typeof marked.defaults[opt] !== 'boolean' |
|
? null |
|
: false; |
|
} else { |
|
options[opt] = typeof marked.defaults[opt] !== 'boolean' |
|
? argv.shift() |
|
: true; |
|
} |
|
} else { |
|
files.push(arg); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
async function getData() { |
|
if (!input) { |
|
if (files.length <= 2) { |
|
if (string) { |
|
return string; |
|
} |
|
return await getStdin(); |
|
} |
|
input = files.pop(); |
|
} |
|
return await readFile(input, 'utf8'); |
|
} |
|
|
|
function resolveFile(file) { |
|
return resolve(file.replace(/^~/, homedir)); |
|
} |
|
|
|
function fileExists(file) { |
|
return access(resolveFile(file)).then(() => true, () => false); |
|
} |
|
|
|
async function runConfig(file) { |
|
const configFile = resolveFile(file); |
|
let markedConfig; |
|
try { |
|
|
|
markedConfig = require(configFile); |
|
} catch (err) { |
|
if (err.code !== 'ERR_REQUIRE_ESM') { |
|
throw err; |
|
} |
|
|
|
markedConfig = await import('file:///' + configFile); |
|
} |
|
|
|
if (markedConfig.default) { |
|
markedConfig = markedConfig.default; |
|
} |
|
|
|
if (typeof markedConfig === 'function') { |
|
markedConfig(marked); |
|
} else { |
|
marked.use(markedConfig); |
|
} |
|
} |
|
|
|
const data = await getData(); |
|
|
|
if (config) { |
|
if (!await fileExists(config)) { |
|
throw Error(`Cannot load config file '${config}'`); |
|
} |
|
|
|
await runConfig(config); |
|
} else { |
|
const defaultConfig = [ |
|
'~/.marked.json', |
|
'~/.marked.js', |
|
'~/.marked/index.js' |
|
]; |
|
|
|
for (const configFile of defaultConfig) { |
|
if (await fileExists(configFile)) { |
|
await runConfig(configFile); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
const html = tokens |
|
? JSON.stringify(marked.lexer(data, options), null, 2) |
|
: await marked.parse(data, options); |
|
|
|
if (output) { |
|
if (noclobber && await fileExists(output)) { |
|
nodeProcess.stderr.write('marked: output file \'' + output + '\' already exists, disable the \'-n\' / \'--no-clobber\' flag to overwrite\n'); |
|
nodeProcess.exit(1); |
|
} |
|
return await writeFile(output, html); |
|
} |
|
|
|
nodeProcess.stdout.write(html + '\n'); |
|
} |
|
|
|
|
|
|
|
|
|
function getStdin() { |
|
return new Promise((resolve, reject) => { |
|
const stdin = nodeProcess.stdin; |
|
let buff = ''; |
|
|
|
stdin.setEncoding('utf8'); |
|
|
|
stdin.on('data', function(data) { |
|
buff += data; |
|
}); |
|
|
|
stdin.on('error', function(err) { |
|
reject(err); |
|
}); |
|
|
|
stdin.on('end', function() { |
|
resolve(buff); |
|
}); |
|
|
|
stdin.resume(); |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
function camelize(text) { |
|
return text.replace(/(\w)-(\w)/g, function(_, a, b) { |
|
return a + b.toUpperCase(); |
|
}); |
|
} |
|
|
|
try { |
|
await start(nodeProcess.argv.slice()); |
|
nodeProcess.exit(0); |
|
} catch (err) { |
|
if (err.code === 'ENOENT') { |
|
nodeProcess.stderr.write('marked: output to ' + err.path + ': No such directory'); |
|
} |
|
nodeProcess.stderr.write(err); |
|
return nodeProcess.exit(1); |
|
} |
|
} |
|
|