版博士V2.0程序
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. const { InvalidArgumentError } = require('./error.js');
  2. // @ts-check
  3. class Option {
  4. /**
  5. * Initialize a new `Option` with the given `flags` and `description`.
  6. *
  7. * @param {string} flags
  8. * @param {string} [description]
  9. */
  10. constructor(flags, description) {
  11. this.flags = flags;
  12. this.description = description || '';
  13. this.required = flags.includes('<'); // A value must be supplied when the option is specified.
  14. this.optional = flags.includes('['); // A value is optional when the option is specified.
  15. // variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument
  16. this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
  17. this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.
  18. const optionFlags = splitOptionFlags(flags);
  19. this.short = optionFlags.shortFlag;
  20. this.long = optionFlags.longFlag;
  21. this.negate = false;
  22. if (this.long) {
  23. this.negate = this.long.startsWith('--no-');
  24. }
  25. this.defaultValue = undefined;
  26. this.defaultValueDescription = undefined;
  27. this.presetArg = undefined;
  28. this.envVar = undefined;
  29. this.parseArg = undefined;
  30. this.hidden = false;
  31. this.argChoices = undefined;
  32. this.conflictsWith = [];
  33. this.implied = undefined;
  34. }
  35. /**
  36. * Set the default value, and optionally supply the description to be displayed in the help.
  37. *
  38. * @param {any} value
  39. * @param {string} [description]
  40. * @return {Option}
  41. */
  42. default(value, description) {
  43. this.defaultValue = value;
  44. this.defaultValueDescription = description;
  45. return this;
  46. }
  47. /**
  48. * Preset to use when option used without option-argument, especially optional but also boolean and negated.
  49. * The custom processing (parseArg) is called.
  50. *
  51. * @example
  52. * new Option('--color').default('GREYSCALE').preset('RGB');
  53. * new Option('--donate [amount]').preset('20').argParser(parseFloat);
  54. *
  55. * @param {any} arg
  56. * @return {Option}
  57. */
  58. preset(arg) {
  59. this.presetArg = arg;
  60. return this;
  61. }
  62. /**
  63. * Add option name(s) that conflict with this option.
  64. * An error will be displayed if conflicting options are found during parsing.
  65. *
  66. * @example
  67. * new Option('--rgb').conflicts('cmyk');
  68. * new Option('--js').conflicts(['ts', 'jsx']);
  69. *
  70. * @param {string | string[]} names
  71. * @return {Option}
  72. */
  73. conflicts(names) {
  74. this.conflictsWith = this.conflictsWith.concat(names);
  75. return this;
  76. }
  77. /**
  78. * Specify implied option values for when this option is set and the implied options are not.
  79. *
  80. * The custom processing (parseArg) is not called on the implied values.
  81. *
  82. * @example
  83. * program
  84. * .addOption(new Option('--log', 'write logging information to file'))
  85. * .addOption(new Option('--trace', 'log extra details').implies({ log: 'trace.txt' }));
  86. *
  87. * @param {Object} impliedOptionValues
  88. * @return {Option}
  89. */
  90. implies(impliedOptionValues) {
  91. this.implied = Object.assign(this.implied || {}, impliedOptionValues);
  92. return this;
  93. }
  94. /**
  95. * Set environment variable to check for option value.
  96. *
  97. * An environment variable is only used if when processed the current option value is
  98. * undefined, or the source of the current value is 'default' or 'config' or 'env'.
  99. *
  100. * @param {string} name
  101. * @return {Option}
  102. */
  103. env(name) {
  104. this.envVar = name;
  105. return this;
  106. }
  107. /**
  108. * Set the custom handler for processing CLI option arguments into option values.
  109. *
  110. * @param {Function} [fn]
  111. * @return {Option}
  112. */
  113. argParser(fn) {
  114. this.parseArg = fn;
  115. return this;
  116. }
  117. /**
  118. * Whether the option is mandatory and must have a value after parsing.
  119. *
  120. * @param {boolean} [mandatory=true]
  121. * @return {Option}
  122. */
  123. makeOptionMandatory(mandatory = true) {
  124. this.mandatory = !!mandatory;
  125. return this;
  126. }
  127. /**
  128. * Hide option in help.
  129. *
  130. * @param {boolean} [hide=true]
  131. * @return {Option}
  132. */
  133. hideHelp(hide = true) {
  134. this.hidden = !!hide;
  135. return this;
  136. }
  137. /**
  138. * @api private
  139. */
  140. _concatValue(value, previous) {
  141. if (previous === this.defaultValue || !Array.isArray(previous)) {
  142. return [value];
  143. }
  144. return previous.concat(value);
  145. }
  146. /**
  147. * Only allow option value to be one of choices.
  148. *
  149. * @param {string[]} values
  150. * @return {Option}
  151. */
  152. choices(values) {
  153. this.argChoices = values.slice();
  154. this.parseArg = (arg, previous) => {
  155. if (!this.argChoices.includes(arg)) {
  156. throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(', ')}.`);
  157. }
  158. if (this.variadic) {
  159. return this._concatValue(arg, previous);
  160. }
  161. return arg;
  162. };
  163. return this;
  164. }
  165. /**
  166. * Return option name.
  167. *
  168. * @return {string}
  169. */
  170. name() {
  171. if (this.long) {
  172. return this.long.replace(/^--/, '');
  173. }
  174. return this.short.replace(/^-/, '');
  175. }
  176. /**
  177. * Return option name, in a camelcase format that can be used
  178. * as a object attribute key.
  179. *
  180. * @return {string}
  181. * @api private
  182. */
  183. attributeName() {
  184. return camelcase(this.name().replace(/^no-/, ''));
  185. }
  186. /**
  187. * Check if `arg` matches the short or long flag.
  188. *
  189. * @param {string} arg
  190. * @return {boolean}
  191. * @api private
  192. */
  193. is(arg) {
  194. return this.short === arg || this.long === arg;
  195. }
  196. /**
  197. * Return whether a boolean option.
  198. *
  199. * Options are one of boolean, negated, required argument, or optional argument.
  200. *
  201. * @return {boolean}
  202. * @api private
  203. */
  204. isBoolean() {
  205. return !this.required && !this.optional && !this.negate;
  206. }
  207. }
  208. /**
  209. * This class is to make it easier to work with dual options, without changing the existing
  210. * implementation. We support separate dual options for separate positive and negative options,
  211. * like `--build` and `--no-build`, which share a single option value. This works nicely for some
  212. * use cases, but is tricky for others where we want separate behaviours despite
  213. * the single shared option value.
  214. */
  215. class DualOptions {
  216. /**
  217. * @param {Option[]} options
  218. */
  219. constructor(options) {
  220. this.positiveOptions = new Map();
  221. this.negativeOptions = new Map();
  222. this.dualOptions = new Set();
  223. options.forEach(option => {
  224. if (option.negate) {
  225. this.negativeOptions.set(option.attributeName(), option);
  226. } else {
  227. this.positiveOptions.set(option.attributeName(), option);
  228. }
  229. });
  230. this.negativeOptions.forEach((value, key) => {
  231. if (this.positiveOptions.has(key)) {
  232. this.dualOptions.add(key);
  233. }
  234. });
  235. }
  236. /**
  237. * Did the value come from the option, and not from possible matching dual option?
  238. *
  239. * @param {any} value
  240. * @param {Option} option
  241. * @returns {boolean}
  242. */
  243. valueFromOption(value, option) {
  244. const optionKey = option.attributeName();
  245. if (!this.dualOptions.has(optionKey)) return true;
  246. // Use the value to deduce if (probably) came from the option.
  247. const preset = this.negativeOptions.get(optionKey).presetArg;
  248. const negativeValue = (preset !== undefined) ? preset : false;
  249. return option.negate === (negativeValue === value);
  250. }
  251. }
  252. /**
  253. * Convert string from kebab-case to camelCase.
  254. *
  255. * @param {string} str
  256. * @return {string}
  257. * @api private
  258. */
  259. function camelcase(str) {
  260. return str.split('-').reduce((str, word) => {
  261. return str + word[0].toUpperCase() + word.slice(1);
  262. });
  263. }
  264. /**
  265. * Split the short and long flag out of something like '-m,--mixed <value>'
  266. *
  267. * @api private
  268. */
  269. function splitOptionFlags(flags) {
  270. let shortFlag;
  271. let longFlag;
  272. // Use original very loose parsing to maintain backwards compatibility for now,
  273. // which allowed for example unintended `-sw, --short-word` [sic].
  274. const flagParts = flags.split(/[ |,]+/);
  275. if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1])) shortFlag = flagParts.shift();
  276. longFlag = flagParts.shift();
  277. // Add support for lone short flag without significantly changing parsing!
  278. if (!shortFlag && /^-[^-]$/.test(longFlag)) {
  279. shortFlag = longFlag;
  280. longFlag = undefined;
  281. }
  282. return { shortFlag, longFlag };
  283. }
  284. exports.Option = Option;
  285. exports.splitOptionFlags = splitOptionFlags;
  286. exports.DualOptions = DualOptions;