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