diff --git a/src/CommandLineUtils/Abstractions/ValueParserProvider.cs b/src/CommandLineUtils/Abstractions/ValueParserProvider.cs index 69eb6eed..57aa28d8 100644 --- a/src/CommandLineUtils/Abstractions/ValueParserProvider.cs +++ b/src/CommandLineUtils/Abstractions/ValueParserProvider.cs @@ -21,18 +21,18 @@ internal ValueParserProvider() AddRange( new IValueParser[] { - StringValueParser.Singleton, - BooleanValueParser.Singleton, - ByteValueParser.Singleton, - Int16ValueParser.Singleton, - Int32ValueParser.Singleton, - Int64ValueParser.Singleton, - UInt16ValueParser.Singleton, - UInt32ValueParser.Singleton, - UInt64ValueParser.Singleton, - FloatValueParser.Singleton, - DoubleValueParser.Singleton, - UriValueParser.Singleton, + StockValueParsers.String, + StockValueParsers.Boolean, + StockValueParsers.Byte, + StockValueParsers.Int16, + StockValueParsers.Int32, + StockValueParsers.Int64, + StockValueParsers.UInt16, + StockValueParsers.UInt32, + StockValueParsers.UInt64, + StockValueParsers.Float, + StockValueParsers.Double, + StockValueParsers.Uri, }); } @@ -96,14 +96,14 @@ internal IValueParser GetParserImpl() if (typeInfo.IsEnum) { - return new EnumParser(type); + return EnumParser.Create(type); } if (ReflectionHelper.IsNullableType(typeInfo, out var wrappedType)) { if (wrappedType.GetTypeInfo().IsEnum) { - return new NullableValueParser(new EnumParser(wrappedType)); + return new NullableValueParser(EnumParser.Create(wrappedType)); } if (_parsers.TryGetValue(wrappedType, out parser)) @@ -125,8 +125,8 @@ internal IValueParser GetParserImpl() { return null; } - var parserType = typeof(ValueTupleValueParser<>).MakeGenericType(typeInfo.GenericTypeArguments[1]); - return (IValueParser)Activator.CreateInstance(parserType, new object[] { innerParser }); + var method = typeof(ValueTupleValueParser).GetTypeInfo().GetMethod(nameof(ValueTupleValueParser.Create)).MakeGenericMethod(typeInfo.GenericTypeArguments[1]); + return (IValueParser)method.Invoke(null, new object[] { innerParser }); } return null; diff --git a/src/CommandLineUtils/Internal/ValueParsers/BooleanValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/BooleanValueParser.cs deleted file mode 100644 index 11849fb7..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/BooleanValueParser.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - using System; - using System.Globalization; - - internal class BooleanValueParser : IValueParser - { - private BooleanValueParser() - { } - - public static BooleanValueParser Singleton { get; } = new BooleanValueParser(); - - public Type TargetType { get; } = typeof(bool); - - public bool Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return default; - - if (value == "T" || value == "t") return true; - if (value == "F" || value == "f") return false; - - if (!bool.TryParse(value, out var result)) - { - if (short.TryParse(value, out var bit)) - { - if (bit == 0) return false; - if (bit == 1) return true; - } - - throw new FormatException($"Invalid value specified for {argName}. Cannot convert '{value}' to a boolean."); - } - - return result; - } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/ByteValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/ByteValueParser.cs deleted file mode 100644 index c82aeb37..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/ByteValueParser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - using System; - using System.Globalization; - - internal class ByteValueParser : IValueParser - { - private ByteValueParser() - { } - - public static ByteValueParser Singleton { get; } = new ByteValueParser(); - - public Type TargetType { get; } = typeof(byte); - - public byte Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return default; - - if (!byte.TryParse(value, NumberStyles.Integer, culture.NumberFormat, out var result)) - { - throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid number."); - } - - return result; - } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/DoubleValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/DoubleValueParser.cs deleted file mode 100644 index 09a433bb..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/DoubleValueParser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - internal class DoubleValueParser : IValueParser - { - private DoubleValueParser() - { } - - public static DoubleValueParser Singleton { get; } = new DoubleValueParser(); - - public Type TargetType { get; } = typeof(double); - - public double Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return default; - - if (!double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, culture.NumberFormat, out var result)) - { - throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid floating-point number."); - } - - return result; - } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/EnumParser.cs b/src/CommandLineUtils/Internal/ValueParsers/EnumParser.cs index 7c8c2032..d2e758e9 100644 --- a/src/CommandLineUtils/Internal/ValueParsers/EnumParser.cs +++ b/src/CommandLineUtils/Internal/ValueParsers/EnumParser.cs @@ -6,27 +6,22 @@ namespace McMaster.Extensions.CommandLineUtils.Abstractions { - internal class EnumParser : IValueParser + internal static class EnumParser { - public EnumParser(Type enumType) - { - TargetType = enumType; - } - - public Type TargetType { get; } - - public object Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return Enum.ToObject(TargetType, 0); - - try + public static IValueParser Create(Type enumType) => + ValueParser.Create(enumType, (argName, value, culture) => { - return Enum.Parse(TargetType, value, ignoreCase: true); - } - catch - { - throw new FormatException($"Invalid value specified for {argName}. Allowed values are: {string.Join(", ", Enum.GetNames(TargetType))}."); - } - } + if (value == null) return Enum.ToObject(enumType, 0); + + try + { + return Enum.Parse(enumType, value, ignoreCase: true); + } + catch + { + throw new FormatException( + $"Invalid value specified for {argName}. Allowed values are: {string.Join(", ", Enum.GetNames(enumType))}."); + } + }); } } diff --git a/src/CommandLineUtils/Internal/ValueParsers/FloatValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/FloatValueParser.cs deleted file mode 100644 index e46b2005..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/FloatValueParser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - internal class FloatValueParser : IValueParser - { - private FloatValueParser() - { } - - public static FloatValueParser Singleton { get; } = new FloatValueParser(); - - public Type TargetType { get; } = typeof(float); - - public float Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return default; - - if (!float.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, culture.NumberFormat, out var result)) - { - throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid floating-point number."); - } - - return result; - } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/Int16ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/Int16ValueParser.cs deleted file mode 100644 index 44ffdcab..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/Int16ValueParser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - internal class Int16ValueParser : IValueParser - { - private Int16ValueParser() - { } - - public static Int16ValueParser Singleton { get; } = new Int16ValueParser(); - - public Type TargetType { get; } = typeof(short); - - public short Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return default; - - if (!short.TryParse(value, NumberStyles.Integer, culture, out var result)) - { - throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid number."); - } - - return result; - } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/Int32ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/Int32ValueParser.cs deleted file mode 100644 index 341a0c41..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/Int32ValueParser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - internal class Int32ValueParser : IValueParser - { - private Int32ValueParser() - { } - - public static Int32ValueParser Singleton { get; } = new Int32ValueParser(); - - public Type TargetType { get; } = typeof(int); - - public int Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return default; - - if (!int.TryParse(value, NumberStyles.Integer, culture.NumberFormat, out var result)) - { - throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid number."); - } - - return result; - } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/Int64ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/Int64ValueParser.cs deleted file mode 100644 index d0d52849..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/Int64ValueParser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - internal class Int64ValueParser : IValueParser - { - private Int64ValueParser() - { } - - public static Int64ValueParser Singleton { get; } = new Int64ValueParser(); - - public Type TargetType { get; } = typeof(long); - - public long Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return default; - - if (!long.TryParse(value, NumberStyles.Integer, culture.NumberFormat, out var result)) - { - throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid number."); - } - - return result; - } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/StockValueParsers.cs b/src/CommandLineUtils/Internal/ValueParsers/StockValueParsers.cs new file mode 100644 index 00000000..917178e6 --- /dev/null +++ b/src/CommandLineUtils/Internal/ValueParsers/StockValueParsers.cs @@ -0,0 +1,80 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace McMaster.Extensions.CommandLineUtils.Abstractions +{ + using System; + using System.Globalization; + + internal static class StockValueParsers + { + public static readonly IValueParser Boolean = ValueParser.Create( + (argName, value, culture) => + { + if (value == null) return default; + + if (value == "T" || value == "t") return true; + if (value == "F" || value == "f") return false; + + if (!bool.TryParse(value, out var result)) + { + if (short.TryParse(value, out var bit)) + { + if (bit == 0) return false; + if (bit == 1) return true; + } + + throw InvalidValueException(argName, $"Cannot convert '{value}' to a boolean."); + } + + return result; + }); + + public static readonly IValueParser String = ValueParser.Create((_, value, __) => value); + + public static readonly IValueParser Uri = ValueParser.Create( + (_, value, culture) => new Uri(value, UriKind.RelativeOrAbsolute)); + + private static FormatException InvalidValueException(string argName, string specifics) => + new FormatException($"Invalid value specified for {argName}. {specifics}"); + + private delegate bool NumberParser(string s, NumberStyles styles, IFormatProvider provider, out T result); + + private static IValueParser Create(NumberParser parser, NumberStyles styles, + Func errorSelector) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (errorSelector == null) throw new ArgumentNullException(nameof(errorSelector)); + + return ValueParser.Create((value, culture) => parser(value, styles, culture.NumberFormat, out var result) + ? (true, result) + : default, + errorSelector); + } + + private static IValueParser FloatingPointParser(NumberParser parser) => + Create(parser, NumberStyles.Float + | NumberStyles.AllowThousands, + (argName, value) => InvalidValueException(argName, $"'{value}' is not a valid floating-point number.")); + + public static readonly IValueParser Double = FloatingPointParser(double.TryParse); + public static readonly IValueParser Float = FloatingPointParser (float.TryParse ); + + private static IValueParser IntegerParser(NumberParser parser) => + Create(parser, NumberStyles.Integer, (argName, value) => InvalidValueException(argName, $"'{value}' is not a valid number.")); + + public static readonly IValueParser Int16 = IntegerParser(short.TryParse); + public static readonly IValueParser Int32 = IntegerParser (int.TryParse ); + public static readonly IValueParser Int64 = IntegerParser (long.TryParse ); + + private static IValueParser NonNegativeIntegerParser(NumberParser parser) => + Create(parser, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, + (argName, value) => InvalidValueException(argName, $"'{value}' is not a valid, non-negative number.")); + + public static readonly IValueParser Byte = NonNegativeIntegerParser (byte.TryParse ); + public static readonly IValueParser UInt16 = NonNegativeIntegerParser(ushort.TryParse); + public static readonly IValueParser UInt32 = NonNegativeIntegerParser (uint.TryParse ); + public static readonly IValueParser UInt64 = NonNegativeIntegerParser (ulong.TryParse ); + + } +} diff --git a/src/CommandLineUtils/Internal/ValueParsers/StringValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/StringValueParser.cs deleted file mode 100644 index a1e99dd4..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/StringValueParser.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - internal class StringValueParser : IValueParser - { - private StringValueParser() - { } - - public static StringValueParser Singleton { get; } = new StringValueParser(); - - public Type TargetType { get; } = typeof(string); - - public string Parse(string argName, string value, CultureInfo culture) => value; - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/TupleValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/TupleValueParser.cs index 5326d17e..25dc42be 100644 --- a/src/CommandLineUtils/Internal/ValueParsers/TupleValueParser.cs +++ b/src/CommandLineUtils/Internal/ValueParsers/TupleValueParser.cs @@ -2,32 +2,20 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Globalization; namespace McMaster.Extensions.CommandLineUtils.Abstractions { - internal class TupleValueParser : IValueParser> + internal class TupleValueParser { - private readonly IValueParser _typeParser; - - public TupleValueParser(IValueParser typeParser) - { - _typeParser = typeParser; - } - - public Type TargetType { get; } = typeof(Tuple); - - public Tuple Parse(string argName, string value, CultureInfo culture) + public static IValueParser> Create(IValueParser typeParser) { - if (value == null) - { - return Tuple.Create(false, default); - } + if (typeParser == null) throw new ArgumentNullException(nameof(typeParser)); - return Tuple.Create(true, _typeParser.Parse(argName, value, culture)); + return + ValueParser.Create((argName, value, culture) => + value == null + ? Tuple.Create(false, default) + : Tuple.Create(true, typeParser.Parse(argName, value, culture))); } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); } } diff --git a/src/CommandLineUtils/Internal/ValueParsers/UInt16ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/UInt16ValueParser.cs deleted file mode 100644 index fda3fb03..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/UInt16ValueParser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - internal class UInt16ValueParser : IValueParser - { - private UInt16ValueParser() - { } - - public static UInt16ValueParser Singleton { get; } = new UInt16ValueParser(); - - public Type TargetType { get; } = typeof(ushort); - - public ushort Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return default; - - if (!ushort.TryParse(value, NumberStyles.Integer, culture, out var result)) - { - throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid, non-negative number."); - } - return result; - } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/UInt32ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/UInt32ValueParser.cs deleted file mode 100644 index c56f5c00..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/UInt32ValueParser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - internal class UInt32ValueParser : IValueParser - { - private UInt32ValueParser() - { } - - public static UInt32ValueParser Singleton { get; } = new UInt32ValueParser(); - - public Type TargetType { get; } = typeof(uint); - - public uint Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return default; - - if (!uint.TryParse(value, NumberStyles.Integer, culture, out var result)) - { - throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid, non-negative number."); - } - return result; - } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/UInt64ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/UInt64ValueParser.cs deleted file mode 100644 index 5a9ec3b3..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/UInt64ValueParser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - internal class UInt64ValueParser : IValueParser - { - private UInt64ValueParser() - { } - - public static UInt64ValueParser Singleton { get; } = new UInt64ValueParser(); - - public Type TargetType { get; } = typeof(ulong); - - public ulong Parse(string argName, string value, CultureInfo culture) - { - if (value == null) return default; - - if (!ulong.TryParse(value, NumberStyles.Integer, culture, out var result)) - { - throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid, non-negative number."); - } - return result; - } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/UriValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/UriValueParser.cs deleted file mode 100644 index 248b7699..00000000 --- a/src/CommandLineUtils/Internal/ValueParsers/UriValueParser.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Globalization; - -namespace McMaster.Extensions.CommandLineUtils.Abstractions -{ - internal class UriValueParser : IValueParser - { - private UriValueParser() - { } - - public static UriValueParser Singleton { get; } = new UriValueParser(); - - public Type TargetType { get; } = typeof(Uri); - - public Uri Parse(string argName, string value, CultureInfo culture) => new Uri(value, UriKind.RelativeOrAbsolute); - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); - } -} diff --git a/src/CommandLineUtils/Internal/ValueParsers/ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/ValueParser.cs new file mode 100644 index 00000000..a01ae5d2 --- /dev/null +++ b/src/CommandLineUtils/Internal/ValueParsers/ValueParser.cs @@ -0,0 +1,104 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace McMaster.Extensions.CommandLineUtils.Abstractions +{ + using System; + using System.Globalization; + + /// + /// Provides methods for creating + /// boilerplate implementations. + /// + + public static class ValueParser + { + /// + /// Creates an implementation for a type + /// given a parsing function that receives an argument name, a value + /// to parse and a culture to use for parsing. + /// + + public static IValueParser Create(Type targetType, Func parser) => + new DelegatingValueParser(targetType, Create(parser)); + + /// + /// Creates an implementation given + /// a parsing function that receives an argument name, a value to + /// parse and a culture to use for parsing. + /// + + public static IValueParser Create(Func parser) => + new DelegatingValueParser(parser); + + /// + /// Creates an implementation given + /// a parsing function that receives an argument name, a value to + /// parse, a culture to use for parsing and returns a tuple whose + /// first element indicates whether parsing was successful and + /// second element is the parsed value. + /// + + public static IValueParser Create(Func parser) => + Create(parser, (argName, value) => new FormatException($"Invalid value specified for {argName}. '{value}' is an invalid representation of {typeof(T).Name}.")); + + /// + /// Creates an implementation given + /// a parsing function that receives an argument name, a value to + /// parse, a culture to use for parsing and returns a tuple whose + /// first element indicates whether parsing was successful and + /// second element is the parsed value. An additional parameter + /// specifies a function that returns the + /// to throw, given the argument name and value, when parsing is + /// unsuccessful. + /// + + public static IValueParser Create(Func parser, Func errorSelector) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (errorSelector == null) throw new ArgumentNullException(nameof(errorSelector)); + + return Create((argName, value, culture) => + { + if (value == null) return default; + + var (parsed, result) = parser(value, culture); + return parsed ? result : throw errorSelector(argName, value); + }); + } + + private sealed class DelegatingValueParser : IValueParser + { + readonly Func _parser; + + public DelegatingValueParser(Func parser) + { + _parser = parser ?? throw new ArgumentNullException(nameof(parser)); + } + + public Type TargetType => typeof(T); + + public T Parse(string argName, string value, CultureInfo culture) => + _parser(argName, value, culture); + + object IValueParser.Parse(string argName, string value, CultureInfo culture) => + Parse(argName, value, culture); + } + + private class DelegatingValueParser : IValueParser + { + private readonly IValueParser _parser; + + public DelegatingValueParser(Type targetType, IValueParser parser) + { + TargetType = targetType ?? throw new ArgumentNullException(nameof(targetType)); + _parser = parser ?? throw new ArgumentNullException(nameof(parser)); + } + + public Type TargetType { get; } + + public object Parse(string argName, string value, CultureInfo culture) => + _parser.Parse(argName, value, culture); + } + } +} diff --git a/src/CommandLineUtils/Internal/ValueParsers/ValueTupleValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/ValueTupleValueParser.cs index 508634ec..c2467250 100644 --- a/src/CommandLineUtils/Internal/ValueParsers/ValueTupleValueParser.cs +++ b/src/CommandLineUtils/Internal/ValueParsers/ValueTupleValueParser.cs @@ -2,32 +2,20 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Globalization; namespace McMaster.Extensions.CommandLineUtils.Abstractions { - internal class ValueTupleValueParser : IValueParser<(bool, T)> + internal static class ValueTupleValueParser { - private readonly IValueParser _typeParser; - - public ValueTupleValueParser(IValueParser typeParser) - { - _typeParser = typeParser; - } - - public Type TargetType { get; } = typeof((bool, T)); - - public (bool, T) Parse(string argName, string value, CultureInfo culture) + public static IValueParser<(bool, T)> Create(IValueParser typeParser) { - if (value == null) - { - return (true, default(T)); - } + if (typeParser == null) throw new ArgumentNullException(nameof(typeParser)); - return (true, _typeParser.Parse(argName, value, culture)); + return + ValueParser.Create((argName, value, culture) => + value == null + ? (true, default) + : (true, typeParser.Parse(argName, value, culture))); } - - object IValueParser.Parse(string argName, string value, CultureInfo culture) - => this.Parse(argName, value, culture); } }