| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- '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 <first> <second> <rest...>
- 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 <first> <second> [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;
|