diff --git a/pom.xml b/pom.xml index fe75472..7d583bc 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,11 @@ hibernate-validator 8.0.0.Final + + io.pebbletemplates + pebble + 3.2.1 + diff --git a/src/main/java/run/undead/javalin/example/Server.java b/src/main/java/run/undead/javalin/example/Server.java index 935fdf7..7eb9020 100644 --- a/src/main/java/run/undead/javalin/example/Server.java +++ b/src/main/java/run/undead/javalin/example/Server.java @@ -29,6 +29,7 @@ public static void main(String[] args) { ), undeadConf) // use the UndeadJavalin instance to register Undead Views to routes .undead("/count", new UndeadCounter()) + .undead("/count/pebble", new UndeadCounterPebble()) .undead("/count/{start}", new UndeadCounter()) .undead("/dashboard", new UndeadSalesDashboard()) .undead("/user/new", new UndeadUserForm()) diff --git a/src/main/java/run/undead/javalin/example/view/UndeadCounterPebble.java b/src/main/java/run/undead/javalin/example/view/UndeadCounterPebble.java new file mode 100644 index 0000000..666ff13 --- /dev/null +++ b/src/main/java/run/undead/javalin/example/view/UndeadCounterPebble.java @@ -0,0 +1,49 @@ +package run.undead.javalin.example.view; + +import io.pebbletemplates.pebble.PebbleEngine; +import run.undead.context.Context; +import run.undead.event.UndeadEvent; +import run.undead.pebble.PebbleTemplateAdaptor; +import run.undead.template.UndeadTemplate; +import run.undead.view.Meta; +import run.undead.view.View; + +import java.util.Map; + +public class UndeadCounterPebble implements View { + private Integer count; + private PebbleTemplateAdaptor adaptor = new PebbleTemplateAdaptor(new PebbleEngine.Builder() + .build()); + + public UndeadCounterPebble() { + this.count = 0; + } + + @Override + public void mount(Context context, Map sessionData, Map params) { + if (params.get("start") != null) { + this.count = Integer.parseInt((String) params.get("start")); + } else { + this.count = 0; + } + } + + @Override + public void handleEvent(Context context, UndeadEvent event) { + if (event.type().equals("inc")) { + this.count++; + } else if (event.type().equals("dec") && this.count > 0) { + this.count--; + } + } + + @Override + public UndeadTemplate render(Meta meta) { + try { + return adaptor.render("count.html", Map.of("count", this.count)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/run/undead/pebble/PebbleTemplateAdaptor.java b/src/main/java/run/undead/pebble/PebbleTemplateAdaptor.java new file mode 100644 index 0000000..880a00c --- /dev/null +++ b/src/main/java/run/undead/pebble/PebbleTemplateAdaptor.java @@ -0,0 +1,72 @@ +package run.undead.pebble; + +import io.pebbletemplates.pebble.PebbleEngine; +import io.pebbletemplates.pebble.template.PebbleTemplate; +import run.undead.template.UndeadTemplate; + +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class PebbleTemplateAdaptor { + + private final PebbleEngine engine; + + public PebbleTemplateAdaptor() { + this(new PebbleEngine.Builder().build()); + } + public PebbleTemplateAdaptor(PebbleEngine engine) { + this.engine = engine; + } + + public UndeadTemplate render(String template, Map data) throws Exception { + PebbleTemplate compiledTemplate = engine.getTemplate(template); + + var writer = new StringTemplateWriter(); + compiledTemplate.evaluate(writer, data); + return new UndeadTemplate(writer.toStringTemplate(), false); + } + + class StringTemplateWriter extends Writer { + private final List fragments = new ArrayList<>(); + private final List values = new ArrayList<>(); + private int writeCounts = 0; + + @Override + public void write(char[] cbuf, int off, int len) { + var data = new String(cbuf, off, len); +// System.out.println("write:" + data); + // all we can do is alternate between fragments and values + if(writeCounts % 2 == 0) { + fragments.add(new String(cbuf, off, len)); + } else { + values.add(new String(cbuf, off, len)); + } + writeCounts++; + } + + @Override + public void flush() { + // no-op + } + + @Override + public void close() { + // no-op + } + + public StringTemplate toStringTemplate() { + if(fragments.size() == values.size()) { + throw new RuntimeException("template part needs default value; likely caused by a control statement like an if statement with zero output. try adding an else statement with a default or empty value"); + } + return StringTemplate.of(fragments, values); + } + + @Override + public String toString() { + return toStringTemplate().interpolate(); + } + + } +} diff --git a/src/main/resources/count.html b/src/main/resources/count.html new file mode 100644 index 0000000..6dfd589 --- /dev/null +++ b/src/main/resources/count.html @@ -0,0 +1,21 @@ +
+
Zombie Count
+
+ + + + + +
+
\ No newline at end of file diff --git a/src/main/resources/sub.html b/src/main/resources/sub.html new file mode 100644 index 0000000..6e4a52b --- /dev/null +++ b/src/main/resources/sub.html @@ -0,0 +1,7 @@ +{% if sub == "foo" %} +

foo

+{% elseif sub == "bar" %} +

bar

+{% else %} +

baz

+{% endif %} diff --git a/src/main/resources/test.html b/src/main/resources/test.html new file mode 100644 index 0000000..866380d --- /dev/null +++ b/src/main/resources/test.html @@ -0,0 +1,28 @@ + + + + + Title + + +Hello {{name}}! + +{% for article in articles %} +

{{ article.title }}

+

{{ article.content }}

+{% else %} +

There are no articles.

+{% endfor %} + + +{% if category == "news" %} +{{ news }} +{% elseif category == "sports" %} +{{ sports }} +{% else %} +

Please select a category

+{% endif %} + +{% include "sub.html" %} + + \ No newline at end of file diff --git a/src/test/java/run/undead/pebble/PebbleAdaptorTest.java b/src/test/java/run/undead/pebble/PebbleAdaptorTest.java new file mode 100644 index 0000000..f070d1d --- /dev/null +++ b/src/test/java/run/undead/pebble/PebbleAdaptorTest.java @@ -0,0 +1,66 @@ +package run.undead.pebble; + +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import static org.junit.jupiter.api.Assertions.assertEquals; +public class PebbleAdaptorTest { + + @Test + public void testAdaptor() throws Exception { + var adaptor = new PebbleTemplateAdaptor(); + var template = adaptor.render("test.html", + Map.of( + "name", "

Undead

", + "articles", List.of(), + "sub", "foo" + ) + ); + + assertEquals(StringTemplate.STR.""" + + + + + Title + + + Hello <h1>Undead</h1>! + +

There are no articles.

+ + +

Please select a category

+ +

foo

+ + """,template.toString()); + System.out.println("parts:" + template.toParts()); + var parts = new LinkedHashMap<>(); + parts.put("0", "&lt;h1&gt;Undead&lt;/h1&gt;"); + parts.put("1", "<p> There are no articles. </p>\n"); + parts.put("2", "<p>Please select a category</p>\n"); + parts.put("3", "<p>foo</p>\n"); + parts.put("s", List.of( + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "Hello ", + "!\n" + + "\n", + "\n" + + "\n", + "\n", + "\n" + + "" + )); + assertEquals(parts, template.toParts()); + + } +}