Skip to content

Commit

Permalink
[WIP] updating readme to reflect latest library state
Browse files Browse the repository at this point in the history
  • Loading branch information
msangel committed Nov 6, 2023
1 parent 4dd315e commit cc37dba
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 68 deletions.
84 changes: 40 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ In Ruby, you'd render a template like this:
With Liqp, the equivalent looks like this:

```java
Template template = Template.parse("hi {{name}}");
TemplateParser parser = new TemplateParser.Builder().build();
Template template = parser.parse("hi {{name}}");
String rendered = template.render("name", "tobi");
System.out.println(rendered);
/*
Expand All @@ -99,8 +100,8 @@ The following examples are equivalent to the previous Liqp example:
#### Map example

```java
Template template = Template.parse("hi {{name}}");
Map<String, Object> map = new HashMap<String, Object>();
Template template = new TemplateParser.Builder().build().parse("hi {{name}}");
Map<String, Object> map = new HashMap<>();
map.put("name", "tobi");
String rendered = template.render(map);
System.out.println(rendered);
Expand All @@ -112,7 +113,7 @@ System.out.println(rendered);
#### JSON example

```java
Template template = Template.parse("hi {{name}}");
Template template = new TemplateParser.Builder().build().parse("hi {{name}}");
String rendered = template.render("{\"name\" : \"tobi\"}");
System.out.println(rendered);
/*
Expand All @@ -124,10 +125,10 @@ System.out.println(rendered);

```java
class MyParams implements Inspectable {
public String name = "tobi";
public String name = "tobi";
};
Template template = Template.parse("hi {{name}}");
String rendered =template.render(new MyParams());
Template template = TemplateParser.DEFAULT.parse("hi {{name}}");
String rendered = template.render(new MyParams());
System.out.println(rendered);
/*
hi tobi
Expand All @@ -142,51 +143,46 @@ class MyLazy implements LiquidSupport {
return Collections.singletonMap("name", "tobi");
}
};
Template template = Template.parse("hi {{name}}");
Template template = TemplateParser.DEFAULT.parse("hi {{name}}");
String rendered = template.render(new MyLazy());
System.out.println(rendered);
/*
hi tobi
*/
```

#### Strict variables example
#### Controlling library behavior
The library has a set of keys to control the parsing/rendering process. All of them are set on `TemplateParser.Builder` class. Here they are:
* `withFlavor(Flavor flavor)` - flavor of the liquid language. Flavor is nothing else than a predefined set of other settings. Here are supported flavors:
* `Flavor.JEKYLL` - flavor that defines all settings, so it tries to behave like jekyll's templates
* `Flavor.LIQUID` - the same for liquid's templates
* `Flavor.LIQP` (default) - developer of this library found some default behavior of two flavors above somehow weird in selected cases. So this flavor appears.
* `withStripSingleLine(boolean stripSingleLine)`- if `true` then all blank lines left by outputless tags are removed. Default is `false`.
* `withStripSpaceAroundTags(boolean stripSpacesAroundTags)` - if `true` then all whitespaces around tags are removed. Default is `false`.
* `withObjectMapper(ObjectMapper mapper)` - if provided then this mapper is used for converting json strings to objects and internal object conversion. If not provided, then default mapper is used. Default one is good. Also, the default one is always accessible via TemplateContext instance:`context.getParser().getMapper();`
* `withTag(Tag tag)` - register custom tag to be used in templates.
* `withBlock(Block block)` - register custom block to be used in templates. The difference between tag and block is that block has open and closing tag and can contain other content like a text, tags and blocks.
* `withFilter(Filter filter)` - register custom filter to be used in templates. See below for examples.
* `withEvaluateInOutputTag(boolean evaluateInOutputTag)` - both `Flavor.JEKYLL` and `Flavor.LIQUID` are not allows to evaluate expressions in output tags, simply ignoring the expression and printing out very first token of the expression. Yes, this: `{{ 97 > 96 }}` will print `97`. This is known [bug/feature](https://github.com/Shopify/liquid/issues/1102) of those temlators. If you want to change this behavior and evaluate those expressions, set this flag to `true`. Also, the default flavor `Flavor.LIQP` has this flag set to `true` already.
* `withStrictTypedExpressions(boolean strictTypedExpressions)` - ruby is strong-typed language. So comparing different types is not allowed there. This library tries to mimic ruby's type system in a way so all not explicit types (created or manipulated inside of templates) are converted with this schema: `nil` -> `null`; `boolean` -> `boolean`; `string` -> `java.lang.String`; any numbers -> `java.math.BigDecimal`, any datetime -> `java.time.ZonedDateTime`. If you want to change this behavior, and allow comparing in expressions in a less restricted way, set this flag to `true`, then the lax (javascript-like) approach for comparing in expressions will be used. Also, the default flavor `Flavor.LIQP` has this flag set to `true` already, others has it `false` by default.
* `withLiquidStyleInclude(boolean liquidStyleInclude)` - if `true` then include tag will use [syntax from liquid](https://shopify.dev/docs/api/liquid/tags/include), otherwice [jekyll syntax](https://jekyllrb.com/docs/includes/) will be used. Default depends of flavor. `Flavor.LIQUID` and `Flavor.LIQP` has this flag set to `true` already. `Flavor.JEKYLL` has it `false`.
* `withStrictVariables(boolean strictVariables)` - if set to `true` then all variables must be defined before usage, if some variable is not defined, the exception will be thrown. If `false` then all undefined variables will be treated as `null`. Default is `false`.
* `withShowExceptionsFromInclude(boolean showExceptionsFromInclude)` - if set to `true` then all exceptions from included templates will be thrown. If `false` then all exceptions from included templates will be ignored. Default is `true`.
* `withEvaluateMode(TemplateParser.EvaluateMode evaluateMode)` - there exists two rendering modes: `TemplateParser.EvaluateMode.LAZY` and `TemplateParser.EvaluateMode.EAGER`. By default, the `lazy` one is used. This should do the work in most cases.
* In `lazy` mode the template parameters are evaluating on demand and specific properties are read from there only if they are needed. Each filter/tag trying to do its work with its own parameter object, that can be literally anything.
* In `eager` the entire parameter object is converted into plain data tree structure that are made **only** from maps and lists, so tags/filters do know how to work with these kinds of objects. Special case - temporal objects, they are consumed as is.
* `withRenderTransformer(RenderTransformer renderTransformer)` - even if most of elements (filters/tags/blocks) returns its results most cases as `String`, the task of combining all those strings into a final result is a task of `liqp.RenderTransformer` implementation. The default `liqp.RenderTransformerDefaultImpl` uses `StringBuilder` for that task, so template rendering is fast. Althought, you might have special needs or environment to render the results.
* `withLocale(Locale locale)` - locale to be used for rendering. Default is `Locale.ENGLISH`. Used mostly for time rendering.
* `withDefaultTimeZone(ZoneId defaultTimeZone)` - default time zone to be used for rendering. Default is `ZoneId.systemDefault()`. Used mostly for time rendering.
* `withEnvironmentMapConfigurator(Consumer<Map<String, Object>> configurator)` - if provided then this configurator is called before each template rendering. It can be used to set some global variables for all templates built with given `TemplateParser`.
* `withSnippetsFolderName(String snippetsFolderName)` - define folder to be used for searching files by `include` tag. Defaults depend on flavor: `Flavor.LIQUID` and `Flavor.LIQP` has this set to `snippets`; `Flavor.JEKYLL` uses `_includes`.
* `withNameResolver(NameResolver nameResolver)` - if provided then this resolver is used for resolving names of included files. If not provided, then default resolver is used. Default resolver is `liqp.antlr.LocalFSNameResolver` that uses `java.nio.file.Path` for resolving names in local file system. That can be changed to any other resolver, for example, to resolve names in classpath or in remote file system or even build templates dynamically by name.
* `withMaxIterations(int maxIterations)` - maximum number of iterations allowed in a template. Default is `Integer.MAX_VALUE`. Used to prevent infinite loops.
* `withMaxSizeRenderedString(int maxSizeRenderedString)` - maximum size of rendered string. Default is `Integer.MAX_VALUE`. Used to prevent out of memory errors.
* `withMaxRenderTimeMillis(long maxRenderTimeMillis)` - maximum time allowed for template rendering. Default is `Long.MAX_VALUE`. Used to prevent never-ending rendering.
* `withMaxTemplateSizeBytes(long maxTemplateSizeBytes)` - maximum size of template. Default is `Long.MAX_VALUE`. Used to prevent out of memory errors.
* `withErrorMode(ErrorMode errorMode)` - error mode to be used. Default is `ErrorMode.STRICT`.

Strict variables means that value for every key must be provided, otherwise an exception occurs.

```java
Template template = Template.parse("hi {{name}}")
.withRenderSettings(new RenderSettings.Builder().withStrictVariables(true).build());
String rendered = template.render(); // no value for "name"
// exception is thrown
```

#### Eager and Lazy evaluate mode
There exists two rendering modes: lazy and eager.
* In `lazy` mode the template parameters are evaluating on demand and specific properties are read from there only if they are needed. Each filter/tag trying to do its work with its own parameter object, that can be literally anything.
* In `eager` the entire parameter object is converted into plain data tree structure that are made <strong>only</strong> from maps and lists, so tags/filters do know how to work with these kinds of objects. Special case - temporal objects, they are consumed as is.

By <strong>default</strong>, the `lazy` one is used. This should do the work in most cases.

Switching mode is possible via providing special `RenderSettings`.
Example usage of `lazy` mode:
```java
RenderSettings renderSettings = new RenderSettings.Builder()
.withEvaluateMode(RenderSettings.EvaluateMode.EAGER)
.build();

