diff --git a/endgen-dist/pom.xml b/endgen-dist/pom.xml index dda6922..8a2fedb 100644 --- a/endgen-dist/pom.xml +++ b/endgen-dist/pom.xml @@ -5,7 +5,7 @@ nu.zoom.dsl endgen - 1.2-SNAPSHOT + 1.3-SNAPSHOT endgen-dist diff --git a/endgen-maven-plugin/README.md b/endgen-maven-plugin/README.md new file mode 100644 index 0000000..3df592d --- /dev/null +++ b/endgen-maven-plugin/README.md @@ -0,0 +1,35 @@ +# Configure + +Add the following to your `pom.xml` + +```xml + + + + nu.zoom.dsl + endgen-maven-plugin + 1.2-SNAPSHOT + + + + endgen + + + ${project.basedir}/src/main/endpoint-templates + ${project.basedir}/src/main/endgen/test01.endpoints + + + + + + +``` + +Replace the `` with the latest published version of the endgen plugin. + +* `templates` should point to the template directory to use. +* `dsl` should be the file to generate code from. +* `output` can be used to specify the directory where the generated files are written. Default is `${project.build.directory}/generated-sources/endgen`. +* `parser` can be used to force the use of either the `Endpoints` or the `States` parser. Default is to determined by looking at the file ending of the dsl-file. + +If you have several DSL-files that you wish to generate from you can repeat the `` block with other configurations. \ No newline at end of file diff --git a/endgen-maven-plugin/pom.xml b/endgen-maven-plugin/pom.xml new file mode 100644 index 0000000..9974c25 --- /dev/null +++ b/endgen-maven-plugin/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + nu.zoom.dsl + endgen + 1.3-SNAPSHOT + + endgen-maven-plugin + maven-plugin + + + 3.15.1 + + + + org.apache.maven + maven-plugin-api + 3.9.9 + provided + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven-plugin-tools.version} + provided + + + nu.zoom.dsl + parser + ${project.parent.version} + + + \ No newline at end of file diff --git a/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/EndgenMojo.java b/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/EndgenMojo.java new file mode 100644 index 0000000..757eee5 --- /dev/null +++ b/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/EndgenMojo.java @@ -0,0 +1,68 @@ +package nu.zoom.dsl.maven; + +import nu.zoom.dsl.run.Runner; +import nu.zoom.dsl.run.ValidationException; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +import java.io.File; +import java.util.Optional; + +@Mojo( + name = "endgen", + defaultPhase = LifecyclePhase.GENERATE_SOURCES +) +public class EndgenMojo extends AbstractMojo { + @Parameter( + name = "templates", + defaultValue = "${project.build.sourceDirectory}/main/endgen-templates" + ) + File templates; + + @Parameter( + name = "output", + defaultValue = "${project.build.directory}/generated-sources/endgen" + ) + File output; + + @Parameter(name = "dsl", required = true) + File dsl; + + @Parameter(name = "parser") + String parser; + + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + try { + Runner.run( + optional(dsl).map(File::toPath).orElseThrow(), + optional(templates).map(File::toPath), + optional(output).map(File::toPath), + getParserType(parser), + new MavenLogger(getLog()) + ); + } catch (Exception e) { + throw new MojoExecutionException(e.getMessage(), e); + } + } + + private Optional getParserType(final String type) throws ValidationException { + if (type == null) { + return Optional.empty(); + } + try { + return Optional.of(Runner.ParserType.valueOf(type)); + } catch (IllegalArgumentException e) { + throw new ValidationException(e); + } + } + + private Optional optional(T arg) { + return arg == null ? Optional.empty() : Optional.of(arg); + } +} diff --git a/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/MavenLogger.java b/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/MavenLogger.java new file mode 100644 index 0000000..03b98be --- /dev/null +++ b/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/MavenLogger.java @@ -0,0 +1,17 @@ +package nu.zoom.dsl.maven; + +import nu.zoom.dsl.run.Logger; +import org.apache.maven.plugin.logging.Log; + +public class MavenLogger implements Logger { + private final Log delegate; + + public MavenLogger(Log delegate) { + this.delegate = delegate; + } + + @Override + public void println(String message) { + this.delegate.debug(message); + } +} diff --git a/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/Run.java b/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/Run.java new file mode 100644 index 0000000..c8a0779 --- /dev/null +++ b/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/Run.java @@ -0,0 +1,52 @@ +package nu.zoom.dsl.maven; + +import java.io.File; + +public class Run { + private File templates; + private File output; + private File dsl; + private String parser; + + public File getTemplates() { + return templates; + } + + public void setTemplates(File templates) { + this.templates = templates; + } + + public File getOutput() { + return output; + } + + public void setOutput(File output) { + this.output = output; + } + + public File getDsl() { + return dsl; + } + + public void setDsl(File dsl) { + this.dsl = dsl; + } + + public String getParser() { + return parser; + } + + public void setParser(String parser) { + this.parser = parser; + } + + @Override + public String toString() { + return "Run{" + + "templates=" + templates + + ", output=" + output + + ", dsl=" + dsl + + ", parser='" + parser + '\'' + + '}'; + } +} diff --git a/parser/pom.xml b/parser/pom.xml index ba9d182..67c6925 100644 --- a/parser/pom.xml +++ b/parser/pom.xml @@ -20,7 +20,7 @@ nu.zoom.dsl endgen - 1.2-SNAPSHOT + 1.3-SNAPSHOT parser diff --git a/parser/src/main/java/nu/zoom/dsl/cli/EndpointsCLI.java b/parser/src/main/java/nu/zoom/dsl/cli/EndpointsCLI.java index e3d5794..8d79757 100644 --- a/parser/src/main/java/nu/zoom/dsl/cli/EndpointsCLI.java +++ b/parser/src/main/java/nu/zoom/dsl/cli/EndpointsCLI.java @@ -14,7 +14,7 @@ package nu.zoom.dsl.cli; import nu.zoom.dsl.ast.DocumentNode; -import nu.zoom.dsl.ast.ParserWrapper; +import nu.zoom.dsl.run.*; import nu.zoom.dsl.freemarker.Generator; import picocli.CommandLine; import picocli.CommandLine.Command; @@ -24,8 +24,8 @@ import picocli.CommandLine.Parameters; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; +import java.util.Optional; import java.util.concurrent.Callable; @Command( @@ -34,20 +34,16 @@ import java.util.concurrent.Callable; description = "Generate source code from an endpoints specification file." ) public class EndpointsCLI implements Callable { - public enum ParserType { - Endpoints, - States - } @SuppressWarnings("unused") @Parameters(index = "0", description = "The source endpoints DSL file.") private Path file; @SuppressWarnings("CanBeFinal") - @Option(names = {"-t", "--template"}, defaultValue = "endpoints-template", description = "The template directory. Default is ${DEFAULT-VALUE}") + @Option(names = {"-t", "--template"}, defaultValue = Runner.DEFAULT_TEMPLATE_DIRECTORY_NAME, description = "The template directory. Default is ${DEFAULT-VALUE}") private Path templateDir ; @SuppressWarnings("CanBeFinal") - @Option(names = {"-o", "--output"}, defaultValue = "endpoints-output", description = "The directory to write the generated code to. Default is ${DEFAULT-VALUE}") + @Option(names = {"-o", "--output"}, defaultValue = Runner.DEFAULT_OUTPUT_DIRECTORY_NAME, description = "The directory to write the generated code to. Default is ${DEFAULT-VALUE}") private Path outputDir ; @SuppressWarnings("unused") @@ -55,7 +51,7 @@ public class EndpointsCLI implements Callable { private Boolean verbose = false; @Option(names = {"-p", "--parser"}, description = "Force use of a specific parser instead of determining from filename. Valid values: ${COMPLETION-CANDIDATES}.") - private ParserType parser = null; + private Runner.ParserType parser = null; public static void main(String[] args) { int exitCode = new CommandLine(new EndpointsCLI()).execute(args); @@ -65,32 +61,14 @@ public class EndpointsCLI implements Callable { @Override public Integer call() { try { - validateTemplateDirectory(); - validateInputFile(); - validateOutputDirectory(); - verbose("Parsing: " + file.toAbsolutePath()); - if (parser == null) { - if (file.getFileName().toString().endsWith(".states")) { - parser = ParserType.States; - } - } - final DocumentNode rootNode ; - if (parser == ParserType.States) { - verbose("using state grammar.") ; - rootNode = ParserWrapper.parseStates(file); - } else { - verbose("using endpoints grammar.") ; - rootNode = ParserWrapper.parseEndpoints(file); - } - verbose("AST: " + rootNode); - verbose("Generating from templates in: " + templateDir.toAbsolutePath()); - Generator generator = new Generator(templateDir, rootNode, outputDir); - List generatedPaths = generator.generate(); - if (generatedPaths.isEmpty()) { - System.out.println("No generated paths found."); - } else { - generatedPaths.forEach(p -> verbose("Generated: " + p.toAbsolutePath())); - } + final Logger logger = this.verbose ? new StdoutLogger() : new NullLogger() ; + Runner.run( + this.file, + Optional.of(this.templateDir), + Optional.of(this.outputDir), + parser == null ? Optional.empty() : Optional.of(parser), + logger + ); return 0; } catch (Exception e) { System.err.println(e.getMessage()); diff --git a/parser/src/main/java/nu/zoom/dsl/run/EndgenException.java b/parser/src/main/java/nu/zoom/dsl/run/EndgenException.java new file mode 100644 index 0000000..7771e11 --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/run/EndgenException.java @@ -0,0 +1,15 @@ +package nu.zoom.dsl.run; + +public abstract class EndgenException extends Exception { + public EndgenException(String message, Throwable cause) { + super(message, cause); + } + + public EndgenException(Throwable cause) { + super(cause); + } + + public EndgenException(String message) { + super(message); + } +} diff --git a/parser/src/main/java/nu/zoom/dsl/run/GeneratorException.java b/parser/src/main/java/nu/zoom/dsl/run/GeneratorException.java new file mode 100644 index 0000000..5157d97 --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/run/GeneratorException.java @@ -0,0 +1,15 @@ +package nu.zoom.dsl.run; + +public class GeneratorException extends EndgenException { + public GeneratorException(String message) { + super(message); + } + + public GeneratorException(String message, Throwable cause) { + super(message, cause); + } + + public GeneratorException(Throwable cause) { + super(cause); + } +} diff --git a/parser/src/main/java/nu/zoom/dsl/run/Logger.java b/parser/src/main/java/nu/zoom/dsl/run/Logger.java new file mode 100644 index 0000000..80826d3 --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/run/Logger.java @@ -0,0 +1,5 @@ +package nu.zoom.dsl.run; + +public interface Logger { + void println(String message); +} diff --git a/parser/src/main/java/nu/zoom/dsl/run/NullLogger.java b/parser/src/main/java/nu/zoom/dsl/run/NullLogger.java new file mode 100644 index 0000000..014f473 --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/run/NullLogger.java @@ -0,0 +1,8 @@ +package nu.zoom.dsl.run; + +public class NullLogger implements Logger { + @Override + public void println(String message) { + + } +} diff --git a/parser/src/main/java/nu/zoom/dsl/run/ParserException.java b/parser/src/main/java/nu/zoom/dsl/run/ParserException.java new file mode 100644 index 0000000..b726384 --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/run/ParserException.java @@ -0,0 +1,15 @@ +package nu.zoom.dsl.run; + +public class ParserException extends EndgenException { + public ParserException(String message) { + super(message); + } + + public ParserException(String message, Throwable cause) { + super(message, cause); + } + + public ParserException(Throwable cause) { + super(cause); + } +} diff --git a/parser/src/main/java/nu/zoom/dsl/ast/ParserWrapper.java b/parser/src/main/java/nu/zoom/dsl/run/ParserWrapper.java similarity index 93% rename from parser/src/main/java/nu/zoom/dsl/ast/ParserWrapper.java rename to parser/src/main/java/nu/zoom/dsl/run/ParserWrapper.java index 11a95fd..ce62d9c 100644 --- a/parser/src/main/java/nu/zoom/dsl/ast/ParserWrapper.java +++ b/parser/src/main/java/nu/zoom/dsl/run/ParserWrapper.java @@ -11,8 +11,11 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -package nu.zoom.dsl.ast; +package nu.zoom.dsl.run; +import nu.zoom.dsl.ast.DocumentNode; +import nu.zoom.dsl.ast.EndpointsVisitorTransformer; +import nu.zoom.dsl.ast.StatesVisitorTransformer; import nu.zoom.dsl.parser.*; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; diff --git a/parser/src/main/java/nu/zoom/dsl/run/Runner.java b/parser/src/main/java/nu/zoom/dsl/run/Runner.java new file mode 100644 index 0000000..ebf162b --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/run/Runner.java @@ -0,0 +1,96 @@ +package nu.zoom.dsl.run; + +import freemarker.template.TemplateException; +import nu.zoom.dsl.ast.DocumentNode; +import nu.zoom.dsl.freemarker.Generator; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; + +public final class Runner { + public static final String DEFAULT_TEMPLATE_DIRECTORY_NAME = "endpoints-template"; + public static final String DEFAULT_OUTPUT_DIRECTORY_NAME = "endpoints-output"; + + public static void run( + Path dsl, + Optional templates, + Optional output, + Optional maybeParser, + Logger logger + ) throws ValidationException, IOException, GeneratorException { + Path templatesDir = templates.orElse(Paths.get(DEFAULT_TEMPLATE_DIRECTORY_NAME)); + Path outputDir = output.orElse(Paths.get(DEFAULT_OUTPUT_DIRECTORY_NAME)); + + validateOutputDirectory(outputDir); + validateTemplateDirectory(templatesDir); + validateInputFile(dsl); + logger.println("Parsing: " + dsl.toAbsolutePath()); + + final ParserType parser = + maybeParser.orElseGet( + () -> { + if (dsl.getFileName().toString().endsWith(".states")) { + return ParserType.States; + } else { + return ParserType.Endpoints; + } + } + ); + + final DocumentNode rootNode; + if (parser == ParserType.States) { + logger.println("using state grammar."); + rootNode = ParserWrapper.parseStates(dsl); + } else { + logger.println("using endpoints grammar."); + rootNode = ParserWrapper.parseEndpoints(dsl); + } + logger.println("AST: " + rootNode); + logger.println("Generating from templates in: " + templatesDir.toAbsolutePath()); + Generator generator = new Generator(templatesDir, rootNode, outputDir); + List generatedPaths = null; + try { + generatedPaths = generator.generate(); + } catch (TemplateException e) { + throw new GeneratorException(e); + } + if (generatedPaths.isEmpty()) { + System.out.println("No generated paths found."); + } else { + generatedPaths.forEach(p -> logger.println("Generated: " + p.toAbsolutePath())); + } + } + + private static void validateOutputDirectory(Path outputDir) throws IOException, ValidationException { + if (Files.notExists(outputDir)) { + Files.createDirectories(outputDir); + } + if (!Files.isDirectory(outputDir)) { + throw new ValidationException("Output directory: '" + outputDir + " 'is not a directory."); + } + } + + private static void validateTemplateDirectory(Path templateDir) throws ValidationException { + if (!Files.isDirectory(templateDir)) { + throw new ValidationException("Template directory '" + templateDir + "' is not a directory."); + } + } + + private static void validateInputFile(Path file) throws ValidationException { + if (Files.notExists(file)) { + throw new ValidationException("Input file '" + file + "' does not exist."); + } + if (!Files.isReadable(file) || !Files.isRegularFile(file)) { + throw new ValidationException("Input file '" + file + "' is not a readable file."); + } + } + + public enum ParserType { + Endpoints, + States + } +} diff --git a/parser/src/main/java/nu/zoom/dsl/run/StdoutLogger.java b/parser/src/main/java/nu/zoom/dsl/run/StdoutLogger.java new file mode 100644 index 0000000..7b0b7d4 --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/run/StdoutLogger.java @@ -0,0 +1,9 @@ +package nu.zoom.dsl.run; + +public class StdoutLogger implements Logger { + + @Override + public void println(String message) { + System.out.println(message); + } +} diff --git a/parser/src/main/java/nu/zoom/dsl/run/ValidationException.java b/parser/src/main/java/nu/zoom/dsl/run/ValidationException.java new file mode 100644 index 0000000..1627d72 --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/run/ValidationException.java @@ -0,0 +1,15 @@ +package nu.zoom.dsl.run; + +public class ValidationException extends EndgenException { + public ValidationException(String message) { + super(message); + } + + public ValidationException(String message, Throwable cause) { + super(message, cause); + } + + public ValidationException(Throwable cause) { + super(cause); + } +} diff --git a/pom.xml b/pom.xml index 86744cc..82a7a48 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ nu.zoom.dsl endgen - 1.2-SNAPSHOT + 1.3-SNAPSHOT pom @@ -65,6 +65,7 @@ parser endgen-dist + endgen-maven-plugin diff --git a/sample-maven/pom.xml b/sample-maven/pom.xml new file mode 100644 index 0000000..99fdbab --- /dev/null +++ b/sample-maven/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + nu.zoom.dsl + sample-maven + 1.0-SNAPSHOT + jar + + + 21 + 21 + UTF-8 + + + + + ASF 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + + + + + + Johan Maasing + johan@zoom.nu + + developer + + + + + + + + nu.zoom.dsl + endgen-maven-plugin + 1.2-SNAPSHOT + + + + endgen + + + ${project.basedir}/src/main/endpoint-templates + ${project.basedir}/src/main/endgen/test01.endpoints + + + + + + + + diff --git a/sample-maven/src/main/endgen/test01.endpoints b/sample-maven/src/main/endgen/test01.endpoints new file mode 100644 index 0000000..6f212ed --- /dev/null +++ b/sample-maven/src/main/endgen/test01.endpoints @@ -0,0 +1,28 @@ +/* + Copyright 2025 "Johan Maasing" + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +{ + some: configvalue, + someother: value, + package: se.rutdev.senash +} + +/some/endpoint <- SomeType(foo:String) +Embedded(foo:Bar) +/some/other/endpoint <- (bar:Seq[Embedded]) +/yet/other/endpoint2 <- (bar2:Seq[AType]) -> NamedResponse(foo:Bar) +AType(data: java.util.List) +/yet/other/endpoint3 <- (bar2:Seq[AType]) -> (foo:Bar) diff --git a/sample-maven/src/main/endgen/test01.states b/sample-maven/src/main/endgen/test01.states new file mode 100644 index 0000000..3b9a67a --- /dev/null +++ b/sample-maven/src/main/endgen/test01.states @@ -0,0 +1,21 @@ +/* + Copyright 2025 "Johan Maasing" + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +{ title: SomeNodes, package: nu.zoom.dsl.states } + +start(s:S) -> message(foo:foo) -> middle(foo:foo) , +middle -> selfmessage(bar:bar) -> middle(bar:bar), +middle -> message(bar:baz) -> end diff --git a/sample-maven/src/main/endpoint-templates/nu/zoom/dsl/sample/Endpoints.java.ftl b/sample-maven/src/main/endpoint-templates/nu/zoom/dsl/sample/Endpoints.java.ftl new file mode 100644 index 0000000..624c722 --- /dev/null +++ b/sample-maven/src/main/endpoint-templates/nu/zoom/dsl/sample/Endpoints.java.ftl @@ -0,0 +1,22 @@ +// Copyright 2025 "Johan Maasing" +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +<#list meta.templateDirectories>package<#items as dir>${dir}<#sep>.; + +class Endpoints() { +<#list endpoints as endpoint> + /* <#list endpoint.paths.paths><#items as segment>/${segment} */ + public void handle${endpoint.inputType?cap_first} + + +} \ No newline at end of file