From 817c42ed7c22840f38bc497738bddee2643f14c2 Mon Sep 17 00:00:00 2001 From: Katamanov Andrey Date: Wed, 10 Feb 2016 00:26:55 +0500 Subject: [PATCH 1/3] add CaseInsensitiveValues param to ParserSettings for parsing enum values in caseInsensetive way --- src/CommandLine/Core/InstanceBuilder.cs | 5 +++-- src/CommandLine/Core/InstanceChooser.cs | 1 + src/CommandLine/Core/TypeConverter.cs | 22 +++++++++---------- src/CommandLine/Parser.cs | 2 ++ src/CommandLine/ParserSettings.cs | 12 ++++++++++ .../Unit/Core/InstanceBuilderTests.cs | 3 +++ .../Unit/Core/OptionMapperTests.cs | 2 +- 7 files changed, 33 insertions(+), 14 deletions(-) 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..c16f9569 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.CaseInsensitiveValues, 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.CaseInsensitiveValues, settings.ParsingCulture, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 87b92e1f..88fe246d 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 caseInsensitiveValues; private TextWriter helpWriter; private bool ignoreUnknownArguments; private CultureInfo parsingCulture; @@ -26,6 +27,7 @@ public class ParserSettings : IDisposable public ParserSettings() { caseSensitive = true; + caseInsensitiveValues = 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 CaseInsensitiveValues + { + get { return caseInsensitiveValues; } + set { PopsicleSetter.Set(Consumed, ref caseInsensitiveValues, 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..92421b30 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -25,6 +25,7 @@ private static ParserResult InvokeBuild(string[] arguments) (args, optionSpecs) => Tokenizer.ConfigureTokenizer(StringComparer.Ordinal, false, false)(args, optionSpecs), arguments, StringComparer.Ordinal, + false, CultureInfo.InvariantCulture, Enumerable.Empty()); } @@ -36,6 +37,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()); } @@ -411,6 +413,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 From afd1d5ace909d12445a4767babb740af28c5f37b Mon Sep 17 00:00:00 2001 From: Katamanov Andrey Date: Fri, 12 Feb 2016 01:34:13 +0500 Subject: [PATCH 2/3] renamed CaseInsensitiveValues to CaseInsensitiveEnumValues --- src/CommandLine/Parser.cs | 4 ++-- src/CommandLine/ParserSettings.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index c16f9569..f2cae77b 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -97,7 +97,7 @@ public ParserResult ParseArguments(IEnumerable args) (arguments, optionSpecs) => Tokenize(arguments, optionSpecs, settings), args, settings.NameComparer, - settings.CaseInsensitiveValues, + settings.CaseInsensitiveEnumValues, settings.ParsingCulture, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); @@ -126,7 +126,7 @@ public ParserResult ParseArguments(Func factory, IEnumerable ar (arguments, optionSpecs) => Tokenize(arguments, optionSpecs, settings), args, settings.NameComparer, - settings.CaseInsensitiveValues, + settings.CaseInsensitiveEnumValues, settings.ParsingCulture, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 88fe246d..1777f05f 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -15,7 +15,7 @@ public class ParserSettings : IDisposable { private bool disposed; private bool caseSensitive; - private bool caseInsensitiveValues; + private bool caseInsensitiveEnumValues; private TextWriter helpWriter; private bool ignoreUnknownArguments; private CultureInfo parsingCulture; @@ -27,7 +27,7 @@ public class ParserSettings : IDisposable public ParserSettings() { caseSensitive = true; - caseInsensitiveValues = false; + caseInsensitiveEnumValues = false; parsingCulture = CultureInfo.InvariantCulture; } @@ -54,10 +54,10 @@ public bool CaseSensitive /// 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 CaseInsensitiveValues + public bool CaseInsensitiveEnumValues { - get { return caseInsensitiveValues; } - set { PopsicleSetter.Set(Consumed, ref caseInsensitiveValues, value); } + get { return caseInsensitiveEnumValues; } + set { PopsicleSetter.Set(Consumed, ref caseInsensitiveEnumValues, value); } } /// From 07d7344c842a1da8d5ed361fc79fc461e18855f9 Mon Sep 17 00:00:00 2001 From: Katamanov Andrey Date: Mon, 15 Feb 2016 19:18:15 +0500 Subject: [PATCH 3/3] add tests for CaseInsensitiveEnumValues param --- .../Unit/Core/InstanceBuilderTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 92421b30..fa098e43 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -30,6 +30,19 @@ private static ParserResult InvokeBuild(string[] arguments) 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()); + } + private static ParserResult InvokeBuildImmutable(string[] arguments) { return InstanceBuilder.Build( @@ -261,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() {