Skip to content

Commit

Permalink
Respect the Index property value of OptionAttribute
Browse files Browse the repository at this point in the history
  • Loading branch information
mayuki committed May 14, 2020
1 parent d16e69c commit ccbd487
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 8 deletions.
33 changes: 25 additions & 8 deletions src/ConsoleAppFramework/ConsoleAppEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ bool TryGetInvokeArguments(ParameterInfo[] parameters, string?[] args, int argsO
}
}

var argumentDictionary = ParseArgument(args, argsOffset, optionTypeByOptionName);
var (argumentDictionary, optionByIndex) = ParseArgument(args, argsOffset, optionTypeByOptionName);
invokeArgs = new object[parameters.Length];

for (int i = 0; i < parameters.Length; i++)
Expand All @@ -237,14 +237,24 @@ bool TryGetInvokeArguments(ParameterInfo[] parameters, string?[] args, int argsO
if (!string.IsNullOrWhiteSpace(option?.ShortName) && char.IsDigit(option!.ShortName, 0)) throw new InvalidOperationException($"Option '{item.Name}' has a short name, but the short name must start with A-Z or a-z.");

var value = default(OptionParameter);

// Indexed arguments (e.g. [Option(0)])
if (option != null && option.Index != -1)
{
if (argsOffset + i < args.Length)
if (optionByIndex.Count <= option.Index)
{
value = new OptionParameter { Value = args[argsOffset + i] };
if (!item.HasDefaultValue)
{
throw new InvalidOperationException($"Required argument {option.Index} was not found in specified arguments.");
}
}
else
{
value = optionByIndex[option.Index];
}
}

// Keyed options (e.g. -foo -bar )
if (value.Value != null || argumentDictionary.TryGetValue(item.Name, out value) || argumentDictionary.TryGetValue(option?.ShortName?.TrimStart('-') ?? "", out value))
{
if (parameters[i].ParameterType == typeof(bool) && value.Value == null)
Expand Down Expand Up @@ -361,18 +371,20 @@ bool TryGetInvokeArguments(ParameterInfo[] parameters, string?[] args, int argsO
return null;
}

static ReadOnlyDictionary<string, OptionParameter> ParseArgument(string?[] args, int argsOffset, IReadOnlyDictionary<string, Type> optionTypeByName)
static (ReadOnlyDictionary<string, OptionParameter> OptionByKey, IReadOnlyList<OptionParameter> OptionByIndex) ParseArgument(string?[] args, int argsOffset, IReadOnlyDictionary<string, Type> optionTypeByName)
{
var dict = new Dictionary<string, OptionParameter>(args.Length, StringComparer.OrdinalIgnoreCase);
var options = new List<OptionParameter>();
for (int i = argsOffset; i < args.Length;)
{
var key = args[i++];
if (key is null || !key.StartsWith("-"))
var arg = args[i++];
if (arg is null || !arg.StartsWith("-"))
{
options.Add(new OptionParameter() { Value = arg });
continue; // not key
}

key = key.TrimStart('-');
var key = arg.TrimStart('-');

if (optionTypeByName.TryGetValue(key, out var optionType))
{
Expand All @@ -387,9 +399,14 @@ static ReadOnlyDictionary<string, OptionParameter> ParseArgument(string?[] args,
i++;
}
}
else
{
// not key
options.Add(new OptionParameter() { Value = arg });
}
}

return new ReadOnlyDictionary<string, OptionParameter>(dict);
return (new ReadOnlyDictionary<string, OptionParameter>(dict), options);
}

struct OptionParameter
Expand Down
53 changes: 53 additions & 0 deletions tests/ConsoleAppFramework.Integration.Test/MultipleCommandTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ public void OptionAndArg_Option()
console.Output.Should().Contain("Hello Cysharp (-128)");
}

[Fact]
public void OptionAndArg_Option_ReverseOrdered()
{
using var console = new CaptureConsoleOutput();
var args = new string[] { "hello", "-age", "-128", "Cysharp" };
Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<CommandTests_Multiple_OptionAndArg>(args);
console.Output.Should().Contain("Hello Cysharp (-128)");
}

[Fact]
public void OptionAndArg_Help()
{
Expand Down Expand Up @@ -146,6 +155,50 @@ public class CommandTests_Multiple_OptionAndArg : ConsoleAppBase
public void Konnichiwa() => Console.WriteLine("Konnichiwa");
}

[Fact]
public void OptionAndArg_Option_MixedOrdered_Default()
{
using var console = new CaptureConsoleOutput();
var args = new string[] { "hello", "-age", "18", "Hello", "Cysharp" };
Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<CommandTests_Multiple_OptionAndArg_MixedOrdered>(args);
console.Output.Should().Contain("Hello Cysharp (18)");
}

[Fact]
public void OptionAndArg_Option_MixedOrdered_2()
{
using var console = new CaptureConsoleOutput();
var args = new string[] { "hello", "Hello", "-age", "18", "Cysharp" };
Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<CommandTests_Multiple_OptionAndArg_MixedOrdered>(args);
console.Output.Should().Contain("Hello Cysharp (18)");
}

[Fact]
public void OptionAndArg_Option_MixedOrdered_3()
{
using var console = new CaptureConsoleOutput();
var args = new string[] { "hello", "Hello", "Cysharp", "-age", "18" };
Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<CommandTests_Multiple_OptionAndArg_MixedOrdered>(args);
console.Output.Should().Contain("Hello Cysharp (18)");
}

[Fact]
public void OptionAndArg_Option_Mixed_Optional()
{
using var console = new CaptureConsoleOutput();
var args = new string[] { "greet" };
Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<CommandTests_Multiple_OptionAndArg_MixedOrdered>(args);
console.Output.Should().Contain("Konnichiwa Anonymous");
}

public class CommandTests_Multiple_OptionAndArg_MixedOrdered : ConsoleAppBase
{
[Command("hello")]
public void Hello([Option(1)]string name, int age, [Option(0)]string greeting) => Console.WriteLine($"{greeting} {name} ({age})");
[Command("greet")]
public void Greet([Option(0)]string greeting = "Konnichiwa", [Option(0)]string name = "Anonymous") => Console.WriteLine($"{greeting} {name}");
}

[Fact]
public void OptionHelp()
{
Expand Down

0 comments on commit ccbd487

Please sign in to comment.