Skip to content

Commit 08e2e2a

Browse files
authored
Add usage printer overload (#40)
* Create base exception class that exposes the failed argument * Add PrintUsage(IArgument) * Throw exception instead of just creating a new instance * Add exception testing
1 parent 3f19037 commit 08e2e2a

14 files changed

+419
-125
lines changed

CommandLineParser.Tests/CommandLineParser.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<IsPackable>false</IsPackable>
77

8-
<RootNamespace>MatthiWare.CommandLineParser.Tests</RootNamespace>
8+
<RootNamespace>MatthiWare.CommandLine.Tests</RootNamespace>
99

1010
<LangVersion>7.3</LangVersion>
1111
</PropertyGroup>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using MatthiWare.CommandLine;
6+
using MatthiWare.CommandLine.Core.Attributes;
7+
using MatthiWare.CommandLine.Core.Exceptions;
8+
using Xunit;
9+
10+
namespace MatthiWare.CommandLine.Tests.Exceptions
11+
{
12+
public class ExceptionsTest
13+
{
14+
[Fact]
15+
public void CommandNotFoundTest()
16+
{
17+
var parser = new CommandLineParser();
18+
19+
parser.AddCommand().Name("missing").Required();
20+
21+
var result = parser.Parse(new string[] { });
22+
23+
Assert.True(result.HasErrors);
24+
25+
Assert.IsType<CommandNotFoundException>(result.Errors.First());
26+
27+
Assert.Same(parser.Commands.First(), result.Errors.Cast<CommandNotFoundException>().First().Command);
28+
}
29+
30+
[Fact]
31+
public void OptionNotFoundTest()
32+
{
33+
var parser = new CommandLineParser<Options>();
34+
35+
var result = parser.Parse(new string[] { });
36+
37+
Assert.True(result.HasErrors);
38+
39+
Assert.IsType<OptionNotFoundException>(result.Errors.First());
40+
41+
Assert.Same(parser.Options.First(), result.Errors.Cast<OptionNotFoundException>().First().Option);
42+
}
43+
44+
[Fact]
45+
public void CommandParseExceptionTest()
46+
{
47+
var parser = new CommandLineParser();
48+
49+
parser.AddCommand<Options>()
50+
.Name("missing")
51+
.Required()
52+
.Configure(opt => opt.MissingOption)
53+
.Name("o")
54+
.Required();
55+
56+
var result = parser.Parse(new string[] { "missing", "-o", "bla" });
57+
58+
Assert.True(result.HasErrors);
59+
60+
Assert.IsType<CommandParseException>(result.Errors.First());
61+
62+
Assert.Same(parser.Commands.First(), result.Errors.Cast<CommandParseException>().First().Command);
63+
}
64+
65+
[Fact]
66+
public void OptionParseExceptionTest()
67+
{
68+
var parser = new CommandLineParser<Options>();
69+
70+
var result = parser.Parse(new string[] { "-m", "bla" });
71+
72+
Assert.True(result.HasErrors);
73+
74+
Assert.IsType<OptionParseException>(result.Errors.First());
75+
76+
Assert.Same(parser.Options.First(), result.Errors.Cast<OptionParseException>().First().Option);
77+
}
78+
79+
private class Options
80+
{
81+
[Required, Name("m", "missing")]
82+
public int MissingOption { get; set; }
83+
}
84+
}
85+
}

CommandLineParser.Tests/Usage/HelpDisplayCommandTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public void TestHelpDisplayFiresCorrectly(string[] args, bool fires)
2525
var usagePrinterMock = new Mock<IUsagePrinter>();
2626

2727
usagePrinterMock.Setup(mock => mock.PrintUsage()).Callback(() => calledFlag = true);
28+
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<IArgument>())).Callback(() => calledFlag = true);
2829
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<ICommandLineCommand>())).Callback(() => calledFlag = true);
2930
usagePrinterMock.Setup(mock => mock.PrintUsage(It.IsAny<ICommandLineOption>())).Callback(() => calledFlag = true);
3031

CommandLineParser.Tests/Usage/UsagePrinterTests.cs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
using MatthiWare.CommandLine;
1+
using System.Linq;
22
using MatthiWare.CommandLine.Abstractions;
33
using MatthiWare.CommandLine.Abstractions.Command;
44
using MatthiWare.CommandLine.Abstractions.Usage;
55
using MatthiWare.CommandLine.Core.Attributes;
6+
using MatthiWare.CommandLine.Core.Exceptions;
7+
using MatthiWare.CommandLine.Core.Usage;
68
using Moq;
79
using Xunit;
810

