1const { InvalidArgumentError } = require('./error.js'); 2 3// @ts-check 4 5class Argument { 6 /** 7 * Initialize a new command argument with the given name and description. 8 * The default is that the argument is required, and you can explicitly 9 * indicate this with <> around the name. Put [] around the name for an optional argument. 10 * 11 * @param {string} name 12 * @param {string} [description] 13 */ 14 15 constructor(name, description) { 16 this.description = description || ''; 17 this.variadic = false; 18 this.parseArg = undefined; 19 this.defaultValue = undefined; 20 this.defaultValueDescription = undefined; 21 this.argChoices = undefined; 22 23 switch (name[0]) { 24 case '<': // e.g. <required> 25 this.required = true; 26 this._name = name.slice(1, -1); 27 break; 28 case '[': // e.g. [optional] 29 this.required = false; 30 this._name = name.slice(1, -1); 31 break; 32 default: 33 this.required = true; 34 this._name = name; 35 break; 36 } 37 38 if (this._name.length > 3 && this._name.slice(-3) === '...') { 39 this.variadic = true; 40 this._name = this._name.slice(0, -3); 41 } 42 } 43 44 /** 45 * Return argument name. 46 * 47 * @return {string} 48 */ 49 50 name() { 51 return this._name; 52 } 53 54 /** 55 * @api private 56 */ 57 58 _concatValue(value, previous) { 59 if (previous === this.defaultValue || !Array.isArray(previous)) { 60 return [value]; 61 } 62 63 return previous.concat(value); 64 } 65 66 /** 67 * Set the default value, and optionally supply the description to be displayed in the help. 68 * 69 * @param {any} value 70 * @param {string} [description] 71 * @return {Argument} 72 */ 73 74 default(value, description) { 75 this.defaultValue = value; 76 this.defaultValueDescription = description; 77 return this; 78 } 79 80 /** 81 * Set the custom handler for processing CLI command arguments into argument values. 82 * 83 * @param {Function} [fn] 84 * @return {Argument} 85 */ 86 87 argParser(fn) { 88 this.parseArg = fn; 89 return this; 90 } 91 92 /** 93 * Only allow argument value to be one of choices. 94 * 95 * @param {string[]} values 96 * @return {Argument} 97 */ 98 99 choices(values) { 100 this.argChoices = values.slice(); 101 this.parseArg = (arg, previous) => { 102 if (!this.argChoices.includes(arg)) { 103 throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(', ')}.`); 104 } 105 if (this.variadic) { 106 return this._concatValue(arg, previous); 107 } 108 return arg; 109 }; 110 return this; 111 } 112 113 /** 114 * Make argument required. 115 */ 116 argRequired() { 117 this.required = true; 118 return this; 119 } 120 121 /** 122 * Make argument optional. 123 */ 124 argOptional() { 125 this.required = false; 126 return this; 127 } 128} 129 130/** 131 * Takes an argument and returns its human readable equivalent for help usage. 132 * 133 * @param {Argument} arg 134 * @return {string} 135 * @api private 136 */ 137 138function humanReadableArgName(arg) { 139 const nameOutput = arg.name() + (arg.variadic === true ? '...' : ''); 140 141 return arg.required 142 ? '<' + nameOutput + '>' 143 : '[' + nameOutput + ']'; 144} 145 146exports.Argument = Argument; 147exports.humanReadableArgName = humanReadableArgName; 148