349 lines
11 KiB
Markdown
349 lines
11 KiB
Markdown
|
# jackspeak
|
||
|
|
||
|
A very strict and proper argument parser.
|
||
|
|
||
|
Validate string, boolean, and number options, from the command
|
||
|
line and the environment.
|
||
|
|
||
|
Call the `jack` method with a config object, and then chain
|
||
|
methods off of it.
|
||
|
|
||
|
At the end, call the `.parse()` method, and you'll get an object
|
||
|
with `positionals` and `values` members.
|
||
|
|
||
|
Any unrecognized configs or invalid values will throw an error.
|
||
|
|
||
|
As long as you define configs using object literals, types will
|
||
|
be properly inferred and TypeScript will know what kinds of
|
||
|
things you got.
|
||
|
|
||
|
If you give it a prefix for environment variables, then defaults
|
||
|
will be read from the environment, and parsed values written back
|
||
|
to it, so you can easily pass configs through to child processes.
|
||
|
|
||
|
Automatically generates a `usage`/`help` banner by calling the
|
||
|
`.usage()` method.
|
||
|
|
||
|
Unless otherwise noted, all methods return the object itself.
|
||
|
|
||
|
## USAGE
|
||
|
|
||
|
```js
|
||
|
import { jack } from 'jackspeak'
|
||
|
// this works too:
|
||
|
// const { jack } = require('jackspeak')
|
||
|
|
||
|
const { positionals, values } = jack({ envPrefix: 'FOO' })
|
||
|
.flag({
|
||
|
asdf: { description: 'sets the asfd flag', short: 'a', default: true },
|
||
|
'no-asdf': { description: 'unsets the asdf flag', short: 'A' },
|
||
|
foo: { description: 'another boolean', short: 'f' },
|
||
|
})
|
||
|
.optList({
|
||
|
'ip-addrs': {
|
||
|
description: 'addresses to ip things',
|
||
|
delim: ',', // defaults to '\n'
|
||
|
default: ['127.0.0.1'],
|
||
|
},
|
||
|
})
|
||
|
.parse([
|
||
|
'some',
|
||
|
'positional',
|
||
|
'--ip-addrs',
|
||
|
'192.168.0.1',
|
||
|
'--ip-addrs',
|
||
|
'1.1.1.1',
|
||
|
'args',
|
||
|
'--foo', // sets the foo flag
|
||
|
'-A', // short for --no-asdf, sets asdf flag to false
|
||
|
])
|
||
|
|
||
|
console.log(process.env.FOO_ASDF) // '0'
|
||
|
console.log(process.env.FOO_FOO) // '1'
|
||
|
console.log(values) // {
|
||
|
// 'ip-addrs': ['192.168.0.1', '1.1.1.1'],
|
||
|
// foo: true,
|
||
|
// asdf: false,
|
||
|
// }
|
||
|
console.log(process.env.FOO_IP_ADDRS) // '192.168.0.1,1.1.1.1'
|
||
|
console.log(positionals) // ['some', 'positional', 'args']
|
||
|
```
|
||
|
|
||
|
## `jack(options: JackOptions = {}) => Jack`
|
||
|
|
||
|
Returns a `Jack` object that can be used to chain and add
|
||
|
field definitions. The other methods (apart from `validate()`,
|
||
|
`parse()`, and `usage()` obviously) return the same Jack object,
|
||
|
updated with the new types, so they can be chained together as
|
||
|
shown in the code examples.
|
||
|
|
||
|
Options:
|
||
|
|
||
|
- `allowPositionals` Defaults to true. Set to `false` to not
|
||
|
allow any positional arguments.
|
||
|
|
||
|
- `envPrefix` Set to a string to write configs to and read
|
||
|
configs from the environment. For example, if set to `MY_APP`
|
||
|
then the `foo-bar` config will default based on the value of
|
||
|
`env.MY_APP_FOO_BAR` and will write back to that when parsed.
|
||
|
|
||
|
Boolean values are written as `'1'` and `'0'`, and will be
|
||
|
treated as `true` if they're `'1'` or false otherwise.
|
||
|
|
||
|
Number values are written with their `toString()`
|
||
|
representation.
|
||
|
|
||
|
Strings are just strings.
|
||
|
|
||
|
Any value with `multiple: true` will be represented in the
|
||
|
environment split by a delimiter, which defaults to `\n`.
|
||
|
|
||
|
- `env` The place to read/write environment variables. Defaults
|
||
|
to `process.env`.
|
||
|
|
||
|
- `usage` A short usage string to print at the top of the help
|
||
|
banner.
|
||
|
|
||
|
- `stopAtPositional` Boolean, default false. Stop parsing opts
|
||
|
and flags at the first positional argument. This is useful if
|
||
|
you want to pass certain options to subcommands, like some
|
||
|
programs do, so you can stop parsing and pass the positionals
|
||
|
to the subcommand to parse.
|
||
|
|
||
|
### `Jack.heading(text: string, level?: 1 | 2 | 3 | 4 | 5 | 6)`
|
||
|
|
||
|
Define a short string heading, used in the `usage()` output.
|
||
|
|
||
|
Indentation of the heading and subsequent description/config
|
||
|
usage entries (up until the next heading) is set by the heading
|
||
|
level.
|
||
|
|
||
|
If the first usage item defined is a heading, it is always
|
||
|
treated as level 1, regardless of the argument provided.
|
||
|
|
||
|
Headings level 1 and 2 will have a line of padding underneath
|
||
|
them. Headings level 3 through 6 will not.
|
||
|
|
||
|
### `Jack.description(text: string, { pre?: boolean } = {})`
|
||
|
|
||
|
Define a long string description, used in the `usage()` output.
|
||
|
|
||
|
If the `pre` option is set to `true`, then whitespace will not be
|
||
|
normalized. However, if any line is too long for the width
|
||
|
allotted, it will still be wrapped.
|
||
|
|
||
|
## Option Definitions
|
||
|
|
||
|
Configs are defined by calling the appropriate field definition
|
||
|
method with an object where the keys are the long option name,
|
||
|
and the value defines the config.
|
||
|
|
||
|
Options:
|
||
|
|
||
|
- `type` Only needed for the `addFields` method, as the others
|
||
|
set it implicitly. Can be `'string'`, `'boolean'`, or
|
||
|
`'number'`.
|
||
|
- `multiple` Only needed for the `addFields` method, as the
|
||
|
others set it implicitly. Set to `true` to define an array
|
||
|
type. This means that it can be set on the CLI multiple times,
|
||
|
set as an array in the `values`
|
||
|
and it is represented in the environment as a delimited string.
|
||
|
- `short` A one-character shorthand for the option.
|
||
|
- `description` Some words to describe what this option is and
|
||
|
why you'd set it.
|
||
|
- `hint` (Only relevant for non-boolean types) The thing to show
|
||
|
in the usage output, like `--option=<hint>`
|
||
|
- `validate` A function that returns false (or throws) if an
|
||
|
option value is invalid.
|
||
|
- `default` A default value for the field. Note that this may be
|
||
|
overridden by an environment variable, if present.
|
||
|
|
||
|
### `Jack.flag({ [option: string]: definition, ... })`
|
||
|
|
||
|
Define one or more boolean fields.
|
||
|
|
||
|
Boolean options may be set to `false` by using a
|
||
|
`--no-${optionName}` argument, which will be implicitly created
|
||
|
if it's not defined to be something else.
|
||
|
|
||
|
If a boolean option named `no-${optionName}` with the same
|
||
|
`multiple` setting is in the configuration, then that will be
|
||
|
treated as a negating flag.
|
||
|
|
||
|
### `Jack.flagList({ [option: string]: definition, ... })`
|
||
|
|
||
|
Define one or more boolean array fields.
|
||
|
|
||
|
### `Jack.num({ [option: string]: definition, ... })`
|
||
|
|
||
|
Define one or more number fields. These will be set in the
|
||
|
environment as a stringified number, and included in the `values`
|
||
|
object as a number.
|
||
|
|
||
|
### `Jack.numList({ [option: string]: definition, ... })`
|
||
|
|
||
|
Define one or more number list fields. These will be set in the
|
||
|
environment as a delimited set of stringified numbers, and
|
||
|
included in the `values` as a number array.
|
||
|
|
||
|
### `Jack.opt({ [option: string]: definition, ... })`
|
||
|
|
||
|
Define one or more string option fields.
|
||
|
|
||
|
### `Jack.optList({ [option: string]: definition, ... })`
|
||
|
|
||
|
Define one or more string list fields.
|
||
|
|
||
|
### `Jack.addFields({ [option: string]: definition, ... })`
|
||
|
|
||
|
Define one or more fields of any type. Note that `type` and
|
||
|
`multiple` must be set explicitly on each definition when using
|
||
|
this method.
|
||
|
|
||
|
## Actions
|
||
|
|
||
|
Use these methods on a Jack object that's already had its config
|
||
|
fields defined.
|
||
|
|
||
|
### `Jack.parse(args: string[] = process.argv): { positionals: string[], values: OptionsResults }`
|
||
|
|
||
|
Parse the arguments list, write to the environment if `envPrefix`
|
||
|
is set, and returned the parsed values and remaining positional
|
||
|
arguments.
|
||
|
|
||
|
### `Jack.validate(o: any): asserts o is OptionsResults`
|
||
|
|
||
|
Throws an error if the object provided is not a valid result set,
|
||
|
for the configurations defined thusfar.
|
||
|
|
||
|
### `Jack.usage(): string`
|
||
|
|
||
|
Returns the compiled `usage` string, with all option descriptions
|
||
|
and heading/description text, wrapped to the appropriate width
|
||
|
for the terminal.
|
||
|
|
||
|
### `Jack.setConfigValues(options: OptionsResults, src?: string)`
|
||
|
|
||
|
Validate the `options` argument, and set the default value for
|
||
|
each field that appears in the options.
|
||
|
|
||
|
Values provided will be overridden by environment variables or
|
||
|
command line arguments.
|
||
|
|
||
|
### `Jack.usageMarkdown(): string`
|
||
|
|
||
|
Returns the compiled `usage` string, with all option descriptions
|
||
|
and heading/description text, but as markdown instead of
|
||
|
formatted for a terminal, for generating HTML documentation for
|
||
|
your CLI.
|
||
|
|
||
|
## Some Example Code
|
||
|
|
||
|
Also see [the examples
|
||
|
folder](https://github.com/isaacs/jackspeak/tree/master/examples)
|
||
|
|
||
|
```js
|
||
|
import { jack } from 'jackspeak'
|
||
|
|
||
|
const j = jack({
|
||
|
// Optional
|
||
|
// This will be auto-generated from the descriptions if not supplied
|
||
|
// top level usage line, printed by -h
|
||
|
// will be auto-generated if not specified
|
||
|
usage: 'foo [options] <files>',
|
||
|
})
|
||
|
.heading('The best Foo that ever Fooed')
|
||
|
.description(
|
||
|
`
|
||
|
Executes all the files and interprets their output as
|
||
|
TAP formatted test result data.
|
||
|
|
||
|
To parse TAP data from stdin, specify "-" as a filename.
|
||
|
`
|
||
|
)
|
||
|
|
||
|
// flags don't take a value, they're boolean on or off, and can be
|
||
|
// turned off by prefixing with `--no-`
|
||
|
// so this adds support for -b to mean --bail, or -B to mean --no-bail
|
||
|
.flag({
|
||
|
flag: {
|
||
|
// specify a short value if you like. this must be a single char
|
||
|
short: 'f',
|
||
|
// description is optional as well.
|
||
|
description: `Make the flags wave`,
|
||
|
// default value for flags is 'false', unless you change it
|
||
|
default: true,
|
||
|
},
|
||
|
'no-flag': {
|
||
|
// you can can always negate a flag with `--no-flag`
|
||
|
// specifying a negate option will let you define a short
|
||
|
// single-char option for negation.
|
||
|
short: 'F',
|
||
|
description: `Do not wave the flags`,
|
||
|
},
|
||
|
})
|
||
|
|
||
|
// Options that take a value are specified with `opt()`
|
||
|
.opt({
|
||
|
reporter: {
|
||
|
short: 'R',
|
||
|
description: 'the style of report to display',
|
||
|
},
|
||
|
})
|
||
|
|
||
|
// if you want a number, say so, and jackspeak will enforce it
|
||
|
.num({
|
||
|
jobs: {
|
||
|
short: 'j',
|
||
|
description: 'how many jobs to run in parallel',
|
||
|
default: 1,
|
||
|
},
|
||
|
})
|
||
|
|
||
|
// A list is an option that can be specified multiple times,
|
||
|
// to expand into an array of all the settings. Normal opts
|
||
|
// will just give you the last value specified.
|
||
|
.optList({
|
||
|
'node-arg': {},
|
||
|
})
|
||
|
|
||
|
// a flagList is an array of booleans, so `-ddd` is [true, true, true]
|
||
|
// count the `true` values to treat it as a counter.
|
||
|
.flagList({
|
||
|
debug: { short: 'd' },
|
||
|
})
|
||
|
|
||
|
// opts take a value, and is set to the string in the results
|
||
|
// you can combine multiple short-form flags together, but
|
||
|
// an opt will end the combine chain, posix-style. So,
|
||
|
// -bofilename would be like --bail --output-file=filename
|
||
|
.opt({
|
||
|
'output-file': {
|
||
|
short: 'o',
|
||
|
// optional: make it -o<file> in the help output insead of -o<value>
|
||
|
hint: 'file',
|
||
|
description: `Send the raw output to the specified file.`,
|
||
|
},
|
||
|
})
|
||
|
|
||
|
// now we can parse argv like this:
|
||
|
const { values, positionals } = j.parse(process.argv)
|
||
|
|
||
|
// or decide to show the usage banner
|
||
|
console.log(j.usage())
|
||
|
|
||
|
// or validate an object config we got from somewhere else
|
||
|
try {
|
||
|
j.validate(someConfig)
|
||
|
} catch (er) {
|
||
|
console.error('someConfig is not valid!', er)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Name
|
||
|
|
||
|
The inspiration for this module is [yargs](http://npm.im/yargs), which
|
||
|
is pirate talk themed. Yargs has all the features, and is infinitely
|
||
|
flexible. "Jackspeak" is the slang of the royal navy. This module
|
||
|
does not have all the features. It is declarative and rigid by design.
|