Skip to content

Instantly share code, notes, and snippets.

@nedtwigg
Created March 22, 2023 17:56
Show Gist options
  • Save nedtwigg/a629e23ec5269894f0ad9558cf5f8d60 to your computer and use it in GitHub Desktop.
Save nedtwigg/a629e23ec5269894f0ad9558cf5f8d60 to your computer and use it in GitHub Desktop.
A gradle plugin which generates Kotlin model files for each JTE template
import gg.jte.ContentType;
import gg.jte.compiler.TemplateParserVisitorAdapter;
import gg.jte.gradle.JteExtension;
import gg.jte.gradle.JteGradle;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import javax.naming.Name;
import org.gradle.api.DefaultTask;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileType;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import org.gradle.work.ChangeType;
import org.gradle.work.Incremental;
import gg.jte.TemplateConfig;
import gg.jte.compiler.TemplateParser;
import gg.jte.compiler.TemplateType;
import org.gradle.work.InputChanges;
public class JteModelPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().apply(JteGradle.class);
var extension = project.getExtensions().getByType(JteExtension.class);
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
SourceSet main = javaPluginExtension.getSourceSets().findByName("main");
var jteModelsTask = project.getTasks().register("jteModels", RenderModelClasses.class, task -> {
var jteModels = new File(project.getBuildDir(), "jte-models");
main.getJava().srcDir(jteModels);
task.getOutputDir().set(jteModels);
task.getInputDir().set(extension.getSourceDirectory().get().toFile());
task.getPackageName().set(extension.getPackageName());
task.getContentType().set(extension.getContentType());
});
project.getTasks().named("compileKotlin").configure(task -> task.dependsOn(jteModelsTask));
}
public static abstract class RenderModelClasses extends DefaultTask {
@Incremental
@PathSensitive(PathSensitivity.RELATIVE)
@InputDirectory
abstract DirectoryProperty getInputDir();
@OutputDirectory
abstract DirectoryProperty getOutputDir();
@Input
abstract Property<String> getPackageName();
@Input
abstract Property<ContentType> getContentType();
@TaskAction
public void render(InputChanges changes) throws IOException {
var templateConfig = new TemplateConfig(getContentType().get(), getPackageName().get());
var renderer = new JteRenderer(getInputDir().getAsFile().get(), templateConfig);
for (var change : changes.getFileChanges(getInputDir())) {
if (change.getFileType() == FileType.DIRECTORY) {
return;
}
if (!change.getFile().getName().endsWith(".jte")) {
continue;
}
var targetFileJte = getOutputDir().file(change.getNormalizedPath()).get().getAsFile().getAbsolutePath();
var targetFile = new File(targetFileJte.substring(0, targetFileJte.length() - 4) + ".kt");
if (change.getChangeType() == ChangeType.REMOVED) {
targetFile.delete();
} else {
targetFile.getParentFile().mkdirs();
Files.write(targetFile.toPath(), renderer.render(change.getFile()).getBytes(StandardCharsets.UTF_8));
}
}
}
}
static String convertJavaToKotlin(String javaType) {
if (javaType.equals("boolean")) {
return "Boolean";
} else {
return javaType;
}
}
static class JteRenderer {
final File rootDir;
final TemplateConfig config;
JteRenderer(File rootDir, TemplateConfig config) {
this.rootDir = rootDir;
this.config = config;
}
String render(File file) throws IOException {
var pkg = file.getParentFile().getAbsolutePath().substring(rootDir.getAbsolutePath().length() + 1).replace(File.separatorChar, '.');
var name = file.getName();
var lastDot = name.lastIndexOf('.');
name = name.substring(0, lastDot);
var imports = new LinkedHashSet<String>();
imports.add("gg.jte.TemplateEngine");
imports.add("gg.jte.TemplateOutput");
var params = new LinkedHashMap<String, String>();
new TemplateParser(Files.readString(file.toPath()), TemplateType.Template, new TemplateParserVisitorAdapter() {
@Override
public void onImport(String importClass) {
imports.add(importClass);
}
@Override
public void onParam(String parameter) {
var spaceIdx = parameter.indexOf(' ');
var type = parameter.substring(0, spaceIdx).trim();
var name = parameter.substring(spaceIdx + 1).trim();
params.put(name, convertJavaToKotlin(type));
}
}, config).parse();
var builder = new StringBuilder();
builder.append("package " + pkg + "\n");
builder.append("\n");
for (var imp : imports) {
builder.append("import " + imp + "\n");
}
builder.append("\n");
builder.append("class " + name + "(\n");
params.forEach((paramName, type) -> {
builder.append("\tval " + paramName + ": " + type + ",\n");
});
builder.append("\t) : common.JteModel {\n");
builder.append("\n");
builder.append("\toverride fun render(engine: TemplateEngine, output: TemplateOutput) {\n");
builder.append("\t\tengine.render(\"" + pkg.replace('.', '/') + "/" + name + ".jte\", mapOf(\n");
params.forEach((paramName, type) -> {
builder.append("\t\t\t\"" + paramName + "\" to " + paramName + ",\n");
});
builder.append("\t\t), output)\n");
builder.append("\t}\n");
builder.append("}");
return builder.toString();
}
}
}
@nedtwigg
Copy link
Author

This is a Gradle plugin which you can stick into your buildSrc directory. It generates model files which match the jte templates. You can see example output in this gist.

casid/jte#205 for further context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment