Skip to content

Commit f8d2cee

Browse files
Fix option groups (#575)
* Add MyGet package provider to consume the daily builds. (#566) * Options groups take in account default value * Do not allow options groups and exclusive set names to be used together * Multiple group errors are shown together * MissingGroupOptionError compare option names Co-authored-by: Mohamed Hassan <[email protected]>
1 parent 1ac060c commit f8d2cee

7 files changed

+180
-9
lines changed

appveyor.yml

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#version should be only changed with RELEASE eminent, see RELEASE.md
22

3-
version: 2.7.83-beta-{build}
3+
version: 2.7.84-beta-{build}
44

55
image: Visual Studio 2019
66

77
clone_depth: 1
88
pull_requests:
9-
do_not_increment_build_number: true
9+
do_not_increment_build_number: false
1010

1111
init:
1212
- ps: |
@@ -15,8 +15,9 @@ init:
1515
if ($env:APPVEYOR_REPO_TAG -eq "true") {
1616
$ver = $env:APPVEYOR_REPO_TAG_NAME
1717
if($ver.StartsWith("v") -eq $true) { $ver = $ver.Substring(1) }
18-
Update-AppveyorBuild -Version $ver
19-
}
18+
Update-AppveyorBuild -Version $ver
19+
}
20+
- ps: Write-Host "APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow
2021

2122
environment:
2223
matrix:
@@ -57,3 +58,12 @@ deploy:
5758
artifact: 'NuGetPackages'
5859
on:
5960
APPVEYOR_REPO_TAG: true
61+
62+
#myget
63+
- provider: NuGet
64+
server: https://www.myget.org/F/commandlineparser/api/v2/package
65+
api_key:
66+
secure: ltHh/DsAk+Y7qbJwzUO4+i1U+7uGTLVYXTdW0+Rk2z7jqj5DDNNlih9J8K7bU4bH
67+
artifact: 'NuGetPackages'
68+
symbol_server: https://www.myget.org/F/commandlineparser/symbols/api/v2/package
69+

src/CommandLine/Core/SpecificationPropertyRules.cs

+26-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,35 @@ public static IEnumerable<Func<IEnumerable<SpecificationProperty>, IEnumerable<E
1818
{
1919
EnforceMutuallyExclusiveSet(),
2020
EnforceGroup(),
21+
EnforceMutuallyExclusiveSetAndGroupAreNotUsedTogether(),
2122
EnforceRequired(),
2223
EnforceRange(),
2324
EnforceSingle(tokens)
2425
};
2526
}
2627

28+
private static Func<IEnumerable<SpecificationProperty>, IEnumerable<Error>> EnforceMutuallyExclusiveSetAndGroupAreNotUsedTogether()
29+
{
30+
return specProps =>
31+
{
32+
var options =
33+
from sp in specProps
34+
where sp.Specification.IsOption()
35+
let o = (OptionSpecification)sp.Specification
36+
where o.SetName.Length > 0
37+
where o.Group.Length > 0
38+
select o;
39+
40+
if (options.Any())
41+
{
42+
return from o in options
43+
select new GroupOptionAmbiguityError(new NameInfo(o.ShortName, o.LongName));
44+
}
45+
46+
return Enumerable.Empty<Error>();
47+
};
48+
}
49+
2750
private static Func<IEnumerable<SpecificationProperty>, IEnumerable<Error>> EnforceGroup()
2851
{
2952
return specProps =>
@@ -36,14 +59,15 @@ where o.Group.Length > 0
3659
select new
3760
{
3861
Option = o,
39-
Value = sp.Value
62+
Value = sp.Value,
63+
DefaultValue = sp.Specification.DefaultValue
4064
};
4165

4266
var groups = from o in optionsValues
4367
group o by o.Option.Group into g
4468
select g;
4569

46-
var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing()));
70+
var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing() && g.DefaultValue.IsNothing()));
4771

4872
if (errorGroups.Any())
4973
{

src/CommandLine/Error.cs

+35-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System;
44
using System.Collections.Generic;
5+
using System.Linq;
56

67
namespace CommandLine
78
{
@@ -74,8 +75,11 @@ public enum ErrorType
7475
/// <summary>
7576
/// Value of <see cref="CommandLine.MissingGroupOptionError"/> type.
7677
/// </summary>
77-
MissingGroupOptionError
78-
78+
MissingGroupOptionError,
79+
/// <summary>
80+
/// Value of <see cref="CommandLine.GroupOptionAmbiguityError"/> type.
81+
/// </summary>
82+
GroupOptionAmbiguityError
7983
}
8084

8185
/// <summary>
@@ -532,7 +536,7 @@ internal InvalidAttributeConfigurationError()
532536
}
533537
}
534538

