Improve child command usage info in help text

This commit is contained in:
Tyrrrz
2020-10-23 23:36:36 +03:00
parent 9557d386e2
commit 3abdfb1acf
3 changed files with 100 additions and 73 deletions

View File

@@ -64,6 +64,33 @@ namespace CliFx.Tests
_output.WriteLine(stdOut.GetString()); _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<DefaultCommand>()
.AddCommand<NamedCommand>()
.AddCommand<NamedSubCommand>()
.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] [Fact]
public async Task Help_text_shows_all_valid_values_for_enum_arguments() public async Task Help_text_shows_all_valid_values_for_enum_arguments()
{ {

View File

@@ -105,6 +105,54 @@ namespace CliFx.Domain
WriteLine(); 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
? "<value>"
: "<values...>"
);
Write(' ');
}
// Options placeholder
Write(ConsoleColor.White, "[options]");
WriteLine();
}
private void WriteCommandUsage( private void WriteCommandUsage(
CommandSchema command, CommandSchema command,
IReadOnlyList<CommandSchema> childCommands) IReadOnlyList<CommandSchema> childCommands)
@@ -114,76 +162,28 @@ namespace CliFx.Domain
WriteHeader("Usage"); 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(); WriteVerticalMargin();
foreach (var childCommand in childCommands) foreach (var childCommand in childCommands)
{ {
WriteCommandUsageLineItem(childCommand, compactCommand: false, size: 4); 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
? "<value>"
: "<values...>"
);
}
// Options placeholder
Write(' ');
Write(ConsoleColor.White, "[options]");
WriteLine();
}
private void WriteCommandParameters(CommandSchema command) private void WriteCommandParameters(CommandSchema command)
{ {
if (!command.Parameters.Any()) if (!command.Parameters.Any())
@@ -285,7 +285,7 @@ namespace CliFx.Domain
if (!option.IsRequired) if (!option.IsRequired)
{ {
var defaultValue = argumentDefaultValues.GetValueOrDefault(option); var defaultValue = argumentDefaultValues.GetValueOrDefault(option);
var defaultValueFormatted = FormatDefaultValue(defaultValue); var defaultValueFormatted = TryFormatDefaultValue(defaultValue);
if (defaultValueFormatted != null) if (defaultValueFormatted != null)
{ {
Write($"Default: {defaultValueFormatted}."); Write($"Default: {defaultValueFormatted}.");
@@ -377,7 +377,7 @@ namespace CliFx.Domain
private static string FormatValidValues(IReadOnlyList<string> values) => private static string FormatValidValues(IReadOnlyList<string> values) =>
values.Select(v => v.Quote()).JoinToString(", "); values.Select(v => v.Quote()).JoinToString(", ");
private static string? FormatDefaultValue(object? defaultValue) private static string? TryFormatDefaultValue(object? defaultValue)
{ {
if (defaultValue == null) if (defaultValue == null)
return null; return null;
@@ -408,4 +408,4 @@ namespace CliFx.Domain
} }
} }
} }
} }

View File

@@ -178,6 +178,12 @@ namespace CliFx
{ {
var parameter = stackFrame.Parameters[i]; var parameter = stackFrame.Parameters[i];
// Separator
if (i > 0)
{
console.Error.Write(", ");
}
// "IConsole" // "IConsole"
console.WithForegroundColor(ConsoleColor.Blue, () => console.WithForegroundColor(ConsoleColor.Blue, () =>
console.Error.Write(parameter.Type) console.Error.Write(parameter.Type)
@@ -192,12 +198,6 @@ namespace CliFx
console.Error.Write(parameter.Name) console.Error.Write(parameter.Name)
); );
} }
// Separator
if (stackFrame.Parameters.Count > 1 && i < stackFrame.Parameters.Count - 1)
{
console.Error.Write(", ");
}
} }
console.Error.Write(") "); console.Error.Write(") ");