Merge branch 'render'

master
Tom Alexander 4 years ago
commit a76a7bb2b8
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE

4
.gitignore vendored

@ -5,3 +5,7 @@ Cargo.lock
# Backup files generated by rustfmt
**/*.rs.bk
# Javascript junk if you run the compliance tests sans docker
js/node_modules
js/package-lock.json

@ -14,3 +14,5 @@ path = "src/bin.rs"
[dependencies]
nom = { git = "https://github.com/tomalexander/nom.git", branch = "take_until_parser_matches" }
serde = "1.0.106"
serde_json = "1.0.51"

@ -0,0 +1,26 @@
FROM rustlang/rust:nightly-alpine3.10
# findutils needed to replace busybox xargs because it is failing to
# pass stdin through when using the `-a` flag
RUN apk --no-cache add bash git npm nodejs findutils
# Create unprivileged user
RUN addgroup -S duster && adduser -S duster -G duster
# Install LinkedIn DustJS. Installing it globally before copying in
# the repo to avoid spamming the npm servers with every codebase
# change.
RUN npm install -g dustjs-linkedin dustjs-helpers
# Copy repo into duster user's directory
RUN mkdir /home/duster/duster
WORKDIR /home/duster/duster
COPY . .
RUN chown -R duster:duster /home/duster/duster
USER duster
RUN git clean -dfxq
RUN cargo build
RUN npm link dustjs-linkedin dustjs-helpers
ENTRYPOINT ["/home/duster/duster/js/run_compliance_suite.bash"]

@ -4,4 +4,4 @@ An implementation of the [LinkedIn fork of DustJS](https://www.dustjs.com/) writ
**NOT RECOMMENDED FOR PUBLIC USE**
This code is available free and open source under the [0BSD](https://choosealicense.com/licenses/0bsd/), but it is a very early-stage project. You're welcome to use it, fork it, print it out and fold it into a hat, etc... but you will find that this project is not yet polished nor feature complete. While this repository uses the 0BSD license which does not require the inclusion of a copyright notice/text in any distribution, it depends on [nom](https://github.com/Geal/nom) which is under the MIT license and the Rust standard library which is [dual licensed](https://github.com/rust-lang/rust/issues/67014).
This code is available free and open source under the [0BSD](https://choosealicense.com/licenses/0bsd/), but it is a very early-stage project. You're welcome to use it, fork it, print it out and fold it into a hat, etc... but you will find that this project is not yet polished nor feature complete. While this repository uses the 0BSD license which does not require the inclusion of a copyright notice/text in any distribution, it depends on [nom](https://github.com/Geal/nom) which is under the MIT license, the Rust standard library which is [dual licensed](https://github.com/rust-lang/rust/issues/67014), serde_json which is [dual licensed](https://github.com/serde-rs/json), and serde which is [dual licensed](https://github.com/serde-rs/serde).

@ -0,0 +1,59 @@
Compliance Tests
================
These tests run my implementation of LinkedIn's Dust and the official implementation of [LinkedIn's DustJS](https://www.dustjs.com) as compares the output to ensure they match.
Running in Docker
=================
Go to the root directory of this repository where there is a `Dockerfile` and run:
``` sh
docker build -t duster . && docker run --rm -i -t duster
```
This command will run through the test cases in `js/test_cases`, comparing the output of the official LinkedIn DustJS implementation against the output of the `duster` implementation. If there are any differences, it will flag the test as a failure.
The tests have a structure of:
``` text
test_cases
└── hello_world
├── input1.json
├── input2.json
├── main.dust
└── partial.dust
```
The folder name `hello_world` is the name of the test group. For each test group there must be a file named `main.dust`. This file will be the top-level template that gets rendered. All other `*.dust` files will get registered into the dust rendering context as partials, with the same name as their file excluding the file extension. For example, `main.dust` could invoke `partial.dust` with `{>partial/}`. Each `*.json` file is a separate context that will be passed into the dust renderer, making each `*.json` file form a test inside the test group.
Running Manually
================
Individually
------------
If you want to invoke the LinkedIn DustJS manually, the `dustjs_shim` expects the json context to be passed in over stdin, and a list of templates to be passed in as arguments, with the first argument serving as the main template. An example invocation from this folder would therefore be:
``` sh
npm install dustjs-linkedin
cat test_cases/hello_world/input1.json | node dustjs_shim.js test_cases/hello_world/main.dust
```
Unlike the docker variant, there is no requirement for the template to be named `main.dust` since it will render the first argument as the main template. Running the same test case through the `duster` implementation would be:
``` sh
npm install dustjs-linkedin
cat test_cases/hello_world/input1.json | ../target/debug/duster-cli test_cases/hello_world/main.dust
```
Batch
-----
If you instead would like to run the entire compliance test suite manually outside of docker, you can run:
``` sh
npm install dustjs-linkedin
(cd .. && cargo build)
./run_compliance_suite.bash
```

@ -0,0 +1,50 @@
// var dust = require("dustjs-linkedin");
var dust = require("dustjs-helpers");
var fs = require("fs");
const path = require("path");
dust.helpers.dumpParameters = function(chunk, context, bodies, params) {
// Dump the parameters to a dust helper to figure out what is passed
// to a helper. I figure I need to make sure I'm passing
// approximately the same information to my render functions if I
// end up supporting custom helpers in the future to make sure
// helpers can be ported easily.
// console.log(JSON.stringify(Array.prototype.slice.call(arguments)));
console.log("chunk: ", chunk);
console.log("context: ", JSON.stringify(context));
console.log("bodies: ", bodies);
console.log("params: ", params);
}
var argv = process.argv.slice(2);
if (argv.length < 1) {
console.error("Expecting only 1 argument (a path to a template)");
process.exit(1);
}
var context = JSON.parse(fs.readFileSync(0, "utf-8"));
var main_template = path.parse(argv[0])["name"];
for (var i = 0, len = argv.length; i < len; ++i) {
var filename = path.parse(argv[i])["name"];
try {
var template_source = fs.readFileSync(argv[i], "utf-8");
} catch (err) {
console.error(err);
process.exit(1);
}
var compiled_template = dust.compile(template_source, filename);
dust.loadSource(compiled_template);
}
dust.render(main_template, context, function(err, out) {
if(err) {
console.error(err);
process.exit(1);
} else {
console.log(out);
process.exit(0);
}
});

@ -0,0 +1,11 @@
This folder is for test cases that are NEVER expected to work on duster but I've written to investigate the behavior of original dust. Examples of this are:
Custom javascript dust helpers
------------------------------
Inside the shim I wrote a very basic custom dust helper to investigate the chunk, context, bodies, and params parameters so that I can theoretically expose a similar interface for custom rust dust helpers. Since I won't be perfectly replicating those objects, duster would never output a byte-for-byte identical output for that helper.
Javascript context helpers
--------------------------
I do not intend to integrate a javascript engine into duster, so context helpers written as javascript inside the json context will not work on duster. I believe, based on my interface-based approach, I'll be able to support runtime functions written in rust and stored in rust objects (not from JSON) to serve the same functionality as context helpers but that still remains to be seen.

@ -0,0 +1,30 @@
Chunk
-----
Some sort of object which contains an array of rendered elements. For example, using:
```
Testing dump parameters
{#names}
{.}
{/names}
```
The first interation would have `["Testing dump parameters", "Alice"]`.
Question: Do any helpers read from chunk or just write to it? If its just writing, then my current architecture of just returning a `String` would work fine, though having a shared buffer thats written to would probably reduce the allocations and therefore improve performance.
Context
-------
Some sort of object that contains essentially the "breadcrumbs" variable I am currently using to track the context tree. Also tracks template name, globals and options. I guess options would be that whole preserve whitespace or not option. I was just going to store this option on the dust renderer type, but I guess I should bake it into the breadcrumbs to pass into helpers if I implement support for custom helpers.
Bodies
------
No idea, but based on the name I assume it contains the contents of the helper, or at least some way to render the internal body of the helper.
Params
------
A mapping of the parameters directly on the helper.

@ -0,0 +1,7 @@
Testing dump parameters
{#names}
{.}
{@dumpParameters foo="bar"}
Internal
{/dumpParameters}
{/names}

@ -0,0 +1,53 @@
#!/usr/bin/env bash
#
# Runs the full suite of tests against LinkedIn DustJS and duster to compare the result
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
failed_count=0
show_diff=0
while (( "$#" )); do
if [ "$1" = "--help" ]; then
cat<<EOF
Runs the full compliance test suite.
Options:
--show-diff Shows the difference between the two dust implementations
EOF
exit 0
elif [ "$1" = "--show-diff" ]; then
show_diff=1
else
(>&2 echo "Unrecognized option: $1")
exit 1
fi
shift
done
while read -r test_group; do
set +e
if [ $show_diff -eq 1 ]; then
"$DIR/run_single_test.bash" --show-diff "$test_group"
else
"$DIR/run_single_test.bash" --show-diff "$test_group"
fi
result=$?
if [ $result -ne 0 ]; then
failed_count=$((failed_count + result))
fi
set -e
done <<<"$(find "$DIR/test_cases" -maxdepth 1 -mindepth 1 -type d ! -name '_*' | sort)"
ignored_count=$(find "$DIR/test_cases" -maxdepth 1 -mindepth 1 -type d -name '_*' | wc -l)
echo ""
echo "$ignored_count ignored tests"
if [ $failed_count -ne 0 ]; then
echo "$failed_count failed tests"
exit 1
else
echo "All tests passed"
fi

@ -0,0 +1,9 @@
#!/usr/bin/env bash
#
# Runs the full suite of tests against LinkedIn DustJS and duster to compare the result
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
docker build -t duster . && docker run --rm -i -t duster "${@}"

@ -0,0 +1,85 @@
#!/usr/bin/env bash
#
# Runs a single test against LinkedIn DustJS and duster to compare the result
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
test_group=""
test_mode=""
function show_help {
cat<<EOF
Runs a single test from the compliance test suite.
Usage: run_single_test.bash [options] <path_to_test_folder>
Options:
--show-diff Shows the difference between the two dust implementations
--dustjs Print the output of dustjs instead of comparing
--duster Print the output of duster instead of comparing
EOF
}
while (( "$#" )); do
if [ "$1" = "--help" ]; then
show_help
exit 0
elif [ "$1" = "--show-diff" ]; then
show_diff=1
elif [ "$1" = "--dustjs" ]; then
test_mode="dustjs"
elif [ "$1" = "--duster" ]; then
test_mode="duster"
elif [ ! "$1" = -* ]; then
test_group="$1"
else
(>&2 echo "Unrecognized option: $1")
exit 1
fi
shift
done
# Assert a test group was specified
if [ "$test_group" = "" ]; then
show_help
exit 1
fi
failed_count=0
test_group_name=$(basename "$test_group")
while read -r test_case; do
test_case_file_name=$(basename "$test_case")
test_case_name=${test_case_file_name%.*}
set +e
if [ "$test_mode" = "dustjs" ] || [ "$test_mode" = "" ]; then
dustjs_output=$(xargs -a <(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name 'main.dust'; find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.dust' ! -name 'main.dust' | sort) node "$DIR/dustjs_shim.js" < "$test_case")
fi
if [ "$test_mode" = "duster" ] || [ "$test_mode" = "" ]; then
duster_output=$(xargs -a <(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name 'main.dust'; find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.dust' ! -name 'main.dust' | sort) "$DIR/../target/debug/duster-cli" < "$test_case")
fi
if [ "$test_mode" = "dustjs" ]; then
cat <<<"$dustjs_output"
elif [ "$test_mode" = "duster" ]; then
cat <<<"$duster_output"
else
(
if cmp -s <(cat <<<"$dustjs_output") <(cat <<<"$duster_output"); then
echo "$test_group_name::$test_case_name PASSED"
else
echo "$test_group_name::$test_case_name FAILED"
if [ $show_diff -eq 1 ]; then
diff --label "dustjs-linkedin" --label "duster" <(cat <<<"$dustjs_output") <(cat <<<"$duster_output")
fi
exit 1
fi
)
if [ $? -ne 0 ]; then
failed_count=$((failed_count + 1))
fi
fi
set -e
done <<<"$(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.json' | sort)"
exit "$failed_count"

@ -0,0 +1,3 @@
Type casting appears to work on referenced values as well as literals. Do I add a separate type cast function or push the entire implementation of the helpers off into the traits? If I'm supporting custom helpers in the future I'll need to push off the helper implementation eventually anyway.
What other types are available? All I see referenced in the docs is "number".

@ -0,0 +1,4 @@
{
"str": "7",
"int": 7
}

@ -0,0 +1,5 @@
{@eq key=str value="7"}str is equal to "7"{:else}str does not equal "7"{/eq}{~n}
{@eq key=int value="7"}int is equal to "7"{:else}int does not equal "7"{/eq}{~n}
{@eq key=int value="7" type="number"}int is equal to "7"::number{:else}int does not equal "7"::number{/eq}{~n}
{@eq key=int value=str}int is equal to str{:else}int does not equal str{/eq}{~n}
{@eq key=int value=str type="number"}int is equal to str::number{:else}int does not equal str::number{/eq}{~n}

@ -0,0 +1 @@
The block content is from {+inject/}

@ -0,0 +1,4 @@
{"people": [
{"name": "Alice", "item": "cat"},
{"name": "Bob"}
]}

@ -0,0 +1,2 @@
{<inject}Before Partial{/inject}
{>"base"/}

@ -0,0 +1 @@
The block content is from {+inject/}

@ -0,0 +1,14 @@
{
"people": [
{
"name": "Alice",
"item": "cat"
},
{
"name": "Bob"
}
],
"pet": {
"name": "fluffy"
}
}

@ -0,0 +1,6 @@
{#pet}
{>"base"/}
{/pet}
{#people}
{<inject}by {name}{/inject}
{/people}

@ -0,0 +1 @@
The block content is from {+inject/}{~n}

@ -0,0 +1,2 @@
{<inject}beta{/inject}
{>"alpha"/}

@ -0,0 +1,4 @@
{"people": [
{"name": "Alice", "item": "cat"},
{"name": "Bob"}
]}

@ -0,0 +1,3 @@
{>"beta"/}
{>"alpha"/}
{<inject}main{/inject}

@ -0,0 +1,2 @@
{<inject}beta{/inject}
{>"alpha"/}

@ -0,0 +1,4 @@
{"people": [
{"name": "Alice", "item": "cat"},
{"name": "Bob"}
]}

@ -0,0 +1,3 @@
The block content is from {+inject/}{~n}
{>"beta"/}
The block content is from {+inject/}{~n}

@ -0,0 +1,9 @@
Blocks seem to be rendered with the last inline partial of the same name.
Blocks appear to be able to be registered in a loop, however, their context is defined by the context during the call to the block `{+}` as opposed to when their override is defined by an inline partial `{<}`.
Even if the surrounding dust would prevent a section from being rendered, the inline context is still registered, which makes me think inline contexts are parsed out in a single pass regardless of the context.
Inline partials in sub-templates do not seem to bubble up to blocks in the higher templates.
After the inverse register order test it seems that it takes the last inline partial with that name at that level, walking up the tree of partials. So each file will have exactly one value for each block name, consisting of the final inline partial with that name. Then when rendering the block, it will walk up the tree just like the context tree.

@ -0,0 +1 @@
The block content is from {+inject/}

@ -0,0 +1,4 @@
{"people": [
{"name": "Alice", "item": "cat"},
{"name": "Bob"}
]}

@ -0,0 +1,3 @@
{<inject}Before Partial{/inject}
{>"base"/}
{<inject}After Partial{/inject}

@ -0,0 +1 @@
The block content is from {+inject/}

@ -0,0 +1,14 @@
{
"people": [
{
"name": "Alice",
"item": "cat"
},
{
"name": "Bob"
}
],
"pet": {
"name": "fluffy"
}
}

@ -0,0 +1,10 @@
{#pet}
{>"base"/}
{/pet}
{#people}
{<inject}by {name}{/inject}
{/people}
{#cake}
OMG CAKE
{<inject}with cake{/inject}
{/cake}

@ -0,0 +1,7 @@
{
"commander": "Chris",
"names": {
"captain": "Alice",
"first_officer": "Bob"
}
}

@ -0,0 +1,5 @@
{#names}
Hello {.captain}!{~n}
Hello {.commander}!{~n}
Hello {commander}!{~n}
{/names}

@ -0,0 +1 @@
{"names": ["Alice", "Bob", "Chris"]}

@ -0,0 +1,3 @@
{#names}
Hello {.}!
{/names}

@ -0,0 +1,2 @@
Testing dynamic partials{~n}
{>"tmpl{name}"/}

@ -0,0 +1 @@
Exists appears to follow the same truthiness rules that sections follow, rather than merely checking if a value exists.

@ -0,0 +1 @@
{"things": ["Alice", "Bob", "Chris"]}

@ -0,0 +1 @@
{"things": {"name": "Alice", "keyboard": "K-Type"}}

@ -0,0 +1 @@
{"there_are_no_things": 4}

@ -0,0 +1 @@
{"things": "just a string"}

@ -0,0 +1,5 @@
{?things}
Thing: {things}
{:else}
No things {.}
{/things}

@ -0,0 +1,74 @@
Priority
--------
Partials: Explicit context takes priority over parameters
Sections: New context takes priority, then parameters, then explicit
Sections with loops: Loop variables ($idx and $len) take priority over parameters and explicit context
$idx and $len
-------------
$idx and $len do not survive through an explicit context setting, which will work perfectly with my injected-context architecture.
You can use $idx and $len as your explicit context, but as scalar values I do not think there is a way to access them anyway.
Exists and Not-Exists
---------------------
Looks like you can exlicitly set a context with exists and not-exists tags too. This works out well in the parser because I am using the same code for those blocks.
Partials
--------
Explicitly setting a context in a partial also works. The explicit context takes priority over the parameters in the partial tag.
This works for both regular named partials and quoted partials.
While normally parameters are injected 1 level above the current context, if both parameters and explicit are set, they are injected below the current context. So if the context tree was `1->2->3`, with just parameters you'd have `1->2->parameters->3` but with an explicit context you have `parameters->explicit`.
Helpers
-------
Explicitly setting a context in a helper works too.
Blocks and Inline Partials
--------------------------
Explicitly setting a context on an inline partial does not work, but it does not error out either, so I need to add support for this in the parser.
Explicitly setting a context on a block does work.
The explicit context for blocks is evaluated in the context for that template file containing the block, not the inline partial (so, whatever the context is when we invoke the partial containing the block).
Parameters on blocks and inline partials appear to not be read but they do not error out.
References
----------
Explicitly setting a context does not work on a reference, but it has revealed that invalid dust is rendered verbatim. I'm going to leave that commented out until I can address that in a later branch.
Paths
-----
Explicit contexts do support deep paths.
Else Blocks
-----------
Else blocks also use an explicit context.
Complete Failure
----------------
If the walk destination does not exist, and the explicit context does not exist, then you end up with absolutely no context.
Regular Path Failure
--------------------
If the regular path fails but the explicit path succeeds then the context is set to the explicit path.
Falsey Explicit Path
--------------------
Since dust would not walk to a falsey path, theres no difference between a falsey path and a non-existent path.

@ -0,0 +1 @@
{+pet_line}BLOCK {$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}{/pet_line}

@ -0,0 +1 @@
{+pet_line:explicit}BLOCK {$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}{/pet_line}

@ -0,0 +1,6 @@
{#block}
{+pet_line}BLOCK: {contents}{~n}{/pet_line}
{/block}
{#inline_partial}
{<pet_line:message}INLINE PARTIAL: {contents}{~n}{/pet_line}
{/inline_partial}

@ -0,0 +1,4 @@
{#block.message}
{>explicit_evaluation_time_split_default/}
{/block.message}
{<pet_line:message}INLINE PARTIAL: {contents}{~n}{/pet_line}

@ -0,0 +1,64 @@
{
"another_idx": {
"$idx": 21,
"$len": 22,
"other": 23
},
"block": {
"message": {
"contents": "Explicit contexts are evaluated in the context of the block."
}
},
"contents": "Explicit contexts are evaluated in the global context.",
"deep_explicit": {
"explicit": {
"pet": "cat"
}
},
"empty_array": [],
"explicit": {
"pet": "cat"
},
"has_idx": {
"$idx": 14
},
"inline_partial": {
"message": {
"contents": "Explicit contexts are evaluated in the context of the inline partial."
}
},
"loop": [
{
"friend": "Bob",
"person": {
"name": "Alice"
}
},
{
"friend": "Chris",
"person": {
"name": "Bob"
}
},
{
"friend": "Alice",
"person": {
"name": "Chris"
}
}
],
"partial_context": {
"block": {
"message": {
"contents": "Explicit contexts are evaluated in the context of the block inside the partial context."
}
},
"contents": "Explicit contexts are evaluated in the global context inside the partial context.",
"inline_partial": {
"message": {
"contents": "Explicit contexts are evaluated in the context of the inline partial inside the partial context."
}
}
},
"some_global": "dog"
}

@ -0,0 +1,415 @@
{! First we do it without explicit context setting which results in being able to read some_global but not pet !}
Section Regular{~n}
==============={~n}
{#loop}
{#person}
{$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/person}
{/loop}
{! Then we do it with explicit context setting which should result in being able ot read pet but not some_global !}
Section Explicit{~n}
================{~n}
{#loop}
{#person:explicit}
{$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/person}
{/loop}
{! What happens if we try to use an explicit context with an exists block instead of a section !}
Exists Regular{~n}
=============={~n}
{#loop}
{?person}
{$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}
{/person}
{/loop}
Exists Explicit{~n}
==============={~n}
{#loop}
{?person:explicit}
{$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}
{/person}
{/loop}
{! Can you explicitly set the context for a partial? !}
Partial Regular{~n}
==============={~n}
{#loop}
{>other/}
{/loop}
Partial Explicit{~n}
================{~n}
{#loop}
{>other:explicit/}
{/loop}
Quoted Partial Explicit{~n}
======================={~n}
{#loop}
{>"other":explicit/}
{/loop}
Partial Regular with parameters{~n}
==============================={~n}
{#loop}
{>other pet="rabbit"/}
{/loop}
Partial Explicit with parameters{~n}
================================{~n}
{#loop}
{>other:explicit pet="rabbit"/}
{/loop}
{! Can you explicitly set the context for a helper? !}
Helper Regular{~n}
=============={~n}
{#loop}
{@eq key="foo" value="foo"}
{$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}
{/eq}
{/loop}
Helper Explicit{~n}
==============={~n}
{#loop}
{@eq:explicit key="foo" value="foo"}
{$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}
{/eq}
{/loop}
{! Can you explicitly set the context for inline partials or blocks? !}
Block Regular{~n}
============={~n}
{#loop}
{>default/}
{/loop}
Inline Partial Regular{~n}
======================{~n}
{#loop}
{>override/}
{/loop}
Block Explicit{~n}
=============={~n}
{#loop}
{>default_explicit/}
{/loop}
Inline Partial Explicit{~n}
======================={~n}
{#loop}
{>override_explicit/}
{/loop}
Inline Partial and Block Explicit{~n}
================================={~n}
{#loop}
{>override_double_explicit/}
{/loop}
{! Can you explicitly set the context for references? !}
{! Commented out until I add support for rendering invalid dust verbatim
Reference Regular{~n}
================={~n}
{.}{~n}
Reference Explicit{~n}
=================={~n}
{.:some_global}{~n}
!}
{! Can you explicitly set the context with a path? !}
Path Regular{~n}
============{~n}
{#loop}
{#person}
{$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/person}
{/loop}
Path Explicit{~n}
============={~n}
{#loop}
{#person:deep_explicit.explicit}
{$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/person}
{/loop}
{! Can you explicitly set the context to a variable? !}
Variable Regular{~n}
================{~n}
{#loop}
{#person}
{$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/person}
{/loop}
Variable Explicit{~n}
================={~n}
{#loop}
{#person:$idx}
{$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/person}
{/loop}
{! What context is set on else blocks? !}
Else Block Regular{~n}
=================={~n}
{#loop}
{#empty_array}
MAIN {$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{:else}
ELSE {$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/empty_array}
{/loop}
Else Block Explicit{~n}
==================={~n}
{#loop}
{#empty_array:explicit}
MAIN {$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}
{:else}
ELSE {$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}
{/empty_array}
{/loop}
{! What context is set when the explicit context path does not exist? !}
Failed Explicit Regular{~n}
======================={~n}
{#loop}
{#person}
{$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/person}
{/loop}
Failed Explicit Explicit{~n}
========================{~n}
{#loop}
{#person:foobar}
{$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/person}
{/loop}
{! What context is set when the regular path and explicit context path does not exist? !}
Failed Everything Regular{~n}
========================={~n}
BEFORE {.|js}{~n}
{#foo}
MAIN {.|js}{~n}
{:else}
ELSE {.|js}{~n}
{/foo}
Failed Everything Explicit{~n}
=========================={~n}
{#foo:bar}
MAIN {.|js}{~n}
{:else}
ELSE {.|js}{~n}
{/foo}
{! What context is set when the regular context path does not exist? !}
Failed Regular Path Regular{~n}
==========================={~n}
{#loop}
{#foo}
MAIN {$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{:else}
ELSE {$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/foo}
{/loop}
Failed Regular Path Explicit{~n}
============================{~n}
{#loop}
{#foo:explicit}
MAIN {$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{:else}
ELSE {$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/foo}
{/loop}
{! What context is set when the explicit path is falsey? !}
Falsey Explicit Path Regular{~n}
============================{~n}
{#loop}
{#foo}
MAIN {$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{:else}
ELSE {$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{/foo}
{/loop}
Falsey Explicit Path Explicit{~n}
============================={~n}
{#loop}
{#foo:empty_array}
MAIN {$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{:else}
ELSE {$idx}: {name} has a pet {pet} but not a {some_global}{~n}
{.|js}{~n}
{/foo}
{/loop}
{! Since partial parameters are normally injected 1 level above the current context, and explicit contexts are below the partial parameters, is the order parameters->current_context->explicit or current_context->parameters->explicit when both are being used? !}
Partial Overloaded Regular with parameters{~n}
=========================================={~n}
{#loop}
{>other_friend friend="Dave" pet="rabbit"/}
{/loop}
Partial Overloaded Explicit with parameters{~n}
==========================================={~n}
{#loop}
{>other_friend:explicit friend="Dave" pet="rabbit"/}
{/loop}
{! Is an explicit context on an inline partial evaluated in the context of the block or the context of the inline partial? !}
Explicit Evaluation Time Global{~n}
==============================={~n}
{>explicit_evaluation_time/}
Explicit Evaluation Time Partial Context{~n}
========================================{~n}
{#partial_context}
{>explicit_evaluation_time/}
{/partial_context}
{! The previous test discovered that the inline partial's explicit context is evaluated based on the context when invoking the full-blown partial, HOWEVER, it shared the same partial context for both the block and the inline partial. To conclusively prove if the explicit context is evaluated on the inline partial and not the block, we need a different partial context for each one. !}
Explicit Evaluation Time Split Partial Context Default{~n}
======================================================{~n}
{#partial_context}
{#block.message}
{>explicit_evaluation_time_split_default/}
{/block.message}
{/partial_context}
Explicit Evaluation Time Split Partial Context OVerride{~n}
======================================================={~n}
{#partial_context}
{#inline_partial}
{>explicit_evaluation_time_split_override/}
{/inline_partial}
{/partial_context}
{! What happens with sections with explicit context and parameters !}
Section set $idx as a parameter{~n}
==============================={~n}
{#explicit $idx="7"}
$idx is {$idx}{~n}
$len is {$len}{~n}
{/explicit}
Section set $idx as a parameter and new context{~n}
==============================================={~n}
{#has_idx $idx="7"}
$idx is {$idx}{~n}
$len is {$len}{~n}
{/has_idx}
Section set $idx as a parameter, new context, and explicit context{~n}
=================================================================={~n}
{#has_idx:another_idx $idx="7" $len="8"}
$idx is {$idx}{~n}
$len is {$len}{~n}
other is {other}{~n}
{/has_idx}
Section vs Partial priority{~n}
==========================={~n}
{#some_global:explicit pet="snake"}
{pet}{~n}
{/some_global}
{>priority:explicit pet="snake"/}
Exists vs Partial priority{~n}
=========================={~n}
{?some_global:explicit pet="snake"}
{pet}{~n}
{/some_global}
{>priority:explicit pet="snake"/}
Section vs NotExists priority{~n}
=========================={~n}
{^some_global:explicit pet="snake"}
MAIN {pet}{~n}
{:else}
ELSE {pet}{~n}
{/some_global}
{>priority:explicit pet="snake"/}
Section vs Partial priority Failed Walk{~n}
======================================={~n}
{#doesnotexist:explicit pet="snake"}
MAIN {pet}{~n}
{:else}
ELSE {pet}{~n}
{/doesnotexist}
{>priority:explicit pet="snake"/}
Section Loop set $idx as a parameter{~n}
===================================={~n}
{#loop $idx="7"}
$idx is {$idx}{~n}
$len is {$len}{~n}
{/loop}
Section Loop set $idx in explicit context{~n}
========================================={~n}
{#loop:has_idx}
$idx is {$idx}{~n}
$len is {$len}{~n}
{/loop}
Section Loop set $idx in explicit context and parameter{~n}
======================================================={~n}
{#loop:has_idx $idx="7"}
$idx is {$idx}{~n}
$len is {$len}{~n}
{/loop}
{! Lets check the priority order for all the tag types now that we know that sections and partials have a different order !}
Section vs Partial priority{~n}
==========================={~n}
{#some_global:explicit pet="snake"}
{pet}{~n}
{/some_global}
{>priority:explicit pet="snake"/}
Exists vs Section priority{~n}
=========================={~n}
{?some_global:explicit pet="snake"}
{pet}{~n}
{/some_global}
{>priority:explicit pet="snake"/}
Not Exists vs Section priority{~n}
=============================={~n}
{^empty_array:explicit pet="snake"}
{pet}{~n}
{/empty_array}
{>priority:explicit pet="snake"/}
{! TODO: Add helpers once we have a helper that sets a variable !}
{! Do blocks or inline partials have parameters? !}
Do blocks or inline partials have parameters{~n}
============================================{~n}
{+some_block_override pet="snake" name="cuddlebunny"}
{pet}, {name}{~n}
{/some_block_override}
{<some_block_override pet="lizard" name="rover"}
{pet}, {name}{~n}
{/some_block_override}
Does exists have parameters{~n}
==========================={~n}
{?some_global pet="snake"}
{pet}{~n}
{/some_global}

@ -0,0 +1 @@
{$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}

@ -0,0 +1 @@
{$idx}: {person.name}'s friend {friend} has a pet {pet} but not a {some_global}{~n}

@ -0,0 +1,2 @@
{>default/}
{<pet_line}INLINE PARTIAL {$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}{/pet_line}

@ -0,0 +1,2 @@
{>default_explicit/}
{<pet_line:explicit}INLINE PARTIAL {$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}{/pet_line}

@ -0,0 +1,2 @@
{>default/}
{<pet_line:explicit}INLINE PARTIAL {$idx}: {person.name} has a pet {pet} but not a {some_global}{~n}{/pet_line}

@ -0,0 +1,13 @@
HTML Escaping
-------------
Dust automatically applies the `|h` filter to html escape unless `|s` is applied to disable automatic html escaping. It seems that if you manually specify `|h` and `|s` in the same filter, then it still html escapes, so my theory on the logic is:
Iterate over all filters
If `|s` is not present append `|h`, otherwise, leave filters as-in
During render, `|s` does nothing, so we can just remove it on the dust side to prevent confusion.
Quoting
-------
Oddly enough, `boolean|j|js` gets no quotes (meaning `boolean|j` remains a boolean) but `boolean|h|js` does get quotes (meaning that `boolean|h` becomes a string)

@ -0,0 +1,15 @@
{
"string": "{\"foo\": \"bar\"}",
"integer": 4,
"float": 7.4,
"boolean": true,
"null": null,
"array": [
"foo",
"bar"
],
"object": {
"foo": "bar"
},
"special_characters": "<>xx\b&\"'\t\f\n\r\\!@#$%^&*()[]{}<>,./?:;_-+=`"
}

@ -0,0 +1,40 @@
Special characters: {special_characters}{~n}
Special characters html escaping disabled: {special_characters|s}{~n}
Special characters html escaping disabled and enabled: {special_characters|s|h}{~n}
Special characters html escaping enabled and disabled: {special_characters|h|s}{~n}
Special characters html escaped once: {special_characters|h}{~n}
Special characters html escaped twice: {special_characters|h|h}{~n}
Object string parsed: {string|jp}{~n}
Object string parsed and stringified: {string|jp|js}{~n}
Object string stringified and parsed: {string|js|jp}{~n}
Array: {array}{~n}
Array stringified: {array|js}{~n}
Array stringified and parsed: {array|js|jp}{~n}
Object: {object}{~n}
Object html escaped: {object|h}{~n}
Object html escaping disabled: {object|s}{~n}
Object stringified: {object|js}{~n}
Object stringified and parsed: {object|js|jp}{~n}
Object stringified, html escaping disabled, parsed, stringified, and html escaped: {object|js|s|jp|js|h}{~n}
Special characters: {special_characters}{~n}
Special characters html escaping disabled and javascript escaped: {special_characters|s|j}{~n}
Special characters javascript escaped and html escaping disabled: {special_characters|j|s}{~n}
Special characters javascript escaped once: {special_characters|j}{~n}
Special characters javascript escaped twice: {special_characters|j|j}{~n}
Special characters: {special_characters}{~n}
Special characters html escaping disabled and encodeURI: {special_characters|s|u}{~n}
Special characters encodeURI and html escaping disabled: {special_characters|u|s}{~n}
Special characters encodeURI once: {special_characters|u}{~n}
Special characters encodeURI twice: {special_characters|u|u}{~n}
Special characters: {special_characters}{~n}
Special characters html escaping disabled and encodeURIComponent: {special_characters|s|uc}{~n}
Special characters encodeURIComponent and html escaping disabled: {special_characters|uc|s}{~n}
Special characters encodeURIComponent once: {special_characters|uc}{~n}
Special characters encodeURIComponent twice: {special_characters|uc|uc}{~n}

@ -0,0 +1,6 @@
{
"name": [
"foo",
"bar"
]
}

@ -0,0 +1,517 @@
Hello {name}!{~n}
Hello {name|s}!{~n}
Hello {name|h}!{~n}
Hello {name|j}!{~n}
Hello {name|u}!{~n}
Hello {name|uc}!{~n}
Hello {name|js}!{~n}
Hello {name|s|h}!{~n}
Hello {name|s|j}!{~n}
Hello {name|s|u}!{~n}
Hello {name|s|uc}!{~n}
Hello {name|s|js}!{~n}
Hello {name|h|s}!{~n}
Hello {name|h|j}!{~n}
Hello {name|h|u}!{~n}
Hello {name|h|uc}!{~n}
Hello {name|h|js}!{~n}
Hello {name|j|s}!{~n}
Hello {name|j|h}!{~n}
Hello {name|j|u}!{~n}
Hello {name|j|uc}!{~n}
Hello {name|j|js}!{~n}
Hello {name|u|s}!{~n}
Hello {name|u|h}!{~n}
Hello {name|u|j}!{~n}
Hello {name|u|uc}!{~n}
Hello {name|u|js}!{~n}
Hello {name|uc|s}!{~n}
Hello {name|uc|h}!{~n}
Hello {name|uc|j}!{~n}
Hello {name|uc|u}!{~n}
Hello {name|uc|js}!{~n}
Hello {name|js|s}!{~n}
Hello {name|js|h}!{~n}
Hello {name|js|j}!{~n}
Hello {name|js|u}!{~n}
Hello {name|js|uc}!{~n}
Hello {name|s|h|j}!{~n}
Hello {name|s|h|u}!{~n}
Hello {name|s|h|uc}!{~n}
Hello {name|s|h|js}!{~n}
Hello {name|s|j|h}!{~n}
Hello {name|s|j|u}!{~n}
Hello {name|s|j|uc}!{~n}
Hello {name|s|j|js}!{~n}
Hello {name|s|u|h}!{~n}
Hello {name|s|u|j}!{~n}
Hello {name|s|u|uc}!{~n}
Hello {name|s|u|js}!{~n}
Hello {name|s|uc|h}!{~n}
Hello {name|s|uc|j}!{~n}
Hello {name|s|uc|u}!{~n}
Hello {name|s|uc|js}!{~n}
Hello {name|s|js|h}!{~n}
Hello {name|s|js|j}!{~n}
Hello {name|s|js|u}!{~n}
Hello {name|s|js|uc}!{~n}
Hello {name|h|s|j}!{~n}
Hello {name|h|s|u}!{~n}
Hello {name|h|s|uc}!{~n}
Hello {name|h|s|js}!{~n}
Hello {name|h|j|s}!{~n}
Hello {name|h|j|u}!{~n}
Hello {name|h|j|uc}!{~n}
Hello {name|h|j|js}!{~n}
Hello {name|h|u|s}!{~n}
Hello {name|h|u|j}!{~n}
Hello {name|h|u|uc}!{~n}
Hello {name|h|u|js}!{~n}
Hello {name|h|uc|s}!{~n}
Hello {name|h|uc|j}!{~n}
Hello {name|h|uc|u}!{~n}
Hello {name|h|uc|js}!{~n}
Hello {name|h|js|s}!{~n}
Hello {name|h|js|j}!{~n}
Hello {name|h|js|u}!{~n}
Hello {name|h|js|uc}!{~n}
Hello {name|j|s|h}!{~n}
Hello {name|j|s|u}!{~n}
Hello {name|j|s|uc}!{~n}
Hello {name|j|s|js}!{~n}
Hello {name|j|h|s}!{~n}
Hello {name|j|h|u}!{~n}
Hello {name|j|h|uc}!{~n}
Hello {name|j|h|js}!{~n}
Hello {name|j|u|s}!{~n}
Hello {name|j|u|h}!{~n}
Hello {name|j|u|uc}!{~n}
Hello {name|j|u|js}!{~n}
Hello {name|j|uc|s}!{~n}
Hello {name|j|uc|h}!{~n}
Hello {name|j|uc|u}!{~n}
Hello {name|j|uc|js}!{~n}
Hello {name|j|js|s}!{~n}
Hello {name|j|js|h}!{~n}
Hello {name|j|js|u}!{~n}
Hello {name|j|js|uc}!{~n}
Hello {name|u|s|h}!{~n}
Hello {name|u|s|j}!{~n}
Hello {name|u|s|uc}!{~n}
Hello {name|u|s|js}!{~n}
Hello {name|u|h|s}!{~n}
Hello {name|u|h|j}!{~n}
Hello {name|u|h|uc}!{~n}
Hello {name|u|h|js}!{~n}
Hello {name|u|j|s}!{~n}
Hello {name|u|j|h}!{~n}
Hello {name|u|j|uc}!{~n}
Hello {name|u|j|js}!{~n}
Hello {name|u|uc|s}!{~n}
Hello {name|u|uc|h}!{~n}
Hello {name|u|uc|j}!{~n}
Hello {name|u|uc|js}!{~n}
Hello {name|u|js|s}!{~n}
Hello {name|u|js|h}!{~n}
Hello {name|u|js|j}!{~n}
Hello {name|u|js|uc}!{~n}
Hello {name|uc|s|h}!{~n}
Hello {name|uc|s|j}!{~n}
Hello {name|uc|s|u}!{~n}
Hello {name|uc|s|js}!{~n}
Hello {name|uc|h|s}!{~n}
Hello {name|uc|h|j}!{~n}
Hello {name|uc|h|u}!{~n}
Hello {name|uc|h|js}!{~n}
Hello {name|uc|j|s}!{~n}
Hello {name|uc|j|h}!{~n}
Hello {name|uc|j|u}!{~n}
Hello {name|uc|j|js}!{~n}
Hello {name|uc|u|s}!{~n}
Hello {name|uc|u|h}!{~n}
Hello {name|uc|u|j}!{~n}
Hello {name|uc|u|js}!{~n}
Hello {name|uc|js|s}!{~n}
Hello {name|uc|js|h}!{~n}
Hello {name|uc|js|j}!{~n}
Hello {name|uc|js|u}!{~n}
Hello {name|js|s|h}!{~n}
Hello {name|js|s|j}!{~n}
Hello {name|js|s|u}!{~n}
Hello {name|js|s|uc}!{~n}
Hello {name|js|h|s}!{~n}
Hello {name|js|h|j}!{~n}
Hello {name|js|h|u}!{~n}
Hello {name|js|h|uc}!{~n}
Hello {name|js|j|s}!{~n}
Hello {name|js|j|h}!{~n}
Hello {name|js|j|u}!{~n}
Hello {name|js|j|uc}!{~n}
Hello {name|js|u|s}!{~n}
Hello {name|js|u|h}!{~n}
Hello {name|js|u|j}!{~n}
Hello {name|js|u|uc}!{~n}
Hello {name|js|uc|s}!{~n}
Hello {name|js|uc|h}!{~n}
Hello {name|js|uc|j}!{~n}
Hello {name|js|uc|u}!{~n}
Hello {name|s|h|j|u}!{~n}
Hello {name|s|h|j|uc}!{~n}
Hello {name|s|h|j|js}!{~n}
Hello {name|s|h|u|j}!{~n}
Hello {name|s|h|u|uc}!{~n}
Hello {name|s|h|u|js}!{~n}
Hello {name|s|h|uc|j}!{~n}
Hello {name|s|h|uc|u}!{~n}
Hello {name|s|h|uc|js}!{~n}
Hello {name|s|h|js|j}!{~n}
Hello {name|s|h|js|u}!{~n}
Hello {name|s|h|js|uc}!{~n}
Hello {name|s|j|h|u}!{~n}
Hello {name|s|j|h|uc}!{~n}
Hello {name|s|j|h|js}!{~n}
Hello {name|s|j|u|h}!{~n}
Hello {name|s|j|u|uc}!{~n}
Hello {name|s|j|u|js}!{~n}
Hello {name|s|j|uc|h}!{~n}
Hello {name|s|j|uc|u}!{~n}
Hello {name|s|j|uc|js}!{~n}
Hello {name|s|j|js|h}!{~n}
Hello {name|s|j|js|u}!{~n}
Hello {name|s|j|js|uc}!{~n}
Hello {name|s|u|h|j}!{~n}
Hello {name|s|u|h|uc}!{~n}
Hello {name|s|u|h|js}!{~n}
Hello {name|s|u|j|h}!{~n}
Hello {name|s|u|j|uc}!{~n}
Hello {name|s|u|j|js}!{~n}
Hello {name|s|u|uc|h}!{~n}
Hello {name|s|u|uc|j}!{~n}
Hello {name|s|u|uc|js}!{~n}
Hello {name|s|u|js|h}!{~n}
Hello {name|s|u|js|j}!{~n}
Hello {name|s|u|js|uc}!{~n}
Hello {name|s|uc|h|j}!{~n}
Hello {name|s|uc|h|u}!{~n}
Hello {name|s|uc|h|js}!{~n}
Hello {name|s|uc|j|h}!{~n}
Hello {name|s|uc|j|u}!{~n}
Hello {name|s|uc|j|js}!{~n}
Hello {name|s|uc|u|h}!{~n}
Hello {name|s|uc|u|j}!{~n}
Hello {name|s|uc|u|js}!{~n}
Hello {name|s|uc|js|h}!{~n}
Hello {name|s|uc|js|j}!{~n}
Hello {name|s|uc|js|u}!{~n}
Hello {name|s|js|h|j}!{~n}
Hello {name|s|js|h|u}!{~n}
Hello {name|s|js|h|uc}!{~n}
Hello {name|s|js|j|h}!{~n}
Hello {name|s|js|j|u}!{~n}
Hello {name|s|js|j|uc}!{~n}
Hello {name|s|js|u|h}!{~n}
Hello {name|s|js|u|j}!{~n}
Hello {name|s|js|u|uc}!{~n}
Hello {name|s|js|uc|h}!{~n}
Hello {name|s|js|uc|j}!{~n}
Hello {name|s|js|uc|u}!{~n}
Hello {name|h|s|j|u}!{~n}
Hello {name|h|s|j|uc}!{~n}
Hello {name|h|s|j|js}!{~n}
Hello {name|h|s|u|j}!{~n}
Hello {name|h|s|u|uc}!{~n}
Hello {name|h|s|u|js}!{~n}
Hello {name|h|s|uc|j}!{~n}
Hello {name|h|s|uc|u}!{~n}