Skip to content

Commit 98308d2

Browse files
Jon Sequeirajonsequitur
authored andcommitted
fix #2765: add RootCommand.HelpName
1 parent 0b720d0 commit 98308d2

6 files changed

Lines changed: 131 additions & 3 deletions

File tree

System.CommandLine.v3.ncrunchsolution

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
<Settings>
33
<AllowParallelTestExecution>True</AllowParallelTestExecution>
44
<CustomBuildProperties>
5-
<Value>TargetFrameworks = net8.0</Value>
6-
<Value>TargetFramework = net8.0</Value>
5+
<Value>TargetFrameworks = net10.0</Value>
6+
<Value>TargetFramework = net10.0</Value>
77
</CustomBuildProperties>
88
<EnableRDI>False</EnableRDI>
99
<RdiConfigured>True</RdiConfigured>

src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
public static System.String ExecutablePath { get; }
140140
.ctor(System.String description = )
141141
public System.Collections.Generic.IList<Directive> Directives { get; }
142+
public System.String HelpName { get; set; }
142143
public System.Void Add(Directive directive)
143144
public abstract class Symbol
144145
public System.String Description { get; set; }

src/System.CommandLine.Tests/Help/HelpBuilderTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,61 @@ public void Usage_section_does_not_contain_hidden_argument()
336336
help.Should().NotContain("hidden");
337337
}
338338

339+
[Fact]
340+
public void Usage_section_uses_HelpName_when_set_on_root_command()
341+
{
342+
var rootCommand = new RootCommand
343+
{
344+
HelpName = "my-custom-tool"
345+
};
346+
rootCommand.Options.Add(new Option<string>("--opt") { Description = "an option" });
347+
348+
_helpBuilder.Write(rootCommand, _console);
349+
350+
var expected =
351+
$"Usage:{NewLine}" +
352+
$"{_indentation}my-custom-tool [options]";
353+
354+
_console.ToString().Should().Contain(expected);
355+
}
356+
357+
[Fact]
358+
public void Usage_section_uses_HelpName_when_set_on_subcommand_parent()
359+
{
360+
var rootCommand = new RootCommand
361+
{
362+
HelpName = "my-custom-tool"
363+
};
364+
var subcommand = new Command("sub", "a subcommand");
365+
subcommand.Options.Add(new Option<string>("--opt") { Description = "an option" });
366+
rootCommand.Subcommands.Add(subcommand);
367+
368+
_helpBuilder.Write(subcommand, _console);
369+
370+
var expected =
371+
$"Usage:{NewLine}" +
372+
$"{_indentation}my-custom-tool sub [options]";
373+
374+
_console.ToString().Should().Contain(expected);
375+
}
376+
377+
[Fact]
378+
public void Usage_section_uses_Name_when_HelpName_is_not_set()
379+
{
380+
var command = new Command("the-command", "command help");
381+
command.Options.Add(new Option<string>("--opt") { Description = "an option" });
382+
var rootCommand = new RootCommand();
383+
rootCommand.Subcommands.Add(command);
384+
385+
_helpBuilder.Write(command, _console);
386+
387+
var expected =
388+
$"Usage:{NewLine}" +
389+
$"{_indentation}{_executableName} the-command [options]";
390+
391+
_console.ToString().Should().Contain(expected);
392+
}
393+
339394
#endregion Usage
340395

341396
#region Arguments

src/System.CommandLine.Tests/RootCommandTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,38 @@ public void Root_command_name_defaults_to_executable_name()
1515

1616
rootCommand.Name.Should().Be(RootCommand.ExecutableName);
1717
}
18+
19+
[Fact]
20+
public void HelpName_can_be_set_explicitly()
21+
{
22+
var rootCommand = new RootCommand
23+
{
24+
HelpName = "my-tool"
25+
};
26+
27+
rootCommand.HelpName.Should().Be("my-tool");
28+
}
29+
30+
[Fact]
31+
public void HelpName_can_be_set_to_null_explicitly()
32+
{
33+
var rootCommand = new RootCommand
34+
{
35+
HelpName = null
36+
};
37+
38+
rootCommand.HelpName.Should().BeNull();
39+
}
40+
41+
[Fact]
42+
public void Setting_HelpName_does_not_change_Name()
43+
{
44+
var rootCommand = new RootCommand
45+
{
46+
HelpName = "my-tool"
47+
};
48+
49+
rootCommand.Name.Should().Be(RootCommand.ExecutableName);
50+
}
1851
}
1952
}

src/System.CommandLine/Help/HelpBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ IEnumerable<string> GetUsageParts()
135135
displayOptionTitle = parentCommand.Options.Any(x => x.Recursive && !x.Hidden);
136136
}
137137

138-
yield return parentCommand.Name;
138+
yield return (parentCommand is RootCommand root ? root.HelpName : null) ?? parentCommand.Name;
139139

140140
if (parentCommand.Arguments.Any())
141141
{

src/System.CommandLine/RootCommand.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.CommandLine.Completions;
66
using System.CommandLine.Help;
77
using System.IO;
8+
using System.Linq;
9+
using System.Reflection;
810

911
namespace System.CommandLine
1012
{
@@ -20,6 +22,10 @@ public class RootCommand : Command
2022
{
2123
private static string? _executablePath;
2224
private static string? _executableName;
25+
private static string? _toolCommandName;
26+
private static bool _toolCommandNameInitialized;
27+
private string? _helpName;
28+
private bool _helpNameSet;
2329

2430
/// <param name="description">The description of the command, shown in help.</param>
2531
public RootCommand(string description = "") : base(ExecutableName, description)
@@ -31,6 +37,23 @@ public RootCommand(string description = "") : base(ExecutableName, description)
3137
new SuggestDirective()
3238
};
3339
}
40+
41+
/// <summary>
42+
/// Gets or sets the name used for the root command in help output.
43+
/// </summary>
44+
/// <remarks>
45+
/// If not explicitly set, defaults to the <c>ToolCommandName</c> MSBuild property value
46+
/// (when available via assembly metadata), or <c>null</c> to fall back to <see cref="Symbol.Name"/>.
47+
/// </remarks>
48+
public string? HelpName
49+
{
50+
get => _helpNameSet ? _helpName : ToolCommandName;
51+
set
52+
{
53+
_helpName = value;
54+
_helpNameSet = true;
55+
}
56+
}
3457

3558
/// <summary>
3659
/// Represents all of the directives that are valid under the root command.
@@ -52,5 +75,21 @@ public static string ExecutableName
5275
/// The path to the currently running executable.
5376
/// </summary>
5477
public static string ExecutablePath => _executablePath ??= Environment.GetCommandLineArgs()[0];
78+
79+
private static string? ToolCommandName
80+
{
81+
get
82+
{
83+
if (!_toolCommandNameInitialized)
84+
{
85+
_toolCommandName = Assembly.GetEntryAssembly()?
86+
.GetCustomAttributes<AssemblyMetadataAttribute>()
87+
.FirstOrDefault(a => a.Key == "System.CommandLine.ToolCommandName")?.Value;
88+
_toolCommandNameInitialized = true;
89+
}
90+
91+
return _toolCommandName;
92+
}
93+
}
5594
}
5695
}

0 commit comments

Comments
 (0)