Skip to content

Commit 165bad7

Browse files
committed
Merge branch 'Artentus-develop' into develop
2 parents 5bb3235 + 3cb5018 commit 165bad7

File tree

8 files changed

+160
-16
lines changed

8 files changed

+160
-16
lines changed

demo/ReadText.Demo/Options.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ interface IOptions
2727
string FileName { get; set; }
2828
}
2929

30-
[Verb("head", HelpText = "Displays first lines of a file.")]
30+
[Verb("head", true, HelpText = "Displays first lines of a file.")]
3131
class HeadOptions : IOptions
3232
{
3333
public uint? Lines { get; set; }
@@ -62,4 +62,4 @@ class TailOptions : IOptions
6262

6363
public string FileName { get; set; }
6464
}
65-
}
65+
}

src/CommandLine/Core/InstanceChooser.cs

+41-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ public static ParserResult<object> Choose(
2323
bool autoVersion,
2424
IEnumerable<ErrorType> nonFatalErrors)
2525
{
26+
var verbs = Verb.SelectFromTypes(types);
27+
var defaultVerbs = verbs.Where(t => t.Item1.IsDefault);
28+
29+
int defaultVerbCount = defaultVerbs.Count();
30+
if (defaultVerbCount > 1)
31+
return MakeNotParsed(types, new MultipleDefaultVerbsError());
32+
33+
var defaultVerb = defaultVerbCount == 1 ? defaultVerbs.First() : null;
34+
2635
Func<ParserResult<object>> choose = () =>
2736
{
2837
var firstArg = arguments.First();
@@ -31,25 +40,52 @@ public static ParserResult<object> Choose(
3140
nameComparer.Equals(command, firstArg) ||
3241
nameComparer.Equals(string.Concat("--", command), firstArg);
3342

34-
var verbs = Verb.SelectFromTypes(types);
35-
3643
return (autoHelp && preprocCompare("help"))
3744
? MakeNotParsed(types,
3845
MakeHelpVerbRequestedError(verbs,
3946
arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer))
4047
: (autoVersion && preprocCompare("version"))
4148
? MakeNotParsed(types, new VersionRequestedError())
42-
: MatchVerb(tokenizer, verbs, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors);
49+
: MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors);
4350
};
4451

4552
return arguments.Any()
4653
? choose()
47-
: MakeNotParsed(types, new NoVerbSelectedError());
54+
: (defaultVerbCount == 1
55+
? MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors)
56+
: MakeNotParsed(types, new NoVerbSelectedError()));
57+
}
58+
59+
private static ParserResult<object> MatchDefaultVerb(
60+
Func<IEnumerable<string>, IEnumerable<OptionSpecification>, Result<IEnumerable<Token>, Error>> tokenizer,
61+
IEnumerable<Tuple<Verb, Type>> verbs,
62+
Tuple<Verb, Type> defaultVerb,
63+
IEnumerable<string> arguments,
64+
StringComparer nameComparer,
65+
bool ignoreValueCase,
66+
CultureInfo parsingCulture,
67+
bool autoHelp,
68+
bool autoVersion,
69+
IEnumerable<ErrorType> nonFatalErrors)
70+
{
71+
return !(defaultVerb is null)
72+
? InstanceBuilder.Build(
73+
Maybe.Just<Func<object>>(() => defaultVerb.Item2.AutoDefault()),
74+
tokenizer,
75+
arguments,
76+
nameComparer,
77+
ignoreValueCase,
78+
parsingCulture,
79+
autoHelp,
80+
autoVersion,
81+
nonFatalErrors)
82+
: MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First()));
4883
}
4984

5085
private static ParserResult<object> MatchVerb(
5186
Func<IEnumerable<string>, IEnumerable<OptionSpecification>, Result<IEnumerable<Token>, Error>> tokenizer,
5287
IEnumerable<Tuple<Verb, Type>> verbs,
88+
Tuple<Verb, Type> defaultVerb,
5389
IEnumerable<string> arguments,
5490
StringComparer nameComparer,
5591
bool ignoreValueCase,
@@ -71,7 +107,7 @@ private static ParserResult<object> MatchVerb(
71107
autoHelp,
72108
autoVersion,
73109
nonFatalErrors)
74-
: MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First()));
110+
: MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors);
75111
}
76112

