Skip to content

Commit ddeb09e

Browse files
authored
Merge pull request #41 from Cysharp/hotfix/OptionIndex
Respect the Index property value of OptionAttribute
2 parents d16e69c + ccbd487 commit ddeb09e

File tree

2 files changed

+78
-8
lines changed

2 files changed

+78
-8
lines changed

src/ConsoleAppFramework/ConsoleAppEngine.cs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ bool TryGetInvokeArguments(ParameterInfo[] parameters, string?[] args, int argsO
227227
}
228228
}
229229

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

233233
for (int i = 0; i < parameters.Length; i++)
@@ -237,14 +237,24 @@ bool TryGetInvokeArguments(ParameterInfo[] parameters, string?[] args, int argsO
237237
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.");
238238

239239
var value = default(OptionParameter);
240+
241+
// Indexed arguments (e.g. [Option(0)])
240242
if (option != null && option.Index != -1)
241243
{
242-
if (argsOffset + i < args.Length)
244+
if (optionByIndex.Count <= option.Index)
243245
{
244-
value = new OptionParameter { Value = args[argsOffset + i] };
246+
if (!item.HasDefaultValue)
247+
{
248+
throw new InvalidOperationException($"Required argument {option.Index} was not found in specified arguments.");
249+
}
250+
}
251+
else
252+
{
253+
value = optionByIndex[option.Index];
245254
}
246255
}
247256

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

364-
static ReadOnlyDictionary<string, OptionParameter> ParseArgument(string?[] args, int argsOffset, IReadOnlyDictionary<string, Type> optionTypeByName)
374+
static (ReadOnlyDictionary<string, OptionParameter> OptionByKey, IReadOnlyList<OptionParameter> OptionByIndex) ParseArgument(string?[] args, int argsOffset, IReadOnlyDictionary<string, Type> optionTypeByName)
365375
{
366376
var dict = new Dictionary<string, OptionParameter>(args.Length, StringComparer.OrdinalIgnoreCase);
377+
var options = new List<OptionParameter>();
367378
for (int i = argsOffset; i < args.Length;)
368379
{
369-
var key = args[i++];
370-
if (key is null || !key.StartsWith("-"))
380+
var arg = args[i++];
381+
if (arg is null || !arg.StartsWith("-"))
371382
{
383+
options.Add(new OptionParameter() { Value = arg });
372384
continue; // not key
373385
}
374386

375-
key = key.TrimStart('-');
387+
var key = arg.TrimStart('-');
376388

377389
if (optionTypeByName.TryGetValue(key, out var optionType))
378390
{
@@ -387,9 +399,14 @@ static ReadOnlyDictionary<string, OptionParameter> ParseArgument(string?[] args,
387399
i++;
388400
}
389401
}
402+
else
403+
{
404+
// not key
405+
options.Add(new OptionParameter() { Value = arg });
406+
}
390407
}
391408

392-
return new ReadOnlyDictionary<string, OptionParameter>(dict);
409+
return (new ReadOnlyDictionary<string, OptionParameter>(dict), options);
393410
}
394411

395412
struct OptionParameter

tests/ConsoleAppFramework.Integration.Test/MultipleCommandTest.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ public void OptionAndArg_Option()
8686
console.Output.Should().Contain("Hello Cysharp (-128)");
8787
}
8888

89+
[Fact]
90+
public void OptionAndArg_Option_ReverseOrdered()
91+
{
92+
using var console = new CaptureConsoleOutput();
93+
var args = new string[] { "hello", "-age", "-128", "Cysharp" };
94+
Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<CommandTests_Multiple_OptionAndArg>(args);
95+
console.Output.Should().Contain("Hello Cysharp (-128)");
96+
}
97+
8998
[Fact]
9099
public void OptionAndArg_Help()
91100
{
@@ -146,6 +155,50 @@ public class CommandTests_Multiple_OptionAndArg : ConsoleAppBase
146155
public void Konnichiwa() => Console.WriteLine("Konnichiwa");
147156
}
148157

158+
[Fact]
159+
public void OptionAndArg_Option_MixedOrdered_Default()
160+
{
161+
using var console = new CaptureConsoleOutput();
162+
var args = new string[] { "hello", "-age", "18", "Hello", "Cysharp" };
163+
Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<CommandTests_Multiple_OptionAndArg_MixedOrdered>(args);
164+
console.Output.Should().Contain("Hello Cysharp (18)");
165+
}
166+
167+
[Fact]
168+
public void OptionAndArg_Option_MixedOrdered_2()
169+
{
170+
using var console = new CaptureConsoleOutput();
171+
var args = new string[] { "hello", "Hello", "-age", "18", "Cysharp" };
172+
Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<CommandTests_Multiple_OptionAndArg_MixedOrdered>(args);
173+
console.Output.Should().Contain("Hello Cysharp (18)");
174+
}
175+
176+
[Fact]
177+
public void OptionAndArg_Option_MixedOrdered_3()
178+
{
179+
using var console = new CaptureConsoleOutput();
180+
var args = new string[] { "hello", "Hello", "Cysharp", "-age", "18" };
181+
Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<CommandTests_Multiple_OptionAndArg_MixedOrdered>(args);
182+
console.Output.Should().Contain("Hello Cysharp (18)");
183+
}
184+
185+
[Fact]
186+
public void OptionAndArg_Option_Mixed_Optional()
187+
{
188+
using var console = new CaptureConsoleOutput();
189+
var args = new string[] { "greet" };
190+
Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync<CommandTests_Multiple_OptionAndArg_MixedOrdered>(args);
191+
console.Output.Should().Contain("Konnichiwa Anonymous");
192+
}
193+
194+
public class CommandTests_Multiple_OptionAndArg_MixedOrdered : ConsoleAppBase
195+
{
196+
[Command("hello")]
197+
public void Hello([Option(1)]string name, int age, [Option(0)]string greeting) => Console.WriteLine($"{greeting} {name} ({age})");
198+
[Command("greet")]
199+
public void Greet([Option(0)]string greeting = "Konnichiwa", [Option(0)]string name = "Anonymous") => Console.WriteLine($"{greeting} {name}");
200+
}
201+
149202
[Fact]
150203
public void OptionHelp()
151204
{

0 commit comments

Comments
 (0)