Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 49 additions & 32 deletions src/Spectre.Console.Cli/Internal/Binding/CommandValueResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,28 +78,16 @@ public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeReso
}
else
{
var (converter, stringConstructor) = GetConverter(lookup, binder, resolver, mapped.Parameter);
if (converter == null)
{
throw CommandRuntimeException.NoConverterFound(mapped.Parameter);
}

object? value;
var converter = GetConverter(lookup, binder, resolver, mapped.Parameter);
var mappedValue = mapped.Value ?? string.Empty;
try
{
try
{
value = converter.ConvertFromInvariantString(mappedValue);
}
catch (NotSupportedException) when (stringConstructor != null)
{
value = stringConstructor.Invoke(new object[] { mappedValue });
}
value = converter.ConvertFrom(mappedValue);
}
catch (Exception exception) when (exception is not CommandRuntimeException)
{
throw CommandRuntimeException.ConversionFailed(mapped, converter, exception);
throw CommandRuntimeException.ConversionFailed(mapped, converter.TypeConverter, exception);
}

// Assign the value to the parameter.
Expand Down Expand Up @@ -130,17 +118,14 @@ public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeReso
{
if (result != null && result.GetType() != parameter.ParameterType)
{
var (converter, _) = GetConverter(lookup, binder, resolver, parameter);
if (converter != null)
{
result = result is Array array ? ConvertArray(array, converter) : converter.ConvertFrom(result);
}
var converter = GetConverter(lookup, binder, resolver, parameter);
result = result is Array array ? ConvertArray(array, converter) : converter.ConvertFrom(result);
}

return result;
}

private static Array ConvertArray(Array sourceArray, TypeConverter converter)
private static Array ConvertArray(Array sourceArray, SmartConverter converter)
{
Array? targetArray = null;
for (var i = 0; i < sourceArray.Length; i++)
Expand All @@ -161,14 +146,8 @@ private static Array ConvertArray(Array sourceArray, TypeConverter converter)
}

[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 SmartConverter GetConverter(CommandValueLookup lookup, CommandValueBinder binder, ITypeResolver resolver, CommandParameter parameter)
{
static ConstructorInfo? GetStringConstructor(Type type)
{
var constructor = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string) }, null);
return constructor?.GetParameters()[0].ParameterType == typeof(string) ? constructor : null;
}

if (parameter.Converter == null)
{
if (parameter.ParameterType.IsArray)
Expand All @@ -180,7 +159,7 @@ private static (TypeConverter? Converter, ConstructorInfo? StringConstructor) Ge
throw new InvalidOperationException("Could not get element type");
}

return (TypeDescriptor.GetConverter(elementType), GetStringConstructor(elementType));
return new SmartConverter(TypeDescriptor.GetConverter(elementType), elementType);
}

if (parameter.IsFlagValue())
Expand All @@ -200,13 +179,51 @@ private static (TypeConverter? Converter, ConstructorInfo? StringConstructor) Ge
}

// Return a converter for the flag element type.
return (TypeDescriptor.GetConverter(value.Type), GetStringConstructor(value.Type));
return new SmartConverter(TypeDescriptor.GetConverter(value.Type), value.Type);
}

return (TypeDescriptor.GetConverter(parameter.ParameterType), GetStringConstructor(parameter.ParameterType));
return new SmartConverter(TypeDescriptor.GetConverter(parameter.ParameterType), parameter.ParameterType);
}

var type = Type.GetType(parameter.Converter.ConverterTypeName);
return (resolver.Resolve(type) as TypeConverter, null);
if (type == null || resolver.Resolve(type) is not TypeConverter typeConverter)
{
throw CommandRuntimeException.NoConverterFound(parameter);
}

return new SmartConverter(typeConverter, type);
}

/// <summary>
/// Convert inputs using the given <see cref="TypeConverter"/> and fallback to finding a constructor taking a single argument of the input type.
/// </summary>
private readonly ref struct SmartConverter
{
public SmartConverter(TypeConverter typeConverter, Type type)
{
TypeConverter = typeConverter;
Type = type;
}

public TypeConverter TypeConverter { get; }
private Type Type { get; }

public object? ConvertFrom(object input)
{
try
{
return TypeConverter.ConvertFrom(null, CultureInfo.InvariantCulture, input);
}
catch (NotSupportedException)
{
var constructor = Type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new[] { input.GetType() }, null);
if (constructor == null)
{
throw;
}

return constructor.Invoke(new[] { input });
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class HorseSettings : MammalSettings
public DayOfWeek Day { get; set; }

[CommandOption("--file")]
[DefaultValue("food.txt")]
public FileInfo File { get; set; }

[CommandOption("--directory")]
Expand Down
1 change: 1 addition & 0 deletions test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,7 @@ public void Should_Run_The_Named_Command_Not_The_Default_Command()
{
horse.Legs.ShouldBe(4);
horse.Name.ShouldBe("Arkle");
horse.File.Name.ShouldBe("food.txt");
});
}

Expand Down