@@ -52,7 +54,7 @@ public void UsagePrinterPrintsOptionCorrectly()
5254

5355
parser.Parse(new[] { "-o", "--help" });
5456

55-
printerMock.Verify(mock => mock.PrintUsage(It.IsAny<ICommandLineOption>()), Times.Once());
57+
printerMock.Verify(mock => mock.PrintUsage(It.IsAny<IArgument>()), Times.Once());
5658
}
5759

5860
[Fact]
@@ -71,7 +73,63 @@ public void UsagePrinterPrintsCommandCorrectly()
7173

7274
parser.Parse(new[] { "-o", "bla", "cmd", "--help" });
7375

74-
printerMock.Verify(mock => mock.PrintUsage(It.IsAny<ICommandLineCommand>()), Times.Once());
76+
printerMock.Verify(mock => mock.PrintUsage(It.IsAny<IArgument>()), Times.Once());
7577
}
78+
79+
[Theory]
80+
[InlineData(new string[] { "-o", "bla", "cmd" }, true, false)]
81+
[InlineData(new string[] { "-o", "bla", "cmd", "-x", "bla" }, false, false)]
82+
[InlineData(new string[] { "cmd", "-x", "bla" }, false, true)]
83+
public void CustomInvokedPrinterWorksCorrectly(string[] args, bool cmdPassed, bool optPassed)
84+
{
85+
var builderMock = new Mock<IUsageBuilder>();
86+
87+
var parserOptions = new CommandLineParserOptions
88+
{
89+
AutoPrintUsageAndErrors = false
90+
};
91+
92+
var parser = new CommandLineParser<UsagePrinterGetsCalledOptions>(parserOptions);
93+
94+
parser.Printer = new UsagePrinter(parserOptions, parser, builderMock.Object);
95+
96+
parser.AddCommand<UsagePrinterCommandOptions>()
97+
.Name("cmd")
98+
.Required();
99+
100+
var result = parser.Parse(args);
101+
102+
builderMock.Verify(mock => mock.Print(), Times.Never());
103+
builderMock.Verify(mock => mock.PrintCommand(It.IsAny<string>(), It.IsAny<ICommandLineCommandContainer>()), Times.Never());
104+
builderMock.Verify(mock => mock.PrintOption(It.IsAny<ICommandLineOption>(), It.IsAny<int>(), It.IsAny<bool>()), Times.Never());
105+
106+
if (result.HelpRequested)
107+
parser.Printer.PrintUsage(result.HelpRequestedFor);
108+
109+
if (result.HasErrors)
110+
{
111+
foreach (var err in result.Errors)
112+
{
113+
if (!(err is BaseParserException baseParserException)) continue;
114+
115+
parser.Printer.PrintUsage(baseParserException.Argument);
116+
}
117+
}
118+
119+
builderMock.Verify(
120+
mock => mock.Print(),
121+
ToTimes(result.HelpRequested || result.HasErrors));
122+
123+
builderMock.Verify(
124+
mock => mock.PrintCommand(It.IsAny<string>(), It.IsAny<ICommandLineCommandContainer>()),
125+
ToTimes(cmdPassed));
126+
127+
builderMock.Verify(
128+
mock => mock.PrintOption(It.IsAny<ICommandLineOption>(), It.IsAny<int>(), It.IsAny<bool>()),
129+
ToTimes(optPassed));
130+
}
131+
132+
private Times ToTimes(bool input)
133+
=> input ? Times.Once() : Times.Never();
76134
}
77135
}

CommandLineParser/Abstractions/Usage/IUsagePrinter.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,31 @@
22

33
namespace MatthiWare.CommandLine.Abstractions.Usage
44
{
5+
/// <summary>
6+
/// CLI Usage Output Printer
7+
/// </summary>
58
public interface IUsagePrinter
69
{
10+
/// <summary>
11+
/// Print global usage
12+
/// </summary>
713
void PrintUsage();
14+
/// <summary>
15+
/// Print an argument
16+
/// </summary>
17+
/// <param name="argument">The given argument</param>
18+
void PrintUsage(IArgument argument);
19+
20+
/// <summary>
21+
/// Print command usage
22+
/// </summary>
23+
/// <param name="command">The given command</param>
824
void PrintUsage(ICommandLineCommand command);
25+
26+
/// <summary>
27+
/// Print option usage
28+
/// </summary>
29+
/// <param name="option">The given option</param>
930
void PrintUsage(ICommandLineOption option);
1031
}
1132
}

CommandLineParser/CommandLineParser.xml

Lines changed: 73 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)