Skip to content

Commit 3c1af6b

Browse files
committed
add HelpBuilder.Write(IOption)
1 parent 641ee79 commit 3c1af6b

File tree

2 files changed

+76
-42
lines changed

2 files changed

+76
-42
lines changed

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using FluentAssertions;
1111
using System.Linq;
1212
using System.Text;
13+
using FluentAssertions.Execution;
1314
using Xunit;
1415
using Xunit.Abstractions;
1516
using static System.Environment;
@@ -1089,15 +1090,15 @@ public void Options_section_does_not_contain_hidden_argument()
10891090
var command = new Command("the-command", "Does things.");
10901091
var opt1 = new Option("option1")
10911092
{
1092-
Argument = new Argument<int>()
1093+
Argument = new Argument<int>
10931094
{
10941095
Name = "the-hidden",
10951096
IsHidden = true
10961097
}
10971098
};
10981099
var opt2 = new Option("option2")
10991100
{
1100-
Argument = new Argument<int>()
1101+
Argument = new Argument<int>
11011102
{
11021103
Name = "the-visible",
11031104
IsHidden = false
@@ -1238,7 +1239,7 @@ public void Option_aliases_are_shown_before_long_names_regardless_of_alphabetica
12381239
}
12391240

12401241
[Fact]
1241-
public void Help_describes_default_value_for_option_with_defaultable_argument()
1242+
public void Help_describes_default_value_for_option_with_argument_having_default_value()
12421243
{
12431244
var argument = new Argument
12441245
{
@@ -1289,6 +1290,28 @@ public void Help_should_not_contain_default_value_for_hidden_argument_defined_fo
12891290
help.Should().NotContain($"[default: the-arg-value]");
12901291
}
12911292

1293+
[Fact]
1294+
public void Option_help_can_be_requested_in_isolation()
1295+
{
1296+
var option = new Option(
1297+
new[] { "-z", "-a", "--zzz", "--aaa" },
1298+
"from a to z");
1299+
1300+
_helpBuilder.Write(option);
1301+
1302+
using var _ = new AssertionScope();
1303+
1304+
var output = _console.Out.ToString();
1305+
1306+
output
1307+
.Should()
1308+
.NotContain("Options");
1309+
1310+
output
1311+
.Should()
1312+
.Be("-a, -z, --aaa, --zzz from a to z");
1313+
}
1314+
12921315
#endregion Options
12931316

12941317
#region Subcommands

src/System.CommandLine/Help/HelpBuilder.cs

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ public virtual void Write(ICommand command)
7070
AddAdditionalArguments(command);
7171
}
7272

73+
public virtual void Write(IOption option)
74+
{
75+
if (option is null)
76+
{
77+
throw new ArgumentNullException(nameof(option));
78+
}
79+
80+
var item = GetOptionHelpItems(option).ToList()[0];
81+
82+
Console.Out.Write($"{item.Invocation} {item.Description}");
83+
}
84+
7385
protected int CurrentIndentation => _indentationLevel * IndentationSize;
7486

7587
/// <summary>
@@ -121,7 +133,6 @@ private void AppendBlankLine()
121133
Console.Out.WriteLine();
122134
}
123135

124-
125136
/// <summary>
126137
/// Writes whitespace to the console based on the provided offset,
127138
/// defaulting to the <see cref="CurrentIndentation"/>
@@ -203,7 +214,9 @@ private void AppendDescription(string description)
203214
/// Maximum number of characters accross all <see cref="HelpItem">help items</see>
204215
/// occupied by the invocation text
205216
/// </param>
206-
protected void AppendHelpItem(HelpItem helpItem, int maxInvocationWidth)
217+
protected void AppendHelpItem(
218+
HelpItem helpItem,
219+
int maxInvocationWidth)
207220
{
208221
if (helpItem is null)
209222
{
@@ -346,11 +359,6 @@ protected virtual string DefaultValueHint(IArgument argument, bool isSingleArgum
346359
_ => ""
347360
};
348361

349-
/// <summary>
350-
/// Formats the help rows for a given option
351-
/// </summary>
352-
/// <param name="symbol"></param>
353-
/// <returns>A new <see cref="HelpItem"/></returns>
354362
private IEnumerable<HelpItem> GetOptionHelpItems(ISymbol symbol)
355363
{
356364
var rawAliases = symbol
@@ -416,7 +424,7 @@ protected virtual void AddSynopsis(ICommand command)
416424
}
417425

418426
var title = $"{command.Name}:";
419-
HelpSection.Write(this, title, command.Description);
427+
HelpSection.WriteHeading(this, title, command.Description);
420428
}
421429

422430
/// <summary>
@@ -477,7 +485,7 @@ protected virtual void AddUsage(ICommand command)
477485
usage.Add(Usage.AdditionalArguments);
478486
}
479487

480-
HelpSection.Write(this, Usage.Title, string.Join(" ", usage.Where(u => !string.IsNullOrWhiteSpace(u))));
488+
HelpSection.WriteHeading(this, Usage.Title, string.Join(" ", usage.Where(u => !string.IsNullOrWhiteSpace(u))));
481489
}
482490

