Improve option converter and add support for dynamic types constructable or parseable from string

This commit is contained in:
Alexey Golub
2019-06-09 01:51:46 +03:00
parent fd6ed3ca72
commit e0211fc141
5 changed files with 225 additions and 9 deletions

View File

@@ -44,6 +44,10 @@ namespace CliFx.Tests
yield return new TestCaseData("01:00:00", typeof(TimeSpan?), new TimeSpan(01, 00, 00));
yield return new TestCaseData(null, typeof(TimeSpan?), null);
yield return new TestCaseData("value", typeof(TestStringConstructable), new TestStringConstructable("value"));
yield return new TestCaseData("value", typeof(TestStringParseable), TestStringParseable.Parse("value"));
}
[Test]

View File

@@ -0,0 +1,12 @@
namespace CliFx.Tests.TestObjects
{
public struct TestStringConstructable
{
public string Value { get; }
public TestStringConstructable(string value)
{
Value = value;
}
}
}

View File

@@ -0,0 +1,17 @@
namespace CliFx.Tests.TestObjects
{
public partial struct TestStringParseable
{
public string Value { get; }
private TestStringParseable(string value)
{
Value = value;
}
}
public partial struct TestStringParseable
{
public static TestStringParseable Parse(string value) => new TestStringParseable(value);
}
}

View File

@@ -0,0 +1,21 @@
using System;
namespace CliFx.Exceptions
{
public class CommandOptionConvertException : Exception
{
public CommandOptionConvertException()
{
}
public CommandOptionConvertException(string message)
: base(message)
{
}
public CommandOptionConvertException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -1,5 +1,8 @@
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using CliFx.Exceptions;
using CliFx.Internal;
namespace CliFx.Services
@@ -22,35 +25,194 @@ namespace CliFx.Services
{
// String or object
if (targetType == typeof(string) || targetType == typeof(object))
{
return value;
}
// Bool
if (targetType == typeof(bool))
return value.IsNullOrWhiteSpace() || bool.Parse(value);
{
if (value.IsNullOrWhiteSpace())
return true;
if (bool.TryParse(value, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to boolean.");
}
// Char
if (targetType == typeof(char))
{
if (value.Length == 1)
return value[0];
throw new CommandOptionConvertException(
$"Can't convert value [{value}] to char. The value is either empty or longer than one character.");
}
// Sbyte
if (targetType == typeof(sbyte))
{
if (sbyte.TryParse(value, NumberStyles.Integer, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to sbyte.");
}
// Byte
if (targetType == typeof(byte))
{
if (byte.TryParse(value, NumberStyles.Integer, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to byte.");
}
// Short
if (targetType == typeof(short))
{
if (short.TryParse(value, NumberStyles.Integer, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to short.");
}
// Ushort
if (targetType == typeof(ushort))
{
if (ushort.TryParse(value, NumberStyles.Integer, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to ushort.");
}
// Int
if (targetType == typeof(int))
{
if (int.TryParse(value, NumberStyles.Integer, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to int.");
}
// Uint
if (targetType == typeof(uint))
{
if (uint.TryParse(value, NumberStyles.Integer, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to uint.");
}
// Long
if (targetType == typeof(long))
{
if (long.TryParse(value, NumberStyles.Integer, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to long.");
}
// Ulong
if (targetType == typeof(ulong))
{
if (ulong.TryParse(value, NumberStyles.Integer, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to ulong.");
}
// Float
if (targetType == typeof(float))
{
if (float.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to float.");
}
// Double
if (targetType == typeof(double))
{
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to double.");
}
// Decimal
if (targetType == typeof(decimal))
{
if (decimal.TryParse(value, NumberStyles.Number, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to decimal.");
}
// DateTime
if (targetType == typeof(DateTime))
return DateTime.Parse(value, _formatProvider);
{
if (DateTime.TryParse(value, _formatProvider, DateTimeStyles.None, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to DateTime.");
}
// DateTimeOffset
if (targetType == typeof(DateTimeOffset))
return DateTimeOffset.Parse(value, _formatProvider);
{
if (DateTimeOffset.TryParse(value, _formatProvider, DateTimeStyles.None, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to DateTimeOffset.");
}
// TimeSpan
if (targetType == typeof(TimeSpan))
return TimeSpan.Parse(value, _formatProvider);
{
if (TimeSpan.TryParse(value, _formatProvider, out var result))
return result;
throw new CommandOptionConvertException($"Can't convert value [{value}] to TimeSpan.");
}
// Enum
if (targetType.IsEnum)
return Enum.Parse(targetType, value, true);
{
if (Enum.GetNames(targetType).Contains(value, StringComparer.OrdinalIgnoreCase))
return Enum.Parse(targetType, value, true);
throw new CommandOptionConvertException(
$"Can't convert value [{value}] to [{targetType}]. The value is not defined on the enum.");
}
// Nullable
var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType);
if (nullableUnderlyingType != null)
return !value.IsNullOrWhiteSpace() ? ConvertOption(value, nullableUnderlyingType) : null;
{
if (value.IsNullOrWhiteSpace())
return null;
// All other types
return Convert.ChangeType(value, targetType, _formatProvider);
return ConvertOption(value, nullableUnderlyingType);
}
// Has a constructor that accepts a single string
var stringConstructor = targetType.GetConstructor(new[] {typeof(string)});
if (stringConstructor != null)
{
return stringConstructor.Invoke(new object[] {value});
}
// Has a static parse method that accepts a single string
var parseMethod = targetType.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, null, new[] {typeof(string)}, null);
if (parseMethod != null)
{
return parseMethod.Invoke(null, new object[] {value});
}
// Unknown type
throw new CommandOptionConvertException($"Can't convert value [{value}] to unrecognized type [{targetType}].");
}
}
}