diff --git a/.gitignore b/.gitignore index 7acc024..48010bb 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,4 @@ build/ .vscode/ ### Mac OS ### -.DS_Store - -/endpoints-output/** \ No newline at end of file +.DS_Store \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 75dcf6e..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,38 +0,0 @@ -def version = '0.0.0-SNAPSHOT' -pipeline { - agent { - label 'x86' - } - tools { - maven 'Maven-3.9.9' - jdk 'Zulu-24' - } - - stages { - stage('Checkout') { - steps { - checkout scmGit( - branches: [[name: '*/main']], - userRemoteConfigs: [[ - credentialsId: 'forgejo-user-accesstoken', - url : 'https://vcs.zoom.nu/zoom/endgen.git' - ]] - ) - } - } - stage('Maven package & deploy') { - steps { - withCredentials([file(credentialsId: 'jenkins-settings.xml', variable: 'SETTINGS_XML')]) { - script { - version = sh(returnStdout: true, script: 'mvn --global-settings ${SETTINGS_XML} help:evaluate -Dexpression=project.version -q -DforceStdout') - currentBuild.description = "cluster-admin:$version" - } - echo "Building version ${version}" - sh """ - mvn --global-settings \${SETTINGS_XML} clean package deploy - """ - } - } - } - } -} \ No newline at end of file diff --git a/README.md b/README.md index 3262cf7..c647419 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ This is a converter tool that reads a DSL and generates output files. Endgen is Open Source Software using the [Apache Software License v2.0](http://www.apache.org/licenses/LICENSE-2.0) ## Motivation + The motivation behind this tool is that I wanted to generate boilerplate code for handling HTTP Endpoints (hence the endgen name). @@ -22,125 +23,94 @@ parser and a code generator using [freemarker](https://freemarker.apache.org). | Endgen | ------------------------------------------------- ----------- ||--------| |-----------| |------------|| -| endpoint | || Parser | | In-Memory | | Freemarker || ------------------ -| file | --> || | --> | AST | --> | engine || --> | Output file | -\__________\ ||--------| |-----------| |------------|| | mytemplate.xxx | - -------------------------------------------------- \________________\ +| endpoint | || Parser | | In-Memory | | Freemarker || --------------- +| file | --> || | --> | AST | --> | engine || --> | Output file | +\__________\ ||--------| |-----------| |------------|| \_____________\ + ------------------------------------------------- - ^ | - ---------------------- - | mytemplate.xxx.ftl | - \____________________\ + ---------------- + | Template.ftl | + \_______________\ ``` - -Endgen currently contains two separate parsers: -* endpoint - A DSL for expressing HTTP endpoints. -* state - A DSL for expressing state and transitions. - -Only one parser will be sued when reading a file. Determined by the file name ending; -'.endpoints', or '.states', or by a command line argument. - -The endpoint-DSL and the state-DSL share the grammar for expressing configuration and data types -,see below for details. - ## How to Run -You need a Java 21 (or later) runtime and java in the path. A very convenient way to install a java runtime is [SdkMan](https://sdkman.io). + +You need a Java 24 runtime and java in the path. A very convenient way to install a java runtime is [SdkMan](https://sdkman.io). Unpack the archive, run the provided shellscript file. ### Usage ``` -sage: run.sh [-hvV] [-o=] [-p=] [-t=] +Usage: EndpointsCLI [-hvV] [-o=] [-t=] Generate source code from an endpoints specification file. The source endpoints DSL file. -h, --help Show this help message and exit. -o, --output= The directory to write the generated code to. - Default is endpoints-output - -p, --parser= Force use of a specific parser instead of - determining from filename. Valid values: - Endpoints, States. + Default is ~/endpoints-output -t, --template= The template directory. Default is - endpoints-template + ~/endpoints-templates -v, --verbose Print verbose debug messages. -V, --version Print version information and exit. ``` -## Endpoint DSL example +## DSL example + In the simplest form the DSL looks like this ``` /some/endpoint <- SomeType(foo:String) ``` -This gets parsed into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) which in this case holds a list of +This gets parsed into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) which is this case holds a list of Path segments and a data strucutre representing the input/body type. ## Code generation example + When the parser is done reading the DSL it will look in a directory for [freemarker](https://freemarker.apache.org) templates. For each template it finds it sends in the AST. The resulting file (per template) is written to an output directory. -The templates must have the file ending `.ftl` - this ending is stripped when generating the output file. So a template -called `types.java.ftl` will generate a file called `types.java`. - The idea being that you can take these files and probably adapt them before checking them into your project. Endgen does not aim to be a roundtrip tool (i.e. reading the generated source, or being smart in updating them etc). It is also a very limited DSL, you can for example not express what type of HTTP Verb to use or declare response codes. There are no plans to extend the DSL to do that either. ## DSL -This is the ANTLR grammar for the root of the Endpoint-DSL + +This is the ANTLR grammar for the root of the DSL ```antlrv4 document : generatorconfig? (namedTypeDeclaration|endpoint)* ; ``` - -the corresponding grammar for the root of the State-DSL - -```antlrv4 -document : generatorconfig? transition (',' transition)* ; -``` - -### Configuration block -Both types of DSL files has an optional `generatorconfig` block at the top. +Meaning that the DSL file has an optional `generatorconfig` block at the top. Then you can write either; a type +definition, or an endpoint declaration, as many times as you like. Here is an example: ``` { package: se.rutdev.senash, - mykey: myvalue + ending: .scala } -``` -This consists of a config block with 2 items, the 'package' and the 'mykey' definition. These are available to be used -in the freemarker template as a Map of String-keys to String-values. - -### Endpoint DSL -After the optional configuration block you can write either; a type definition, or an endpoint declaration, and repeat -as many times as you like. - -Here is an example: -``` /some/endpoint <- SomeType(foo:String) Embedded(foo:Bar) /some/other/endpoint <- (bar:Seq[Embedded]) ``` -### Endpoint definition +This consists of a config block with 2 items, the 'package' and the 'ending' deinfition. These are available to be used +in the freemarker template as a Map of String-keys to String-values. -`/some/endpoint <- SomeType(foo:String)` is an endpoint definition. It declares one endpoint that have a request body +`/some/endpoint <- SomeType(foo:String)` is an endpoint declaration. It declares one endpoint that have a request body data type called `SomeType` that has a field called `foo` of the type `String`. -### Data types -Both DSL-grammars use the Scala convention of writing data types after the field name separated by a colon. Of course -the parsers do not know anything about java or scala types, as far as the parser is concerned these are 2 strings and -the first one is just named: field-name and the other string is named: field-type. +The DSL uses Scala convention of writing data types after the field name separated by a colon. Of course the DSL parser +does not know anything about java or scala types, as far as it is concerned these are 2 strings and the first one is +just named field-name and the other string is named field-type. `Embedded(foo:Bar)` is a `namedTypeDeclaration` which is parsed the same way as the request type above. But isn't tied to a specific endpoint. -### Automatically named endpoint data types `/some/other/endpoint <- (bar:Seq[Embedded])` is another endpoint declaration. However this time the request body is not named in the DSL. But all datatypes must have a name so it will simply name it after the last path segment and tack on the string 'Request' at the end. So the AST till contain a datatype named `endpointRequest` with a field named @@ -152,175 +122,9 @@ decide to generate in the templates. The only 'semantic' validation the parser performs is to check that not two types have the same name. -### Endpoint reponse data type -It is possible to have an optional response data type declared like so: +### DSL config -`/some/other/endpoint <- (bar:Seq[Embedded]) -> ResponseType(foo: Bar)` +The only key in the config block the generator looks at is called `ending`, this will be used as the file ending for +the resulting file of applying the freemarker template. -The right pointing arrow `->` denotes a response type, it can be an anonymous data type in which case the parser till -name it from the last path segment and add 'Response' to the end of the data type name. - -### State DSL -This is an example of a state file: -``` -start -> message -> middle , -middle -> selfmessage -> middle, -middle -> endmessage -> end -``` -The file declares 3 transitions. The first line states: Transition from the 'start' state to the 'middle' state with -the message 'message'. - -From this we can see that the file contains 3 state definitions `start`, `middle` and `end`. -A state definition will be parsed as a data type with the name of the state as the type name. It also contains 3 -message definitions `message`, `selfmessage` and `endmessage`. Message definitions will also be parsed as data types. - -Since the parser will extract datatypes it is possible to define the fields of the data types. This is a slightly more -complicated example: - -``` -start -> message -> middle, -middle -> selfmessage -> middle(bar:bar), -middle -> message -> end -``` -The data type for `middle` will have a field declaration with the name `bar` and the type `Bar`. - -Fields for the same state data type, or message data type, will be merged. Here is a complex example: - -``` -start(s:S) -> message(foo:foo) -> middle(foo:foo) , -middle -> selfmessage(bar:bar) -> middle(bar:bar), -middle -> message(bar:baz) -> end -``` - -Note that we can declare fields on both the `from` and `to` state declarations. The `middle` datat type will have field -definitons for `foo` and `bar`. - -The data type for `message` will have fields for `foo` and `bar`. - -One restriction is that a state and a messages may share have the same name, i.e. be parsed as the same data type. - -## Generating -If the parser is successful it will hold the following data in the AST - -```java -public record GeneratorNode( - Map config, - Set typeDefinitions, - List endpoints, - Set states, - Meta meta) { -} -``` - -Depending on the parser used the endpoints or the states will be null but config and typeDefinitions are populated the -same for both parsers. - -This will be passed to the freemarker engine as the 'root' data object, meaning you have access to the parts in your -freemarker template like this: - -```injectedfreemarker -<#list typeDefinitions as type> - This is the datat type name: ${type.name?cap_first} with the first letter capitalized. - -``` - -That is, you can directly reference `typeDefinitions`, `endpoints`, `states` or `config`. - -### Config -The config object is simply a String-map with the keys and values unfiltered from the input file. Here is an example -that writes the value for a config key called 'package'. - -`package ${config.package}` - -### Data types -These are all the data types the parser have collected, either from explicit declarations, request payloads, response -bodies, states or messages. - -```java -public record TypeNode(String name, List fields) { } -public record FieldNode(String name, String type) { } -``` - -Here is an example template that writes the data types as Scala case classes -```injectedfreemarker -object Protocol: -<#list typeDefinitions?sort as type> - case class ${type.name?cap_first}( - <#list type.fields as field> - ${field.name} : ${field.type}, - - ) - -``` - -### Endpoints -The parser will collect the following data for endpoint declarations - -```java -public record EndpointNode( - PathsNode paths, - String inputType, - Optional outputType) {} - -public record PathsNode(List paths) {} -``` - -This is an example that will write out the endpoints with the path first, then the Input data type, then the optional -Output data type. - -```injectedfreemarker -<#list endpoints as endpoint> - <#list endpoint.paths.paths> - <#items as segment>/${segment} - Input: - ${endpoint.inputType?cap_first} - Output: - <#if endpoint.outputType.isPresent()> - ${endpoint.outputType.get()?cap_first} - <#else> - Not specified - - - - -``` - -### States - -The set of states will hold items of this shape: - -```injectedfreemarker -public record StateNode(String name, String data, Set transitions) { -} -``` -* `name` is the name of the state. -* `data` is the name of the data type for the state. -* `transistions` are the outgoing arrows from the named state. - -Transitions have this structure: - -```injectedfreemarker -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 +## Generating \ No newline at end of file diff --git a/endgen-dist/pom.xml b/endgen-dist/pom.xml index 8a2fedb..9d2a59c 100644 --- a/endgen-dist/pom.xml +++ b/endgen-dist/pom.xml @@ -1,11 +1,13 @@ - + 4.0.0 nu.zoom.dsl endgen - 1.3-SNAPSHOT + 1.0-SNAPSHOT endgen-dist diff --git a/endgen-dist/src/main/resources/run.sh b/endgen-dist/src/main/resources/run.sh index 75894b3..720ffe7 100755 --- a/endgen-dist/src/main/resources/run.sh +++ b/endgen-dist/src/main/resources/run.sh @@ -1,4 +1,4 @@ #! /bin/sh SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd "${SCRIPT_DIR}" -java -jar parser-${artifact.baseVersion}.jar "$@" \ No newline at end of file +java -jar parser-${artifact.baseVersion}.jar \ No newline at end of file diff --git a/endgen-maven-plugin/README.md b/endgen-maven-plugin/README.md deleted file mode 100644 index 3df592d..0000000 --- a/endgen-maven-plugin/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# 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 deleted file mode 100644 index 9974c25..0000000 --- a/endgen-maven-plugin/pom.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - 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 deleted file mode 100644 index 757eee5..0000000 --- a/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/EndgenMojo.java +++ /dev/null @@ -1,68 +0,0 @@ -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 deleted file mode 100644 index 03b98be..0000000 --- a/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/MavenLogger.java +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index c8a0779..0000000 --- a/endgen-maven-plugin/src/main/java/nu/zoom/dsl/maven/Run.java +++ /dev/null @@ -1,52 +0,0 @@ -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/states-templates/Codecs.scala.ftl b/endpoints-templates/Codecs.ftl similarity index 100% rename from states-templates/Codecs.scala.ftl rename to endpoints-templates/Codecs.ftl diff --git a/endpoints-templates/nu/zoom/dsl/Endpoints.scala.ftl b/endpoints-templates/Endpoints.ftl similarity index 92% rename from endpoints-templates/nu/zoom/dsl/Endpoints.scala.ftl rename to endpoints-templates/Endpoints.ftl index fbc3538..a68864c 100644 --- a/endpoints-templates/nu/zoom/dsl/Endpoints.scala.ftl +++ b/endpoints-templates/Endpoints.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. -<#list meta.templateDirectories>package<#items as dir>${dir}<#sep>.; +package ${config.package} class Endpoints: <#list endpoints as endpoint> diff --git a/states-templates/Types.scala.ftl b/endpoints-templates/Protocol.ftl similarity index 96% rename from states-templates/Types.scala.ftl rename to endpoints-templates/Protocol.ftl index f2322b6..faa5d55 100644 --- a/states-templates/Types.scala.ftl +++ b/endpoints-templates/Protocol.ftl @@ -13,7 +13,7 @@ // limitations under the License. package ${config.package} -object StateAndMessageTypes: +object Protocol: <#list typeDefinitions?sort as type> case class ${type.name?cap_first}( <#list type.fields as field> diff --git a/endpoints-templates/endpoints.txt.ftl b/endpoints-templates/endpoints.txt.ftl deleted file mode 100644 index 4904c00..0000000 --- a/endpoints-templates/endpoints.txt.ftl +++ /dev/null @@ -1,16 +0,0 @@ -Generated from template: ${meta.templateFile} - -<#list endpoints as endpoint> - <#list endpoint.paths.paths> - <#items as segment>/${segment} - Input: - ${endpoint.inputType?cap_first} - Output: - <#if endpoint.outputType.isPresent()> - ${endpoint.outputType.get()?cap_first} - <#else> - Not specified - - - - \ No newline at end of file diff --git a/endpoints-templates/nu/zoom/dsl/Codecs.scala.ftl b/endpoints-templates/nu/zoom/dsl/Codecs.scala.ftl deleted file mode 100644 index 7ea382d..0000000 --- a/endpoints-templates/nu/zoom/dsl/Codecs.scala.ftl +++ /dev/null @@ -1,19 +0,0 @@ -// 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>.; - -object Codecs: -<#list typeDefinitions as type> - given Codec[${type.name?cap_first}] = deriveCodec - diff --git a/endpoints-templates/nu/zoom/dsl/Protocol.scala.ftl b/endpoints-templates/nu/zoom/dsl/Protocol.scala.ftl deleted file mode 100644 index 41e6547..0000000 --- a/endpoints-templates/nu/zoom/dsl/Protocol.scala.ftl +++ /dev/null @@ -1,23 +0,0 @@ -// 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>.; - -object Protocol: -<#list typeDefinitions?sort as type> - case class ${type.name?cap_first}( - <#list type.fields as field> - ${field.name} : ${field.type}, - - ) - diff --git a/parser/pom.xml b/parser/pom.xml index 67c6925..5e312e1 100644 --- a/parser/pom.xml +++ b/parser/pom.xml @@ -14,13 +14,15 @@ // See the License for the specific language governing permissions and // limitations under the License. --> - + 4.0.0 nu.zoom.dsl endgen - 1.3-SNAPSHOT + 1.0-SNAPSHOT parser @@ -50,14 +52,13 @@ maven-compiler-plugin 3.13.0 - ${maven.compiler.source} - ${maven.compiler.target} + 24 + 24 org.apache.maven.plugins maven-jar-plugin - 3.4.2 diff --git a/parser/src/main/antlr4/imports/Common.g4 b/parser/src/main/antlr4/imports/Common.g4 deleted file mode 100644 index 8844343..0000000 --- a/parser/src/main/antlr4/imports/Common.g4 +++ /dev/null @@ -1,41 +0,0 @@ -// 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. -grammar Common; -generatorconfig : '{' (configitem)? (',' configitem)* '}'; -configitem : configkey ':' configvalue ; -configkey : IDENTIFIER ; -configvalue : (IDENTIFIER|VALUE) ; -namedTypeDeclaration : typeName typeDeclaration ; -typeName : IDENTIFIER ; -typeDeclaration : '(' typeField (',' typeField)* ')' ; -typeField : fieldName COLON fieldType ; -fieldName : IDENTIFIER ; -fieldType : IDENTIFIER ; - -fragment LOWERCASE : [a-z] ; -fragment UPPERCASE : [A-Z] ; -fragment GENERICS : '['|']'|'<'|'>' ; -fragment DOT : '.' ; -fragment COMMENT_BEGIN : '/*' ; -fragment COMMENT_END : '*/' ; -fragment DIGIT : [0-9] ; - -WS : [ \t\n\r]+ -> skip; -COMMENT : COMMENT_BEGIN .*? COMMENT_END -> skip; -LEFT_ARROW : '<-' ; -RIGHT_ARROW : '->' ; -IDENTIFIER : (LOWERCASE | UPPERCASE) (LOWERCASE | UPPERCASE | DIGIT | GENERICS | DOT)* ; -VALUE : ~[ ,{}:()/="#';*\n\r\t]+ ; -SLASH : '/' ; -COLON : ':' ; \ No newline at end of file diff --git a/parser/src/main/antlr4/nu/zoom/dsl/parser/Endpoints.g4 b/parser/src/main/antlr4/nu/zoom/dsl/parser/Endpoints.g4 index 1a74607..4f47bf8 100644 --- a/parser/src/main/antlr4/nu/zoom/dsl/parser/Endpoints.g4 +++ b/parser/src/main/antlr4/nu/zoom/dsl/parser/Endpoints.g4 @@ -12,11 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. grammar Endpoints; -import Common; - document : generatorconfig? (namedTypeDeclaration|endpoint)* ; -requestBody : LEFT_ARROW (namedTypeDeclaration | typeDeclaration | IDENTIFIER) ; -responseBody : RIGHT_ARROW (namedTypeDeclaration | typeDeclaration | IDENTIFIER) ; +generatorconfig : '{' (configitem)? (',' configitem)* '}'; +configitem : configkey ':' configvalue ; +configkey : IDENTIFIER ; +configvalue : (IDENTIFIER|VALUE) ; +namedTypeDeclaration : typeName typeDeclaration ; +typeName : IDENTIFIER ; +typeDeclaration : '(' typeField (',' typeField)* ')' ; +typeField : fieldName ':' fieldType ; +fieldName : IDENTIFIER ; +fieldType : IDENTIFIER ; +requestBody : REQUEST_PREFIX (namedTypeDeclaration | typeDeclaration | IDENTIFIER) ; +responseBody : RESPONSE_PREFIX (namedTypeDeclaration | typeDeclaration | IDENTIFIER) ; endpoint : path requestBody responseBody?; path : (pathSegment)+ ; pathSegment : SLASH (IDENTIFIER|VALUE) ; + + +fragment DIGIT : [0-9] ; +fragment LOWERCASE : [a-z] ; +fragment UPPERCASE : [A-Z] ; +fragment GENERICS : '['|']'|'<'|'>' ; +fragment DOT : '.' ; +fragment COMMENT_BEGIN : '/*' ; +fragment COMMENT_END : '*/' ; +WS : [ \t\n\r]+ -> skip; +COMMENT : COMMENT_BEGIN .*? COMMENT_END -> skip; +REQUEST_PREFIX : '<-' ; +RESPONSE_PREFIX : '->' ; +SLASH : '/' ; +IDENTIFIER : (LOWERCASE | UPPERCASE) (LOWERCASE | UPPERCASE | DIGIT | GENERICS | DOT)* ; +VALUE : ~[ ,{}:()/="#';*\n\r\t]+ ; diff --git a/parser/src/main/antlr4/nu/zoom/dsl/parser/States.g4 b/parser/src/main/antlr4/nu/zoom/dsl/parser/States.g4 deleted file mode 100644 index d2a2b4d..0000000 --- a/parser/src/main/antlr4/nu/zoom/dsl/parser/States.g4 +++ /dev/null @@ -1,22 +0,0 @@ -// 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. -grammar States; -import Common; - -document : generatorconfig? transition (',' transition)* ; -transition : from RIGHT_ARROW message RIGHT_ARROW to ; -from : state ; -to : state ; -message : typeName typeDeclaration? ; -state : typeName typeDeclaration? ; \ No newline at end of file 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 7edb239..24ac244 100644 --- a/parser/src/main/java/nu/zoom/dsl/ast/DocumentNode.java +++ b/parser/src/main/java/nu/zoom/dsl/ast/DocumentNode.java @@ -13,14 +13,11 @@ // 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; public record DocumentNode( Map config, - Set typeDefinitions, - List endpoints, - Set states) { + List typeDefinitions, + List endpoints) { } diff --git a/parser/src/main/java/nu/zoom/dsl/ast/EndpointsVisitorTransformer.java b/parser/src/main/java/nu/zoom/dsl/ast/EndpointsVisitorTransformer.java index e530a80..5d846d6 100644 --- a/parser/src/main/java/nu/zoom/dsl/ast/EndpointsVisitorTransformer.java +++ b/parser/src/main/java/nu/zoom/dsl/ast/EndpointsVisitorTransformer.java @@ -19,8 +19,7 @@ import org.antlr.v4.runtime.tree.TerminalNode; import java.util.*; -public class EndpointsVisitorTransformer - extends EndpointsBaseVisitor { +public class EndpointsVisitorTransformer extends EndpointsBaseVisitor { private final ArrayList endpoints = new ArrayList<>(); private final HashMap config = new HashMap<>(); private final HashSet dataTypes = new HashSet<>(); @@ -36,8 +35,8 @@ public class EndpointsVisitorTransformer return Map.copyOf(config); } - public Set getDataTypes() { - return Set.copyOf(dataTypes); + public List getDataTypes() { + return List.copyOf(dataTypes); } @Override @@ -131,7 +130,7 @@ public class EndpointsVisitorTransformer ).toList(); } - // Concatenate the text from two terminal nodes. Useful for contexts that are either an identifier or a value, + // Concatenate the text from to terminal nodes. Useful for contexts that are either an identifier or a value, // and you just want the text from whichever is not null. private String getText(TerminalNode identifier, TerminalNode value) { return diff --git a/parser/src/main/java/nu/zoom/dsl/ast/GeneratorNode.java b/parser/src/main/java/nu/zoom/dsl/ast/GeneratorNode.java deleted file mode 100644 index 2cea7ca..0000000 --- a/parser/src/main/java/nu/zoom/dsl/ast/GeneratorNode.java +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 567f3ab..0000000 --- a/parser/src/main/java/nu/zoom/dsl/ast/Meta.java +++ /dev/null @@ -1,9 +0,0 @@ -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/run/ParserWrapper.java b/parser/src/main/java/nu/zoom/dsl/ast/ParserWrapper.java similarity index 51% rename from parser/src/main/java/nu/zoom/dsl/run/ParserWrapper.java rename to parser/src/main/java/nu/zoom/dsl/ast/ParserWrapper.java index ce62d9c..2252cac 100644 --- a/parser/src/main/java/nu/zoom/dsl/run/ParserWrapper.java +++ b/parser/src/main/java/nu/zoom/dsl/ast/ParserWrapper.java @@ -11,48 +11,25 @@ // 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.run; +package nu.zoom.dsl.ast; -import nu.zoom.dsl.ast.DocumentNode; -import nu.zoom.dsl.ast.EndpointsVisitorTransformer; -import nu.zoom.dsl.ast.StatesVisitorTransformer; -import nu.zoom.dsl.parser.*; +import nu.zoom.dsl.parser.EndpointsLexer; +import nu.zoom.dsl.parser.EndpointsParser; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import java.util.List; -import java.util.Set; public class ParserWrapper { - public static DocumentNode parseEndpoints(Path sourcePath) throws IOException { + public static DocumentNode parse(Path sourcePath) throws IOException { var ins = CharStreams.fromPath(sourcePath, StandardCharsets.UTF_8); EndpointsLexer lexer = new EndpointsLexer(ins); EndpointsParser parser = new EndpointsParser(new CommonTokenStream(lexer)); var document = parser.document(); var astTransformer = new EndpointsVisitorTransformer(); astTransformer.visit(document); - return new DocumentNode( - astTransformer.getConfig(), - astTransformer.getDataTypes(), - astTransformer.getEndpoints(), - Set.of() - ); - } - public static DocumentNode parseStates(Path sourcePath) throws IOException { - var ins = CharStreams.fromPath(sourcePath, StandardCharsets.UTF_8); - StatesLexer lexer = new StatesLexer(ins); - StatesParser parser = new StatesParser(new CommonTokenStream(lexer)); - var document = parser.document(); - var astTransformer = new StatesVisitorTransformer(); - astTransformer.visit(document); - return new DocumentNode( - astTransformer.getConfig(), - astTransformer.getTypes(), - List.of(), - astTransformer.getStates() - ); + return new DocumentNode(astTransformer.getConfig(), astTransformer.getDataTypes(), astTransformer.getEndpoints()); } } diff --git a/parser/src/main/java/nu/zoom/dsl/ast/StateNode.java b/parser/src/main/java/nu/zoom/dsl/ast/StateNode.java deleted file mode 100644 index 5f8b7ff..0000000 --- a/parser/src/main/java/nu/zoom/dsl/ast/StateNode.java +++ /dev/null @@ -1,6 +0,0 @@ -package nu.zoom.dsl.ast; - -import java.util.Set; - -public record StateNode(String name, String data, Set transitions) { -} diff --git a/parser/src/main/java/nu/zoom/dsl/ast/StatesVisitorTransformer.java b/parser/src/main/java/nu/zoom/dsl/ast/StatesVisitorTransformer.java deleted file mode 100644 index cd4bef0..0000000 --- a/parser/src/main/java/nu/zoom/dsl/ast/StatesVisitorTransformer.java +++ /dev/null @@ -1,132 +0,0 @@ -package nu.zoom.dsl.ast; - -import nu.zoom.dsl.parser.StatesBaseVisitor; -import nu.zoom.dsl.parser.StatesParser; -import org.antlr.v4.runtime.tree.TerminalNode; - -import java.util.*; -import java.util.stream.Stream; - -public class StatesVisitorTransformer extends StatesBaseVisitor { - private final HashMap config = new HashMap<>(); - private final HashSet nodeTypes = new HashSet<>(); - private final HashSet messageTypes = new HashSet<>(); - // from -> - private final HashMap> transitions = new HashMap<>(); - - @Override - public StatesParser.DocumentContext visitTransition(StatesParser.TransitionContext ctx) { - String from = ctx.from().state().typeName().IDENTIFIER().getText() ; - String to = ctx.to().state().typeName().IDENTIFIER().getText() ; - String message = ctx.message().typeName().IDENTIFIER().getText() ; - this.transitions.computeIfAbsent(from, k -> new HashMap<>()).put(to, message); - return super.visitTransition(ctx); - } - - @Override - public StatesParser.DocumentContext visitState(StatesParser.StateContext ctx) { - String stateName = ctx.typeName().IDENTIFIER().getText() ; - List fields = extractFields(ctx.typeDeclaration()) ; - this.nodeTypes.add(new TypeNode(stateName, fields)); - return super.visitState(ctx); - } - - @Override - public StatesParser.DocumentContext visitMessage(StatesParser.MessageContext ctx) { - String messageName = ctx.typeName().IDENTIFIER().getText() ; - List fields = extractFields(ctx.typeDeclaration()) ; - this.messageTypes.add(new TypeNode(messageName, fields)); - return super.visitMessage(ctx); - } - - @Override - public StatesParser.DocumentContext visitConfigitem(StatesParser.ConfigitemContext ctx) { - String configKey = ctx.configkey().IDENTIFIER().getText(); - String configValue = getText(ctx.configvalue().IDENTIFIER(), ctx.configvalue().VALUE()); - this.config.put(configKey, configValue); - return super.visitConfigitem(ctx); - } - - public Set getStates() { - HashSet states = new HashSet<>(); - this.transitions.forEach((state,v)->{ - HashSet transitionNodes = new HashSet<>(); - v.forEach((to, message) -> transitionNodes.add(new TransitionNode(message, to))); - states.add(new StateNode(state, "", transitionNodes)) ; - }) ; - return states ; - } - - public Map getConfig() { - return Map.copyOf(config); - } - - public Set getTypes() { - final HashMap stateTypeNodes = new HashMap<>(); - this.nodeTypes.forEach(typeNode -> { - if (stateTypeNodes.containsKey(typeNode.name())) { - TypeNode mergedNode = mergeTypeFields(typeNode, stateTypeNodes.get(typeNode.name())); - stateTypeNodes.put(typeNode.name(), mergedNode); - } else { - stateTypeNodes.put(typeNode.name(), typeNode); - } - }) ; - final HashMap messageTypeNodes = new HashMap<>(); - this.messageTypes.forEach(typeNode -> { - if (stateTypeNodes.containsKey(typeNode.name())) { - throw new ParseException("Message " + typeNode.name() + " conflicts with state with the same type name"); - } - if (messageTypeNodes.containsKey(typeNode.name())) { - TypeNode mergedNode = mergeTypeFields(typeNode, messageTypeNodes.get(typeNode.name())); - messageTypeNodes.put(typeNode.name(), mergedNode); - } else { - messageTypeNodes.put(typeNode.name(), typeNode); - } - }) ; - HashSet allTypeNodes = new HashSet<>(stateTypeNodes.values()); - allTypeNodes.addAll(messageTypeNodes.values()); - return allTypeNodes ; - } - - private TypeNode mergeTypeFields(TypeNode t1, TypeNode t2) { - List t1Fields = (t1 != null) ? t1.fields() : List.of() ; - List t2Fields = (t2 != null) ? t2.fields() : List.of() ; - HashMap mergedFields = new HashMap<>(); - t1Fields.forEach(field -> { - if (mergedFields.containsKey(field.name())) { - throw new ParseException("Duplicate field name: " + field.name()); - } - mergedFields.put(field.name(), field); - }); - t2Fields.forEach(field -> { - if (mergedFields.containsKey(field.name())) { - throw new ParseException("Duplicate field name: " + field.name()); - } - mergedFields.put(field.name(), field); - }); - return new TypeNode(t1.name(), mergedFields.values().stream().toList()) ; - } - - private List extractFields(StatesParser.TypeDeclarationContext declaration) { - if (declaration == null) { - return Collections.emptyList(); - } - return declaration - .typeField() - .stream() - .map( - ctx -> - new FieldNode(ctx.fieldName().getText(), ctx.fieldType().getText()) - ) - .toList(); - } - - // Concatenate the text from two terminal nodes. Useful for contexts that are either an identifier or a value, - // and you just want the text from whichever is not null. - private String getText(TerminalNode identifier, TerminalNode value) { - return - ((identifier != null) ? identifier.getText() : "") + - ((value != null) ? value.getText() : ""); - } - -} diff --git a/parser/src/main/java/nu/zoom/dsl/ast/TransitionNode.java b/parser/src/main/java/nu/zoom/dsl/ast/TransitionNode.java deleted file mode 100644 index 90ebd3e..0000000 --- a/parser/src/main/java/nu/zoom/dsl/ast/TransitionNode.java +++ /dev/null @@ -1,4 +0,0 @@ -package nu.zoom.dsl.ast; - -public record TransitionNode(String message, String toState) { -} 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 8d79757..57bf2bc 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.run.*; +import nu.zoom.dsl.ast.ParserWrapper; 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( @@ -39,20 +39,17 @@ public class EndpointsCLI implements Callable { private Path file; @SuppressWarnings("CanBeFinal") - @Option(names = {"-t", "--template"}, defaultValue = Runner.DEFAULT_TEMPLATE_DIRECTORY_NAME, description = "The template directory. Default is ${DEFAULT-VALUE}") - private Path templateDir ; + @Option(names = {"-t", "--template"}, description = "The template directory. Default is ~/endpoints-templates") + private Path templateDir = Paths.get(System.getProperty("user.dir"), "endpoints-templates"); @SuppressWarnings("CanBeFinal") - @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 ; + @Option(names = {"-o", "--output"}, description = "The directory to write the generated code to. Default is ~/endpoints-output") + private Path outputDir = Paths.get(System.getProperty("user.dir"), "endpoints-output"); @SuppressWarnings("unused") @Option(names = {"-v", "--verbose"}, description = "Print verbose debug messages.") 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 Runner.ParserType parser = null; - public static void main(String[] args) { int exitCode = new CommandLine(new EndpointsCLI()).execute(args); System.exit(exitCode); @@ -61,14 +58,15 @@ public class EndpointsCLI implements Callable { @Override public Integer call() { try { - 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 - ); + validateTemplateDirectory(); + validateInputFile(); + validateOutputDirectory(); + DocumentNode rootNode = ParserWrapper.parse(file); + Generator generator = new Generator(templateDir, rootNode, outputDir); + List generatedPaths = generator.generate(); + if (generatedPaths.isEmpty()) { + System.out.println("No generated paths found."); + } return 0; } catch (Exception e) { System.err.println(e.getMessage()); @@ -76,12 +74,6 @@ public class EndpointsCLI implements Callable { } } - private void verbose(String message) { - if (this.verbose) { - System.out.println(message); - } - } - private void validateOutputDirectory() throws IOException { if (Files.notExists(this.outputDir)) { Files.createDirectories(this.outputDir); 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 0458669..bb1a29a 100644 --- a/parser/src/main/java/nu/zoom/dsl/freemarker/Generator.java +++ b/parser/src/main/java/nu/zoom/dsl/freemarker/Generator.java @@ -18,8 +18,6 @@ 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; @@ -28,72 +26,46 @@ 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 documentNode; - 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 data; + private final Path outputDir ; + private final Configuration cfg; - 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 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 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; - } - } + public List generate() throws IOException, TemplateException { + try (Stream files = Files.list(templatesDir)) { + List templates = files.filter(p -> p.toString().endsWith(".ftl")).toList() ; + ArrayList out = new ArrayList<>(); + for (Path template : templates) { + String configEnding = this.data.config().get("ending") ; + String ending = configEnding != null ? configEnding.trim() : ""; + Path outpath = outputDir.resolve(outputFilenameFromTemplate(template.getFileName(), ending)); + Template ftl = this.cfg.getTemplate(template.getFileName().toString()) ; + try (var outw = Files.newBufferedWriter(outpath, StandardCharsets.UTF_8)) { + ftl.process(this.data, outw); + out.add(outpath); + } + } + return out ; + } + } - private String outputFilenameFromTemplate(String template) { - return template.substring(0, template.length() - TEMPLATE_EXTENSION_LENGTH); - } + private String outputFilenameFromTemplate(Path template, String ending) { + return template.getFileName().toString().replace(".ftl", ending); + } } diff --git a/parser/src/main/java/nu/zoom/dsl/run/EndgenException.java b/parser/src/main/java/nu/zoom/dsl/run/EndgenException.java deleted file mode 100644 index 7771e11..0000000 --- a/parser/src/main/java/nu/zoom/dsl/run/EndgenException.java +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index 5157d97..0000000 --- a/parser/src/main/java/nu/zoom/dsl/run/GeneratorException.java +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index 80826d3..0000000 --- a/parser/src/main/java/nu/zoom/dsl/run/Logger.java +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index 014f473..0000000 --- a/parser/src/main/java/nu/zoom/dsl/run/NullLogger.java +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index b726384..0000000 --- a/parser/src/main/java/nu/zoom/dsl/run/ParserException.java +++ /dev/null @@ -1,15 +0,0 @@ -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/run/Runner.java b/parser/src/main/java/nu/zoom/dsl/run/Runner.java deleted file mode 100644 index ebf162b..0000000 --- a/parser/src/main/java/nu/zoom/dsl/run/Runner.java +++ /dev/null @@ -1,96 +0,0 @@ -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 deleted file mode 100644 index 7b0b7d4..0000000 --- a/parser/src/main/java/nu/zoom/dsl/run/StdoutLogger.java +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 1627d72..0000000 --- a/parser/src/main/java/nu/zoom/dsl/run/ValidationException.java +++ /dev/null @@ -1,15 +0,0 @@ -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 82a7a48..8625d2e 100644 --- a/pom.xml +++ b/pom.xml @@ -14,17 +14,19 @@ // See the License for the specific language governing permissions and // limitations under the License. --> - + 4.0.0 nu.zoom.dsl endgen - 1.3-SNAPSHOT + 1.0-SNAPSHOT pom - 21 - 21 + 24 + 24 UTF-8 @@ -65,7 +67,6 @@ parser endgen-dist - endgen-maven-plugin - + \ No newline at end of file diff --git a/sample-maven/pom.xml b/sample-maven/pom.xml deleted file mode 100644 index 99fdbab..0000000 --- a/sample-maven/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - 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 deleted file mode 100644 index 6f212ed..0000000 --- a/sample-maven/src/main/endgen/test01.endpoints +++ /dev/null @@ -1,28 +0,0 @@ -/* - 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 deleted file mode 100644 index 3b9a67a..0000000 --- a/sample-maven/src/main/endgen/test01.states +++ /dev/null @@ -1,21 +0,0 @@ -/* - 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 deleted file mode 100644 index 624c722..0000000 --- a/sample-maven/src/main/endpoint-templates/nu/zoom/dsl/sample/Endpoints.java.ftl +++ /dev/null @@ -1,22 +0,0 @@ -// 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 diff --git a/states-templates/nodes.md.ftl b/states-templates/nodes.md.ftl deleted file mode 100644 index 89a5603..0000000 --- a/states-templates/nodes.md.ftl +++ /dev/null @@ -1,29 +0,0 @@ -``` -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. -``` - -# The nodes - -```mermaid ---- -title: ${config.title} ---- -stateDiagram-v2 -<#list states as state> - <#list state.transitions as transition> - ${state.name} --> ${transition.toState} : ${transition.message} - - -``` \ No newline at end of file diff --git a/test01.endpoints b/test01.endpoints index 6f212ed..e2db8ab 100644 --- a/test01.endpoints +++ b/test01.endpoints @@ -17,7 +17,8 @@ { some: configvalue, someother: value, - package: se.rutdev.senash + package: se.rutdev.senash, + ending: .scala } /some/endpoint <- SomeType(foo:String) diff --git a/test01.states b/test01.states deleted file mode 100644 index 3b9a67a..0000000 --- a/test01.states +++ /dev/null @@ -1,21 +0,0 @@ -/* - 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