Skip to content

Commit d2add4f

Browse files
authored
Rework input parsing (#94)
* Empty option prefixes are not allowed * Short options need to have a length of 1 * Return results by yielding * Add API obsolete safety test * Add second implementation of argument manager that will work better * Add command context to keep track of the current command * Add logging * Test run with ArgumentManager2 implementation * Add logging in tests * Implement input parsing feature v2 fixes #89 * Use new ArgumentManager everywhere * Remove old ArgumentManager * Inject IArgumentManager * Remove non async versions
1 parent 7a5f5ff commit d2add4f

37 files changed

+607
-683
lines changed

CommandLineParser.Tests/Command/SubCommandTests.cs

+1-32
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,6 @@ public SubCommandTests(ITestOutputHelper testOutputHelper) : base(testOutputHelp
1515
{
1616
}
1717

18-
[Theory]
19-
[InlineData(true, "something", 15, -1)]
20-
[InlineData(false, "something", 15, -1)]
21-
[InlineData(true, "", 15, -1)]
22-
public void TestSubCommandWorksCorrectlyInModel(bool autoExecute, string bla, int i, int n)
23-
{
24-
var lock1 = new ManualResetEventSlim();
25-
var lock2 = new ManualResetEventSlim();
26-
27-
Services.AddSingleton(new MainCommand(lock1, autoExecute, bla, i, n));
28-
Services.AddSingleton(new SubCommand(lock2, autoExecute, bla, i, n));
29-
30-
var parser = new CommandLineParser<MainModel>(Services);
31-
32-
var result = parser.Parse(new[] { "main", "-b", bla, "sub", "-i", i.ToString(), "-n", n.ToString() });
33-
34-
result.AssertNoErrors();
35-
36-
if (!autoExecute)
37-
{
38-
Assert.All(result.CommandResults.Select(r => r.Executed), Assert.False);
39-
40-
result.ExecuteCommands();
41-
}
42-
43-
Assert.True(lock1.Wait(1000), "MainCommand didn't execute in time.");
44-
Assert.True(lock2.Wait(1000), "SubCommand didn't execute in time.");
45-
46-
Assert.All(result.CommandResults.Select(r => r.Executed), Assert.True);
47-
}
48-
4918
[Theory]
5019
[InlineData(true, "something", 15, -1)]
5120
[InlineData(false, "something", 15, -1)]
@@ -68,7 +37,7 @@ public async Task TestSubCommandWorksCorrectlyInModelAsync(bool autoExecute, str
6837
{
6938
Assert.All(result.CommandResults.Select(r => r.Executed), Assert.False);
7039

71-
result.ExecuteCommands();
40+
await result.ExecuteCommandsAsync(default);
7241
}
7342

7443
Assert.True(lock1.Wait(1000), "MainCommand didn't execute in time.");

CommandLineParser.Tests/CommandLineParserTests.cs

+33-76
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ public override void OnConfigure(ICommandConfigurationBuilder<object> builder)
3030
}
3131
}
3232

33+
[Theory]
34+
[InlineData("", "--")]
35+
[InlineData("-", "")]
36+
[InlineData("---", "---")]
37+
public void InvalidOptionsThrowException(string shortOption, string longOption)
38+
{
39+
var options = new CommandLineParserOptions
40+
{
41+
PrefixShortOption = shortOption,
42+
PrefixLongOption = longOption
43+
};
44+
45+
Assert.Throws<ArgumentException>(() => new CommandLineParser(options, Services));
46+
}
47+
3348
[Theory]
3449
[InlineData(true)]
3550
[InlineData(false)]
@@ -53,7 +68,7 @@ public void CommandLineParserUsesCorrectOptions()
5368
{
5469
var opt = new CommandLineParserOptions();
5570

56-
var parser = new CommandLineParser(opt);
71+
var parser = new CommandLineParser(opt, Services);
5772

5873
Assert.Equal(opt, parser.ParserOptions);
5974
}
@@ -131,7 +146,7 @@ public async Task CommandLineParserUsesContainerCorrectlyAsync(bool generic)
131146
[Fact]
132147
public void AutoExecuteCommandsWithExceptionDoesntCrashTheParser()
133148
{
134-
var parser = new CommandLineParser();
149+
var parser = new CommandLineParser(Services);
135150

136151
var ex = new Exception("uh-oh");
137152

@@ -151,7 +166,7 @@ public void AutoExecuteCommandsWithExceptionDoesntCrashTheParser()
151166
[Fact]
152167
public async Task AutoExecuteCommandsWithExceptionDoesntCrashTheParserAsync()
153168
{
154-
var parser = new CommandLineParser();
169+
var parser = new CommandLineParser(Services);
155170

156171
var ex = new Exception("uh-oh");
157172

@@ -172,32 +187,10 @@ public async Task AutoExecuteCommandsWithExceptionDoesntCrashTheParserAsync()
172187
Assert.Equal(ex, result.Errors.First().GetBaseException());
173188
}
174189

175-
//[Fact]
176-
//public void CommandLineParserUsesArgumentFactoryCorrectly()
177-
//{
178-
// var resolverMock = new Mock<IArgumentResolver<string>>();
179-
// resolverMock.Setup(_ => _.CanResolve(It.IsAny<ArgumentModel>())).Returns(true).Verifiable();
180-
// resolverMock.Setup(_ => _.Resolve(It.IsAny<ArgumentModel>())).Returns("return").Verifiable();
181-
182-
// var argResolverFactory = new Mock<IServiceProvider>();
183-
// argResolverFactory.Setup(c => c.GetService(typeof(string))).Returns(resolverMock.Object).Verifiable();
184-
185-
// var parser = new CommandLineParser<AddOption>(.Object);
186-
187-
// parser.Configure(p => p.Message).Name("m");
188-
189-
// var result = parser.Parse(new[] { "app.exe", "-m" });
190-
191-
// result.AssertNoErrors();
192-
193-
// resolverMock.VerifyAll();
194-
// argResolverFactory.Verify();
195-
//}
196-
197190
[Fact]
198191
public void ParseTests()
199192
{
200-
var parser = new CommandLineParser<Options>();
193+
var parser = new CommandLineParser<Options>(Services);
201194

202195
parser.Configure(opt => opt.Option1)
203196
.Name("o")
@@ -221,7 +214,7 @@ public void ParseTests()
221214
[InlineData(new[] { "app.exe", "-e" }, true, default(EnumOption))]
222215
public void ParseEnumInArguments(string[] args, bool hasErrors, EnumOption enumOption)
223216
{
224-
var parser = new CommandLineParser<EnumOptions>();
217+
var parser = new CommandLineParser<EnumOptions>(Services);
225218

226219
parser.Configure(opt => opt.EnumOption)
227220
.Name("e")
@@ -286,7 +279,7 @@ void Test<T>()
286279

287280
private void TestParsingWithDefaults<T>(string[] args, T defaultValue, T result1, T result2, T result3)
288281
{
289-
var parser = new CommandLineParser<OptionsWithThreeParams<T>>();
282+
var parser = new CommandLineParser<OptionsWithThreeParams<T>>(Services);
290283

291284
parser.Configure(opt => opt.Option1)
292285
.Name("1")
@@ -312,48 +305,12 @@ private void TestParsingWithDefaults<T>(string[] args, T defaultValue, T result1
312305
Assert.Equal(result3, parsed.Result.Option3);
313306
}
314307

315-
[Fact]
316-
public void ParseWithCommandTests()
317-
{
318-
var wait = new ManualResetEvent(false);
319-
320-
var parser = new CommandLineParser<Options>();
321-
322-
parser.Configure(opt => opt.Option1)
323-
.Name("o")
324-
.Default("Default message")
325-
.Required();
326-
327-
var addCmd = parser.AddCommand<AddOption>()
328-
.Name("add")
329-
.OnExecuting((opt, cmdOpt) =>
330-
{
331-
Assert.Equal("test", opt.Option1);
332-
Assert.Equal("my message", cmdOpt.Message);
333-
wait.Set();
334-
});
335-
336-
addCmd.Configure(opt => opt.Message)
337-
.Name("m", "message")
338-
.Required();
339-
340-
var parsed = parser.Parse(new string[] { "app.exe", "-o", "test", "add", "-m=my message" });
341-
342-
parsed.AssertNoErrors();
343-
344-
Assert.Equal("test", parsed.Result.Option1);
345-
346-
parsed.ExecuteCommands();
347-
348-
Assert.True(wait.WaitOne(2000));
349-
}
350-
351308
[Fact]
352309
public async Task ParseWithCommandTestsAsync()
353310
{
354311
var wait = new ManualResetEvent(false);
355312

356-
var parser = new CommandLineParser<Options>();
313+
var parser = new CommandLineParser<Options>(Services);
357314

358315
parser.Configure(opt => opt.Option1)
359316
.Name("o")
@@ -386,7 +343,7 @@ public async Task ParseWithCommandTestsAsync()
386343

387344
Assert.Equal("test", parsed.Result.Option1);
388345

389-
parsed.ExecuteCommands();
346+
parsed.ExecuteCommandsAsync(default).GetAwaiter().GetResult();
390347

391348
Assert.True(wait.WaitOne(2000));
392349
}
@@ -398,7 +355,7 @@ public async Task ParseWithCommandTestsAsync()
398355
[InlineData(new[] { "-m", "message1", "add", "-m", "message2" }, "message1", "message2")]
399356
public void ParseCommandTests(string[] args, string result1, string result2)
400357
{
401-
var parser = new CommandLineParser<AddOption>();
358+
var parser = new CommandLineParser<AddOption>(Services);
402359
var wait = new ManualResetEvent(false);
403360

404361
parser.AddCommand<AddOption>()
@@ -434,7 +391,7 @@ public void ParseCommandTests(string[] args, string result1, string result2)
434391
[InlineData(new[] { "-m", "message1", "add", "-m", "message2" }, "message1", "message2")]
435392
public async Task ParseCommandTestsAsync(string[] args, string result1, string result2)
436393
{
437-
var parser = new CommandLineParser<AddOption>();
394+
var parser = new CommandLineParser<AddOption>(Services);
438395
var wait = new ManualResetEvent(false);
439396

440397
parser.AddCommand<AddOption>()
@@ -473,7 +430,7 @@ public async Task ParseCommandTestsAsync(string[] args, string result1, string r
473430
[InlineData(new string[] { "-x", "false" }, false)]
474431
public void BoolResolverSpecialCaseParsesCorrectly(string[] args, bool expected)
475432
{
476-
var parser = new CommandLineParser<Options>();
433+
var parser = new CommandLineParser<Options>(Services);
477434

478435
parser.Configure(opt => opt.Option2)
479436
.Name("x", "xsomething")
@@ -493,7 +450,7 @@ public void BoolResolverSpecialCaseParsesCorrectly(string[] args, bool expected)
493450
[InlineData(new string[] { "command", "-v" }, true)]
494451
public void BoolResolverSpecialCaseParsesCorrectlyWithDefaultValueAndNotBeingSpecified(string[] args, bool expected)
495452
{
496-
var parser = new CommandLineParser<Model_Issue_35>();
453+
var parser = new CommandLineParser<Model_Issue_35>(Services);
497454

498455
parser.AddCommand().Name("command");
499456

@@ -515,7 +472,7 @@ private class Model_Issue_35
515472
[Fact]
516473
public void ConfigureTests()
517474
{
518-
var parser = new CommandLineParser<Options>();
475+
var parser = new CommandLineParser<Options>(Services);
519476

520477
parser.Configure(opt => opt.Option1)
521478
.Name("o", "opt")
@@ -549,7 +506,7 @@ public void ConfigureTests()
549506
[InlineData(new string[] { "--message", "test" }, "testtransformed", false)]
550507
public void TransformationWorksAsExpected(string[] args, string expected, bool errors)
551508
{
552-
var parser = new CommandLineParser<AddOption>();
509+
var parser = new CommandLineParser<AddOption>(Services);
553510

554511
parser.Configure(a => a.Message)
555512
.Name("m", "message")
@@ -570,7 +527,7 @@ public void TransformationWorksAsExpected(string[] args, string expected, bool e
570527
[InlineData(new string[] { "--int", "10" }, 20, false)]
571528
public void TransformationWorksAsExpectedForInts(string[] args, int expected, bool errors)
572529
{
573-
var parser = new CommandLineParser<IntOptions>();
530+
var parser = new CommandLineParser<IntOptions>(Services);
574531

575532
parser.Configure(a => a.SomeInt)
576533
.Name("i", "int")
@@ -593,7 +550,7 @@ public void TransformationWorksAsExpectedForCommandOptions(string[] args, int ex
593550
{
594551
int outcome = -1;
595552

596-
var parser = new CommandLineParser();
553+
var parser = new CommandLineParser(Services);
597554

598555
var cmd = parser.AddCommand<IntOptions>()
599556
.Name("cmd")
@@ -620,7 +577,7 @@ public void TransformationWorksAsExpectedForCommandOptions(string[] args, int ex
620577
[InlineData(new string[] { "cmd", "--string", "test", "-s2", "test" }, "test", false)]
621578
public void CustomTypeWithStringTryParseGetsParsedCorrectly(string[] args, string expected, bool errors)
622579
{
623-
var parser = new CommandLineParser<StringTryParseTypeOptions>();
580+
var parser = new CommandLineParser<StringTryParseTypeOptions>(Services);
624581

625582
var result = parser.Parse(args);
626583

@@ -640,7 +597,7 @@ public void CustomTypeWithStringTryParseGetsParsedCorrectly(string[] args, strin
640597
[InlineData(new string[] { "cmd", "--string", "test", "-s2", "test", "-s3", "test" }, "test", false)]
641598
public void CustomTypeWithStringConstructorGetsParsedCorrectly(string[] args, string expected, bool errors)
642599
{
643-
var parser = new CommandLineParser<StringTypeOptions>();
600+
var parser = new CommandLineParser<StringTypeOptions>(Services);
644601

645602
var result = parser.Parse(args);
646603

CommandLineParser.Tests/OptionBuilderTest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void OptionBuilderConfiguresOptionCorrectly()
2222
new CommandLineParserOptions { PrefixLongOption = string.Empty, PrefixShortOption = string.Empty },
2323
new object(),
2424
XUnitExtensions.CreateLambda<object, string>(o => o.ToString()),
25-
new DefaultResolver<object>(), NullLogger.Instance);
25+
new DefaultResolver<object>(NullLogger<CommandLineParser>.Instance), NullLogger.Instance);
2626

2727
var builder = cmdOption as IOptionBuilder;
2828
var option = cmdOption as CommandLineOptionBase;
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
using MatthiWare.CommandLine.Core;
22
using Microsoft.Extensions.DependencyInjection;
33
using System;
4+
using Xunit.Abstractions;
45

56
namespace MatthiWare.CommandLine.Tests.Parsing.Resolvers
67
{
7-
public abstract class BaseResolverTests
8+
public abstract class BaseResolverTests : TestBase
89
{
910
public IServiceProvider ServiceProvider { get; }
1011

11-
public BaseResolverTests()
12+
public BaseResolverTests(ITestOutputHelper outputHelper)
13+
: base(outputHelper)
1214
{
13-
var services = new ServiceCollection();
15+
Services.AddDefaultResolvers();
1416

15-
services.AddDefaultResolvers();
16-
17-
ServiceProvider = services.BuildServiceProvider();
17+
ServiceProvider = Services.BuildServiceProvider();
1818
}
1919
}
2020
}

CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs

+5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
using MatthiWare.CommandLine.Abstractions.Parsing;
33
using Microsoft.Extensions.DependencyInjection;
44
using Xunit;
5+
using Xunit.Abstractions;
56

67
namespace MatthiWare.CommandLine.Tests.Parsing.Resolvers
78
{
89
public class BoolResolverTests
910
: BaseResolverTests
1011
{
12+
public BoolResolverTests(ITestOutputHelper outputHelper) : base(outputHelper)
13+
{
14+
}
15+
1116
[Theory]
1217
[InlineData("yes")]
1318
[InlineData("1")]

CommandLineParser.Tests/Parsing/Resolvers/DefaultResolverTests.cs

+5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
using MatthiWare.CommandLine.Abstractions.Parsing;
33
using Microsoft.Extensions.DependencyInjection;
44
using Xunit;
5+
using Xunit.Abstractions;
56

67
namespace MatthiWare.CommandLine.Tests.Parsing.Resolvers
78
{
89
public class DefaultResolverTests
910
: BaseResolverTests
1011
{
12+
public DefaultResolverTests(ITestOutputHelper outputHelper) : base(outputHelper)
13+
{
14+
}
15+
1116
[Theory]
1217
[InlineData(true, "-m", "test")]
1318
[InlineData(true, "-m", "my string")]

CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs

+5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
using MatthiWare.CommandLine.Abstractions.Parsing;
33
using Microsoft.Extensions.DependencyInjection;
44
using Xunit;
5+
using Xunit.Abstractions;
56

67
namespace MatthiWare.CommandLine.Tests.Parsing.Resolvers
78
{
89
public class DoubleResolverTests
910
: BaseResolverTests
1011
{
12+
public DoubleResolverTests(ITestOutputHelper outputHelper) : base(outputHelper)
13+
{
14+
}
15+
1116
[Theory]
1217
[InlineData(true, "6E-14")]
1318
[InlineData(false, "false")]

0 commit comments

Comments
 (0)