### Problem Statement(s)
1. There's a difference between an option/flag being declared & it's definition
2. "options" nomenclature was overloaded
3. Inconsistant API (ex. arguments accepted, & values returned, could be a String|Array)
4. Throw if arguments passed don't pass validation/parsing (ie. exception handling)
### Success Criteria
- npm version 9 will adopt this for parsing CLI arguments
- npm version 8 can warn/polyfill to deprecate CLI arg parsing that would differ from this
- jackspeak is reduced to merely a set of argument requirements, types, and validation
Key requirement: easier than `for (const arg of process.mainArgs) { switch (arg) { ... } }`
### Add `process.mainArgs`
This can be done even if the rest of it changes completely or is abandoned, it is unanimously desired by all process.argv parsers right now. Please add it asap.
Implementation:
```javascript
process.mainArgs = process.argv.slice(process._exec ? 1 : 2)
```
```javascript
#!/usr/bin/env node
// foo.js
console.log(process.mainArgs)
```
```
# process.argv.slice(2)
node foo.js a b c
// [ 'a', 'b', 'c' ]
# process.argv.slice(1)
node -p 'process.mainArgs' a b c
// ['a', 'b', 'c']
```
Done, do it, ship it yesterday.
### Isaac's List Of ~~Annoying~~ Important Questions
- Is `cmd --foo=bar baz` the same as `cmd baz --foo=bar`?
- Yes, if `withValue: ['foo']`, otherwise no
- Does the parser execute a function?
- no
- Does the parser execute one of several functions, depending on input?
- no
- Can subcommands take options that are distinct from the main command?
- no (this might be a problem? at least it's a more definitive "opinion")
- Does it output generated help when no options match?
- no
- Does it generated short usage? Like: `usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]`
- no (no usage/help at all)
- Does the user provide the long usage text? For each option? For the whole command?
- no
- Do subcommands (if implemented) have their own usage output?
- no
- Does usage print if the user runs `cmd --help`?
- no
- Does it set `process.exitCode`?
- no
- Does usage print to stderr or stdout?
- N/A
- Does it check types? (Say, specify that an option is a boolean, number, etc.)
- no
- Can an option have more than one type? (string or false, for example)
- no
- Can the user define a type? (Say, `type: path` to call `path.resolve()` on the argument.)
- no
- Does a `--foo=0o22` mean 0, 22, 18, or "0o22"?
- `"0o22"`
- Does it coerce types?
- no
- Does `--no-foo` coerce to `--foo=false`? For all flags? Only boolean flags?
- no, it sets `{args:{'no-foo': true}}`
- Is `--foo` the same as `--foo=true`? Only for known booleans? Only at the end?
- no, `--foo` is the same as `--foo=`
- Does it read environment variables? Ie, is `FOO=1 cmd` the same as `cmd --foo=1`?
- no
- Do unknown arguments raise an error? Are they parsed? Are they treated as positional arguments?
- no, they are parsed, not treated as positionals
- Does `--` signal the end of flags/options?
- **open question**
- If `--` signals the end, is `--` included as a positional? is `program -- foo` the same as `program foo`? Are both `{positionals:['foo']}`, or is the first one `{positionals:['--', 'foo']}`?
- Does the API specify whether a `--` was present/relevant?
- no
- Is `-foo` the same as `--foo`?
- yes <-- ! kind of a blocker for shortopts !
- Recommend: "No, -foo is shortopts form of --f --o --o" (assuming none are defined, or withValues)
- Is `---foo` the same as `--foo`?
- yes (?!)
- other options:
1. `---foo` is a key of `-foo`
2. this is a positional
3. throw (we can't parse it)
- This gets ridiculous: `node prog.js --------------------------------foo`?
- Is `-` a positional? ie, `bash some-test.sh | tap -`
- ??
- Any support for posix-style shortopts? How are they defined?
- **open question**
- Is `-F` the same as `--F`?
- yes
- Is `-AB` the same as `-A -B`? (Ever? Sometimes? Always?)
- no, never
- Is `-AB` the same as `-BA`? (Ever? Sometimes? Always?)
- no, never
- Is `-Bvalue` the same as `-B value`? (Ever? Sometimes? Always?)
- `rm -rf`
- `ls -laF`
- `tap -bcRspec` -> `tap --bail --color --reporter=spec`
- `tap -bxc` === `tap -xbc`, order is irrelevant
- `tap -bxc` -> `tap --bail --x --color`
- in other words:
```javascript
parseArgs(['-bxc'], {
short: { // this should throw if a key has length >1?
b: 'bail',
c: 'color',
},
})
{
args: {
'bail': true,
'x': true,
'color': true,
},
values: {
bail: [undefined],
x: [undefined],
color: [undefined],
},
}
```
- pseudocode:
- position = 0
- while position < string.length
- if (string[position] is a shortopt)
- add expanded form
- if it takes a value
- value is string.slice(position), break
- break
- else
- position++
- continue
- else
- treat it as if it was `{short: { x: 'x' }}`, ie, expand as if it was `--bail --x`
- value is "", key is the single char
- `args.x = true`
-
- Is `-Bvalue` the same as `-B=value`? (Ever? Sometimes? Always?)
- Any support for "set this multiple times for more thingness"? Eg, `foo -v` for some verbosity, `foo -vvvv` for lots of verbosity?
- **open question** - yes, if shortopts
- `{ args, values } = parseArgs(['-vvvv'], { short: { v: 'verbose' }, multiples: ['verbose']})`
`const { values: { verbose: { length: verbosity }}} = { values: { 'verbose': [undefined, undefined, undefined, undefined ] }}`, I would look at `values.verbose.length` for level of verbosity.
`args.verbose = true`
Suggestion: Add a strict mode to throw if unknown args are encountered
```javascript
const result = parseArgs(args, { strict: true })
```
### Potential Solution(s)
1. Split up the existence of an argument from it's value
2. Change names & corresponding documentation
3. Standardize on Arrays
4. Don't coerce values so that there is nothing to validate
```js
const argv = ['--foo=a', '--', '--foo=b', '--bar=baz', '--', 'xyz']
const { args, values, positionals } = parseArgs(argv)
expect({
args: {
foo: true,
},
values: {
foo: ['a'],
},
positionals: [
'--foo=b',
'--bar=baz',
'--',
'xyz'
]
})
npm test --loglevel=silly -- --snapshot=true
const argv = ['--foo=a', 'b', '--foo', 'c']
const { args, values, positionals } = parseArgs(argv)
args // { foo: true }
values // { foo: [''] }
const argv = ['a', '--foo']
const { args, values, positionals } = parseArgs(argv, { withValue: ['foo'] })
// '--foo' === '--foo=' unless followed by positional and withValue:['foo']
const argv = ['--foo', 'a', 'b', '--foo', 'c']
const { args, values, positionals } = parseArgs(argv, { withValue: ['foo'] })
args // { foo: true }
values // { foo: ['c'] }
positionals // []
const argv = ['--foo', 'a', '--foo', 'b']
const { args, values, positionals } = parseArgs(argv, { withValue: ['foo'], multiples: ['foo'] })
args // { foo: true }
values // { foo: ['a', 'b'] }
positionals // []
const argv = ['--foo', 'a', 'b', '--foo', 'c']
const { args, values, positionals } = parseArgs(argv, { withValue: ['foo'], multiples: ['foo'] })
args // { foo: true, bar: true }
values // { foo: ['a', 'c'] }
positionals // ['b']
// node.js --a --b --c
const { args, values, positionals } = util.parseArgs()
args
{
a: true,
b: true,
c: true
}
values
{
a: undefined,
b: undefined,
c: undefined
}
positionals
[]
// if process._exec, default is process.argv.slice(1)
node -p 'require("util").parseArgs()' foo --bar=baz
print:
{
args: {
bar: true,
},
values: {
bar: ['baz']
},
positionals: [
'foo'
]
}
node program.js <top-level opts> subcommand <subcommand opts>
```
```js
// script.js
// called via `node script.js --foo`
const argv = util.parseArgs()
argv.foo // true <- because it's the defaut value
// called via `node script.js --foo=true`
argv.foo // true <- Boolean: because we defined it
// called via `node script.js --foo=hello`
argv.foo // 'hello' <- String: because we defined it
// called via `node script.js`
argv.foo // ... undefined?
// current
const { options, positionals } = util.parseArgs()
// new idea...
const { args, values, positionals } = util.parseArgs()
args // declarations
values // definitions
// --------------------------------------------------
//const { parseArgs } = require('util')
// --------------------------------------------------
const argv = ['--foo=false', '-foo=true']
// new
const { args, values, positionals } = parseArgs(argv, { multiples: ['foo']})
args.foo // true // return boolean
values.foo // ['false', 'true'] // return Array
positionals // Array
// old
const { options, positionals } = parseArgs(argv)
options.foo // false <- do logic...
// --------------------------------------------------
const argv = ['--foo=true']
const { args, values } = parseArgs(argv)
args.foo // true
values.foo // 'true'
// --------------------------------------------------
const argv = ['--foo=bar', '--foo', 'baz']
const { args, values, positionals } = parseArgs(argv)
args.foo // true
values.foo // 'bar'
positonals // ['baz']
// --------------------------------------------------
const argv = ['--foo=bar', '--foo', 'baz']
const { args, values, positionals } = parseArgs(argv)
args.foo // true
values.foo // 'bar'
positionals // ['baz']
// --------------------------------------------------
const argv = ['--foo=bar', '--foo', 'baz']
const { values } = parseArgs(argv, { withValue: ['foo'] })
values.foo // 'baz'
// --------------------------------------------------
const argv = ['--foo=bar', '--foo', 'baz']
const { values } = parseArgs(argv, { multiples: ['foo'] })
values.foo // ['bar', undefined]
// --------------------------------------------------
const argv = ['--file=bar', '--foo', 'baz']
const { values } = parseArgs(argv, { withValue: ['foo'], multiples: ['foo'] })
values.foo // ['bar', 'baz']
// --------------------------------------------------
const argv = ['--foo=bar', '--foo', 'baz']
const { values } = parseArgs(argv, { withValue: ['foo'], multiples: ['foo'] })
values.foo // ['bar', 'baz']
// --------------------------------------------------
// Example of the 99%...
const argv = ['--foo']
const { args } = parseArgs(argv)
args.foo // true
args.bar // undefined
// --------------------------------------------------
npm install express graphql
///
```