Skip to content

Commit 75b8a0c

Browse files
committed
Rework command parser
- Previously CommandParser contained parser which was scannerless type of brute force parsing of command line args. - Contract in that old parser wasn't super clear what is its role with caller as it was given options and registration was parsed in a Shell class. - Add completely new parser package which has better model and which will be much easier to modify for future needs. - Change some interfaces around parsing so that we do as much in this new parsing model instead of pre-parsing something in a Shell class. - We also try to move away from using exceptions as a message delivery which had its own problems. Instead introducing parser messages which gives better info when errors are detected. - Add ParserConfig class which allows to expose settings to change some parser features. Later this will be exposed to user so that some features can be turned on/off for an actual shell needs. - Fixes #646
1 parent e8d5d70 commit 75b8a0c

40 files changed

+3826
-1256
lines changed

spring-shell-core/src/main/java/org/springframework/shell/Shell.java

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,6 @@ protected Object evaluate(Input input) {
229229
this.exitCodeMappings.reset(mappingFunctions);
230230
}
231231

232-
List<String> wordsForArgs = wordsForArguments(command, words);
233-
234232
Thread commandThread = Thread.currentThread();
235233
Object sh = Signals.register("INT", () -> commandThread.interrupt());
236234

@@ -243,7 +241,7 @@ protected Object evaluate(Input input) {
243241
Object evaluate = null;
244242
Exception e = null;
245243
try {
246-
evaluate = execution.evaluate(commandRegistration.get(), wordsForArgs.toArray(new String[0]));
244+
evaluate = execution.evaluate(words.toArray(new String[0]));
247245
}
248246
catch (UndeclaredThrowableException ute) {
249247
if (ute.getCause() instanceof InterruptedException || ute.getCause() instanceof ClosedByInterruptException) {
@@ -327,21 +325,6 @@ private boolean noInput(Input input) {
327325
|| (input.words().iterator().next().matches("\\s*//.*"));
328326
}
329327

330-
/**
331-
* Returns the list of words to be considered for argument resolving. Drops the first N
332-
* words used for the command, as well as an optional empty word at the end of the list
333-
* (which may be present if user added spaces before submitting the buffer)
334-
*/
335-
private List<String> wordsForArguments(String command, List<String> words) {
336-
int wordsUsedForCommandKey = command.split(" ").length;
337-
List<String> args = words.subList(wordsUsedForCommandKey, words.size());
338-
int last = args.size() - 1;
339-
if (last >= 0 && "".equals(args.get(last))) {
340-
args.remove(last);
341-
}
342-
return args;
343-
}
344-
345328
/**
346329
* Gather completion proposals given some (incomplete) input the user has already typed
347330
* in. When and how this method is invoked is implementation specific and decided by the

spring-shell-core/src/main/java/org/springframework/shell/command/CommandExecution.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 the original author or authors.
2+
* Copyright 2022-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@
3838
import org.springframework.shell.command.CommandRegistration.TargetInfo.TargetType;
3939
import org.springframework.shell.command.invocation.InvocableShellMethod;
4040
import org.springframework.shell.command.invocation.ShellMethodArgumentResolverComposite;
41+
import org.springframework.shell.command.parser.ParserConfig;
4142
import org.springframework.util.ObjectUtils;
4243

4344
/**
@@ -50,11 +51,10 @@ public interface CommandExecution {
5051
/**
5152
* Evaluate a command with a given arguments.
5253
*
53-
* @param registration the command registration
5454
* @param args the command args
5555
* @return evaluated execution
5656
*/
57-
Object evaluate(CommandRegistration registration, String[] args);
57+
Object evaluate(String[] args);
5858

5959
/**
6060
* Gets an instance of a default {@link CommandExecution}.
@@ -114,17 +114,17 @@ public DefaultCommandExecution(List<? extends HandlerMethodArgumentResolver> res
114114
this.commandCatalog = commandCatalog;
115115
}
116116

117-
public Object evaluate(CommandRegistration registration, String[] args) {
117+
public Object evaluate(String[] args) {
118+
CommandParser parser = CommandParser.of(conversionService, commandCatalog.getRegistrations(), new ParserConfig());
119+
CommandParserResults results = parser.parse(args);
120+
CommandRegistration registration = results.registration();
121+
118122
// fast fail with availability before doing anything else
119123
Availability availability = registration.getAvailability();
120124
if (availability != null && !availability.isAvailable()) {
121125
return new CommandNotCurrentlyAvailable(registration.getCommand(), availability);
122126
}
123127

124-
List<CommandOption> options = registration.getOptions();
125-
CommandParser parser = CommandParser.of(conversionService);
126-
CommandParserResults results = parser.parse(options, args);
127-
128128
// check help options to short circuit
129129
boolean handleHelpOption = false;
130130
HelpOptionInfo helpOption = registration.getHelpOption();
@@ -157,11 +157,11 @@ public Object evaluate(CommandRegistration registration, String[] args) {
157157
CommandRegistration usedRegistration;
158158
if (handleHelpOption) {
159159
String command = registration.getCommand();
160-
CommandParser helpParser = CommandParser.of(conversionService);
160+
CommandParser helpParser = CommandParser.of(conversionService, commandCatalog.getRegistrations(),
161+
new ParserConfig());
161162
CommandRegistration helpCommandRegistration = commandCatalog.getRegistrations()
162163
.get(registration.getHelpOption().getCommand());
163-
List<CommandOption> helpOptions = helpCommandRegistration.getOptions();
164-
CommandParserResults helpResults = helpParser.parse(helpOptions, new String[] { "--command", command });
164+
CommandParserResults helpResults = helpParser.parse(new String[] { "help", "--command", command });
165165
results = helpResults;
166166
usedRegistration = helpCommandRegistration;
167167
}

0 commit comments

Comments
 (0)