Add support for arrays in [DefaultValue] attributes (#1164)

Fixes #1163
This commit is contained in:
Cédric Luthi 2023-05-11 15:26:53 +02:00 committed by GitHub
parent 6acf9b8c63
commit dac2097321
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 125 additions and 20 deletions

View File

@ -65,6 +65,11 @@ internal sealed class CommandValueBinder
private object GetArray(CommandParameter parameter, object? value) private object GetArray(CommandParameter parameter, object? value)
{ {
if (value is Array)
{
return value;
}
// Add a new item to the array // Add a new item to the array
var array = (Array?)_lookup.GetValue(parameter); var array = (Array?)_lookup.GetValue(parameter);
Array newArray; Array newArray;

View File

@ -133,13 +133,33 @@ internal static class CommandValueResolver
var (converter, _) = GetConverter(lookup, binder, resolver, parameter); var (converter, _) = GetConverter(lookup, binder, resolver, parameter);
if (converter != null) if (converter != null)
{ {
result = converter.ConvertFrom(result); result = result is Array array ? ConvertArray(array, converter) : converter.ConvertFrom(result);
} }
} }
return result; return result;
} }
private static Array ConvertArray(Array sourceArray, TypeConverter converter)
{
Array? targetArray = null;
for (var i = 0; i < sourceArray.Length; i++)
{
var item = sourceArray.GetValue(i);
if (item != null)
{
var converted = converter.ConvertFrom(item);
if (converted != null)
{
targetArray ??= Array.CreateInstance(converted.GetType(), sourceArray.Length);
targetArray.SetValue(converted, i);
}
}
}
return targetArray ?? sourceArray;
}
[SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "It's OK")] [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "It's OK")]
private static (TypeConverter? Converter, ConstructorInfo? StringConstructor) GetConverter(CommandValueLookup lookup, CommandValueBinder binder, ITypeResolver resolver, CommandParameter parameter) private static (TypeConverter? Converter, ConstructorInfo? StringConstructor) GetConverter(CommandValueLookup lookup, CommandValueBinder binder, ITypeResolver resolver, CommandParameter parameter)
{ {

View File

@ -348,7 +348,17 @@ internal static class HelpWriter
var columns = new List<string> { GetOptionParts(option) }; var columns = new List<string> { GetOptionParts(option) };
if (defaultValueColumn) if (defaultValueColumn)
{ {
columns.Add(option.DefaultValue == null ? " " : $"[bold]{option.DefaultValue.ToString().EscapeMarkup()}[/]"); static string Bold(object obj) => $"[bold]{obj.ToString().EscapeMarkup()}[/]";
var defaultValue = option.DefaultValue switch
{
null => " ",
"" => " ",
Array { Length: 0 } => " ",
Array array => string.Join(", ", array.Cast<object>().Select(Bold)),
_ => Bold(option.DefaultValue),
};
columns.Add(defaultValue);
} }
columns.Add(option.Description?.TrimEnd('.') ?? " "); columns.Add(option.Description?.TrimEnd('.') ?? " ");

View File

@ -9,4 +9,9 @@ public class LionSettings : CatSettings
[CommandOption("-c <CHILDREN>")] [CommandOption("-c <CHILDREN>")]
[Description("The number of children the lion has.")] [Description("The number of children the lion has.")]
public int Children { get; set; } public int Children { get; set; }
[CommandOption("-d <DAY>")]
[Description("The days the lion goes hunting.")]
[DefaultValue(new[] { DayOfWeek.Monday, DayOfWeek.Thursday })]
public DayOfWeek[] HuntDays { get; set; }
} }

View File

@ -33,3 +33,18 @@ public sealed class RequiredArgumentWithDefaultValueSettings : CommandSettings
[DefaultValue("Hello World")] [DefaultValue("Hello World")]
public string Greeting { get; set; } public string Greeting { get; set; }
} }
public sealed class OptionWithArrayOfEnumDefaultValueSettings : CommandSettings
{
[CommandOption("--days")]
[DefaultValue(new[] { DayOfWeek.Sunday, DayOfWeek.Saturday })]
public DayOfWeek[] Days { get; set; }
}
public sealed class OptionWithArrayOfStringDefaultValueAndTypeConverterSettings : CommandSettings
{
[CommandOption("--numbers")]
[DefaultValue(new[] { "2", "3" })]
[TypeConverter(typeof(StringToIntegerConverter))]
public int[] Numbers { get; set; }
}

View File

@ -10,8 +10,9 @@ ARGUMENTS:
OPTIONS: OPTIONS:
DEFAULT DEFAULT
-h, --help Prints help information -h, --help Prints help information
-a, --alive Indicates whether or not the animal is alive -a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE> -n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100 --agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has -c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting

View File

@ -13,8 +13,9 @@ ARGUMENTS:
OPTIONS: OPTIONS:
DEFAULT DEFAULT
-h, --help Prints help information -h, --help Prints help information
-a, --alive Indicates whether or not the animal is alive -a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE> -n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100 --agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has -c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting

View File

@ -10,8 +10,9 @@ ARGUMENTS:
OPTIONS: OPTIONS:
DEFAULT DEFAULT
-h, --help Prints help information -h, --help Prints help information
-a, --alive Indicates whether or not the animal is alive -a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE> -n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100 --agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has -c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting

View File

@ -10,11 +10,12 @@ ARGUMENTS:
OPTIONS: OPTIONS:
DEFAULT DEFAULT
-h, --help Prints help information -h, --help Prints help information
-a, --alive Indicates whether or not the animal is alive -a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE> -n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100 --agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has -c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting
COMMANDS: COMMANDS:
giraffe <LENGTH> The giraffe command giraffe <LENGTH> The giraffe command

View File

@ -8,5 +8,7 @@ ARGUMENTS:
<TEETH> The number of teeth the lion has <TEETH> The number of teeth the lion has
OPTIONS: OPTIONS:
-h, --help Prints help information DEFAULT
-c <CHILDREN> The number of children the lion has -h, --help Prints help information
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting

View File

@ -396,6 +396,50 @@ public sealed partial class CommandAppTests
}); });
} }
[Fact]
public void Should_Assign_Array_Default_Value_To_Command_Option()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<OptionWithArrayOfEnumDefaultValueSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(Array.Empty<string>());
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<OptionWithArrayOfEnumDefaultValueSettings>().And(settings =>
{
settings.Days.ShouldBe(new[] { DayOfWeek.Sunday, DayOfWeek.Saturday });
});
}
[Fact]
public void Should_Assign_Array_Default_Value_To_Command_Option_Using_Converter_If_Necessary()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<OptionWithArrayOfStringDefaultValueAndTypeConverterSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(Array.Empty<string>());
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<OptionWithArrayOfStringDefaultValueAndTypeConverterSettings>().And(settings =>
{
settings.Numbers.ShouldBe(new[] { 2, 3 });
});
}
[Fact] [Fact]
public void Should_Throw_If_Required_Argument_Have_Default_Value() public void Should_Throw_If_Required_Argument_Have_Default_Value()
{ {