diff --git a/src/main/java/de/prob2/jupyter/CommandUtils.java b/src/main/java/de/prob2/jupyter/CommandUtils.java index 786e4f16fc2518ae20f6a9e65675b65b23f2153f..35713d5e2441416d480e1798ff8788ac838d2629 100644 --- a/src/main/java/de/prob2/jupyter/CommandUtils.java +++ b/src/main/java/de/prob2/jupyter/CommandUtils.java @@ -89,53 +89,61 @@ public final class CommandUtils { } } - private static <T> @NotNull String parseSingleArg(final @NotNull ParsedArguments parsed, final @NotNull String remainingArgs, final @NotNull PositionalParameter<T> param) { - final Parameter.ParseResult<T> parsedSingleArg = param.parse(remainingArgs); - parsed.put(param, parsedSingleArg.getParsedArg()); - return parsedSingleArg.getRemainingArgString(); - } - - private static <T> void checkParsedParameter(final @NotNull ParsedArguments parsed, final @NotNull PositionalParameter<T> param) { - if (!parsed.containsKey(param)) { - if (param.isOptional()) { - parsed.put(param, param.getDefaultValue()); - } else { - throw new UserErrorException("Missing required parameter " + param.getIdentifier()); - } - } - } - - public static @NotNull ParsedArguments parseArgs(final @NotNull Parameters parameters, final @NotNull String argString) { - final ParsedArguments parsed = new ParsedArguments(Collections.emptyMap()); + public static @NotNull SplitResult splitArgs(final @NotNull Parameters parameters, final @NotNull String argString) { + final SplitArguments splitArgs = new SplitArguments(Collections.emptyMap()); String remainingArgs; if (parameters.getBodyParam().isPresent()) { - final String[] split = argString.split("\n", 2); - final PositionalParameter.RequiredRemainder bodyParam = parameters.getBodyParam().get(); - if (split.length < 2) { - throw new UserErrorException("Missing required body " + bodyParam.getIdentifier()); + final String[] argsAndBody = argString.split("\n", 2); + remainingArgs = argsAndBody[0]; + if (argsAndBody.length > 1) { + splitArgs.add(parameters.getBodyParam().get(), argsAndBody[1]); } - remainingArgs = split[0]; - parsed.put(bodyParam, split[1]); } else { remainingArgs = argString; } - for (final PositionalParameter<?> param : parameters.getPositionalParameters()) { + for (int i = 0; i < parameters.getPositionalParameters().size();) { + final PositionalParameter<?> param = parameters.getPositionalParameters().get(i); if (remainingArgs.isEmpty()) { break; } - remainingArgs = parseSingleArg(parsed, remainingArgs, param); + final Parameter.SplitResult splitSingleArg = param.split(remainingArgs); + splitArgs.add(param, splitSingleArg.getSplitArg()); + remainingArgs = splitSingleArg.getRemainingArgString(); + + if (!param.isRepeating()) { + i++; + } } - if (!remainingArgs.isEmpty()) { - throw new UserErrorException("Expected at most " + parameters.getPositionalParameters().size() + " arguments, got extra argument: " + remainingArgs); + + return new SplitResult(splitArgs, remainingArgs); + } + + private static <T> void validateSplitParameter(final @NotNull ParsedArguments parsed, final @NotNull SplitArguments splitArgs, final @NotNull Parameter<T> param) { + parsed.put(param, param.validate(splitArgs.get(param))); + } + + public static @NotNull ParsedArguments validateSplitArgs(final @NotNull Parameters parameters, final SplitResult split) { + if (!split.getRemaining().isEmpty()) { + throw new UserErrorException("Expected at most " + parameters.getPositionalParameters().size() + " arguments, got extra argument: " + split.getRemaining()); } + + final ParsedArguments parsed = new ParsedArguments(Collections.emptyMap()); + for (final PositionalParameter<?> param : parameters.getPositionalParameters()) { - checkParsedParameter(parsed, param); + validateSplitParameter(parsed, split.getArguments(), param); } + + parameters.getBodyParam().ifPresent(bodyParam -> validateSplitParameter(parsed, split.getArguments(), bodyParam)); + return parsed; } + public static @NotNull ParsedArguments parseArgs(final @NotNull Parameters parameters, final @NotNull String argString) { + return validateSplitArgs(parameters, splitArgs(parameters, argString)); + } + public static @NotNull Map<@NotNull String, @NotNull String> parsePreferences(final @NotNull List<@NotNull String> args) { final Map<String, String> preferences = new HashMap<>(); for (final String arg : args) { diff --git a/src/main/java/de/prob2/jupyter/Parameter.java b/src/main/java/de/prob2/jupyter/Parameter.java index bb1fcb93a474bfe18f1b44919d2eb60692d3a84e..f609eb13a7f6e9dfaca9bc096e55e1dafe841992 100644 --- a/src/main/java/de/prob2/jupyter/Parameter.java +++ b/src/main/java/de/prob2/jupyter/Parameter.java @@ -1,23 +1,25 @@ package de.prob2.jupyter; +import java.util.List; + import com.google.common.base.MoreObjects; import org.jetbrains.annotations.NotNull; public abstract class Parameter<T> { - public static final class ParseResult<T> { - private final T parsedArg; + public static final class SplitResult { + private final @NotNull String splitArg; private final @NotNull String remainingArgString; - public ParseResult(final T parsedArg, final @NotNull String remainingArgString) { + public SplitResult(final @NotNull String splitArg, final @NotNull String remainingArgString) { super(); - this.parsedArg = parsedArg; + this.splitArg = splitArg; this.remainingArgString = remainingArgString; } - public T getParsedArg() { - return this.parsedArg; + public @NotNull String getSplitArg() { + return this.splitArg; } public @NotNull String getRemainingArgString() { @@ -37,11 +39,11 @@ public abstract class Parameter<T> { return this.identifier; } - public abstract boolean isOptional(); + public abstract boolean isRepeating(); - public abstract T getDefaultValue(); + public abstract Parameter.SplitResult split(final @NotNull String argString); - public abstract Parameter.ParseResult<T> parse(final @NotNull String argString); + public abstract T validate(final @NotNull List<@NotNull String> argValues); @Override public String toString() { diff --git a/src/main/java/de/prob2/jupyter/PositionalParameter.java b/src/main/java/de/prob2/jupyter/PositionalParameter.java index 35c89887dc6bd2d487f9a8e1996c0aa25e511401..f3d448e6b19f4484879437e63c08d1db4dc4f8c9 100644 --- a/src/main/java/de/prob2/jupyter/PositionalParameter.java +++ b/src/main/java/de/prob2/jupyter/PositionalParameter.java @@ -1,54 +1,72 @@ package de.prob2.jupyter; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Optional; import org.jetbrains.annotations.NotNull; public abstract class PositionalParameter<T> extends Parameter<T> { - public static final class RequiredSingle extends PositionalParameter<@NotNull String> { - public RequiredSingle(final @NotNull String identifier) { + public abstract static class ExactlyOne extends PositionalParameter<@NotNull String> { + protected ExactlyOne(final @NotNull String identifier) { super(identifier); } @Override - public boolean isOptional() { + public boolean isRepeating() { return false; } @Override - public @NotNull String getDefaultValue() { - throw new UnsupportedOperationException("Not an optional parameter"); + public @NotNull String validate(final @NotNull List<@NotNull String> argValues) { + if (argValues.isEmpty()) { + throw new UserErrorException("Missing required parameter " + this.getIdentifier()); + } else if (argValues.size() > 1) { + throw new AssertionError("Regular (single) required parameter " + this.getIdentifier() + " has more than one value, this should never happen!"); + } + + return argValues.get(0); + } + } + + public abstract static class ZeroOrOne extends PositionalParameter<@NotNull Optional<String>> { + protected ZeroOrOne(final @NotNull String identifier) { + super(identifier); + } + + @Override + public boolean isRepeating() { + return false; } @Override - public @NotNull Parameter.ParseResult<@NotNull String> parse(final @NotNull String argString) { - final String[] split = CommandUtils.ARG_SPLIT_PATTERN.split(argString, 2); - return new Parameter.ParseResult<>(split[0], split.length > 1 ? split[1] : ""); + public @NotNull Optional<String> validate(final @NotNull List<@NotNull String> argValues) { + if (argValues.size() > 1) { + throw new AssertionError("Regular (single) optional parameter " + this.getIdentifier() + " has more than one value, this should never happen!"); + } + + return argValues.stream().findAny(); } } - public static final class OptionalSingle extends PositionalParameter<@NotNull Optional<String>> { - public OptionalSingle(final @NotNull String identifier) { + public static final class RequiredSingle extends PositionalParameter.ExactlyOne { + public RequiredSingle(final @NotNull String identifier) { super(identifier); } @Override - public boolean isOptional() { - return true; + public @NotNull Parameter.SplitResult split(final @NotNull String argString) { + return splitOnce(argString); } - - @Override - public @NotNull Optional<String> getDefaultValue() { - return Optional.empty(); + } + + public static final class OptionalSingle extends PositionalParameter.ZeroOrOne { + public OptionalSingle(final @NotNull String identifier) { + super(identifier); } @Override - public @NotNull Parameter.ParseResult<@NotNull Optional<String>> parse(final @NotNull String argString) { - final String[] split = CommandUtils.ARG_SPLIT_PATTERN.split(argString, 2); - return new Parameter.ParseResult<>(Optional.of(split[0]), split.length > 1 ? split[1] : ""); + public @NotNull Parameter.SplitResult split(final @NotNull String argString) { + return splitOnce(argString); } } @@ -58,19 +76,22 @@ public abstract class PositionalParameter<T> extends Parameter<T> { } @Override - public boolean isOptional() { - return false; + public boolean isRepeating() { + return true; } @Override - public @NotNull List<@NotNull String> getDefaultValue() { - throw new UnsupportedOperationException("Not an optional parameter"); + public @NotNull Parameter.SplitResult split(final @NotNull String argString) { + return splitOnce(argString); } @Override - public @NotNull Parameter.ParseResult<@NotNull List<@NotNull String>> parse(final @NotNull String argString) { - final String[] split = CommandUtils.ARG_SPLIT_PATTERN.split(argString); - return new Parameter.ParseResult<>(Arrays.asList(split), ""); + public @NotNull List<@NotNull String> validate(final @NotNull List<@NotNull String> argValues) { + if (argValues.isEmpty()) { + throw new UserErrorException("Missing required parameter " + this.getIdentifier()); + } + + return argValues; } } @@ -80,61 +101,40 @@ public abstract class PositionalParameter<T> extends Parameter<T> { } @Override - public boolean isOptional() { + public boolean isRepeating() { return true; } @Override - public @NotNull List<@NotNull String> getDefaultValue() { - return Collections.emptyList(); + public @NotNull Parameter.SplitResult split(final @NotNull String argString) { + return splitOnce(argString); } @Override - public @NotNull Parameter.ParseResult<@NotNull List<@NotNull String>> parse(final @NotNull String argString) { - final String[] split = CommandUtils.ARG_SPLIT_PATTERN.split(argString); - return new Parameter.ParseResult<>(Arrays.asList(split), ""); + public @NotNull List<@NotNull String> validate(final @NotNull List<@NotNull String> argValues) { + return argValues; } } - public static final class RequiredRemainder extends PositionalParameter<@NotNull String> { + public static final class RequiredRemainder extends PositionalParameter.ExactlyOne { public RequiredRemainder(final @NotNull String identifier) { super(identifier); } @Override - public boolean isOptional() { - return false; - } - - @Override - public @NotNull String getDefaultValue() { - throw new UnsupportedOperationException("Not an optional parameter"); - } - - @Override - public Parameter.ParseResult<@NotNull String> parse(final @NotNull String argString) { - return new Parameter.ParseResult<>(argString, ""); + public @NotNull Parameter.SplitResult split(final @NotNull String argString) { + return new Parameter.SplitResult(argString, ""); } } - public static final class OptionalRemainder extends PositionalParameter<@NotNull Optional<String>> { + public static final class OptionalRemainder extends PositionalParameter.ZeroOrOne { public OptionalRemainder(final @NotNull String identifier) { super(identifier); } @Override - public boolean isOptional() { - return true; - } - - @Override - public @NotNull Optional<String> getDefaultValue() { - return Optional.empty(); - } - - @Override - public Parameter.ParseResult<@NotNull Optional<String>> parse(final @NotNull String argString) { - return new Parameter.ParseResult<>(Optional.of(argString), ""); + public @NotNull Parameter.SplitResult split(final @NotNull String argString) { + return new Parameter.SplitResult(argString, ""); } } @@ -142,4 +142,9 @@ public abstract class PositionalParameter<T> extends Parameter<T> { super(identifier); } + @NotNull + static Parameter.SplitResult splitOnce(final @NotNull String argString) { + final String[] split = CommandUtils.ARG_SPLIT_PATTERN.split(argString, 2); + return new Parameter.SplitResult(split[0], split.length > 1 ? split[1] : ""); + } } diff --git a/src/main/java/de/prob2/jupyter/SplitArguments.java b/src/main/java/de/prob2/jupyter/SplitArguments.java new file mode 100644 index 0000000000000000000000000000000000000000..906b1fb7860efca50c9a300f3c679f8826c3db91 --- /dev/null +++ b/src/main/java/de/prob2/jupyter/SplitArguments.java @@ -0,0 +1,40 @@ +package de.prob2.jupyter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.common.base.MoreObjects; + +import org.jetbrains.annotations.NotNull; + +public final class SplitArguments { + private final @NotNull Map<@NotNull Parameter<?>, @NotNull List<@NotNull String>> values; + + public SplitArguments(final @NotNull Map<@NotNull Parameter<?>, @NotNull List<@NotNull String>> values) { + super(); + + this.values = new HashMap<>(values); + } + + public boolean containsKey(final @NotNull Parameter<?> parameter) { + return this.values.containsKey(parameter); + } + + public @NotNull List<@NotNull String> get(final @NotNull Parameter<?> parameter) { + return this.values.getOrDefault(parameter, Collections.emptyList()); + } + + public void add(final @NotNull Parameter<?> parameter, final String value) { + this.values.computeIfAbsent(parameter, p -> new ArrayList<>()).add(value); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("values", this.values) + .toString(); + } +} diff --git a/src/main/java/de/prob2/jupyter/SplitResult.java b/src/main/java/de/prob2/jupyter/SplitResult.java new file mode 100644 index 0000000000000000000000000000000000000000..deed42c5d62fe013f2f959fba02cfdf1074d8f1b --- /dev/null +++ b/src/main/java/de/prob2/jupyter/SplitResult.java @@ -0,0 +1,23 @@ +package de.prob2.jupyter; + +import org.jetbrains.annotations.NotNull; + +public final class SplitResult { + private final @NotNull SplitArguments arguments; + private final @NotNull String remaining; + + public SplitResult(final @NotNull SplitArguments arguments, final @NotNull String remaining) { + super(); + + this.arguments = arguments; + this.remaining = remaining; + } + + public @NotNull SplitArguments getArguments() { + return this.arguments; + } + + public @NotNull String getRemaining() { + return this.remaining; + } +}