Skip to content

Commit 39449ef

Browse files
committed
Fix issue when subcommands use ICommandLineValidatable. Add tests and (while we’re at it) split the other tests into multiple methods.
1 parent 2367586 commit 39449ef

File tree

2 files changed

+105
-33
lines changed

2 files changed

+105
-33
lines changed

Src/CommandLine.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public static class CommandLineParser
187187
/// on the command line.</returns>
188188
public static TArgs Parse<TArgs>(string[] args, Func<ConsoleColoredString, ConsoleColoredString> helpProcessor = null)
189189
{
190-
return (TArgs) parseCommandLine(getCommandInfo(typeof(TArgs)), args, 0, helpProcessor);
190+
return (TArgs) parseCommandLine(getCommandInfo(typeof(TArgs)), args, 0, helpProcessor, false);
191191
}
192192

193193
/// <summary>
@@ -359,7 +359,7 @@ private static Type[] getDirectSubcommands(Type type)
359359
return types.ToArray();
360360
}
361361

362-
private static object parseCommandLine(CommandInfo cmd, string[] args, int i, Func<ConsoleColoredString, ConsoleColoredString> helpProcessor)
362+
private static object parseCommandLine(CommandInfo cmd, string[] args, int i, Func<ConsoleColoredString, ConsoleColoredString> helpProcessor, bool suppressValidation)
363363
{
364364
if (i < args.Length)
365365
if (args[i] == "-?" || args[i] == "/?" || args[i] == "--?" || args[i] == "/h" || args[i] == "--help" || args[i] == "-help" || args[i] == "help")
@@ -405,7 +405,7 @@ private static object parseCommandLine(CommandInfo cmd, string[] args, int i, Fu
405405
if (positional is CmdLineSubcommand)
406406
{
407407
// Special case: recursive call
408-
ret = parseCommandLine(((CmdLineSubcommand) positional).Subcommand, args, i, helpProcessor);
408+
ret = parseCommandLine(((CmdLineSubcommand) positional).Subcommand, args, i, helpProcessor, suppressValidation || typeof(ICommandLineValidatable).IsAssignableFrom(cmd.Type));
409409
i = args.Length;
410410
}
411411
else if (positional is CmdLineStringArrayPositional)
@@ -430,7 +430,7 @@ private static object parseCommandLine(CommandInfo cmd, string[] args, int i, Fu
430430
action(ret);
431431

432432
ConsoleColoredString error = null;
433-
if (typeof(ICommandLineValidatable).IsAssignableFrom(cmd.Type))
433+
if (!suppressValidation && typeof(ICommandLineValidatable).IsAssignableFrom(cmd.Type))
434434
error = ((ICommandLineValidatable) ret).Validate();
435435

436436
if (error != null)

Tests/CommandLineTests.cs

Lines changed: 101 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,77 @@
1-
using Xunit;
1+
using RT.Util.Consoles;
2+
using Xunit;
23

34
namespace RT.CommandLine.Tests;
45

56
public sealed class CmdLineTests
67
{
78
#pragma warning disable 0649 // Field is never assigned to, and will always have its default value null
8-
private class CommandLine1
9+
private class CommandLineWithOption
910
{
1011
[IsPositional, IsMandatory]
1112
public string Arg;
1213
[Option("-o")]
1314
public string Option;
1415
}
15-
private class CommandLine2
16+
private class CommandLineWithArray
1617
{
1718
[Option("--stuff")]
1819
public string Stuff;
1920
[IsPositional]
2021
public string[] Args;
2122
}
23+
24+
[CommandLine]
25+
abstract class CmdBase1 : ICommandLineValidatable
26+
{
27+
[IsPositional, IsMandatory]
28+
public string Base;
29+
30+
public static int ValidateCalled = 0;
31+
32+
public ConsoleColoredString Validate()
33+
{
34+
ValidateCalled++;
35+
return null;
36+
}
37+
}
38+
39+
[CommandName("sub")]
40+
sealed class CmdSubcmd1 : CmdBase1
41+
{
42+
[IsPositional, IsMandatory]
43+
public string ItemName;
44+
}
45+
46+
[CommandLine]
47+
abstract class CmdBase2
48+
{
49+
[IsPositional, IsMandatory]
50+
public string Base;
51+
}
52+
53+
[CommandName("sub")]
54+
sealed class CmdSubcmd2 : CmdBase2, ICommandLineValidatable
55+
{
56+
[IsPositional, IsMandatory]
57+
public string ItemName;
58+
59+
public static int ValidateCalled = 0;
60+
61+
public ConsoleColoredString Validate()
62+
{
63+
ValidateCalled++;
64+
return null;
65+
}
66+
}
2267
#pragma warning restore 0649 // Field is never assigned to, and will always have its default value null
2368

2469
[Fact]
25-
public static void Test()
70+
public static void TestMinusMinus()
2671
{
2772
try
2873
{
29-
CommandLineParser.Parse<CommandLine1>([]);
74+
CommandLineParser.Parse<CommandLineWithOption>([]);
3075
Assert.Fail();
3176
}
3277
catch (CommandLineParseException e)
@@ -35,46 +80,53 @@ public static void Test()
3580
}
3681
try
3782
{
38-
CommandLineParser.Parse<CommandLine1>(["-o", "Option"]);
83+
CommandLineParser.Parse<CommandLineWithOption>(["-o", "Option"]);
3984
Assert.Fail();
4085
}
4186
catch (CommandLineParseException e)
4287
{
4388
Assert.Equal("The parameter <Arg> is mandatory and must be specified.", e.Message);
4489
}
4590

46-
var c1 = CommandLineParser.Parse<CommandLine1>(["Arg"]);
47-
Assert.Equal("Arg", c1.Arg);
48-
Assert.Null(c1.Option);
49-
50-
c1 = CommandLineParser.Parse<CommandLine1>(["-o", "Arg1", "Arg2"]);
51-
Assert.Equal("Arg1", c1.Option);
52-
Assert.Equal("Arg2", c1.Arg);
91+
var c = CommandLineParser.Parse<CommandLineWithOption>(["Arg"]);
92+
Assert.Equal("Arg", c.Arg);
93+
Assert.Null(c.Option);
5394

54-
c1 = CommandLineParser.Parse<CommandLine1>(["Arg1", "-o", "Arg2"]);
55-
Assert.Equal("Arg1", c1.Arg);
56-
Assert.Equal("Arg2", c1.Option);
95+
c = CommandLineParser.Parse<CommandLineWithOption>(["-o", "Arg1", "Arg2"]);
96+
Assert.Equal("Arg1", c.Option);
97+
Assert.Equal("Arg2", c.Arg);
5798

58-
c1 = CommandLineParser.Parse<CommandLine1>(["--", "-o"]);
59-
Assert.Equal("-o", c1.Arg);
60-
Assert.Null(c1.Option);
99+
c = CommandLineParser.Parse<CommandLineWithOption>(["Arg1", "-o", "Arg2"]);
100+
Assert.Equal("Arg1", c.Arg);
101+
Assert.Equal("Arg2", c.Option);
61102

103+
c = CommandLineParser.Parse<CommandLineWithOption>(["--", "-o"]);
104+
Assert.Equal("-o", c.Arg);
105+
Assert.Null(c.Option);
106+
}
62107

63-
var c2 = CommandLineParser.Parse<CommandLine2>("--stuff blah abc def".Split(' '));
64-
Assert.Equal("blah", c2.Stuff);
65-
Assert.True(c2.Args.SequenceEqual(["abc", "def"]));
108+
[Fact]
109+
public static void TestArrays()
110+
{
111+
var c = CommandLineParser.Parse<CommandLineWithArray>("--stuff blah abc def".Split(' '));
112+
Assert.Equal("blah", c.Stuff);
113+
Assert.True(c.Args.SequenceEqual(["abc", "def"]));
66114

67-
c2 = CommandLineParser.Parse<CommandLine2>("def --stuff thingy abc".Split(' '));
68-
Assert.Equal("thingy", c2.Stuff);
69-
Assert.True(c2.Args.SequenceEqual(["def", "abc"]));
115+
c = CommandLineParser.Parse<CommandLineWithArray>("def --stuff thingy abc".Split(' '));
116+
Assert.Equal("thingy", c.Stuff);
117+
Assert.True(c.Args.SequenceEqual(["def", "abc"]));
70118

71-
c2 = CommandLineParser.Parse<CommandLine2>("--stuff stuff -- abc --stuff blah -- def".Split(' '));
72-
Assert.Equal("stuff", c2.Stuff);
73-
Assert.True(c2.Args.SequenceEqual(["abc", "--stuff", "blah", "--", "def"]));
119+
c = CommandLineParser.Parse<CommandLineWithArray>("--stuff stuff -- abc --stuff blah -- def".Split(' '));
120+
Assert.Equal("stuff", c.Stuff);
121+
Assert.True(c.Args.SequenceEqual(["abc", "--stuff", "blah", "--", "def"]));
122+
}
74123

124+
[Fact]
125+
public static void TestInvalidOptionAndLingo()
126+
{
75127
try
76128
{
77-
CommandLineParser.Parse<CommandLine2>("--blah stuff".Split(' '));
129+
CommandLineParser.Parse<CommandLineWithArray>("--blah stuff".Split(' '));
78130
Assert.Fail();
79131
}
80132
catch (CommandLineParseException e)
@@ -86,4 +138,24 @@ public static void Test()
86138
Assert.Equal("Неизвестная опция или команда: --blah.", e.GetColoredMessage(tr).ToString());
87139
}
88140
}
141+
142+
[Fact]
143+
public static void TestSubcommandValidation()
144+
{
145+
CmdBase1.ValidateCalled = 0;
146+
var c = CommandLineParser.Parse<CmdBase1>(["base", "sub", "item"]);
147+
Assert.IsType<CmdSubcmd1>(c);
148+
var cs1 = (CmdSubcmd1) c;
149+
Assert.Equal("base", cs1.Base);
150+
Assert.Equal("item", cs1.ItemName);
151+
Assert.Equal(1, CmdBase1.ValidateCalled);
152+
153+
CmdSubcmd2.ValidateCalled = 0;
154+
var c2 = CommandLineParser.Parse<CmdBase2>(["base", "sub", "item"]);
155+
Assert.IsType<CmdSubcmd2>(c2);
156+
var cs2 = (CmdSubcmd2) c2;
157+
Assert.Equal("base", cs2.Base);
158+
Assert.Equal("item", cs2.ItemName);
159+
Assert.Equal(1, CmdSubcmd2.ValidateCalled);
160+
}
89161
}

0 commit comments

Comments
 (0)