mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Improve option converter and add support for dynamic types constructable or parseable from string
This commit is contained in:
@@ -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("01:00:00", typeof(TimeSpan?), new TimeSpan(01, 00, 00));
|
||||||
|
|
||||||
yield return new TestCaseData(null, typeof(TimeSpan?), null);
|
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]
|
[Test]
|
||||||
|
|||||||
12
CliFx.Tests/TestObjects/TestStringConstructable.cs
Normal file
12
CliFx.Tests/TestObjects/TestStringConstructable.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CliFx.Tests.TestObjects
|
||||||
|
{
|
||||||
|
public struct TestStringConstructable
|
||||||
|
{
|
||||||
|
public string Value { get; }
|
||||||
|
|
||||||
|
public TestStringConstructable(string value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
CliFx.Tests/TestObjects/TestStringParseable.cs
Normal file
17
CliFx.Tests/TestObjects/TestStringParseable.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
CliFx/Exceptions/CommandOptionConvertException.cs
Normal file
21
CliFx/Exceptions/CommandOptionConvertException.cs
Normal 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using CliFx.Exceptions;
|
||||||
using CliFx.Internal;
|
using CliFx.Internal;
|
||||||
|
|
||||||
namespace CliFx.Services
|
namespace CliFx.Services
|
||||||
@@ -22,35 +25,194 @@ namespace CliFx.Services
|
|||||||
{
|
{
|
||||||
// String or object
|
// String or object
|
||||||
if (targetType == typeof(string) || targetType == typeof(object))
|
if (targetType == typeof(string) || targetType == typeof(object))
|
||||||
|
{
|
||||||
return value;
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
// Bool
|
// Bool
|
||||||
if (targetType == typeof(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
|
// DateTime
|
||||||
if (targetType == typeof(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
|
// DateTimeOffset
|
||||||
if (targetType == typeof(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
|
// TimeSpan
|
||||||
if (targetType == typeof(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
|
// Enum
|
||||||
if (targetType.IsEnum)
|
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
|
// Nullable
|
||||||
var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType);
|
var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType);
|
||||||
if (nullableUnderlyingType != null)
|
if (nullableUnderlyingType != null)
|
||||||
return !value.IsNullOrWhiteSpace() ? ConvertOption(value, nullableUnderlyingType) : null;
|
{
|
||||||
|
if (value.IsNullOrWhiteSpace())
|
||||||
|
return null;
|
||||||
|
|
||||||
// All other types
|
return ConvertOption(value, nullableUnderlyingType);
|
||||||
return Convert.ChangeType(value, targetType, _formatProvider);
|
}
|
||||||
|
|
||||||
|
// 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}].");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user