Skip to content

Commit 1e791b5

Browse files
committed
Add unit tests for FlagCounter and implement it
Now things like -vvv for triple-verbose can be done in user code.
1 parent 418f6e2 commit 1e791b5

File tree

8 files changed

+184
-127
lines changed

8 files changed

+184
-127
lines changed

src/CommandLine/Core/InstanceBuilder.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ public static ParserResult<T> Build<T>(
8989
OptionMapper.MapValues(
9090
(from pt in specProps where pt.Specification.IsOption() select pt),
9191
optionsPartition,
92-
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase),
92+
(vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, parsingCulture, ignoreValueCase),
9393
nameComparer);
9494

9595
var valueSpecPropsResult =
9696
ValueMapper.MapValues(
9797
(from pt in specProps where pt.Specification.IsValue() orderby ((ValueSpecification)pt.Specification).Index select pt),
9898
valuesPartition,
99-
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase));
99+
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, false, parsingCulture, ignoreValueCase));
100100

101101
var missingValueErrors = from token in errorsPartition
102102
select

src/CommandLine/Core/OptionMapper.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static Result<
1515
MapValues(
1616
IEnumerable<SpecificationProperty> propertyTuples,
1717
IEnumerable<KeyValuePair<string, IEnumerable<string>>> options,
18-
Func<IEnumerable<string>, Type, bool, Maybe<object>> converter,
18+
Func<IEnumerable<string>, Type, bool, bool, Maybe<object>> converter,
1919
StringComparer comparer)
2020
{
2121
var sequencesAndErrors = propertyTuples
@@ -28,7 +28,7 @@ public static Result<
2828
if (matched.IsJust())
2929
{
3030
var matches = matched.GetValueOrDefault(Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>());
31-
var values = new HashSet<string>();
31+
var values = new List<string>();
3232
foreach (var kvp in matches)
3333
{
3434
foreach (var value in kvp.Value)
@@ -37,7 +37,9 @@ public static Result<
3737
}
3838
}
3939

40-
return converter(values, pt.Property.PropertyType, pt.Specification.TargetType != TargetType.Sequence)
40+
bool isFlag = pt.Specification.Tag == SpecificationType.Option && ((OptionSpecification)pt.Specification).FlagCounter;
41+
42+
return converter(values, isFlag ? typeof(bool) : pt.Property.PropertyType, pt.Specification.TargetType != TargetType.Sequence, isFlag)
4143
.Select(value => Tuple.Create(pt.WithValue(Maybe.Just(value)), Maybe.Nothing<Error>()))
4244
.GetValueOrDefault(
4345
Tuple.Create<SpecificationProperty, Maybe<Error>>(

src/CommandLine/Core/OptionSpecification.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public OptionSpecification(string shortName, string longName, bool required, str
2020
char separator, Maybe<object> defaultValue, string helpText, string metaValue, IEnumerable<string> enumValues,
2121
Type conversionType, TargetType targetType, string group, bool flagCounter, bool hidden)
2222
: base(SpecificationType.Option,
23-
required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden)
23+
required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, conversionType == typeof(int) && flagCounter ? TargetType.Switch : targetType, hidden)
2424
{
2525
this.shortName = shortName;
2626
this.longName = longName;

src/CommandLine/Core/TypeConverter.cs

+14-4
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ namespace CommandLine.Core
1313
{
1414
static class TypeConverter
1515
{
16-
public static Maybe<object> ChangeType(IEnumerable<string> values, Type conversionType, bool scalar, CultureInfo conversionCulture, bool ignoreValueCase)
16+
public static Maybe<object> ChangeType(IEnumerable<string> values, Type conversionType, bool scalar, bool isFlag, CultureInfo conversionCulture, bool ignoreValueCase)
1717
{
18-
return scalar
19-
? ChangeTypeScalar(values.Last(), conversionType, conversionCulture, ignoreValueCase)
20-
: ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase);
18+
return isFlag
19+
? ChangeTypeFlagCounter(values, conversionType, conversionCulture, ignoreValueCase)
20+
: scalar
21+
? ChangeTypeScalar(values.Last(), conversionType, conversionCulture, ignoreValueCase)
22+
: ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase);
2123
}
2224

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

51+
private static Maybe<object> ChangeTypeFlagCounter(IEnumerable<string> values, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase)
52+
{
53+
var converted = values.Select(value => ChangeTypeScalar(value, typeof(bool), conversionCulture, ignoreValueCase));
54+
return converted.Any(maybe => maybe.MatchNothing())
55+
? Maybe.Nothing<object>()
56+
: Maybe.Just((object)converted.Count(value => value.IsJust()));
57+
}
58+
4959
private static object ConvertString(string value, Type type, CultureInfo conversionCulture)
5060
{
5161
try
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information.
2+
3+
namespace CommandLine.Tests.Fakes
4+
{
5+
public class Options_With_FlagCounter_Switches
6+
{
7+
[Option('v', FlagCounter=true)]
8+
public int Verbose { get; set; }
9+
10+
[Option('s', FlagCounter=true)]
11+
public int Silent { get; set; }
12+
}
13+
}

tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void Map_boolean_switch_creates_boolean_value()
3737
var result = OptionMapper.MapValues(
3838
specProps.Where(pt => pt.Specification.IsOption()),
3939
tokenPartitions,
40-
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false),
40+
(vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, CultureInfo.InvariantCulture, false),
4141
StringComparer.Ordinal
4242
);
4343

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

7878
var property = result.SucceededWith().Single();
@@ -101,7 +101,7 @@ public void Map_with_multi_instance_sequence()
101101
var result = OptionMapper.MapValues(
102102
specProps.Where(pt => pt.Specification.IsOption()),
103103
tokenPartitions,
104-
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false),
104+
(vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, CultureInfo.InvariantCulture, false),
105105
StringComparer.Ordinal);
106106

107107
var property = result.SucceededWith().Single();
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,116 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Globalization;
4-
using Xunit;
5-
using FluentAssertions;
6-
using CSharpx;
7-
using CommandLine.Core;
8-
9-
namespace CommandLine.Tests.Unit.Core
10-
{
11-
public class TypeConverterTests
12-
{
13-
enum TestEnum
14-
{
15-
ValueA = 1,
16-
ValueB = 2
17-
}
18-
19-
[Theory]
20-
[MemberData(nameof(ChangeType_scalars_source))]
21-
public void ChangeType_scalars(string testValue, Type destinationType, bool expectFail, object expectedResult)
22-
{
23-
Maybe<object> result = TypeConverter.ChangeType(new[] {testValue}, destinationType, true, CultureInfo.InvariantCulture, true);
24-
25-
if (expectFail)
26-
{
27-
result.MatchNothing().Should().BeTrue("should fail parsing");
28-
}
29-
else
30-
{
31-
result.MatchJust(out object matchedValue).Should().BeTrue("should parse successfully");
32-
Assert.Equal(matchedValue, expectedResult);
33-
}
34-
}
35-
36-
[Fact]
37-
public void ChangeType_Scalar_LastOneWins()
38-
{
39-
var values = new[] { "100", "200", "300", "400", "500" };
40-
var result = TypeConverter.ChangeType(values, typeof(int), true, CultureInfo.InvariantCulture, true);
41-
result.MatchJust(out var matchedValue).Should().BeTrue("should parse successfully");
42-
Assert.Equal(500, matchedValue);
43-
44-
}
45-
46-
public static IEnumerable<object[]> ChangeType_scalars_source
47-
{
48-
get
49-
{
50-
return new[]
51-
{
52-
new object[] {"1", typeof (int), false, 1},
53-
new object[] {"0", typeof (int), false, 0},
54-
new object[] {"-1", typeof (int), false, -1},
55-
new object[] {"abcd", typeof (int), true, null},
56-
new object[] {"1.0", typeof (int), true, null},
57-
new object[] {int.MaxValue.ToString(), typeof (int), false, int.MaxValue},
58-
new object[] {int.MinValue.ToString(), typeof (int), false, int.MinValue},
59-
new object[] {((long) int.MaxValue + 1).ToString(), typeof (int), true, null},
60-
new object[] {((long) int.MinValue - 1).ToString(), typeof (int), true, null},
61-
62-
new object[] {"1", typeof (uint), false, (uint) 1},
63-
// new object[] {"0", typeof (uint), false, (uint) 0}, //cause warning: Skipping test case with duplicate ID
64-
// new object[] {"-1", typeof (uint), true, null}, //cause warning: Skipping test case with duplicate ID
65-
new object[] {uint.MaxValue.ToString(), typeof (uint), false, uint.MaxValue},
66-
new object[] {uint.MinValue.ToString(), typeof (uint), false, uint.MinValue},
67-
new object[] {((long) uint.MaxValue + 1).ToString(), typeof (uint), true, null},
68-
new object[] {((long) uint.MinValue - 1).ToString(), typeof (uint), true, null},
69-
70-
new object[] {"true", typeof (bool), false, true},
71-
new object[] {"True", typeof (bool), false, true},
72-
new object[] {"TRUE", typeof (bool), false, true},
73-
new object[] {"false", typeof (bool), false, false},
74-
new object[] {"False", typeof (bool), false, false},
75-
new object[] {"FALSE", typeof (bool), false, false},
76-
new object[] {"abcd", typeof (bool), true, null},
77-
new object[] {"0", typeof (bool), true, null},
78-
new object[] {"1", typeof (bool), true, null},
79-
80-
new object[] {"1.0", typeof (float), false, 1.0f},
81-
new object[] {"0.0", typeof (float), false, 0.0f},
82-
new object[] {"-1.0", typeof (float), false, -1.0f},
83-
new object[] {"abcd", typeof (float), true, null},
84-
85-
new object[] {"1.0", typeof (double), false, 1.0},
86-
new object[] {"0.0", typeof (double), false, 0.0},
87-
new object[] {"-1.0", typeof (double), false, -1.0},
88-
new object[] {"abcd", typeof (double), true, null},
89-
90-
new object[] {"1.0", typeof (decimal), false, 1.0m},
91-
new object[] {"0.0", typeof (decimal), false, 0.0m},
92-
new object[] {"-1.0", typeof (decimal), false, -1.0m},
93-
new object[] {"-1.123456", typeof (decimal), false, -1.123456m},
94-
new object[] {"abcd", typeof (decimal), true, null},
95-
96-
new object[] {"", typeof (string), false, ""},
97-
new object[] {"abcd", typeof (string), false, "abcd"},
98-
99-
new object[] {"ValueA", typeof (TestEnum), false, TestEnum.ValueA},
100-
new object[] {"VALUEA", typeof (TestEnum), false, TestEnum.ValueA},
101-
new object[] {"ValueB", typeof(TestEnum), false, TestEnum.ValueB},
102-
new object[] {((int) TestEnum.ValueA).ToString(), typeof (TestEnum), false, TestEnum.ValueA},
103-
new object[] {((int) TestEnum.ValueB).ToString(), typeof (TestEnum), false, TestEnum.ValueB},
104-
new object[] {((int) TestEnum.ValueB + 1).ToString(), typeof (TestEnum), true, null},
105-
new object[] {((int) TestEnum.ValueA - 1).ToString(), typeof (TestEnum), true, null},
106-
107-
// Failed before #339
108-
new object[] {"false", typeof (int), true, 0},
109-
new object[] {"true", typeof (int), true, 0}
110-
};
111-
}
112-
}
113-
}
114-
}
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using Xunit;
5+
using FluentAssertions;
6+
using CSharpx;
7+
using CommandLine.Core;
8+
9+
namespace CommandLine.Tests.Unit.Core
10+
{
11+
public class TypeConverterTests
12+
{
13+
enum TestEnum
14+
{
15+
ValueA = 1,
16+
ValueB = 2
17+
}
18+
19+
[Theory]
20+
[MemberData(nameof(ChangeType_scalars_source))]
21+
public void ChangeType_scalars(string testValue, Type destinationType, bool expectFail, object expectedResult)
22+
{
23+
Maybe<object> result = TypeConverter.ChangeType(new[] {testValue}, destinationType, true, false, CultureInfo.InvariantCulture, true);
24+
25+
if (expectFail)
26+
{
27+
result.MatchNothing().Should().BeTrue("should fail parsing");
28+
}
29+
else
30+
{
31+
result.MatchJust(out object matchedValue).Should().BeTrue("should parse successfully");
32+
Assert.Equal(matchedValue, expectedResult);
33+
}
34+
}
35+
36+
[Fact]
37+
public void ChangeType_Scalar_LastOneWins()
38+
{
39+
var values = new[] { "100", "200", "300", "400", "500" };
40+
var result = TypeConverter.ChangeType(values, typeof(int), true, false, CultureInfo.InvariantCulture, true);
41+
result.MatchJust(out var matchedValue).Should().BeTrue("should parse successfully");
42+
Assert.Equal(500, matchedValue);
43+
44+
}
45+
46+
// TODO: Write test for TypeConverter.ChangeType when isFlag = true
47+
48+
public static IEnumerable<object[]> ChangeType_scalars_source
49+
{
50+
get
51+
{
52+
return new[]
53+
{
54+
new object[] {"1", typeof (int), false, 1},
55+
new object[] {"0", typeof (int), false, 0},
56+
new object[] {"-1", typeof (int), false, -1},
57+
new object[] {"abcd", typeof (int), true, null},
58+
new object[] {"1.0", typeof (int), true, null},
59+
new object[] {int.MaxValue.ToString(), typeof (int), false, int.MaxValue},
60+
new object[] {int.MinValue.ToString(), typeof (int), false, int.MinValue},
61+
new object[] {((long) int.MaxValue + 1).ToString(), typeof (int), true, null},
62+
new object[] {((long) int.MinValue - 1).ToString(), typeof (int), true, null},
63+
64+
new object[] {"1", typeof (uint), false, (uint) 1},
65+
// new object[] {"0", typeof (uint), false, (uint) 0}, //cause warning: Skipping test case with duplicate ID
66+
// new object[] {"-1", typeof (uint), true, null}, //cause warning: Skipping test case with duplicate ID
67+
new object[] {uint.MaxValue.ToString(), typeof (uint), false, uint.MaxValue},
68+
new object[] {uint.MinValue.ToString(), typeof (uint), false, uint.MinValue},
69+
new object[] {((long) uint.MaxValue + 1).ToString(), typeof (uint), true, null},
70+
new object[] {((long) uint.MinValue - 1).ToString(), typeof (uint), true, null},
71+
72+
new object[] {"true", typeof (bool), false, true},
73+
new object[] {"True", typeof (bool), false, true},
74+
new object[] {"TRUE", typeof (bool), false, true},
75+
new object[] {"false", typeof (bool), false, false},
76+
new object[] {"False", typeof (bool), false, false},
77+
new object[] {"FALSE", typeof (bool), false, false},
78+
new object[] {"abcd", typeof (bool), true, null},
79+
new object[] {"0", typeof (bool), true, null},
80+
new object[] {"1", typeof (bool), true, null},
81+
82+
new object[] {"1.0", typeof (float), false, 1.0f},
83+
new object[] {"0.0", typeof (float), false, 0.0f},
84+
new object[] {"-1.0", typeof (float), false, -1.0f},
85+
new object[] {"abcd", typeof (float), true, null},
86+
87+
new object[] {"1.0", typeof (double), false, 1.0},
88+
new object[] {"0.0", typeof (double), false, 0.0},
89+
new object[] {"-1.0", typeof (double), false, -1.0},
90+
new object[] {"abcd", typeof (double), true, null},
91+
92+
new object[] {"1.0", typeof (decimal), false, 1.0m},
93+
new object[] {"0.0", typeof (decimal), false, 0.0m},
94+
new object[] {"-1.0", typeof (decimal), false, -1.0m},
95+
new object[] {"-1.123456", typeof (decimal), false, -1.123456m},
96+
new object[] {"abcd", typeof (decimal), true, null},
97+
98+
new object[] {"", typeof (string), false, ""},
99+
new object[] {"abcd", typeof (string), false, "abcd"},
100+
101+
new object[] {"ValueA", typeof (TestEnum), false, TestEnum.ValueA},
102+
new object[] {"VALUEA", typeof (TestEnum), false, TestEnum.ValueA},
103+
new object[] {"ValueB", typeof(TestEnum), false, TestEnum.ValueB},
104+
new object[] {((int) TestEnum.ValueA).ToString(), typeof (TestEnum), false, TestEnum.ValueA},
105+
new object[] {((int) TestEnum.ValueB).ToString(), typeof (TestEnum), false, TestEnum.ValueB},
106+
new object[] {((int) TestEnum.ValueB + 1).ToString(), typeof (TestEnum), true, null},
107+
new object[] {((int) TestEnum.ValueA - 1).ToString(), typeof (TestEnum), true, null},
108+
109+
// Failed before #339
110+
new object[] {"false", typeof (int), true, 0},
111+
new object[] {"true", typeof (int), true, 0}
112+
};
113+
}
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)