Parameter name is treated as parameter value
If I omit parameter value for one parameter name, next parametername is treated as value of the parameter. Occurred at v2.9.1 from Nuget.
What I observed
Code:
using System;
using CommandLine;
class Options
{
[Option('a', "option1", Required = true)]
public string Option1 { get; set; }
[Option('b', "option2", Required = false)]
public int option2 { get; set; }
}
class Program
{
static int Main(string[] args)
{
return Parser.Default.ParseArguments<Options>(args)
.MapResult(
options => RunAndReturnExitCode(options),
_ => -1);
}
static int RunAndReturnExitCode(Options options)
{
Console.WriteLine($"option1={options.Option1}, option2={options.option2}");
return 0;
}
}
Command line,
$command --option1 --option2 5
gives the result output,
option1=option2, option2=0
But expected result is to report error with lacking of required "option1".
Suggested fix
In the method "CommandLine.Core.TokenPartitioner.PartitionTokensByType", scalarTokens.add(Scalar nem token) shall be occurred only when the following value is confirmed.
public static Tuple<IEnumerable<Token>, IEnumerable<Token>, IEnumerable<Token>, IEnumerable<Token>> PartitionTokensByType(
IEnumerable<Token> tokens,
Func<string, Maybe<TypeDescriptor>> typeLookup)
{
var switchTokens = new List<Token>();
var scalarTokens = new List<Token>();
var sequenceTokens = new List<Token>();
var nonOptionTokens = new List<Token>();
var sequences = new Dictionary<Token, IList<Token>>();
var count = new Dictionary<Token, int>();
var max = new Dictionary<Token, Maybe<int>>();
var state = SequenceState.TokenSearch;
var separatorSeen = false;
Token nameToken = null;
foreach (var token in tokens)
{
if (token.IsValueForced())
{
separatorSeen = false;
nonOptionTokens.Add(token);
}
else if (token.IsName())
{
separatorSeen = false;
if (typeLookup(token.Text).MatchJust(out var info))
{
switch (info.TargetType)
{
case TargetType.Switch:
nameToken = null;
switchTokens.Add(token);
state = SequenceState.TokenSearch;
break;
case TargetType.Scalar:
nameToken = token;
// scalarTokens.Add(nameToken); <- don't add here
state = SequenceState.ScalarTokenFound;
break;
case TargetType.Sequence:
nameToken = token;
if (! sequences.ContainsKey(nameToken))
{
sequences[nameToken] = new List<Token>();
count[nameToken] = 0;
max[nameToken] = info.MaxItems;
}
state = SequenceState.SequenceTokenFound;
break;
}
}
else
{
nameToken = null;
nonOptionTokens.Add(token);
state = SequenceState.TokenSearch;
}
}
else
{
switch (state)
{
case SequenceState.TokenSearch:
case SequenceState.ScalarTokenFound when nameToken == null:
case SequenceState.SequenceTokenFound when nameToken == null:
separatorSeen = false;
nameToken = null;
nonOptionTokens.Add(token);
state = SequenceState.TokenSearch;
break;
case SequenceState.ScalarTokenFound:
separatorSeen = false;
scalarTokens.Add(nameToken); // add here instead so that scalar name token is valid only when a value is given
nameToken = null;
scalarTokens.Add(token);
state = SequenceState.TokenSearch;
break;
......
confirmed via https://dotnetfiddle.net/apr8W3
See also #861
This also happens with non-consecutive optional args and displays stranger behavior when more options / different types are added.
Value for B is still ignored with boolean (or IEnumerable<T> args) in between.
> Tool.exe --StringA --BoolA --StringB 1
StringA=StringB; StringB=; BoolA=True
Values ignored when either B or D are specified.
> Tool.exe --StringA --StringB 1 --StringC --StringD
StringA=StringB; StringB=; StringC=; StringD=
> Tool.exe --StringA --StringB --StringC --StringD 1
StringA=StringB; StringB=; StringC=StringD; StringD=
But only value for B is ignored and not D when both are specified.
> Tool.exe --StringA --StringB 1 --StringC --StringD 2
StringA=StringB; StringB=; StringC=; StringD=2
I think it would be nice if there was the ability to configure it to throw an exception as some suggest and also an option to allow the missing value and take a default. I have a situation where I have a flag that I want to allow to be optionally supplied with or without a value.
> Foo.exe --string1 --boolean
string1=""; boolean=true
> Foo.exe --string1 "something" --boolean
string1="something"; boolean=true
In this example my string is actually a string? on the option class. That way I have an option the user can pass that acts as an on/off flag that will also take an optional data parameter when turned on.