Skip to content

Commit 704e640

Browse files
authored
Expose Command collection properties as mutable IList, remove AddCommand, AddOption and AddArugment methods (#1989)
* expose Command.Arguments/Options/Subcommands as a mutable List use a custom type that implements IList to ensure that on every added element the parent is set as well * replace Add(Option), Add(Command) and Add(Argument) methods with Add(Symbol) * remove AddArguments, use Arguments.Add instead * remove AddOption, use Options.Add instead * remove AddCommand, use Subcommands.Add instead * hide Command.Add(Symbol) from intellisense (it's public only for C# duck typing)
1 parent d5f3c3b commit 704e640

27 files changed

+200
-163
lines changed

src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,15 @@ System.CommandLine
4646
public static Argument<T> AcceptExistingOnly<T>(this Argument<T> argument)
4747
public class Command : IdentifierSymbol, System.Collections.Generic.IEnumerable<Symbol>, System.Collections.IEnumerable
4848
.ctor(System.String name, System.String description = null)
49-
public System.Collections.Generic.IReadOnlyList<Argument> Arguments { get; }
49+
public System.Collections.Generic.IList<Argument> Arguments { get; }
5050
public System.Collections.Generic.IEnumerable<Symbol> Children { get; }
5151
public ICommandHandler Handler { get; set; }
52-
public System.Collections.Generic.IReadOnlyList<Option> Options { get; }
53-
public System.Collections.Generic.IReadOnlyList<Command> Subcommands { get; }
52+
public System.Collections.Generic.IList<Option> Options { get; }
53+
public System.Collections.Generic.IList<Command> Subcommands { get; }
5454
public System.Boolean TreatUnmatchedTokensAsErrors { get; set; }
5555
public System.Collections.Generic.List<System.Action<System.CommandLine.Parsing.CommandResult>> Validators { get; }
56-
public System.Void Add(Option option)
57-
public System.Void Add(Argument argument)
58-
public System.Void Add(Command command)
59-
public System.Void AddArgument(Argument argument)
60-
public System.Void AddCommand(Command command)
56+
public System.Void Add(Symbol symbol)
6157
public System.Void AddGlobalOption(Option option)
62-
public System.Void AddOption(Option option)
6358
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.CommandLine.Completions.CompletionContext context)
6459
public System.Collections.Generic.IEnumerator<Symbol> GetEnumerator()
6560
public static class CommandExtensions

src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_CustomScenarios.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ public void SetupOneOptWithNestedCommand()
2121
var rootCommand = new Command("root_command");
2222
var nestedCommand = new Command("nested_command");
2323
var option = new Option<int>("-opt1", () => 123);
24-
nestedCommand.AddOption(option);
25-
rootCommand.AddCommand(nestedCommand);
24+
nestedCommand.Options.Add(option);
25+
rootCommand.Subcommands.Add(nestedCommand);
2626

2727
_testParser = new Parser(rootCommand);
2828
_testSymbolsAsString = "root_command nested_command -opt1 321";

src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_NestedCommands.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ private void GenerateTestNestedCommands(Command parent, int depth, int countPerL
4040
{
4141
string cmdName = $"{parent.Name}_{depth}.{i}";
4242
Command cmd = new(cmdName);
43-
parent.AddCommand(cmd);
43+
parent.Subcommands.Add(cmd);
4444
GenerateTestNestedCommands(cmd, depth - 1, countPerLevel);
4545
}
4646
}

src/System.CommandLine.Benchmarks/CommandLine/Perf_Suggestions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public void Setup_FromParseResult()
5454

5555
foreach (var option in GenerateOptionsArray(TestSuggestionsCount))
5656
{
57-
testCommand.AddOption(option);
57+
testCommand.Options.Add(option);
5858
}
5959

6060
_testParseResult = testCommand.Parse("--wrong");

src/System.CommandLine.DragonFruit/CommandLine.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,12 @@ public static void ConfigureFromMethod(
160160

161161
foreach (var option in method.BuildOptions())
162162
{
163-
command.AddOption(option);
163+
command.Options.Add(option);
164164
}
165165

166166
if (method.GetParameters().FirstOrDefault(p => _argumentParameterNames.Contains(p.Name)) is { } argsParam)
167167
{
168-
command.AddArgument(ArgumentBuilder.CreateArgument(argsParam));
168+
command.Arguments.Add(ArgumentBuilder.CreateArgument(argsParam));
169169
}
170170

171171
command.Handler = CommandHandler.Create(method, target);

src/System.CommandLine.Generator.Tests/GeneratedCommandHandlerTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,9 @@ void Execute(Character character, IConsole console)
9595

9696
var command = new Command("command");
9797
var nameOption = new Option<string>("--name");
98-
command.AddOption(nameOption);
98+
command.Options.Add(nameOption);
9999
var ageOption = new Option<int>("--age");
100-
command.AddOption(ageOption);
100+
command.Options.Add(ageOption);
101101

102102
command.SetHandler<Action<Character, IConsole>>(Execute, nameOption, ageOption);
103103

@@ -118,9 +118,9 @@ int Execute(int first, int second)
118118

119119
var command = new Command("add");
120120
var firstArgument = new Argument<int>("first");
121-
command.AddArgument(firstArgument);
121+
command.Arguments.Add(firstArgument);
122122
var secondArgument = new Argument<int>("second");
123-
command.AddArgument(secondArgument);
123+
command.Arguments.Add(secondArgument);
124124

125125
command.SetHandler<Func<int, int, int>>(Execute, firstArgument, secondArgument);
126126

@@ -243,12 +243,12 @@ void Execute2(string value)
243243

244244
var command1 = new Command("first");
245245
var argument1 = new Argument<string>("first-value");
246-
command1.AddArgument(argument1);
246+
command1.Arguments.Add(argument1);
247247
command1.SetHandler<Action<string>>(Execute1, argument1);
248248

249249
var command2 = new Command("second");
250250
var argument2 = new Argument<string>("second-value");
251-
command2.AddArgument(argument2);
251+
command2.Arguments.Add(argument2);
252252
command2.SetHandler<Action<string>>(Execute2, argument2);
253253

254254
await command1.InvokeAsync("first v1", _console);

src/System.CommandLine.Hosting.Tests/HostingHandlerTest.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public static async Task Can_have_diferent_handlers_based_on_command()
5959
{
6060
var root = new RootCommand();
6161

62-
root.AddCommand(new MyCommand());
63-
root.AddCommand(new MyOtherCommand());
62+
root.Subcommands.Add(new MyCommand());
63+
root.Subcommands.Add(new MyOtherCommand());
6464
var parser = new CommandLineBuilder(root)
6565
.UseHost(host =>
6666
{
@@ -90,7 +90,7 @@ public static async Task Can_bind_to_arguments_via_injection()
9090
{
9191
var service = new MyService();
9292
var cmd = new RootCommand();
93-
cmd.AddCommand(new MyOtherCommand());
93+
cmd.Subcommands.Add(new MyOtherCommand());
9494
var parser = new CommandLineBuilder(cmd)
9595
.UseHost(host =>
9696
{
@@ -113,8 +113,8 @@ public static async Task Invokes_DerivedClass()
113113
var service = new MyService();
114114

115115
var cmd = new RootCommand();
116-
cmd.AddCommand(new MyCommand());
117-
cmd.AddCommand(new MyOtherCommand());
116+
cmd.Subcommands.Add(new MyCommand());
117+
cmd.Subcommands.Add(new MyOtherCommand());
118118
var parser = new CommandLineBuilder(cmd)
119119
.UseHost((builder) => {
120120
builder.ConfigureServices(services =>
@@ -155,7 +155,7 @@ public class MyCommand : Command
155155
{
156156
public MyCommand() : base(name: "mycommand")
157157
{
158-
AddOption(new Option<int>("--int-option")); // or nameof(Handler.IntOption).ToKebabCase() if you don't like the string literal
158+
Options.Add(new Option<int>("--int-option")); // or nameof(Handler.IntOption).ToKebabCase() if you don't like the string literal
159159
}
160160

161161
public class MyHandler : ICommandHandler
@@ -204,8 +204,8 @@ public class MyOtherCommand : Command
204204
{
205205
public MyOtherCommand() : base(name: "myothercommand")
206206
{
207-
AddOption(new Option<int>("--int-option")); // or nameof(Handler.IntOption).ToKebabCase() if you don't like the string literal
208-
AddArgument(new Argument<string>("One"));
207+
Options.Add(new Option<int>("--int-option")); // or nameof(Handler.IntOption).ToKebabCase() if you don't like the string literal
208+
Arguments.Add(new Argument<string>("One"));
209209
}
210210

211211
public class MyHandler : ICommandHandler

src/System.CommandLine.Hosting.Tests/HostingTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ public static void UseHost_binds_parsed_arguments_to_options()
202202
MyOptions options = null;
203203

204204
var rootCmd = new RootCommand();
205-
rootCmd.AddOption(new Option<int>($"-{nameof(MyOptions.MyArgument)}"));
205+
rootCmd.Options.Add(new Option<int>($"-{nameof(MyOptions.MyArgument)}"));
206206
rootCmd.Handler = CommandHandler.Create((IHost host) =>
207207
{
208208
options = host.Services

src/System.CommandLine.NamingConventionBinder.Tests/ModelBinderTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public void Explicitly_configured_default_values_can_be_bound_by_name_to_constru
101101
var option = new Option<string>("--string-option", () => "the default");
102102

103103
var command = new Command("the-command");
104-
command.AddOption(option);
104+
command.Options.Add(option);
105105
var binder = new ModelBinder(typeof(ClassWithMultiLetterCtorParameters));
106106

107107
var parser = new Parser(command);
@@ -176,7 +176,7 @@ public void Types_having_constructors_accepting_a_single_string_are_bound_using_
176176
var option = new Option<DirectoryInfo>("--value");
177177

178178
var command = new Command("the-command");
179-
command.AddOption(option);
179+
command.Options.Add(option);
180180
var binder = new ModelBinder(typeof(ClassWithCtorParameter<DirectoryInfo>));
181181
var bindingContext = new InvocationContext(command.Parse($"--value \"{tempPath}\"")).BindingContext;
182182

@@ -191,7 +191,7 @@ public void Explicitly_configured_default_values_can_be_bound_by_name_to_propert
191191
var option = new Option<string>("--value", () => "the default");
192192

193193
var command = new Command("the-command");
194-
command.AddOption(option);
194+
command.Options.Add(option);
195195
var binder = new ModelBinder(typeof(ClassWithSetter<string>));
196196

197197
var parser = new Parser(command);
@@ -421,7 +421,7 @@ public void PropertyInfo_can_be_bound_to_argument()
421421
{
422422
var command = new Command("the-command");
423423
var argument = new Argument<int> { Arity = ArgumentArity.ExactlyOne };
424-
command.AddArgument(argument);
424+
command.Arguments.Add(argument);
425425

426426
var type = typeof(ClassWithMultiLetterSetters);
427427
var binder = new ModelBinder(type);
@@ -441,7 +441,7 @@ public void PropertyExpression_can_be_bound_to_option()
441441
{
442442
var command = new Command("the-command");
443443
var option = new Option<int>("--fred");
444-
command.AddOption(option);
444+
command.Options.Add(option);
445445

446446
var binder = new ModelBinder<ClassWithMultiLetterSetters>();
447447

@@ -461,7 +461,7 @@ public void PropertyExpression_can_be_bound_to_argument()
461461
{
462462
var command = new Command("the-command");
463463
var argument = new Argument<int> { Arity = ArgumentArity.ExactlyOne };
464-
command.AddArgument(argument);
464+
command.Arguments.Add(argument);
465465

466466
var binder = new ModelBinder<ClassWithMultiLetterSetters>();
467467

@@ -493,7 +493,7 @@ public void Option_argument_is_bound_to_longest_constructor()
493493
public void Command_argument_is_bound_to_longest_constructor()
494494
{
495495
var rootCommand = new RootCommand();
496-
rootCommand.AddArgument(new Argument<int> { Name = nameof(ClassWithMultipleCtor.IntProperty) });
496+
rootCommand.Arguments.Add(new Argument<int> { Name = nameof(ClassWithMultipleCtor.IntProperty) });
497497
var parser = new Parser(rootCommand);
498498

499499
var bindingContext = new InvocationContext(parser.Parse("42")).BindingContext;

src/System.CommandLine.NamingConventionBinder.Tests/ParameterBindingTests.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ void Execute(string name, int age)
3030
}
3131

3232
var command = new Command("command");
33-
command.AddOption(new Option<string>("--name"));
34-
command.AddOption(new Option<int>("--age"));
33+
command.Options.Add(new Option<string>("--name"));
34+
command.Options.Add(new Option<int>("--age"));
3535
command.Handler = CommandHandler.Create<string, int>(Execute);
3636

3737
await command.InvokeAsync("command --age 425 --name Gandalf", _console);
@@ -74,8 +74,8 @@ void Execute(string name, int AGE)
7474
}
7575

7676
var command = new Command("command");
77-
command.AddOption(new Option<string>("--NAME"));
78-
command.AddOption(new Option<int>("--age"));
77+
command.Options.Add(new Option<string>("--NAME"));
78+
command.Options.Add(new Option<int>("--age"));
7979
command.Handler = CommandHandler.Create<string, int>(Execute);
8080

8181
await command.InvokeAsync("command --age 425 --NAME Gandalf", _console);
@@ -350,8 +350,8 @@ void Execute(string name, int age)
350350
}
351351

352352
var command = new Command("command");
353-
command.AddArgument(new Argument<int>("age"));
354-
command.AddArgument(new Argument<string>("name"));
353+
command.Arguments.Add(new Argument<int>("age"));
354+
command.Arguments.Add(new Argument<string>("name"));
355355
command.Handler = CommandHandler.Create<string, int>(Execute);
356356

357357
await command.InvokeAsync("command 425 Gandalf", _console);
@@ -394,8 +394,8 @@ void Execute(string name, int AGE)
394394
}
395395

396396
var command = new Command("command");
397-
command.AddArgument(new Argument<int>("AGE"));
398-
command.AddArgument(new Argument<string>("Name"));
397+
command.Arguments.Add(new Argument<int>("AGE"));
398+
command.Arguments.Add(new Argument<string>("Name"));
399399
command.Handler = CommandHandler.Create<string, int>(Execute);
400400

401401
await command.InvokeAsync("command 425 Gandalf", _console);
@@ -417,8 +417,8 @@ void Execute(string fullnameOrNickname, int age)
417417
}
418418

419419
var command = new Command("command");
420-
command.AddArgument(new Argument<int>("age"));
421-
command.AddArgument(new Argument<string>("fullname|nickname"));
420+
command.Arguments.Add(new Argument<int>("age"));
421+
command.Arguments.Add(new Argument<string>("fullname|nickname"));
422422
command.Handler = CommandHandler.Create<string, int>(Execute);
423423

424424
await command.InvokeAsync("command 425 Gandalf", _console);

src/System.CommandLine.Tests/ArgumentTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ public async Task Custom_argument_parser_is_only_called_once()
352352

353353
var command = new RootCommand();
354354
command.SetHandler((int value) => handlerWasCalled = true, option);
355-
command.AddOption(option);
355+
command.Options.Add(option);
356356

357357
await command.InvokeAsync("--value 42");
358358

src/System.CommandLine.Tests/Binding/SetHandlerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void Binding_is_correct_for_Action_overload_having_arity_(int arity)
8585

8686
for (var i = 1; i <= arity; i++)
8787
{
88-
command.AddArgument(new Argument<int>($"i{i}"));
88+
command.Arguments.Add(new Argument<int>($"i{i}"));
8989

9090
commandLine += $" {i}";
9191
}
@@ -176,7 +176,7 @@ public void Binding_is_correct_for_Func_overload_having_arity_(int arity)
176176

177177
for (var i = 1; i <= arity; i++)
178178
{
179-
command.AddArgument(new Argument<int>($"i{i}"));
179+
command.Arguments.Add(new Argument<int>($"i{i}"));
180180

181181
commandLine += $" {i}";
182182
}

src/System.CommandLine.Tests/CommandTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public void Commands_at_multiple_levels_can_have_their_own_arguments()
9898
{
9999
new Argument<string>()
100100
};
101-
outer.AddCommand(
101+
outer.Subcommands.Add(
102102
new Command("inner")
103103
{
104104
new Argument<string[]>()

src/System.CommandLine.Tests/GlobalOptionTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public void Global_options_appear_in_options_list()
1919

2020
var child = new Command("child");
2121

22-
root.AddCommand(child);
22+
root.Subcommands.Add(child);
2323

2424
root.Options.Should().Contain(option);
2525
}
@@ -90,7 +90,7 @@ public void Subcommands_added_after_a_global_option_is_added_to_parent_will_reco
9090

9191
var child = new Command("child");
9292

93-
root.AddCommand(child);
93+
root.Subcommands.Add(child);
9494

9595
root.Parse("child --global 123").GetValue(option).Should().Be(123);
9696

@@ -104,15 +104,15 @@ public void Subcommands_with_global_option_should_propagate_option_to_children()
104104

105105
var firstChild = new Command("first");
106106

107-
root.AddCommand(firstChild);
107+
root.Subcommands.Add(firstChild);
108108

109109
var option = new Option<int>("--global");
110110

111111
firstChild.AddGlobalOption(option);
112112

113113
var secondChild = new Command("second");
114114

115-
firstChild.AddCommand(secondChild);
115+
firstChild.Subcommands.Add(secondChild);
116116

117117
root.Parse("first second --global 123").GetValue(option).Should().Be(123);
118118

src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ public void Option_can_fallback_to_default_when_customizing(bool conditionA, boo
241241
var command = new Command("test");
242242
var option = new Option<string>("--option", "description");
243243

244-
command.AddOption(option);
244+
command.Options.Add(option);
245245

246246
var helpBuilder = new HelpBuilder(LocalizationResources.Instance, LargeMaxWidth);
247247
helpBuilder.CustomizeSymbol(option,
@@ -278,7 +278,7 @@ public void Argument_can_fallback_to_default_when_customizing(
278278
var argument = new Argument<string>("arg", "description");
279279
argument.SetDefaultValue("default");
280280

281-
command.AddArgument(argument);
281+
command.Arguments.Add(argument);
282282

283283
var helpBuilder = new HelpBuilder(LocalizationResources.Instance, LargeMaxWidth);
284284
helpBuilder.CustomizeSymbol(argument,

0 commit comments

Comments
 (0)