Map<String, Object> in = Collections.singletonMap("a", new Object() {
public String val = "tobi";
});

String res = Template.parse("hi {{a.val}}")
.withRenderSettings(renderSettings)
.render(in);
System.out.println(res);
/*
hi tobi
*/
```

### 2.1 Custom filters

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/liqp/Examples.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public Object apply(Object value, TemplateContext context, Object... params) {

private static void customLoopBlock() {

TemplateParser parser = new TemplateParser.Builder().withInsertion(new Block("loop") {
TemplateParser parser = new TemplateParser.Builder().withBlock(new Block("loop") {
@Override
public Object render(TemplateContext context, LNode... nodes) {

Expand Down Expand Up @@ -181,7 +181,7 @@ public static void demoStrictVariables() {
}

public static void customRandomTag() {
TemplateParser parser = new TemplateParser.Builder().withInsertion(new Tag("rand") {
TemplateParser parser = new TemplateParser.Builder().withTag(new Tag("rand") {
private final Random rand = new Random();

@Override
Expand Down
15 changes: 11 additions & 4 deletions src/main/java/liqp/TemplateParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import liqp.antlr.LocalFSNameResolver;
import liqp.antlr.NameResolver;
import liqp.blocks.Block;
import liqp.filters.Filter;
import liqp.filters.Filters;
import liqp.parser.Flavor;
import liqp.parser.LiquidSupport;
import liqp.tags.Tag;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;

Expand Down Expand Up @@ -127,7 +129,7 @@ public static class Builder {

private Flavor flavor;
private boolean stripSpacesAroundTags = false;
private boolean stripSingleLine;
private boolean stripSingleLine = false;
private ObjectMapper mapper;
private List<Insertion> insertions = new ArrayList<>();
private List<Filter> filters = new ArrayList<>();
Expand All @@ -139,7 +141,7 @@ public static class Builder {


private boolean strictVariables = false;
private boolean showExceptionsFromInclude;
private boolean showExceptionsFromInclude = true;
private EvaluateMode evaluateMode = EvaluateMode.LAZY;
private Locale locale = DEFAULT_LOCALE;
private ZoneId defaultTimeZone;
Expand Down Expand Up @@ -221,8 +223,13 @@ public Builder withObjectMapper(ObjectMapper mapper) {
return this;
}

public Builder withInsertion(Insertion insertion) {
this.insertions.add(insertion);
public Builder withBlock(Block block) {
this.insertions.add(block);
return this;
}

public Builder withTag(Tag tag) {
this.insertions.add(tag);
return this;
}

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/liqp/nodes/InsertionNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

public class InsertionNode implements LNode {

private Insertion insertion;
private LNode[] tokens;
private final Insertion insertion;
private final LNode[] tokens;

public InsertionNode(Insertion insertion, List<LNode> tokens) {
this(insertion.name, insertion, tokens.toArray(new LNode[tokens.size()]));
this(insertion.name, insertion, tokens.toArray(new LNode[0]));
}

public InsertionNode(Insertion insertion, LNode... tokens) {
Expand Down
14 changes: 7 additions & 7 deletions src/test/java/liqp/InsertionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class InsertionTest {
public void testNestedCustomTagsAndBlocks() {

TemplateParser templateParser = new TemplateParser.Builder()
.withInsertion(new Block("block") {
.withBlock(new Block("block") {
@Override
public Object render(TemplateContext context, LNode... nodes) {
String data = (nodes.length >= 2 ? nodes[1].render(context) : nodes[0].render(
Expand All @@ -25,7 +25,7 @@ public Object render(TemplateContext context, LNode... nodes) {
return "blk[" + data + "]";
}
})
.withInsertion(new Tag("simple") {
.withTag(new Tag("simple") {
@Override
public Object render(TemplateContext context, LNode... nodes) {
return "(sim)";
Expand All @@ -41,7 +41,7 @@ public Object render(TemplateContext context, LNode... nodes) {
public void testNestedCustomTagsAndBlocksAsOneCollection() {
String templateString = "{% block %}a{% simple %}b{% block %}c{% endblock %}d{% endblock %}";

TemplateParser parser = new TemplateParser.Builder().withInsertion(
TemplateParser parser = new TemplateParser.Builder().withBlock(
new Block("block") {
@Override
public Object render(TemplateContext context, LNode... nodes) {
Expand All @@ -50,7 +50,7 @@ public Object render(TemplateContext context, LNode... nodes) {

return "blk[" + data + "]";
}
}).withInsertion(new Tag("simple") {
}).withTag(new Tag("simple") {
@Override
public Object render(TemplateContext context, LNode... nodes) {
return "(sim)";
Expand All @@ -64,7 +64,7 @@ public Object render(TemplateContext context, LNode... nodes) {
@Test
public void testCustomTag() throws RecognitionException {

TemplateParser parser = new TemplateParser.Builder().withInsertion(new Tag("twice") {
TemplateParser parser = new TemplateParser.Builder().withTag(new Tag("twice") {
@Override
public Object render(TemplateContext context, LNode... nodes) {
Double number = super.asNumber(nodes[0].render(context)).doubleValue();
Expand All @@ -80,7 +80,7 @@ public Object render(TemplateContext context, LNode... nodes) {

@Test
public void testCustomTagBlock() throws RecognitionException {
TemplateParser templateParser = new TemplateParser.Builder().withInsertion(new Block("twice") {
TemplateParser templateParser = new TemplateParser.Builder().withBlock(new Block("twice") {
@Override
public Object render(TemplateContext context, LNode... nodes) {
LNode blockNode = nodes[nodes.length - 1];
Expand Down Expand Up @@ -186,7 +186,7 @@ public void no_transformTest() throws RecognitionException {
@Test
public void testCustomTagRegistration() {
TemplateParser parser = new TemplateParser.Builder()
.withInsertion(new Tag("custom_tag") {
.withTag(new Tag("custom_tag") {
@Override
public Object render(TemplateContext context, LNode... nodes) {
return "xxx";
Expand Down
31 changes: 30 additions & 1 deletion src/test/java/liqp/ReadmeSamplesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,47 @@
import org.junit.Test;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.assertEquals;

public class ReadmeSamplesTest {


@Test
public void testReadMeIntro() {
TemplateParser parser = new TemplateParser.Builder().build();
Template template = parser.parse("hi {{name}}");
String rendered = template.render("name", "tobi");
System.out.println(rendered);
}

@Test
public void testReadMeMap() {
Template template = new TemplateParser.Builder().build().parse("hi {{name}}");
Map<String, Object> map = new HashMap<>();
map.put("name", "tobi");
String rendered = template.render(map);
System.out.println(rendered);
}

@Test
public void testReadMeJson() {
Template template = new TemplateParser.Builder().build().parse("hi {{name}}");
String rendered = template.render("{\"name\" : \"tobi\"}");
System.out.println(rendered);
}

@Test
@SuppressWarnings({"unused", "FieldMayBeFinal"})
public void testInspectable() {
class MyParams implements Inspectable {
@SuppressWarnings("unused")
public String name = "tobi";
};
Template template = TemplateParser.DEFAULT.parse("hi {{name}}");
String rendered = template.render(new MyParams());
System.out.println(rendered);
assertEquals("hi tobi", rendered);
}

Expand All @@ -31,6 +59,7 @@ public Map<String, Object> toLiquid() {
};
Template template = TemplateParser.DEFAULT.parse("hi {{name}}");
String rendered = template.render(new MyLazy());
System.out.println(rendered);
assertEquals("hi tobi", rendered);
}

Expand Down
4 changes: 2 additions & 2 deletions src/test/java/liqp/TemplateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ public void testCustomTagMissingErrorReporting() {
@Test
public void testWithCustomTag() {
// given
TemplateParser parser = new TemplateParser.Builder().withInsertion(new Tag("custom_tag") {
TemplateParser parser = new TemplateParser.Builder().withTag(new Tag("custom_tag") {
@Override
public Object render(TemplateContext context, LNode... nodes) {
return "xxx";
Expand All @@ -283,7 +283,7 @@ public Object render(TemplateContext context, LNode... nodes) {
@Test
public void testWithCustomBlock() {
// given
TemplateParser parser = new TemplateParser.Builder().withInsertion(new Block("custom_uppercase_block") {
TemplateParser parser = new TemplateParser.Builder().withBlock(new Block("custom_uppercase_block") {
@Override
public Object render(TemplateContext context, LNode... nodes) {
LNode block = nodes[0];
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/liqp/nodes/BlockNodeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class BlockNodeTest {
*/
@Test
public void customTagTest() throws RecognitionException {
TemplateParser parser = new TemplateParser.Builder().withInsertion(new Block("testtag"){
TemplateParser parser = new TemplateParser.Builder().withBlock(new Block("testtag"){
@Override
public Object render(TemplateContext context, LNode... nodes) {
return null;
Expand Down
Loading

0 comments on commit cc37dba

Please sign in to comment.