diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index 3a122424..b6921d82 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -17,6 +17,7 @@ public static ParserResult Build( Func, IEnumerable, Result, Error>> tokenizer, IEnumerable arguments, StringComparer nameComparer, + bool ignoreValueCase, CultureInfo parsingCulture, IEnumerable nonFatalErrors) { @@ -57,14 +58,14 @@ public static ParserResult Build( OptionMapper.MapValues( (from pt in specProps where pt.Specification.IsOption() select pt), optionsPartition, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture), + (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase), nameComparer); var valueSpecPropsResult = ValueMapper.MapValues( (from pt in specProps where pt.Specification.IsValue() select pt), valuesPartition, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture)); + (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase)); var missingValueErrors = from token in errorsPartition select diff --git a/src/CommandLine/Core/InstanceChooser.cs b/src/CommandLine/Core/InstanceChooser.cs index fa47b965..f68216b1 100644 --- a/src/CommandLine/Core/InstanceChooser.cs +++ b/src/CommandLine/Core/InstanceChooser.cs @@ -60,6 +60,7 @@ private static ParserResult MatchVerb( tokenizer, arguments.Skip(1), nameComparer, + false, parsingCulture, nonFatalErrors) : MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First())); diff --git a/src/CommandLine/Core/TypeConverter.cs b/src/CommandLine/Core/TypeConverter.cs index 66b9ab67..18df00a0 100644 --- a/src/CommandLine/Core/TypeConverter.cs +++ b/src/CommandLine/Core/TypeConverter.cs @@ -12,14 +12,14 @@ namespace CommandLine.Core { static class TypeConverter { - public static Maybe ChangeType(IEnumerable values, Type conversionType, bool scalar, CultureInfo conversionCulture) + public static Maybe ChangeType(IEnumerable values, Type conversionType, bool scalar, CultureInfo conversionCulture, bool ignoreValueCase) { return scalar - ? ChangeTypeScalar(values.Single(), conversionType, conversionCulture) - : ChangeTypeSequence(values, conversionType, conversionCulture); + ? ChangeTypeScalar(values.Single(), conversionType, conversionCulture, ignoreValueCase) + : ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase); } - private static Maybe ChangeTypeSequence(IEnumerable values, Type conversionType, CultureInfo conversionCulture) + private static Maybe ChangeTypeSequence(IEnumerable values, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase) { var type = conversionType.GetGenericArguments() @@ -28,22 +28,22 @@ private static Maybe ChangeTypeSequence(IEnumerable values, Type .FromJustOrFail( new ApplicationException("Non scalar properties should be sequence of type IEnumerable.")); - var converted = values.Select(value => ChangeTypeScalar(value, type, conversionCulture)); + var converted = values.Select(value => ChangeTypeScalar(value, type, conversionCulture, ignoreValueCase)); return converted.Any(a => a.MatchNothing()) ? Maybe.Nothing() : Maybe.Just(converted.Select(c => ((Just)c).Value).ToUntypedArray(type)); } - private static Maybe ChangeTypeScalar(string value, Type conversionType, CultureInfo conversionCulture) + private static Maybe ChangeTypeScalar(string value, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase) { - var result = ChangeTypeScalarImpl(value, conversionType, conversionCulture); + var result = ChangeTypeScalarImpl(value, conversionType, conversionCulture, ignoreValueCase); result.Match((_,__) => { }, e => e.First().RethrowWhenAbsentIn( new[] { typeof(InvalidCastException), typeof(FormatException), typeof(OverflowException) })); return result.ToMaybe(); } - private static Result ChangeTypeScalarImpl(string value, Type conversionType, CultureInfo conversionCulture) + private static Result ChangeTypeScalarImpl(string value, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase) { Func changeType = () => { @@ -72,7 +72,7 @@ private static Result ChangeTypeScalarImpl(string value, Type return value.IsBooleanString() ? value.ToBoolean() : conversionType.IsEnum - ? value.ToEnum(conversionType) : safeChangeType(); + ? value.ToEnum(conversionType, ignoreValueCase) : safeChangeType(); }; Func makeType = () => @@ -94,12 +94,12 @@ private static Result ChangeTypeScalarImpl(string value, Type : makeType); } - private static object ToEnum(this string value, Type conversionType) + private static object ToEnum(this string value, Type conversionType, bool ignoreValueCase) { object parsedValue; try { - parsedValue = Enum.Parse(conversionType, value); + parsedValue = Enum.Parse(conversionType, value, ignoreValueCase); } catch (ArgumentException) { diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index deb629fc..f2cae77b 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -97,6 +97,7 @@ public ParserResult ParseArguments(IEnumerable args) (arguments, optionSpecs) => Tokenize(arguments, optionSpecs, settings), args, settings.NameComparer, + settings.CaseInsensitiveEnumValues, settings.ParsingCulture, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); @@ -125,6 +126,7 @@ public ParserResult ParseArguments(Func factory, IEnumerable ar (arguments, optionSpecs) => Tokenize(arguments, optionSpecs, settings), args, settings.NameComparer, + settings.CaseInsensitiveEnumValues, settings.ParsingCulture, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 87b92e1f..1777f05f 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -15,6 +15,7 @@ public class ParserSettings : IDisposable { private bool disposed; private bool caseSensitive; + private bool caseInsensitiveEnumValues; private TextWriter helpWriter; private bool ignoreUnknownArguments; private CultureInfo parsingCulture; @@ -26,6 +27,7 @@ public class ParserSettings : IDisposable public ParserSettings() { caseSensitive = true; + caseInsensitiveEnumValues = false; parsingCulture = CultureInfo.InvariantCulture; } @@ -48,6 +50,16 @@ public bool CaseSensitive set { PopsicleSetter.Set(Consumed, ref caseSensitive, value); } } + /// + /// Gets or sets a value indicating whether perform case sensitive comparisons of values. + /// Note that case insensitivity only applies to values, not the parameters. + /// + public bool CaseInsensitiveEnumValues + { + get { return caseInsensitiveEnumValues; } + set { PopsicleSetter.Set(Consumed, ref caseInsensitiveEnumValues, value); } + } + /// /// Gets or sets the culture used when parsing arguments to typed properties. /// diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index f015a27e..fa098e43 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -25,6 +25,20 @@ private static ParserResult InvokeBuild(string[] arguments) (args, optionSpecs) => Tokenizer.ConfigureTokenizer(StringComparer.Ordinal, false, false)(args, optionSpecs), arguments, StringComparer.Ordinal, + false, + CultureInfo.InvariantCulture, + Enumerable.Empty()); + } + + private static ParserResult InvokeBuildEnumValuesCaseIgnore(string[] arguments) + where T : new() + { + return InstanceBuilder.Build( + Maybe.Just>(() => new T()), + (args, optionSpecs) => Tokenizer.ConfigureTokenizer(StringComparer.Ordinal, false, false)(args, optionSpecs), + arguments, + StringComparer.Ordinal, + true, CultureInfo.InvariantCulture, Enumerable.Empty()); } @@ -36,6 +50,7 @@ private static ParserResult InvokeBuildImmutable(string[] arguments) (args, optionSpecs) => Tokenizer.ConfigureTokenizer(StringComparer.Ordinal, false, false)(args, optionSpecs), arguments, StringComparer.Ordinal, + false, CultureInfo.InvariantCulture, Enumerable.Empty()); } @@ -259,6 +274,27 @@ public void Parse_enum_value(string[] arguments, Colors expected) // Teardown } + [Theory] + [InlineData(new[] { "--colors", "red" }, Colors.Red)] + [InlineData(new[] { "--colors", "green" }, Colors.Green)] + [InlineData(new[] { "--colors", "blue" }, Colors.Blue)] + [InlineData(new[] { "--colors", "0" }, Colors.Red)] + [InlineData(new[] { "--colors", "1" }, Colors.Green)] + [InlineData(new[] { "--colors", "2" }, Colors.Blue)] + public void Parse_enum_value_ignore_case(string[] arguments, Colors expected) + { + // Fixture setup in attribute + + // Exercize system + var result = InvokeBuildEnumValuesCaseIgnore( + arguments); + + // Verify outcome + expected.ShouldBeEquivalentTo(((Parsed)result).Value.Colors); + + // Teardown + } + [Fact] public void Parse_enum_value_with_wrong_index_generates_BadFormatConversionError() { @@ -411,6 +447,7 @@ public void Double_dash_force_subsequent_arguments_as_values() args => Tokenizer.Tokenize(args, name => NameLookup.Contains(name, optionSpecs, StringComparer.Ordinal))), arguments, StringComparer.Ordinal, + false, CultureInfo.InvariantCulture, Enumerable.Empty()); diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index 661c798a..5a786677 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -34,7 +34,7 @@ public void Map_boolean_switch_creates_boolean_value() var result = OptionMapper.MapValues( specProps.Where(pt => pt.Specification.IsOption()), tokenPartitions, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture), + (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false), StringComparer.InvariantCulture); // Verify outcome