Skip to content

Commit 817bfc5

Browse files
authored
Fix error in handling HelpOption and VersionOption (#28)
* Fix #26 - fix error in handling HelpOption and VersionOption when StopParsingAfterVersionOption/StopParsingAfterHelpOption is set * Add additional tests for StopParsingAfterHelp/VersionOption
1 parent 32a46ca commit 817bfc5

File tree

6 files changed

+171
-4
lines changed

6 files changed

+171
-4
lines changed

src/CommandLineUtils/CommandLineApplication.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,13 @@ private CommandLineApplication(CommandLineApplication parent, string name, bool
153153

154154
/// <summary>
155155
/// Stops the parsing argument when <see cref="OptionHelp"/> is matched. Defaults to <c>true</c>.
156-
/// This will prevent any <see cref="Invoke" /> methods from being called.
156+
/// This will prevent <see cref="Invoke" /> or <see cref="ValidationErrorHandler" /> from being called.
157157
/// </summary>
158158
public bool StopParsingAfterHelpOption { get; set; } = true;
159159

160160
/// <summary>
161161
/// Stops the parsing argument when <see cref="OptionVersion"/> is matched. Defaults to <c>true</c>.
162-
/// This will prevent any <see cref="Invoke" /> methods from being called.
162+
/// This will prevent <see cref="Invoke" /> or <see cref="ValidationErrorHandler" /> from being called.
163163
/// </summary>
164164
public bool StopParsingAfterVersionOption { get; set; } = true;
165165

@@ -525,12 +525,26 @@ private int Execute(List<string> args)
525525
if (command.OptionHelp == option)
526526
{
527527
command.ShowHelp();
528-
return 0;
528+
option.TryParse(null);
529+
var parent = command;
530+
while (parent.Parent != null) parent = parent.Parent;
531+
parent.SelectedCommand = command;
532+
if (StopParsingAfterHelpOption)
533+
{
534+
return 0;
535+
}
529536
}
530537
else if (command.OptionVersion == option)
531538
{
532539
command.ShowVersion();
533-
return 0;
540+
option.TryParse(null);
541+
var parent = command;
542+
while (parent.Parent != null) parent = parent.Parent;
543+
parent.SelectedCommand = command;
544+
if (StopParsingAfterVersionOption)
545+
{
546+
return 0;
547+
}
534548
}
535549

536550
if (shortOption.Length == 2)

src/CommandLineUtils/Internal/ReflectionAppBuilder.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ private void EnsureInitialized()
8080
var parsingOptionsAttr = typeInfo.GetCustomAttribute<CommandAttribute>();
8181
parsingOptionsAttr?.Configure(App);
8282

83+
if (App.Name == null)
84+
{
85+
App.Name = type.Name;
86+
}
87+
8388
if (parsingOptionsAttr?.ThrowOnUnexpectedArgument == false)
8489
{
8590
AddRemainingArgsProperty(typeInfo);

test/CommandLineUtils.Tests/CommandLineApplicationTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,5 +736,29 @@ public void PathCanBeRelativeOrAbsolute()
736736
new CommandLineApplication(NullConsole.Singleton, "C:/path", false);
737737
new CommandLineApplication(NullConsole.Singleton, "../path", false);
738738
}
739+
740+
[Fact]
741+
public void ShortOptionsCanHaveMultipleCharacters()
742+
{
743+
var app = new CommandLineApplication();
744+
var optOne = app.Option("-o1|--option1", "Option1", CommandOptionType.NoValue);
745+
var optTwo = app.Option("-o2|--option2", "Option2", CommandOptionType.NoValue);
746+
747+
app.Execute("-o2");
748+
Assert.False(optOne.HasValue(), "Option1 should not be set");
749+
Assert.True(optTwo.HasValue(), "Option2 should be set");
750+
}
751+
752+
[Fact]
753+
public void OptionsCanVaryByCase()
754+
{
755+
var app = new CommandLineApplication();
756+
var optBig = app.Option("-F|--file", "File", CommandOptionType.NoValue);
757+
var optLittle = app.Option("-f|--force", "force", CommandOptionType.NoValue);
758+
759+
app.Execute("-f");
760+
Assert.False(optBig.HasValue(), "File should not be set");
761+
Assert.True(optLittle.HasValue(), "force should be set");
762+
}
739763
}
740764
}

test/CommandLineUtils.Tests/HelpOptionAttributeTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44
using System;
55
using System.Reflection;
66
using Xunit;
7+
using Xunit.Abstractions;
78

