Skip to content

Commit

Permalink
Add unit tests for FlagCounter and implement it
Browse files Browse the repository at this point in the history
Now things like -vvv for triple-verbose can be done in user code.
  • Loading branch information
rmunn committed Jun 2, 2020
1 parent 418f6e2 commit 1e791b5
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 127 deletions.
4 changes: 2 additions & 2 deletions src/CommandLine/Core/InstanceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@ public static ParserResult<T> Build<T>(
OptionMapper.MapValues(
(from pt in specProps where pt.Specification.IsOption() select pt),
optionsPartition,
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase),
(vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, parsingCulture, ignoreValueCase),
nameComparer);
var valueSpecPropsResult =
ValueMapper.MapValues(
(from pt in specProps where pt.Specification.IsValue() orderby ((ValueSpecification)pt.Specification).Index select pt),
valuesPartition,
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase));
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, false, parsingCulture, ignoreValueCase));
var missingValueErrors = from token in errorsPartition
select
Expand Down
8 changes: 5 additions & 3 deletions src/CommandLine/Core/OptionMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static Result<
MapValues(
IEnumerable<SpecificationProperty> propertyTuples,
IEnumerable<KeyValuePair<string, IEnumerable<string>>> options,
Func<IEnumerable<string>, Type, bool, Maybe<object>> converter,
Func<IEnumerable<string>, Type, bool, bool, Maybe<object>> converter,
StringComparer comparer)
{
var sequencesAndErrors = propertyTuples
Expand All @@ -28,7 +28,7 @@ public static Result<
if (matched.IsJust())
{
var matches = matched.GetValueOrDefault(Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>());
var values = new HashSet<string>();
var values = new List<string>();
foreach (var kvp in matches)
{
foreach (var value in kvp.Value)
Expand All @@ -37,7 +37,9 @@ public static Result<
}
}
return converter(values, pt.Property.PropertyType, pt.Specification.TargetType != TargetType.Sequence)
bool isFlag = pt.Specification.Tag == SpecificationType.Option && ((OptionSpecification)pt.Specification).FlagCounter;
return converter(values, isFlag ? typeof(bool) : pt.Property.PropertyType, pt.Specification.TargetType != TargetType.Sequence, isFlag)
.Select(value => Tuple.Create(pt.WithValue(Maybe.Just(value)), Maybe.Nothing<Error>()))
.GetValueOrDefault(
Tuple.Create<SpecificationProperty, Maybe<Error>>(
Expand Down
2 changes: 1 addition & 1 deletion src/CommandLine/Core/OptionSpecification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public OptionSpecification(string shortName, string longName, bool required, str
char separator, Maybe<object> defaultValue, string helpText, string metaValue, IEnumerable<string> enumValues,
Type conversionType, TargetType targetType, string group, bool flagCounter, bool hidden)
: base(SpecificationType.Option,
required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden)
required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, conversionType == typeof(int) && flagCounter ? TargetType.Switch : targetType, hidden)
{
this.shortName = shortName;
this.longName = longName;
Expand Down
18 changes: 14 additions & 4 deletions src/CommandLine/Core/TypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ namespace CommandLine.Core
{
static class TypeConverter
{
public static Maybe<object> ChangeType(IEnumerable<string> values, Type conversionType, bool scalar, CultureInfo conversionCulture, bool ignoreValueCase)
public static Maybe<object> ChangeType(IEnumerable<string> values, Type conversionType, bool scalar, bool isFlag, CultureInfo conversionCulture, bool ignoreValueCase)
{
return scalar
? ChangeTypeScalar(values.Last(), conversionType, conversionCulture, ignoreValueCase)
: ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase);
return isFlag
? ChangeTypeFlagCounter(values, conversionType, conversionCulture, ignoreValueCase)
: scalar
? ChangeTypeScalar(values.Last(), conversionType, conversionCulture, ignoreValueCase)
: ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase);
}

private static Maybe<object> ChangeTypeSequence(IEnumerable<string> values, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase)
Expand Down Expand Up @@ -46,6 +48,14 @@ private static Maybe<object> ChangeTypeScalar(string value, Type conversionType,
return result.ToMaybe();
}

private static Maybe<object> ChangeTypeFlagCounter(IEnumerable<string> values, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase)
{
var converted = values.Select(value => ChangeTypeScalar(value, typeof(bool), conversionCulture, ignoreValueCase));
return converted.Any(maybe => maybe.MatchNothing())
? Maybe.Nothing<object>()
: Maybe.Just((object)converted.Count(value => value.IsJust()));
}

private static object ConvertString(string value, Type type, CultureInfo conversionCulture)
{
try
Expand Down
13 changes: 13 additions & 0 deletions tests/CommandLine.Tests/Fakes/Options_With_FlagCounter_Switches.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information.

namespace CommandLine.Tests.Fakes
{
public class Options_With_FlagCounter_Switches
{
[Option('v', FlagCounter=true)]
public int Verbose { get; set; }

[Option('s', FlagCounter=true)]
public int Silent { get; set; }
}
}
6 changes: 3 additions & 3 deletions tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,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, false),
(vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, CultureInfo.InvariantCulture, false),
StringComparer.Ordinal
);

Expand Down Expand Up @@ -72,7 +72,7 @@ public void Map_with_multi_instance_scalar()
var result = OptionMapper.MapValues(
specProps.Where(pt => pt.Specification.IsOption()),
tokenPartitions,
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false),
(vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, CultureInfo.InvariantCulture, false),
StringComparer.Ordinal);

var property = result.SucceededWith().Single();
Expand Down Expand Up @@ -101,7 +101,7 @@ public void Map_with_multi_instance_sequence()
var result = OptionMapper.MapValues(
specProps.Where(pt => pt.Specification.IsOption()),
tokenPartitions,
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false),
(vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, CultureInfo.InvariantCulture, false),
StringComparer.Ordinal);

