jackspeak 1.4.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,261 +2,205 @@
2
2
 
3
3
  A very strict and proper argument parser.
4
4
 
5
- ## USAGE
6
-
7
- Pass one or more objects into the exported `jack(...)` function. Each
8
- object can have the following fields, and would typically represent a
9
- "section" in a usage/help output.
5
+ Validate string, boolean, and number options, from the command
6
+ line and the environment.
10
7
 
11
- Using multiple sections allows for using some of the "special" fields
12
- as argument names as well; just put them in different sections.
8
+ Call the `jack` method with a config object, and then chain
9
+ methods off of it.
13
10
 
14
- - `main` Function
11
+ At the end, call the `.parse()` method, and you'll get an object
12
+ with `positionals` and `values` members.
15
13
 
16
- May only appear once. If provided, will be called with the resulting
17
- parsed object.
14
+ Any unrecognized configs or invalid values will throw an error.
18
15
 
19
- Each of the defined flags and options will be set on the result
20
- object, as well as a special `_` array containing all the positional
21
- arguments. `_` also has the following properties:
16
+ As long as you define configs using object literals, types will
17
+ be properly inferred and TypeScript will know what kinds of
18
+ things you got.
22
19
 
23
- - `usage` A function that dumps the help output to stdout.
24
- - `explicit` A `Set` containing the names of all arguments that were
25
- explicitly set.
26
- - `original` The `argv` array before any expansions.
27
- - `parsed` The `argv` array once all aliases have been expanded.
28
- - `reparse` A function that takes a string or array of strings, and
29
- parses it according to the options initially provided. Note that
30
- this does _not_ update the results object, it's just for expanding
31
- aliases and short options.
32
- - `update` A function that takes either a single `--key=val`, an array
33
- of `--key=val` values, or an object (as read from an rc file, for
34
- example), and updates the result data accordingly. This will _not_
35
- update any options set explicitly in the original argv.
20
+ If you give it a prefix for environment variables, then defaults
21
+ will be read from the environment, and parsed values written back
22
+ to it, so you can easily pass configs through to child processes.
36
23
 
37
- - `usage` String or Array
24
+ Automatically generates a `usage`/`help` banner by calling the
25
+ `.usage()` method.
38
26
 
39
- The `Usage: ...` bits that go at the top of the help output
27
+ Unless otherwise noted, all methods return the object itself.
40
28
 
41
- - `description` String
42
-
43
- A heading for the section. Something like `File Options` to
44
- preface all of the options for working with files.
29
+ ## USAGE
45
30
 
46
- - `help` String
31
+ ```js
32
+ import { jack } from 'jackspeak'
33
+ // this works too:
34
+ // const { jack } = require('jackspeak')
35
+
36
+ const { positionals, values } = jack({ envPrefix: 'FOO' })
37
+ .flag({
38
+ asdf: { description: 'sets the asfd flag', short: 'a', default: true },
39
+ 'no-asdf': { description: 'unsets the asdf flag', short: 'A' },
40
+ foo: { description: 'another boolean', short: 'f' },
41
+ })
42
+ .optList({
43
+ 'ip-addrs': {
44
+ description: 'addresses to ip things',
45
+ delim: ',', // defaults to '\n'
46
+ default: ['127.0.0.1'],
47
+ },
48
+ })
49
+ .parse([
50
+ 'some',
51
+ 'positional',
52
+ '--ip-addrs',
53
+ '192.168.0.1',
54
+ '--ip-addrs',
55
+ '1.1.1.1',
56
+ 'args',
57
+ '--foo', // sets the foo flag
58
+ '-A', // short for --no-asdf, sets asdf flag to false
59
+ ])
60
+
61
+ console.log(process.env.FOO_ASDF) // '0'
62
+ console.log(process.env.FOO_FOO) // '1'
63
+ console.log(values) // {
64
+ // 'ip-addrs': ['192.168.0.1', '1.1.1.1'],
65
+ // foo: true,
66
+ // asdf: false,
67
+ // }
68
+ console.log(process.env.FOO_IP_ADDRS) // '192.168.0.1,1.1.1.1'
69
+ console.log(positionals) // ['some', 'positional', 'args']
70
+ ```
47
71
 
48
- A longer-form (multi-paragraph) section of text that explains the
49
- stuff in more details.
72
+ ## `jack(options: JackOptions = {})`
50
73
 
51
- - `argv` Array
74
+ Options:
52
75
 
53
- A list of arguments to parse. If not provided, jackspeak will
54
- pull form `process.argv`. It knows how to skip over the node binary
55
- and main script filename.
76
+ - `allowPositionals` Defaults to true. Set to `false` to not
77
+ allow any positional arguments.
56
78
 
57
- If a section is just an array, then it'll be treated as the argv.
79
+ - `envPrefix` Set to a string to write configs to and read
80
+ configs from the environment. For example, if set to `MY_APP`
81
+ then the `foo-bar` config will default based on the value of
82
+ `env.MY_APP_FOO_BAR` and will write back to that when parsed.
58
83
 
59
- - `env` Object
84
+ Boolean values are written as `'1'` and `'0'`, and will be
85
+ treated as `true` if they're `'1'` or false otherwise.
60
86
 
61
- A set of key-value pairs to pull environment variables from. If
62
- not specified, jackspeak will pull from `process.env`.
87
+ Number values are written with their `toString()`
88
+ representation.
63
89
 
64
- Note that environs are parsed and loaded right away when they are
65
- defined, so you must put `env` on a jackspeak object before
66
- definint any environent
90
+ Strings are just strings.
67
91
 
68
- - One or more argument definition objects. These can be formed using
69
- the functions exported by `require('jackspeak')`. The key is the
70
- full canonical name of the argument as it appears in the parsed
71
- result set.
92
+ Any value with `multiple: true` will be represented in the
93
+ environment split by a delimiter, which defaults to `\n`.
72
94
 
73
- Note that the `--help` flag with the `-h` shorthand will be added
74
- by default, and that `--` will always stop parsing and treat the
75
- rest of the argv as positional arguments. However, `help` and
76
- `--` _may_ be added to a jack section to customize the usage text.
95
+ - `env` The place to read/write environment variables. Defaults
96
+ to `process.env`.
77
97
 
78
- All types can have the following options:
98
+ - `usage` A short usage string to print at the top of the help
99
+ banner.
79
100
 
80
- - `description` - Help text for this option.
101
+ - `stopAtPositional` Boolean, default false. Stop parsing opts
102
+ and flags at the first positional argument. This is useful if
103
+ you want to pass certain options to subcommands, like some
104
+ programs do, so you can stop parsing and pass the positionals
105
+ to the subcommand to parse.
81
106
 
82
- - `hidden` - Do not show this value in the help output.
107
+ ### `Jack.heading(text: string)`
83
108
 
84
- - `implies` - JavaScript object of values to set in the result
85
- objet when this flag or option is encountered in the arguments.
86
- This can be used to have one flag enable another by default, for
87
- example.
109
+ Define a short string heading, used in the `usage()` output.
88
110
 
89
- The types are:
111
+ ### `Jack.description(text: string)`
90
112
 
91
- - `flag(options)` - A boolean value which can be set or unset, but
92
- not given a value.
113
+ Define a long string description, used in the `usage()` output.
93
114
 
94
- Flags can have the following options:
115
+ ## Option Definitions
95
116
 
96
- - `default` - Either `true` or `false`. If unspecified, flags
97
- default to `false`.
117
+ Configs are defined by calling the appropriate field definition
118
+ method with an object where the keys are the long option name,
119
+ and the value defines the config.
98
120
 
99
- - `envDefault` - The name of an environment variable which provides
100
- the default value for this flag. The environment variable will
101
- be parsed as an `env(flag(...))` value, with `'1'` for true and
102
- `'0'` for false.
121
+ Options:
103
122
 
