blob: b774c31ed9d299e8640866ce6a1bf4a8ef24f3a3 [file] [log] [blame]
Yang Guo4fd355c2019-09-19 10:59:03 +02001'use strict';
2
3const yargs = require('yargs/yargs');
4const flatten = require('flat');
5const castArray = require('lodash/castArray');
6const some = require('lodash/some');
7const isPlainObject = require('lodash/isPlainObject');
8const camelCase = require('lodash/camelCase');
9const kebabCase = require('lodash/kebabCase');
10const omitBy = require('lodash/omitBy');
11
12function isAlias(key, alias) {
13 return some(alias, (aliases) => castArray(aliases).indexOf(key) !== -1);
14}
15
16function hasDefaultValue(key, value, defaults) {
17 return value === defaults[key];
18}
19
20function isCamelCased(key, argv) {
21 return /[A-Z]/.test(key) && camelCase(key) === key && // Is it camel case?
22 argv[kebabCase(key)] != null; // Is the standard version defined?
23}
24
25function keyToFlag(key) {
26 return key.length === 1 ? `-${key}` : `--${key}`;
27}
28
29function unparseOption(key, value, unparsed) {
30 if (typeof value === 'string') {
31 unparsed.push(keyToFlag(key), value);
32 } else if (value === true) {
33 unparsed.push(keyToFlag(key));
34 } else if (value === false) {
35 unparsed.push(`--no-${key}`);
36 } else if (Array.isArray(value)) {
37 value.forEach((item) => unparseOption(key, item, unparsed));
38 } else if (isPlainObject(value)) {
39 const flattened = flatten(value, { safe: true });
40
41 for (const flattenedKey in flattened) {
42 unparseOption(`${key}.${flattenedKey}`, flattened[flattenedKey], unparsed);
43 }
44 // Fallback case (numbers and other types)
45 } else if (value != null) {
46 unparsed.push(keyToFlag(key), `${value}`);
47 }
48}
49
50function unparsePositional(argv, options, unparsed) {
51 const knownPositional = [];
52
53 // Unparse command if set, collecting all known positional arguments
54 // e.g.: build <first> <second> <rest...>
55 if (options.command) {
56 const { 0: cmd, index } = options.command.match(/[^<[]*/);
57 const { demanded, optional } = yargs()
58 .getCommandInstance()
59 .parseCommand(`foo ${options.command.substr(index + cmd.length)}`);
60
61 // Push command (can be a deep command)
62 unparsed.push(...cmd.trim().split(/\s+/));
63
64 // Push positional arguments
65 [...demanded, ...optional].forEach(({ cmd: cmds, variadic }) => {
66 knownPositional.push(...cmds);
67
68 const cmd = cmds[0];
69 const args = (variadic ? argv[cmd] || [] : [argv[cmd]])
70 .filter((arg) => arg != null)
71 .map((arg) => `${arg}`);
72
73 unparsed.push(...args);
74 });
75 }
76
77 // Unparse unkown positional arguments
78 argv._ && unparsed.push(...argv._.slice(knownPositional.length));
79
80 return knownPositional;
81}
82
83function unparseOptions(argv, options, knownPositional, unparsed) {
84 const optionsArgv = omitBy(argv, (value, key) =>
85 // Remove positional arguments
86 knownPositional.includes(key) ||
87 // Remove special _, -- and $0
88 ['_', '--', '$0'].includes(key) ||
89 // Remove aliases
90 isAlias(key, options.alias) ||
91 // Remove default values
92 hasDefaultValue(key, value, options.default) ||
93 // Remove camel-cased
94 isCamelCased(key, argv));
95
96 for (const key in optionsArgv) {
97 unparseOption(key, optionsArgv[key], unparsed);
98 }
99}
100
101function unparseEndOfOptions(argv, options, unparsed) {
102 // Unparse ending (--) arguments if set
103 argv['--'] && unparsed.push('--', ...argv['--']);
104}
105
106// ------------------------------------------------------------
107
108function unparser(argv, options) {
109 options = Object.assign({
110 alias: {},
111 default: {},
112 command: null,
113 }, options);
114
115 const unparsed = [];
116
117 // Unparse known & unknown positional arguments (foo <first> <second> [rest...])
118 // All known positional will be returned so that they are not added as flags
119 const knownPositional = unparsePositional(argv, options, unparsed);
120
121 // Unparse option arguments (--foo hello --bar hi)
122 unparseOptions(argv, options, knownPositional, unparsed);
123
124 // Unparse "end-of-options" arguments (stuff after " -- ")
125 unparseEndOfOptions(argv, options, unparsed);
126
127 return unparsed;
128}
129
130module.exports = unparser;