Skip to content

Commit 2ac4bce

Browse files
committed
bug fixes for #723
1 parent 87000a9 commit 2ac4bce

File tree

11 files changed

+161
-93
lines changed

11 files changed

+161
-93
lines changed

src/System.CommandLine.Rendering.Tests/ViewRenderingTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ public void In_Ansi_mode_ConsoleView_keeps_track_of_position_so_that_multiple_Wr
4141
var renderer = new ConsoleRenderer(
4242
_terminal,
4343
OutputMode.Ansi);
44-
4544

4645
var view = new StringsView(new[] {
4746
"1",

src/System.CommandLine.Tests/ArgumentTests.cs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Generic;
55
using System.CommandLine.Builder;
6+
using System.CommandLine.Help;
67
using System.CommandLine.Invocation;
78
using System.CommandLine.Parsing;
89
using System.IO;
@@ -120,7 +121,7 @@ public void GetDefaultValue_can_return_null()
120121
}
121122

122123
[Fact]
123-
public void Validation_failure_message_can_be_specified()
124+
public void Validation_failure_message_can_be_specified_when_parsing_tokens()
124125
{
125126
var argument = new Argument<FileSystemInfo>(result =>
126127
{
@@ -138,6 +139,46 @@ public void Validation_failure_message_can_be_specified()
138139
.Be("oops!");
139140
}
140141

142+
[Fact]
143+
public void Validation_failure_message_can_be_specified_when_evaluating_default_argument_value()
144+
{
145+
var argument = new Argument<FileSystemInfo>(result =>
146+
{
147+
result.ErrorMessage = "oops!";
148+
return null;
149+
}, true);
150+
151+
argument.Parse("")
152+
.Errors
153+
.Should()
154+
.ContainSingle(e => e.SymbolResult.Symbol == argument)
155+
.Which
156+
.Message
157+
.Should()
158+
.Be("oops!");
159+
}
160+
161+
[Fact]
162+
public void Validation_failure_message_can_be_specified_when_evaluating_default_option_value()
163+
{
164+
var option = new Option<FileSystemInfo>(
165+
"-x",
166+
result =>
167+
{
168+
result.ErrorMessage = "oops!";
169+
return null;
170+
}, true);
171+
172+
option.Parse("")
173+
.Errors
174+
.Should()
175+
.ContainSingle()
176+
.Which
177+
.Message
178+
.Should()
179+
.Be("oops!");
180+
}
181+
141182
[Fact]
142183
public void custom_parsing_of_scalar_value_from_an_argument_with_one_token()
143184
{
@@ -309,6 +350,42 @@ public void Default_value_and_custom_argument_parser_can_be_used_together()
309350
.Should()
310351
.Be(123);
311352
}
353+
354+
[Fact]
355+
public void Multiple_command_arguments_can_have_custom_parse_delegates()
356+
{
357+
var root = new RootCommand
358+
{
359+
new Argument<FileInfo[]>("from", argumentResult =>
360+
{
361+
argumentResult.ErrorMessage = "nope";
362+
return null;
363+
}, true)
364+
{
365+
Arity = new ArgumentArity(0, 2)
366+
},
367+
new Argument<DirectoryInfo>("to", argumentResult =>
368+
{
369+
argumentResult.ErrorMessage = "UH UH";
370+
return null;
371+
}, true)
372+
{
373+
Arity = ArgumentArity.ExactlyOne
374+
}
375+
};
376+
377+
var result = root.Parse("a.txt b.txt /path/to/dir");
378+
379+
result.Errors
380+
.Select(e => e.Message)
381+
.Should()
382+
.Contain("nope");
383+
384+
result.Errors
385+
.Select(e => e.Message)
386+
.Should()
387+
.Contain("UH UH");
388+
}
312389

313390
[Fact]
314391
public void When_custom_conversion_fails_then_an_option_does_not_accept_further_arguments()

src/System.CommandLine.Tests/OptionTests.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -325,14 +325,6 @@ public void Option_T_default_value_is_validated()
325325
.Select(e => e.Message)
326326
.Should()
327327
.BeEquivalentTo(new[] { "ERR" });
328-
329-
option
330-
.Parse("")
331-
.Errors
332-
.Select(e => e.Message)
333-
.Should()
334-
.BeEquivalentTo(new[] { "ERR" });
335-
336328
}
337329

338330
protected override Symbol CreateSymbol(string name) => new Option(name);

src/System.CommandLine.Tests/ParseDiagramTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.CommandLine.Parsing;
5+
using System.IO;
56
using FluentAssertions;
67
using Xunit;
78

@@ -114,5 +115,22 @@ public void Parse_diagram_indicates_which_tokens_were_applied_to_which_command_a
114115
.Should()
115116
.Be("[ the-command [ first <one> ] [ second <two> ] [ third <three> <four> <five> ] ]");
116117
}
118+
119+
[Fact]
120+
public void Parse_diagram_indicates_which_tokens_were_applied_to_which_command_argument_for_sequences_of_complex_types()
121+
{
122+
var command = new Command("the-command")
123+
{
124+
new Argument<FileInfo> { Name = "first" },
125+
new Argument<FileInfo> { Name = "second" },
126+
new Argument<FileInfo[]> { Name = "third" }
127+
};
128+
129+
var result = command.Parse("one two three four five");
130+
131+
result.Diagram()
132+
.Should()
133+
.Be("[ the-command [ first <one> ] [ second <two> ] [ third <three> <four> <five> ] ]");
134+
}
117135
}
118136
}

