Output files are named as the templates with the ending stripped.
This commit is contained in:
parent
b86568a6d0
commit
6f1b026dd5
6 changed files with 30 additions and 34 deletions
35
README.md
35
README.md
|
@ -4,7 +4,6 @@ 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)
|
Endgen is Open Source Software using the [Apache Software License v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
The motivation behind this tool is that I wanted to generate boilerplate code for handling HTTP Endpoints (hence the
|
The motivation behind this tool is that I wanted to generate boilerplate code for handling HTTP Endpoints (hence the
|
||||||
endgen name).
|
endgen name).
|
||||||
|
|
||||||
|
@ -23,25 +22,24 @@ parser and a code generator using [freemarker](https://freemarker.apache.org).
|
||||||
| Endgen |
|
| Endgen |
|
||||||
-------------------------------------------------
|
-------------------------------------------------
|
||||||
----------- ||--------| |-----------| |------------||
|
----------- ||--------| |-----------| |------------||
|
||||||
| endpoint | || Parser | | In-Memory | | Freemarker || ---------------
|
| endpoint | || Parser | | In-Memory | | Freemarker || ------------------
|
||||||
| file | --> || | --> | AST | --> | engine || --> | Output file |
|
| file | --> || | --> | AST | --> | engine || --> | Output file |
|
||||||
\__________\ ||--------| |-----------| |------------|| \_____________\
|
\__________\ ||--------| |-----------| |------------|| | mytemplate.xxx |
|
||||||
------------------------------------------------- -
|
-------------------------------------------------- \________________\
|
||||||
^
|
^
|
||||||
|
|
|
|
||||||
----------------
|
----------------------
|
||||||
| Template.ftl |
|
| mytemplate.xxx.ftl |
|
||||||
\_______________\
|
\____________________\
|
||||||
```
|
```
|
||||||
## How to Run
|
## How to Run
|
||||||
|
|
||||||
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).
|
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.
|
Unpack the archive, run the provided shellscript file.
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
```
|
```
|
||||||
Usage: EndpointsCLI [-hvV] [-o=<outputDir>] [-t=<templateDir>] <file>
|
Usage: run.sh [-hvV] [-o=<outputDir>] [-t=<templateDir>] <file>
|
||||||
Generate source code from an endpoints specification file.
|
Generate source code from an endpoints specification file.
|
||||||
<file> The source endpoints DSL file.
|
<file> The source endpoints DSL file.
|
||||||
-h, --help Show this help message and exit.
|
-h, --help Show this help message and exit.
|
||||||
|
@ -55,28 +53,28 @@ Generate source code from an endpoints specification file.
|
||||||
```
|
```
|
||||||
|
|
||||||
## DSL example
|
## DSL example
|
||||||
|
|
||||||
In the simplest form the DSL looks like this
|
In the simplest form the DSL looks like this
|
||||||
```
|
```
|
||||||
/some/endpoint <- SomeType(foo:String)
|
/some/endpoint <- SomeType(foo:String)
|
||||||
```
|
```
|
||||||
|
|
||||||
This gets parsed into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) which is this case holds a list of
|
This gets parsed into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) which in this case holds a list of
|
||||||
Path segments and a data strucutre representing the input/body type.
|
Path segments and a data strucutre representing the input/body type.
|
||||||
|
|
||||||
## Code generation example
|
## Code generation example
|
||||||
|
|
||||||
When the parser is done reading the DSL it will look in a directory for [freemarker](https://freemarker.apache.org)
|
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
|
templates. For each template it finds it sends in the AST. The resulting file (per template) is written to an
|
||||||
output directory.
|
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
|
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
|
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
|
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.
|
no plans to extend the DSL to do that either.
|
||||||
|
|
||||||
## DSL
|
## DSL
|
||||||
|
|
||||||
This is the ANTLR grammar for the root of the DSL
|
This is the ANTLR grammar for the root of the DSL
|
||||||
|
|
||||||
```antlrv4
|
```antlrv4
|
||||||
|
@ -89,7 +87,7 @@ Here is an example:
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
package: se.rutdev.senash,
|
package: se.rutdev.senash,
|
||||||
ending: .scala
|
mykey: myvalue
|
||||||
}
|
}
|
||||||
|
|
||||||
/some/endpoint <- SomeType(foo:String)
|
/some/endpoint <- SomeType(foo:String)
|
||||||
|
@ -98,7 +96,7 @@ Embedded(foo:Bar)
|
||||||
/some/other/endpoint <- (bar:Seq[Embedded])
|
/some/other/endpoint <- (bar:Seq[Embedded])
|
||||||
```
|
```
|
||||||
|
|
||||||
This consists of a config block with 2 items, the 'package' and the 'ending' deinfition. These are available to be used
|
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.
|
in the freemarker template as a Map of String-keys to String-values.
|
||||||
|
|
||||||
`/some/endpoint <- SomeType(foo:String)` is an endpoint declaration. 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
|
||||||
|
@ -113,7 +111,6 @@ just named field-name and the other string is named field-type.
|
||||||
to a specific endpoint.
|
to a specific endpoint.
|
||||||
|
|
||||||
### Automatically named data types
|
### Automatically named data types
|
||||||
|
|
||||||
`/some/other/endpoint <- (bar:Seq[Embedded])` is another endpoint declaration. However this time the request body is
|
`/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
|
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
|
tack on the string 'Request' at the end. So the AST till contain a datatype named `endpointRequest` with a field named
|
||||||
|
@ -126,7 +123,6 @@ decide to generate in the templates.
|
||||||
The only 'semantic' validation the parser performs is to check that not two types have the same name.
|
The only 'semantic' validation the parser performs is to check that not two types have the same name.
|
||||||
|
|
||||||
### Reponse data types
|
### Reponse data types
|
||||||
|
|
||||||
It is possible to have an optional response data type declared like so:
|
It is possible to have an optional response data type declared like so:
|
||||||
|
|
||||||
`/some/other/endpoint <- (bar:Seq[Embedded]) -> ResponseType(foo: Bar)`
|
`/some/other/endpoint <- (bar:Seq[Embedded]) -> ResponseType(foo: Bar)`
|
||||||
|
@ -135,12 +131,10 @@ The right pointing arrow `->` denotes a response type, it can be an anonymous da
|
||||||
name it from the last path segment and add 'Response' to the end of the data type name.
|
name it from the last path segment and add 'Response' to the end of the data type name.
|
||||||
|
|
||||||
### DSL config
|
### DSL config
|
||||||
|
|
||||||
The only key in the config block the generator looks at is called `ending`, this will be used as the file ending for
|
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 resulting file of applying the freemarker template.
|
||||||
|
|
||||||
## Generating
|
## Generating
|
||||||
|
|
||||||
If the parser is successful it will hold the following data in the AST
|
If the parser is successful it will hold the following data in the AST
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
@ -162,14 +156,12 @@ This will be passed to the freemarker engine as the 'root' data object, meaning
|
||||||
That is, you can directly reference `typeDefinitions`, `endpoints` or `config`.
|
That is, you can directly reference `typeDefinitions`, `endpoints` or `config`.
|
||||||
|
|
||||||
### Config
|
### Config
|
||||||
|
|
||||||
The config object is simply a String-map with the keys and values unfiltered from the input file. Here is an example
|
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'.
|
that writes the value for a config key called 'package'.
|
||||||
|
|
||||||
`package ${config.package}`
|
`package ${config.package}`
|
||||||
|
|
||||||
### Data types
|
### Data types
|
||||||
|
|
||||||
These are all the data types the parser have collected, either from explicit declarations, request payloads and response
|
These are all the data types the parser have collected, either from explicit declarations, request payloads and response
|
||||||
bodies.
|
bodies.
|
||||||
|
|
||||||
|
@ -191,7 +183,6 @@ object Protocol:
|
||||||
```
|
```
|
||||||
|
|
||||||
### Endpoints
|
### Endpoints
|
||||||
|
|
||||||
The parser will collect the following data for endpoint declarations
|
The parser will collect the following data for endpoint declarations
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
|
@ -33,6 +33,8 @@ public class Generator {
|
||||||
private final DocumentNode data;
|
private final DocumentNode data;
|
||||||
private final Path outputDir;
|
private final Path outputDir;
|
||||||
private final Configuration cfg;
|
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 {
|
public Generator(Path templatesDir, DocumentNode data, Path outputDir) throws IOException {
|
||||||
this.templatesDir = Objects.requireNonNull(templatesDir);
|
this.templatesDir = Objects.requireNonNull(templatesDir);
|
||||||
|
@ -49,13 +51,16 @@ public class Generator {
|
||||||
|
|
||||||
public List<Path> generate() throws IOException, TemplateException {
|
public List<Path> generate() throws IOException, TemplateException {
|
||||||
try (Stream<Path> files = Files.list(templatesDir)) {
|
try (Stream<Path> files = Files.list(templatesDir)) {
|
||||||
List<Path> templates = files.filter(p -> p.toString().endsWith(".ftl")).toList() ;
|
List<String> templates = files
|
||||||
|
.map(Path::getFileName)
|
||||||
|
.map(Path::toString)
|
||||||
|
.filter(p -> p.length() > TEMPLATE_EXTENSION_LENGTH && p.endsWith(TEMPLATE_EXTENSION)
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
ArrayList<Path> out = new ArrayList<>();
|
ArrayList<Path> out = new ArrayList<>();
|
||||||
for (Path template : templates) {
|
for (String template : templates) {
|
||||||
String configEnding = this.data.config().get("ending") ;
|
Path outpath = outputDir.resolve(outputFilenameFromTemplate(template));
|
||||||
String ending = configEnding != null ? configEnding.trim() : "";
|
Template ftl = this.cfg.getTemplate(template);
|
||||||
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)) {
|
try (var outw = Files.newBufferedWriter(outpath, StandardCharsets.UTF_8)) {
|
||||||
ftl.process(this.data, outw);
|
ftl.process(this.data, outw);
|
||||||
out.add(outpath);
|
out.add(outpath);
|
||||||
|
@ -65,7 +70,7 @@ public class Generator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String outputFilenameFromTemplate(Path template, String ending) {
|
private String outputFilenameFromTemplate(String template) {
|
||||||
return template.getFileName().toString().replace(".ftl", ending);
|
return template.substring(0, template.length() - TEMPLATE_EXTENSION_LENGTH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue