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);
+ }
}
}
}