Skip to content

Commit 4fb4f8c

Browse files
authored
Improve Help Commands (#23)
* Add extra bool conditions * Add comments * Add BoolResolver special cases * Added extra cases * Add HelpRequested property * Improve help command * Fixed 2 tests and fixed issue where a required command option didn't produce an error when it was missing. * Update nuspec for v0.2
1 parent 1007398 commit 4fb4f8c

22 files changed

+417
-73
lines changed

CommandLineParser.Tests/CommandLineParserTests.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public void CommandLineParserUsesArgumentFactoryCorrectly()
5858

5959
var parser = new CommandLineParser<AddOption>(argResolverFactory.Object);
6060

61-
parser.Configure(p => p.Message).Name("-m");
61+
parser.Configure(p => p.Message).Name("m");
6262

6363
var result = parser.Parse(new[] { "app.exe", "-m" });
6464

@@ -239,6 +239,29 @@ public void ParseCommandTests(string[] args, string result1, string result2)
239239
Assert.True(wait.WaitOne(2000));
240240
}
241241

242+
[Theory]
243+
[InlineData(new string[] { "-x", "" }, true)]
244+
[InlineData(new string[] { "-x" }, true)]
245+
[InlineData(new string[] { "-x", "1" }, true)]
246+
[InlineData(new string[] { "-x", "true" }, true)]
247+
[InlineData(new string[] { "-x", "false" }, false)]
248+
public void BoolResolverSpecialCaseParsesCorrectly(string[] args, bool expected)
249+
{
250+
var parser = new CommandLineParser<Options>();
251+
252+
//parser.Configure(opt => opt.Option1)
253+
// .Name("o", "opt")
254+
// .Default("Default message");
255+
256+
parser.Configure(opt => opt.Option2)
257+
.Name("x", "xsomething")
258+
.Required();
259+
260+
var result = parser.Parse(args);
261+
262+
Assert.Equal(expected, result.Result.Option2);
263+
}
264+
242265
[Fact]
243266
public void ConfigureTests()
244267
{

CommandLineParser.Tests/Parsing/ParserResultTest.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
2-
2+
using System.Collections.Generic;
3+
using System.Linq;
34
using MatthiWare.CommandLine.Abstractions.Parsing.Command;
45
using MatthiWare.CommandLine.Core.Parsing;
56

@@ -16,21 +17,19 @@ public void TestMergeResultOfErrorsWorks()
1617
{
1718
var result = new ParseResult<object>();
1819

19-
Assert.Null(result.Error);
20+
Assert.Empty(result.Errors);
2021

2122
var exception1 = new Exception("test");
2223

2324
result.MergeResult(new[] { exception1 });
2425

2526
Assert.True(result.HasErrors);
26-
Assert.Same(exception1, result.Error);
27+
Assert.Same(exception1, result.Errors.First());
2728

2829
result.MergeResult(new[] { new Exception("2") });
2930

3031
Assert.True(result.HasErrors);
31-
Assert.NotSame(exception1, result.Error);
32-
33-
Assert.IsType<AggregateException>(result.Error);
32+
Assert.NotSame(exception1, result.Errors.Skip(1).First());
3433
}
3534

3635
[Fact]
@@ -41,7 +40,7 @@ public void TestMergeResultOfCommandResultWorks()
4140
var mockCmdResult = new Mock<ICommandParserResult>();
4241

4342
mockCmdResult.SetupGet(x => x.HasErrors).Returns(false);
44-
mockCmdResult.SetupGet(x => x.Error).Returns((Exception)null);
43+
mockCmdResult.SetupGet(x => x.Errors).Returns(new List<Exception>());
4544

4645
result.MergeResult(mockCmdResult.Object);
4746

@@ -59,7 +58,9 @@ public void TestMergeResultOfResultWorks()
5958

6059
result.MergeResult(obj);
6160

62-
Assert.Null(result.Error);
61+
Assert.False(result.HasErrors);
62+
63+
Assert.Empty(result.Errors);
6364

6465
Assert.Same(obj, result.Result);
6566
}

CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public class BoolResolverTests
1111
[InlineData("yes")]
1212
[InlineData("1")]
1313
[InlineData("true")]
14+
[InlineData("")]
15+
[InlineData(null)]
1416
public void TestResolveTrue(string input)
1517
{
1618
var resolver = new BoolResolver();
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using MatthiWare.CommandLine;
5+
using MatthiWare.CommandLine.Abstractions;
6+
using MatthiWare.CommandLine.Abstractions.Command;
7+
using MatthiWare.CommandLine.Abstractions.Usage;
8+
using MatthiWare.CommandLine.Core.Attributes;
9+
using Moq;
10+
using Xunit;
11+
12+
namespace MatthiWare.CommandLineParser.Tests.Usage
13+
{
14+
public class HelpDisplayCommandTests
15+
{
16+
[Theory]
17+
[InlineData(new string[] { "db", "--help" }, true)]
18+
[InlineData(new string[] { "db" }, true)]
19+
[InlineData(new string[] { "db", "-o", "--help" }, true)]
20+
[InlineData(new string[] { "db", "-o", "help" }, false)]
21+
[InlineData(new string[] { "-x", "blabla", "--help" }, true)]
22+
[InlineData(new string[] { }, true)]
23+
[InlineData(new string[] { "-x", "--help" }, true)]
24+
[InlineData(new string[] { "--help" }, true)]
25+
public void TestHelpDisplayFiresCorrectly(string[] args, bool fires)
26+
{
27+
bool calledFlag = false;
28+
29+
var usagePrinterMock = new Mock<IUsagePrinter>();
30+
31+
usagePrinterMock.Setup(mock => mock.PrintUsage()).Callback(() => calledFlag = true);
32+
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<ICommandLineCommand>())).Callback(() => calledFlag = true);
33+
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<ICommandLineOption>())).Callback(() => calledFlag = true);
34+
35+
var parser = new CommandLineParser<Options>
36+
{
37+
Printer = usagePrinterMock.Object
38+
};
39+
40+
var cmd = parser.AddCommand<CommandOptions>()
41+
.Name("db")
42+
.Description("Database commands");
43+
44+
parser.Parse(args);
45+
46+
Assert.Equal<bool>(fires, calledFlag);
47+
}
48+
49+
public class Options
50+
{
51+
[Name("x"), Description("Some description")]
52+
public string Option { get; set; }
53+
}
54+
55+
public class CommandOptions
56+
{
57+
[Name("o"), Description("Some description"), Required]
58+
public string Option { get; set; }
59+
}
60+
}
61+
}

CommandLineParser.Tests/Usage/UsagePrinterTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using MatthiWare.CommandLine;
2+
using MatthiWare.CommandLine.Abstractions;
3+
using MatthiWare.CommandLine.Abstractions.Command;
24
using MatthiWare.CommandLine.Abstractions.Usage;
35
using MatthiWare.CommandLine.Core.Attributes;
46
using Moq;
@@ -14,6 +16,12 @@ private class UsagePrinterGetsCalledOptions
1416
public string Option { get; set; }
1517
}
1618

19+
private class UsagePrinterCommandOptions
20+
{
21+
[Name("x"), Required]
22+
public string Option { get; set; }
23+
}
24+
1725
[Theory]
1826
[InlineData(new string[] { }, true)]
1927
[InlineData(new string[] { "-o", "bla" }, false)]
@@ -31,5 +39,39 @@ public void UsagePrintGetsCalledInCorrectCases(string[] args, bool called)
3139

3240
printerMock.Verify(mock => mock.PrintUsage(), called ? Times.Once() : Times.Never());
3341
}
42+
43+
[Fact]
44+
public void UsagePrinterPrintsOptionCorrectly()
45+
{
46+
var printerMock = new Mock<IUsagePrinter>();
47+
48+
var parser = new CommandLineParser<UsagePrinterGetsCalledOptions>
49+
{
50+
Printer = printerMock.Object
51+
};
52+
53+
parser.Parse(new[] { "-o", "--help" });
54+
55+
printerMock.Verify(mock => mock.PrintUsage(It.IsAny<ICommandLineOption>()), Times.Once());
56+
}
57+
58+
[Fact]
59+
public void UsagePrinterPrintsCommandCorrectly()
60+
{
61+
var printerMock = new Mock<IUsagePrinter>();
62+
63+
var parser = new CommandLineParser<UsagePrinterGetsCalledOptions>
64+
{
65+
Printer = printerMock.Object
66+
};
67+
68+
parser.AddCommand<UsagePrinterCommandOptions>()
69+
.Name("cmd")
70+
.Required();
71+
72+
parser.Parse(new[] { "-o", "bla", "cmd", "--help" });
73+
74+
printerMock.Verify(mock => mock.PrintUsage(It.IsAny<ICommandLineCommand>()), Times.Once());
75+
}
3476
}
3577
}