var property = result.SucceededWith().Single();
Expand Down
230 changes: 116 additions & 114 deletions tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs
Original file line number Diff line number Diff line change
@@ -1,114 +1,116 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Xunit;
using FluentAssertions;
using CSharpx;
using CommandLine.Core;

namespace CommandLine.Tests.Unit.Core
{
public class TypeConverterTests
{
enum TestEnum
{
ValueA = 1,
ValueB = 2
}

[Theory]
[MemberData(nameof(ChangeType_scalars_source))]
public void ChangeType_scalars(string testValue, Type destinationType, bool expectFail, object expectedResult)
{
Maybe<object> result = TypeConverter.ChangeType(new[] {testValue}, destinationType, true, CultureInfo.InvariantCulture, true);

if (expectFail)
{
result.MatchNothing().Should().BeTrue("should fail parsing");
}
else
{
result.MatchJust(out object matchedValue).Should().BeTrue("should parse successfully");
Assert.Equal(matchedValue, expectedResult);
}
}

[Fact]
public void ChangeType_Scalar_LastOneWins()
{
var values = new[] { "100", "200", "300", "400", "500" };
var result = TypeConverter.ChangeType(values, typeof(int), true, CultureInfo.InvariantCulture, true);
result.MatchJust(out var matchedValue).Should().BeTrue("should parse successfully");
Assert.Equal(500, matchedValue);

}

public static IEnumerable<object[]> ChangeType_scalars_source
{
get
{
return new[]
{
new object[] {"1", typeof (int), false, 1},
new object[] {"0", typeof (int), false, 0},
new object[] {"-1", typeof (int), false, -1},
new object[] {"abcd", typeof (int), true, null},
new object[] {"1.0", typeof (int), true, null},
new object[] {int.MaxValue.ToString(), typeof (int), false, int.MaxValue},
new object[] {int.MinValue.ToString(), typeof (int), false, int.MinValue},
new object[] {((long) int.MaxValue + 1).ToString(), typeof (int), true, null},
new object[] {((long) int.MinValue - 1).ToString(), typeof (int), true, null},

new object[] {"1", typeof (uint), false, (uint) 1},
// new object[] {"0", typeof (uint), false, (uint) 0}, //cause warning: Skipping test case with duplicate ID
// new object[] {"-1", typeof (uint), true, null}, //cause warning: Skipping test case with duplicate ID
new object[] {uint.MaxValue.ToString(), typeof (uint), false, uint.MaxValue},
new object[] {uint.MinValue.ToString(), typeof (uint), false, uint.MinValue},
new object[] {((long) uint.MaxValue + 1).ToString(), typeof (uint), true, null},
new object[] {((long) uint.MinValue - 1).ToString(), typeof (uint), true, null},

new object[] {"true", typeof (bool), false, true},
new object[] {"True", typeof (bool), false, true},
new object[] {"TRUE", typeof (bool), false, true},
new object[] {"false", typeof (bool), false, false},
new object[] {"False", typeof (bool), false, false},
new object[] {"FALSE", typeof (bool), false, false},
new object[] {"abcd", typeof (bool), true, null},
new object[] {"0", typeof (bool), true, null},
new object[] {"1", typeof (bool), true, null},

new object[] {"1.0", typeof (float), false, 1.0f},
new object[] {"0.0", typeof (float), false, 0.0f},
new object[] {"-1.0", typeof (float), false, -1.0f},
new object[] {"abcd", typeof (float), true, null},

new object[] {"1.0", typeof (double), false, 1.0},
new object[] {"0.0", typeof (double), false, 0.0},
new object[] {"-1.0", typeof (double), false, -1.0},
new object[] {"abcd", typeof (double), true, null},

new object[] {"1.0", typeof (decimal), false, 1.0m},
new object[] {"0.0", typeof (decimal), false, 0.0m},
new object[] {"-1.0", typeof (decimal), false, -1.0m},
new object[] {"-1.123456", typeof (decimal), false, -1.123456m},
new object[] {"abcd", typeof (decimal), true, null},

new object[] {"", typeof (string), false, ""},
new object[] {"abcd", typeof (string), false, "abcd"},

new object[] {"ValueA", typeof (TestEnum), false, TestEnum.ValueA},
new object[] {"VALUEA", typeof (TestEnum), false, TestEnum.ValueA},
new object[] {"ValueB", typeof(TestEnum), false, TestEnum.ValueB},
new object[] {((int) TestEnum.ValueA).ToString(), typeof (TestEnum), false, TestEnum.ValueA},
new object[] {((int) TestEnum.ValueB).ToString(), typeof (TestEnum), false, TestEnum.ValueB},
new object[] {((int) TestEnum.ValueB + 1).ToString(), typeof (TestEnum), true, null},
new object[] {((int) TestEnum.ValueA - 1).ToString(), typeof (TestEnum), true, null},

// Failed before #339
new object[] {"false", typeof (int), true, 0},
new object[] {"true", typeof (int), true, 0}
};
}
}
}
}
using System;
using System.Collections.Generic;
using System.Globalization;
using Xunit;
using FluentAssertions;
using CSharpx;
using CommandLine.Core;

