From 0f3459b6037fe8a80bcaf3c6f067d63e6a58842b Mon Sep 17 00:00:00 2001 From: Brett Smith Date: Fri, 15 Mar 2024 12:03:09 +0000 Subject: [PATCH] Added `t:instruct` with one built-in instruction, `reset` that resets the current output buffer. --- README.md | 129 ++++++++++++++++-- .../com/sshtools/tinytemplate/Templates.java | 22 +++ 2 files changed, 141 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3eb6af1..f33c046 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,18 @@ A lightweight Java string template engine. While it is intended to be used with will work with any text content. While small, it has some unique features and is fast and flexible. -It requires just 5 HTML-like tags, and a bash-like variable expression syntax. +It requires just 6 HTML-like tags, and a bash-like variable expression syntax. ## Status -Feature complete. Just some test coverage to complete and addition of Javadoc. +Feature complete. Just some test coverage to complete and more documentation. ## Features * No dependencies, JPMS compliant, Graal Native Image friendly * Fast. See Design Choices. * Simple Java. Public API consists of just 2 main classes, `TemplateModel` and `TemplateProcessor`. - * Simple Content. Just `` (and ``), ``, `` and ``. Bash like variable such as `${myVar}`. + * Simple Content. Just `` (and ``), ``, ``, `` and ``. Bash like variable such as `${myVar}`. * Internationalisation features. ## Design Choices @@ -136,7 +136,9 @@ key1=Some Text key2=Some other text with an argument. Random number is {0} ``` -## Variable Expansion +## Usage + +### Variable Expansion TinyTemplate supports a sort-of-like-Bash syntax for variable expansion. The exact behaviour of each *string* replacement depends on a *parameter* and an *operator*. @@ -150,7 +152,7 @@ Most patterns evaluate a named *parameter*. This can be any *condition*, *variab * Evaluates to `true` when a *variable* of the same name exists and is not an empty string. * Evaluates to `true` when any other type exists. -### ${parameter} +#### ${parameter} Simplest type. Just always substitute with with value of a *variable* from the model. @@ -158,7 +160,7 @@ Simplest type. Just always substitute with with value of a *variable* from the m ${todaysDate} ``` -### ${parameter:?string:otherString} +#### ${parameter:?string:otherString} If *parameter* evaluates to false as either a *variable* or *condition*, the expansion of *otherString* is substituted. Otherwise, the expansion of *string* is substituted. @@ -167,7 +169,7 @@ is substituted. Otherwise, the expansion of *string* is substituted. ${isPM:?Post Meridiem:Ante Meridiem noon} ``` -### ${parameter:-string} +#### ${parameter:-string} If *parameter* evaluates to false as either a *variable* or *condition*, the expansion of *string* is substituted. Otherwise, the value of *parameter* is substituted. @@ -176,7 +178,7 @@ Otherwise, the value of *parameter* is substituted. ${location:-Unknown Location} ``` -### ${parameter:+string} +#### ${parameter:+string} If *parameter* evaluates to false as either a *variable* or *condition*, an empty string is substituted, otherwise the expansion of *string* is substituted. @@ -185,10 +187,117 @@ the expansion of *string* is substituted. ``` -### ${parameter:=string} +#### ${parameter:=string} If *parameter* evaluates to false as either a *variable* or *condition*, the expansion of word is substituted, otherwise an empty string is substituted. ``` -``` \ No newline at end of file +``` + +### Tags + +TinyTemplates primary use is with HTML and fragments of HTML. Tags by default use an *XML* syntax so as to +work well with code editors. Each tag starts with `t:`, so we suggest that you start all documents +with the following header .. + +```html + +``` + +.. and all *fragments* of HTML with the following. + +```html + + +``` + +In both cases, the first line introduces the `t` namespace, so subsequent tags that appear in your +document will not be marked as syntax errors by your editor. + +The 2nd line used with fragments, will cause TinyTemplate to reset it's buffer, and forget any output +so far collected. In effect, it will remove the first line. + +#### If / Else + +Allows conditional inclusion of one or two blocks on content. Every condition in the template is +assigned a *name*, which will be tied to a piece of Java code which produces whether it evaluates to +`true`. + +```html + +

Hello World!

+
+``` + +And the Java. + +```java +model.condition("feelingFriendly", true); +``` + +You can also use `` to provide content that will be rendered when the condition evaluates +to `false`. + +```html + +

Hello World!

+ +

Go away world!

+
+``` + +And the Java. + +```java +model.condition("feelingFriendly", false); +``` + +If no such named condition exists, then checks will be made to see if a *Variable* with the same name +exists. If it doesn't exist, the condition will evaluate as `false`. If it does exist however, then +it's result will depend on the value and it's type. + + * `null` evaluates as false. + * Empty string `""` evaluates as false. + * Any number with a zero value evaluates as false. + * All other values evaluate as true. + +If there is no such condition, and no such variable, then checks will be made to see if any such +*Include* or *Object* exists. + +```html + + +

Name : ${name}

+

Age : ${age}

+

Location : ${location}

+
+ +

I don't know who I am

+
+``` + +```java +if(me != null) { + model.object("me", content -> Template.ofContent(content). + variable("name", "Joe B"). + variable("age", 44). + variable("location", "London")); +} +``` + +#### Include + +TODO + +#### List + +TODO + +#### Object + +TODO + +#### Instruct + +TODO \ No newline at end of file diff --git a/src/main/java/com/sshtools/tinytemplate/Templates.java b/src/main/java/com/sshtools/tinytemplate/Templates.java index 7bfcb5e..3855edd 100644 --- a/src/main/java/com/sshtools/tinytemplate/Templates.java +++ b/src/main/java/com/sshtools/tinytemplate/Templates.java @@ -44,6 +44,7 @@ import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Pattern; @@ -406,6 +407,7 @@ public static TemplateModel ofResource(String resource, Optional lo private final Reader xtext; private StringBuilder buffer = null; + private Optional> instruction = Optional.empty(); public final static Object[] NO_ARGS = new Object[0]; @@ -486,6 +488,11 @@ public List bundles(Locale local) { public List bundles() { return bundles.stream().map(b -> b.apply(locale())).collect(Collectors.toList()); } + + public TemplateModel instruction(Consumer instruction) { + this.instruction = Optional.of(instruction); + return this; + } public TemplateModel bundles(Collection bundles) { bundles.forEach(this::bundle); @@ -1014,6 +1021,11 @@ else if (ch == 't') { block.state = State.START; buf.setLength(0); } + else if(directive.startsWith("t:instruct ")) { + instruction(block, directive.substring(11)); + block.state = State.START; + buf.setLength(0); + } else if (process) { if(processDirective(block, directive)) { buf.setLength(0); @@ -1060,6 +1072,16 @@ else if (process) { } } + private void instruction(Block block, String instruction) { + logger.ifPresent(l -> l.debug("Processing instruction ''{0}''", instruction)); + if(instruction.equals("reset")) { + block.out.setLength(0); + } + else { + block.model.instruction.ifPresent(ip -> ip.accept(instruction)); + } + } + private boolean processDirective(Block block, String directive) { var spc = directive.indexOf(' ');