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
This commit is contained in:
Janne Valkealahti
2023-02-08 15:47:08 +00:00
parent e8d5d70792
commit 75b8a0c90e
40 changed files with 3826 additions and 1256 deletions

View File

@@ -21,6 +21,8 @@ import java.util.Set;
import org.springframework.context.annotation.Bean;
import org.springframework.shell.command.CommandRegistration;
import org.springframework.shell.command.annotation.Command;
import org.springframework.shell.command.annotation.Option;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;
@@ -107,6 +109,102 @@ public class OptionTypeCommands {
}
}
@Command(command = BaseE2ECommands.ANNO, group = BaseE2ECommands.GROUP)
public static class Annotation extends BaseE2ECommands {
@Command(command = "option-type-string")
public String optionTypeStringAnnotation(
@Option(longNames = "arg1")
String arg1
) {
return "Hello " + arg1;
}
@Command(command = "option-type-boolean")
public String optionTypeBooleanAnnotation(
@ShellOption()
@Option(longNames = "arg1")
boolean arg1,
@ShellOption(defaultValue = "true")
@Option(longNames = "arg2", defaultValue = "true")
boolean arg2,
@ShellOption(defaultValue = "false")
@Option(longNames = "arg3", defaultValue = "false")
boolean arg3,
@ShellOption()
@Option(longNames = "arg4")
Boolean arg4,
@ShellOption(defaultValue = "true")
@Option(longNames = "arg5", defaultValue = "true")
Boolean arg5,
@ShellOption(defaultValue = "false")
@Option(longNames = "arg6", defaultValue = "false")
Boolean arg6
) {
return String.format("Hello arg1=%s arg2=%s arg3=%s arg4=%s arg5=%s arg6=%s", arg1, arg2, arg3, arg4, arg5,
arg6);
}
@Command(command = "option-type-integer")
public String optionTypeIntegerAnnotation(
@Option(longNames = "arg1")
int arg1,
@Option(longNames = "arg2")
Integer arg2
) {
return String.format("Hello '%s' '%s'", arg1, arg2);
}
@Command(command = "option-type-enum")
public String optionTypeEnumAnnotation(
@Option(longNames = "arg1")
OptionTypeEnum arg1
) {
return "Hello " + arg1;
}
@Command(command = "option-type-string-array")
public String optionTypeStringArrayAnnotation(
@Option(longNames = "arg1")
String[] arg1
) {
return "Hello " + stringOfStrings(arg1);
}
@Command(command = "option-type-int-array")
public String optionTypeIntArrayAnnotation(
@Option(longNames = "arg1")
int[] arg1
) {
return "Hello " + stringOfInts(arg1);
}
@Command(command = "option-type-string-list")
public String optionTypeStringListAnnotation(
@Option(longNames = "arg1")
List<String> arg1
) {
return "Hello " + arg1;
}
@Command(command = "option-type-string-set")
public String optionTypeStringSetAnnotation(
@Option(longNames = "arg1")
Set<String> arg1
) {
return "Hello " + arg1;
}
@Command(command = "option-type-string-collection")
public String optionTypeStringCollectionAnnotation(
@Option(longNames = "arg1")
Collection<String> arg1
) {
return "Hello " + arg1;
}
}
@Component
public static class Registration extends BaseE2ECommands {

View File

@@ -17,24 +17,27 @@ package org.springframework.shell.samples.e2e;
import org.junit.jupiter.params.ParameterizedTest;
import org.springframework.shell.command.annotation.EnableCommand;
import org.springframework.shell.samples.AbstractSampleTests;
import org.springframework.shell.samples.e2e.OptionTypeCommands.Annotation;
import org.springframework.shell.samples.e2e.OptionTypeCommands.LegacyAnnotation;
import org.springframework.shell.samples.e2e.OptionTypeCommands.Registration;
import org.springframework.shell.test.ShellTestClient.BaseShellSession;
import org.springframework.test.context.ContextConfiguration;
@ContextConfiguration(classes = { LegacyAnnotation.class, Registration.class })
@EnableCommand(Annotation.class)
class OptionTypeCommandsTests extends AbstractSampleTests {
@ParameterizedTest
@E2ESource(command = "option-type-string --arg1 hi", annox = false)
@E2ESource(command = "option-type-string --arg1 hi")
void optionTypeString(String command, boolean interactive) {
BaseShellSession<?> session = createSession(command, interactive);
assertScreenContainsText(session, "Hello hi");
}
@ParameterizedTest
@E2ESource(command = "option-type-boolean", annox = false, reg = false)
@E2ESource(command = "option-type-boolean", reg = false)
void optionTypeBooleanWithAnno(String command, boolean interactive) {
BaseShellSession<?> session = createSession(command, interactive);
assertScreenContainsText(session, "Hello arg1=false arg2=true arg3=false arg4=false arg5=true arg6=false");
@@ -48,49 +51,49 @@ class OptionTypeCommandsTests extends AbstractSampleTests {
}
@ParameterizedTest
@E2ESource(command = "option-type-integer --arg1 1 --arg2 2", annox = false)
@E2ESource(command = "option-type-integer --arg1 1 --arg2 2")
void optionTypeInteger(String command, boolean interactive) {
BaseShellSession<?> session = createSession(command, interactive);
assertScreenContainsText(session, "Hello '1' '2'");
}
@ParameterizedTest
@E2ESource(command = "option-type-enum --arg1 ONE", annox = false)
@E2ESource(command = "option-type-enum --arg1 ONE")
void optionTypeEnum(String command, boolean interactive) {
BaseShellSession<?> session = createSession(command, interactive);
assertScreenContainsText(session, "Hello ONE");
}
@ParameterizedTest
@E2ESource(command = "option-type-string-array --arg1 one two", annox = false)
@E2ESource(command = "option-type-string-array --arg1 one two")
void optionTypeStringArray(String command, boolean interactive) {
BaseShellSession<?> session = createSession(command, interactive);
assertScreenContainsText(session, "Hello [one,two]");
}
@ParameterizedTest
@E2ESource(command = "option-type-int-array --arg1 1 2", annox = false)
@E2ESource(command = "option-type-int-array --arg1 1 2")
void optionTypeIntArray(String command, boolean interactive) {
BaseShellSession<?> session = createSession(command, interactive);
assertScreenContainsText(session, "Hello [1,2]");
}
@ParameterizedTest
@E2ESource(command = "option-type-string-list --arg1 one two", annox = false)
@E2ESource(command = "option-type-string-list --arg1 one two")
void optionTypeStringList(String command, boolean interactive) {
BaseShellSession<?> session = createSession(command, interactive);
assertScreenContainsText(session, "Hello [one, two]");
}
@ParameterizedTest
@E2ESource(command = "option-type-string-set --arg1 one two", annox = false)
@E2ESource(command = "option-type-string-set --arg1 one two")
void optionTypeStringSet(String command, boolean interactive) {
BaseShellSession<?> session = createSession(command, interactive);
assertScreenContainsText(session, "Hello [one, two]");
}
@ParameterizedTest
@E2ESource(command = "option-type-string-collection --arg1 one two", annox = false)
@E2ESource(command = "option-type-string-collection --arg1 one two")
void optionTypeStringCollection(String command, boolean interactive) {
BaseShellSession<?> session = createSession(command, interactive);
assertScreenContainsText(session, "Hello [one, two]");