namespace CommandLine.Tests.Unit.Core
{
public class TypeConverterTests
{
enum TestEnum
{
ValueA = 1,
ValueB = 2
}

[Theory]
[MemberData(nameof(ChangeType_scalars_source))]
public void ChangeType_scalars(string testValue, Type destinationType, bool expectFail, object expectedResult)
{
Maybe<object> result = TypeConverter.ChangeType(new[] {testValue}, destinationType, true, false, CultureInfo.InvariantCulture, true);

if (expectFail)
{
result.MatchNothing().Should().BeTrue("should fail parsing");
}
else
{
result.MatchJust(out object matchedValue).Should().BeTrue("should parse successfully");
Assert.Equal(matchedValue, expectedResult);
}
}

[Fact]
public void ChangeType_Scalar_LastOneWins()
{
var values = new[] { "100", "200", "300", "400", "500" };
var result = TypeConverter.ChangeType(values, typeof(int), true, false, CultureInfo.InvariantCulture, true);
result.MatchJust(out var matchedValue).Should().BeTrue("should parse successfully");
Assert.Equal(500, matchedValue);

}

// TODO: Write test for TypeConverter.ChangeType when isFlag = true

public static IEnumerable<object[]> ChangeType_scalars_source
{
get
{
return new[]
{
new object[] {"1", typeof (int), false, 1},
new object[] {"0", typeof (int), false, 0},
new object[] {"-1", typeof (int), false, -1},
new object[] {"abcd", typeof (int), true, null},
new object[] {"1.0", typeof (int), true, null},
new object[] {int.MaxValue.ToString(), typeof (int), false, int.MaxValue},
new object[] {int.MinValue.ToString(), typeof (int), false, int.MinValue},
new object[] {((long) int.MaxValue + 1).ToString(), typeof (int), true, null},
new object[] {((long) int.MinValue - 1).ToString(), typeof (int), true, null},

new object[] {"1", typeof (uint), false, (uint) 1},
// new object[] {"0", typeof (uint), false, (uint) 0}, //cause warning: Skipping test case with duplicate ID
// new object[] {"-1", typeof (uint), true, null}, //cause warning: Skipping test case with duplicate ID
new object[] {uint.MaxValue.ToString(), typeof (uint), false, uint.MaxValue},
new object[] {uint.MinValue.ToString(), typeof (uint), false, uint.MinValue},
new object[] {((long) uint.MaxValue + 1).ToString(), typeof (uint), true, null},
new object[] {((long) uint.MinValue - 1).ToString(), typeof (uint), true, null},

new object[] {"true", typeof (bool), false, true},
new object[] {"True", typeof (bool), false, true},
new object[] {"TRUE", typeof (bool), false, true},
new object[] {"false", typeof (bool), false, false},
new object[] {"False", typeof (bool), false, false},
new object[] {"FALSE", typeof (bool), false, false},
new object[] {"abcd", typeof (bool), true, null},
new object[] {"0", typeof (bool), true, null},
new object[] {"1", typeof (bool), true, null},

new object[] {"1.0", typeof (float), false, 1.0f},
new object[] {"0.0", typeof (float), false, 0.0f},
new object[] {"-1.0", typeof (float), false, -1.0f},
new object[] {"abcd", typeof (float), true, null},

new object[] {"1.0", typeof (double), false, 1.0},
new object[] {"0.0", typeof (double), false, 0.0},
new object[] {"-1.0", typeof (double), false, -1.0},
new object[] {"abcd", typeof (double), true, null},

new object[] {"1.0", typeof (decimal), false, 1.0m},
new object[] {"0.0", typeof (decimal), false, 0.0m},
new object[] {"-1.0", typeof (decimal), false, -1.0m},
new object[] {"-1.123456", typeof (decimal), false, -1.123456m},
new object[] {"abcd", typeof (decimal), true, null},

new object[] {"", typeof (string), false, ""},
new object[] {"abcd", typeof (string), false, "abcd"},

new object[] {"ValueA", typeof (TestEnum), false, TestEnum.ValueA},
new object[] {"VALUEA", typeof (TestEnum), false, TestEnum.ValueA},
new object[] {"ValueB", typeof(TestEnum), false, TestEnum.ValueB},
new object[] {((int) TestEnum.ValueA).ToString(), typeof (TestEnum), false, TestEnum.ValueA},
new object[] {((int) TestEnum.ValueB).ToString(), typeof (TestEnum), false, TestEnum.ValueB},
new object[] {((int) TestEnum.ValueB + 1).ToString(), typeof (TestEnum), true, null},
new object[] {((int) TestEnum.ValueA - 1).ToString(), typeof (TestEnum), true, null},

// Failed before #339
new object[] {"false", typeof (int), true, 0},
new object[] {"true", typeof (int), true, 0}
};
}
}
}
}
Loading

0 comments on commit 1e791b5

Please sign in to comment.