const path = require('path') const microargs = require('microargs') const { get, difference, isEmpty, padEnd, forEach, capitalize, omit, isString } = require('lodash') class CLIError extends Error { } function optionToString (optionName) { return optionName.length === 1 ? `-${optionName}` : `--${optionName}` } function optionsToString (optionsKeys) { return optionsKeys.map(optionToString).join(' ') } function printHelp (scriptName, annotations, logger) { if (isEmpty(annotations)) { logger.log('Documentation not found') return null } const { description, params, options } = annotations const extra = omit(annotations, ['description', 'params', 'options']) const usageOptions = isEmpty(options) ? '' : '[options]' const usageParams = isEmpty(params) ? '' : `[${params.join(' ')}]` logger.log(`Usage: ${path.basename(scriptName)} ${usageOptions} ${usageParams}\n`) if (description) { logger.log(`${description}\n`) } if (!isEmpty(options)) { logger.log('Options:\n') forEach(options, (value, key) => { logger.log(` ${padEnd(optionToString(key), 12)}${value}`) }) } forEach(extra, (value, key) => { logger.log(`\n${capitalize(key)}:\n`) logger.log(`${value}\n`) }) } module.exports = (argv, annotations = {}, help, logger = console) => { help = help || printHelp return (callback) => { const { params, options } = microargs(argv.slice(2)) const scriptName = path.basename(argv[1]) if (isString(annotations)) { annotations = { description: annotations } } if (options.help) { return help(scriptName, annotations, logger) } const annotatedOptionsKeys = (get(annotations, 'options') && Object.keys(annotations.options)) || [] const optionsKeys = Object.keys(options) const illegalOptionsKeys = difference(optionsKeys, annotatedOptionsKeys) if (annotatedOptionsKeys.length && illegalOptionsKeys.length) { const msg = `Illegal option: ${optionsToString(illegalOptionsKeys)}\n` + `Available options: ${optionsToString(annotatedOptionsKeys)}\n` + `Type "${scriptName} --help" for more information` throw new CLIError(msg) } return callback(options, ...params) } } module.exports.CLIError = CLIError