From 4def47231a10a5ccecd436ea6b2cb0b8730896d8 Mon Sep 17 00:00:00 2001 From: jbock Date: Sun, 30 Apr 2023 19:12:05 +0200 Subject: [PATCH] add options parseOrExitMethodAcceptsList and enableAtFileExpansion --- .../net/jbock/processor/SourceElement.java | 20 +++++++++ .../writing/HasCommandRepresentation.java | 8 ++++ .../net/jbock/writing/ParseOrExitMethod.java | 45 ++++++++++++++----- .../jbock/processor/ParseOrExitFullTest.java | 28 +++++------- .../java/net/jbock/examples/GitCommand.java | 3 +- jbock/src/main/java/net/jbock/Command.java | 11 +++++ .../src/main/java/net/jbock/SuperCommand.java | 11 +++++ .../java/net/jbock/util/ParseRequest.java | 11 +++++ .../java/net/jbock/util/ParseRequestTest.java | 25 +++++++++++ 9 files changed, 131 insertions(+), 31 deletions(-) create mode 100644 jbock/src/test/java/net/jbock/util/ParseRequestTest.java diff --git a/compiler/src/main/java/net/jbock/processor/SourceElement.java b/compiler/src/main/java/net/jbock/processor/SourceElement.java index ae6848fa..b3bd4278 100644 --- a/compiler/src/main/java/net/jbock/processor/SourceElement.java +++ b/compiler/src/main/java/net/jbock/processor/SourceElement.java @@ -92,6 +92,18 @@ boolean isSkipGeneratingParseOrExitMethod() { SuperCommand::skipGeneratingParseOrExitMethod); } + boolean isParseOrExitMethodAcceptsList() { + return command.fold( + Command::parseOrExitMethodAcceptsList, + SuperCommand::parseOrExitMethodAcceptsList); + } + + boolean isEnableAtFileExpansion() { + return command.fold( + Command::enableAtFileExpansion, + SuperCommand::enableAtFileExpansion); + } + String[] getDescription() { return command.fold( Command::description, @@ -150,4 +162,12 @@ public List description() { public boolean skipGeneratingParseOrExitMethod() { return command.isSkipGeneratingParseOrExitMethod(); } + + public boolean parseOrExitMethodAcceptsList() { + return command.isParseOrExitMethodAcceptsList(); + } + + public boolean enableAtFileExpansion() { + return command.isEnableAtFileExpansion(); + } } diff --git a/compiler/src/main/java/net/jbock/writing/HasCommandRepresentation.java b/compiler/src/main/java/net/jbock/writing/HasCommandRepresentation.java index 32b32ec5..32cba449 100644 --- a/compiler/src/main/java/net/jbock/writing/HasCommandRepresentation.java +++ b/compiler/src/main/java/net/jbock/writing/HasCommandRepresentation.java @@ -50,4 +50,12 @@ final List> allMappings() { final boolean isSuperCommand() { return commandRepresentation.sourceElement().isSuperCommand(); } + + final boolean parseOrExitMethodAcceptsList() { + return commandRepresentation.sourceElement().parseOrExitMethodAcceptsList(); + } + + final boolean enableAtFileExpansion() { + return commandRepresentation.sourceElement().enableAtFileExpansion(); + } } diff --git a/compiler/src/main/java/net/jbock/writing/ParseOrExitMethod.java b/compiler/src/main/java/net/jbock/writing/ParseOrExitMethod.java index a27fcc8c..0607ce12 100644 --- a/compiler/src/main/java/net/jbock/writing/ParseOrExitMethod.java +++ b/compiler/src/main/java/net/jbock/writing/ParseOrExitMethod.java @@ -9,8 +9,11 @@ import net.jbock.util.AtFileError; import net.jbock.util.ParseRequest; +import java.util.List; + import static io.jbock.javapoet.MethodSpec.methodBuilder; import static io.jbock.javapoet.ParameterSpec.builder; +import static net.jbock.common.Constants.LIST_OF_STRING; import static net.jbock.common.Constants.STRING; final class ParseOrExitMethod extends HasCommandRepresentation { @@ -33,26 +36,44 @@ final class ParseOrExitMethod extends HasCommandRepresentation { MethodSpec define() { - ParameterSpec args = builder(ArrayTypeName.of(STRING), "args").build(); + ParameterSpec args = parseOrExitMethodAcceptsList() ? + builder(LIST_OF_STRING, "args").build() : + builder(ArrayTypeName.of(STRING), "args").build(); ParameterSpec notSuccess = builder(generatedTypes.parseResultType(), "failure").build(); ParameterSpec err = builder(AtFileError.class, "err").build(); CodeBlock.Builder code = CodeBlock.builder(); - code.beginControlFlow("if ($1N.length > 0 && $2S.equals($1N[0]))", args, "--help") - .add("$T.builder().build()\n", StandardErrorHandler.class).indent() + if (parseOrExitMethodAcceptsList()) { + code.beginControlFlow("if (!$1N.isEmpty() && $2S.equals($1N.get(0)))", args, "--help"); + } else { + code.beginControlFlow("if ($1N.length > 0 && $2S.equals($1N[0]))", args, "--help"); + } + code.add("$T.builder().build()\n", StandardErrorHandler.class).indent() .add(".printUsageDocumentation($N());\n", createModelMethod.get()).unindent() .addStatement("$T.exit(0)", System.class) .endControlFlow(); - code.add("return $T.from($N).expand()\n", ParseRequest.class, args).indent() - .add(".mapLeft($1N -> $1N.addModel($2N()))\n", err, createModelMethod.get()) - .add(".flatMap(this::$N)\n", parseMethod.get()) - .add(".orElseThrow($N -> {\n", notSuccess).indent() - .addStatement("$T.builder().build().printErrorMessage($N)", - StandardErrorHandler.class, notSuccess) - .addStatement("$T.exit(1)", System.class) - .addStatement("return new $T()", RuntimeException.class).unindent() - .addStatement("})").unindent(); + if (enableAtFileExpansion()) { + code.add("return $T.from($N).expand()\n", ParseRequest.class, args).indent() + .add(".mapLeft($1N -> $1N.addModel($2N()))\n", err, createModelMethod.get()) + .add(".flatMap(this::$N)\n", parseMethod.get()) + .add(".orElseThrow($N -> {\n", notSuccess).indent() + .addStatement("$T.builder().build().printErrorMessage($N)", + StandardErrorHandler.class, notSuccess) + .addStatement("$T.exit(1)", System.class) + .addStatement("return new $T()", RuntimeException.class).unindent() + .addStatement("})").unindent(); + } else { + CodeBlock pArgs = parseOrExitMethodAcceptsList() ? + CodeBlock.of("$N", args) : + CodeBlock.of("$T.of($N)", List.class, args); + code.add("return parse($L).orElseThrow($N -> {\n", pArgs, notSuccess).indent() + .addStatement("$T.builder().build().printErrorMessage($N)", + StandardErrorHandler.class, notSuccess) + .addStatement("$T.exit(1)", System.class) + .addStatement("return new $T()", RuntimeException.class).unindent() + .addStatement("})"); + } return methodBuilder("parseOrExit").addParameter(args) .addModifiers(sourceElement().accessModifiers()) .returns(generatedTypes.sourceElement().typeName()) diff --git a/compiler/src/test/java/net/jbock/processor/ParseOrExitFullTest.java b/compiler/src/test/java/net/jbock/processor/ParseOrExitFullTest.java index 8917e969..ce6813d6 100644 --- a/compiler/src/test/java/net/jbock/processor/ParseOrExitFullTest.java +++ b/compiler/src/test/java/net/jbock/processor/ParseOrExitFullTest.java @@ -38,7 +38,6 @@ void testBasicGenerated() { "import net.jbock.parse.VarargsParameterParser;", "import net.jbock.util.ExConvert;", "import net.jbock.util.ExFailure;", - "import net.jbock.util.ParseRequest;", "import net.jbock.util.ParsingFailed;", "", "@Generated(", @@ -62,14 +61,11 @@ void testBasicGenerated() { " .printUsageDocumentation(createModel());", " System.exit(0);", " }", - " return ParseRequest.from(args).expand()", - " .mapLeft(err -> err.addModel(createModel()))", - " .flatMap(this::parse)", - " .orElseThrow(failure -> {", - " StandardErrorHandler.builder().build().printErrorMessage(failure);", - " System.exit(1);", - " return new RuntimeException();", - " });", + " return parse(List.of(args)).orElseThrow(failure -> {", + " StandardErrorHandler.builder().build().printErrorMessage(failure);", + " System.exit(1);", + " return new RuntimeException();", + " });", " }", "", " CommandModel createModel() {", @@ -130,7 +126,6 @@ void testPublicParser() { "import net.jbock.parse.VarargsParameterParser;", "import net.jbock.util.ExConvert;", "import net.jbock.util.ExFailure;", - "import net.jbock.util.ParseRequest;", "import net.jbock.util.ParsingFailed;", "", "@Generated(", @@ -154,14 +149,11 @@ void testPublicParser() { " .printUsageDocumentation(createModel());", " System.exit(0);", " }", - " return ParseRequest.from(args).expand()", - " .mapLeft(err -> err.addModel(createModel()))", - " .flatMap(this::parse)", - " .orElseThrow(failure -> {", - " StandardErrorHandler.builder().build().printErrorMessage(failure);", - " System.exit(1);", - " return new RuntimeException();", - " });", + " return parse(List.of(args)).orElseThrow(failure -> {", + " StandardErrorHandler.builder().build().printErrorMessage(failure);", + " System.exit(1);", + " return new RuntimeException();", + " });", " }", "", " public CommandModel createModel() {", diff --git a/examples/src/main/java/net/jbock/examples/GitCommand.java b/examples/src/main/java/net/jbock/examples/GitCommand.java index 4757d86e..b3b72631 100644 --- a/examples/src/main/java/net/jbock/examples/GitCommand.java +++ b/examples/src/main/java/net/jbock/examples/GitCommand.java @@ -14,7 +14,8 @@ interface GitCommand { @Command( name = "git-add", - description = "Add file contents to the index") + description = "Add file contents to the index", + parseOrExitMethodAcceptsList = true) interface AddCommand { @VarargsParameter List pathspec(); diff --git a/jbock/src/main/java/net/jbock/Command.java b/jbock/src/main/java/net/jbock/Command.java index fad6e3ea..6ba61716 100644 --- a/jbock/src/main/java/net/jbock/Command.java +++ b/jbock/src/main/java/net/jbock/Command.java @@ -56,6 +56,17 @@ */ boolean skipGeneratingParseOrExitMethod() default false; + /** + * If {@code true}, the generated {@code parseOrExit} method + * will accept {@code List} instead of {@code String[]}. + */ + boolean parseOrExitMethodAcceptsList() default false; + + /** + * Set to {@code true} to enable at-file expansion. + */ + boolean enableAtFileExpansion() default false; + /** * If {@code true}, the generated parser class will be * {@code public}. Otherwise, it will be package-private. diff --git a/jbock/src/main/java/net/jbock/SuperCommand.java b/jbock/src/main/java/net/jbock/SuperCommand.java index 85573039..eeaf32cf 100644 --- a/jbock/src/main/java/net/jbock/SuperCommand.java +++ b/jbock/src/main/java/net/jbock/SuperCommand.java @@ -52,6 +52,17 @@ */ boolean skipGeneratingParseOrExitMethod() default false; + /** + * If {@code true}, the generated {@code parseOrExit} method + * will accept {@code List} instead of {@code String[]}. + */ + boolean parseOrExitMethodAcceptsList() default false; + + /** + * Set to {@code true} to enable at-file expansion. + */ + boolean enableAtFileExpansion() default false; + /** * @return {@code true} if public parser should be generated * @see Command#publicParser() diff --git a/jbock/src/main/java/net/jbock/util/ParseRequest.java b/jbock/src/main/java/net/jbock/util/ParseRequest.java index 80579e8a..52287a1e 100644 --- a/jbock/src/main/java/net/jbock/util/ParseRequest.java +++ b/jbock/src/main/java/net/jbock/util/ParseRequest.java @@ -32,6 +32,17 @@ public static ParseRequest from(String[] args) { return new ParseRequestSimple(List.of(args)); } + public static ParseRequest from(List args) { + if (args.size() >= 1 + && args.get(0).length() >= 2 + && args.get(0).startsWith("@")) { + String fileName = args.get(0).substring(1); + List rest = args.subList(1, args.size()); + return new ParseRequestExpand(Paths.get(fileName), rest); + } + return new ParseRequestSimple(args); + } + /** * Returns a Right containing the result of {@code @-file} expansion. * If an error occurs during {@code @-file} reading, returns a Left containing diff --git a/jbock/src/test/java/net/jbock/util/ParseRequestTest.java b/jbock/src/test/java/net/jbock/util/ParseRequestTest.java new file mode 100644 index 00000000..e26d12c1 --- /dev/null +++ b/jbock/src/test/java/net/jbock/util/ParseRequestTest.java @@ -0,0 +1,25 @@ +package net.jbock.util; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ParseRequestTest { + + @Test + void testParseList() { + ParseRequest parseRequest = ParseRequest.from(List.of("a")); + assertTrue(parseRequest.expand().isRight()); + assertEquals(parseRequest.expand().getRight().orElseThrow(), List.of("a")); + } + + @Test + void testParseArray() { + ParseRequest parseRequest = ParseRequest.from(new String[]{"a"}); + assertTrue(parseRequest.expand().isRight()); + assertEquals(parseRequest.expand().getRight().orElseThrow(), List.of("a")); + } +}