• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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