diff --git a/src/CommandLineUtils/Abstractions/IValueParser.cs b/src/CommandLineUtils/Abstractions/IValueParser.cs
new file mode 100644
index 00000000..b2bb7169
--- /dev/null
+++ b/src/CommandLineUtils/Abstractions/IValueParser.cs
@@ -0,0 +1,27 @@
+// 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;
+
+ ///
+ /// The interface for defining a value parser.
+ ///
+ public interface IValueParser
+ {
+ ///
+ /// Gets the Type that this value parser is defined for.
+ ///
+ Type TargetType { get; }
+
+ ///
+ /// Parses the raw string value.
+ ///
+ /// The name of the argument this value will be bound to.
+ /// The raw string value to parse.
+ /// The parsed value object.
+ /// When the value cannot be parsed.
+ object Parse(string argName, string value);
+ }
+}
diff --git a/src/CommandLineUtils/Abstractions/ValueParserProvider.cs b/src/CommandLineUtils/Abstractions/ValueParserProvider.cs
new file mode 100644
index 00000000..b0c50e34
--- /dev/null
+++ b/src/CommandLineUtils/Abstractions/ValueParserProvider.cs
@@ -0,0 +1,149 @@
+// 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.Collections.Generic;
+ using System.Reflection;
+
+ ///
+ /// A store of value parsers that are used to convert argument values from strings to types.
+ ///
+ public class ValueParserProvider
+ {
+ private readonly Dictionary _parsers = new Dictionary(10);
+
+ internal ValueParserProvider()
+ {
+ this.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,
+ });
+ }
+
+ internal IValueParser GetParser(Type type)
+ {
+ if (this._parsers.TryGetValue(type, out var parser))
+ {
+ return parser;
+ }
+
+ var typeInfo = type.GetTypeInfo();
+
+ if (typeInfo.IsEnum)
+ {
+ return new EnumParser(type);
+ }
+
+ if (ReflectionHelper.IsNullableType(typeInfo, out var wrappedType))
+ {
+ if (wrappedType.GetTypeInfo().IsEnum)
+ {
+ return new NullableValueParser(new EnumParser(wrappedType));
+ }
+
+ if (this._parsers.TryGetValue(wrappedType, out parser))
+ {
+ return new NullableValueParser(parser);
+ }
+ }
+
+ return parser;
+ }
+
+
+ ///
+ /// Add a new value parser to the provider.
+ ///
+ /// An instance of the parser that is used to convert an argument from a string.
+ ///
+ /// A value parser with the same is already registered.
+ ///
+ /// is null.
+ public void Add(IValueParser parser)
+ {
+ this.SafeAdd(parser);
+ }
+
+ ///
+ /// Add collection of a new value parsers to the provider.
+ ///
+ /// The collection whose parsers should be added.
+ ///
+ /// A value parser with the same is already registered.
+ ///
+ /// is null.
+ public void AddRange(IEnumerable parsers)
+ {
+ if (parsers == null)
+ {
+ throw new ArgumentNullException(nameof(parsers));
+ }
+
+ foreach (var parser in parsers)
+ {
+ this.SafeAdd(parser);
+ }
+ }
+
+ ///
+ /// Add a new value parser to the provider, or if a value provider already exists for
+ /// then replaces it with .
+ ///
+ /// An instance of the parser that is used to convert an argument from a string.
+ /// is null.
+ public void AddOrReplace(IValueParser parser)
+ {
+ this.SafeAdd(parser, andReplace: true);
+ }
+
+ private void SafeAdd(IValueParser parser, bool andReplace = false)
+ {
+ if (parser == null)
+ {
+ throw new ArgumentNullException(nameof(parser));
+ }
+
+ var targetType = parser.TargetType;
+
+ if (targetType == null)
+ {
+ throw new ArgumentNullException(
+ nameof(IValueParser.TargetType),
+ "The value parser must have a target type set");
+ }
+
+ // strip nullable wrappers since we have a dedicated nullable value parser
+ targetType = ReflectionHelper.IsNullableType(targetType.GetTypeInfo(), out var wrappedType)
+ ? wrappedType
+ : targetType;
+
+ if (this._parsers.ContainsKey(targetType))
+ {
+ if (andReplace)
+ {
+ this._parsers.Remove(targetType);
+ }
+ else
+ {
+ throw new ArgumentException(
+ $"Value parser provider for type '{targetType}' already exists.");
+ }
+ }
+
+ this._parsers.Add(targetType, parser);
+ }
+ }
+}
diff --git a/src/CommandLineUtils/Attributes/OptionAttribute.cs b/src/CommandLineUtils/Attributes/OptionAttribute.cs
index aa3f8459..fb43fb78 100755
--- a/src/CommandLineUtils/Attributes/OptionAttribute.cs
+++ b/src/CommandLineUtils/Attributes/OptionAttribute.cs
@@ -3,6 +3,7 @@
using System;
using System.Reflection;
+using McMaster.Extensions.CommandLineUtils.Abstractions;
namespace McMaster.Extensions.CommandLineUtils
{
@@ -66,7 +67,7 @@ public OptionAttribute(string template, string description, CommandOptionType op
internal CommandOption Configure(CommandLineApplication app, PropertyInfo prop)
{
- var optionType = GetOptionType(prop);
+ var optionType = GetOptionType(prop, app.ValueParsers);
CommandOption option;
if (Template != null)
{
@@ -94,14 +95,14 @@ internal CommandOption Configure(CommandLineApplication app, PropertyInfo prop)
return option;
}
- private CommandOptionType GetOptionType(PropertyInfo prop)
+ private CommandOptionType GetOptionType(PropertyInfo prop, ValueParserProvider valueParsers)
{
CommandOptionType optionType;
if (OptionType.HasValue)
{
optionType = OptionType.Value;
}
- else if (!CommandOptionTypeMapper.Default.TryGetOptionType(prop.PropertyType, out optionType))
+ else if (!CommandOptionTypeMapper.Default.TryGetOptionType(prop.PropertyType, valueParsers, out optionType))
{
throw new InvalidOperationException(Strings.CannotDetermineOptionType(prop));
}
diff --git a/src/CommandLineUtils/CommandLineApplication.Execute.cs b/src/CommandLineUtils/CommandLineApplication.Execute.cs
index 9ab3d575..16e1813b 100644
--- a/src/CommandLineUtils/CommandLineApplication.Execute.cs
+++ b/src/CommandLineUtils/CommandLineApplication.Execute.cs
@@ -57,7 +57,7 @@ public static int Execute(CommandLineContext context)
return app.Execute(context.Arguments);
}
}
- catch (CommandParsingException ex)
+ catch (Exception ex) when (ex is CommandParsingException || ex is FormatException)
{
context.Console.Error.WriteLine(ex.Message);
return ValidationErrorExitCode;
diff --git a/src/CommandLineUtils/CommandLineApplication.cs b/src/CommandLineUtils/CommandLineApplication.cs
index df22dc79..1ec81010 100644
--- a/src/CommandLineUtils/CommandLineApplication.cs
+++ b/src/CommandLineUtils/CommandLineApplication.cs
@@ -94,6 +94,7 @@ internal CommandLineApplication(CommandLineApplication parent,
ValidationErrorHandler = DefaultValidationErrorHandler;
SetContext(context);
_services = new Lazy(() => new ServiceProvider(this));
+ ValueParsers = new ValueParserProvider();
_conventionContext = CreateConventionContext();
@@ -247,6 +248,19 @@ public CommandOption OptionHelp
///
public StringComparison OptionsComparison { get; set; }
+ ///
+ /// Gets the default value parser provider.
+ ///
+ /// The value parsers control how argument values are converted from strings to other types. Additional value
+ /// parsers can be added so that domain specific types can converted. In-built value parsers can also be replaced
+ /// for precise control of all type conversion.
+ ///
+ ///
+ /// Value parsers are currently only used by the Attribute API.
+ ///
+ ///
+ public ValueParserProvider ValueParsers { get; private set; }
+
///
///
/// Defines the working directory of the application. Defaults to .
diff --git a/src/CommandLineUtils/Conventions/ArgumentAttributeConvention.cs b/src/CommandLineUtils/Conventions/ArgumentAttributeConvention.cs
index 3db79b0a..17836f3f 100644
--- a/src/CommandLineUtils/Conventions/ArgumentAttributeConvention.cs
+++ b/src/CommandLineUtils/Conventions/ArgumentAttributeConvention.cs
@@ -99,7 +99,9 @@ private void AddArgument(PropertyInfo prop,
if (argument.MultipleValues)
{
- var collectionParser = CollectionParserProvider.Default.GetParser(prop.PropertyType);
+ var collectionParser = CollectionParserProvider.Default.GetParser(
+ prop.PropertyType,
+ convention.Application.ValueParsers);
if (collectionParser == null)
{
throw new InvalidOperationException(Strings.CannotDetermineParserType(prop));
@@ -115,7 +117,7 @@ private void AddArgument(PropertyInfo prop,
}
else
{
- var parser = ValueParserProvider.Default.GetParser(prop.PropertyType);
+ var parser = convention.Application.ValueParsers.GetParser(prop.PropertyType);
if (parser == null)
{
throw new InvalidOperationException(Strings.CannotDetermineParserType(prop));
diff --git a/src/CommandLineUtils/Conventions/OptionAttributeConventionBase.cs b/src/CommandLineUtils/Conventions/OptionAttributeConventionBase.cs
index 2aac251f..f535fa68 100644
--- a/src/CommandLineUtils/Conventions/OptionAttributeConventionBase.cs
+++ b/src/CommandLineUtils/Conventions/OptionAttributeConventionBase.cs
@@ -53,7 +53,7 @@ private protected void AddOption(ConventionContext context, CommandOption option
switch (option.OptionType)
{
case CommandOptionType.MultipleValue:
- var collectionParser = CollectionParserProvider.Default.GetParser(prop.PropertyType);
+ var collectionParser = CollectionParserProvider.Default.GetParser(prop.PropertyType, context.Application.ValueParsers);
if (collectionParser == null)
{
throw new InvalidOperationException(Strings.CannotDetermineParserType(prop));
@@ -62,7 +62,7 @@ private protected void AddOption(ConventionContext context, CommandOption option
setter.Invoke(context.ModelAccessor.GetModel(), collectionParser.Parse(option.LongName, option.Values)));
break;
case CommandOptionType.SingleOrNoValue:
- var valueTupleParser = ValueTupleParserProvider.Default.GetParser(prop.PropertyType);
+ var valueTupleParser = ValueTupleParserProvider.Default.GetParser(prop.PropertyType, context.Application.ValueParsers);
if (valueTupleParser == null)
{
throw new InvalidOperationException(Strings.CannotDetermineParserType(prop));
@@ -71,7 +71,7 @@ private protected void AddOption(ConventionContext context, CommandOption option
setter.Invoke(context.ModelAccessor.GetModel(), valueTupleParser.Parse(option.HasValue(), option.LongName, option.Value())));
break;
case CommandOptionType.SingleValue:
- var parser = ValueParserProvider.Default.GetParser(prop.PropertyType);
+ var parser = context.Application.ValueParsers.GetParser(prop.PropertyType);
if (parser == null)
{
throw new InvalidOperationException(Strings.CannotDetermineParserType(prop));
diff --git a/src/CommandLineUtils/Internal/CollectionParserProvider.cs b/src/CommandLineUtils/Internal/CollectionParserProvider.cs
index 2dcdb480..1f03f5a7 100644
--- a/src/CommandLineUtils/Internal/CollectionParserProvider.cs
+++ b/src/CommandLineUtils/Internal/CollectionParserProvider.cs
@@ -5,7 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using McMaster.Extensions.CommandLineUtils.ValueParsers;
+using McMaster.Extensions.CommandLineUtils.Abstractions;
namespace McMaster.Extensions.CommandLineUtils
{
@@ -16,12 +16,12 @@ private CollectionParserProvider()
public static CollectionParserProvider Default { get; } = new CollectionParserProvider();
- public ICollectionParser GetParser(Type type)
+ public ICollectionParser GetParser(Type type, ValueParserProvider valueParsers)
{
if (type.IsArray)
{
var elementType = type.GetElementType();
- var elementParser = ValueParserProvider.Default.GetParser(elementType);
+ var elementParser = valueParsers.GetParser(elementType);
if (elementParser == null)
{
return null;
@@ -35,7 +35,7 @@ public ICollectionParser GetParser(Type type)
{
var typeDef = type.GetGenericTypeDefinition();
var elementType = typeInfo.GetGenericArguments().First();
- var elementParser = ValueParserProvider.Default.GetParser(elementType);
+ var elementParser = valueParsers.GetParser(elementType);
if (typeof(IList<>) == typeDef
|| typeof(IEnumerable<>) == typeDef
diff --git a/src/CommandLineUtils/Internal/CommandOptionTypeMapper.cs b/src/CommandLineUtils/Internal/CommandOptionTypeMapper.cs
index 26f80c1f..138a7233 100644
--- a/src/CommandLineUtils/Internal/CommandOptionTypeMapper.cs
+++ b/src/CommandLineUtils/Internal/CommandOptionTypeMapper.cs
@@ -5,6 +5,7 @@
using System.Collections;
using System.Linq;
using System.Reflection;
+using McMaster.Extensions.CommandLineUtils.Abstractions;
namespace McMaster.Extensions.CommandLineUtils
{
@@ -15,11 +16,14 @@ private CommandOptionTypeMapper()
public static CommandOptionTypeMapper Default { get; } = new CommandOptionTypeMapper();
- public bool TryGetOptionType(Type clrType, out CommandOptionType optionType)
+ public bool TryGetOptionType(
+ Type clrType,
+ ValueParserProvider valueParsers,
+ out CommandOptionType optionType)
{
try
{
- optionType = GetOptionType(clrType);
+ optionType = GetOptionType(clrType, valueParsers);
return true;
}
catch
@@ -29,7 +33,7 @@ public bool TryGetOptionType(Type clrType, out CommandOptionType optionType)
}
}
- public CommandOptionType GetOptionType(Type clrType)
+ public CommandOptionType GetOptionType(Type clrType, ValueParserProvider valueParsers = null)
{
if (clrType == typeof(bool))
{
@@ -57,12 +61,12 @@ public CommandOptionType GetOptionType(Type clrType)
var typeDef = typeInfo.GetGenericTypeDefinition();
if (typeDef == typeof(Nullable<>))
{
- return GetOptionType(typeInfo.GetGenericArguments().First());
+ return GetOptionType(typeInfo.GetGenericArguments().First(), valueParsers);
}
if (typeDef == typeof(Tuple<,>) && typeInfo.GenericTypeArguments[0] == typeof(bool))
{
- if (GetOptionType(typeInfo.GenericTypeArguments[1]) == CommandOptionType.SingleValue)
+ if (GetOptionType(typeInfo.GenericTypeArguments[1], valueParsers) == CommandOptionType.SingleValue)
{
return CommandOptionType.SingleOrNoValue;
}
@@ -70,7 +74,7 @@ public CommandOptionType GetOptionType(Type clrType)
if (typeDef == typeof(ValueTuple<,>) && typeInfo.GenericTypeArguments[0] == typeof(bool))
{
- if (GetOptionType(typeInfo.GenericTypeArguments[1]) == CommandOptionType.SingleValue)
+ if (GetOptionType(typeInfo.GenericTypeArguments[1], valueParsers) == CommandOptionType.SingleValue)
{
return CommandOptionType.SingleOrNoValue;
}
@@ -90,7 +94,12 @@ public CommandOptionType GetOptionType(Type clrType)
return CommandOptionType.SingleValue;
}
- throw new ArgumentException("Could not determine CommandOptionType", nameof(clrType));
+ if (valueParsers?.GetParser(clrType) != null)
+ {
+ return CommandOptionType.SingleValue;
+ }
+
+ throw new ArgumentException("Could not determine CommandOptionType", clrType.Name);
}
}
}
diff --git a/src/CommandLineUtils/Internal/IValueParser.cs b/src/CommandLineUtils/Internal/IValueParser.cs
deleted file mode 100644
index c70a7596..00000000
--- a/src/CommandLineUtils/Internal/IValueParser.cs
+++ /dev/null
@@ -1,10 +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
-{
- internal interface IValueParser
- {
- object Parse(string argName, string value);
- }
-}
diff --git a/src/CommandLineUtils/Internal/ReflectionHelper.cs b/src/CommandLineUtils/Internal/ReflectionHelper.cs
index 6cf482f2..5dd3ad72 100644
--- a/src/CommandLineUtils/Internal/ReflectionHelper.cs
+++ b/src/CommandLineUtils/Internal/ReflectionHelper.cs
@@ -82,5 +82,13 @@ public static object[] BindParameters(MethodInfo method, CommandLineApplication
return arguments;
}
+
+ public static bool IsNullableType(TypeInfo typeInfo, out Type wrappedType)
+ {
+ var result = typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>);
+ wrappedType = result ? typeInfo.GetGenericArguments().First() : null;
+
+ return result;
+ }
}
}
diff --git a/src/CommandLineUtils/Internal/ValueParserProvider.cs b/src/CommandLineUtils/Internal/ValueParserProvider.cs
deleted file mode 100644
index 24e8dca6..00000000
--- a/src/CommandLineUtils/Internal/ValueParserProvider.cs
+++ /dev/null
@@ -1,66 +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.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using McMaster.Extensions.CommandLineUtils.ValueParsers;
-
-namespace McMaster.Extensions.CommandLineUtils
-{
- internal class ValueParserProvider
- {
- private Dictionary _parsers = new Dictionary
- {
- { typeof(string), StringValueParser.Singleton },
- { typeof(bool), BooleanValueParser.Singleton },
- { typeof(byte), ByteValueParser.Singleton },
- { typeof(short), Int16ValueParser.Singleton },
- { typeof(int), Int32ValueParser.Singleton },
- { typeof(long), Int64ValueParser.Singleton },
- { typeof(ushort), UInt16ValueParser.Singleton },
- { typeof(uint), UInt32ValueParser.Singleton },
- { typeof(ulong), UInt64ValueParser.Singleton },
- { typeof(float), FloatValueParser.Singleton },
- { typeof(double), DoubleValueParser.Singleton },
- };
-
- private ValueParserProvider()
- { }
-
- public static ValueParserProvider Default { get; } = new ValueParserProvider();
-
- public IValueParser GetParser(Type type)
- {
- if (_parsers.TryGetValue(type, out var parser))
- {
- return parser;
- }
-
- var typeInfo = type.GetTypeInfo();
-
- if (typeInfo.IsEnum)
- {
- return new EnumParser(type);
- }
-
- if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>))
- {
- var wrappedType = type.GetTypeInfo().GetGenericArguments().First();
-
- if (wrappedType.GetTypeInfo().IsEnum)
- {
- return new NullableValueParser(new EnumParser(wrappedType));
- }
-
- if (_parsers.TryGetValue(wrappedType, out parser))
- {
- return new NullableValueParser(parser);
- }
- }
-
- return parser;
- }
- }
-}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/ArrayParser.cs b/src/CommandLineUtils/Internal/ValueParsers/ArrayParser.cs
index f2e3b595..27a12a1b 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/ArrayParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/ArrayParser.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
-namespace McMaster.Extensions.CommandLineUtils.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
internal class ArrayParser : ICollectionParser
{
diff --git a/src/CommandLineUtils/Internal/ValueParsers/BooleanValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/BooleanValueParser.cs
index 183bf416..49598358 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/BooleanValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/BooleanValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class BooleanValueParser : IValueParser
{
private BooleanValueParser()
@@ -10,11 +12,13 @@ private BooleanValueParser()
public static BooleanValueParser Singleton { get; } = new BooleanValueParser();
+ public Type TargetType { get; } = typeof(bool);
+
public object Parse(string argName, string value)
{
if (!bool.TryParse(value, out var result))
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. Cannot convert '{value}' to a boolean.");
+ throw new FormatException($"Invalid value specified for {argName}. Cannot convert '{value}' to a boolean.");
}
return result;
}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/ByteValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/ByteValueParser.cs
index 16d3ff40..b0951993 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/ByteValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/ByteValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class ByteValueParser : IValueParser
{
private ByteValueParser()
@@ -10,11 +12,13 @@ private ByteValueParser()
public static ByteValueParser Singleton { get; } = new ByteValueParser();
+ public Type TargetType { get; } = typeof(byte);
+
public object Parse(string argName, string value)
{
if (!byte.TryParse(value, out var result))
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. '{value}' is not a valid number.");
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid number.");
}
return result;
}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/DoubleValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/DoubleValueParser.cs
index 66127b10..716200fb 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/DoubleValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/DoubleValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class DoubleValueParser : IValueParser
{
private DoubleValueParser()
@@ -10,11 +12,13 @@ private DoubleValueParser()
public static DoubleValueParser Singleton { get; } = new DoubleValueParser();
+ public Type TargetType { get; } = typeof(double);
+
public object Parse(string argName, string value)
{
if (!double.TryParse(value, out var result))
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. '{value}' is not a valid floating-point number.");
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid floating-point number.");
}
return result;
}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/EnumParser.cs b/src/CommandLineUtils/Internal/ValueParsers/EnumParser.cs
index f02510e7..6503a65b 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/EnumParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/EnumParser.cs
@@ -3,7 +3,7 @@
using System;
-namespace McMaster.Extensions.CommandLineUtils.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
internal class EnumParser : IValueParser
{
@@ -14,6 +14,15 @@ public EnumParser(Type enumType)
_enumType = enumType;
}
+ public Type TargetType
+ {
+ get
+ {
+ // Note: Because Enum's are a special case, this value is never used
+ return _enumType;
+ }
+ }
+
public object Parse(string argName, string value)
{
try
@@ -22,7 +31,7 @@ public object Parse(string argName, string value)
}
catch
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. Allowed values are: {string.Join(", ", Enum.GetNames(_enumType))}.");
+ 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
index c1528737..7a898de5 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/FloatValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/FloatValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class FloatValueParser : IValueParser
{
private FloatValueParser()
@@ -10,11 +12,13 @@ private FloatValueParser()
public static FloatValueParser Singleton { get; } = new FloatValueParser();
+ public Type TargetType { get; } = typeof(float);
+
public object Parse(string argName, string value)
{
if (!float.TryParse(value, out var result))
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. '{value}' is not a valid floating-point number.");
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid floating-point number.");
}
return result;
}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/HashSetParser.cs b/src/CommandLineUtils/Internal/ValueParsers/HashSetParser.cs
index f047e554..4ed37b40 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/HashSetParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/HashSetParser.cs
@@ -5,7 +5,7 @@
using System.Collections.Generic;
using System.Reflection;
-namespace McMaster.Extensions.CommandLineUtils.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
internal class HashSetParser : ICollectionParser
{
diff --git a/src/CommandLineUtils/Internal/ValueParsers/Int16ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/Int16ValueParser.cs
index bbfc35de..522c1b78 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/Int16ValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/Int16ValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class Int16ValueParser : IValueParser
{
private Int16ValueParser()
@@ -10,11 +12,13 @@ private Int16ValueParser()
public static Int16ValueParser Singleton { get; } = new Int16ValueParser();
+ public Type TargetType { get; } = typeof(short);
+
public object Parse(string argName, string value)
{
if (!short.TryParse(value, out var result))
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. '{value}' is not a valid number.");
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid number.");
}
return result;
diff --git a/src/CommandLineUtils/Internal/ValueParsers/Int32ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/Int32ValueParser.cs
index e8b2144e..d9f4ef20 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/Int32ValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/Int32ValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class Int32ValueParser : IValueParser
{
private Int32ValueParser()
@@ -10,11 +12,13 @@ private Int32ValueParser()
public static Int32ValueParser Singleton { get; } = new Int32ValueParser();
+ public Type TargetType { get; } = typeof(int);
+
public object Parse(string argName, string value)
{
if (!int.TryParse(value, out var result))
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. '{value}' is not a valid number.");
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid number.");
}
return result;
}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/Int64ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/Int64ValueParser.cs
index b5e9554c..a00908a9 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/Int64ValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/Int64ValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class Int64ValueParser : IValueParser
{
private Int64ValueParser()
@@ -10,11 +12,13 @@ private Int64ValueParser()
public static Int64ValueParser Singleton { get; } = new Int64ValueParser();
+ public Type TargetType { get; } = typeof(long);
+
public object Parse(string argName, string value)
{
if (!long.TryParse(value, out var result))
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. '{value}' is not a valid number.");
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid number.");
}
return result;
}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/ListParser.cs b/src/CommandLineUtils/Internal/ValueParsers/ListParser.cs
index beb1d2e0..0b1ab932 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/ListParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/ListParser.cs
@@ -5,7 +5,7 @@
using System.Collections;
using System.Collections.Generic;
-namespace McMaster.Extensions.CommandLineUtils.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
internal class ListParser : ICollectionParser
{
diff --git a/src/CommandLineUtils/Internal/ValueParsers/NullableValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/NullableValueParser.cs
index a5091844..b28a9cbd 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/NullableValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/NullableValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class NullableValueParser : IValueParser
{
private readonly IValueParser _wrapped;
@@ -12,6 +14,15 @@ public NullableValueParser(IValueParser boxedParser)
_wrapped = boxedParser;
}
+ public Type TargetType
+ {
+ get
+ {
+ throw new InvalidOperationException($"{nameof(NullableValueParser)} does not have a target type");
+ }
+ }
+
+
public object Parse(string argName, string value)
{
if (string.IsNullOrWhiteSpace(value))
diff --git a/src/CommandLineUtils/Internal/ValueParsers/StringValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/StringValueParser.cs
index 57e1a285..9b6dd980 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/StringValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/StringValueParser.cs
@@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-namespace McMaster.Extensions.CommandLineUtils.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class StringValueParser : IValueParser
{
private StringValueParser()
@@ -11,6 +13,8 @@ private StringValueParser()
public static StringValueParser Singleton { get; } = new StringValueParser();
+ public Type TargetType { get; } = typeof(string);
+
public object Parse(string argName, string value) => value;
}
}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/TupleValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/TupleValueParser.cs
index e8980fea..2795821c 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/TupleValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/TupleValueParser.cs
@@ -3,7 +3,7 @@
using System;
-namespace McMaster.Extensions.CommandLineUtils.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
internal class TupleValueParser : ITupleValueParser
{
diff --git a/src/CommandLineUtils/Internal/ValueParsers/UInt16ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/UInt16ValueParser.cs
index b7ed3c0b..10378f4a 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/UInt16ValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/UInt16ValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class UInt16ValueParser : IValueParser
{
private UInt16ValueParser()
@@ -10,11 +12,13 @@ private UInt16ValueParser()
public static UInt16ValueParser Singleton { get; } = new UInt16ValueParser();
+ public Type TargetType { get; } = typeof(ushort);
+
public object Parse(string argName, string value)
{
if (!ushort.TryParse(value, out var result))
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. '{value}' is not a valid, non-negative number.");
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid, non-negative number.");
}
return result;
}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/UInt32ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/UInt32ValueParser.cs
index 190688a5..fd049c21 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/UInt32ValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/UInt32ValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class UInt32ValueParser : IValueParser
{
private UInt32ValueParser()
@@ -10,11 +12,13 @@ private UInt32ValueParser()
public static UInt32ValueParser Singleton { get; } = new UInt32ValueParser();
+ public Type TargetType { get; } = typeof(uint);
+
public object Parse(string argName, string value)
{
if (!uint.TryParse(value, out var result))
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. '{value}' is not a valid, non-negative number.");
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid, non-negative number.");
}
return result;
}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/UInt64ValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/UInt64ValueParser.cs
index c3b5e5ae..3683f3d9 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/UInt64ValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/UInt64ValueParser.cs
@@ -1,8 +1,10 @@
// 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.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
+ using System;
+
internal class UInt64ValueParser : IValueParser
{
private UInt64ValueParser()
@@ -10,11 +12,13 @@ private UInt64ValueParser()
public static UInt64ValueParser Singleton { get; } = new UInt64ValueParser();
+ public Type TargetType { get; } = typeof(ulong);
+
public object Parse(string argName, string value)
{
if (!ulong.TryParse(value, out var result))
{
- throw new CommandParsingException(null, $"Invalid value specified for {argName}. '{value}' is not a valid, non-negative number.");
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid, non-negative number.");
}
return result;
}
diff --git a/src/CommandLineUtils/Internal/ValueParsers/ValueTupleValueParser.cs b/src/CommandLineUtils/Internal/ValueParsers/ValueTupleValueParser.cs
index 583d0180..fea05086 100644
--- a/src/CommandLineUtils/Internal/ValueParsers/ValueTupleValueParser.cs
+++ b/src/CommandLineUtils/Internal/ValueParsers/ValueTupleValueParser.cs
@@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-namespace McMaster.Extensions.CommandLineUtils.ValueParsers
+namespace McMaster.Extensions.CommandLineUtils.Abstractions
{
internal class ValueTupleValueParser : ITupleValueParser
{
diff --git a/src/CommandLineUtils/Internal/ValueTupleParserProvider.cs b/src/CommandLineUtils/Internal/ValueTupleParserProvider.cs
index f9cd4db4..89e1b336 100644
--- a/src/CommandLineUtils/Internal/ValueTupleParserProvider.cs
+++ b/src/CommandLineUtils/Internal/ValueTupleParserProvider.cs
@@ -3,7 +3,7 @@
using System;
using System.Reflection;
-using McMaster.Extensions.CommandLineUtils.ValueParsers;
+using McMaster.Extensions.CommandLineUtils.Abstractions;
namespace McMaster.Extensions.CommandLineUtils
{
@@ -12,7 +12,7 @@ internal class ValueTupleParserProvider
private ValueTupleParserProvider() { }
public static ValueTupleParserProvider Default { get; } = new ValueTupleParserProvider();
- public ITupleValueParser GetParser(Type type)
+ public ITupleValueParser GetParser(Type type, ValueParserProvider valueParsers)
{
var typeInfo = type.GetTypeInfo();
if (!typeInfo.IsGenericType)
@@ -22,7 +22,7 @@ public ITupleValueParser GetParser(Type type)
var typeDef = typeInfo.GetGenericTypeDefinition();
if (typeDef == typeof(Tuple<,>) && typeInfo.GenericTypeArguments[0] == typeof(bool))
{
- var innerParser = ValueParserProvider.Default.GetParser(typeInfo.GenericTypeArguments[1]);
+ var innerParser = valueParsers.GetParser(typeInfo.GenericTypeArguments[1]);
if (innerParser == null)
{
return null;
@@ -33,7 +33,7 @@ public ITupleValueParser GetParser(Type type)
if (typeDef == typeof(ValueTuple<,>) && typeInfo.GenericTypeArguments[0] == typeof(bool))
{
- var innerParser = ValueParserProvider.Default.GetParser(typeInfo.GenericTypeArguments[1]);
+ var innerParser = valueParsers.GetParser(typeInfo.GenericTypeArguments[1]);
if (innerParser == null)
{
return null;
diff --git a/test/CommandLineUtils.Tests/ValueParserProviderCustomTests.cs b/test/CommandLineUtils.Tests/ValueParserProviderCustomTests.cs
new file mode 100644
index 00000000..745116f1
--- /dev/null
+++ b/test/CommandLineUtils.Tests/ValueParserProviderCustomTests.cs
@@ -0,0 +1,247 @@
+// 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;
+using System.Collections.Generic;
+using Xunit;
+using McMaster.Extensions.CommandLineUtils.Abstractions;
+
+namespace McMaster.Extensions.CommandLineUtils.Tests
+{
+ public class ValueParserProviderCustomTests
+ {
+
+ private class MyDateTimeOffsetParser : IValueParser
+ {
+ public Type TargetType { get; } = typeof(DateTimeOffset);
+
+ public object Parse(string argName, string value)
+ {
+ if (!DateTimeOffset.TryParse(value, out var result))
+ {
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid date time (with offset)");
+ }
+
+ return result;
+ }
+ }
+
+ // scenario: specialized domain value in the format of 1=123.456=abc
+ private class ComplexTupleParser : IValueParser
+ {
+ public Type TargetType { get; } = typeof(ValueTuple?);
+
+ public object Parse(string argName, string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return default(ValueTuple?);
+ }
+
+ var fragments = value.Split('=');
+
+ try
+ {
+ var item1 = double.Parse(fragments[0]);
+ var item2 = double.Parse(fragments[1]);
+ var item3 = fragments[2];
+ return (ValueTuple?)(item1, item2, item3);
+ }
+ catch(Exception ex)
+ {
+ throw new FormatException(
+ $"Invalid value specified for {argName}. '{value} is not a valid time span (with offset)",
+ ex);
+ }
+ }
+ }
+
+ // scenario: for some reason I insist on using thin spaces instead of commas for the thousands delimitters
+ private class MyDoubleParser : IValueParser
+ {
+ // This is a trivial example but rooted in a real standard
+ // https://en.wikipedia.org/wiki/ISO_31-0#Numbers
+ private readonly NumberFormatInfo _iso80000NumberFormatInfo;
+
+ public MyDoubleParser()
+ {
+ this._iso80000NumberFormatInfo = (NumberFormatInfo)CultureInfo.InvariantCulture.NumberFormat.Clone();
+
+ // a thin space
+ this._iso80000NumberFormatInfo.NumberGroupSeparator = "\u2009";
+ }
+
+ public Type TargetType { get; } = typeof(double);
+
+ public object Parse(string argName, string value)
+ {
+ if (!double.TryParse(value, NumberStyles.Number, _iso80000NumberFormatInfo, out var result))
+ {
+ throw new FormatException($"Invalid value specified for {argName}. '{value}' is not a valid ISO80000 double");
+ }
+
+ return result;
+ }
+ }
+
+ private class CustomParserProgram
+ {
+ [Argument(0)]
+ public DateTimeOffset DateTimeOffset { get; }
+
+ [Argument(1)]
+ public double Double { get; }
+
+ [Argument(2)]
+ public ValueTuple? ComplexValue { get; }
+ }
+
+ [Fact]
+ public void CustomParsersCanBeAdded()
+ {
+ var expectedDate = new DateTimeOffset(2018, 02, 16, 21, 30, 33, 45, TimeSpan.FromHours(10));
+ var expectedDouble = 123456.789;
+ ValueTuple? expectedComplexValue = null;
+
+ var app = new CommandLineApplication();
+
+ app.ValueParsers.AddRange(new IValueParser[] { new MyDateTimeOffsetParser(), new ComplexTupleParser() });
+ app.ValueParsers.AddOrReplace(new MyDoubleParser());
+
+ app.Conventions.UseAttributes();
+
+ // We're omitting the third argument to test nullable-ness. The ComplexValue type (with a value) is tested
+ // in the next test
+ var args = new[] { expectedDate.ToString("O"), "123 456.789" };
+ app.Parse(args);
+ var model = app.Model;
+
+ Assert.Equal(expectedDate, model.DateTimeOffset);
+ Assert.Equal(expectedDouble, model.Double);
+ Assert.Equal(expectedComplexValue, model.ComplexValue);
+ }
+
+ private class CustomParserProgramOptions
+ {
+ [Option]
+ public ValueTuple? ComplexValue { get; }
+ }
+
+ [Fact]
+ public void CustomParsersSupportComplexGenericTypes()
+ {
+ ValueTuple? expectedComplexValue = (1, 123.456, "abc");
+
+ var app = new CommandLineApplication();
+
+ app.ValueParsers.Add(new ComplexTupleParser());
+
+ app.Conventions.UseAttributes();
+
+ var args = $"-c=1=123.456=abc";
+ app.Parse(args);
+ var model = app.Model;
+
+ Assert.Equal(expectedComplexValue, model.ComplexValue);
+ }
+
+ [Fact]
+ public void CustomParsersAreAutomaticallySingleValues()
+ {
+ var app = new CommandLineApplication();
+
+ app.ValueParsers.AddRange(new IValueParser[] { new MyDateTimeOffsetParser(), new ComplexTupleParser() });
+ app.ValueParsers.AddOrReplace(new MyDoubleParser());
+
+ var optionMapper = CommandOptionTypeMapper.Default;
+ Assert.Equal(
+ CommandOptionType.SingleValue,
+ optionMapper.GetOptionType(typeof(DateTimeOffset), app.ValueParsers));
+ Assert.Equal(
+ CommandOptionType.SingleValue,
+ optionMapper.GetOptionType(typeof(ValueTuple?), app.ValueParsers));
+ Assert.Equal(
+ CommandOptionType.SingleValue,
+ optionMapper.GetOptionType(typeof(double), app.ValueParsers));
+ }
+
+ private class BadValueParser : IValueParser
+ {
+ public Type TargetType { get; } = null;
+
+ public object Parse(string argName, string value)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Fact]
+ public void ThrowsIfNoType()
+ {
+ var ex = Assert.Throws(
+ () =>
+ {
+ var app = new CommandLineApplication();
+ app.ValueParsers.Add(new BadValueParser());
+ });
+
+ Assert.Contains("TargetType", ex.Message);
+ }
+
+ [Fact]
+ public void ThrowsIfAlreadyRegistered()
+ {
+ var ex = Assert.Throws(
+ () =>
+ {
+ var app = new CommandLineApplication();
+ app.ValueParsers.Add(new ComplexTupleParser());
+ app.ValueParsers.Add(new ComplexTupleParser());
+ });
+
+ Assert.Contains(
+ "Value parser provider for type 'System.ValueTuple`3[System.Int32,System.Double,System.String]' already exists.",
+ ex.Message);
+ }
+
+ [Fact]
+ public void AddThrowsIfNullParser()
+ {
+ var ex = Assert.Throws(
+ () =>
+ {
+ var app = new CommandLineApplication();
+ app.ValueParsers.Add(null);
+ });
+
+ Assert.Contains("parser", ex.Message);
+ }
+
+ [Fact]
+ public void AddRangeThrowsIfNullCollection()
+ {
+ var ex = Assert.Throws(
+ () =>
+ {
+ var app = new CommandLineApplication();
+ app.ValueParsers.AddRange(null);
+ });
+
+ Assert.Contains("parsers", ex.Message);
+ }
+
+ [Fact]
+ public void AddOrReplaceThrowsIfNullparser()
+ {
+ var ex = Assert.Throws(
+ () =>
+ {
+ var app = new CommandLineApplication();
+ app.ValueParsers.AddOrReplace(null);
+ });
+
+ Assert.Contains("parser", ex.Message);
+ }
+ }
+}