1 |
|
2 | const t = require('typical')
|
3 |
|
4 | /**
|
5 | * @module definition
|
6 | */
|
7 |
|
8 | /**
|
9 | * Describes a command-line option. Additionally, you can add `description` and `typeLabel` properties and make use of [command-line-usage](https://github.com/75lb/command-line-usage).
|
10 | * @alias module:definition
|
11 | * @typicalname option
|
12 | */
|
13 | class OptionDefinition {
|
14 | constructor (definition) {
|
15 | /**
|
16 | * The only required definition property is `name`, so the simplest working example is
|
17 | * ```js
|
18 | * [
|
19 | * { name: "file" },
|
20 | * { name: "verbose" },
|
21 | * { name: "depth"}
|
22 | * ]
|
23 | * ```
|
24 | *
|
25 | * In this case, the value of each option will be either a Boolean or string.
|
26 | *
|
27 | * | # | Command line args | .parse() output |
|
28 | * | --- | -------------------- | ------------ |
|
29 | * | 1 | `--file` | `{ file: true }` |
|
30 | * | 2 | `--file lib.js --verbose` | `{ file: "lib.js", verbose: true }` |
|
31 | * | 3 | `--verbose very` | `{ verbose: "very" }` |
|
32 | * | 4 | `--depth 2` | `{ depth: "2" }` |
|
33 | *
|
34 | * Unicode option names and aliases are valid, for example:
|
35 | * ```js
|
36 | * [
|
37 | * { name: 'один' },
|
38 | * { name: '两' },
|
39 | * { name: 'три', alias: 'т' }
|
40 | * ]
|
41 | * ```
|
42 | * @type {string}
|
43 | */
|
44 | this.name = definition.name
|
45 |
|
46 | /**
|
47 | * The `type` value is a setter function (you receive the output from this), enabling you to be specific about the type and value received.
|
48 | *
|
49 | * You can use a class, if you like:
|
50 | *
|
51 | * ```js
|
52 | * const fs = require('fs')
|
53 | *
|
54 | * function FileDetails(filename){
|
55 | * if (!(this instanceof FileDetails)) return new FileDetails(filename)
|
56 | * this.filename = filename
|
57 | * this.exists = fs.existsSync(filename)
|
58 | * }
|
59 | *
|
60 | * const cli = commandLineArgs([
|
61 | * { name: 'file', type: FileDetails },
|
62 | * { name: 'depth', type: Number }
|
63 | * ])
|
64 | * ```
|
65 | *
|
66 | * | # | Command line args| .parse() output |
|
67 | * | --- | ----------------- | ------------ |
|
68 | * | 1 | `--file asdf.txt` | `{ file: { filename: 'asdf.txt', exists: false } }` |
|
69 | *
|
70 | * The `--depth` option expects a `Number`. If no value was set, you will receive `null`.
|
71 | *
|
72 | * | # | Command line args | .parse() output |
|
73 | * | --- | ----------------- | ------------ |
|
74 | * | 2 | `--depth` | `{ depth: null }` |
|
75 | * | 3 | `--depth 2` | `{ depth: 2 }` |
|
76 | *
|
77 | * @type {function}
|
78 | * @default String
|
79 | */
|
80 | this.type = definition.type || String
|
81 |
|
82 | /**
|
83 | * getopt-style short option names. Can be any single character (unicode included) except a digit or hypen.
|
84 | *
|
85 | * ```js
|
86 | * [
|
87 | * { name: "hot", alias: "h", type: Boolean },
|
88 | * { name: "discount", alias: "d", type: Boolean },
|
89 | * { name: "courses", alias: "c" , type: Number }
|
90 | * ]
|
91 | * ```
|
92 | *
|
93 | * | # | Command line | .parse() output |
|
94 | * | --- | ------------ | ------------ |
|
95 | * | 1 | `-hcd` | `{ hot: true, courses: null, discount: true }` |
|
96 | * | 2 | `-hdc 3` | `{ hot: true, discount: true, courses: 3 }` |
|
97 | *
|
98 | * @type {string}
|
99 | */
|
100 | this.alias = definition.alias
|
101 |
|
102 | /**
|
103 | * Set this flag if the option takes a list of values. You will receive an array of values, each passed through the `type` function (if specified).
|
104 | *
|
105 | * ```js
|
106 | * [
|
107 | * { name: "files", type: String, multiple: true }
|
108 | * ]
|
109 | * ```
|
110 | *
|
111 | * | # | Command line | .parse() output |
|
112 | * | --- | ------------ | ------------ |
|
113 | * | 1 | `--files one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` |
|
114 | * | 2 | `--files one.js --files two.js` | `{ files: [ 'one.js', 'two.js' ] }` |
|
115 | * | 3 | `--files *` | `{ files: [ 'one.js', 'two.js' ] }` |
|
116 | *
|
117 | * @type {boolean}
|
118 | */
|
119 | this.multiple = definition.multiple
|
120 |
|
121 | /**
|
122 | * Any unclaimed command-line args will be set on this option. This flag is typically set on the most commonly-used option to make for more concise usage (i.e. `$ myapp *.js` instead of `$ myapp --files *.js`).
|
123 | *
|
124 | * ```js
|
125 | * [
|
126 | * { name: "files", type: String, multiple: true, defaultOption: true }
|
127 | * ]
|
128 | * ```
|
129 | *
|
130 | * | # | Command line | .parse() output |
|
131 | * | --- | ------------ | ------------ |
|
132 | * | 1 | `--files one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` |
|
133 | * | 2 | `one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` |
|
134 | * | 3 | `*` | `{ files: [ 'one.js', 'two.js' ] }` |
|
135 | *
|
136 | * @type {boolean}
|
137 | */
|
138 | this.defaultOption = definition.defaultOption
|
139 |
|
140 | /**
|
141 | * An initial value for the option.
|
142 | *
|
143 | * ```js
|
144 | * [
|
145 | * { name: "files", type: String, multiple: true, defaultValue: [ "one.js" ] },
|
146 | * { name: "max", type: Number, defaultValue: 3 }
|
147 | * ]
|
148 | * ```
|
149 | *
|
150 | * | # | Command line | .parse() output |
|
151 | * | --- | ------------ | ------------ |
|
152 | * | 1 | | `{ files: [ 'one.js' ], max: 3 }` |
|
153 | * | 2 | `--files two.js` | `{ files: [ 'two.js' ], max: 3 }` |
|
154 | * | 3 | `--max 4` | `{ files: [ 'one.js' ], max: 4 }` |
|
155 | *
|
156 | * @type {*}
|
157 | */
|
158 | this.defaultValue = definition.defaultValue
|
159 |
|
160 | /**
|
161 | * When your app has a large amount of options it makes sense to organise them in groups.
|
162 | *
|
163 | * There are two automatic groups: `_all` (contains all options) and `_none` (contains options without a `group` specified in their definition).
|
164 | *
|
165 | * ```js
|
166 | * [
|
167 | * { name: "verbose", group: "standard" },
|
168 | * { name: "help", group: [ "standard", "main" ] },
|
169 | * { name: "compress", group: [ "server", "main" ] },
|
170 | * { name: "static", group: "server" },
|
171 | * { name: "debug" }
|
172 | * ]
|
173 | * ```
|
174 | *
|
175 | *<table>
|
176 | * <tr>
|
177 | * <th>#</th><th>Command Line</th><th>.parse() output</th>
|
178 | * </tr>
|
179 | * <tr>
|
180 | * <td>1</td><td><code>--verbose</code></td><td><pre><code>
|
181 | *{
|
182 | * _all: { verbose: true },
|
183 | * standard: { verbose: true }
|
184 | *}
|
185 | *</code></pre></td>
|
186 | * </tr>
|
187 | * <tr>
|
188 | * <td>2</td><td><code>--debug</code></td><td><pre><code>
|
189 | *{
|
190 | * _all: { debug: true },
|
191 | * _none: { debug: true }
|
192 | *}
|
193 | *</code></pre></td>
|
194 | * </tr>
|
195 | * <tr>
|
196 | * <td>3</td><td><code>--verbose --debug --compress</code></td><td><pre><code>
|
197 | *{
|
198 | * _all: {
|
199 | * verbose: true,
|
200 | * debug: true,
|
201 | * compress: true
|
202 | * },
|
203 | * standard: { verbose: true },
|
204 | * server: { compress: true },
|
205 | * main: { compress: true },
|
206 | * _none: { debug: true }
|
207 | *}
|
208 | *</code></pre></td>
|
209 | * </tr>
|
210 | * <tr>
|
211 | * <td>4</td><td><code>--compress</code></td><td><pre><code>
|
212 | *{
|
213 | * _all: { compress: true },
|
214 | * server: { compress: true },
|
215 | * main: { compress: true }
|
216 | *}
|
217 | *</code></pre></td>
|
218 | * </tr>
|
219 | *</table>
|
220 | *
|
221 | * @type {string|string[]}
|
222 | */
|
223 | this.group = definition.group
|
224 |
|
225 | /* pick up any remaining properties */
|
226 | for (let prop in definition) {
|
227 | if (!this[prop]) this[prop] = definition[prop]
|
228 | }
|
229 | }
|
230 |
|
231 | isBoolean (value) {
|
232 | return this.type === Boolean || (t.isFunction(this.type) && this.type.name === 'Boolean')
|
233 | }
|
234 | }
|
235 |
|
236 | module.exports = OptionDefinition
|