CommandLineParser/Abstractions/IArgument.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace MatthiWare.CommandLine.Abstractions
55
{
66
/// <summary>
77
/// Represents an argument
8-
/// <see cref="ArgumentModel"/>, <see cref="ICommandLineOption"/> and <see cref="ICommandLineCommand"/> for more info.
8+
/// See <see cref="ICommandLineOption"/> and <see cref="ICommandLineCommand"/> for more info.
99
/// </summary>
1010
public interface IArgument { }
1111
}

CommandLineParser/Abstractions/Models/ArgumentModel.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
namespace MatthiWare.CommandLine.Abstractions.Models
1+
using System.Diagnostics;
2+
3+
namespace MatthiWare.CommandLine.Abstractions.Models
24
{
35
/// <summary>
46
/// Model for command line arguments
57
/// </summary>
8+
[DebuggerDisplay("Argument key: {Key} value: {Value}")]
69
public struct ArgumentModel
710
{
811
/// <summary>

CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ namespace MatthiWare.CommandLine.Abstractions.Parsing.Command
1010
/// </summary>
1111
public interface ICommandParserResult
1212
{
13+
IArgument HelpRequestedFor { get; }
14+
15+
/// <summary>
16+
/// Returns true if the user specified a help option
17+
/// </summary>
18+
bool HelpRequested { get; }
19+
1320
/// <summary>
1421
/// Subcommands of the current command
1522
/// </summary>
@@ -26,15 +33,15 @@ public interface ICommandParserResult
2633
bool HasErrors { get; }
2734

2835
/// <summary>
29-
/// Contains the thrown exception during parsing.
36+
/// Contains the thrown exception(s) during parsing.
3037
/// </summary>
31-
Exception Error { get; }
38+
IReadOnlyCollection<Exception> Errors { get; }
3239

3340
/// <summary>
3441
/// Executes the command
3542
/// </summary>
3643
/// <exception cref="InvalidOperationException">
37-
/// Result contains exceptions. For more info see <see cref="HasErrors"/> and <see cref="Error"/> properties.
44+
/// Result contains exceptions. For more info see <see cref="HasErrors"/> and <see cref="Errors"/> properties.
3845
/// </exception>
3946
void ExecuteCommand();
4047
}

CommandLineParser/Abstractions/Parsing/IParserResult.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@ namespace MatthiWare.CommandLine.Abstractions.Parsing
77
{
88
public interface IParserResult<TResult>
99
{
10+
/// <summary>
11+
/// Returns true if the user specified a help option
12+
/// </summary>
13+
bool HelpRequested { get; }
14+
15+
IArgument HelpRequestedFor { get; }
16+
1017
/// <summary>
1118
/// Parsed result
1219
/// </summary>
1320
/// <exception cref="InvalidOperationException">
14-
/// Result contains exceptions. For more info see <see cref="HasErrors"/> and <see cref="Error"/> properties.
21+
/// Result contains exceptions. For more info see <see cref="HasErrors"/> and <see cref="Errors"/> properties.
1522
/// </exception>
1623
TResult Result { get; }
1724

@@ -23,13 +30,13 @@ public interface IParserResult<TResult>
2330
/// <summary>
2431
/// Contains the thrown exception during parsing.
2532
/// </summary>
26-
Exception Error { get; }
33+
IReadOnlyCollection<Exception> Errors { get; }
2734

2835
/// <summary>
2936
/// Executes the commands
3037
/// </summary>
3138
/// /// <exception cref="InvalidOperationException">
32-
/// Result contains exceptions. For more info see <see cref="HasErrors"/> and <see cref="Error"/> properties.
39+
/// Result contains exceptions. For more info see <see cref="HasErrors"/> and <see cref="Errors"/> properties.
3340
/// </exception>
3441
void ExecuteCommands();
3542

CommandLineParser/Abstractions/Usage/IUsagePrinter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ public interface IUsagePrinter
66
{
77
void PrintUsage();
88
void PrintUsage(ICommandLineCommand command);
9+
void PrintUsage(ICommandLineOption option);
910
}
1011
}

0 commit comments

Comments
 (0)