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(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]
|
||||
|
||||
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.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}].");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user