Closed nhz-io closed 6 months ago
There is a bug in the code detecting whether the remaining characters in the argument are a number. If the argument ends in a number it is treated as a number, even if there are letters before that.
The search /-?\d+(\.\d*)?(e-?\d+)?$/
is not anchored at the start and thinks 1b2
is a number because it ends in a number. So it treats -a1b2
like -a123
and assigns the "number" to the a
option.
https://github.com/minimistjs/minimist/blob/980d7ac61a0b4bd552711251ac107d506b23e41f/index.js#L159
So, the correct behavior would be to treat everything as boolean flags? (even for repeat sequence) for example :
-a1b22
should produce:
{ '2': true, _:[], a: true, b:true }
Is it correct?
~(I am making a Go port of minimist and wouldnt want to port buggy behavior as well)~ Not anymore. I gave up :)
No, the intended parsing would be to assign the number 22 to the option b
.
Working through argument:
-a1b22
: we have some short optionsa
+ 1b22
: we have an option a
and the rest is not a number, set a
to true1
+ b22
: we have an option 1
, set option 1
to trueb
+ 22
: we have an option b
and the rest is a number, set option b
to 22-a1b22
should produce:
{ '1': true, _: [], a: true, b: 22 }
For comparison, yargs also started from the Optimist code and does not have this issue. Here is the commit with the fix adding the anchor to match the start of the string:
Thank you very much for the assist!
Thanks, a PR would be most appreciated.
Now, another discrepancy
-1
{ '1': true, _: [] }
-1
{ _: [ -1 ] }
Which one is correct? (I prefer minimist handling over yargs-parser tbh)
In this case the parsing of -1
as an option is intended behaviour. Utilities can use a digit as a short option. For example:
ls -1
This is tested here: https://github.com/minimistjs/minimist/blob/980d7ac61a0b4bd552711251ac107d506b23e41f/test/short.js#L8
And another one
-a=b=c
{ _: [], a: 'b' }
-a=b=c
{ _: [], a: 'b=c' }
Please open -a=b=c
as a new issue so it can be tracked separately.
(For interest, I fixed a similar bug in early iteration of parseArgs
! https://github.com/pkgjs/parseargs/pull/43)
Another one, this one seems intentional
-a-b-c
{ _: [], a: '-b-c' }
And in general any first non-alphanumeric occurrence after alphanumeric will be treated as option value.
And no unicode support either
-абв
{ _: [], 'а': 'бв' }
And another edge-case
-=1
{ '1': true, _: [], '=': true }
-a-b-c
I think this is by design.
-абв
This falls under the non-alphanumeric handling of the first case, and probably as intended.
-=1
Not sure this is intentional, but the first character after the -
is assumed to be an option. Likewise for -+
.
Consider the following case:
console.log(require('minimist')(["--no-foo"], {string: ["foo"]}))
Will output:
{ _: [], foo: false }
Getting boolean
, but we have it specified as a string
.
Comes from here: https://github.com/minimistjs/minimist/blob/62fde7d935f83417fb046741531a9e2346a36976/index.js#L157-L159
I think setting false when --no-foo
is specified is probably a feature rather than a bug.
This allows the author to tell the difference between --foo==string
and --foo=
and --no-foo
.
However, I don't think there is a "right" answer as the user did specify string
as you noted so there is a counter argument!
In the absence of a clear bug, I support sticking with the existing behaviour. It does not currently appear in the README or the tests.
Another edge-case, looks like a bug:
console.log(require('minimist')(['--bool', '--str', 'foobar'], {
alias:{str:['bool']}, string:["str"], boolean: true
}))
Results in:
{ _: [ 'foobar' ], bool: [ '', '' ], str: [ '', '' ] }
However, when using explicit assignment, the result is more sensible:
console.log(require('minimist')(['--str=foobar', '--bool'], {
alias:{str:['bool']}, string:["str"], boolean: true
}))
Results in:
{ _: [], str: [ 'foobar', '' ], bool: [ 'foobar', '' ] }
I think this is likely as intended:
boolean: true
means "treat all double hyphenated arguments without equal signs as boolean", so the option does not take an option value from the following argumentopts.string
means always treat as strings
, which in particular disables the automatic conversion of numbersSo --str foobar
is parsed with --str
as a lone option, and the "string" conversion means the value is stored as a string rather than as true
.
Several subtle behaviours in combination!
Yes, but there is an inconsistency between two cases. The one with explicit = assignment and the other one with positional even though they technically mean the same thing
They don't mean the same thing when boolean:true
. Quoting the whole README item:
opts.boolean
- a boolean, string or array of strings to always treat as booleans. iftrue
will treat all double hyphenated arguments without equal signs as boolean (e.g. affects--foo
, not-f
or--foo=bar
)
Yeah, the way you frame it is correct, but the output shows that in both cases both args are not treated as booleans, both time they are treated as strings, but with inconsistent value depending on how you pass your values
I'm comfortable with the current behaviour as being reasonable, but it will be a while before I can double-check my understanding and put a story together.
With the input configuration specifying both opts.boolean:true
and opts.string
the result is almost by definition ambiguous, so that does fit under the issue title of "Ambiguous behaviour", whether or not it is intended. 😄
What i meant by ambiguity is the inconsistent results, whenever the similar arguments are passed.
In both cases the result is an array of strings, but in one case its ['', '']
and in the other its ['foobar', '']
even though both invocation are semantically similar
A parser has to decide how to process --foo bar
. The default approach in minimist is that options are assumed to take an option-argument if one is available, so this is by default parsed like --foo=bar
.
The boolean
configuration option changes this default behaviour. A boolean option is assumed not to take an option argument, so --foo bar
is parsed like bar --foo
.
An argument with an explicit =
like --foo=bar
is parsed as an option and its value, whatever the configuration.
I'm not certain there's anything further to discuss here, so I'll close this. Please file a new issue (one per thing, please) if I'm mistaken!
This is confusing
Case 1: as expected
Case 2: everything turned into boolean flags
Case 3: no more flags, just value again
Case 4: back to booleans
So, when it ends with a letter - its booleans, and when it ends with a number its value Any thoughts?