commandline icon indicating copy to clipboard operation
commandline copied to clipboard

Parameter name is treated as parameter value

Open HidetoshiMiyao opened this issue 3 years ago • 4 comments

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;

                        ......

HidetoshiMiyao avatar Oct 24 '22 08:10 HidetoshiMiyao

confirmed via https://dotnetfiddle.net/apr8W3

ericnewton76 avatar Mar 07 '23 18:03 ericnewton76

See also #861

ermshiperete avatar Mar 30 '23 17:03 ermshiperete

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

nayanshah avatar Sep 27 '23 00:09 nayanshah

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.

wiredwiz avatar Feb 22 '24 04:02 wiredwiz