Skip to content

Commit 2326c8e

Browse files
committed
Refactor the command line parser
1 parent 63cfe1d commit 2326c8e

File tree

3 files changed

+439
-300
lines changed

3 files changed

+439
-300
lines changed

src/CommandLineUtils/CommandLineApplication.Parser.cs

Lines changed: 3 additions & 278 deletions
Original file line numberDiff line numberDiff line change
@@ -2,248 +2,17 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
// This file has been modified from the original form. See Notice.txt in the project root for more information.
44

5-
using System;
6-
using System.Collections;
75
using System.Collections.Generic;
86
using System.ComponentModel.DataAnnotations;
9-
using System.IO;
10-
using System.Linq;
11-
using System.Text;
12-
using System.Threading.Tasks;
137

148
namespace McMaster.Extensions.CommandLineUtils
159
{
1610
partial class CommandLineApplication
1711
{
18-
// used to keep track of arguments added from the response file
19-
private int _responseFileArgsEnd = -1;
20-
2112
private int Execute(List<string> args)
2213
{
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-
}
14+
var processor = new CommandLineProcessor(this, args);
15+
var command = processor.Process();
24716

24817
var result = command.GetValidationResult();
24918

@@ -255,7 +24,7 @@ private int Execute(List<string> args)
25524
return command.Invoke();
25625
}
25726

258-
internal CommandLineApplication SelectedCommand { get; private set; }
27+
internal CommandLineApplication SelectedCommand { get; set; }
25928

26029
private bool _settingConsole;
26130

@@ -279,49 +48,5 @@ internal void SetConsole(IConsole console)
27948

28049
_settingConsole = false;
28150
}
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-
}
32651
}
32752
}

0 commit comments

Comments
 (0)