Skip to content

Commit 40a4104

Browse files
authored
Merge pull request #798 from batzen/fix/issue-776
Fix for #776 and #797
2 parents 9ba45a7 + ff825e5 commit 40a4104

File tree

4 files changed

+86
-17
lines changed

4 files changed

+86
-17
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,7 @@ artifacts/*
3737
*.DotSettings.user
3838
# Visual Studio 2015 cache/options directory
3939
.vs/
40+
# Rider
41+
.idea/
4042

4143
[R|r]elease/**

src/CommandLine/Core/Tokenizer.cs

+16-13
Original file line numberDiff line numberDiff line change
@@ -86,29 +86,32 @@ public static Result<IEnumerable<Token>, Error> ExplodeOptionList(
8686
return Result.Succeed(exploded as IEnumerable<Token>, tokenizerResult.SuccessMessages());
8787
}
8888

89+
/// <summary>
90+
/// Normalizes the given <paramref name="tokens"/>.
91+
/// </summary>
92+
/// <returns>The given <paramref name="tokens"/> minus all names, and their value if one was present, that are not found using <paramref name="nameLookup"/>.</returns>
8993
public static IEnumerable<Token> Normalize(
9094
IEnumerable<Token> tokens, Func<string, bool> nameLookup)
9195
{
92-
var indexes =
96+
var toExclude =
9397
from i in
9498
tokens.Select(
9599
(t, i) =>
96100
{
97-
var prev = tokens.ElementAtOrDefault(i - 1).ToMaybe();
98-
return t.IsValue() && ((Value)t).ExplicitlyAssigned
99-
&& prev.MapValueOrDefault(p => p.IsName() && !nameLookup(p.Text), false)
100-
? Maybe.Just(i)
101-
: Maybe.Nothing<int>();
101+
if (t.IsName() == false
102+
|| nameLookup(t.Text))
103+
{
104+
return Maybe.Nothing<Tuple<Token, Token>>();
105+
}
106+
107+
var next = tokens.ElementAtOrDefault(i + 1).ToMaybe();
108+
var removeValue = next.MatchJust(out var nextValue)
109+
&& next.MapValueOrDefault(p => p.IsValue() && ((Value)p).ExplicitlyAssigned, false);
110+
return Maybe.Just(new Tuple<Token, Token>(t, removeValue ? nextValue : null));
102111
}).Where(i => i.IsJust())
103112
select i.FromJustOrFail();
104113

105-
var toExclude =
106-
from t in
107-
tokens.Select((t, i) => indexes.Contains(i) ? Maybe.Just(t) : Maybe.Nothing<Token>())
108-
.Where(t => t.IsJust())
109-
select t.FromJustOrFail();
110-
111-
var normalized = tokens.Where(t => toExclude.Contains(t) == false);
114+
var normalized = tokens.Where(t => toExclude.Any(e => ReferenceEquals(e.Item1, t) || ReferenceEquals(e.Item2, t)) == false);
112115

113116
return normalized;
114117
}

tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs

+32-4
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ public void Explode_scalar_with_separator_in_even_args_input_returns_sequence()
6262
}
6363

6464
[Fact]
65-
public void Normalize_should_remove_all_value_with_explicit_assignment_of_existing_name()
65+
public void Normalize_should_remove_all_names_and_values_with_explicit_assignment_of_non_existing_names()
6666
{
6767
// Fixture setup
6868
var expectedTokens = new[] {
69-
Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"),
70-
Token.Name("unknown"), Token.Name("switch") };
69+
Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"),
70+
Token.Name("switch") };
7171
Func<string, bool> nameLookup =
7272
name => name.Equals("x") || name.Equals("string-seq") || name.Equals("switch");
7373

@@ -78,7 +78,7 @@ public void Normalize_should_remove_all_value_with_explicit_assignment_of_existi
7878
Enumerable.Empty<Token>()
7979
.Concat(
8080
new[] {
81-
Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"),
81+
Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"),
8282
Token.Name("unknown"), Token.Value("value0", true), Token.Name("switch") })
8383
//,Enumerable.Empty<Error>()),
8484
, nameLookup);
@@ -89,6 +89,34 @@ public void Normalize_should_remove_all_value_with_explicit_assignment_of_existi
8989
// Teardown
9090
}
9191

92+
[Fact]
93+
public void Normalize_should_remove_all_names_of_non_existing_names()
94+
{
95+
// Fixture setup
96+
var expectedTokens = new[] {
97+
Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"),
98+
Token.Name("switch") };
99+
Func<string, bool> nameLookup =
100+
name => name.Equals("x") || name.Equals("string-seq") || name.Equals("switch");
101+
102+
// Exercize system
103+
var result =
104+
Tokenizer.Normalize(
105+
//Result.Succeed(
106+
Enumerable.Empty<Token>()
107+
.Concat(
108+
new[] {
109+
Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"),
110+
Token.Name("unknown"), Token.Name("switch") })
111+
//,Enumerable.Empty<Error>()),
112+
, nameLookup);
113+
114+
// Verify outcome
115+
result.Should().BeEquivalentTo(expectedTokens);
116+
117+
// Teardown
118+
}
119+
92120
[Fact]
93121
public void Should_properly_parse_option_with_equals_in_value()
94122
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using FluentAssertions;
2+
using Xunit;
3+
4+
// Issue #776 and #797
5+
// When IgnoreUnknownArguments is used and there are unknown arguments with explicitly assigned values, other arguments with explicit assigned values should not be influenced.
6+
// The bug only occured when the value was the same for a known and an unknown argument.
7+
8+
namespace CommandLine.Tests.Unit
9+
{
10+
public class Issue776Tests
11+
{
12+
[Theory]
13+
[InlineData("3")]
14+
[InlineData("4")]
15+
public void IgnoreUnknownArguments_should_work_for_all_values(string dummyValue)
16+
{
17+
var arguments = new[] { "--cols=4", $"--dummy={dummyValue}" };
18+
var result = new Parser(with => { with.IgnoreUnknownArguments = true; })
19+
.ParseArguments<Options>(arguments);
20+
21+
Assert.Empty(result.Errors);
22+
Assert.Equal(ParserResultType.Parsed, result.Tag);
23+
24+
result.WithParsed(options =>
25+
{
26+
options.Cols.Should().Be(4);
27+
});
28+
}
29+
30+
private class Options
31+
{
32+
[Option("cols", Required = false)]
33+
public int Cols { get; set; }
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)