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 If the parser is successful it will hold the following data in the AST
```java ```java
public record DocumentNode( public record GeneratorNode(
Map<String, String> config, Map<String, String> config,
Set<TypeNode> typeDefinitions, Set<TypeNode> typeDefinitions,
List<EndpointNode> endpoints, List<EndpointNode> endpoints,
Set<StateNode> states) { Set<StateNode> states,
Meta meta) {
} }
``` ```
@ -304,3 +305,14 @@ public record TransitionNode(String message, String toState) {
``` ```
* `name` is the message name. * `name` is the message name.
* `toState` is the name of the target state. * `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 endpoints as endpoint>
<#list endpoint.paths.paths> <#list endpoint.paths.paths>
<#items as segment>/${segment}</#items> <#items as segment>/${segment}</#items>

View file

@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package ${config.package} <#list meta.templateDirectories>package <#items as dir>${dir}<#sep>.</#items>;</#list>
object Codecs: object Codecs:
<#list typeDefinitions as type> <#list typeDefinitions as type>

View file

@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package ${config.package} <#list meta.templateDirectories>package<#items as dir>${dir}<#sep>.</#items>;</#list>
class Endpoints: class Endpoints:
<#list endpoints as endpoint> <#list endpoints as endpoint>

View file

@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package ${config.package} <#list meta.templateDirectories>package<#items as dir>${dir}<#sep>.</#items>;</#list>
object Protocol: object Protocol:
<#list typeDefinitions?sort as type> <#list typeDefinitions?sort as type>

View file

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
package nu.zoom.dsl.ast; package nu.zoom.dsl.ast;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.TemplateException;
import freemarker.template.TemplateExceptionHandler; import freemarker.template.TemplateExceptionHandler;
import nu.zoom.dsl.ast.DocumentNode; import nu.zoom.dsl.ast.DocumentNode;
import nu.zoom.dsl.ast.GeneratorNode;
import nu.zoom.dsl.ast.Meta;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitOption;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public class Generator { public class Generator {
private final Path templatesDir; private final Path templatesDir;
private final DocumentNode data; private final DocumentNode documentNode;
private final Path outputDir; private final Path outputDir;
private final Configuration cfg; private final Configuration cfg;
private final String TEMPLATE_EXTENSION = ".ftl"; private final String TEMPLATE_EXTENSION = ".ftl";
private final int TEMPLATE_EXTENSION_LENGTH = TEMPLATE_EXTENSION.length(); private final int TEMPLATE_EXTENSION_LENGTH = TEMPLATE_EXTENSION.length();
public Generator(Path templatesDir, DocumentNode data, Path outputDir) throws IOException { public Generator(Path templatesDir, DocumentNode documentNode, Path outputDir) throws IOException {
this.templatesDir = Objects.requireNonNull(templatesDir); this.templatesDir = Objects.requireNonNull(templatesDir);
this.data = Objects.requireNonNull(data); this.documentNode = Objects.requireNonNull(documentNode);
this.outputDir = Objects.requireNonNull(outputDir); this.outputDir = Objects.requireNonNull(outputDir);
this.cfg = new Configuration(Configuration.VERSION_2_3_34); this.cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setDirectoryForTemplateLoading(templatesDir.toFile()); cfg.setDirectoryForTemplateLoading(templatesDir.toFile());
cfg.setDefaultEncoding("UTF-8"); cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false); cfg.setLogTemplateExceptions(false);
cfg.setWrapUncheckedExceptions(true); cfg.setWrapUncheckedExceptions(true);
cfg.setFallbackOnNullLoopVariable(false); cfg.setFallbackOnNullLoopVariable(false);
} }
public List<Path> generate() throws IOException, TemplateException { public List<Path> generate() throws IOException, TemplateException {
try (Stream<Path> files = Files.walk(templatesDir)) { try (Stream<Path> files = Files.walk(templatesDir)) {
List<Path> templates = files List<Path> templates = files
.filter(p -> { .filter(p -> {
var fname = p.getFileName().toString(); var fname = p.getFileName().toString();
return fname.length() > TEMPLATE_EXTENSION_LENGTH && fname.endsWith(TEMPLATE_EXTENSION) ; return fname.length() > TEMPLATE_EXTENSION_LENGTH && fname.endsWith(TEMPLATE_EXTENSION);
} }
) )
.map(p -> templatesDir.relativize(p)) .map(p -> templatesDir.relativize(p))
.toList(); .toList();
ArrayList<Path> out = new ArrayList<>(); ArrayList<Path> out = new ArrayList<>();
for (Path template : templates) { for (Path template : templates) {
Path outpath = outputDir.resolve(outputFilenameFromTemplate(template.toString())); Path outpath = outputDir.resolve(outputFilenameFromTemplate(template.toString()));
Files.createDirectories(outpath.getParent()); Files.createDirectories(outpath.getParent());
Template ftl = this.cfg.getTemplate(template.toString()); Path templateSubdirectory = template.getParent();
try (var outw = Files.newBufferedWriter(outpath, StandardCharsets.UTF_8)) { ArrayList<String> templateDirectories= new ArrayList<>() ;
ftl.process(this.data, outw); if (templateSubdirectory != null) {
out.add(outpath); var ti = templateSubdirectory.iterator();
} while (ti.hasNext()) {
} templateDirectories.add(ti.next().toString());
return out; }
} }
} 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) { private String outputFilenameFromTemplate(String template) {
return template.substring(0, template.length() - TEMPLATE_EXTENSION_LENGTH); return template.substring(0, template.length() - TEMPLATE_EXTENSION_LENGTH);
} }
} }