2025-05-12 05:38:44 +09:00

279 lines
8.9 KiB
JavaScript

/* global Opal */
const yargs = require('yargs')
const asciidoctor = require('@asciidoctor/core')()
const convertOptions = (args, attrs) => {
const attributes = attrs || []
const backend = args.backend
const doctype = args.doctype
const safeMode = args['safe-mode']
const embedded = args.embedded === true || args['no-header-footer'] === true || args.standalone === false
const standalone = !embedded
const sectionNumbers = args['section-numbers']
const baseDir = args['base-dir']
const destinationDir = args['destination-dir']
const outFile = args['out-file']
const templateDir = args['template-dir']
const templateEngine = args['template-engine']
const quiet = args.quiet
const verbose = args.verbose
const timings = args.timings
const trace = args.trace
const requireLib = args.require
let level = args['failure-level'].toUpperCase()
if (level === 'WARNING') {
level = 'WARN'
}
const failureLevel = asciidoctor.LoggerSeverity[level]
const debug = verbose && args['verbose-sole-argument'] !== true
if (debug) {
console.log('require ' + requireLib)
console.log('backend ' + backend)
console.log('doctype ' + doctype)
console.log('standalone ' + standalone)
console.log('section-numbers ' + sectionNumbers)
console.log('failure-level ' + level)
console.log('quiet ' + quiet)
console.log('verbose ' + verbose)
console.log('timings ' + timings)
console.log('trace ' + trace)
console.log('base-dir ' + baseDir)
console.log('destination-dir ' + destinationDir)
console.log('template-dir ' + templateDir)
console.log('template-engine ' + templateEngine)
}
const verboseMode = quiet ? 0 : verbose ? 2 : 1
if (sectionNumbers) {
attributes.push('sectnums')
}
const cliAttributes = args.attribute
if (cliAttributes) {
attributes.push(...cliAttributes)
}
if (debug) {
console.log('verbose-mode ' + verboseMode)
console.log('attributes ' + attributes)
}
const options = {
doctype: doctype,
safe: safeMode,
standalone: standalone,
failure_level: failureLevel,
verbose: verboseMode,
timings: timings,
trace: trace
}
if (backend) {
options.backend = backend
}
if (baseDir != null) {
options.base_dir = baseDir
}
if (destinationDir != null) {
options.to_dir = destinationDir
}
if (templateDir) {
options.template_dirs = templateDir
}
if (templateEngine) {
options.template_engine = templateEngine
}
if (typeof outFile !== 'undefined') {
if (outFile === '') {
options.to_file = '-'
} else {
options.to_file = outFile
options.mkdirs = true
}
} else {
options.mkdirs = true
}
options.attributes = attributes
if (debug) {
console.log('options ' + JSON.stringify(options))
}
if (options.to_file === '-') {
options.to_file = Opal.gvars.stdout
}
return options
}
class Options {
constructor (options) {
this.options = options || {}
this.args = {
standalone: typeof this.options.standalone !== 'undefined' ? this.options.standalone : true,
backend: this.options.backend,
'safe-mode': typeof this.options.safe !== 'undefined' ? this.options.safe : 'unsafe'
}
if (Array.isArray(this.options.attributes)) {
this.attributes = options.attributes
} else if (typeof this.options.attributes === 'object') {
const attrs = this.options.attributes
const attributes = []
Object.keys(attrs).forEach((key) => {
attributes.push(`${key}=${attrs[key]}`)
})
this.attributes = attributes
} else {
this.attributes = []
}
this.cmd = yargs
.option('backend', {
alias: 'b',
describe: 'set output format backend',
type: 'string'
})
.option('doctype', {
alias: 'd',
describe: 'document type to use when converting document',
choices: ['article', 'book', 'manpage', 'inline']
})
.option('out-file', {
alias: 'o',
describe: 'output file (default: based on path of input file) use \'\' to output to STDOUT',
type: 'string'
})
.option('safe-mode', {
alias: 'S',
describe: 'set safe mode level explicitly, disables potentially dangerous macros in source files, such as include::[]',
choices: ['unsafe', 'safe', 'server', 'secure']
})
.option('embedded', {
alias: 'e',
describe: 'suppress enclosing document structure and output an embedded document',
type: 'boolean'
})
.option('no-header-footer', {
alias: 's',
describe: 'suppress enclosing document structure and output an embedded document',
type: 'boolean'
})
.option('section-numbers', {
alias: 'n',
default: false,
describe: 'auto-number section titles in the HTML backend disabled by default',
type: 'boolean'
})
.option('base-dir', {
// QUESTION: should we check that the directory exists ? coerce to a directory ?
alias: 'B',
describe: 'base directory containing the document and resources (default: directory of source file)',
type: 'string'
})
.option('destination-dir', {
// QUESTION: should we check that the directory exists ? coerce to a directory ?
alias: 'D',
describe: 'destination output directory (default: directory of source file)',
type: 'string'
})
.option('failure-level', {
default: 'FATAL',
describe: 'set minimum logging level that triggers non-zero exit code',
choices: ['info', 'INFO', 'warn', 'WARN', 'warning', 'WARNING', 'error', 'ERROR', 'fatal', 'FATAL']
})
.option('quiet', {
alias: 'q',
default: false,
describe: 'suppress warnings',
type: 'boolean'
})
.option('trace', {
default: false,
describe: 'include backtrace information on errors',
type: 'boolean'
})
.option('verbose', {
alias: 'v',
default: false,
describe: 'enable verbose mode',
type: 'boolean'
})
.option('timings', {
alias: 't',
default: false,
describe: 'enable timings mode',
type: 'boolean'
})
.option('template-dir', {
alias: 'T',
array: true,
describe: 'a directory containing custom converter templates that override the built-in converter (may be specified multiple times)',
type: 'string'
})
.option('template-engine', {
alias: 'E',
describe: 'template engine to use for the custom converter templates',
type: 'string'
})
.option('attribute', {
alias: 'a',
array: true,
describe: 'a document attribute to set in the form of key, key! or key=value pair',
type: 'string'
})
.option('require', {
alias: 'r',
array: true,
describe: 'require the specified library before executing the processor, using the standard Node require',
type: 'string'
})
.version(false)
.option('version', {
alias: 'V',
default: false,
describe: 'display the version and runtime environment (or -v if no other flags or arguments)',
type: 'boolean'
})
.help(false)
.option('help', {
describe: `print a help message
show this usage if TOPIC is not specified or recognized
show an overview of the AsciiDoc syntax if TOPIC is syntax`,
type: 'string'
})
.nargs('template-dir', 1)
.nargs('attribute', 1)
.nargs('require', 1)
.usage(`$0 [options...] files...
Translate the AsciiDoc source file or file(s) into the backend output format (e.g., HTML 5, DocBook 5, etc.)
By default, the output is written to a file with the basename of the source file and the appropriate extension`)
.example('$0 -b html5 doc.asciidoc', 'convert an AsciiDoc file to HTML5; result will be written in a file named doc.html')
.epilogue('For more information, please visit https://asciidoctor.org/docs')
this.yargs = yargs
}
parse (argv) {
const processArgs = argv.slice(2)
this.argv = argv
const args = this.argsParser().parse(processArgs)
Object.assign(this.args, args)
const files = this.args.files
this.stdin = files && files.length === 0 && processArgs[processArgs.length - 1] === '-'
if (this.stdin) {
this.args['out-file'] = this.args['out-file'] || '-'
}
this.args['verbose-sole-argument'] = this.args.verbose && processArgs.length === 1
const options = convertOptions(this.args, this.attributes)
Object.assign(this.options, options)
return this
}
addOption (key, opt) {
this.cmd.option(key, opt)
return this
}
argsParser () {
return this.yargs
.detectLocale(false)
.wrap(Math.min(120, this.yargs.terminalWidth()))
.command('$0 [files...]', '', () => this.cmd)
.parserConfiguration({
'boolean-negation': false
})
}
}
module.exports = Options