summaryrefslogtreecommitdiff
path: root/node_modules/commander/lib/option.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/commander/lib/option.js')
-rw-r--r--node_modules/commander/lib/option.js331
1 files changed, 331 insertions, 0 deletions
diff --git a/node_modules/commander/lib/option.js b/node_modules/commander/lib/option.js
new file mode 100644
index 0000000..d61fc5f
--- /dev/null
+++ b/node_modules/commander/lib/option.js
@@ -0,0 +1,331 @@
+const { InvalidArgumentError } = require('./error.js');
+
+// @ts-check
+
+class Option {
+ /**
+ * Initialize a new `Option` with the given `flags` and `description`.
+ *
+ * @param {string} flags
+ * @param {string} [description]
+ */
+
+ constructor(flags, description) {
+ this.flags = flags;
+ this.description = description || '';
+
+ this.required = flags.includes('<'); // A value must be supplied when the option is specified.
+ this.optional = flags.includes('['); // A value is optional when the option is specified.
+ // variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument
+ this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
+ this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.
+ const optionFlags = splitOptionFlags(flags);
+ this.short = optionFlags.shortFlag;
+ this.long = optionFlags.longFlag;
+ this.negate = false;
+ if (this.long) {
+ this.negate = this.long.startsWith('--no-');
+ }
+ this.defaultValue = undefined;
+ this.defaultValueDescription = undefined;
+ this.presetArg = undefined;
+ this.envVar = undefined;
+ this.parseArg = undefined;
+ this.hidden = false;
+ this.argChoices = undefined;
+ this.conflictsWith = [];
+ this.implied = undefined;
+ }
+
+ /**
+ * Set the default value, and optionally supply the description to be displayed in the help.
+ *
+ * @param {any} value
+ * @param {string} [description]
+ * @return {Option}
+ */
+
+ default(value, description) {
+ this.defaultValue = value;
+ this.defaultValueDescription = description;
+ return this;
+ }
+
+ /**
+ * Preset to use when option used without option-argument, especially optional but also boolean and negated.
+ * The custom processing (parseArg) is called.
+ *
+ * @example
+ * new Option('--color').default('GREYSCALE').preset('RGB');
+ * new Option('--donate [amount]').preset('20').argParser(parseFloat);
+ *
+ * @param {any} arg
+ * @return {Option}
+ */
+
+ preset(arg) {
+ this.presetArg = arg;
+ return this;
+ }
+
+ /**
+ * Add option name(s) that conflict with this option.
+ * An error will be displayed if conflicting options are found during parsing.
+ *
+ * @example
+ * new Option('--rgb').conflicts('cmyk');
+ * new Option('--js').conflicts(['ts', 'jsx']);
+ *
+ * @param {string | string[]} names
+ * @return {Option}
+ */
+
+ conflicts(names) {
+ this.conflictsWith = this.conflictsWith.concat(names);
+ return this;
+ }
+
+ /**
+ * Specify implied option values for when this option is set and the implied options are not.
+ *
+ * The custom processing (parseArg) is not called on the implied values.
+ *
+ * @example
+ * program
+ * .addOption(new Option('--log', 'write logging information to file'))
+ * .addOption(new Option('--trace', 'log extra details').implies({ log: 'trace.txt' }));
+ *
+ * @param {Object} impliedOptionValues
+ * @return {Option}
+ */
+ implies(impliedOptionValues) {
+ let newImplied = impliedOptionValues;
+ if (typeof impliedOptionValues === 'string') {
+ // string is not documented, but easy mistake and we can do what user probably intended.
+ newImplied = { [impliedOptionValues]: true };
+ }
+ this.implied = Object.assign(this.implied || {}, newImplied);
+ return this;
+ }
+
+ /**
+ * Set environment variable to check for option value.
+ *
+ * An environment variable is only used if when processed the current option value is
+ * undefined, or the source of the current value is 'default' or 'config' or 'env'.
+ *
+ * @param {string} name
+ * @return {Option}
+ */
+
+ env(name) {
+ this.envVar = name;
+ return this;
+ }
+
+ /**
+ * Set the custom handler for processing CLI option arguments into option values.
+ *
+ * @param {Function} [fn]
+ * @return {Option}
+ */
+
+ argParser(fn) {
+ this.parseArg = fn;
+ return this;
+ }
+
+ /**
+ * Whether the option is mandatory and must have a value after parsing.
+ *
+ * @param {boolean} [mandatory=true]
+ * @return {Option}
+ */
+
+ makeOptionMandatory(mandatory = true) {
+ this.mandatory = !!mandatory;
+ return this;
+ }
+
+ /**
+ * Hide option in help.
+ *
+ * @param {boolean} [hide=true]
+ * @return {Option}
+ */
+
+ hideHelp(hide = true) {
+ this.hidden = !!hide;
+ return this;
+ }
+
+ /**
+ * @api private
+ */
+
+ _concatValue(value, previous) {
+ if (previous === this.defaultValue || !Array.isArray(previous)) {
+ return [value];
+ }
+
+ return previous.concat(value);
+ }
+
+ /**
+ * Only allow option value to be one of choices.
+ *
+ * @param {string[]} values
+ * @return {Option}
+ */
+
+ choices(values) {
+ this.argChoices = values.slice();
+ this.parseArg = (arg, previous) => {
+ if (!this.argChoices.includes(arg)) {
+ throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(', ')}.`);
+ }
+ if (this.variadic) {
+ return this._concatValue(arg, previous);
+ }
+ return arg;
+ };
+ return this;
+ }
+
+ /**
+ * Return option name.
+ *
+ * @return {string}
+ */
+
+ name() {
+ if (this.long) {
+ return this.long.replace(/^--/, '');
+ }
+ return this.short.replace(/^-/, '');
+ }
+
+ /**
+ * Return option name, in a camelcase format that can be used
+ * as a object attribute key.
+ *
+ * @return {string}
+ * @api private
+ */
+
+ attributeName() {
+ return camelcase(this.name().replace(/^no-/, ''));
+ }
+
+ /**
+ * Check if `arg` matches the short or long flag.
+ *
+ * @param {string} arg
+ * @return {boolean}
+ * @api private
+ */
+
+ is(arg) {
+ return this.short === arg || this.long === arg;
+ }
+
+ /**
+ * Return whether a boolean option.
+ *
+ * Options are one of boolean, negated, required argument, or optional argument.
+ *
+ * @return {boolean}
+ * @api private
+ */
+
+ isBoolean() {
+ return !this.required && !this.optional && !this.negate;
+ }
+}
+
+/**
+ * This class is to make it easier to work with dual options, without changing the existing
+ * implementation. We support separate dual options for separate positive and negative options,
+ * like `--build` and `--no-build`, which share a single option value. This works nicely for some
+ * use cases, but is tricky for others where we want separate behaviours despite
+ * the single shared option value.
+ */
+class DualOptions {
+ /**
+ * @param {Option[]} options
+ */
+ constructor(options) {
+ this.positiveOptions = new Map();
+ this.negativeOptions = new Map();
+ this.dualOptions = new Set();
+ options.forEach(option => {
+ if (option.negate) {
+ this.negativeOptions.set(option.attributeName(), option);
+ } else {
+ this.positiveOptions.set(option.attributeName(), option);
+ }
+ });
+ this.negativeOptions.forEach((value, key) => {
+ if (this.positiveOptions.has(key)) {
+ this.dualOptions.add(key);
+ }
+ });
+ }
+
+ /**
+ * Did the value come from the option, and not from possible matching dual option?
+ *
+ * @param {any} value
+ * @param {Option} option
+ * @returns {boolean}
+ */
+ valueFromOption(value, option) {
+ const optionKey = option.attributeName();
+ if (!this.dualOptions.has(optionKey)) return true;
+
+ // Use the value to deduce if (probably) came from the option.
+ const preset = this.negativeOptions.get(optionKey).presetArg;
+ const negativeValue = (preset !== undefined) ? preset : false;
+ return option.negate === (negativeValue === value);
+ }
+}
+
+/**
+ * Convert string from kebab-case to camelCase.
+ *
+ * @param {string} str
+ * @return {string}
+ * @api private
+ */
+
+function camelcase(str) {
+ return str.split('-').reduce((str, word) => {
+ return str + word[0].toUpperCase() + word.slice(1);
+ });
+}
+
+/**
+ * Split the short and long flag out of something like '-m,--mixed <value>'
+ *
+ * @api private
+ */
+
+function splitOptionFlags(flags) {
+ let shortFlag;
+ let longFlag;
+ // Use original very loose parsing to maintain backwards compatibility for now,
+ // which allowed for example unintended `-sw, --short-word` [sic].
+ const flagParts = flags.split(/[ |,]+/);
+ if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1])) shortFlag = flagParts.shift();
+ longFlag = flagParts.shift();
+ // Add support for lone short flag without significantly changing parsing!
+ if (!shortFlag && /^-[^-]$/.test(longFlag)) {
+ shortFlag = longFlag;
+ longFlag = undefined;
+ }
+ return { shortFlag, longFlag };
+}
+
+exports.Option = Option;
+exports.splitOptionFlags = splitOptionFlags;
+exports.DualOptions = DualOptions;