diff --git a/CliFx.Tests/HelpTextSpecs.cs b/CliFx.Tests/HelpTextSpecs.cs index 0939236..722eabe 100644 --- a/CliFx.Tests/HelpTextSpecs.cs +++ b/CliFx.Tests/HelpTextSpecs.cs @@ -64,6 +64,33 @@ namespace CliFx.Tests _output.WriteLine(stdOut.GetString()); } + [Fact] + public async Task Help_text_shows_usage_format_which_lists_available_sub_commands() + { + // Arrange + var (console, stdOut, _) = VirtualConsole.CreateBuffered(); + + var application = new CliApplicationBuilder() + .AddCommand() + .AddCommand() + .AddCommand() + .UseConsole(console) + .Build(); + + // Act + var exitCode = await application.RunAsync(new[] {"--help"}); + + // Assert + exitCode.Should().Be(0); + stdOut.GetString().Should().ContainAll( + "Usage", + "... named", + "... named sub" + ); + + _output.WriteLine(stdOut.GetString()); + } + [Fact] public async Task Help_text_shows_all_valid_values_for_enum_arguments() { diff --git a/CliFx/Domain/HelpTextWriter.cs b/CliFx/Domain/HelpTextWriter.cs index 5a05b95..b7528c6 100644 --- a/CliFx/Domain/HelpTextWriter.cs +++ b/CliFx/Domain/HelpTextWriter.cs @@ -105,6 +105,54 @@ namespace CliFx.Domain WriteLine(); } + private void WriteCommandUsageLineItem(CommandSchema command, bool showChildCommandsPlaceholder) + { + // Command name + if (!string.IsNullOrWhiteSpace(command.Name)) + { + Write(ConsoleColor.Cyan, command.Name); + Write(' '); + } + + // Child command placeholder + if (showChildCommandsPlaceholder) + { + Write(ConsoleColor.Cyan, "[command]"); + Write(' '); + } + + // Parameters + foreach (var parameter in command.Parameters) + { + Write(parameter.IsScalar + ? $"<{parameter.Name}>" + : $"<{parameter.Name}...>" + ); + Write(' '); + } + + // Required options + foreach (var option in command.Options.Where(o => o.IsRequired)) + { + Write(ConsoleColor.White, !string.IsNullOrWhiteSpace(option.Name) + ? $"--{option.Name}" + : $"-{option.ShortName}" + ); + Write(' '); + + Write(option.IsScalar + ? "" + : "" + ); + Write(' '); + } + + // Options placeholder + Write(ConsoleColor.White, "[options]"); + + WriteLine(); + } + private void WriteCommandUsage( CommandSchema command, IReadOnlyList childCommands) @@ -114,76 +162,28 @@ namespace CliFx.Domain WriteHeader("Usage"); - WriteCommandUsageLineItem(command, childCommands.Count > 0); + // Exe name + WriteHorizontalMargin(); + Write(_metadata.ExecutableName); + Write(' '); - if (!IsEmpty) + // Current command usage + WriteCommandUsageLineItem(command, childCommands.Any()); + + // Sub commands usage + if (childCommands.Any()) + { WriteVerticalMargin(); - foreach (var childCommand in childCommands) - { - WriteCommandUsageLineItem(childCommand, compactCommand: false, size: 4); + foreach (var childCommand in childCommands) + { + WriteHorizontalMargin(); + Write("... "); + WriteCommandUsageLineItem(childCommand, false); + } } } - private void WriteCommandUsageLineItem( - CommandSchema command, - bool compactCommand = true, - int size = 2) - { - WriteHorizontalMargin(size); - if (compactCommand) - { - // Exe name - Write(_metadata.ExecutableName); - Write(' '); - - } - // Command name - if (!string.IsNullOrWhiteSpace(command.Name)) - { - // this is fragile, because we rely that subcommand name consists - // of all required tokens - Write(ConsoleColor.Cyan, command.Name); - } - // Child command placeholder - if (compactCommand) - { - Write(' '); - Write(ConsoleColor.Cyan, "[command]"); - } - // Parameters - foreach (var parameter in command.Parameters) - { - Write(' '); - Write(parameter.IsScalar - ? $"<{parameter.Name}>" - : $"<{parameter.Name}...>" - ); - } - - // Required options - foreach (var option in command.Options.Where(o => o.IsRequired)) - { - Write(' '); - Write(ConsoleColor.White, !string.IsNullOrWhiteSpace(option.Name) - ? $"--{option.Name}" - : $"-{option.ShortName}" - ); - - Write(' '); - Write(option.IsScalar - ? "" - : "" - ); - } - - // Options placeholder - Write(' '); - Write(ConsoleColor.White, "[options]"); - - WriteLine(); - } - private void WriteCommandParameters(CommandSchema command) { if (!command.Parameters.Any()) @@ -285,7 +285,7 @@ namespace CliFx.Domain if (!option.IsRequired) { var defaultValue = argumentDefaultValues.GetValueOrDefault(option); - var defaultValueFormatted = FormatDefaultValue(defaultValue); + var defaultValueFormatted = TryFormatDefaultValue(defaultValue); if (defaultValueFormatted != null) { Write($"Default: {defaultValueFormatted}."); @@ -377,7 +377,7 @@ namespace CliFx.Domain private static string FormatValidValues(IReadOnlyList values) => values.Select(v => v.Quote()).JoinToString(", "); - private static string? FormatDefaultValue(object? defaultValue) + private static string? TryFormatDefaultValue(object? defaultValue) { if (defaultValue == null) return null; @@ -408,4 +408,4 @@ namespace CliFx.Domain } } } -} +} \ No newline at end of file diff --git a/CliFx/IConsole.cs b/CliFx/IConsole.cs index c05d151..72a2153 100644 --- a/CliFx/IConsole.cs +++ b/CliFx/IConsole.cs @@ -178,6 +178,12 @@ namespace CliFx { var parameter = stackFrame.Parameters[i]; + // Separator + if (i > 0) + { + console.Error.Write(", "); + } + // "IConsole" console.WithForegroundColor(ConsoleColor.Blue, () => console.Error.Write(parameter.Type) @@ -192,12 +198,6 @@ namespace CliFx console.Error.Write(parameter.Name) ); } - - // Separator - if (stackFrame.Parameters.Count > 1 && i < stackFrame.Parameters.Count - 1) - { - console.Error.Write(", "); - } } console.Error.Write(") ");