89
namespace McMaster.Extensions.CommandLineUtils.Tests
910
{
1011
public class HelpOptionTests
1112
{
13+
private readonly ITestOutputHelper _output;
14+
15+
public HelpOptionTests(ITestOutputHelper output)
16+
{
17+
_output = output;
18+
}
19+
1220
private class NoHelpOptionClass
1321
{
1422
[Option]
@@ -121,5 +129,44 @@ public void SetsHelpOptionOnProp()
121129
Assert.Equal("help", builder.App.OptionHelp.LongName);
122130
Assert.Equal("My help info", builder.App.OptionHelp.Description);
123131
}
132+
133+
[HelpOption]
134+
private class SimpleHelpApp
135+
{
136+
private void OnExecute()
137+
{
138+
throw new InvalidOperationException("This method should not be invoked");
139+
}
140+
}
141+
142+
[Theory]
143+
[InlineData("-h")]
144+
[InlineData("-?")]
145+
[InlineData("--help")]
146+
public void OnExecuteIsNotInvokedWhenHelpOptionSpecified(string arg)
147+
{
148+
Assert.Equal(0, CommandLineApplication.Execute<SimpleHelpApp>(new TestConsole(_output), arg));
149+
}
150+
151+
[Command(StopParsingAfterHelpOption = false)]
152+
private class KeepParsingHelpApp
153+
{
154+
[HelpOption]
155+
public bool IsHelp { get; }
156+
157+
private int OnExecute()
158+
{
159+
return 15;
160+
}
161+
}
162+
163+
[Theory]
164+
[InlineData("-h")]
165+
[InlineData("-?")]
166+
[InlineData("--help")]
167+
public void InvokesOnExecuteWhenStopParsingHelpDisabled(string arg)
168+
{
169+
Assert.Equal(15, CommandLineApplication.Execute<KeepParsingHelpApp>(new TestConsole(_output), arg));
170+
}
124171
}
125172
}

test/CommandLineUtils.Tests/ValidationTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Nate McMaster.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.ComponentModel.DataAnnotations;
56
using Xunit;
67
using Xunit.Abstractions;
@@ -168,5 +169,37 @@ public void ValidatesEmailOption(string[] args, int exitCode)
168169
{
169170
Assert.Equal(exitCode, CommandLineApplication.Execute<EmailOptionApp>(args));
170171
}
172+
173+
private class ValidationErrorApp
174+
{
175+
[Required, Option]
176+
public string Name { get; }
177+
private int OnValidationError()
178+
{
179+
return 7;
180+
}
181+
}
182+
183+
[Fact]
184+
public void OnValidationErrorIsInvokedOnError()
185+
{
186+
Assert.Equal(7, CommandLineApplication.Execute<ValidationErrorApp>(new TestConsole(_output)));
187+
}
188+
189+
private class ThrowOnExecuteApp
190+
{
191+
[Option, Required]
192+
public string Name { get; }
193+
private int OnExecute()
194+
{
195+
throw new InvalidOperationException("This method should not be invoked");
196+
}
197+
}
198+
199+
[Fact]
200+
public void OnExecuteIsNotInvokedOnValidationError()
201+
{
202+
Assert.Equal(1, CommandLineApplication.Execute<ThrowOnExecuteApp>(new TestConsole(_output)));
203+
}
171204
}
172205
}

test/CommandLineUtils.Tests/VersionOptionAttributeTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44
using System;
55
using System.Reflection;
66
using Xunit;
7+
using Xunit.Abstractions;
78

89
namespace McMaster.Extensions.CommandLineUtils.Tests
910
{
1011
public class VersionOptionTests
1112
{
13+
private readonly ITestOutputHelper _output;
14+
15+
public VersionOptionTests(ITestOutputHelper output)
16+
{
17+
_output = output;
18+
}
19+
1220
private class NoVersionOptionClass
1321
{
1422
[Option]
@@ -138,5 +146,41 @@ public void SetsVersionOptionOnProp()
138146
Assert.Equal("version", builder.App.OptionVersion.LongName);
139147
Assert.Equal("My version info", builder.App.OptionVersion.Description);
140148
}
149+
150+
[VersionOption("-?|-V|--version", "1.0.0")]
151+
private class SimpleVersionApp
152+
{
153+
private void OnExecute()
154+
{
155+
throw new InvalidOperationException("This method should not be invoked");
156+
}
157+
}
158+
159+
[Theory]
160+
[InlineData("-?")]
161+
[InlineData("-V")]
162+
[InlineData("--version")]
163+
public void OnExecuteIsNotInvokedWhenVersionOptionSpecified(string arg)
164+
{
165+
Assert.Equal(0, CommandLineApplication.Execute<SimpleVersionApp>(new TestConsole(_output), arg));
166+
}
167+
168+
[Command(StopParsingAfterVersionOption = false)]
169+
private class KeepParsingVersionApp
170+
{
171+
[VersionOption("1.0.0")]
172+
public bool IsVersion { get; }
173+
174+
private int OnExecute()
175+
{
176+
return 14;
177+
}
178+
}
179+
180+
[Fact]
181+
public void InvokesOnExecuteWhenStopParsingVersionDisabled()
182+
{
183+
Assert.Equal(14, CommandLineApplication.Execute<KeepParsingVersionApp>(new TestConsole(_output), "--version"));
184+
}
141185
}
142186
}

0 commit comments

Comments
 (0)