index.js 4.1 KB

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