diff --git a/CliFx.Tests/ArgumentBindingSpecs.Commands.cs b/CliFx.Tests/ArgumentBindingSpecs.Commands.cs index ec2d0de..c50a827 100644 --- a/CliFx.Tests/ArgumentBindingSpecs.Commands.cs +++ b/CliFx.Tests/ArgumentBindingSpecs.Commands.cs @@ -122,6 +122,15 @@ namespace CliFx.Tests public ValueTask ExecuteAsync(IConsole console) => default; } + [Command] + private class ArrayOptionCommand : ICommand + { + [CommandOption("option", 'o')] + public IReadOnlyList? Option { get; set; } + + public ValueTask ExecuteAsync(IConsole console) => default; + } + [Command] private class RequiredOptionCommand : ICommand { diff --git a/CliFx.Tests/ArgumentBindingSpecs.cs b/CliFx.Tests/ArgumentBindingSpecs.cs index 7a604b9..eb8b462 100644 --- a/CliFx.Tests/ArgumentBindingSpecs.cs +++ b/CliFx.Tests/ArgumentBindingSpecs.cs @@ -910,6 +910,28 @@ namespace CliFx.Tests }); } + [Fact] + public void Property_annotated_as_an_option_can_be_bound_from_multiple_values_even_if_the_inputs_use_mixed_naming() + { + // Arrange + var schema = ApplicationSchema.Resolve(new[] {typeof(ArrayOptionCommand)}); + + var input = new CommandLineInputBuilder() + .AddOption("option", "foo") + .AddOption("o", "bar") + .AddOption("option", "baz") + .Build(); + + // Act + var command = schema.InitializeEntryPoint(input); + + // Assert + command.Should().BeEquivalentTo(new ArrayOptionCommand + { + Option = new[] {"foo", "bar", "baz"} + }); + } + [Fact] public void Property_annotated_as_a_required_option_must_always_be_bound_to_some_value() { diff --git a/CliFx/Domain/CommandSchema.cs b/CliFx/Domain/CommandSchema.cs index c0129cf..cdc201b 100644 --- a/CliFx/Domain/CommandSchema.cs +++ b/CliFx/Domain/CommandSchema.cs @@ -93,7 +93,7 @@ namespace CliFx.Domain // All inputs must be bound var remainingOptionInputs = optionInputs.ToList(); - // Keep track of required options so that we can raise an error if any of them are not set + // All required options must be set var unsetRequiredOptions = Options.Where(o => o.IsRequired).ToList(); // Environment variables @@ -112,15 +112,21 @@ namespace CliFx.Domain } } + // TODO: refactor this part? I wrote this while sick // Direct input - foreach (var optionInput in optionInputs) + foreach (var option in Options) { - var option = Options.FirstOrDefault(o => o.MatchesNameOrShortName(optionInput.Alias)); + var inputs = optionInputs + .Where(i => option.MatchesNameOrShortName(i.Alias)) + .ToArray(); - if (option != null) + if (inputs.Any()) { - option.Inject(command, optionInput.Values); - remainingOptionInputs.Remove(optionInput); + option.Inject(command, inputs.SelectMany(i => i.Values).ToArray()); + + foreach (var input in inputs) + remainingOptionInputs.Remove(input); + unsetRequiredOptions.Remove(option); } } @@ -139,7 +145,7 @@ namespace CliFx.Domain { throw new CliFxException(new StringBuilder() .AppendLine("Unrecognized options provided:") - .AppendBulletList(remainingOptionInputs.Select(o => o.Alias)) + .AppendBulletList(remainingOptionInputs.Select(o => o.Alias).Distinct()) .ToString()); } }