'use strict'; const flatten = require('flat'); const camelcase = require('camelcase'); const decamelize = require('decamelize'); const isPlainObj = require('is-plain-obj'); function isAlias(key, alias) { // TODO Switch to Object.values one Node.js 6 is dropped return Object.keys(alias).some((id) => [].concat(alias[id]).indexOf(key) !== -1); } function hasDefaultValue(key, value, defaults) { return value === defaults[key]; } function isCamelCased(key, argv) { return /[A-Z]/.test(key) && camelcase(key) === key && // Is it camel case? argv[decamelize(key, '-')] != null; // Is the standard version defined? } function keyToFlag(key) { return key.length === 1 ? `-${key}` : `--${key}`; } function parseCommand(cmd) { const extraSpacesStrippedCommand = cmd.replace(/\s{2,}/g, ' '); const splitCommand = extraSpacesStrippedCommand.split(/\s+(?![^[]*]|[^<]*>)/); const bregex = /\.*[\][<>]/g; const firstCommand = splitCommand.shift(); if (!firstCommand) { throw new Error(`No command found in: ${cmd}`); } const parsedCommand = { cmd: firstCommand.replace(bregex, ''), demanded: [], optional: [], }; splitCommand.forEach((cmd, i) => { let variadic = false; cmd = cmd.replace(/\s/g, ''); if (/\.+[\]>]/.test(cmd) && i === splitCommand.length - 1) { variadic = true; } if (/^\[/.test(cmd)) { parsedCommand.optional.push({ cmd: cmd.replace(bregex, '').split('|'), variadic, }); } else { parsedCommand.demanded.push({ cmd: cmd.replace(bregex, '').split('|'), variadic, }); } }); return parsedCommand; } function unparseOption(key, value, unparsed) { if (typeof value === 'string') { unparsed.push(keyToFlag(key), value); } else if (value === true) { unparsed.push(keyToFlag(key)); } else if (value === false) { unparsed.push(`--no-${key}`); } else if (Array.isArray(value)) { value.forEach((item) => unparseOption(key, item, unparsed)); } else if (isPlainObj(value)) { const flattened = flatten(value, { safe: true }); for (const flattenedKey in flattened) { if (!isCamelCased(flattenedKey, flattened)) { unparseOption(`${key}.${flattenedKey}`, flattened[flattenedKey], unparsed); } } // Fallback case (numbers and other types) } else if (value != null) { unparsed.push(keyToFlag(key), `${value}`); } } function unparsePositional(argv, options, unparsed) { const knownPositional = []; // Unparse command if set, collecting all known positional arguments // e.g.: build if (options.command) { const { 0: cmd, index } = options.command.match(/[^<[]*/); const { demanded, optional } = parseCommand(`foo ${options.command.substr(index + cmd.length)}`); // Push command (can be a deep command) unparsed.push(...cmd.trim().split(/\s+/)); // Push positional arguments [...demanded, ...optional].forEach(({ cmd: cmds, variadic }) => { knownPositional.push(...cmds); const cmd = cmds[0]; const args = (variadic ? argv[cmd] || [] : [argv[cmd]]) .filter((arg) => arg != null) .map((arg) => `${arg}`); unparsed.push(...args); }); } // Unparse unkown positional arguments argv._ && unparsed.push(...argv._.slice(knownPositional.length)); return knownPositional; } function unparseOptions(argv, options, knownPositional, unparsed) { for (const key of Object.keys(argv)) { const value = argv[key]; if ( // Remove positional arguments knownPositional.includes(key) || // Remove special _, -- and $0 ['_', '--', '$0'].includes(key) || // Remove aliases isAlias(key, options.alias) || // Remove default values hasDefaultValue(key, value, options.default) || // Remove camel-cased isCamelCased(key, argv) ) { continue; } unparseOption(key, argv[key], unparsed); } } function unparseEndOfOptions(argv, options, unparsed) { // Unparse ending (--) arguments if set argv['--'] && unparsed.push('--', ...argv['--']); } // ------------------------------------------------------------ function unparser(argv, options) { options = Object.assign({ alias: {}, default: {}, command: null, }, options); const unparsed = []; // Unparse known & unknown positional arguments (foo [rest...]) // All known positional will be returned so that they are not added as flags const knownPositional = unparsePositional(argv, options, unparsed); // Unparse option arguments (--foo hello --bar hi) unparseOptions(argv, options, knownPositional, unparsed); // Unparse "end-of-options" arguments (stuff after " -- ") unparseEndOfOptions(argv, options, unparsed); return unparsed; } module.exports = unparser;