Makes it possible to put templates in subdirectories

This commit is contained in:
Johan Maasing 2025-05-02 09:25:27 +02:00
parent 68dc70c176
commit 7e8aa018e9
Signed by: johan
GPG key ID: FFD31BABEE2DEED2
9 changed files with 109 additions and 53 deletions

View file

@ -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<String, String> config,
Set<TypeNode> typeDefinitions,
List<EndpointNode> endpoints,
Set<StateNode> states) {
public record GeneratorNode(
Map<String, String> config,
Set<TypeNode> typeDefinitions,
List<EndpointNode> endpoints,
Set<StateNode> states,
Meta meta) {
}
```
@ -304,3 +305,14 @@ 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.
It 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>.</#items>;</#list>
```

View file

@ -1,3 +1,5 @@
Generated from template: ${meta.templateFile}
<#list endpoints as endpoint>
<#list endpoint.paths.paths>
<#items as segment>/${segment}</#items>

View file

@ -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>.</#items>;</#list>
object Codecs:
<#list typeDefinitions as type>

View file

@ -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>.</#items>;</#list>
class Endpoints:
<#list endpoints as endpoint>

View file

@ -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>.</#items>;</#list>
object Protocol:
<#list typeDefinitions?sort as type>

View file

@ -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;

View file

@ -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<String, String> config,
Set<TypeNode> typeDefinitions,
List<EndpointNode> endpoints,
Set<StateNode> states,
Meta meta) {
}

View file

@ -0,0 +1,9 @@
package nu.zoom.dsl.ast;
import java.util.List;
public record Meta(
List<String> templateDirectories,
String templateFile
) {
}

View file

@ -18,63 +18,82 @@ 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;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
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<Path> generate() throws IOException, TemplateException {
try (Stream<Path> files = Files.walk(templatesDir)) {
List<Path> 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<Path> out = new ArrayList<>();
for (Path template : templates) {
Path outpath = outputDir.resolve(outputFilenameFromTemplate(template.toString()));
Files.createDirectories(outpath.getParent());
Template ftl = this.cfg.getTemplate(template.toString());
try (var outw = Files.newBufferedWriter(outpath, StandardCharsets.UTF_8)) {
ftl.process(this.data, outw);
out.add(outpath);
}
}
return out;
}
}
public List<Path> generate() throws IOException, TemplateException {
try (Stream<Path> files = Files.walk(templatesDir)) {
List<Path> 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<Path> out = new ArrayList<>();
for (Path template : templates) {
Path outpath = outputDir.resolve(outputFilenameFromTemplate(template.toString()));
Files.createDirectories(outpath.getParent());
Path templateSubdirectory = template.getParent();
ArrayList<String> 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);
}
}