77113
private static HelpVerbRequestedError MakeHelpVerbRequestedError(

src/CommandLine/Core/Verb.cs

+15-4
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@ sealed class Verb
1212
private readonly string name;
1313
private readonly string helpText;
1414
private readonly bool hidden;
15+
private readonly bool isDefault;
1516

16-
public Verb(string name, string helpText, bool hidden = false)
17+
public Verb(string name, string helpText, bool hidden = false, bool isDefault = false)
1718
{
18-
this.name = name ?? throw new ArgumentNullException(nameof(name));
19+
if ( string.IsNullOrWhiteSpace(name))
20+
throw new ArgumentNullException(nameof(name));
21+
this.name = name;
22+
1923
this.helpText = helpText ?? throw new ArgumentNullException(nameof(helpText));
2024
this.hidden = hidden;
25+
this.isDefault = isDefault;
2126
}
2227

2328
public string Name
@@ -35,12 +40,18 @@ public bool Hidden
3540
get { return hidden; }
3641
}
3742

43+
public bool IsDefault
44+
{
45+
get => isDefault;
46+
}
47+
3848
public static Verb FromAttribute(VerbAttribute attribute)
3949
{
4050
return new Verb(
4151
attribute.Name,
4252
attribute.HelpText,
43-
attribute.Hidden
53+
attribute.Hidden,
54+
attribute.IsDefault
4455
);
4556
}
4657

@@ -54,4 +65,4 @@ select Tuple.Create(
5465
type);
5566
}
5667
}
57-
}
68+
}

src/CommandLine/Error.cs

+18-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ public enum ErrorType
7979
/// <summary>
8080
/// Value of <see cref="CommandLine.GroupOptionAmbiguityError"/> type.
8181
/// </summary>
82-
GroupOptionAmbiguityError
82+
GroupOptionAmbiguityError,
83+
/// <summary>
84+
/// Value of <see cref="CommandLine.MultipleDefaultVerbsError"/> type.
85+
/// </summary>
86+
MultipleDefaultVerbsError
87+
8388
}
8489

8590
/// <summary>
@@ -592,4 +597,16 @@ internal GroupOptionAmbiguityError(NameInfo option)
592597
Option = option;
593598
}
594599
}
600+
601+
/// <summary>
602+
/// Models an error generated when multiple default verbs are defined.
603+
/// </summary>
604+
public sealed class MultipleDefaultVerbsError : Error
605+
{
606+
public const string ErrorMessage = "More than one default verb is not allowed.";
607+
608+
internal MultipleDefaultVerbsError()
609+
: base(ErrorType.MultipleDefaultVerbsError)
610+
{ }
611+
}
595612
}

src/CommandLine/Text/SentenceBuilder.cs

+3
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ public override Func<Error, string> FormatError
161161
case ErrorType.GroupOptionAmbiguityError:
162162
var groupOptionAmbiguityError = (GroupOptionAmbiguityError)error;
163163
return "Both SetName and Group are not allowed in option: (".JoinTo(groupOptionAmbiguityError.Option.NameText, ")");
164+
case ErrorType.MultipleDefaultVerbsError:
165+
return MultipleDefaultVerbsError.ErrorMessage;
166+
164167
}
165168
throw new InvalidOperationException();
166169
};

src/CommandLine/VerbAttribute.cs

