Skip to content

Commit

Permalink
Added t:instruct with one built-in instruction, reset that resets…
Browse files Browse the repository at this point in the history
… the current output buffer.
  • Loading branch information
brett-smith committed Mar 15, 2024
1 parent 43102bd commit 0f3459b
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 10 deletions.
129 changes: 119 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<t:if>` (and `<t:else>`), `<t:include>`, `<t:object>` and `<t:list>`. Bash like variable such as `${myVar}`.
* Simple Content. Just `<t:if>` (and `<t:else>`), `<t:include>`, `<t:object>`, `<t:list>` and `<t:instruct/>`. Bash like variable such as `${myVar}`.
* Internationalisation features.

## Design Choices
Expand Down Expand Up @@ -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*.
Expand All @@ -150,15 +152,15 @@ 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.

```
${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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -185,10 +187,117 @@ the expansion of *string* is substituted.
<input type="checked" ${selected:+checked} name="selected">
```

### ${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.

```
<button type="button" ${clipboard-empty:=disabled} id="paste">Paste</button>
```
```

### 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
<html lang="en" xmlns:t="https://jadaptive.com/t">
```

.. and all *fragments* of HTML with the following.

```html
<html lang="en" xmlns:t="https://jadaptive.com/t">
<t:instruct reset/>
```

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
<t:if feelingFriendly>
<p>Hello World!</p>
</t:if>
```

And the Java.

```java
model.condition("feelingFriendly", true);
```

You can also use `<t:else/>` to provide content that will be rendered when the condition evaluates
to `false`.

```html
<t:if feelingFriendly>
<p>Hello World!</p>
<t:else/>
<p>Go away world!</p>
</t:if>
```

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
<t:if me>
<t:object me>
<p>Name : ${name}</p>
<p>Age : ${age}</p>
<p>Location : ${location}</p>
</t:object>
<t:else/>
<p>I don't know who I am</p>
</t:if>
```

```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
22 changes: 22 additions & 0 deletions src/main/java/com/sshtools/tinytemplate/Templates.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -406,6 +407,7 @@ public static TemplateModel ofResource(String resource, Optional<ClassLoader> lo

private final Reader xtext;
private StringBuilder buffer = null;
private Optional<Consumer<String>> instruction = Optional.empty();

public final static Object[] NO_ARGS = new Object[0];

Expand Down Expand Up @@ -486,6 +488,11 @@ public List<ResourceBundle> bundles(Locale local) {
public List<ResourceBundle> bundles() {
return bundles.stream().map(b -> b.apply(locale())).collect(Collectors.toList());
}

public TemplateModel instruction(Consumer<String> instruction) {
this.instruction = Optional.of(instruction);
return this;
}

public TemplateModel bundles(Collection<ResourceBundle> bundles) {
bundles.forEach(this::bundle);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(' ');
Expand Down

0 comments on commit 0f3459b

Please sign in to comment.