diff --git a/.gitignore b/.gitignore index 55f900a1..4969d4de 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +37,7 @@ artifacts/* *.DotSettings.user # Visual Studio 2015 cache/options directory .vs/ +# Rider +.idea/ [R|r]elease/** diff --git a/src/CommandLine/Core/Tokenizer.cs b/src/CommandLine/Core/Tokenizer.cs index b71ec7e2..fe94fc61 100644 --- a/src/CommandLine/Core/Tokenizer.cs +++ b/src/CommandLine/Core/Tokenizer.cs @@ -86,29 +86,32 @@ public static Result, Error> ExplodeOptionList( return Result.Succeed(exploded as IEnumerable, tokenizerResult.SuccessMessages()); } + /// + /// Normalizes the given . + /// + /// The given minus all names, and their value if one was present, that are not found using . public static IEnumerable Normalize( IEnumerable tokens, Func nameLookup) { - var indexes = + var toExclude = from i in tokens.Select( (t, i) => { - var prev = tokens.ElementAtOrDefault(i - 1).ToMaybe(); - return t.IsValue() && ((Value)t).ExplicitlyAssigned - && prev.MapValueOrDefault(p => p.IsName() && !nameLookup(p.Text), false) - ? Maybe.Just(i) - : Maybe.Nothing(); + if (t.IsName() == false + || nameLookup(t.Text)) + { + return Maybe.Nothing>(); + } + + var next = tokens.ElementAtOrDefault(i + 1).ToMaybe(); + var removeValue = next.MatchJust(out var nextValue) + && next.MapValueOrDefault(p => p.IsValue() && ((Value)p).ExplicitlyAssigned, false); + return Maybe.Just(new Tuple(t, removeValue ? nextValue : null)); }).Where(i => i.IsJust()) select i.FromJustOrFail(); - var toExclude = - from t in - tokens.Select((t, i) => indexes.Contains(i) ? Maybe.Just(t) : Maybe.Nothing()) - .Where(t => t.IsJust()) - select t.FromJustOrFail(); - - var normalized = tokens.Where(t => toExclude.Contains(t) == false); + var normalized = tokens.Where(t => toExclude.Any(e => ReferenceEquals(e.Item1, t) || ReferenceEquals(e.Item2, t)) == false); return normalized; } diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index a489b741..ea7268be 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -62,12 +62,12 @@ public void Explode_scalar_with_separator_in_even_args_input_returns_sequence() } [Fact] - public void Normalize_should_remove_all_value_with_explicit_assignment_of_existing_name() + public void Normalize_should_remove_all_names_and_values_with_explicit_assignment_of_non_existing_names() { // Fixture setup var expectedTokens = new[] { - Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), - Token.Name("unknown"), Token.Name("switch") }; + Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"), + Token.Name("switch") }; Func nameLookup = name => name.Equals("x") || name.Equals("string-seq") || name.Equals("switch"); @@ -78,7 +78,7 @@ public void Normalize_should_remove_all_value_with_explicit_assignment_of_existi Enumerable.Empty() .Concat( new[] { - Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), + Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"), Token.Name("unknown"), Token.Value("value0", true), Token.Name("switch") }) //,Enumerable.Empty()), , nameLookup); @@ -89,6 +89,34 @@ public void Normalize_should_remove_all_value_with_explicit_assignment_of_existi // Teardown } + [Fact] + public void Normalize_should_remove_all_names_of_non_existing_names() + { + // Fixture setup + var expectedTokens = new[] { + Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"), + Token.Name("switch") }; + Func nameLookup = + name => name.Equals("x") || name.Equals("string-seq") || name.Equals("switch"); + + // Exercize system + var result = + Tokenizer.Normalize( + //Result.Succeed( + Enumerable.Empty() + .Concat( + new[] { + Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"), + Token.Name("unknown"), Token.Name("switch") }) + //,Enumerable.Empty()), + , nameLookup); + + // Verify outcome + result.Should().BeEquivalentTo(expectedTokens); + + // Teardown + } + [Fact] public void Should_properly_parse_option_with_equals_in_value() { diff --git a/tests/CommandLine.Tests/Unit/Issue776Tests.cs b/tests/CommandLine.Tests/Unit/Issue776Tests.cs new file mode 100644 index 00000000..2e247f9b --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue776Tests.cs @@ -0,0 +1,36 @@ +using FluentAssertions; +using Xunit; + +// Issue #776 and #797 +// When IgnoreUnknownArguments is used and there are unknown arguments with explicitly assigned values, other arguments with explicit assigned values should not be influenced. +// The bug only occured when the value was the same for a known and an unknown argument. + +namespace CommandLine.Tests.Unit +{ + public class Issue776Tests + { + [Theory] + [InlineData("3")] + [InlineData("4")] + public void IgnoreUnknownArguments_should_work_for_all_values(string dummyValue) + { + var arguments = new[] { "--cols=4", $"--dummy={dummyValue}" }; + var result = new Parser(with => { with.IgnoreUnknownArguments = true; }) + .ParseArguments(arguments); + + Assert.Empty(result.Errors); + Assert.Equal(ParserResultType.Parsed, result.Tag); + + result.WithParsed(options => + { + options.Cols.Should().Be(4); + }); + } + + private class Options + { + [Option("cols", Required = false)] + public int Cols { get; set; } + } + } +}