UNPKG

7.11 kBJavaScriptView Raw
1'use strict'
2const 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 */
13class 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
236module.exports = OptionDefinition