Skip to content

Commit afdf0fe

Browse files
committed
allow UseHelp to be called more than once on the same command
1 parent 5b25af5 commit afdf0fe

File tree

3 files changed

+121
-35
lines changed

3 files changed

+121
-35
lines changed

src/System.CommandLine.Tests/UseHelpTests.cs

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public async Task UseHelp_writes_help_for_the_specified_command()
3434

3535
_console.Out.ToString().Should().Contain($"{RootCommand.ExecutableName} command subcommand");
3636
}
37-
37+
3838
[Fact]
3939
public async Task UseHelp_interrupts_execution_of_the_specified_command()
4040
{
@@ -73,20 +73,6 @@ public async Task UseHelp_accepts_default_values(string value)
7373
_console.Out.ToString().Should().StartWith("Usage:");
7474
}
7575

76-
[Fact]
77-
public async Task UseHelp_accepts_custom_aliases()
78-
{
79-
var parser =
80-
new CommandLineBuilder()
81-
.AddCommand(new Command("command"))
82-
.UseHelp(new Option(new[] { "~cthulhu" }))
83-
.Build();
84-
85-
await parser.InvokeAsync("command ~cthulhu", _console);
86-
87-
_console.Out.ToString().Should().StartWith("Usage:");
88-
}
89-
9076
[Fact]
9177
public async Task UseHelp_does_not_display_when_option_defined_with_same_alias()
9278
{
@@ -138,5 +124,60 @@ public void There_are_no_parse_errors_when_help_is_invoked_on_subcommand()
138124
.Should()
139125
.BeEmpty();
140126
}
127+
128+
[Theory]
129+
[InlineData("-h")]
130+
[InlineData("inner -h")]
131+
public void UseHelp_can_be_called_more_than_once_on_the_same_CommandLineBuilder(string commandline)
132+
{
133+
var root = new RootCommand
134+
{
135+
new Command("inner")
136+
};
137+
138+
var parser = new CommandLineBuilder(root)
139+
.UseHelp()
140+
.UseHelp()
141+
.Build();
142+
143+
var console1 = new TestConsole();
144+
145+
parser.Invoke(commandline, console1);
146+
147+
console1.Out.ToString().Should().Contain("Usage:");
148+
console1.Error.ToString().Should().BeEmpty();
149+
}
150+
151+
[Theory]
152+
[InlineData("-h")]
153+
[InlineData("inner -h")]
154+
public void UseHelp_can_be_called_more_than_once_on_the_same_command_with_different_CommandLineBuilders(string commandline)
155+
{
156+
var root = new RootCommand
157+
{
158+
new Command("inner")
159+
};
160+
161+
var parser1 = new CommandLineBuilder(root)
162+
.UseHelp()
163+
.Build();
164+
165+
var console1 = new TestConsole();
166+
167+
parser1.Invoke(commandline, console1);
168+
169+
console1.Out.ToString().Should().Contain("Usage:");
170+
console1.Error.ToString().Should().BeEmpty();
171+
172+
var parser2 = new CommandLineBuilder(root)
173+
.UseHelp()
174+
.Build();
175+
var console2 = new TestConsole();
176+
177+
parser2.Invoke(commandline, console2);
178+
179+
console2.Out.ToString().Should().Contain("Usage:");
180+
console2.Error.ToString().Should().BeEmpty();
181+
}
141182
}
142183
}

src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -268,38 +268,69 @@ void Default(Exception exception, InvocationContext context)
268268

269269
public static CommandLineBuilder UseHelp(this CommandLineBuilder builder)
270270
{
271-
var helpOption = new Option(new []
272-
{
273-
"-h",
274-
"/h",
275-
"--help",
276-
"-?",
277-
"/?"
278-
}, "Show help and usage information");
271+
var helpOption = new HelpOption();
279272

280273
return builder.UseHelp(helpOption);
281274
}
282275

283-
public static CommandLineBuilder UseHelp(
276+
internal static CommandLineBuilder UseHelp(
284277
this CommandLineBuilder builder,
285278
Option helpOption)
286279
{
287-
builder.HelpOption = helpOption ?? throw new ArgumentNullException(nameof(helpOption));
288-
builder.HelpOption.Argument.Arity = ArgumentArity.Zero;
289-
290-
builder.Command.AddGlobalOption(builder.HelpOption);
291-
292-
builder.AddMiddleware(async (context, next) =>
280+
if (builder.HelpOption == null)
293281
{
294-
if (!ShowHelp(context, builder.HelpOption))
282+
builder.HelpOption = helpOption;
283+
builder.Command.TryAddGlobalOption(helpOption);
284+
285+
builder.AddMiddleware(async (context, next) =>
295286
{
296-
await next(context);
297-
}
298-
}, MiddlewareOrderInternal.HelpOption);
287+
if (!ShowHelp(context, builder.HelpOption))
288+
{
289+
await next(context);
290+
}
291+
}, MiddlewareOrderInternal.HelpOption);
292+
293+
return builder;
294+
}
299295

300296
return builder;
301297
}
302298

299+
private class HelpOption : Option
300+
{
301+
public HelpOption() : base(new[]
302+
{
303+
"-h",
304+
"/h",
305+
"--help",
306+
"-?",
307+
"/?"
308+
}, "Show help and usage information")
309+
{
310+
}
311+
312+
public override Argument Argument
313+
{
314+
get => Argument.None;
315+
set => throw new NotSupportedException();
316+
}
317+
318+
protected bool Equals(HelpOption other)
319+
{
320+
return other != null;
321+
}
322+
323+
public override bool Equals(object obj)
324+
{
325+
return obj is HelpOption;
326+
}
327+
328+
public override int GetHashCode()
329+
{
330+
return typeof(HelpOption).GetHashCode();
331+
}
332+
}
333+
303334
public static TBuilder UseHelpBuilder<TBuilder>(this TBuilder builder, Func<BindingContext, IHelpBuilder> getHelpBuilder)
304335
where TBuilder : CommandLineBuilder
305336
{
@@ -381,7 +412,7 @@ public static CommandLineBuilder UseSuggestDirective(
381412
{
382413
int position;
383414

384-
if (values.FirstOrDefault() is string positionString)
415+
if (values.FirstOrDefault() is { } positionString)
385416
{
386417
position = int.Parse(positionString);
387418
}

src/System.CommandLine/Command.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ public void AddGlobalOption(Option option)
3636
Children.AddWithoutAliasCollisionCheck(option);
3737
}
3838

39+
public bool TryAddGlobalOption(Option option)
40+
{
41+
if (!_globalOptions.IsAnyAliasInUse(option, out _))
42+
{
43+
_globalOptions.Add(option);
44+
Children.AddWithoutAliasCollisionCheck(option);
45+
return true;
46+
}
47+
else
48+
{
49+
return false;
50+
}
51+
}
52+
3953
public void Add(Symbol symbol) => AddSymbol(symbol);
4054

4155
public void Add(Argument argument) => AddArgument(argument);

0 commit comments

Comments
 (0)