Show valid values of an enum option in help (#53)

This commit is contained in:
Domn Werner
2020-05-08 02:40:23 -07:00
committed by GitHub
parent cbb72b16ae
commit 33ca4da260
4 changed files with 94 additions and 4 deletions

View File

@@ -82,6 +82,20 @@ namespace CliFx.Tests
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
[Command("cmd-with-enum-opts")]
private class EnumOptionsCommand : ICommand
{
public enum ValuesEnum { Value1, Value2, Value3 };
[CommandOption("value", Description = "Enum option.", IsRequired = true)]
public ValuesEnum Value { get; set; } = ValuesEnum.Value1;
[CommandOption("nullable-value", Description = "Nullable enum option.")]
public ValuesEnum? NullableValue { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
[Command("cmd-with-env-vars")] [Command("cmd-with-env-vars")]
private class EnvironmentVariableCommand : ICommand private class EnvironmentVariableCommand : ICommand
{ {

View File

@@ -238,6 +238,34 @@ namespace CliFx.Tests
_output.WriteLine(stdOutData); _output.WriteLine(stdOutData);
} }
[Fact]
public async Task Help_text_shows_usage_format_which_lists_all_valid_values_for_enum_options()
{
// Arrange
await using var stdOut = new MemoryStream();
var console = new VirtualConsole(output: stdOut);
var application = new CliApplicationBuilder()
.AddCommand(typeof(EnumOptionsCommand))
.UseConsole(console)
.Build();
// Act
await application.RunAsync(new[] { "cmd-with-enum-opts", "--help" });
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
// Assert
stdOutData.Should().ContainAll(
"Usage",
"cmd-with-enum-opts", "[options]",
"Options",
"* --value", "Enum option.", "Valid values: Value1, Value2, Value3.",
"--nullable-value", "Nullable enum option.", "Valid values: Value1, Value2, Value3."
);
_output.WriteLine(stdOutData);
}
[Fact] [Fact]
public async Task Help_text_lists_environment_variable_names_for_options_that_have_them_defined() public async Task Help_text_lists_environment_variable_names_for_options_that_have_them_defined()
{ {

View File

@@ -10,6 +10,7 @@ namespace CliFx.Domain
{ {
internal abstract partial class CommandArgumentSchema internal abstract partial class CommandArgumentSchema
{ {
private IReadOnlyList<string>? _validValues;
public PropertyInfo Property { get; } public PropertyInfo Property { get; }
public string? Description { get; } public string? Description { get; }
@@ -18,12 +19,52 @@ namespace CliFx.Domain
public bool IsScalar => TryGetEnumerableArgumentUnderlyingType() == null; public bool IsScalar => TryGetEnumerableArgumentUnderlyingType() == null;
public IReadOnlyList<string> GetValidValues() => _validValues ??
(_validValues = EnumerateValidValues().ToList().AsReadOnly());
protected CommandArgumentSchema(PropertyInfo property, string? description) protected CommandArgumentSchema(PropertyInfo property, string? description)
{ {
Property = property; Property = property;
Description = description; Description = description;
} }
private IEnumerable<string> EnumerateValidValues()
{
var propertyType = Property?.PropertyType;
// Property can actually be null here due to damn it operators
// in CommandOptionSchema lines 103 and 106, so we have to check
// for now. In such case that it is null, let's end early.
if (propertyType is null)
{
yield break;
}
// If 'propertyType' is nullable, this will return a non-null value.
var underlyingType = propertyType.GetNullableUnderlyingType();
// If 'propertyType' is nullable, 'underlying' type will be not null.
if (underlyingType is object)
{
// Handle nullable num.
if (underlyingType.IsEnum)
{
// Reasign so we can do the 'foreach' over the enum values
// only once at the end of the method.
propertyType = underlyingType;
}
}
// Handle non-nullable enums or nullable enums that were "unwrapped".
if (propertyType.IsEnum)
{
foreach (var value in Enum.GetValues(propertyType))
{
yield return value.ToString();
}
}
}
private Type? TryGetEnumerableArgumentUnderlyingType() => private Type? TryGetEnumerableArgumentUnderlyingType() =>
Property.PropertyType != typeof(string) Property.PropertyType != typeof(string)
? Property.PropertyType.GetEnumerableUnderlyingType() ? Property.PropertyType.GetEnumerableUnderlyingType()

View File

@@ -246,18 +246,25 @@ namespace CliFx.Domain
RenderColumnIndent(); RenderColumnIndent();
// Description
if (!string.IsNullOrWhiteSpace(option.Description)) if (!string.IsNullOrWhiteSpace(option.Description))
{ {
Render(option.Description); Render(option.Description);
Render(" "); Render(" ");
} }
var validValues = option.GetValidValues();
if (validValues.Any())
{
Render($"Valid values: {string.Join(", ", validValues)}.");
Render(" ");
}
// TODO: Render default value here.
// Environment variable // Environment variable
if (!string.IsNullOrWhiteSpace(option.EnvironmentVariableName)) if (!string.IsNullOrWhiteSpace(option.EnvironmentVariableName))
{ {
Render($"(Environment variable: {option.EnvironmentVariableName})."); Render($"(Environment variable: {option.EnvironmentVariableName})");
Render(" ");
} }
RenderNewLine(); RenderNewLine();