-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add config option to bin/marked (#2937)
* add config option to bin/marked * add tests * add docs * remove focused tests
- Loading branch information
Showing
6 changed files
with
400 additions
and
205 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
#!/usr/bin/env node | ||
|
||
/** | ||
* Marked CLI | ||
* Copyright (c) 2011-2013, Christopher Jeffrey (MIT License) | ||
*/ | ||
|
||
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); | ||
|
||
/** | ||
* @param {Process} nodeProcess inject process so it can be mocked in tests. | ||
*/ | ||
export async function main(nodeProcess) { | ||
/** | ||
* Man Page | ||
*/ | ||
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.txt'), 'utf8'); | ||
|
||
// eslint-disable-next-line promise/param-names | ||
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); | ||
} | ||
|
||
/** | ||
* Main | ||
*/ | ||
async function start(argv) { | ||
const files = []; | ||
const options = {}; | ||
let input; | ||
let output; | ||
let string; | ||
let arg; | ||
let tokens; | ||
let config; | ||
let opt; | ||
|
||
function getArg() { | ||
let arg = argv.shift(); | ||
|
||
if (arg.indexOf('--') === 0) { | ||
// e.g. --opt | ||
arg = arg.split('='); | ||
if (arg.length > 1) { | ||
// e.g. --opt=val | ||
argv.unshift(arg.slice(1).join('=')); | ||
} | ||
arg = arg[0]; | ||
} else if (arg[0] === '-') { | ||
if (arg.length > 2) { | ||
// e.g. -abc | ||
argv = arg.substring(1).split('').map(function(ch) { | ||
return '-' + ch; | ||
}).concat(argv); | ||
arg = argv.shift(); | ||
} else { | ||
// e.g. -a | ||
} | ||
} else { | ||
// e.g. foo | ||
} | ||
|
||
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 '-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 { | ||
// try require for json | ||
markedConfig = require(configFile); | ||
} catch (err) { | ||
if (err.code !== 'ERR_REQUIRE_ESM') { | ||
throw err; | ||
} | ||
// must import esm | ||
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) { | ||
return await writeFile(output, html); | ||
} | ||
|
||
nodeProcess.stdout.write(html + '\n'); | ||
} | ||
|
||
/** | ||
* Helpers | ||
*/ | ||
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(); | ||
}); | ||
} | ||
|
||
/** | ||
* @param {string} text | ||
*/ | ||
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); | ||
} | ||
} |
Oops, something went wrong.
65b6f14
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
marked-website – ./
marked-website-git-master-markedjs.vercel.app
markedjs.vercel.app
marked.js.org
marked-website-markedjs.vercel.app