483491
private string FormatArgumentUsage(IReadOnlyCollection<IArgument> arguments)
@@ -553,7 +561,10 @@ protected virtual void AddArguments(ICommand command)
553561
commands.Add(command);
554562
}
555563

556-
HelpSection.Write(this, Arguments.Title, commands, GetArgumentHelpItems);
564+
HelpSection.WriteItems(
565+
this,
566+
Arguments.Title,
567+
commands.SelectMany(GetArgumentHelpItems).Distinct().ToArray());
557568
}
558569

559570
/// <summary>
@@ -564,12 +575,15 @@ protected virtual void AddArguments(ICommand command)
564575
protected virtual void AddOptions(ICommand command)
565576
{
566577
var options = command
567-
.Children
568-
.OfType<IOption>()
569-
.Where(ShouldShowHelp)
570-
.ToArray();
571-
572-
HelpSection.Write(this, Options.Title, options, GetOptionHelpItems);
578+
.Children
579+
.OfType<IOption>()
580+
.Where(ShouldShowHelp)
581+
.ToArray();
582+
583+
HelpSection.WriteItems(
584+
this,
585+
Options.Title,
586+
options.SelectMany(GetOptionHelpItems).Distinct().ToArray());
573587
}
574588

575589
/// <summary>
@@ -580,12 +594,14 @@ protected virtual void AddOptions(ICommand command)
580594
protected virtual void AddSubcommands(ICommand command)
581595
{
582596
var subcommands = command
583-
.Children
584-
.OfType<ICommand>()
585-
.Where(ShouldShowHelp)
586-
.ToArray();
587-
588-
HelpSection.Write(this, Commands.Title, subcommands, GetOptionHelpItems);
597+
.Children
598+
.OfType<ICommand>()
599+
.Where(ShouldShowHelp)
600+
.ToArray();
601+
602+
HelpSection.WriteItems(this,
603+
Commands.Title,
604+
subcommands.SelectMany(GetOptionHelpItems).ToArray());
589605
}
590606

591607
protected virtual void AddAdditionalArguments(ICommand command)
@@ -595,7 +611,7 @@ protected virtual void AddAdditionalArguments(ICommand command)
595611
return;
596612
}
597613

598-
HelpSection.Write(this, AdditionalArguments.Title, AdditionalArguments.Description);
614+
HelpSection.WriteHeading(this, AdditionalArguments.Title, AdditionalArguments.Description);
599615
}
600616

601617
private bool ShouldDisplayArgumentHelp(ICommand? command)
@@ -650,12 +666,12 @@ protected bool Equals(HelpItem other) =>
650666

651667
private static class HelpSection
652668
{
653-
public static void Write(
669+
public static void WriteHeading(
654670
HelpBuilder builder,
655671
string title,
656672
string? description = null)
657673
{
658-
if (!ShouldWrite(description, null))
674+
if (!ShouldWrite(description, Array.Empty<ISymbol>()))
659675
{
660676
return;
661677
}
@@ -667,34 +683,35 @@ public static void Write(
667683
builder.AppendBlankLine();
668684
}
669685

670-
public static void Write(
686+
public static void WriteItems(
671687
HelpBuilder builder,
672688
string title,
673-
IReadOnlyCollection<ISymbol>? usageItems = null,
674-
Func<ISymbol, IEnumerable<HelpItem>>? formatter = null,
689+
IReadOnlyCollection<HelpItem> usageItems,
675690
string? description = null)
676691
{
677-
if (!ShouldWrite(description, usageItems))
692+
if (usageItems.Count == 0)
678693
{
679694
return;
680695
}
681696

682697
AppendHeading(builder, title);
683698
builder.Indent();
699+
684700
AddDescription(builder, description);
685-
AddInvocation(builder, usageItems, formatter);
701+
AddInvocation(builder, usageItems);
702+
686703
builder.Outdent();
687704
builder.AppendBlankLine();
688705
}
689706

690-
private static bool ShouldWrite(string? description, IReadOnlyCollection<ISymbol>? usageItems)
707+
private static bool ShouldWrite(string? description, IReadOnlyCollection<ISymbol> usageItems)
691708
{
692709
if (!string.IsNullOrWhiteSpace(description))
693710
{
694711
return true;
695712
}
696713

697-
return usageItems?.Any() == true;
714+
return usageItems.Count > 0;
698715
}
699716

700717
private static void AppendHeading(HelpBuilder builder, string? title = null)
@@ -719,14 +736,8 @@ private static void AddDescription(HelpBuilder builder, string? description = nu
719736

720737
private static void AddInvocation(
721738
HelpBuilder builder,
722-
IReadOnlyCollection<ISymbol>? symbols,
723-
Func<ISymbol, IEnumerable<HelpItem>>? formatter)
739+
IReadOnlyCollection<HelpItem> helpItems)
724740
{
725-
var helpItems = symbols
726-
.SelectMany(formatter)
727-
.Distinct()
728-
.ToList();
729-
730741
var maxWidth = helpItems
731742
.Select(line => line.Invocation.Length)
732743
.OrderByDescending(textLength => textLength)

0 commit comments

Comments
 (0)