+14-3
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,22 @@ namespace CommandLine
1212
public class VerbAttribute : Attribute
1313
{
1414
private readonly string name;
15+
private readonly bool isDefault;
1516
private Infrastructure.LocalizableAttributeProperty helpText;
1617
private Type resourceType;
1718

1819
/// <summary>
1920
/// Initializes a new instance of the <see cref="CommandLine.VerbAttribute"/> class.
2021
/// </summary>
2122
/// <param name="name">The long name of the verb command.</param>
22-
/// <exception cref="System.ArgumentException">Thrown if <paramref name="name"/> is null, empty or whitespace.</exception>
23-
public VerbAttribute(string name)
23+
/// <param name="isDefault">Whether the verb is the default verb.</param>
24+
/// <exception cref="System.ArgumentException">Thrown if <paramref name="name"/> is null, empty or whitespace and <paramref name="isDefault"/> is false.</exception>
25+
public VerbAttribute(string name, bool isDefault = false)
2426
{
2527
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name");
2628

27-
this.name = name;
29+
this.name = name ;
30+
this.isDefault = isDefault;
2831
helpText = new Infrastructure.LocalizableAttributeProperty(nameof(HelpText));
2932
resourceType = null;
3033
}
@@ -62,5 +65,13 @@ public Type ResourceType
6265
get => resourceType;
6366
set => resourceType =helpText.ResourceType = value;
6467
}
68+
69+
/// <summary>
70+
/// Gets whether this verb is the default verb.
71+
/// </summary>
72+
public bool IsDefault
73+
{
74+
get => isDefault;
75+
}
6576
}
6677
}

tests/CommandLine.Tests/Fakes/Verb_Fakes.cs

+22-1
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,25 @@ class Verb_With_Option_And_Value_Of_String_Type
8585
[Value(0)]
8686
public string PosValue { get; set; }
8787
}
88-
}
88+
89+
[Verb("default1", true)]
90+
class Default_Verb_One
91+
{
92+
[Option('t', "test-one")]
93+
public bool TestValueOne { get; set; }
94+
}
95+
96+
[Verb("default2", true)]
97+
class Default_Verb_Two
98+
{
99+
[Option('t', "test-two")]
100+
public bool TestValueTwo { get; set; }
101+
}
102+
103+
[Verb(null, true)]
104+
class Default_Verb_With_Empty_Name
105+
{
106+
[Option('t', "test")]
107+
public bool TestValue { get; set; }
108+
}
109+
}

tests/CommandLine.Tests/Unit/ParserTests.cs

+45
Original file line numberDiff line numberDiff line change
@@ -824,5 +824,50 @@ public void Blank_lines_are_inserted_between_verbs()
824824
// Teardown
825825
}
826826

827+
828+
[Fact]
829+
public void Parse_default_verb_implicit()
830+
{
831+
var parser = Parser.Default;
832+
parser.ParseArguments<Default_Verb_One>(new[] { "-t" })
833+
.WithNotParsed(errors => throw new InvalidOperationException("Must be parsed."))
834+
.WithParsed(args =>
835+
{
836+
Assert.True(args.TestValueOne);
837+
});
838+
}
839+
840+
[Fact]
841+
public void Parse_default_verb_explicit()
842+
{
843+
var parser = Parser.Default;
844+
parser.ParseArguments<Default_Verb_One>(new[] { "default1", "-t" })
845+
.WithNotParsed(errors => throw new InvalidOperationException("Must be parsed."))
846+
.WithParsed(args =>
847+
{
848+
Assert.True(args.TestValueOne);
849+
});
850+
}
851+
852+
[Fact]
853+
public void Parse_multiple_default_verbs()
854+
{
855+
var parser = Parser.Default;
856+
parser.ParseArguments<Default_Verb_One, Default_Verb_Two>(new string[] { })
857+
.WithNotParsed(errors => Assert.IsType<MultipleDefaultVerbsError>(errors.First()))
858+
.WithParsed(args => throw new InvalidOperationException("Should not be parsed."));
859+
}
860+
861+
[Fact]
862+
public void Parse_default_verb_with_empty_name()
863+
{
864+
var parser = Parser.Default;
865+
parser.ParseArguments<Default_Verb_With_Empty_Name>(new[] { "-t" })
866+
.WithNotParsed(errors => throw new InvalidOperationException("Must be parsed."))
867+
.WithParsed(args =>
868+
{
869+
Assert.True(args.TestValue);
870+
});
871+
}
827872
}
828873
}

0 commit comments

Comments
 (0)