Skip to content

Commit 6b7d915

Browse files
rvuistinjonsequitur
authored andcommitted
Improve help sections layout...
- get rid of the `HelpContext.WasSectionSkipped' mutable property. - normalize space between sections. - adapt unit tests and approvals.
1 parent 9a4a20c commit 6b7d915

File tree

6 files changed

+64
-74
lines changed

6 files changed

+64
-74
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,26 +184,26 @@ System.CommandLine.Help
184184
public class HelpBuilder
185185
.ctor(System.Int32 maxWidth = 2147483647)
186186
public System.Int32 MaxWidth { get; }
187-
public System.Void CustomizeLayout(System.Func<HelpContext,System.Collections.Generic.IEnumerable<System.Action<HelpContext>>> getLayout)
187+
public System.Void CustomizeLayout(System.Func<HelpContext,System.Collections.Generic.IEnumerable<System.Func<HelpContext,System.Boolean>>> getLayout)
188188
public System.Void CustomizeSymbol(System.CommandLine.CliSymbol symbol, System.Func<HelpContext,System.String> firstColumnText = null, System.Func<HelpContext,System.String> secondColumnText = null, System.Func<HelpContext,System.String> defaultValue = null)
189189
public System.Void CustomizeSymbol(System.CommandLine.CliSymbol symbol, System.String firstColumnText = null, System.String secondColumnText = null, System.String defaultValue = null)
190190
public TwoColumnHelpRow GetTwoColumnRow(System.CommandLine.CliSymbol symbol, HelpContext context)
191191
public System.Void Write(HelpContext context)
192192
public System.Void Write(System.CommandLine.CliCommand command, System.IO.TextWriter writer)
193193
public System.Void WriteColumns(System.Collections.Generic.IReadOnlyList<TwoColumnHelpRow> items, HelpContext context)
194194
static class Default
195-
public static System.Action<HelpContext> AdditionalArgumentsSection()
196-
public static System.Action<HelpContext> CommandArgumentsSection()
197-
public static System.Action<HelpContext> CommandUsageSection()
195+
public static System.Func<HelpContext,System.Boolean> AdditionalArgumentsSection()
196+
public static System.Func<HelpContext,System.Boolean> CommandArgumentsSection()
197+
public static System.Func<HelpContext,System.Boolean> CommandUsageSection()
198198
public static System.String GetArgumentDefaultValue(System.CommandLine.CliArgument argument)
199199
public static System.String GetArgumentDescription(System.CommandLine.CliArgument argument)
200200
public static System.String GetArgumentUsageLabel(System.CommandLine.CliArgument argument)
201201
public static System.String GetCommandUsageLabel(System.CommandLine.CliCommand symbol)
202-
public static System.Collections.Generic.IEnumerable<System.Action<HelpContext>> GetLayout()
202+
public static System.Collections.Generic.IEnumerable<System.Func<HelpContext,System.Boolean>> GetLayout()
203203
public static System.String GetOptionUsageLabel(System.CommandLine.CliOption symbol)
204-
public static System.Action<HelpContext> OptionsSection()
205-
public static System.Action<HelpContext> SubcommandsSection()
206-
public static System.Action<HelpContext> SynopsisSection()
204+
public static System.Func<HelpContext,System.Boolean> OptionsSection()
205+
public static System.Func<HelpContext,System.Boolean> SubcommandsSection()
206+
public static System.Func<HelpContext,System.Boolean> SynopsisSection()
207207
public class HelpContext
208208
.ctor(HelpBuilder helpBuilder, System.CommandLine.CliCommand command, System.IO.TextWriter output, System.CommandLine.ParseResult parseResult = null)
209209
public System.CommandLine.CliCommand Command { get; }

src/System.CommandLine.Tests/Help/Approvals/HelpBuilderTests.Help_layout_has_not_changed.approved.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,3 @@ Options:
2222
multi-line
2323
description
2424

25-
26-

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

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -387,13 +387,13 @@ public void Help_sections_can_be_replaced()
387387

388388
parseResult.Invoke();
389389

390-
config.Output.ToString().Should().Be($"one{NewLine}{NewLine}two{NewLine}{NewLine}three{NewLine}{NewLine}{NewLine}");
390+
config.Output.ToString().Should().Be($"one{NewLine}{NewLine}two{NewLine}{NewLine}three{NewLine}{NewLine}");
391391

392-
IEnumerable<Action<HelpContext>> CustomLayout(HelpContext _)
392+
IEnumerable<Func<HelpContext, bool>> CustomLayout(HelpContext _)
393393
{
394-
yield return ctx => ctx.Output.WriteLine("one");
395-
yield return ctx => ctx.Output.WriteLine("two");
396-
yield return ctx => ctx.Output.WriteLine("three");
394+
yield return ctx => { ctx.Output.WriteLine("one"); return true; };
395+
yield return ctx => { ctx.Output.WriteLine("two"); return true; };
396+
yield return ctx => { ctx.Output.WriteLine("three"); return true; };
397397
}
398398
}
399399

@@ -418,20 +418,20 @@ public void Help_sections_can_be_supplemented()
418418

419419
var output = config.Output.ToString();
420420

421-
var expected = $"first{NewLine}{NewLine}{defaultHelp}last{NewLine}{NewLine}";
421+
var expected = $"first{NewLine}{NewLine}{defaultHelp}{NewLine}last{NewLine}{NewLine}";
422422

423423
output.Should().Be(expected);
424424

425-
IEnumerable<Action<HelpContext>> CustomLayout(HelpContext _)
425+
IEnumerable<Func<HelpContext, bool>> CustomLayout(HelpContext _)
426426
{
427-
yield return ctx => ctx.Output.WriteLine("first");
427+
yield return ctx => { ctx.Output.WriteLine("first"); return true; };
428428

429429
foreach (var section in HelpBuilder.Default.GetLayout())
430430
{
431431
yield return section;
432432
}
433433

434-
yield return ctx => ctx.Output.WriteLine("last");
434+
yield return ctx => { ctx.Output.WriteLine("last"); return true; };
435435
}
436436
}
437437

@@ -454,13 +454,10 @@ public void Layout_can_be_composed_dynamically_based_on_context()
454454

455455
var config = new CliConfiguration(command);
456456
helpBuilder.CustomizeLayout(c =>
457-
c.Command == commandWithTypicalHelp
458-
? HelpBuilder.Default.GetLayout()
459-
: new Action<HelpContext>[]
460-
{
461-
c => c.Output.WriteLine("Custom layout!")
462-
}
463-
.Concat(HelpBuilder.Default.GetLayout()));
457+
c.Command == commandWithTypicalHelp
458+
? HelpBuilder.Default.GetLayout()
459+
: new Func<HelpContext, bool>[] { c => { c.Output.WriteLine("Custom layout!"); return true; } }
460+
.Concat(HelpBuilder.Default.GetLayout()));
464461

465462
var typicalOutput = new StringWriter();
466463
config.Output = typicalOutput;
@@ -509,7 +506,7 @@ public void Help_default_sections_can_be_wrapped()
509506
$" <option> description{NewLine}" +
510507
$" -?, -h, Show help and {NewLine}" +
511508
$" --help usage {NewLine}" +
512-
$" information{NewLine}{NewLine}{NewLine}");
509+
$" information{NewLine}{NewLine}");
513510
}
514511

515512
[Fact]
@@ -531,11 +528,11 @@ public void Help_customized_sections_can_be_wrapped()
531528
parseResult.Invoke();
532529

533530
string result = config.Output.ToString();
534-
result.Should().Be($" 123 123{NewLine} 456 456{NewLine} 78 789{NewLine} 0{NewLine}{NewLine}{NewLine}");
531+
result.Should().Be($" 123 123{NewLine} 456 456{NewLine} 78 789{NewLine} 0{NewLine}{NewLine}");
535532

536-
IEnumerable<Action<HelpContext>> CustomLayout(HelpContext _)
533+
IEnumerable<Func<HelpContext, bool>> CustomLayout(HelpContext _)
537534
{
538-
yield return ctx => ctx.HelpBuilder.WriteColumns(new[] { new TwoColumnHelpRow("12345678", "1234567890") }, ctx);
535+
yield return ctx => { ctx.HelpBuilder.WriteColumns(new[] { new TwoColumnHelpRow("12345678", "1234567890") }, ctx); return true; };
539536
}
540537
}
541538

src/System.CommandLine/Help/HelpBuilder.Default.cs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ private static string GetIdentifierSymbolUsageLabel(CliSymbol symbol, AliasSet?
123123
/// <summary>
124124
/// Gets the default sections to be written for command line help.
125125
/// </summary>
126-
public static IEnumerable<Action<HelpContext>> GetLayout()
126+
public static IEnumerable<Func<HelpContext, bool>> GetLayout()
127127
{
128128
yield return SynopsisSection();
129129
yield return CommandUsageSection();
@@ -136,49 +136,51 @@ public static IEnumerable<Action<HelpContext>> GetLayout()
136136
/// <summary>
137137
/// Writes a help section describing a command's synopsis.
138138
/// </summary>
139-
public static Action<HelpContext> SynopsisSection() =>
139+
public static Func<HelpContext, bool> SynopsisSection() =>
140140
ctx =>
141141
{
142142
ctx.HelpBuilder.WriteHeading(LocalizationResources.HelpDescriptionTitle(), ctx.Command.Description, ctx.Output);
143+
return true;
143144
};
144145

145146
/// <summary>
146147
/// Writes a help section describing a command's usage.
147148
/// </summary>
148-
public static Action<HelpContext> CommandUsageSection() =>
149+
public static Func<HelpContext, bool> CommandUsageSection() =>
149150
ctx =>
150151
{
151152
ctx.HelpBuilder.WriteHeading(LocalizationResources.HelpUsageTitle(), ctx.HelpBuilder.GetUsage(ctx.Command), ctx.Output);
153+
return true;
152154
};
153155

154156
/// <summary>
155157
/// Writes a help section describing a command's arguments.
156158
/// </summary>
157-
public static Action<HelpContext> CommandArgumentsSection() =>
159+
public static Func<HelpContext, bool> CommandArgumentsSection() =>
158160
ctx =>
159161
{
160162
TwoColumnHelpRow[] commandArguments = ctx.HelpBuilder.GetCommandArgumentRows(ctx.Command, ctx).ToArray();
161163

162-
if (commandArguments.Length <= 0)
164+
if (commandArguments.Length > 0)
163165
{
164-
ctx.WasSectionSkipped = true;
165-
return;
166+
ctx.HelpBuilder.WriteHeading(LocalizationResources.HelpArgumentsTitle(), null, ctx.Output);
167+
ctx.HelpBuilder.WriteColumns(commandArguments, ctx);
168+
return true;
166169
}
167170

168-
ctx.HelpBuilder.WriteHeading(LocalizationResources.HelpArgumentsTitle(), null, ctx.Output);
169-
ctx.HelpBuilder.WriteColumns(commandArguments, ctx);
171+
return false;
170172
};
171173

172174
/// <summary>
173175
/// Writes a help section describing a command's subcommands.
174176
/// </summary>
175-
public static Action<HelpContext> SubcommandsSection() =>
177+
public static Func<HelpContext, bool> SubcommandsSection() =>
176178
ctx => ctx.HelpBuilder.WriteSubcommands(ctx);
177179

178180
/// <summary>
179181
/// Writes a help section describing a command's options.
180182
/// </summary>
181-
public static Action<HelpContext> OptionsSection() =>
183+
public static Func<HelpContext, bool> OptionsSection() =>
182184
ctx =>
183185
{
184186
List<TwoColumnHelpRow> optionRows = new();
@@ -230,21 +232,20 @@ public static Action<HelpContext> OptionsSection() =>
230232
current = parentCommand;
231233
}
232234

233-
if (optionRows.Count <= 0)
235+
if (optionRows.Count > 0)
234236
{
235-
ctx.WasSectionSkipped = true;
236-
return;
237+
ctx.HelpBuilder.WriteHeading(LocalizationResources.HelpOptionsTitle(), null, ctx.Output);
238+
ctx.HelpBuilder.WriteColumns(optionRows, ctx);
239+
return true;
237240
}
238241

239-
ctx.HelpBuilder.WriteHeading(LocalizationResources.HelpOptionsTitle(), null, ctx.Output);
240-
ctx.HelpBuilder.WriteColumns(optionRows, ctx);
241-
ctx.Output.WriteLine();
242+
return false;
242243
};
243244

244245
/// <summary>
245246
/// Writes a help section describing a command's additional arguments, typically shown only when <see cref="CliCommand.TreatUnmatchedTokensAsErrors"/> is set to <see langword="true"/>.
246247
/// </summary>
247-
public static Action<HelpContext> AdditionalArgumentsSection() =>
248+
public static Func<HelpContext, bool> AdditionalArgumentsSection() =>
248249
ctx => ctx.HelpBuilder.WriteAdditionalArguments(ctx);
249250
}
250251
}

src/System.CommandLine/Help/HelpBuilder.cs

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public partial class HelpBuilder
1616
private const string Indent = " ";
1717

1818
private Dictionary<CliSymbol, Customization>? _customizationsBySymbol;
19-
private Func<HelpContext, IEnumerable<Action<HelpContext>>>? _getLayout;
19+
private Func<HelpContext, IEnumerable<Func<HelpContext, bool>>>? _getLayout;
2020

2121
/// <param name="maxWidth">The maximum width in characters after which help output is wrapped.</param>
2222
public HelpBuilder(int maxWidth = int.MaxValue)
@@ -50,15 +50,11 @@ public virtual void Write(HelpContext context)
5050

5151
foreach (var writeSection in GetLayout(context))
5252
{
53-
writeSection(context);
54-
55-
if (!context.WasSectionSkipped)
53+
if (writeSection(context))
5654
{
5755
context.Output.WriteLine();
5856
}
5957
}
60-
61-
context.Output.WriteLine();
6258
}
6359

6460
/// <summary>
@@ -86,8 +82,11 @@ public void CustomizeSymbol(CliSymbol symbol,
8682
/// <summary>
8783
/// Customizes the help sections that will be displayed.
8884
/// </summary>
89-
/// <param name="getLayout">A delegate that returns the sections in the order in which they should be written.</param>
90-
public void CustomizeLayout(Func<HelpContext, IEnumerable<Action<HelpContext>>> getLayout)
85+
/// <param name="getLayout">
86+
/// A delegate that returns the sections in the order in which they should be written.<br/>
87+
/// The section delegate should return false if the section is empty, true otherwise.
88+
/// </param>
89+
public void CustomizeLayout(Func<HelpContext, IEnumerable<Func<HelpContext, bool>>> getLayout)
9190
{
9291
_getLayout = getLayout ?? throw new ArgumentNullException(nameof(getLayout));
9392
}
@@ -149,30 +148,27 @@ private IEnumerable<TwoColumnHelpRow> GetCommandArgumentRows(CliCommand command,
149148
.Select(a => GetTwoColumnRow(a, context))
150149
.Distinct();
151150

152-
private void WriteSubcommands(HelpContext context)
151+
private bool WriteSubcommands(HelpContext context)
153152
{
154153
var subcommands = context.Command.Subcommands.Where(x => !x.Hidden).Select(x => GetTwoColumnRow(x, context)).ToArray();
155-
156-
if (subcommands.Length <= 0)
154+
if (subcommands.Length > 0)
157155
{
158-
context.WasSectionSkipped = true;
159-
return;
156+
WriteHeading(LocalizationResources.HelpCommandsTitle(), null, context.Output);
157+
WriteColumns(subcommands, context);
158+
return true;
160159
}
161-
162-
WriteHeading(LocalizationResources.HelpCommandsTitle(), null, context.Output);
163-
WriteColumns(subcommands, context);
160+
return false;
164161
}
165162

166-
private void WriteAdditionalArguments(HelpContext context)
163+
private bool WriteAdditionalArguments(HelpContext context)
167164
{
168-
if (context.Command.TreatUnmatchedTokensAsErrors)
165+
if (!context.Command.TreatUnmatchedTokensAsErrors)
169166
{
170-
context.WasSectionSkipped = true;
171-
return;
167+
WriteHeading(LocalizationResources.HelpAdditionalArgumentsTitle(),
168+
LocalizationResources.HelpAdditionalArgumentsDescription(), context.Output);
169+
return true;
172170
}
173-
174-
WriteHeading(LocalizationResources.HelpAdditionalArgumentsTitle(),
175-
LocalizationResources.HelpAdditionalArgumentsDescription(), context.Output);
171+
return false;
176172
}
177173

178174
private void WriteHeading(string? heading, string? description, TextWriter writer)
@@ -311,7 +307,7 @@ bool IsOptional(CliArgument argument) =>
311307
argument.Arity.MinimumNumberOfValues == 0;
312308
}
313309

314-
private IEnumerable<Action<HelpContext>> GetLayout(HelpContext context)
310+
private IEnumerable<Func<HelpContext, bool>> GetLayout(HelpContext context)
315311
{
316312
if (_getLayout is null)
317313
{

src/System.CommandLine/Help/HelpContext.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,5 @@ public HelpContext(
4545
/// A text writer to write output to.
4646
/// </summary>
4747
public TextWriter Output { get; }
48-
49-
internal bool WasSectionSkipped { get; set; }
5048
}
5149
}

0 commit comments

Comments
 (0)