Skip to content
Merged
85 changes: 57 additions & 28 deletions src/Aspire.Cli/Commands/GroupedHelpWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using System.CommandLine.Help;
using Aspire.Cli.Resources;

namespace Aspire.Cli.Commands;
Expand Down Expand Up @@ -42,9 +43,7 @@ public static void WriteHelp(Command command, TextWriter writer, int? maxWidth =
}

// Usage
writer.WriteLine(HelpGroupStrings.Usage);
writer.WriteLine(GetIndent() + HelpGroupStrings.UsageSyntax);
writer.WriteLine();
WriteUsage(writer, HelpGroupStrings.UsageSyntax);

// Collect visible subcommands and organize by group.
var grouped = new Dictionary<HelpGroup, List<BaseCommand>>();
Expand Down Expand Up @@ -143,32 +142,48 @@ public static void WriteHelp(Command command, TextWriter writer, int? maxWidth =
var visibleOptions = command.Options.Where(o => !o.Hidden).ToList();
if (visibleOptions.Count > 0)
{
writer.WriteLine(HelpGroupStrings.Options);
WriteTwoColumnSection(
writer,
HelpGroupStrings.Options,
visibleOptions.Select(static opt => (FormatOptionLabel(opt), opt.Description ?? string.Empty)),
width);
}

var optionColumnWidth = 0;
foreach (var opt in visibleOptions)
{
var label = FormatOptionLabel(opt);
if (label.Length > optionColumnWidth)
{
optionColumnWidth = label.Length;
}
}
// Help hint
writer.WriteLine(HelpGroupStrings.HelpHint);
}

optionColumnWidth += 4;
internal static void WriteUsage(TextWriter writer, params string[] usages)
{
writer.WriteLine(HelpGroupStrings.Usage);
foreach (var usage in usages)
{
writer.WriteLine(GetIndent() + usage);
}

foreach (var opt in visibleOptions)
{
var label = FormatOptionLabel(opt);
var desc = opt.Description ?? string.Empty;
WriteTwoColumnRow(writer, label, desc, optionColumnWidth, width);
}
writer.WriteLine();
}

writer.WriteLine();
internal static void WriteTwoColumnSection(TextWriter writer, string heading, IEnumerable<(string Label, string Description)> rows, int maxWidth, bool trailingBlankLine = true)
{
var rowArray = rows.ToArray();
if (rowArray.Length == 0)
{
return;
}

// Help hint
writer.WriteLine(HelpGroupStrings.HelpHint);
writer.WriteLine(heading);

var columnWidth = rowArray.Max(static row => row.Label.Length) + 4;
foreach (var (label, description) in rowArray)
{
WriteTwoColumnRow(writer, label, description, columnWidth, maxWidth);
}

if (trailingBlankLine)
{
writer.WriteLine();
}
}

private static void WriteGroup(TextWriter writer, string heading, List<BaseCommand> commands, int columnWidth, int width)
Expand Down Expand Up @@ -287,7 +302,7 @@ private static string GetArgumentSyntax(Command cmd)
return string.Join(" ", parts);
}

private static string FormatOptionLabel(Option option)
internal static string FormatOptionLabel(Option option, bool includeValueName = false)
{
// Collect all identifiers: Name may not be in Aliases in System.CommandLine 2.0.
var allNames = new HashSet<string>(option.Aliases, StringComparer.Ordinal);
Expand All @@ -296,9 +311,23 @@ private static string FormatOptionLabel(Option option)
allNames.Add(option.Name);
}

var sorted = allNames.OrderBy(a => a.Length).ToList();
return sorted.Count > 1
? $"{sorted[0]}, {sorted[1]}"
: sorted.Count > 0 ? sorted[0] : option.Name;
var label = string.Join(", ", allNames.OrderBy(a => a.Length).ThenBy(a => a, StringComparer.Ordinal));
return includeValueName && !IsBooleanOption(option)
? $"{label} <{GetOptionValueName(option)}>"
: label;
}

private static bool IsBooleanOption(Option option)
{
return option is Option<bool> or HelpOption;
}

private static string GetOptionValueName(Option option)
{
var longName = option.Name.StartsWith("--", StringComparison.Ordinal)
? option.Name
: option.Aliases.FirstOrDefault(static alias => alias.StartsWith("--", StringComparison.Ordinal)) ?? option.Name;

return longName.TrimStart('-');
}
}
Loading
Loading