diff --git a/CliFx/ApplicationMetadata.cs b/CliFx/ApplicationMetadata.cs index 5674724..0f778c1 100644 --- a/CliFx/ApplicationMetadata.cs +++ b/CliFx/ApplicationMetadata.cs @@ -28,7 +28,11 @@ /// /// Initializes an instance of . /// - public ApplicationMetadata(string title, string executableName, string versionText, string? description) + public ApplicationMetadata( + string title, + string executableName, + string versionText, + string? description) { Title = title; ExecutableName = executableName; diff --git a/CliFx/Domain/CommandArgumentSchema.cs b/CliFx/Domain/CommandArgumentSchema.cs index c2300e7..d9fab28 100644 --- a/CliFx/Domain/CommandArgumentSchema.cs +++ b/CliFx/Domain/CommandArgumentSchema.cs @@ -28,44 +28,52 @@ namespace CliFx.Domain private Type? TryGetEnumerableArgumentUnderlyingType() => Property != null && Property.PropertyType != typeof(string) - ? Property.PropertyType.GetEnumerableUnderlyingType() + ? Property.PropertyType.TryGetEnumerableUnderlyingType() : null; private object? ConvertScalar(string? value, Type targetType) { try { - // Custom conversion + // Custom conversion (always takes highest priority) if (ConverterType != null) return ConverterType.CreateInstance().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); - if (primitiveConverter != null) + if (primitiveConverter != null && !string.IsNullOrWhiteSpace(value)) return primitiveConverter(value); - // Enum - if (targetType.IsEnum) + // Enum conversion + if (targetType.IsEnum && !string.IsNullOrWhiteSpace(value)) return Enum.Parse(targetType, value, true); - // Nullable - var nullableUnderlyingType = targetType.GetNullableUnderlyingType(); + // Nullable conversion + var nullableUnderlyingType = targetType.TryGetNullableUnderlyingType(); if (nullableUnderlyingType != null) return !string.IsNullOrWhiteSpace(value) ? ConvertScalar(value, nullableUnderlyingType) : null; - // String-constructible + // String-constructible conversion var stringConstructor = targetType.GetConstructor(new[] {typeof(string)}); if (stringConstructor != null) return stringConstructor.Invoke(new object[] {value!}); - // String-parseable (with format provider) + // String-parseable conversion (with format provider) var parseMethodWithFormatProvider = targetType.TryGetStaticParseMethod(true); if (parseMethodWithFormatProvider != null) return parseMethodWithFormatProvider.Invoke(null, new object[] {value!, FormatProvider}); - // String-parseable (without format provider) + // String-parseable conversion (without format provider) var parseMethod = targetType.TryGetStaticParseMethod(); if (parseMethod != null) return parseMethod.Invoke(null, new object[] {value!}); @@ -78,7 +86,10 @@ namespace CliFx.Domain throw CliFxException.CannotConvertToType(this, value, targetType); } - private object ConvertNonScalar(IReadOnlyList values, Type targetEnumerableType, Type targetElementType) + private object ConvertNonScalar( + IReadOnlyList values, + Type targetEnumerableType, + Type targetElementType) { var array = values .Select(v => ConvertScalar(v, targetElementType)) @@ -133,7 +144,7 @@ namespace CliFx.Domain return Array.Empty(); var underlyingType = - Property.PropertyType.GetNullableUnderlyingType() ?? + Property.PropertyType.TryGetNullableUnderlyingType() ?? Property.PropertyType; // Enum @@ -148,12 +159,9 @@ namespace CliFx.Domain { private static readonly IFormatProvider FormatProvider = CultureInfo.InvariantCulture; - private static readonly IReadOnlyDictionary> PrimitiveConverters = - new Dictionary> + private static readonly IReadOnlyDictionary> PrimitiveConverters = + new Dictionary> { - [typeof(object)] = v => v, - [typeof(string)] = v => v, - [typeof(bool)] = v => string.IsNullOrWhiteSpace(v) || bool.Parse(v), [typeof(char)] = v => v.Single(), [typeof(sbyte)] = v => sbyte.Parse(v, FormatProvider), [typeof(byte)] = v => byte.Parse(v, FormatProvider), diff --git a/CliFx/Domain/HelpTextWriter.cs b/CliFx/Domain/HelpTextWriter.cs index f2b452b..5a05b95 100644 --- a/CliFx/Domain/HelpTextWriter.cs +++ b/CliFx/Domain/HelpTextWriter.cs @@ -385,7 +385,7 @@ namespace CliFx.Domain // Enumerable 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 (!elementType.IsToStringOverriden()) diff --git a/CliFx/Internal/Extensions/TypeExtensions.cs b/CliFx/Internal/Extensions/TypeExtensions.cs index 6a05cdb..4c93a90 100644 --- a/CliFx/Internal/Extensions/TypeExtensions.cs +++ b/CliFx/Internal/Extensions/TypeExtensions.cs @@ -14,9 +14,9 @@ namespace CliFx.Internal.Extensions 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) return null; @@ -29,7 +29,7 @@ namespace CliFx.Internal.Extensions return type .GetInterfaces() - .Select(GetEnumerableUnderlyingType) + .Select(TryGetEnumerableUnderlyingType) .Where(t => t != null) .OrderByDescending(t => t != typeof(object)) // prioritize more specific types .FirstOrDefault(); diff --git a/CliFx/Internal/Polyfills.cs b/CliFx/Internal/Polyfills.cs index 10f6987..daa3965 100644 --- a/CliFx/Internal/Polyfills.cs +++ b/CliFx/Internal/Polyfills.cs @@ -34,7 +34,7 @@ namespace System.Collections.Generic } public static TValue GetValueOrDefault(this IReadOnlyDictionary dic, TKey key) => - dic.TryGetValue(key, out var result) ? result! : default!; + dic.TryGetValue(key!, out var result) ? result! : default!; } }