1/* global Opal */ 2const Yargs = require('yargs/yargs') 3const asciidoctor = require('@asciidoctor/core')() 4 5const convertOptions = (args, attrs) => { 6 const attributes = attrs || [] 7 const backend = args.backend 8 const doctype = args.doctype 9 const safeMode = args['safe-mode'] 10 const embedded = args.embedded === true || args['no-header-footer'] === true || args.standalone === false 11 const standalone = !embedded 12 const sectionNumbers = args['section-numbers'] 13 const baseDir = args['base-dir'] 14 const destinationDir = args['destination-dir'] 15 const outFile = args['out-file'] 16 const templateDir = args['template-dir'] 17 const templateEngine = args['template-engine'] 18 const quiet = args.quiet 19 const verbose = args.verbose 20 const timings = args.timings 21 const trace = args.trace 22 const requireLib = args.require 23 let level = args['failure-level'].toUpperCase() 24 if (level === 'WARNING') { 25 level = 'WARN' 26 } 27 const failureLevel = asciidoctor.LoggerSeverity[level] 28 const debug = verbose && args['verbose-sole-argument'] !== true 29 if (debug) { 30 console.log('require ' + requireLib) 31 console.log('backend ' + backend) 32 console.log('doctype ' + doctype) 33 console.log('standalone ' + standalone) 34 console.log('section-numbers ' + sectionNumbers) 35 console.log('failure-level ' + level) 36 console.log('quiet ' + quiet) 37 console.log('verbose ' + verbose) 38 console.log('timings ' + timings) 39 console.log('trace ' + trace) 40 console.log('base-dir ' + baseDir) 41 console.log('destination-dir ' + destinationDir) 42 console.log('template-dir ' + templateDir) 43 console.log('template-engine ' + templateEngine) 44 } 45 const verboseMode = quiet ? 0 : verbose ? 2 : 1 46 if (sectionNumbers) { 47 attributes.push('sectnums') 48 } 49 const cliAttributes = args.attribute 50 if (cliAttributes) { 51 attributes.push(...cliAttributes) 52 } 53 if (debug) { 54 console.log('verbose-mode ' + verboseMode) 55 console.log('attributes ' + attributes) 56 } 57 const options = { 58 doctype, 59 safe: safeMode, 60 standalone, 61 failure_level: failureLevel, 62 verbose: verboseMode, 63 timings, 64 trace 65 } 66 if (backend) { 67 options.backend = backend 68 } 69 if (baseDir != null) { 70 options.base_dir = baseDir 71 } 72 if (destinationDir != null) { 73 options.to_dir = destinationDir 74 } 75 if (templateDir) { 76 options.template_dirs = templateDir 77 } 78 if (templateEngine) { 79 options.template_engine = templateEngine 80 } 81 if (typeof outFile !== 'undefined') { 82 if (outFile === '') { 83 options.to_file = '-' 84 } else if (outFile === '\'\'') { 85 options.to_file = '-' 86 } else { 87 options.to_file = outFile 88 options.mkdirs = true 89 } 90 } else { 91 options.mkdirs = true 92 } 93 options.attributes = attributes 94 if (debug) { 95 console.log('options ' + JSON.stringify(options)) 96 } 97 if (options.to_file === '-') { 98 options.to_file = Opal.gvars.stdout 99 } 100 return options 101} 102 103class Options { 104 constructor (options) { 105 this.options = options || {} 106 this.args = { 107 standalone: typeof this.options.standalone !== 'undefined' ? this.options.standalone : true, 108 backend: this.options.backend, 109 'safe-mode': typeof this.options.safe !== 'undefined' ? this.options.safe : 'unsafe' 110 } 111 if (Array.isArray(this.options.attributes)) { 112 this.attributes = options.attributes 113 } else if (typeof this.options.attributes === 'object') { 114 const attrs = this.options.attributes 115 const attributes = [] 116 Object.keys(attrs).forEach((key) => { 117 attributes.push(`${key}=${attrs[key]}`) 118 }) 119 this.attributes = attributes 120 } else { 121 this.attributes = [] 122 } 123 const yargs = Yargs() 124 this.cmd = yargs 125 .option('backend', { 126 alias: 'b', 127 describe: 'set output format backend', 128 type: 'string' 129 }) 130 .option('doctype', { 131 alias: 'd', 132 describe: 'document type to use when converting document', 133 choices: ['article', 'book', 'manpage', 'inline'] 134 }) 135 .option('out-file', { 136 alias: 'o', 137 describe: 'output file (default: based on path of input file) use \'\' to output to STDOUT', 138 type: 'string' 139 }) 140 .option('safe-mode', { 141 alias: 'S', 142 describe: 'set safe mode level explicitly, disables potentially dangerous macros in source files, such as include::[]', 143 choices: ['unsafe', 'safe', 'server', 'secure'] 144 }) 145 .option('embedded', { 146 alias: 'e', 147 describe: 'suppress enclosing document structure and output an embedded document', 148 type: 'boolean' 149 }) 150 .option('no-header-footer', { 151 alias: 's', 152 describe: 'suppress enclosing document structure and output an embedded document', 153 type: 'boolean' 154 }) 155 .option('section-numbers', { 156 alias: 'n', 157 default: false, 158 describe: 'auto-number section titles in the HTML backend disabled by default', 159 type: 'boolean' 160 }) 161 .option('base-dir', { 162 // QUESTION: should we check that the directory exists ? coerce to a directory ? 163 alias: 'B', 164 describe: 'base directory containing the document and resources (default: directory of source file)', 165 type: 'string' 166 }) 167 .option('destination-dir', { 168 // QUESTION: should we check that the directory exists ? coerce to a directory ? 169 alias: 'D', 170 describe: 'destination output directory (default: directory of source file)', 171 type: 'string' 172 }) 173 .option('failure-level', { 174 default: 'FATAL', 175 describe: 'set minimum logging level that triggers non-zero exit code', 176 choices: ['info', 'INFO', 'warn', 'WARN', 'warning', 'WARNING', 'error', 'ERROR', 'fatal', 'FATAL'] 177 }) 178 .option('quiet', { 179 alias: 'q', 180 default: false, 181 describe: 'suppress warnings', 182 type: 'boolean' 183 }) 184 .option('trace', { 185 default: false, 186 describe: 'include backtrace information on errors', 187 type: 'boolean' 188 }) 189 .option('verbose', { 190 alias: 'v', 191 default: false, 192 describe: 'enable verbose mode', 193 type: 'boolean' 194 }) 195 .option('timings', { 196 alias: 't', 197 default: false, 198 describe: 'enable timings mode', 199 type: 'boolean' 200 }) 201 .option('template-dir', { 202 alias: 'T', 203 array: true, 204 describe: 'a directory containing custom converter templates that override the built-in converter (may be specified multiple times)', 205 type: 'string' 206 }) 207 .option('template-engine', { 208 alias: 'E', 209 describe: 'template engine to use for the custom converter templates', 210 type: 'string' 211 }) 212 .option('attribute', { 213 alias: 'a', 214 array: true, 215 describe: 'a document attribute to set in the form of key, key! or key=value pair', 216 type: 'string' 217 }) 218 .option('require', { 219 alias: 'r', 220 array: true, 221 describe: 'require the specified library before executing the processor, using the standard Node require', 222 type: 'string' 223 }) 224 .version(false) 225 .option('version', { 226 alias: 'V', 227 default: false, 228 describe: 'display the version and runtime environment (or -v if no other flags or arguments)', 229 type: 'boolean' 230 }) 231 .help(false) 232 .option('help', { 233 describe: `print a help message 234show this usage if TOPIC is not specified or recognized 235show an overview of the AsciiDoc syntax if TOPIC is syntax`, 236 type: 'string' 237 }) 238 .nargs('template-dir', 1) 239 .nargs('attribute', 1) 240 .nargs('require', 1) 241 .usage(`$0 [options...] files... 242Translate the AsciiDoc source file or file(s) into the backend output format (e.g., HTML 5, DocBook 5, etc.) 243By default, the output is written to a file with the basename of the source file and the appropriate extension`) 244 .example('$0 -b html5 doc.asciidoc', 'convert an AsciiDoc file to HTML5; result will be written in a file named doc.html') 245 .epilogue('For more information, please visit https://asciidoctor.org/docs') 246 this.yargs = yargs 247 } 248 249 parse (argv) { 250 const processArgs = argv.slice(2) 251 this.argv = argv 252 const args = this.argsParser().parse(processArgs) 253 Object.assign(this.args, args) 254 const files = this.args.files 255 this.stdin = files && files.length === 0 && processArgs[processArgs.length - 1] === '-' 256 if (this.stdin) { 257 this.args['out-file'] = this.args['out-file'] || '-' 258 } 259 this.args['verbose-sole-argument'] = this.args.verbose && processArgs.length === 1 260 const options = convertOptions(this.args, this.attributes) 261 Object.assign(this.options, options) 262 return this 263 } 264 265 addOption (key, opt) { 266 this.cmd.option(key, opt) 267 return this 268 } 269 270 argsParser () { 271 return this.yargs 272 .detectLocale(false) 273 .wrap(Math.min(120, this.yargs.terminalWidth())) 274 .command('$0 [files...]', '', () => this.cmd) 275 .parserConfiguration({ 276 'boolean-negation': false 277 }) 278 } 279} 280 281module.exports = Options 282