src/System.CommandLine.Tests/ParserTests.MultipleArguments.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public void Multiple_arguments_of_unspecified_type_are_parsed_correctly()
135135
}
136136
};
137137

138-
var result = root.Parse("src.txt", "dest.txt");
138+
var result = root.Parse("src.txt dest.txt");
139139

140140
result.RootCommandResult
141141
.GetArgumentValueOrDefault("source")
@@ -147,7 +147,6 @@ public void Multiple_arguments_of_unspecified_type_are_parsed_correctly()
147147
.Should()
148148
.Be("dest.txt");
149149
}
150-
151150
}
152151
}
153152
}

src/System.CommandLine.Tests/SymbolResultTests.cs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,6 @@ public void An_option_with_a_default_value_and_no_explicitly_provided_argument_h
2727
result.Tokens.Should().BeEmpty();
2828
}
2929

30-
[Fact]
31-
public void Default_values_are_reevaluated_and_not_cached_between_parses()
32-
{
33-
var option =
34-
new Option("-x")
35-
{
36-
Argument = new Argument
37-
{
38-
Arity = ArgumentArity.ExactlyOne
39-
}
40-
};
41-
42-
var i = 0;
43-
option.Argument.SetDefaultValueFactory(() => ++i);
44-
45-
var result1 = option.Parse("");
46-
var result2 = option.Parse("");
47-
48-
result1.ValueForOption<int>("x").Should().Be(1);
49-
result2.ValueForOption<int>("x").Should().Be(2);
50-
}
51-
5230
[Fact]
5331
public void HasOption_can_be_used_to_check_the_presence_of_an_option()
5432
{

src/System.CommandLine.Tests/UseHelpTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.CommandLine.Invocation;
66
using System.CommandLine.IO;
77
using System.CommandLine.Parsing;
8-
using System.IO;
98
using System.Threading.Tasks;
109
using FluentAssertions;
1110
using Xunit;

src/System.CommandLine/Parsing/ArgumentResult.cs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ internal ArgumentResult(
2626

2727
internal ParseError CustomError(Argument argument)
2828
{
29+
if (!string.IsNullOrEmpty(ErrorMessage))
30+
{
31+
return new ParseError(ErrorMessage, this);
32+
}
33+
2934
foreach (var symbolValidator in argument.Validators)
3035
{
3136
var errorMessage = symbolValidator(this);
@@ -55,9 +60,25 @@ internal virtual ArgumentConversionResult Convert(
5560

5661
if (parentResult.UseDefaultValueFor(argument))
5762
{
58-
var defaultValueFor = parentResult.GetDefaultValueFor(argument);
59-
60-
return ArgumentConversionResult.Success(argument, defaultValueFor);
63+
if (argument is Argument arg)
64+
{
65+
var argumentResult = new ArgumentResult(arg, Parent);
66+
67+
var defaultValue = arg.GetDefaultValue(argumentResult);
68+
69+
if (string.IsNullOrEmpty(argumentResult.ErrorMessage))
70+
{
71+
return ArgumentConversionResult.Success(
72+
argument,
73+
defaultValue);
74+
}
75+
else
76+
{
77+
return ArgumentConversionResult.Failure(
78+
argument,
79+
argumentResult.ErrorMessage);
80+
}
81+
}
6182
}
6283

6384
if (argument is Argument a &&
@@ -107,8 +128,5 @@ bool ShouldCheckArity()
107128
optionResult.IsImplicit);
108129
}
109130
}
110-
111-
internal override object CreateDefaultArgumentResultAndGetItsValue(Argument argument) =>
112-
argument.GetDefaultValue(this);
113131
}
114132
}

src/System.CommandLine/Parsing/ParseResultExtensions.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.Collections;
45
using System.Collections.Generic;
56
using System.CommandLine.Binding;
67
using System.CommandLine.Invocation;
@@ -130,14 +131,18 @@ argumentResult.Argument is Argument argument &&
130131

131132
switch (successful.Value)
132133
{
133-
case null:
134-
case IReadOnlyCollection<string> a when a.Count == 0:
134+
case string s:
135+
builder.Append($"<{s}>");
135136
break;
136-
case IEnumerable<string> args:
137+
138+
case IEnumerable items:
137139
builder.Append("<");
138-
builder.Append(string.Join("> <", args));
140+
builder.Append(
141+
string.Join("> <",
142+
items.Cast<object>().ToArray()));
139143
builder.Append(">");
140144
break;
145+
141146
default:
142147
builder.Append("<");
143148
builder.Append(successful.Value);

src/System.CommandLine/Parsing/ParseResultVisitor.cs

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -277,15 +277,11 @@ private void ValidateOptionResult(OptionResult optionResult)
277277

278278
private void ValidateArgumentResult(ArgumentResult argumentResult)
279279
{
280-
if (_errors.Any())
281-
{
282-
return;
283-
}
284-
285280
if (argumentResult.Argument is Argument argument)
286281
{
287-
var parseError = argumentResult.Parent.UnrecognizedArgumentError(argument) ??
288-
argumentResult.CustomError(argument);
282+
var parseError =
283+
argumentResult.Parent.UnrecognizedArgumentError(argument) ??
284+
argumentResult.CustomError(argument);
289285

290286
if (parseError != null)
291287
{
@@ -325,33 +321,21 @@ private void PopulateDefaultValues()
325321
option.CreateImplicitToken(),
326322
commandResult);
327323

328-
var token = new ImplicitToken(
329-
optionResult.GetDefaultValueFor(option.Argument),
330-
TokenType.Argument);
331-
332-
var childArgumentResult = new ArgumentResult(
333-
option.Argument,
334-
optionResult);
324+
var childArgumentResult = optionResult.GetOrCreateDefaultArgumentResult(
325+
option.Argument);
335326

336327
optionResult.Children.Add(childArgumentResult);
337328
commandResult.Children.Add(optionResult);
338-
optionResult.AddToken(token);
339-
childArgumentResult.AddToken(token);
340329
_rootCommandResult.AddToSymbolMap(optionResult);
341330

342331
break;
343332

344333
case Argument argument when argument.HasDefaultValue:
345334

346-
var implicitToken = new ImplicitToken(argument.GetDefaultValue(), TokenType.Argument);
347-
348-
var argumentResult = new ArgumentResult(
349-
argument,
350-
commandResult);
335+
var argumentResult = commandResult.GetOrCreateDefaultArgumentResult(
336+
argument);
351337

352338
commandResult.Children.Add(argumentResult);
353-
commandResult.AddToken(implicitToken);
354-
argumentResult.AddToken(implicitToken);
355339
_rootCommandResult.AddToSymbolMap(argumentResult);
356340

357341
break;

0 commit comments

Comments
 (0)