From 2e5d18fa78151179f7dd73abaa6ec9b72d1d8948 Mon Sep 17 00:00:00 2001 From: Tim Waalewijn <47832938+twaalewijn@users.noreply.github.com> Date: Mon, 29 Nov 2021 23:16:54 +0100 Subject: [PATCH] Added the ability to hide CommandOptions. (#642) CommandOptions now has an IsHidden property that, when set to true, will cause the option to be hidden from the following cases: - Help text using `-h|--help` - Xml representations generated with the `cli xml` command - Diagnostics displayed with the `cli explain` command Hidden options can still be outputted with `cli explain` using the `--hidden` option that is also used to display hidden commands. Fixes #631 --- docs/input/cli/settings.md | 9 +++++++++ .../Cli/Annotations/CommandOptionAttribute.cs | 5 +++++ .../Cli/Internal/Commands/ExplainCommand.cs | 11 +++++++--- .../Cli/Internal/Commands/XmlDocCommand.cs | 1 + .../Cli/Internal/HelpWriter.cs | 2 +- .../Cli/Internal/Modelling/CommandArgument.cs | 2 +- .../Cli/Internal/Modelling/CommandOption.cs | 2 +- .../Internal/Modelling/CommandParameter.cs | 4 +++- .../Data/Commands/HiddenOptionsCommand.cs | 12 +++++++++++ .../Data/Settings/HiddenOptionSettings.cs | 20 +++++++++++++++++++ ...Hidden_Command_Options.Output.verified.txt | 9 +++++++++ ...Hidden_Command_Options.Output.verified.txt | 14 +++++++++++++ .../CommandOptionAttributeTests.cs | 20 +++++++++++++++++++ .../Unit/Cli/CommandAppTests.Help.cs | 19 ++++++++++++++++++ .../Unit/Cli/CommandAppTests.Xml.cs | 15 ++++++++++++++ 15 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 test/Spectre.Console.Tests/Data/Commands/HiddenOptionsCommand.cs create mode 100644 test/Spectre.Console.Tests/Data/Settings/HiddenOptionSettings.cs create mode 100644 test/Spectre.Console.Tests/Expectations/Cli/Help/Hidden_Command_Options.Output.verified.txt create mode 100644 test/Spectre.Console.Tests/Expectations/Cli/Xml/Hidden_Command_Options.Output.verified.txt diff --git a/docs/input/cli/settings.md b/docs/input/cli/settings.md index 979e98a..3b001d4 100644 --- a/docs/input/cli/settings.md +++ b/docs/input/cli/settings.md @@ -53,6 +53,15 @@ There is a special mode for `CommandOptions` on boolean types. Typically all `Co public bool Debug { get; set; } ``` +### Hidden options + +`CommandOptions` can be hidden from being rendered in help by setting `IsHidden` to `true`. + +```csharp +[CommandOption("--hidden-opt", IsHidden = true)] +public bool HiddenOpt { get; set; } +``` + ## Description When rendering help the [`System.ComponentModel.Description`](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.descriptionattribute?view=net-5.0) attribute is supported for specifying the text displayed to the user for both `CommandOption` and `CommandArgument`. diff --git a/src/Spectre.Console/Cli/Annotations/CommandOptionAttribute.cs b/src/Spectre.Console/Cli/Annotations/CommandOptionAttribute.cs index 86a8537..e13c9aa 100644 --- a/src/Spectre.Console/Cli/Annotations/CommandOptionAttribute.cs +++ b/src/Spectre.Console/Cli/Annotations/CommandOptionAttribute.cs @@ -34,6 +34,11 @@ namespace Spectre.Console.Cli /// public bool ValueIsOptional { get; } + /// + /// Gets or sets a value indicating whether this option is hidden from the help text. + /// + public bool IsHidden { get; set; } + /// /// Initializes a new instance of the class. /// diff --git a/src/Spectre.Console/Cli/Internal/Commands/ExplainCommand.cs b/src/Spectre.Console/Cli/Internal/Commands/ExplainCommand.cs index 475d0ff..49e49ca 100644 --- a/src/Spectre.Console/Cli/Internal/Commands/ExplainCommand.cs +++ b/src/Spectre.Console/Cli/Internal/Commands/ExplainCommand.cs @@ -36,7 +36,7 @@ namespace Spectre.Console.Cli [CommandOption("-d|--detailed")] public bool? Detailed { get; } - [Description("Include hidden commands.")] + [Description("Include hidden commands and options.")] [CommandOption("--hidden")] public bool IncludeHidden { get; } } @@ -166,7 +166,7 @@ namespace Spectre.Console.Cli var parametersNode = commandNode.AddNode(ParentMarkup("Parameters")); foreach (var parameter in command.Parameters) { - AddParameter(parametersNode, parameter, detailed); + AddParameter(parametersNode, parameter, detailed, includeHidden); } } @@ -181,8 +181,13 @@ namespace Spectre.Console.Cli } } - private void AddParameter(TreeNode parametersNode, CommandParameter parameter, bool detailed) + private void AddParameter(TreeNode parametersNode, CommandParameter parameter, bool detailed, bool includeHidden) { + if (!includeHidden && parameter.IsHidden) + { + return; + } + if (!detailed) { parametersNode.AddNode( diff --git a/src/Spectre.Console/Cli/Internal/Commands/XmlDocCommand.cs b/src/Spectre.Console/Cli/Internal/Commands/XmlDocCommand.cs index 49645c6..f4e36c9 100644 --- a/src/Spectre.Console/Cli/Internal/Commands/XmlDocCommand.cs +++ b/src/Spectre.Console/Cli/Internal/Commands/XmlDocCommand.cs @@ -153,6 +153,7 @@ namespace Spectre.Console.Cli // Options foreach (var option in command.Parameters.OfType() + .Where(x => !x.IsHidden) .OrderBy(x => string.Join(",", x.LongNames)) .ThenBy(x => string.Join(",", x.ShortNames))) { diff --git a/src/Spectre.Console/Cli/Internal/HelpWriter.cs b/src/Spectre.Console/Cli/Internal/HelpWriter.cs index 927a1e8..fe38eaa 100644 --- a/src/Spectre.Console/Cli/Internal/HelpWriter.cs +++ b/src/Spectre.Console/Cli/Internal/HelpWriter.cs @@ -61,7 +61,7 @@ namespace Spectre.Console.Cli parameters.Add(new HelpOption("v", "version", null, null, "Prints version information")); } - parameters.AddRange(command?.Parameters?.OfType()?.Select(o => + 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)) diff --git a/src/Spectre.Console/Cli/Internal/Modelling/CommandArgument.cs b/src/Spectre.Console/Cli/Internal/Modelling/CommandArgument.cs index 455c95d..9d71d4c 100644 --- a/src/Spectre.Console/Cli/Internal/Modelling/CommandArgument.cs +++ b/src/Spectre.Console/Cli/Internal/Modelling/CommandArgument.cs @@ -16,7 +16,7 @@ namespace Spectre.Console.Cli CommandArgumentAttribute argument, ParameterValueProviderAttribute? valueProvider, IEnumerable validators) : base(parameterType, parameterKind, property, description, converter, defaultValue, - null, valueProvider, validators, argument.IsRequired) + null, valueProvider, validators, argument.IsRequired, false) { Value = argument.ValueName; Position = argument.Position; diff --git a/src/Spectre.Console/Cli/Internal/Modelling/CommandOption.cs b/src/Spectre.Console/Cli/Internal/Modelling/CommandOption.cs index 4da1f37..df0ef73 100644 --- a/src/Spectre.Console/Cli/Internal/Modelling/CommandOption.cs +++ b/src/Spectre.Console/Cli/Internal/Modelling/CommandOption.cs @@ -20,7 +20,7 @@ namespace Spectre.Console.Cli IEnumerable validators, DefaultValueAttribute? defaultValue, bool valueIsOptional) : base(parameterType, parameterKind, property, description, converter, - defaultValue, deconstructor, valueProvider, validators, false) + defaultValue, deconstructor, valueProvider, validators, false, optionAttribute.IsHidden) { LongNames = optionAttribute.LongNames; ShortNames = optionAttribute.ShortNames; diff --git a/src/Spectre.Console/Cli/Internal/Modelling/CommandParameter.cs b/src/Spectre.Console/Cli/Internal/Modelling/CommandParameter.cs index 8f62f8f..318b692 100644 --- a/src/Spectre.Console/Cli/Internal/Modelling/CommandParameter.cs +++ b/src/Spectre.Console/Cli/Internal/Modelling/CommandParameter.cs @@ -19,6 +19,7 @@ namespace Spectre.Console.Cli public List Validators { get; } public ParameterValueProviderAttribute? ValueProvider { get; } public bool Required { get; set; } + public bool IsHidden { get; } public string PropertyName => Property.Name; public virtual bool WantRawValue => ParameterType.IsPairDeconstructable() @@ -30,7 +31,7 @@ namespace Spectre.Console.Cli DefaultValueAttribute? defaultValue, PairDeconstructorAttribute? deconstructor, ParameterValueProviderAttribute? valueProvider, - IEnumerable validators, bool required) + IEnumerable validators, bool required, bool isHidden) { Id = Guid.NewGuid(); ParameterType = parameterType; @@ -43,6 +44,7 @@ namespace Spectre.Console.Cli ValueProvider = valueProvider; Validators = new List(validators ?? Array.Empty()); Required = required; + IsHidden = isHidden; } public bool IsFlagValue() diff --git a/test/Spectre.Console.Tests/Data/Commands/HiddenOptionsCommand.cs b/test/Spectre.Console.Tests/Data/Commands/HiddenOptionsCommand.cs new file mode 100644 index 0000000..4d8358b --- /dev/null +++ b/test/Spectre.Console.Tests/Data/Commands/HiddenOptionsCommand.cs @@ -0,0 +1,12 @@ +using Spectre.Console.Cli; + +namespace Spectre.Console.Tests.Data +{ + public sealed class HiddenOptionsCommand : Command + { + public override int Execute(CommandContext context, HiddenOptionSettings settings) + { + return 0; + } + } +} diff --git a/test/Spectre.Console.Tests/Data/Settings/HiddenOptionSettings.cs b/test/Spectre.Console.Tests/Data/Settings/HiddenOptionSettings.cs new file mode 100644 index 0000000..d9586b8 --- /dev/null +++ b/test/Spectre.Console.Tests/Data/Settings/HiddenOptionSettings.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; +using Spectre.Console.Cli; + +namespace Spectre.Console.Tests.Data +{ + public sealed class HiddenOptionSettings : CommandSettings + { + [CommandArgument(0, "")] + [Description("Dummy argument FOO")] + public int Foo { get; set; } + + [CommandOption("--bar", IsHidden = true)] + [Description("You should not be able to read this unless you used the 'cli explain' command with the '--hidden' option")] + public int Bar { get; set; } + + [CommandOption("--baz")] + [Description("Dummy option BAZ")] + public int Baz { get; set; } + } +} diff --git a/test/Spectre.Console.Tests/Expectations/Cli/Help/Hidden_Command_Options.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Cli/Help/Hidden_Command_Options.Output.verified.txt new file mode 100644 index 0000000..fe241ad --- /dev/null +++ b/test/Spectre.Console.Tests/Expectations/Cli/Help/Hidden_Command_Options.Output.verified.txt @@ -0,0 +1,9 @@ +USAGE: + myapp [OPTIONS] + +ARGUMENTS: + Dummy argument FOO + +OPTIONS: + -h, --help Prints help information + --baz Dummy option BAZ \ No newline at end of file diff --git a/test/Spectre.Console.Tests/Expectations/Cli/Xml/Hidden_Command_Options.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Cli/Xml/Hidden_Command_Options.Output.verified.txt new file mode 100644 index 0000000..d2f2d0b --- /dev/null +++ b/test/Spectre.Console.Tests/Expectations/Cli/Xml/Hidden_Command_Options.Output.verified.txt @@ -0,0 +1,14 @@ + + + + + + + Dummy argument FOO + + + + + \ No newline at end of file diff --git a/test/Spectre.Console.Tests/Unit/Cli/Annotations/CommandOptionAttributeTests.cs b/test/Spectre.Console.Tests/Unit/Cli/Annotations/CommandOptionAttributeTests.cs index d9efb57..23b9ce3 100644 --- a/test/Spectre.Console.Tests/Unit/Cli/Annotations/CommandOptionAttributeTests.cs +++ b/test/Spectre.Console.Tests/Unit/Cli/Annotations/CommandOptionAttributeTests.cs @@ -193,5 +193,25 @@ namespace Spectre.Console.Tests.Unit.Cli.Annotations result.ShortNames.ShouldContain("f"); result.ValueName.ShouldBe("BAR"); } + + [Fact] + public void Is_Not_Hidden_From_Help_By_Default() + { + // Given, When + var result = new CommandOptionAttribute("--foo"); + + // Then + result.IsHidden.ShouldBeFalse(); + } + + [Fact] + public void Can_Indicate_That_It_Must_Be_Hidden_From_Help_Text() + { + // Given, When + var result = new CommandOptionAttribute("--foo") { IsHidden = true }; + + // Then + result.IsHidden.ShouldBeTrue(); + } } } diff --git a/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Help.cs b/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Help.cs index cc8bf6c..fec4d21 100644 --- a/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Help.cs +++ b/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Help.cs @@ -278,6 +278,25 @@ namespace Spectre.Console.Tests.Unit.Cli // Then return Verifier.Verify(result.Output); } + + [Fact] + [Expectation("Hidden_Command_Options")] + public Task Should_Not_Show_Hidden_Command_Options() + { + // Given + var fixture = new CommandAppTester(); + fixture.SetDefaultCommand>(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } } } } diff --git a/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Xml.cs b/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Xml.cs index bb5ca24..1df3ab2 100644 --- a/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Xml.cs +++ b/test/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Xml.cs @@ -137,6 +137,21 @@ namespace Spectre.Console.Tests.Unit.Cli // Then return Verifier.Verify(result.Output); } + + [Fact] + [Expectation("Hidden_Command_Options")] + public Task Should_Not_Dump_Hidden_Options_On_A_Command() + { + // Given + var fixture = new CommandAppTester(); + fixture.SetDefaultCommand(); + + // When + var result = fixture.Run(Constants.XmlDocCommand); + + // Then + return Verifier.Verify(result.Output); + } } } }