This commit is contained in:
Tyrrrz
2020-10-23 21:40:53 +03:00
parent 14ad9d5738
commit 7f2202e869
5 changed files with 36 additions and 24 deletions

View File

@@ -28,7 +28,11 @@
/// <summary> /// <summary>
/// Initializes an instance of <see cref="ApplicationMetadata"/>. /// Initializes an instance of <see cref="ApplicationMetadata"/>.
/// </summary> /// </summary>
public ApplicationMetadata(string title, string executableName, string versionText, string? description) public ApplicationMetadata(
string title,
string executableName,
string versionText,
string? description)
{ {
Title = title; Title = title;
ExecutableName = executableName; ExecutableName = executableName;

View File

@@ -28,44 +28,52 @@ namespace CliFx.Domain
private Type? TryGetEnumerableArgumentUnderlyingType() => private Type? TryGetEnumerableArgumentUnderlyingType() =>
Property != null && Property.PropertyType != typeof(string) Property != null && Property.PropertyType != typeof(string)
? Property.PropertyType.GetEnumerableUnderlyingType() ? Property.PropertyType.TryGetEnumerableUnderlyingType()
: null; : null;
private object? ConvertScalar(string? value, Type targetType) private object? ConvertScalar(string? value, Type targetType)
{ {
try try
{ {
// Custom conversion // Custom conversion (always takes highest priority)
if (ConverterType != null) if (ConverterType != null)
return ConverterType.CreateInstance<IArgumentValueConverter>().ConvertFrom(value!); return ConverterType.CreateInstance<IArgumentValueConverter>().ConvertFrom(value!);
// Primitive // No conversion necessary
if (targetType == typeof(object) || targetType == typeof(string))
return value;
// Bool conversion (special case)
if (targetType == typeof(bool))
return string.IsNullOrWhiteSpace(value) || bool.Parse(value);
// Primitive conversion
var primitiveConverter = PrimitiveConverters.GetValueOrDefault(targetType); var primitiveConverter = PrimitiveConverters.GetValueOrDefault(targetType);
if (primitiveConverter != null) if (primitiveConverter != null && !string.IsNullOrWhiteSpace(value))
return primitiveConverter(value); return primitiveConverter(value);
// Enum // Enum conversion
if (targetType.IsEnum) if (targetType.IsEnum && !string.IsNullOrWhiteSpace(value))
return Enum.Parse(targetType, value, true); return Enum.Parse(targetType, value, true);
// Nullable // Nullable<T> conversion
var nullableUnderlyingType = targetType.GetNullableUnderlyingType(); var nullableUnderlyingType = targetType.TryGetNullableUnderlyingType();
if (nullableUnderlyingType != null) if (nullableUnderlyingType != null)
return !string.IsNullOrWhiteSpace(value) return !string.IsNullOrWhiteSpace(value)
? ConvertScalar(value, nullableUnderlyingType) ? ConvertScalar(value, nullableUnderlyingType)
: null; : null;
// String-constructible // String-constructible conversion
var stringConstructor = targetType.GetConstructor(new[] {typeof(string)}); var stringConstructor = targetType.GetConstructor(new[] {typeof(string)});
if (stringConstructor != null) if (stringConstructor != null)
return stringConstructor.Invoke(new object[] {value!}); return stringConstructor.Invoke(new object[] {value!});
// String-parseable (with format provider) // String-parseable conversion (with format provider)
var parseMethodWithFormatProvider = targetType.TryGetStaticParseMethod(true); var parseMethodWithFormatProvider = targetType.TryGetStaticParseMethod(true);
if (parseMethodWithFormatProvider != null) if (parseMethodWithFormatProvider != null)
return parseMethodWithFormatProvider.Invoke(null, new object[] {value!, FormatProvider}); return parseMethodWithFormatProvider.Invoke(null, new object[] {value!, FormatProvider});
// String-parseable (without format provider) // String-parseable conversion (without format provider)
var parseMethod = targetType.TryGetStaticParseMethod(); var parseMethod = targetType.TryGetStaticParseMethod();
if (parseMethod != null) if (parseMethod != null)
return parseMethod.Invoke(null, new object[] {value!}); return parseMethod.Invoke(null, new object[] {value!});
@@ -78,7 +86,10 @@ namespace CliFx.Domain
throw CliFxException.CannotConvertToType(this, value, targetType); throw CliFxException.CannotConvertToType(this, value, targetType);
} }
private object ConvertNonScalar(IReadOnlyList<string> values, Type targetEnumerableType, Type targetElementType) private object ConvertNonScalar(
IReadOnlyList<string> values,
Type targetEnumerableType,
Type targetElementType)
{ {
var array = values var array = values
.Select(v => ConvertScalar(v, targetElementType)) .Select(v => ConvertScalar(v, targetElementType))
@@ -133,7 +144,7 @@ namespace CliFx.Domain
return Array.Empty<string>(); return Array.Empty<string>();
var underlyingType = var underlyingType =
Property.PropertyType.GetNullableUnderlyingType() ?? Property.PropertyType.TryGetNullableUnderlyingType() ??
Property.PropertyType; Property.PropertyType;
// Enum // Enum
@@ -148,12 +159,9 @@ namespace CliFx.Domain
{ {
private static readonly IFormatProvider FormatProvider = CultureInfo.InvariantCulture; private static readonly IFormatProvider FormatProvider = CultureInfo.InvariantCulture;
private static readonly IReadOnlyDictionary<Type, Func<string?, object?>> PrimitiveConverters = private static readonly IReadOnlyDictionary<Type, Func<string, object?>> PrimitiveConverters =
new Dictionary<Type, Func<string?, object?>> new Dictionary<Type, Func<string, object?>>
{ {
[typeof(object)] = v => v,
[typeof(string)] = v => v,
[typeof(bool)] = v => string.IsNullOrWhiteSpace(v) || bool.Parse(v),
[typeof(char)] = v => v.Single(), [typeof(char)] = v => v.Single(),
[typeof(sbyte)] = v => sbyte.Parse(v, FormatProvider), [typeof(sbyte)] = v => sbyte.Parse(v, FormatProvider),
[typeof(byte)] = v => byte.Parse(v, FormatProvider), [typeof(byte)] = v => byte.Parse(v, FormatProvider),

View File

@@ -385,7 +385,7 @@ namespace CliFx.Domain
// Enumerable // Enumerable
if (!(defaultValue is string) && defaultValue is IEnumerable defaultValues) if (!(defaultValue is string) && defaultValue is IEnumerable defaultValues)
{ {
var elementType = defaultValues.GetType().GetEnumerableUnderlyingType() ?? typeof(object); var elementType = defaultValues.GetType().TryGetEnumerableUnderlyingType() ?? typeof(object);
// If the ToString() method is not overriden, the default value can't be formatted nicely // If the ToString() method is not overriden, the default value can't be formatted nicely
if (!elementType.IsToStringOverriden()) if (!elementType.IsToStringOverriden())

View File

@@ -14,9 +14,9 @@ namespace CliFx.Internal.Extensions
public static bool Implements(this Type type, Type interfaceType) => type.GetInterfaces().Contains(interfaceType); public static bool Implements(this Type type, Type interfaceType) => type.GetInterfaces().Contains(interfaceType);
public static Type? GetNullableUnderlyingType(this Type type) => Nullable.GetUnderlyingType(type); public static Type? TryGetNullableUnderlyingType(this Type type) => Nullable.GetUnderlyingType(type);
public static Type? GetEnumerableUnderlyingType(this Type type) public static Type? TryGetEnumerableUnderlyingType(this Type type)
{ {
if (type.IsPrimitive) if (type.IsPrimitive)
return null; return null;
@@ -29,7 +29,7 @@ namespace CliFx.Internal.Extensions
return type return type
.GetInterfaces() .GetInterfaces()
.Select(GetEnumerableUnderlyingType) .Select(TryGetEnumerableUnderlyingType)
.Where(t => t != null) .Where(t => t != null)
.OrderByDescending(t => t != typeof(object)) // prioritize more specific types .OrderByDescending(t => t != typeof(object)) // prioritize more specific types
.FirstOrDefault(); .FirstOrDefault();

View File

@@ -34,7 +34,7 @@ namespace System.Collections.Generic
} }
public static TValue GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dic, TKey key) => public static TValue GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dic, TKey key) =>
dic.TryGetValue(key, out var result) ? result! : default!; dic.TryGetValue(key!, out var result) ? result! : default!;
} }
} }