diff --git a/src/Spectre.Console.Cli/ConfiguratorExtensions.cs b/src/Spectre.Console.Cli/ConfiguratorExtensions.cs index 630edb49e..e4a234fc1 100644 --- a/src/Spectre.Console.Cli/ConfiguratorExtensions.cs +++ b/src/Spectre.Console.Cli/ConfiguratorExtensions.cs @@ -40,6 +40,23 @@ public static IConfigurator SetApplicationVersion(this IConfigurator configurato return configurator; } + /// + /// Hides the DEFAULT column that lists default values coming from the + /// in the options help text. + /// + /// The configurator. + /// A configurator that can be used to configure the application further. + public static IConfigurator HideOptionDefaultValues(this IConfigurator configurator) + { + if (configurator == null) + { + throw new ArgumentNullException(nameof(configurator)); + } + + configurator.Settings.ShowOptionDefaultValues = false; + return configurator; + } + /// /// Configures the console. /// diff --git a/src/Spectre.Console.Cli/ICommandAppSettings.cs b/src/Spectre.Console.Cli/ICommandAppSettings.cs index 6fb579bac..4e3b5b904 100644 --- a/src/Spectre.Console.Cli/ICommandAppSettings.cs +++ b/src/Spectre.Console.Cli/ICommandAppSettings.cs @@ -15,6 +15,11 @@ public interface ICommandAppSettings /// string? ApplicationVersion { get; set; } + /// + /// Gets or sets a value indicating whether any default values for command options are shown in the help text. + /// + bool ShowOptionDefaultValues { get; set; } + /// /// Gets or sets the . /// @@ -34,11 +39,11 @@ public interface ICommandAppSettings /// /// Gets or sets case sensitivity. /// - CaseSensitivity CaseSensitivity { get; set; } - - /// - /// Gets or sets a value indicating whether trailing period of a description is trimmed. - /// + CaseSensitivity CaseSensitivity { get; set; } + + /// + /// Gets or sets a value indicating whether trailing period of a description is trimmed. + /// bool TrimTrailingPeriod { get; set; } /// diff --git a/src/Spectre.Console.Cli/Internal/CommandExecutor.cs b/src/Spectre.Console.Cli/Internal/CommandExecutor.cs index e4826528b..e80a294c0 100644 --- a/src/Spectre.Console.Cli/Internal/CommandExecutor.cs +++ b/src/Spectre.Console.Cli/Internal/CommandExecutor.cs @@ -53,7 +53,7 @@ public async Task Execute(IConfiguration configuration, IEnumerable if (parsedResult.Tree == null) { // Display help. - configuration.Settings.Console.SafeRender(HelpWriter.Write(model)); + configuration.Settings.Console.SafeRender(HelpWriter.Write(model, configuration.Settings.ShowOptionDefaultValues)); return 0; } @@ -62,7 +62,7 @@ public async Task Execute(IConfiguration configuration, IEnumerable if (leaf.Command.IsBranch || leaf.ShowHelp) { // Branches can't be executed. Show help. - configuration.Settings.Console.SafeRender(HelpWriter.WriteCommand(model, leaf.Command)); + configuration.Settings.Console.SafeRender(HelpWriter.WriteCommand(model, leaf.Command, configuration.Settings.ShowOptionDefaultValues)); return leaf.ShowHelp ? 0 : 1; } @@ -70,7 +70,7 @@ public async Task Execute(IConfiguration configuration, IEnumerable if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required)) { // Display help for default command. - configuration.Settings.Console.SafeRender(HelpWriter.WriteCommand(model, leaf.Command)); + configuration.Settings.Console.SafeRender(HelpWriter.WriteCommand(model, leaf.Command, configuration.Settings.ShowOptionDefaultValues)); return 1; } diff --git a/src/Spectre.Console.Cli/Internal/Configuration/CommandAppSettings.cs b/src/Spectre.Console.Cli/Internal/Configuration/CommandAppSettings.cs index 4723bcb5a..b1d530023 100644 --- a/src/Spectre.Console.Cli/Internal/Configuration/CommandAppSettings.cs +++ b/src/Spectre.Console.Cli/Internal/Configuration/CommandAppSettings.cs @@ -4,6 +4,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings { public string? ApplicationName { get; set; } public string? ApplicationVersion { get; set; } + public bool ShowOptionDefaultValues { get; set; } public IAnsiConsole? Console { get; set; } public ICommandInterceptor? Interceptor { get; set; } public ITypeRegistrarFrontend Registrar { get; set; } @@ -22,6 +23,7 @@ public CommandAppSettings(ITypeRegistrar registrar) { Registrar = new TypeRegistrar(registrar); CaseSensitivity = CaseSensitivity.All; + ShowOptionDefaultValues = true; } public bool IsTrue(Func func, string environmentVariableName) diff --git a/src/Spectre.Console.Cli/Internal/HelpWriter.cs b/src/Spectre.Console.Cli/Internal/HelpWriter.cs index d6b0b3877..da58da88e 100644 --- a/src/Spectre.Console.Cli/Internal/HelpWriter.cs +++ b/src/Spectre.Console.Cli/Internal/HelpWriter.cs @@ -34,42 +34,45 @@ private sealed class HelpOption public string? Value { get; } public bool? ValueIsOptional { get; } public string? Description { get; } + public object? DefaultValue { get; } - public HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description) + public HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description, object? defaultValue) { Short = @short; Long = @long; Value = value; ValueIsOptional = valueIsOptional; Description = description; + DefaultValue = defaultValue; } public static IReadOnlyList Get(CommandModel model, CommandInfo? command) { var parameters = new List(); - parameters.Add(new HelpOption("h", "help", null, null, "Prints help information")); + parameters.Add(new HelpOption("h", "help", null, null, "Prints help information", null)); // At the root and no default command? if (command == null && model?.DefaultCommand == null) { - parameters.Add(new HelpOption("v", "version", null, null, "Prints version information")); + parameters.Add(new HelpOption("v", "version", null, null, "Prints version information", null)); } parameters.AddRange(command?.Parameters.OfType().Where(o => !o.IsHidden).Select(o => new HelpOption( o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(), - o.ValueName, o.ValueIsOptional, o.Description)) + o.ValueName, o.ValueIsOptional, o.Description, + o.ParameterKind == ParameterKind.Flag && o.DefaultValue?.Value is false ? null : o.DefaultValue?.Value)) ?? Array.Empty()); return parameters; } } - public static IEnumerable Write(CommandModel model) + public static IEnumerable Write(CommandModel model, bool writeOptionsDefaultValues) { - return WriteCommand(model, null); + return WriteCommand(model, null, writeOptionsDefaultValues); } - public static IEnumerable WriteCommand(CommandModel model, CommandInfo? command) + public static IEnumerable WriteCommand(CommandModel model, CommandInfo? command, bool writeOptionsDefaultValues) { var container = command as ICommandContainer ?? model; var isDefaultCommand = command?.IsDefaultCommand ?? false; @@ -79,7 +82,7 @@ public static IEnumerable WriteCommand(CommandModel model, CommandI result.AddRange(GetUsage(model, command)); result.AddRange(GetExamples(model, command)); result.AddRange(GetArguments(command)); - result.AddRange(GetOptions(model, command)); + result.AddRange(GetOptions(model, command, writeOptionsDefaultValues)); result.AddRange(GetCommands(model, container, isDefaultCommand)); return result; @@ -266,7 +269,7 @@ private static IEnumerable GetArguments(CommandInfo? command) return result; } - private static IEnumerable GetOptions(CommandModel model, CommandInfo? command) + private static IEnumerable GetOptions(CommandModel model, CommandInfo? command, bool writeDefaultValues) { // Collect all options into a single structure. var parameters = HelpOption.Get(model, command); @@ -282,8 +285,16 @@ private static IEnumerable GetOptions(CommandModel model, CommandIn new Markup(Environment.NewLine), }; + var helpOptions = parameters.ToArray(); + var defaultValueColumn = writeDefaultValues && helpOptions.Any(e => e.DefaultValue != null); + var grid = new Grid(); grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true }); + if (defaultValueColumn) + { + grid.AddColumn(new GridColumn { Padding = new Padding(0, 0, 4, 0) }); + } + grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) }); static string GetOptionParts(HelpOption option) @@ -327,11 +338,22 @@ static string GetOptionParts(HelpOption option) return builder.ToString(); } - foreach (var option in parameters.ToArray()) + if (defaultValueColumn) { - grid.AddRow( - GetOptionParts(option), - option.Description?.TrimEnd('.') ?? " "); + grid.AddRow(" ", "[lime]DEFAULT[/]", " "); + } + + foreach (var option in helpOptions) + { + var columns = new List { GetOptionParts(option) }; + if (defaultValueColumn) + { + columns.Add(option.DefaultValue == null ? " " : $"[bold]{option.DefaultValue.ToString().EscapeMarkup()}[/]"); + } + + columns.Add(option.Description?.TrimEnd('.') ?? " "); + + grid.AddRow(columns.ToArray()); } result.Add(grid); @@ -373,19 +395,19 @@ private static IEnumerable GetCommands( { arguments.Style("silver", $"<{argument.Name.EscapeMarkup()}>"); arguments.Space(); - } + } - if (model.TrimTrailingPeriod) - { + if (model.TrimTrailingPeriod) + { grid.AddRow( arguments.ToString().TrimEnd(), child.Description?.TrimEnd('.') ?? " "); - } - else - { - grid.AddRow( - arguments.ToString().TrimEnd(), - child.Description ?? " "); + } + else + { + grid.AddRow( + arguments.ToString().TrimEnd(), + child.Description ?? " "); } } diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Help/Command.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Help/Command.Output.verified.txt index 0cf3f19ca..189faef26 100644 --- a/test/Spectre.Console.Cli.Tests/Expectations/Help/Command.Output.verified.txt +++ b/test/Spectre.Console.Cli.Tests/Expectations/Help/Command.Output.verified.txt @@ -8,10 +8,11 @@ ARGUMENTS: [LEGS] The number of legs OPTIONS: - -h, --help Prints help information - -a, --alive Indicates whether or not the animal is alive + DEFAULT + -h, --help Prints help information + -a, --alive Indicates whether or not the animal is alive -n, --name - --agility The agility between 0 and 100 + --agility 10 The agility between 0 and 100 COMMANDS: lion The lion command \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Help/Command_Hide_Default.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Help/Command_Hide_Default.Output.verified.txt new file mode 100644 index 000000000..0cf3f19ca --- /dev/null +++ b/test/Spectre.Console.Cli.Tests/Expectations/Help/Command_Hide_Default.Output.verified.txt @@ -0,0 +1,17 @@ +DESCRIPTION: +Contains settings for a cat. + +USAGE: + myapp cat [LEGS] [OPTIONS] + +ARGUMENTS: + [LEGS] The number of legs + +OPTIONS: + -h, --help Prints help information + -a, --alive Indicates whether or not the animal is alive + -n, --name + --agility The agility between 0 and 100 + +COMMANDS: + lion The lion command \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Help/Default.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Help/Default.Output.verified.txt index 27df83873..7c2731bba 100644 --- a/test/Spectre.Console.Cli.Tests/Expectations/Help/Default.Output.verified.txt +++ b/test/Spectre.Console.Cli.Tests/Expectations/Help/Default.Output.verified.txt @@ -9,8 +9,9 @@ ARGUMENTS: [LEGS] The number of legs OPTIONS: - -h, --help Prints help information - -a, --alive Indicates whether or not the animal is alive + DEFAULT + -h, --help Prints help information + -a, --alive Indicates whether or not the animal is alive -n, --name - --agility The agility between 0 and 100 - -c The number of children the lion has \ No newline at end of file + --agility 10 The agility between 0 and 100 + -c The number of children the lion has \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Help/DefaultExamples.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Help/DefaultExamples.Output.verified.txt index 81c19395a..8fc25ef9e 100644 --- a/test/Spectre.Console.Cli.Tests/Expectations/Help/DefaultExamples.Output.verified.txt +++ b/test/Spectre.Console.Cli.Tests/Expectations/Help/DefaultExamples.Output.verified.txt @@ -12,8 +12,9 @@ ARGUMENTS: [LEGS] The number of legs OPTIONS: - -h, --help Prints help information - -a, --alive Indicates whether or not the animal is alive + DEFAULT + -h, --help Prints help information + -a, --alive Indicates whether or not the animal is alive -n, --name - --agility The agility between 0 and 100 - -c The number of children the lion has \ No newline at end of file + --agility 10 The agility between 0 and 100 + -c The number of children the lion has \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args.Output.verified.txt index 1794d6628..7c2731bba 100644 --- a/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args.Output.verified.txt +++ b/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args.Output.verified.txt @@ -1,16 +1,17 @@ -DESCRIPTION: -The lion command. - -USAGE: - myapp [LEGS] [OPTIONS] - -ARGUMENTS: - The number of teeth the lion has - [LEGS] The number of legs - -OPTIONS: - -h, --help Prints help information - -a, --alive Indicates whether or not the animal is alive - -n, --name - --agility The agility between 0 and 100 - -c The number of children the lion has \ No newline at end of file +DESCRIPTION: +The lion command. + +USAGE: + myapp [LEGS] [OPTIONS] + +ARGUMENTS: + The number of teeth the lion has + [LEGS] The number of legs + +OPTIONS: + DEFAULT + -h, --help Prints help information + -a, --alive Indicates whether or not the animal is alive + -n, --name + --agility 10 The agility between 0 and 100 + -c The number of children the lion has \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args_Additional.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args_Additional.Output.verified.txt index 36cb2f6ab..2c38182a5 100644 --- a/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args_Additional.Output.verified.txt +++ b/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args_Additional.Output.verified.txt @@ -1,4 +1,4 @@ -DESCRIPTION: +DESCRIPTION: The lion command. USAGE: @@ -9,11 +9,12 @@ ARGUMENTS: [LEGS] The number of legs OPTIONS: - -h, --help Prints help information - -a, --alive Indicates whether or not the animal is alive + DEFAULT + -h, --help Prints help information + -a, --alive Indicates whether or not the animal is alive -n, --name - --agility The agility between 0 and 100 - -c The number of children the lion has + --agility 10 The agility between 0 and 100 + -c The number of children the lion has COMMANDS: giraffe The giraffe command \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Help.cs b/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Help.cs index 5def4e409..24fb98426 100644 --- a/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Help.cs +++ b/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Help.cs @@ -99,6 +99,30 @@ public Task Should_Output_Command_Correctly() return Verifier.Verify(result.Output); } + [Fact] + [Expectation("Command_Hide_Default")] + public Task Should_Not_Print_Default_Column() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddBranch("cat", animal => + { + animal.SetDescription("Contains settings for a cat."); + animal.AddCommand("lion"); + }); + configurator.HideOptionDefaultValues(); + }); + + // When + var result = fixture.Run("cat", "--help"); + + // Then + return Verifier.Verify(result.Output); + } + [Fact] [Expectation("Leaf")] public Task Should_Output_Leaf_Correctly()