diff --git a/src/Spectre.Console.Cli/Internal/Binding/CommandValueResolver.cs b/src/Spectre.Console.Cli/Internal/Binding/CommandValueResolver.cs index e27702867..3b534199e 100644 --- a/src/Spectre.Console.Cli/Internal/Binding/CommandValueResolver.cs +++ b/src/Spectre.Console.Cli/Internal/Binding/CommandValueResolver.cs @@ -78,18 +78,26 @@ public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeReso } else { - var converter = GetConverter(lookup, binder, resolver, mapped.Parameter); + var (converter, stringConstructor) = GetConverter(lookup, binder, resolver, mapped.Parameter); if (converter == null) { throw CommandRuntimeException.NoConverterFound(mapped.Parameter); } object? value; + var mappedValue = mapped.Value ?? string.Empty; try { - value = converter.ConvertFromInvariantString(mapped.Value ?? string.Empty); + try + { + value = converter.ConvertFromInvariantString(mappedValue); + } + catch (NotSupportedException) when (stringConstructor != null) + { + value = stringConstructor.Invoke(new object[] { mappedValue }); + } } - catch (Exception exception) + catch (Exception exception) when (exception is not CommandRuntimeException) { throw CommandRuntimeException.ConversionFailed(mapped, converter, exception); } @@ -122,7 +130,7 @@ public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeReso { if (result != null && result.GetType() != parameter.ParameterType) { - var converter = GetConverter(lookup, binder, resolver, parameter); + var (converter, _) = GetConverter(lookup, binder, resolver, parameter); if (converter != null) { result = converter.ConvertFrom(result); @@ -133,8 +141,14 @@ public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeReso } [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "It's OK")] - private static TypeConverter? GetConverter(CommandValueLookup lookup, CommandValueBinder binder, ITypeResolver resolver, CommandParameter parameter) + private static (TypeConverter? Converter, ConstructorInfo? StringConstructor) 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) @@ -146,12 +160,12 @@ public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeReso throw new InvalidOperationException("Could not get element type"); } - return TypeDescriptor.GetConverter(elementType); + return (TypeDescriptor.GetConverter(elementType), GetStringConstructor(elementType)); } if (parameter.IsFlagValue()) { - // Is the optional value instanciated? + // Is the optional value instantiated? var value = lookup.GetValue(parameter) as IFlagValue; if (value == null) { @@ -161,18 +175,18 @@ public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeReso value = lookup.GetValue(parameter) as IFlagValue; if (value == null) { - throw new InvalidOperationException("Could not intialize optional value."); + throw new InvalidOperationException("Could not initialize optional value."); } } // Return a converter for the flag element type. - return TypeDescriptor.GetConverter(value.Type); + return (TypeDescriptor.GetConverter(value.Type), GetStringConstructor(value.Type)); } - return TypeDescriptor.GetConverter(parameter.ParameterType); + return (TypeDescriptor.GetConverter(parameter.ParameterType), GetStringConstructor(parameter.ParameterType)); } var type = Type.GetType(parameter.Converter.ConverterTypeName); - return resolver.Resolve(type) as TypeConverter; + return (resolver.Resolve(type) as TypeConverter, null); } } \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Data/Settings/HorseSettings.cs b/test/Spectre.Console.Cli.Tests/Data/Settings/HorseSettings.cs index 7231b5ebd..7a8fa77ec 100644 --- a/test/Spectre.Console.Cli.Tests/Data/Settings/HorseSettings.cs +++ b/test/Spectre.Console.Cli.Tests/Data/Settings/HorseSettings.cs @@ -1,7 +1,15 @@ +using System.IO; + namespace Spectre.Console.Tests.Data; public class HorseSettings : MammalSettings { [CommandOption("-d|--day")] public DayOfWeek Day { get; set; } + + [CommandOption("--file")] + public FileInfo File { get; set; } + + [CommandOption("--directory")] + public DirectoryInfo Directory { get; set; } } \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_1.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_1.Output.verified.txt index c161f0225..021426c55 100644 --- a/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_1.Output.verified.txt +++ b/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_1.Output.verified.txt @@ -30,6 +30,8 @@ diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_3.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_3.Output.verified.txt index cf55628ad..d59c67eb6 100644 --- a/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_3.Output.verified.txt +++ b/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_3.Output.verified.txt @@ -26,6 +26,8 @@ diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_6.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_6.Output.verified.txt index c0a5c45ce..bc1611801 100644 --- a/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_6.Output.verified.txt +++ b/test/Spectre.Console.Cli.Tests/Expectations/Xml/Test_6.Output.verified.txt @@ -32,6 +32,8 @@ Indicates whether or not the animal is alive.