104
- - `short` - A "short" form of the value which is indicated
105
- with a single dash. If `short` is a single character, then
106
- it can be combined gnu-style with other short flags.
123
+ - `type` Only needed for the `addFields` method, as the others
124
+ set it implicitly. Can be `'string'`, `'boolean'`, or
125
+ `'number'`.
126
+ - `multiple` Only needed for the `addFields` method, as the
127
+ others set it implicitly. Set to `true` to define an array
128
+ type. This means that it can be set on the CLI multiple times,
129
+ set as an array in the `values`
130
+ and it is represented in the environment as a delimited string.
131
+ - `short` A one-character shorthand for the option.
132
+ - `description` Some words to describe what this option is and
133
+ why you'd set it.
134
+ - `hint` (Only relevant for non-boolean types) The thing to show
135
+ in the usage output, like `--option=<hint>`
136
+ - `validate` A function that returns false (or throws) if an
137
+ option value is invalid.
138
+ - `default` A default value for the field. Note that this may be
139
+ overridden by an environment variable, if present.
107
140
 
108
- - `negate` - An object defining how the `--no-<whatever>` form
109
- of the flag works. It can have any options that would be
110
- passed to a flag, other than `negate`.
141
+ ### `Jack.flag({ [option: string]: definition, ... })`
111
142
 
112
- For example, it can specify the help text for the negated
113
- form, or provide a different shorthand character. So, for
114
- example, `--color` could have `-c` as a shorthand, and
115
- `--no-color` could be shorthanded to `-C`.
143
+ Define one or more boolean fields.
116
144
 
117
- - `alias` - Either a string or array of what this flag expands
118
- to. This means that the flag key won't have a value, but
119
- will instead be expanded to its alias. To expand an alias
120
- to multiple arguments, use an array. For example, in the
121
- `rsync` program, `-m` expands to `-r -N -l inf
122
- --no-remove-listing`
145
+ Boolean options may be set to `false` by using a
146
+ `--no-${optionName}` argument, which will be implicitly created
147
+ if it's not defined to be something else.
123
148
 
124
- - `opt(options)` - An argument which takes a value.
149
+ If a boolean option named `no-${optionName}` with the same
150
+ `multiple` setting is in the configuration, then that will be
151
+ treated as a negating flag.
125
152
 
126
- Opts can have the following options:
153
+ ### `Jack.flagList({ [option: string]: definition, ... })`
127
154
 
128
- - `default` - A default value. If unspecified, opts default
129
- to `undefined`.
155
+ Define one or more boolean array fields.
130
156
 
131
- - `envDefault` - The name of an environment variable which provides
132
- the default value for this opt.
157
+ ### `Jack.num({ [option: string]: definition, ... })`
133
158
 
134
- - `valid` - An array of valid values. If the user provides a
135
- value outside this set, it will throw an error.
159
+ Define one or more number fields. These will be set in the
160
+ environment as a stringified number, and included in the `values`
161
+ object as a number.
136
162
 
137
- - `alias` - A string or array of options that this option
138
- expands to when used. This works the same as flag aliases,
139
- with the exception that you may include the string
140
- `${value}` in the alias string(s) to substitute in the value
141
- provided to this opt.
163
+ ### `Jack.numList({ [option: string]: definition, ... })`
142
164
 
143
- For example, `--big=<n>` could be an alias for
144
- `--font-size=<n> --bold` by doing:
165
+ Define one or more number list fields. These will be set in the
166
+ environment as a delimited set of stringified numbers, and
167
+ included in the `values` as a number array.
145
168
 
146
- ```js
147
- jack({
148
- big: opt({
149
- alias: ['--font-size=${value}', '--bold']
150
- })
151
- })
152
- ```
153
- - `hint` - A string to use in the help output as the value
154
- provided to the opt. For example, if you wanted to print
155
- `--output=<file>`, then you'd set `hint: 'file'` here.
156
- Defaults to the opt name.
169
+ ### `Jack.opt({ [option: string]: definition, ... })`
157
170
 