535-
public sealed class MissingGroupOptionError : Error
539+
public sealed class MissingGroupOptionError : Error, IEquatable<Error>, IEquatable<MissingGroupOptionError>
536540
{
537541
public const string ErrorMessage = "At least one option in a group must have value.";
538542

@@ -555,5 +559,33 @@ public IEnumerable<NameInfo> Names
555559
{
556560
get { return names; }
557561
}
562+
563+
public new bool Equals(Error obj)
564+
{
565+
var other = obj as MissingGroupOptionError;
566+
if (other != null)
567+
{
568+
return Equals(other);
569+
}
570+
571+
return base.Equals(obj);
572+
}
573+
574+
public bool Equals(MissingGroupOptionError other)
575+
{
576+
if (other == null)
577+
{
578+
return false;
579+
}
580+
581+
return Group.Equals(other.Group) && Names.SequenceEqual(other.Names);
582+
}
583+
}
584+
585+
public sealed class GroupOptionAmbiguityError : NamedError
586+
{
587+
internal GroupOptionAmbiguityError(NameInfo option)
588+
: base(ErrorType.GroupOptionAmbiguityError, option)
589+
{ }
558590
}
559591
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace CommandLine.Tests.Fakes
2+
{
3+
public class Options_With_Multiple_Groups
4+
{
5+
[Option('v', "version")]
6+
public string Version { get; set; }
7+
8+
[Option("option11", Group = "err-group")]
9+
public string Option11 { get; set; }
10+
11+
[Option("option12", Group = "err-group")]
12+
public string Option12 { get; set; }
13+
14+
[Option("option21", Group = "err-group2")]
15+
public string Option21 { get; set; }
16+
17+
[Option("option22", Group = "err-group2")]
18+
public string Option22 { get; set; }
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace CommandLine.Tests.Fakes
2+
{
3+
public class Simple_Options_With_OptionGroup_MutuallyExclusiveSet
4+
{
5+
[Option(HelpText = "Define a string value here.", Group = "test", SetName = "setname", Default = "qwerty123")]
6+
public string StringValue { get; set; }
7+
8+
[Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "test", SetName = "setname")]
9+
public string ShortAndLong { get; set; }
10+
11+
[Option('x', HelpText = "Define a boolean or switch value here.")]
12+
public bool BoolValue { get; set; }
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace CommandLine.Tests.Fakes
2+
{
3+
public class Simple_Options_With_OptionGroup_WithOptionDefaultValue
4+
{
5+
[Option(HelpText = "Define a string value here.", Required = true, Group = "test", Default = "qwerty123")]
6+
public string StringValue { get; set; }
7+
8+
[Option('s', "shortandlong", HelpText = "Example with both short and long name.", Required = true, Group = "test")]
9+
public string ShortAndLong { get; set; }
10+
11+
[Option('x', HelpText = "Define a boolean or switch value here.")]
12+
public bool BoolValue { get; set; }
13+
}
14+
}

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

+57
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,34 @@ public void Options_In_Group_With_No_Values_Generates_MissingGroupOptionError()
11101110
((NotParsed<Options_With_Group>)result).Errors.Should().BeEquivalentTo(expectedResult);
11111111
}
11121112

1113+
[Fact]
1114+
public void Options_In_Group_With_No_Values_Generates_MissingGroupOptionErrors()
1115+
{
1116+
// Fixture setup
1117+
var optionNames1 = new List<NameInfo>
1118+
{
1119+
new NameInfo("", "option11"),
1120+
new NameInfo("", "option12")
1121+
};
1122+
var optionNames2 = new List<NameInfo>
1123+
{
1124+
new NameInfo("", "option21"),
1125+
new NameInfo("", "option22")
1126+
};
1127+
var expectedResult = new[]
1128+
{
1129+
new MissingGroupOptionError("err-group", optionNames1),
1130+
new MissingGroupOptionError("err-group2", optionNames2)
1131+
};
1132+
1133+
// Exercize system
1134+
var result = InvokeBuild<Options_With_Multiple_Groups>(
1135+
new[] { "-v 10.42" });
1136+
1137+
// Verify outcome
1138+
((NotParsed<Options_With_Multiple_Groups>)result).Errors.Should().BeEquivalentTo(expectedResult);
1139+
}
1140+
11131141
[Theory]
11141142
[InlineData("-v", "10.5", "--option1", "test1", "--option2", "test2")]
11151143
[InlineData("-v", "10.5", "--option1", "test1")]
@@ -1164,6 +1192,35 @@ public void Options_In_Group_Ignore_Option_Group_If_Option_Group_Name_Empty()
11641192
errors.Should().BeEquivalentTo(expectedResult);
11651193
}
11661194

1195+
[Fact]
1196+
public void Options_In_Group_Use_Option_Default_Value_When_Available()
1197+
{
1198+
// Exercize system
1199+
var result = InvokeBuild<Simple_Options_With_OptionGroup_WithOptionDefaultValue>(new string[] { "-x" });
1200+
1201+
// Verify outcome
1202+
result.Should().BeOfType<Parsed<Simple_Options_With_OptionGroup_WithOptionDefaultValue>>();
1203+
}
1204+
1205+
[Fact]
1206+
public void Options_In_Group_Do_Not_Allow_Mutually_Exclusive_Set()
1207+
{
1208+
var expectedResult = new[]
1209+
{
1210+
new GroupOptionAmbiguityError(new NameInfo("", "stringvalue")),
1211+
new GroupOptionAmbiguityError(new NameInfo("s", "shortandlong"))
1212+
};
1213+
1214+
// Exercize system
1215+
var result = InvokeBuild<Simple_Options_With_OptionGroup_MutuallyExclusiveSet>(new string[] { "-x" });
1216+
1217+
// Verify outcome
1218+
result.Should().BeOfType<NotParsed<Simple_Options_With_OptionGroup_MutuallyExclusiveSet>>();
1219+
var errors = ((NotParsed<Simple_Options_With_OptionGroup_MutuallyExclusiveSet>)result).Errors;
1220+
1221+
errors.Should().BeEquivalentTo(expectedResult);
1222+
}
1223+
11671224
private class ValueWithNoSetterOptions
11681225
{
11691226
[Value(0, MetaName = "Test", Default = 0)]

0 commit comments

Comments
 (0)