Skip to content

Commit ea2f40c

Browse files
committed
Split CommandLineApplication.cs into separate files and fix bug in ValidationError invoker
1 parent 817bfc5 commit ea2f40c

File tree

5 files changed

+480
-429
lines changed

5 files changed

+480
-429
lines changed
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
// Copyright (c) Nate McMaster.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
// This file has been modified from the original form. See Notice.txt in the project root for more information.
4+
5+
using System;
6+
using System.Collections;
7+
using System.Collections.Generic;
8+
using System.ComponentModel.DataAnnotations;
9+
using System.IO;
10+
using System.Linq;
11+
using System.Text;
12+
using System.Threading.Tasks;
13+
14+
namespace McMaster.Extensions.CommandLineUtils
15+
{
16+
partial class CommandLineApplication
17+
{
18+
// used to keep track of arguments added from the response file
19+
private int _responseFileArgsEnd = -1;
20+
21+
private int Execute(List<string> args)
22+
{
23+
CommandLineApplication command = this;
24+
CommandOption option = null;
25+
IEnumerator<CommandArgument> arguments = null;
26+
27+
for (var index = 0; index < args.Count; index++)
28+
{
29+
var arg = args[index];
30+
31+
if (command.ResponseFileHandling != ResponseFileHandling.Disabled && index > _responseFileArgsEnd && arg.Length > 1 && arg[0] == '@')
32+
{
33+
var path = arg.Substring(1);
34+
var fullPath = Path.IsPathRooted(path)
35+
? path
36+
: Path.Combine(command.WorkingDirectory, path);
37+
38+
IList<string> rspArgs;
39+
try
40+
{
41+
rspArgs = ResponseFileParser.Parse(fullPath, command.ResponseFileHandling);
42+
}
43+
catch (Exception ex)
44+
{
45+
throw new CommandParsingException(this, $"Could not parse the response file '{arg}'", ex);
46+
}
47+
48+
args.InsertRange(index + 1, rspArgs);
49+
_responseFileArgsEnd = index + rspArgs.Count;
50+
continue;
51+
}
52+
53+
var processed = false;
54+
if (!processed && option == null)
55+
{
56+
string[] longOption = null;
57+
string[] shortOption = null;
58+
59+
if (arg != null)
60+
{
61+
if (arg.StartsWith("--"))
62+
{
63+
longOption = arg.Substring(2).Split(new[] { ':', '=' }, 2);
64+
}
65+
else if (arg.StartsWith("-"))
66+
{
67+
shortOption = arg.Substring(1).Split(new[] { ':', '=' }, 2);
68+
}
69+
}
70+
71+
if (longOption != null)
72+
{
73+
processed = true;
74+
var longOptionName = longOption[0];
75+
option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.LongName, longOptionName, StringComparison.Ordinal));
76+
77+
if (option == null)
78+
{
79+
if (string.IsNullOrEmpty(longOptionName) && !command.ThrowOnUnexpectedArgument && AllowArgumentSeparator)
80+
{
81+
// skip over the '--' argument separator
82+
index++;
83+
}
84+
85+
HandleUnexpectedArg(command, args, index, argTypeName: "option");
86+
break;
87+
}
88+
89+
// If we find a help/version option, show information and stop parsing
90+
if (command.OptionHelp == option)
91+
{
92+
command.ShowHelp();
93+
option.TryParse(null);
94+
var parent = command;
95+
while (parent.Parent != null) parent = parent.Parent;
96+
parent.SelectedCommand = command;
97+
if (StopParsingAfterHelpOption)
98+
{
99+
return 0;
100+
}
101+
}
102+
else if (command.OptionVersion == option)
103+
{
104+
command.ShowVersion();
105+
option.TryParse(null);
106+
var parent = command;
107+
while (parent.Parent != null) parent = parent.Parent;
108+
parent.SelectedCommand = command;
109+
if (StopParsingAfterVersionOption)
110+
{
111+
return 0;
112+
}
113+
}
114+
115+
if (longOption.Length == 2)
116+
{
117+
if (!option.TryParse(longOption[1]))
118+
{
119+
command.ShowHint();
120+
throw new CommandParsingException(command, $"Unexpected value '{longOption[1]}' for option '{option.LongName}'");
121+
}
122+
option = null;
123+
}
124+
else if (option.OptionType == CommandOptionType.NoValue)
125+
{
126+
// No value is needed for this option
127+
option.TryParse(null);
128+
option = null;
129+
}
130+
}
131+
132+
if (shortOption != null)
133+
{
134+
processed = true;
135+
option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.ShortName, shortOption[0], StringComparison.Ordinal));
136+
137+
// If not a short option, try symbol option
138+
if (option == null)
139+
{
140+
option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.SymbolName, shortOption[0], StringComparison.Ordinal));
141+
}
142+
143+
if (option == null)
144+
{
145+
HandleUnexpectedArg(command, args, index, argTypeName: "option");
146+
break;
147+
}
148+
149+
// If we find a help/version option, show information and stop parsing
150+
if (command.OptionHelp == option)
151+
{
152+
command.ShowHelp();
153+
option.TryParse(null);
154+
var parent = command;
155+
while (parent.Parent != null) parent = parent.Parent;
156+
parent.SelectedCommand = command;
157+
if (StopParsingAfterHelpOption)
158+
{
159+
return 0;
160+
}
161+
}
162+
else if (command.OptionVersion == option)
163+
{
164+
command.ShowVersion();
165+
option.TryParse(null);
166+
var parent = command;
167+
while (parent.Parent != null) parent = parent.Parent;
168+
parent.SelectedCommand = command;
169+
if (StopParsingAfterVersionOption)
170+
{
171+
return 0;
172+
}
173+
}
174+
175+
if (shortOption.Length == 2)
176+
{
177+
if (!option.TryParse(shortOption[1]))
178+
{
179+
command.ShowHint();
180+
throw new CommandParsingException(command, $"Unexpected value '{shortOption[1]}' for option '{option.LongName}'");
181+
}
182+
option = null;
183+
}
184+
else if (option.OptionType == CommandOptionType.NoValue)
185+
{
186+
// No value is needed for this option
187+
option.TryParse(null);
188+
option = null;
189+
}
190+
}
191+
}
192+
193+
if (!processed && option != null)
194+
{
195+
processed = true;
196+
if (!option.TryParse(arg))
197+
{
198+
command.ShowHint();
199+
throw new CommandParsingException(command, $"Unexpected value '{arg}' for option '{option.LongName}'");
200+
}
201+
option = null;
202+
}
203+
204+
if (!processed && arguments == null)
205+
{
206+
var currentCommand = command;
207+
foreach (var subcommand in command.Commands)
208+
{
209+
if (string.Equals(subcommand.Name, arg, StringComparison.OrdinalIgnoreCase))
210+
{
211+
processed = true;
212+
command = subcommand;
213+
break;
214+
}
215+
}
216+
217+
// If we detect a subcommand
218+
if (command != currentCommand)
219+
{
220+
processed = true;
221+
}
222+
}
223+
if (!processed)
224+
{
225+
if (arguments == null)
226+
{
227+
arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator());
228+
}
229+
if (arguments.MoveNext())
230+
{
231+
processed = true;
232+
arguments.Current.Values.Add(arg);
233+
}
234+
}
235+
if (!processed)
236+
{
237+
HandleUnexpectedArg(command, args, index, argTypeName: "command or argument");
238+
break;
239+
}
240+
}
241+
242+
if (option != null)
243+
{
244+
command.ShowHint();
245+
throw new CommandParsingException(command, $"Missing value for option '{option.LongName}'");
246+
}
247+
248+
var result = command.GetValidationResult();
249+
250+
if (result != ValidationResult.Success)
251+
{
252+
return command.ValidationErrorHandler(result);
253+
}
254+
255+
return command.Invoke();
256+
}
257+
258+
internal CommandLineApplication SelectedCommand { get; private set; }
259+
260+
private bool _settingConsole;
261+
262+
internal void SetConsole(IConsole console)
263+
{
264+
if (_settingConsole)
265+
{
266+
// prevent stack overflow in the event someone has looping command line apps
267+
return;
268+
}
269+
270+
_settingConsole = true;
271+
_console = console;
272+
Out = console.Out;
273+
Error = console.Error;
274+
275+
foreach (var cmd in Commands)
276+
{
277+
cmd.SetConsole(console);
278+
}
279+
280+
_settingConsole = false;
281+
}
282+
283+
private void HandleUnexpectedArg(CommandLineApplication command, IReadOnlyList<string> args, int index, string argTypeName)
284+
{
285+
if (command.ThrowOnUnexpectedArgument)
286+
{
287+
command.ShowHint();
288+
throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'");
289+
}
290+
else
291+
{
292+
// All remaining arguments are stored for further use
293+
command.RemainingArguments.AddRange(new ArraySegment<string>(args.ToArray(), index, args.Count - index));
294+
}
295+
}
296+
297+
private class CommandArgumentEnumerator : IEnumerator<CommandArgument>
298+
{
299+
private readonly IEnumerator<CommandArgument> _enumerator;
300+
301+
public CommandArgumentEnumerator(IEnumerator<CommandArgument> enumerator)
302+
{
303+
_enumerator = enumerator;
304+
}
305+
306+
public CommandArgument Current => _enumerator.Current;
307+
308+
object IEnumerator.Current => Current;
309+
310+
public void Dispose() => _enumerator.Dispose();
311+
312+
public bool MoveNext()
313+
{
314+
if (Current == null || !Current.MultipleValues)
315+
{
316+
return _enumerator.MoveNext();
317+
}
318+
319+
// If current argument allows multiple values, we don't move forward and
320+
// all later values will be added to current CommandArgument.Values
321+
return true;
322+
}
323+
324+
public void Reset() => _enumerator.Reset();
325+
}
326+
}
327+
}

0 commit comments

Comments
 (0)