158
- - `short` - A "short" form of the opt which is indicated
159
- with a single dash. If `short` is a single character, then
160
- it can be combined gnu-style with short flags, and take a
161
- value without an `=` character.
171
+ Define one or more string option fields.
162
172
 
163
- For example, in [tap](https://www.node-tap.org), `-bRspec`
164
- is equivalent to `--bail --reporter=spec`.
173
+ ### `Jack.optList({ [option: string]: definition, ... })`
165
174
 
166
- - `num(options)` - An `opt` that is a number. This will be
167
- provided in the result as an actual number (rather than a
168
- string) and will raise an error if given a non-numeric value.
175
+ Define one or more string list fields.
169
176
 
170
- This is numericized by using the `+` operator, so any
171
- JavaScript number represenation will do.
177
+ ### `Jack.addFields({ [option: string]: definition, ... })`
172
178
 
173
- All of the `opt()` options are supported, plus these:
179
+ Define one or more fields of any type. Note that `type` and
180
+ `multiple` must be set explicitly on each definition when using
181
+ this method.
174
182
 
175
- - `min` - A number that this value cannot be smaller than.
176
- - `max` - A number that this value cannot be larger than.
183
+ ## Actions
177
184
 
178
- - `list(options)` - An option which can take multiple values by
179
- being specified multiple times, and is represented in the result
180
- object as an array of values. If the list is not present in the
181
- arguments, then it will be an empty array.
185
+ Use these methods on a Jack object that's already had its config
186
+ fields defined.
182
187
 
183
- - `count(options)` - A flag which can be set multiple times to
184
- increase a value. Unsetting decrements the value, setting
185
- increments it. This can be useful for things like `-v` to set a
186
- verbosity level, or `-d` to set a debug level.
188
+ ### `Jack.parse(args: string[] = process.argv): { positionals: string[], values: OptionsResults }`
187
189
 
188
- Counts always default to 0.
190
+ Parse the arguments list, write to the environment if `envPrefix`
191
+ is set, and returned the parsed values and remaining positional
192
+ arguments.
189
193
 
190
- Note that a `count` is actually a flag that can be set
191
- multiple times. Thus, it is a composition of the `list` and
192
- `flag` types.
194
+ ### `Jack.validate(o: any): asserts o is OptionsResults`
193
195
 
194
- - `env(options)` - An environment variable that the program is
195
- interested in.
196
-
197
- All environment variables will be present in the result
198
- object. `env()` can be composed with other types to change
199
- how the environment variable is handled.
200
-
201
- - Compose with `flag()` to define an environment variable that
202
- is set to `'1'` to mean `true`, or `'0'` or `''` to mean
203
- `false`. Presented in the result object as a boolean value.
204
- For example:
205
-
206
- ```js
207
- jack({
208
- FOO: env(flag({
209
- description: 'Set to "1" to enable the foo flag'
210
- }))
211
- })
212
- ```
213
-
214
- - Compose with `list()` to define an environment variable that
215
- is set to multiple values separated by a delimiter. For
216
- example:
217
-
218
- ```js
219
- jack({
220
- NODE_DEBUG: env(list({
221
- delimiter: ',',
222
- description: 'Define which bits to debug'
223
- }))
224
- })
225
- ```
226
-
227
- This can be further composed with `num` to pass in a list
228
- of numbers separated by a delimiter.
229
-
230
- When composed with `count` (which is the composition of
231
- `list` and `flag`), you would pass in a delimited list of
232
- `1` and `0` characters, and it'd count up the `1` values.
233
- I don't know why you'd ever do this, but it works.
234
-
235
- - Compose with `num()` to parse the environ as a numeric
236
- value, and raise an error if it is non-numeric.
237
-
238
- ### Type Composition
239
-
240
- Compose types by applying more than one function to the arg
241
- definition options. For example, for a numeric environment
242
- variable, you can do:
196
+ Throws an error if the object provided is not a valid result set,
197
+ for the configurations defined thusfar.
243
198
 
244
- ```js
245
- jack({
246
- HOW_MANY_FOOS: env(num({
247
- description: 'set to define the number of foos'
248
- max: 10,
249
- min: 2,
250
- default: 5,
251
- }))
252
- })
253
- ```
199
+ ### `Jack.usage()`
254
200
 
255
- The order of composition does not matter in normal cases, but note
256
- that some compositions will contradict one another. For example,
257
- composing `flag` (an argument that does not take a value) with `opt`
258
- (an argument that _does_ take a value) will result in the outermost
259
- function taking precedence.
201
+ Returns the compiled `usage` string, with all option descriptions
202
+ and heading/description text, wrapped to the appropriate width
203
+ for the terminal.
260
204
 
261
205
  ## Some Example Code
262
206
 
@@ -264,138 +208,104 @@ Also see [the examples
264
208
  folder](https://github.com/isaacs/jackspeak/tree/master/examples)
265
209
 
266
210
  ```js
267
- const { jack, flag, opt, list, count, num } = require('jackspeak')
268
-
269
- jack({
270
- // Optional
271
- // the function to call with the options argument when it's all done
272
- // if not provided, then jack() will return the parsed options
273
- // if any unknown options are passed in, then it'll abort with
274
- // the usage output and an error message
275
- main: myFunction,
276
-
277
- // Optional
278
- // defaults to process.argv, and slices off the first item if
279
- // it's process.execPath and the second item if it's
280
- // require.main.filename
281
- argv: process.argv,
211
+ import { jack } from 'jackspeak'
282
212
 
213
+ const j = jack({
283
214
  // Optional
284
215
  // This will be auto-generated from the descriptions if not supplied
285
216
  // top level usage line, printed by -h
286
217
  // will be auto-generated if not specified
287
218
  usage: 'foo [options] <files>',
288
-
289
- // Optional
290
- // longer-form help text
291
- // will be reformatted and wrapped to terminal column width,
292
- // so go ahead and format it however you like here.
293
- help: `
219
+ })
220
+ .heading('The best Foo that ever Fooed')
221
+ .description(`
294
222
  Executes all the files and interprets their output as
295
223
  TAP formatted test result data.
296
224
 
297
225
  To parse TAP data from stdin, specify "-" as a filename.
298
- `
226
+ `)
299
227
 
300
228
  // flags don't take a value, they're boolean on or off, and can be
301
229
  // turned off by prefixing with `--no-`
302
230
  // so this adds support for -b to mean --bail, or -B to mean --no-bail
303
- flag: flag({
304
- // specify a short value if you like. this must be a single char
305
- short: 'f',
306
- // description is optional as well.
307
- description: `Make the flags wave`,
308
- // you can can always negate a flag with `--no-flag`
309
- // specifying a negate option will let you define a short
310
- // single-char option for negation.
311
- negate: {
231
+ .flag({
232
+ flag: {
233
+ // specify a short value if you like. this must be a single char
234
+ short: 'f',
235
+ // description is optional as well.
236
+ description: `Make the flags wave`,
237
+ // default value for flags is 'false', unless you change it
238
+ default: true
239
+ },
240
+ 'no-flag': {
241
+ // you can can always negate a flag with `--no-flag`
242
+ // specifying a negate option will let you define a short
243
+ // single-char option for negation.
312
244
  short: 'F',
313
245
  description: `Do not wave the flags`
314
246
  },
315
- // default value for flags is 'false', unless you change it
316
- default: true
317
- }),
247
+ })
318
248
 
319
249
  // Options that take a value are specified with `opt()`
320
- reporter: opt({
321
- short: 'R',
322
- description: 'the style of report to display',
250
+ .opt({
251
+ reporter: {
252
+ short: 'R',
253
+ description: 'the style of report to display',
254
+ },
323
255
  })
324
256
 
325
257
  // if you want a number, say so, and jackspeak will enforce it
326
- jobs: num({
327
- short: 'j',
328
- description: 'how many jobs to run in parallel',
329
- default: 1
330
- }),
331
-
332
- // Aliases can be a flag or option that expands to
333
- // some other value when used.
334
- 'jobs-auto': flag({
335
- short: 'J',
336
- alias: '--jobs=' + require('os').cpus().length
337
- }),
338
-
339
- // you can also set defaults with an environ of course
340
- timeout: num({
341
- short: 't',
342
- default: +process.env.TAP_TIMEOUT || 30,
343
- }),
344
-
345
- // this makes --no-timeout equivalue to setting timeout to zero
346
- 'no-timeout': flag({
347
- short: 'T',
348
- alias: '--timeout=0'
349
- }),
258
+ .num({
259
+ jobs: {
260
+ short: 'j',
261
+ description: 'how many jobs to run in parallel',
262
+ default: 1
263
+ },
264
+ })
350
265
 
351
266
  // A list is an option that can be specified multiple times,
352
267
  // to expand into an array of all the settings. Normal opts
353
268
  // will just give you the last value specified.
354
- 'node-arg': list(),
355
-
356
- // A counter is a flag that increments or decrements its value
357
- // each time it's specified.
358
- // In this case, `-ddd` would return { debug: 3 } in the result
359
- debug: count({
360
- short: 'd'
269
+ .optList({
270
+ 'node-arg': {},
361
271
  })
362
272
 
363
- // an alias can expand to multiple things, not just one
364
- foo: flag({
365
- alias: ['--statements=100', '--lines=100', '--branches=100'],
366
- }),
367
-
368
- // An option alias can take a value and use it in the expansion.
369
- // use `${value}` in the alias to sub in what the user provides
370
- covlevel: opt({
371
- alias: [
372
- '--statements=${value}',
373
- '--lines=${value}',
374
- '--branches=${value}'
375
- ]
376
- }),
377
-
378
- // aliases can recurse, as well
379
- 100: flag({
380
- alias: '--covlevel=100'
381
- }),
273
+ // a flagList is an array of booleans, so `-ddd` is [true, true, true]
274
+ // count the `true` values to treat it as a counter.
275
+ .flagList({
276
+ debug: { short: 'd' }
277
+ })
382
278
 
383
279
  // opts take a value, and is set to the string in the results
384
280
  // you can combine multiple short-form flags together, but
385
281
  // an opt will end the combine chain, posix-style. So,
386
282
  // -bofilename would be like --bail --output-file=filename
387
- 'output-file': opt({
388
- short: 'o',
389
- // optional: make it -o<file> in the help output insead of -o<value>
390
- hint: 'file',
391
- description: `Send the raw output to the specified file.`
392
- }),
393
- })
283
+ .opt({
284
+ 'output-file': {
285
+ short: 'o',
286
+ // optional: make it -o<file> in the help output insead of -o<value>
287
+ hint: 'file',
288
+ description: `Send the raw output to the specified file.`
289
+ }
290
+ })
291
+
292
+ // now we can parse argv like this:
293
+ const { values, positionals } = j.parse(process.argv)
294
+
295
+ // or decide to show the usage banner
296
+ console.log(j.usage())
297
+
298
+ // or validate an object config we got from somewhere else
299
+ try {
300
+ j.validate(someConfig)
301
+ } catch (er) {
302
+ console.error('someConfig is not valid!', er)
303
+ }
394
304
  ```
395
305
 
396
306
  ## Name
397
307
 
398
308
  The inspiration for this module is [yargs](http://npm.im/yargs), which
399
- is pirate talk themed. Yargs has all the features, and is infinitely
400
- flexible. "Jackspeak" is the slang of the royal navy. This module
401
- does not have all the features. It is declarative and rigid by design.
309
+ is pirate talk themed. Yargs has all the features, and is infinitely
310
+ flexible. "Jackspeak" is the slang of the royal navy. This module
311
+ does not have all the features. It is declarative and rigid by design.