diff --git a/README.md b/README.md index d4306c2..3262cf7 100644 --- a/README.md +++ b/README.md @@ -203,11 +203,12 @@ One restriction is that a state and a messages may share have the same name, i.e If the parser is successful it will hold the following data in the AST ```java -public record DocumentNode( - Map config, - Set typeDefinitions, - List endpoints, - Set states) { +public record GeneratorNode( + Map config, + Set typeDefinitions, + List endpoints, + Set states, + Meta meta) { } ``` @@ -304,3 +305,22 @@ public record TransitionNode(String message, String toState) { ``` * `name` is the message name. * `toState` is the name of the target state. + +### Meta + +The meta container holds information about the template file used to generate the output file. +```java +public record Meta( + List templateDirectories, + String templateFile +) { } +``` + +* `templateDirectories` holds the subdirectory below the given `templateDir` where the template was found. +This is useful to generate e.g. java `package`statements like this: + +```injectedfreemarker +<#list meta.templateDirectories>package <#items as dir>${dir}<#sep>.; +``` + +* `templateFile` is the filename of the template (including the .ftl-ending) used to generate the output. \ No newline at end of file diff --git a/endgen-maven-plugin/README.md b/endgen-maven-plugin/README.md new file mode 100644 index 0000000..1e02921 --- /dev/null +++ b/endgen-maven-plugin/README.md @@ -0,0 +1,8 @@ +# References +## Maven + +https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#generatedsourcesdirectory + +### For executing the plugin several times +See executions +https://maven.apache.org/guides/mini/guide-configuring-plugins.html \ 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..e9f42c5 --- /dev/null +++ b/endgen-maven-plugin/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + + nu.zoom.dsl + endgen + 1.2-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} + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven-plugin-tools.version} + + + help-mojo + + helpmojo + + + + + + nu.zoom.dsl + endgen-maven-plugin + 1.2-SNAPSHOT + + + + ${project.build.sourceDirectory}/main/endgen-templates + ${project.build.sourceDirectory}/generated-sources/endgen endpoints-output + ${project.basedir}/../test01.endpoints + + + + + + + + \ 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..8d204aa --- /dev/null +++ b/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/EndgenMojo.java @@ -0,0 +1,64 @@ +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(defaultValue = "${project.build.sourceDirectory}/main/endgen-templates") + File templates; + + @Parameter(defaultValue = "${project.build.outputDirectory}/generated-sources/endgen") + File output; + + @Parameter + File dsl; + + @Parameter + String parser; + + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + getLog().info("Running endgen"); + getLog().info("Using dsl: " + dsl); + 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/endpoints-templates/endpoints.txt.ftl b/endpoints-templates/endpoints.txt.ftl index 9a779fb..4904c00 100644 --- a/endpoints-templates/endpoints.txt.ftl +++ b/endpoints-templates/endpoints.txt.ftl @@ -1,3 +1,5 @@ +Generated from template: ${meta.templateFile} + <#list endpoints as endpoint> <#list endpoint.paths.paths> <#items as segment>/${segment} diff --git a/endpoints-templates/Codecs.scala.ftl b/endpoints-templates/nu/zoom/dsl/Codecs.scala.ftl similarity index 89% rename from endpoints-templates/Codecs.scala.ftl rename to endpoints-templates/nu/zoom/dsl/Codecs.scala.ftl index f11070f..7ea382d 100644 --- a/endpoints-templates/Codecs.scala.ftl +++ b/endpoints-templates/nu/zoom/dsl/Codecs.scala.ftl @@ -11,7 +11,7 @@ // 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 ${config.package} +<#list meta.templateDirectories>package <#items as dir>${dir}<#sep>.; object Codecs: <#list typeDefinitions as type> diff --git a/endpoints-templates/Endpoints.scala.ftl b/endpoints-templates/nu/zoom/dsl/Endpoints.scala.ftl similarity index 92% rename from endpoints-templates/Endpoints.scala.ftl rename to endpoints-templates/nu/zoom/dsl/Endpoints.scala.ftl index a68864c..fbc3538 100644 --- a/endpoints-templates/Endpoints.scala.ftl +++ b/endpoints-templates/nu/zoom/dsl/Endpoints.scala.ftl @@ -11,7 +11,7 @@ // 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 ${config.package} +<#list meta.templateDirectories>package<#items as dir>${dir}<#sep>.; class Endpoints: <#list endpoints as endpoint> diff --git a/endpoints-templates/Protocol.scala.ftl b/endpoints-templates/nu/zoom/dsl/Protocol.scala.ftl similarity index 90% rename from endpoints-templates/Protocol.scala.ftl rename to endpoints-templates/nu/zoom/dsl/Protocol.scala.ftl index faa5d55..41e6547 100644 --- a/endpoints-templates/Protocol.scala.ftl +++ b/endpoints-templates/nu/zoom/dsl/Protocol.scala.ftl @@ -11,7 +11,7 @@ // 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 ${config.package} +<#list meta.templateDirectories>package<#items as dir>${dir}<#sep>.; object Protocol: <#list typeDefinitions?sort as type> diff --git a/parser/src/main/java/nu/zoom/dsl/ast/DocumentNode.java b/parser/src/main/java/nu/zoom/dsl/ast/DocumentNode.java index fcb8621..7edb239 100644 --- a/parser/src/main/java/nu/zoom/dsl/ast/DocumentNode.java +++ b/parser/src/main/java/nu/zoom/dsl/ast/DocumentNode.java @@ -13,6 +13,7 @@ // limitations under the License. package nu.zoom.dsl.ast; +import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Set; diff --git a/parser/src/main/java/nu/zoom/dsl/ast/GeneratorNode.java b/parser/src/main/java/nu/zoom/dsl/ast/GeneratorNode.java new file mode 100644 index 0000000..2cea7ca --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/ast/GeneratorNode.java @@ -0,0 +1,13 @@ +package nu.zoom.dsl.ast; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public record GeneratorNode( + Map config, + Set typeDefinitions, + List endpoints, + Set states, + Meta meta) { +} diff --git a/parser/src/main/java/nu/zoom/dsl/ast/Meta.java b/parser/src/main/java/nu/zoom/dsl/ast/Meta.java new file mode 100644 index 0000000..567f3ab --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/ast/Meta.java @@ -0,0 +1,9 @@ +package nu.zoom.dsl.ast; + +import java.util.List; + +public record Meta( + List templateDirectories, + String templateFile +) { +} 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/freemarker/Generator.java b/parser/src/main/java/nu/zoom/dsl/freemarker/Generator.java index dcfa86a..0458669 100644 --- a/parser/src/main/java/nu/zoom/dsl/freemarker/Generator.java +++ b/parser/src/main/java/nu/zoom/dsl/freemarker/Generator.java @@ -18,6 +18,8 @@ import freemarker.template.Template; import freemarker.template.TemplateException; import freemarker.template.TemplateExceptionHandler; import nu.zoom.dsl.ast.DocumentNode; +import nu.zoom.dsl.ast.GeneratorNode; +import nu.zoom.dsl.ast.Meta; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -26,51 +28,72 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import java.util.stream.Stream; public class Generator { - private final Path templatesDir; - private final DocumentNode data; - private final Path outputDir; - private final Configuration cfg; - private final String TEMPLATE_EXTENSION = ".ftl"; - private final int TEMPLATE_EXTENSION_LENGTH = TEMPLATE_EXTENSION.length(); + private final Path templatesDir; + private final DocumentNode documentNode; + private final Path outputDir; + private final Configuration cfg; + private final String TEMPLATE_EXTENSION = ".ftl"; + private final int TEMPLATE_EXTENSION_LENGTH = TEMPLATE_EXTENSION.length(); - public Generator(Path templatesDir, DocumentNode data, Path outputDir) throws IOException { - this.templatesDir = Objects.requireNonNull(templatesDir); - this.data = Objects.requireNonNull(data); - this.outputDir = Objects.requireNonNull(outputDir); - this.cfg = new Configuration(Configuration.VERSION_2_3_34); - cfg.setDirectoryForTemplateLoading(templatesDir.toFile()); - cfg.setDefaultEncoding("UTF-8"); - cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); - cfg.setLogTemplateExceptions(false); - cfg.setWrapUncheckedExceptions(true); - cfg.setFallbackOnNullLoopVariable(false); - } + public Generator(Path templatesDir, DocumentNode documentNode, Path outputDir) throws IOException { + this.templatesDir = Objects.requireNonNull(templatesDir); + this.documentNode = Objects.requireNonNull(documentNode); + this.outputDir = Objects.requireNonNull(outputDir); + this.cfg = new Configuration(Configuration.VERSION_2_3_34); + cfg.setDirectoryForTemplateLoading(templatesDir.toFile()); + cfg.setDefaultEncoding("UTF-8"); + cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + cfg.setLogTemplateExceptions(false); + cfg.setWrapUncheckedExceptions(true); + cfg.setFallbackOnNullLoopVariable(false); + } - public List generate() throws IOException, TemplateException { - try (Stream files = Files.list(templatesDir)) { - List templates = files - .map(Path::getFileName) - .map(Path::toString) - .filter(p -> p.length() > TEMPLATE_EXTENSION_LENGTH && p.endsWith(TEMPLATE_EXTENSION) - ) - .toList(); - ArrayList out = new ArrayList<>(); - for (String template : templates) { - Path outpath = outputDir.resolve(outputFilenameFromTemplate(template)); - Template ftl = this.cfg.getTemplate(template); - try (var outw = Files.newBufferedWriter(outpath, StandardCharsets.UTF_8)) { - ftl.process(this.data, outw); - out.add(outpath); - } - } - return out; - } - } + public List generate() throws IOException, TemplateException { + try (Stream files = Files.walk(templatesDir)) { + List templates = files + .filter(p -> { + var fname = p.getFileName().toString(); + return fname.length() > TEMPLATE_EXTENSION_LENGTH && fname.endsWith(TEMPLATE_EXTENSION); + } + ) + .map(p -> templatesDir.relativize(p)) + .toList(); + ArrayList out = new ArrayList<>(); + for (Path template : templates) { + Path outpath = outputDir.resolve(outputFilenameFromTemplate(template.toString())); + Files.createDirectories(outpath.getParent()); + Path templateSubdirectory = template.getParent(); + ArrayList templateDirectories= new ArrayList<>() ; + if (templateSubdirectory != null) { + var ti = templateSubdirectory.iterator(); + while (ti.hasNext()) { + templateDirectories.add(ti.next().toString()); + } + } + String templateName = template.getFileName().toString(); + Meta meta = new Meta(templateDirectories, templateName); + GeneratorNode generatorNode = new GeneratorNode( + this.documentNode.config(), + this.documentNode.typeDefinitions(), + this.documentNode.endpoints(), + this.documentNode.states(), + meta + ); + Template ftl = this.cfg.getTemplate(template.toString()); + try (var outw = Files.newBufferedWriter(outpath, StandardCharsets.UTF_8)) { + ftl.process(generatorNode, outw); + out.add(outpath); + } + } + return out; + } + } - private String outputFilenameFromTemplate(String template) { - return template.substring(0, template.length() - TEMPLATE_EXTENSION_LENGTH); - } + private String outputFilenameFromTemplate(String template) { + return template.substring(0, template.length() - TEMPLATE_EXTENSION_LENGTH); + } } 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..cb060c8 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,7 @@ parser endgen-dist + endgen-maven-plugin