Merge branch 'dust_helpers' into render

This commit is contained in:
Tom Alexander 2020-05-16 19:07:03 -04:00
commit 974ce96f14
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
26 changed files with 1492 additions and 149 deletions

View File

@ -10,7 +10,7 @@ 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
RUN npm install -g dustjs-linkedin dustjs-helpers
# Copy repo into duster user's directory
@ -22,5 +22,5 @@ RUN chown -R duster:duster /home/duster/duster
USER duster
RUN git clean -dfxq
RUN cargo build
RUN npm link dustjs-linkedin
RUN npm link dustjs-linkedin dustjs-helpers
ENTRYPOINT ["/home/duster/duster/js/run_compliance_suite.bash"]

View File

@ -1,20 +1,21 @@
var dust = require('dustjs-linkedin');
var fs = require('fs');
const path = require('path');
// var dust = require("dustjs-linkedin");
var dust = require("dustjs-helpers");
var fs = require("fs");
const path = require("path");
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 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');
var template_source = fs.readFileSync(argv[i], "utf-8");
} catch (err) {
console.error(err);
process.exit(1);

View File

@ -38,7 +38,7 @@ while read -r test_group; do
else
echo "$test_group_name::$test_case_name FAILED"
if [ $show_diff -eq 1 ]; then
diff --label "dustjs-linked" --label "duster" <(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" 2>/dev/null ) <(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" 2>/dev/null )
diff --label "dustjs-linkedin" --label "duster" <(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" 2>/dev/null ) <(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" 2>/dev/null )
fi
exit 1
fi
@ -48,7 +48,12 @@ while read -r test_group; do
fi
set -e
done <<<"$(find "$test_group" -maxdepth 1 -mindepth 1 -type f -name '*.json' | sort)"
done <<<"$(find "$DIR/test_cases" -maxdepth 1 -mindepth 1 -type d | sort)"
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"

View File

@ -0,0 +1,15 @@
Without a key parameter, neither the main block nor the else block is rendered.
Literal values work in both keys and values.
Can't Walk Theory
-----------------
Assuming a missing value = cantwalk and a non-existent key = cantwalk then their equality makes sense.
The null tests have proven that absent parameters and missing values do not equal null and therefore it is not just making all falsey values equal.
Non-Scalar Values
-----------------
For non-scalar values, it seems that dust does mark two values as true if they have the same path, but otherwise they are not equal.

View File

@ -0,0 +1,60 @@
{
"str": "master",
"int": 7,
"alpha": 21,
"beta": "21",
"null": null,
"true_value": true,
"false_value": false,
"array_lower": [
3,
5,
7
],
"copy_array_lower": [
3,
5,
7
],
"array_higher": [
8,
9
],
"some_obj": {
"name": "cat"
},
"copy_some_obj": {
"name": "cat"
},
"other_obj": {
"name": "dog"
},
"array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"copy_array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"array_of_other_obj": [
{
"name": "dog"
},
{
"name": "dog"
}
],
"array_of_strings": [
"cat",
"dog"
]
}

View File

@ -0,0 +1,28 @@
Testing helpers:{~n}
str is {str}{~n}
int is {int}{~n}
alpha is {alpha}{~n}
beta is {beta}{~n}
{@eq key=str value="master"}str is equal to "master"{:else}str does not equal "master"{/eq}{~n}
{@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}int is equal to 7{:else}int does not equal 7{/eq}{~n}
{@eq key=alpha value=beta}alpha is equal to beta{:else}alpha does not equal beta{/eq}{~n}
{@eq value=beta}missing key is true{:else}missing key is false{/eq}{~n}
{@eq value=gamma}missing key and non-existent value is true{:else}missing key and non-existent value is false{/eq}{~n}
{@eq key=alpha}missing value is true{:else}missing value is false{/eq}{~n}
{@eq key=gamma}missing value and non-existent key is true{:else}missing value and non-existent key is false{/eq}{~n}
{@eq key="master" value="master"}"master" is equal to "master"{:else}"master" does not equal "master"{/eq}{~n}
{@eq key=null}null equals a missing value{:else}null does not equal a missing value{/eq}{~n}
{@eq key=null value=gamma}null equals a non-existent value{:else}null does not equal a non-existent value{/eq}{~n}
{@eq}no parameters is true{:else}no parameters is false{/eq}{~n}
{@eq key=array_lower value=array_higher}[3,5,7] is equal to [8,9]{:else}[3,5,7] does not equal [8,9]{/eq}{~n}
{! non-scalar and copied value tests !}
{@eq key=array_lower value=array_lower}array_lower is equal to array_lower{:else}array_lower does not equal array_lower{/eq}{~n}
{@eq key=array_lower value=copy_array_lower}array_lower is equal to copy_array_lower{:else}array_lower does not equal copy_array_lower{/eq}{~n}
{@eq key=some_obj value=some_obj}some_obj is equal to some_obj{:else}some_obj does not equal some_obj{/eq}{~n}
{@eq key=some_obj value=copy_some_obj}some_obj is equal to copy_some_obj{:else}some_obj does not equal copy_some_obj{/eq}{~n}
{@eq key=some_obj value=other_obj}some_obj is equal to other_obj{:else}some_obj does not equal other_obj{/eq}{~n}
{@eq key=array_of_some_obj value=array_of_some_obj}array_of_some_obj is equal to array_of_some_obj{:else}array_of_some_obj does not equal array_of_some_obj{/eq}{~n}
{@eq key=array_of_some_obj value=copy_array_of_some_obj}array_of_some_obj is equal to copy_array_of_some_obj{:else}array_of_some_obj does not equal copy_array_of_some_obj{/eq}{~n}
{@eq key=array_of_some_obj value=array_of_other_obj}array_of_some_obj is equal to array_of_other_obj{:else}array_of_some_obj does not equal array_of_other_obj{/eq}{~n}

View File

@ -0,0 +1,37 @@
Based on my tests, it appears dust is sorting based on ascii-table values. This also appears to extend to unicode codepoints based on a symbols test.
Greater than follows the same pattern for not rendering when key is omitted or null.
Longer arrays are greater than shorter arrays if all preceding values match.
Theory for comparing arrays: Compare the values, if theres any mismatched types then take the same action you would for non-matching scalar types.
greater than
------------
All comparisons between non-matching types (for example, int vs string) appear to render the else block.
Comparisons between non-scalar types (like arrays) appears to render the else block
greater than or equals to
-------------------------
All comparisons between non-matching types (for example, int vs string) appear to render the main block.
Comparisons between non-scalar types (like arrays) appears to render the main block
less than
---------
All comparisons between non-matching types (for example, int vs string) appear to render the else block.
Comparisons between non-scalar types (like arrays) appears to render the else block
less than or equal to
---------------------
All comparisons between non-matching types (for example, int vs string) appear to render the main block.
Comparisons between non-scalar types (like arrays) appears to render the main block

View File

@ -0,0 +1,72 @@
{
"str": "master",
"int": 7,
"alpha": 21,
"beta": "21",
"null": null,
"true_value": true,
"false_value": false,
"array_lower": [
3,
5,
7
],
"array_lower_with_array": [
3,
5,
[
7
]
],
"copy_array_lower": [
3,
5,
7
],
"array_higher": [
8,
9
],
"array_higher_longer": [
8,
9,
1
],
"some_obj": {
"name": "cat"
},
"copy_some_obj": {
"name": "cat"
},
"other_obj": {
"name": "dog"
},
"array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"copy_array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"array_of_other_obj": [
{
"name": "dog"
},
{
"name": "dog"
}
],
"array_of_strings": [
"cat",
"dog"
]
}

View File

@ -0,0 +1,42 @@
Testing helpers:{~n}
str is {str}{~n}
int is {int}{~n}
alpha is {alpha}{~n}
beta is {beta}{~n}
{@gt key=str value="master"}str is greater than "master"{:else}str is less than or equal to "master"{/gt}{~n}
{@gt key=str value="7"}str is greater than "7"{:else}str is less than or equal to "7"{/gt}{~n}
{@gt key=int value="7"}int is greater than "7"{:else}int is less than or equal to "7"{/gt}{~n}
{@gt key=int value=7}int is greater than 7{:else}int is less than or equal to 7{/gt}{~n}
{@gt key=int value=6}int is greater than 6{:else}int is less than or equal to 6{/gt}{~n}
{@gt key=alpha value=beta}alpha is greater than beta{:else}alpha is less than or equal to beta{/gt}{~n}
{@gt value=beta}missing key is true{:else}missing key is false{/gt}{~n}
{@gt value=gamma}missing key and non-existent value is true{:else}missing key and non-existent value is false{/gt}{~n}
{@gt key=alpha}missing value is true{:else}missing value is false{/gt}{~n}
{@gt key=gamma}missing value and non-existent key is true{:else}missing value and non-existent key is false{/gt}{~n}
{@gt key="master" value="master"}"master" is greater than "master"{:else}"master" is less than or equal to "master"{/gt}{~n}
{@gt key=null}null is greater than a missing value{:else}null is less than or equal to a missing value{/gt}{~n}
{@gt key=null value=gamma}null is greater than a non-existent value{:else}null is less than or equal to a non-existent value{/gt}{~n}
{@gt}no parameters is true{:else}no parameters is false{/gt}{~n}
{@gt key="a" value="]"}"a" is greater than "]"{:else}"a" is less than or equal to "]"{/gt}{~n}
{@gt key="a" value="A"}"a" is greater than "A"{:else}"a" is less than or equal to "A"{/gt}{~n}
{@gt key="a" value="}"}"a" is greater than "}"{:else}"a" is less than or equal to "}"{/gt}{~n}
{!
Commented out because unicode breaks nom
{@gt key="☃" value="☄"}"☃" is greater than "☄"{:else}"☃" is less than or equal to "☄"{/gt}{~n}
!}
{@gt key=true_value value=false_value}true is greater than false{:else}true is less than or equal to false{/gt}{~n}
{@gt key=array_lower value=array_higher}[3,5,7] is greater than [8,9]{:else}[3,5,7] is less than or equal to [8,9]{/gt}{~n}
{@gt key=array_higher value=array_lower}[8,9] is greater than [3,5,7]{:else}[8,9] is less than or equal to [3,5,7]{/gt}{~n}
{! non-scalar and copied value tests !}
{@gt key=array_lower value=array_lower}array_lower is greater than array_lower{:else}array_lower is less than or equal to array_lower{/gt}{~n}
{@gt key=array_lower value=copy_array_lower}array_lower is greater than copy_array_lower{:else}array_lower is less than or equal to copy_array_lower{/gt}{~n}
{@gt key=some_obj value=some_obj}some_obj is greater than some_obj{:else}some_obj is less than or equal to some_obj{/gt}{~n}
{@gt key=some_obj value=copy_some_obj}some_obj is greater than copy_some_obj{:else}some_obj is less than or equal to copy_some_obj{/gt}{~n}
{@gt key=some_obj value=other_obj}some_obj is greater than other_obj{:else}some_obj is less than or equal to other_obj{/gt}{~n}
{@gt key=array_of_some_obj value=array_of_some_obj}array_of_some_obj is greater than array_of_some_obj{:else}array_of_some_obj is less than or equal to array_of_some_obj{/gt}{~n}
{@gt key=array_of_some_obj value=copy_array_of_some_obj}array_of_some_obj is greater than copy_array_of_some_obj{:else}array_of_some_obj is less than or equal to copy_array_of_some_obj{/gt}{~n}
{@gt key=array_of_some_obj value=array_of_other_obj}array_of_some_obj is greater than array_of_other_obj{:else}array_of_some_obj is less than or equal to array_of_other_obj{/gt}{~n}
{@gt key=array_higher value=array_higher_longer}array_higher is greater than array_higher_longer{:else}array_higher is less than or equal to array_higher_longer{/gt}{~n}
{@gt key=array_lower value=array_lower_with_array}array_lower is greater than array_lower_with_array{:else}array_lower is less than or equal to array_lower_with_array{/gt}{~n}
{@gt key=array_lower_with_array value=array_lower}array_lower_with_array is greater than array_lower{:else}array_lower_with_array is less than or equal to array_lower{/gt}{~n}

View File

@ -0,0 +1,72 @@
{
"str": "master",
"int": 7,
"alpha": 21,
"beta": "21",
"null": null,
"true_value": true,
"false_value": false,
"array_lower": [
3,
5,
7
],
"array_lower_with_array": [
3,
5,
[
7
]
],
"copy_array_lower": [
3,
5,
7
],
"array_higher": [
8,
9
],
"array_higher_longer": [
8,
9,
1
],
"some_obj": {
"name": "cat"
},
"copy_some_obj": {
"name": "cat"
},
"other_obj": {
"name": "dog"
},
"array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"copy_array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"array_of_other_obj": [
{
"name": "dog"
},
{
"name": "dog"
}
],
"array_of_strings": [
"cat",
"dog"
]
}

View File

@ -0,0 +1,41 @@
Testing helpers:{~n}
str is {str}{~n}
int is {int}{~n}
alpha is {alpha}{~n}
beta is {beta}{~n}
{@gte key=str value="master"}str is greater than or equal to "master"{:else}str is less than "master"{/gte}{~n}
{@gte key=str value="7"}str is greater than or equal to "7"{:else}str is less than "7"{/gte}{~n}
{@gte key=int value="7"}int is greater than or equal to "7"{:else}int is less than "7"{/gte}{~n}
{@gte key=int value=7}int is greater than or equal to 7{:else}int is less than 7{/gte}{~n}
{@gte key=int value=6}int is greater than or equal to 6{:else}int is less than 6{/gte}{~n}
{@gte key=alpha value=beta}alpha is greater than or equal to beta{:else}alpha is less than beta{/gte}{~n}
{@gte value=beta}missing key is true{:else}missing key is false{/gte}{~n}
{@gte value=gamma}missing key and non-existent value is true{:else}missing key and non-existent value is false{/gte}{~n}
{@gte key=alpha}missing value is true{:else}missing value is false{/gte}{~n}
{@gte key=gamma}missing value and non-existent key is true{:else}missing value and non-existent key is false{/gte}{~n}
{@gte key="master" value="master"}"master" is greater than or equal to "master"{:else}"master" is less than "master"{/gte}{~n}
{@gte key=null}null is greater than or equal to a missing value{:else}null is less than a missing value{/gte}{~n}
{@gte key=null value=gamma}null is greater than or equal to a non-existent value{:else}null is less than a non-existent value{/gte}{~n}
{@gte}no parameters is true{:else}no parameters is false{/gte}{~n}
{@gte key="a" value="]"}"a" is greater than or equal to "]"{:else}"a" is less than "]"{/gte}{~n}
{@gte key="a" value="A"}"a" is greater than or equal to "A"{:else}"a" is less than "A"{/gte}{~n}
{@gte key="a" value="}"}"a" is greater than or equal to "}"{:else}"a" is less than "}"{/gte}{~n}
{!
Commented out because unicode breaks nom
{@gte key="☃" value="☄"}"☃" is greater than or equal to "☄"{:else}"☃" is less than "☄"{/gte}{~n}
!}
{@gte key=true_value value=false_value}true is greater than or equal to false{:else}true is less than false{/gte}{~n}
{@gte key=array_lower value=array_higher}[3,5,7] is greater than or equal to [8,9]{:else}[3,5,7] is less than [8,9]{/gte}{~n}
{@gte key=array_higher value=array_lower}[8,9] is greater than or equal to [3,5,7]{:else}[8,9] is less than [3,5,7]{/gte}{~n}
{! non-scalar and copied value tests !}
{@gte key=array_lower value=array_lower}array_lower is greater than or equal to array_lower{:else}array_lower is less than array_lower{/gte}{~n}
{@gte key=array_lower value=copy_array_lower}array_lower is greater than or equal to copy_array_lower{:else}array_lower is less than copy_array_lower{/gte}{~n}
{@gte key=some_obj value=some_obj}some_obj is greater than or equal to some_obj{:else}some_obj is less than some_obj{/gte}{~n}
{@gte key=some_obj value=copy_some_obj}some_obj is greater than or equal to copy_some_obj{:else}some_obj is less than copy_some_obj{/gte}{~n}
{@gte key=some_obj value=other_obj}some_obj is greater than or equal to other_obj{:else}some_obj is less than other_obj{/gte}{~n}
{@gte key=array_of_some_obj value=array_of_some_obj}array_of_some_obj is greater than or equal to array_of_some_obj{:else}array_of_some_obj is less than array_of_some_obj{/gte}{~n}
{@gte key=array_of_some_obj value=copy_array_of_some_obj}array_of_some_obj is greater than or equal to copy_array_of_some_obj{:else}array_of_some_obj is less than copy_array_of_some_obj{/gte}{~n}
{@gte key=array_of_some_obj value=array_of_other_obj}array_of_some_obj is greater than or equal to array_of_other_obj{:else}array_of_some_obj is less than array_of_other_obj{/gte}{~n}
{@gte key=array_higher value=array_higher_longer}array_higher is greater than or equal to array_higher_longer{:else}array_higher is less than array_higher_longer{/gte}{~n}
{@gte key=array_lower value=array_lower_with_array}array_lower is greater than or equal to array_lower_with_array{:else}array_lower is less than array_lower_with_array{/gte}{~n}
{@gte key=array_lower_with_array value=array_lower}array_lower_with_array is greater than or equal to array_lower{:else}array_lower_with_array is less than array_lower{/gte}{~n}

View File

@ -0,0 +1,72 @@
{
"str": "master",
"int": 7,
"alpha": 21,
"beta": "21",
"null": null,
"true_value": true,
"false_value": false,
"array_lower": [
3,
5,
7
],
"array_lower_with_array": [
3,
5,
[
7
]
],
"copy_array_lower": [
3,
5,
7
],
"array_higher": [
8,
9
],
"array_higher_longer": [
8,
9,
1
],
"some_obj": {
"name": "cat"
},
"copy_some_obj": {
"name": "cat"
},
"other_obj": {
"name": "dog"
},
"array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"copy_array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"array_of_other_obj": [
{
"name": "dog"
},
{
"name": "dog"
}
],
"array_of_strings": [
"cat",
"dog"
]
}

View File

@ -0,0 +1,41 @@
Testing helpers:{~n}
str is {str}{~n}
int is {int}{~n}
alpha is {alpha}{~n}
beta is {beta}{~n}
{@lt key=str value="master"}str is less than "master"{:else}str is greater than or equal to "master"{/lt}{~n}
{@lt key=str value="7"}str is less than "7"{:else}str is greater than or equal to "7"{/lt}{~n}
{@lt key=int value="7"}int is less than "7"{:else}int is greater than or equal to "7"{/lt}{~n}
{@lt key=int value=7}int is less than 7{:else}int is greater than or equal to 7{/lt}{~n}
{@lt key=int value=6}int is less than 6{:else}int is greater than or equal to 6{/lt}{~n}
{@lt key=alpha value=beta}alpha is less than beta{:else}alpha is greater than or equal to beta{/lt}{~n}
{@lt value=beta}missing key is true{:else}missing key is false{/lt}{~n}
{@lt value=gamma}missing key and non-existent value is true{:else}missing key and non-existent value is false{/lt}{~n}
{@lt key=alpha}missing value is true{:else}missing value is false{/lt}{~n}
{@lt key=gamma}missing value and non-existent key is true{:else}missing value and non-existent key is false{/lt}{~n}
{@lt key="master" value="master"}"master" is less than "master"{:else}"master" is greater than or equal to "master"{/lt}{~n}
{@lt key=null}null is less than a missing value{:else}null is greater than or equal to a missing value{/lt}{~n}
{@lt key=null value=gamma}null is less than a non-existent value{:else}null is greater than or equal to a non-existent value{/lt}{~n}
{@lt}no parameters is true{:else}no parameters is false{/lt}{~n}
{@lt key="a" value="]"}"a" is less than "]"{:else}"a" is greater than or equal to "]"{/lt}{~n}
{@lt key="a" value="A"}"a" is less than "A"{:else}"a" is greater than or equal to "A"{/lt}{~n}
{@lt key="a" value="}"}"a" is less than "}"{:else}"a" is greater than or equal to "}"{/lt}{~n}
{!
Commented out because unicode breaks nom
{@lt key="☃" value="☄"}"☃" is less than "☄"{:else}"☃" is greater than or equal to "☄"{/lt}{~n}
!}
{@lt key=true_value value=false_value}true is less than false{:else}true is greater than or equal to false{/lt}{~n}
{@lt key=array_lower value=array_higher}[3,5,7] is less than [8,9]{:else}[3,5,7] is greater than or equal to [8,9]{/lt}{~n}
{@lt key=array_higher value=array_lower}[8,9] is less than [3,5,7]{:else}[8,9] is greater than or equal to [3,5,7]{/lt}{~n}
{! non-scalar and copied value tests !}
{@lt key=array_lower value=array_lower}array_lower is less than array_lower{:else}array_lower is greater than or equal to array_lower{/lt}{~n}
{@lt key=array_lower value=copy_array_lower}array_lower is less than copy_array_lower{:else}array_lower is greater than or equal to copy_array_lower{/lt}{~n}
{@lt key=some_obj value=some_obj}some_obj is less than some_obj{:else}some_obj is greater than or equal to some_obj{/lt}{~n}
{@lt key=some_obj value=copy_some_obj}some_obj is less than copy_some_obj{:else}some_obj is greater than or equal to copy_some_obj{/lt}{~n}
{@lt key=some_obj value=other_obj}some_obj is less than other_obj{:else}some_obj is greater than or equal to other_obj{/lt}{~n}
{@lt key=array_of_some_obj value=array_of_some_obj}array_of_some_obj is less than array_of_some_obj{:else}array_of_some_obj is greater than or equal to array_of_some_obj{/lt}{~n}
{@lt key=array_of_some_obj value=copy_array_of_some_obj}array_of_some_obj is less than copy_array_of_some_obj{:else}array_of_some_obj is greater than or equal to copy_array_of_some_obj{/lt}{~n}
{@lt key=array_of_some_obj value=array_of_other_obj}array_of_some_obj is less than array_of_other_obj{:else}array_of_some_obj is greater than or equal to array_of_other_obj{/lt}{~n}
{@lt key=array_higher value=array_higher_longer}array_higher is less than array_higher_longer{:else}array_higher is greater than or equal to array_higher_longer{/lt}{~n}
{@lt key=array_lower value=array_lower_with_array}array_lower is less than array_lower_with_array{:else}array_lower is greater than or equal to array_lower_with_array{/lt}{~n}
{@lt key=array_lower_with_array value=array_lower}array_lower_with_array is less than array_lower{:else}array_lower_with_array is greater than or equal to array_lower{/lt}{~n}

View File

@ -0,0 +1,72 @@
{
"str": "master",
"int": 7,
"alpha": 21,
"beta": "21",
"null": null,
"true_value": true,
"false_value": false,
"array_lower": [
3,
5,
7
],
"array_lower_with_array": [
3,
5,
[
7
]
],
"copy_array_lower": [
3,
5,
7
],
"array_higher": [
8,
9
],
"array_higher_longer": [
8,
9,
1
],
"some_obj": {
"name": "cat"
},
"copy_some_obj": {
"name": "cat"
},
"other_obj": {
"name": "dog"
},
"array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"copy_array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"array_of_other_obj": [
{
"name": "dog"
},
{
"name": "dog"
}
],
"array_of_strings": [
"cat",
"dog"
]
}

View File

@ -0,0 +1,41 @@
Testing helpers:{~n}
str is {str}{~n}
int is {int}{~n}
alpha is {alpha}{~n}
beta is {beta}{~n}
{@lte key=str value="master"}str is less than or equal to "master"{:else}str is greater than "master"{/lte}{~n}
{@lte key=str value="7"}str is less than or equal to "7"{:else}str is greater than "7"{/lte}{~n}
{@lte key=int value="7"}int is less than or equal to "7"{:else}int is greater than "7"{/lte}{~n}
{@lte key=int value=7}int is less than or equal to 7{:else}int is greater than 7{/lte}{~n}
{@lte key=int value=6}int is less than or equal to 6{:else}int is greater than 6{/lte}{~n}
{@lte key=alpha value=beta}alpha is less than or equal to beta{:else}alpha is greater than beta{/lte}{~n}
{@lte value=beta}missing key is true{:else}missing key is false{/lte}{~n}
{@lte value=gamma}missing key and non-existent value is true{:else}missing key and non-existent value is false{/lte}{~n}
{@lte key=alpha}missing value is true{:else}missing value is false{/lte}{~n}
{@lte key=gamma}missing value and non-existent key is true{:else}missing value and non-existent key is false{/lte}{~n}
{@lte key="master" value="master"}"master" is less than or equal to "master"{:else}"master" is greater than "master"{/lte}{~n}
{@lte key=null}null is less than or equal to a missing value{:else}null is greater than a missing value{/lte}{~n}
{@lte key=null value=gamma}null is less than or equal to a non-existent value{:else}null is greater than a non-existent value{/lte}{~n}
{@lte}no parameters is true{:else}no parameters is false{/lte}{~n}
{@lte key="a" value="]"}"a" is less than or equal to "]"{:else}"a" is greater than "]"{/lte}{~n}
{@lte key="a" value="A"}"a" is less than or equal to "A"{:else}"a" is greater than "A"{/lte}{~n}
{@lte key="a" value="}"}"a" is less than or equal to "}"{:else}"a" is greater than "}"{/lte}{~n}
{!
Commented out because unicode breaks nom
{@lte key="☃" value="☄"}"☃" is less than or equal to "☄"{:else}"☃" is greater than "☄"{/lte}{~n}
!}
{@lte key=true_value value=false_value}true is less than or equal to false{:else}true is greater than false{/lte}{~n}
{@lte key=array_lower value=array_higher}[3,5,7] is less than or equal to [8,9]{:else}[3,5,7] is greater than [8,9]{/lte}{~n}
{@lte key=array_higher value=array_lower}[8,9] is less than or equal to [3,5,7]{:else}[8,9] is greater than [3,5,7]{/lte}{~n}
{! non-scalar and copied value tests !}
{@lte key=array_lower value=array_lower}array_lower is less than or equal to array_lower{:else}array_lower is greater than array_lower{/lte}{~n}
{@lte key=array_lower value=copy_array_lower}array_lower is less than or equal to copy_array_lower{:else}array_lower is greater than copy_array_lower{/lte}{~n}
{@lte key=some_obj value=some_obj}some_obj is less than or equal to some_obj{:else}some_obj is greater than some_obj{/lte}{~n}
{@lte key=some_obj value=copy_some_obj}some_obj is less than or equal to copy_some_obj{:else}some_obj is greater than copy_some_obj{/lte}{~n}
{@lte key=some_obj value=other_obj}some_obj is less than or equal to other_obj{:else}some_obj is greater than other_obj{/lte}{~n}
{@lte key=array_of_some_obj value=array_of_some_obj}array_of_some_obj is less than or equal to array_of_some_obj{:else}array_of_some_obj is greater than array_of_some_obj{/lte}{~n}
{@lte key=array_of_some_obj value=copy_array_of_some_obj}array_of_some_obj is less than or equal to copy_array_of_some_obj{:else}array_of_some_obj is greater than copy_array_of_some_obj{/lte}{~n}
{@lte key=array_of_some_obj value=array_of_other_obj}array_of_some_obj is less than or equal to array_of_other_obj{:else}array_of_some_obj is greater than array_of_other_obj{/lte}{~n}
{@lte key=array_higher value=array_higher_longer}array_higher is less than or equal to array_higher_longer{:else}array_higher is greater than array_higher_longer{/lte}{~n}
{@lte key=array_lower value=array_lower_with_array}array_lower is less than or equal to array_lower_with_array{:else}array_lower is greater than array_lower_with_array{/lte}{~n}
{@lte key=array_lower_with_array value=array_lower}array_lower_with_array is less than or equal to array_lower{:else}array_lower_with_array is greater than array_lower{/lte}{~n}

View File

@ -0,0 +1,60 @@
{
"str": "master",
"int": 7,
"alpha": 21,
"beta": "21",
"null": null,
"true_value": true,
"false_value": false,
"array_lower": [
3,
5,
7
],
"copy_array_lower": [
3,
5,
7
],
"array_higher": [
8,
9
],
"some_obj": {
"name": "cat"
},
"copy_some_obj": {
"name": "cat"
},
"other_obj": {
"name": "dog"
},
"array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"copy_array_of_some_obj": [
{
"name": "cat"
},
{
"name": "cat"
}
],
"array_of_other_obj": [
{
"name": "dog"
},
{
"name": "dog"
}
],
"array_of_strings": [
"cat",
"dog"
]
}

View File

@ -0,0 +1,28 @@
Testing helpers:{~n}
str is {str}{~n}
int is {int}{~n}
alpha is {alpha}{~n}
beta is {beta}{~n}
{@ne key=str value="master"}str does not equal "master"{:else}str is equal to "master"{/ne}{~n}
{@ne key=str value="7"}str does not equal "7"{:else}str is equal to "7"{/ne}{~n}
{@ne key=int value="7"}int does not equal "7"{:else}int is equal to "7"{/ne}{~n}
{@ne key=int value=7}int does not equal 7{:else}int is equal to 7{/ne}{~n}
{@ne key=alpha value=beta}alpha does not equal beta{:else}alpha is equal to beta{/ne}{~n}
{@ne value=beta}missing key is true{:else}missing key is false{/ne}{~n}
{@ne value=gamma}missing key and non-existent value is true{:else}missing key and non-existent value is false{/ne}{~n}
{@ne key=alpha}missing value is true{:else}missing value is false{/ne}{~n}
{@ne key=gamma}missing value and non-existent key is true{:else}missing value and non-existent key is false{/ne}{~n}
{@ne key="master" value="master"}"master" does not equal "master"{:else}"master" is equal to "master"{/ne}{~n}
{@ne key=null}null does not equal a missing value{:else}null equals a missing value{/ne}{~n}
{@ne key=null value=gamma}null does not equal non-existent value{:else}null equals a non-existent value{/ne}{~n}
{@ne}no parameters is true{:else}no parameters is false{/ne}{~n}
{@ne key=array_lower value=array_higher}[3,5,7] does not equal [8,9]{:else}[3,5,7] is equal to [8,9]{/ne}{~n}
{! non-scalar and copied value tests !}
{@ne key=array_lower value=array_lower}array_lower does not equal array_lower{:else}array_lower is equals to array_lower{/ne}{~n}
{@ne key=array_lower value=copy_array_lower}array_lower does not equal copy_array_lower{:else}array_lower is equals to copy_array_lower{/ne}{~n}
{@ne key=some_obj value=some_obj}some_obj does not equal some_obj{:else}some_obj is equals to some_obj{/ne}{~n}
{@ne key=some_obj value=copy_some_obj}some_obj does not equal copy_some_obj{:else}some_obj is equals to copy_some_obj{/ne}{~n}
{@ne key=some_obj value=other_obj}some_obj does not equal other_obj{:else}some_obj is equals to other_obj{/ne}{~n}
{@ne key=array_of_some_obj value=array_of_some_obj}array_of_some_obj does not equal array_of_some_obj{:else}array_of_some_obj is equals to array_of_some_obj{/ne}{~n}
{@ne key=array_of_some_obj value=copy_array_of_some_obj}array_of_some_obj does not equal copy_array_of_some_obj{:else}array_of_some_obj is equals to copy_array_of_some_obj{/ne}{~n}
{@ne key=array_of_some_obj value=array_of_other_obj}array_of_some_obj does not equal array_of_other_obj{:else}array_of_some_obj is equals to array_of_other_obj{/ne}{~n}

View File

@ -1,5 +1,6 @@
extern crate nom;
use crate::renderer::CompareContextElement;
use parser::Filter;
use renderer::compile_template;
use renderer::CompiledTemplate;
@ -10,6 +11,7 @@ use renderer::RenderError;
use renderer::Renderable;
use renderer::WalkError;
use renderer::Walkable;
use std::cmp::Ordering;
use std::env;
use std::fs;
use std::io::{self, Read};
@ -137,3 +139,120 @@ impl Loopable for serde_json::Value {
}
}
}
impl CompareContextElement for serde_json::Value {
fn equals(&self, other: &dyn ContextElement) -> bool {
// Handle other serde_json::Value
match other.to_any().downcast_ref::<Self>() {
None => (),
Some(other_json_value) => match (self, other_json_value) {
// Non-scalar values not caught in the renderer by the
// identical-path shortcut are always not equal.
(serde_json::Value::Array(_), _)
| (_, serde_json::Value::Array(_))
| (serde_json::Value::Object(_), _)
| (_, serde_json::Value::Object(_)) => return false,
_ => return self == other_json_value,
},
}
// Handle string literals
match other.to_any().downcast_ref::<String>() {
None => (),
Some(other_string) => return self.as_str().map_or(false, |s| s == other_string),
}
// Handle numeric literals
match other.to_any().downcast_ref::<u64>() {
None => (),
Some(other_num) => return self.as_u64().map_or(false, |n| n == *other_num),
}
false
}
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
// Handle other serde_json::Value
match other.to_any().downcast_ref::<Self>() {
None => (),
Some(other_json_value) => {
return match (self, other_json_value) {
(
serde_json::Value::Bool(self_boolean),
serde_json::Value::Bool(other_boolean),
) => self_boolean.partial_cmp(other_boolean),
(
serde_json::Value::Number(self_number),
serde_json::Value::Number(other_number),
) => match (
self_number.as_f64(),
other_number.as_f64(),
self_number.as_u64(),
other_number.as_u64(),
self_number.as_i64(),
other_number.as_i64(),
) {
(_, _, _, _, Some(self_int), Some(other_int)) => {
self_int.partial_cmp(&other_int)
}
(_, _, Some(self_uint), Some(other_uint), _, _) => {
self_uint.partial_cmp(&other_uint)
}
(_, _, Some(_self_uint), _, _, Some(_other_int)) => {
// If the previous matches did not catch
// it, then other must be negative and
// self must be larger than can be
// represented with an i64, therefore self
// is greater than other.
Some(Ordering::Greater)
}
(_, _, _, Some(_other_uint), Some(_self_int), _) => {
// If the previous matches did not catch
// it, then self must be negative and
// other must be larger than can be
// represented with an i64, therefore
// other is greater than self.
Some(Ordering::Less)
}
(Some(self_float), Some(other_float), _, _, _, _) => {
self_float.partial_cmp(&other_float)
}
_ => panic!("This should be impossible since u64 and i64 can both be converted to floats"),
},
(
serde_json::Value::String(self_string),
serde_json::Value::String(other_string),
) => self_string.partial_cmp(other_string),
(serde_json::Value::Array(self_array), serde_json::Value::Array(other_array)) => convert_vec_to_context_element(self_array).partial_cmp(&convert_vec_to_context_element(other_array)),
_ => None,
};
}
}
// Handle string literals
match other.to_any().downcast_ref::<String>() {
None => (),
Some(other_string) => {
return self
.as_str()
.map_or(None, |s| s.partial_cmp(other_string.as_str()))
}
}
// Handle numeric literals
match other.to_any().downcast_ref::<u64>() {
None => (),
Some(other_num) => return self.as_u64().map_or(None, |n| n.partial_cmp(other_num)),
}
None
}
}
/// Create a new vec by of references to the serde_json::Values as
/// ContextElement trait objects so we can use its implementation of
/// PartialOrd.
///
/// You cannot implement a trait you do not define for a type you do
/// not define, so I cannot implement PartialOrd for
/// serde_json::value. Instead, I just re-use the PartialOrd
/// implementation for ContextElement which unfortunately has extra
/// overhead of downcasting. This would be a good spot for
/// optimization.
fn convert_vec_to_context_element(array: &Vec<serde_json::Value>) -> Vec<&dyn ContextElement> {
array.iter().map(|v| v as _).collect()
}

View File

@ -2,11 +2,11 @@ use nom::branch::alt;
use nom::bytes::complete::escaped_transform;
use nom::bytes::complete::is_a;
use nom::bytes::complete::is_not;
use nom::bytes::complete::{tag, take_until, take_until_parser_matches};
use nom::bytes::complete::{tag, take_until, take_until_parser_matches, take_while};
use nom::character::complete::line_ending;
use nom::character::complete::multispace0;
use nom::character::complete::one_of;
use nom::character::complete::{space0, space1};
use nom::character::complete::{digit1, space0, space1};
use nom::combinator::all_consuming;
use nom::combinator::map;
use nom::combinator::opt;
@ -38,8 +38,8 @@ pub enum DustTag<'a> {
DTHelperNotEquals(ParameterizedBlock<'a>),
DTHelperGreaterThan(ParameterizedBlock<'a>),
DTHelperLessThan(ParameterizedBlock<'a>),
DTHelperGreaterThenOrEquals(ParameterizedBlock<'a>),
DTHelperLessThenOrEquals(ParameterizedBlock<'a>),
DTHelperGreaterThanOrEquals(ParameterizedBlock<'a>),
DTHelperLessThanOrEquals(ParameterizedBlock<'a>),
}
#[derive(Clone, Debug, PartialEq)]
@ -106,10 +106,10 @@ pub struct NamedBlock<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct ParameterizedBlock<'a> {
name: &'a str,
params: Vec<KVPair<'a>>,
contents: Option<Body<'a>>,
else_contents: Option<Body<'a>>,
pub name: &'a str,
pub params: Vec<KVPair<'a>>,
pub contents: Option<Body<'a>>,
pub else_contents: Option<Body<'a>>,
}
#[derive(Clone, Debug, PartialEq)]
@ -122,6 +122,7 @@ pub struct Partial<'a> {
pub enum RValue<'a> {
RVPath(Path<'a>),
RVString(String),
RVPositiveInteger(u64),
}
#[derive(Clone, Debug, PartialEq)]
@ -161,8 +162,8 @@ fn dust_tag(i: &str) -> IResult<&str, DustTag> {
named_block("{+", DustTag::DTBlock),
named_block("{<", DustTag::DTInlinePartial),
partial("{>", DustTag::DTPartial),
parameterized_block("{@", "gte", DustTag::DTHelperGreaterThenOrEquals),
parameterized_block("{@", "lte", DustTag::DTHelperLessThenOrEquals),
parameterized_block("{@", "gte", DustTag::DTHelperGreaterThanOrEquals),
parameterized_block("{@", "lte", DustTag::DTHelperLessThanOrEquals),
parameterized_block("{@", "eq", DustTag::DTHelperEquals),
parameterized_block("{@", "ne", DustTag::DTHelperNotEquals),
parameterized_block("{@", "gt", DustTag::DTHelperGreaterThan),
@ -210,11 +211,23 @@ fn path(i: &str) -> IResult<&str, Path> {
))(i)
}
/// Just digits, no signs or decimals
fn postitive_integer_literal(i: &str) -> IResult<&str, u64> {
map(
verify(
map(digit1, |number_string: &str| number_string.parse::<u64>()),
|parse_result| parse_result.is_ok(),
),
|parsed_number| parsed_number.unwrap(),
)(i)
}
/// Either a literal or a path to a value
fn rvalue(i: &str) -> IResult<&str, RValue> {
alt((
map(path, RValue::RVPath),
map(quoted_string, RValue::RVString),
map(postitive_integer_literal, RValue::RVPositiveInteger),
))(i)
}
@ -929,6 +942,29 @@ mod tests {
);
}
#[test]
fn test_literals() {
assert_eq!(
dust_tag(r#"{>foo a="foo" b=179/}"#),
Ok((
"",
DustTag::DTPartial(Partial {
name: "foo".to_owned(),
params: vec![
KVPair {
key: "a",
value: RValue::RVString("foo".to_owned())
},
KVPair {
key: "b",
value: RValue::RVPositiveInteger(179)
}
]
})
))
);
}
#[test]
fn test_helper() {
assert_eq!(

View File

@ -1,9 +1,13 @@
use crate::parser::Filter;
use crate::renderer::errors::RenderError;
use crate::renderer::errors::WalkError;
use std::fmt::Debug;
use std::any::Any;
use std::{cmp::Ordering, fmt::Debug};
pub trait ContextElement: Debug + Walkable + Renderable + Loopable {}
pub trait ContextElement:
Debug + Walkable + Renderable + Loopable + CloneIntoBoxedContextElement + CompareContextElement
{
}
pub trait Walkable {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>;
@ -25,3 +29,41 @@ pub trait Loopable {
/// for each element of the array.
fn get_loop_elements(&self) -> Vec<&dyn ContextElement>;
}
pub trait CastToAny {
fn to_any(&self) -> &dyn Any;
}
pub trait CompareContextElement: CastToAny {
fn equals(&self, other: &dyn ContextElement) -> bool;
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering>;
}
pub trait CloneIntoBoxedContextElement {
fn clone_to_box(&self) -> Box<dyn ContextElement>;
}
impl<C: 'static + ContextElement + Clone> CloneIntoBoxedContextElement for C {
fn clone_to_box(&self) -> Box<dyn ContextElement> {
Box::new(self.clone())
}
}
impl<C: 'static + ContextElement> CastToAny for C {
fn to_any(&self) -> &dyn Any {
self
}
}
impl<'a, 'b> PartialEq<&'b dyn ContextElement> for &'a dyn ContextElement {
fn eq(&self, other: &&'b dyn ContextElement) -> bool {
self.equals(*other)
}
}
impl<'a, 'b> PartialOrd<&'b dyn ContextElement> for &'a dyn ContextElement {
fn partial_cmp(&self, other: &&'b dyn ContextElement) -> Option<Ordering> {
self.partial_compare(*other)
}
}

View File

@ -9,6 +9,7 @@ pub enum RenderError {
TemplateNotFound(String),
}
#[derive(PartialEq)]
pub enum WalkError {
CantWalk,
}

View File

@ -99,6 +99,65 @@ fn extract_inline_partials_from_tag<'a, 'b>(
blocks.insert(&named_block.name, &named_block.contents);
}
DustTag::DTBlock(..) => (),
_ => (), // TODO: Implement the rest
DustTag::DTHelperEquals(parameterized_block) => {
match &parameterized_block.contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
match &parameterized_block.else_contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
}
DustTag::DTHelperNotEquals(parameterized_block) => {
match &parameterized_block.contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
match &parameterized_block.else_contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
}
DustTag::DTHelperGreaterThan(parameterized_block) => {
match &parameterized_block.contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
match &parameterized_block.else_contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
}
DustTag::DTHelperLessThan(parameterized_block) => {
match &parameterized_block.contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
match &parameterized_block.else_contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
}
DustTag::DTHelperGreaterThanOrEquals(parameterized_block) => {
match &parameterized_block.contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
match &parameterized_block.else_contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
}
DustTag::DTHelperLessThanOrEquals(parameterized_block) => {
match &parameterized_block.contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
match &parameterized_block.else_contents {
None => (),
Some(body) => extract_inline_partials_from_body(blocks, &body),
};
}
}
}

View File

@ -7,6 +7,8 @@ mod parameters_context;
mod renderer;
mod walking;
pub use context_element::CloneIntoBoxedContextElement;
pub use context_element::CompareContextElement;
pub use context_element::ContextElement;
pub use context_element::Loopable;
pub use context_element::Renderable;

View File

@ -1,39 +1,75 @@
use crate::parser::KVPair;
use crate::parser::{Filter, RValue};
use crate::renderer::context_element::CompareContextElement;
use crate::renderer::context_element::ContextElement;
use crate::renderer::walking::walk_path;
use crate::renderer::walking::owned_walk_path;
use crate::renderer::Loopable;
use crate::renderer::RenderError;
use crate::renderer::Renderable;
use crate::renderer::WalkError;
use crate::renderer::Walkable;
use std::collections::HashMap;
use std::{cmp::Ordering, collections::HashMap};
#[derive(Clone, Debug)]
pub struct ParametersContext<'a> {
params: HashMap<&'a str, &'a RValue<'a>>,
breadcrumbs: &'a Vec<&'a dyn ContextElement>,
/// Copy the data from an RValue to an Owned struct
///
/// In order to get comparisons to work for our `ContextElement` trait
/// objects, we need to be able to use `std::any::Any`. Unfortunately,
/// `Any` requires that the structs do not have a lifetime (so they
/// will have a `'static` lifetime. This means that we cannot have a
/// `<'a>` appended to the struct type, so the struct cannot contain
/// any borrows. Rather than impose the copy cost in the parser, we
/// are imposing the cost of copying the data in the renderer because
/// the parser has no reason to not be able to reference data from the
/// input string.
#[derive(Clone, Debug, PartialEq)]
pub enum OwnedRValue {
RVPath(OwnedPath),
RVString(String),
RVPositiveInteger(u64),
}
impl<'a> ParametersContext<'a> {
pub fn new(
breadcrumbs: &'a Vec<&'a dyn ContextElement>,
params: &'a Vec<KVPair<'a>>,
) -> ParametersContext<'a> {
let param_map = params
#[derive(Clone, Debug, PartialEq)]
pub struct OwnedPath {
pub keys: Vec<String>,
}
impl From<&RValue<'_>> for OwnedRValue {
fn from(original: &RValue<'_>) -> Self {
match original {
RValue::RVString(text) => OwnedRValue::RVString(text.to_owned()),
RValue::RVPath(path) => OwnedRValue::RVPath(OwnedPath {
keys: path.keys.iter().map(|k| k.to_string()).collect(),
}),
RValue::RVPositiveInteger(num) => OwnedRValue::RVPositiveInteger(*num),
}
}
}
#[derive(Debug)]
pub struct ParametersContext {
params: HashMap<String, OwnedRValue>,
breadcrumbs: Vec<Box<dyn ContextElement>>,
}
impl ParametersContext {
pub fn new(breadcrumbs: &Vec<&dyn ContextElement>, params: &Vec<KVPair>) -> ParametersContext {
let owned_params: HashMap<String, OwnedRValue> = params
.iter()
.map(|pair: &KVPair<'a>| (pair.key, &pair.value))
.map(|kvpair| (kvpair.key.to_string(), OwnedRValue::from(&kvpair.value)))
.collect();
let owned_breadcrumbs: Vec<Box<dyn ContextElement>> =
breadcrumbs.iter().map(|ce| ce.clone_to_box()).collect();
ParametersContext {
params: param_map,
breadcrumbs: breadcrumbs,
params: owned_params,
breadcrumbs: owned_breadcrumbs,
}
}
}
impl<'a> ContextElement for ParametersContext<'a> {}
impl ContextElement for ParametersContext {}
impl<'a> Renderable for ParametersContext<'a> {
impl Renderable for ParametersContext {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
// TODO: Would this even ever be called? Won't matter, but I'd
// like to know. Since it is injected 1 above the current
@ -42,7 +78,7 @@ impl<'a> Renderable for ParametersContext<'a> {
}
}
impl<'a> Loopable for ParametersContext<'a> {
impl Loopable for ParametersContext {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
// TODO: Would this even ever be called? Won't matter, but I'd
// like to know. Since it is injected 1 above the current
@ -51,16 +87,48 @@ impl<'a> Loopable for ParametersContext<'a> {
}
}
impl<'a> Walkable for ParametersContext<'a> {
impl Walkable for ParametersContext {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
let rval = self.params.get(segment).ok_or(WalkError::CantWalk)?;
match rval {
RValue::RVPath(path) => walk_path(self.breadcrumbs, &path.keys),
RValue::RVString(text) => Ok(text),
OwnedRValue::RVPath(path) => owned_walk_path(&self.breadcrumbs, &path.keys),
OwnedRValue::RVString(text) => Ok(text),
OwnedRValue::RVPositiveInteger(num) => Ok(num),
}
}
}
impl Clone for ParametersContext {
fn clone(&self) -> Self {
let new_params: HashMap<String, OwnedRValue> = self
.params
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let new_breadcrumbs: Vec<Box<dyn ContextElement>> = self
.breadcrumbs
.iter()
.map(|bread| bread.clone_to_box())
.collect();
ParametersContext {
params: new_params,
breadcrumbs: new_breadcrumbs,
}
}
}
impl CompareContextElement for ParametersContext {
fn equals(&self, other: &dyn ContextElement) -> bool {
// TODO: Does this ever happen? perhaps I should have a panic here.
false
}
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
// TODO: Does this ever happen? perhaps I should have a panic here.
None
}
}
impl ContextElement for String {}
impl Renderable for String {
@ -84,3 +152,81 @@ impl Walkable for String {
Err(WalkError::CantWalk)
}
}
impl CompareContextElement for String {
fn equals(&self, other: &dyn ContextElement) -> bool {
// If its a String then compare them directly, otherwise defer
// to the other type's implementation of CompareContextElement
// since the end user could add any type.
match other.to_any().downcast_ref::<Self>() {
None => other.equals(self),
Some(other_string) => self == other_string,
}
}
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
// If its a string then compare them directly, otherwise defer
// to the other type's implementation of CompareContextElement
// since the end user could add any type.
match other.to_any().downcast_ref::<Self>() {
None => match other.partial_compare(self) {
None => None,
Some(ord) => match ord {
Ordering::Equal => Some(Ordering::Equal),
Ordering::Greater => Some(Ordering::Less),
Ordering::Less => Some(Ordering::Greater),
},
},
Some(other_string) => self.partial_cmp(other_string),
}
}
}
impl ContextElement for u64 {}
impl Renderable for u64 {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
Ok(self.to_string())
}
}
impl Loopable for u64 {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
vec![self]
}
}
impl Walkable for u64 {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
Err(WalkError::CantWalk)
}
}
impl CompareContextElement for u64 {
fn equals(&self, other: &dyn ContextElement) -> bool {
// If its a u64 then compare them directly, otherwise defer
// to the other type's implementation of CompareContextElement
// since the end user could add any type.
match other.to_any().downcast_ref::<Self>() {
None => other.equals(self),
Some(other_num) => self == other_num,
}
}
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
// If its a u64 then compare them directly, otherwise defer
// to the other type's implementation of CompareContextElement
// since the end user could add any type.
match other.to_any().downcast_ref::<Self>() {
None => match other.partial_compare(self) {
None => None,
Some(ord) => match ord {
Ordering::Equal => Some(Ordering::Equal),
Ordering::Greater => Some(Ordering::Less),
Ordering::Less => Some(Ordering::Greater),
},
},
Some(other_num) => self.partial_cmp(other_num),
}
}
}

View File

@ -1,6 +1,8 @@
use crate::parser::template;
use crate::parser::Body;
use crate::parser::DustTag;
use crate::parser::KVPair;
use crate::parser::RValue;
use crate::parser::Special;
use crate::parser::Template;
use crate::parser::TemplateElement;
@ -12,7 +14,7 @@ use crate::renderer::inline_partial_tree::extract_inline_partials;
use crate::renderer::inline_partial_tree::InlinePartialTreeElement;
use crate::renderer::parameters_context::ParametersContext;
use crate::renderer::walking::walk_path;
use std::collections::HashMap;
use std::{cmp::Ordering, collections::HashMap};
#[derive(Clone, Debug)]
pub struct CompiledTemplate<'a> {
@ -75,6 +77,18 @@ impl<'a> DustRenderer<'a> {
self.render_body(&main_template.contents, breadcrumbs, &new_blocks)
}
fn render_maybe_body(
&'a self,
body: &'a Option<Body>,
breadcrumbs: &Vec<&'a dyn ContextElement>,
blocks: &'a InlinePartialTreeElement<'a>,
) -> Result<String, RenderError> {
match body {
None => Ok("".to_owned()),
Some(body) => Ok(self.render_body(body, breadcrumbs, blocks)?),
}
}
fn render_body(
&'a self,
body: &'a Body,
@ -128,16 +142,13 @@ impl<'a> DustRenderer<'a> {
}
DustTag::DTSection(container) => {
let val = walk_path(breadcrumbs, &container.path.keys);
let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val);
let loop_elements: Vec<&dyn ContextElement> = Self::get_loop_elements(val);
if loop_elements.is_empty() {
// Oddly enough if the value is falsey (like
// an empty array or null), Dust uses the
// original context before walking the path as
// the context for rendering the else block
return match &container.else_contents {
Some(body) => self.render_body(&body, breadcrumbs, blocks),
None => Ok("".to_owned()),
};
return self.render_maybe_body(&container.else_contents, breadcrumbs, blocks);
} else {
match &container.contents {
None => return Ok("".to_owned()),
@ -158,34 +169,22 @@ impl<'a> DustRenderer<'a> {
}
DustTag::DTExists(container) => {
let val = walk_path(breadcrumbs, &container.path.keys);
let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val);
if loop_elements.is_empty() {
return match &container.else_contents {
Some(body) => self.render_body(&body, breadcrumbs, blocks),
None => Ok("".to_owned()),
};
let loop_elements: Vec<&dyn ContextElement> = Self::get_loop_elements(val);
return if loop_elements.is_empty() {
self.render_maybe_body(&container.else_contents, breadcrumbs, blocks)
} else {
return match &container.contents {
None => Ok("".to_owned()),
Some(body) => self.render_body(&body, breadcrumbs, blocks),
self.render_maybe_body(&container.contents, breadcrumbs, blocks)
};
}
}
DustTag::DTNotExists(container) => {
let val = walk_path(breadcrumbs, &container.path.keys);
let loop_elements: Vec<&dyn ContextElement> = self.get_loop_elements(val);
if !loop_elements.is_empty() {
return match &container.else_contents {
Some(body) => self.render_body(&body, breadcrumbs, blocks),
None => Ok("".to_owned()),
};
let loop_elements: Vec<&dyn ContextElement> = Self::get_loop_elements(val);
return if !loop_elements.is_empty() {
self.render_maybe_body(&container.else_contents, breadcrumbs, blocks)
} else {
return match &container.contents {
None => Ok("".to_owned()),
Some(body) => self.render_body(&body, breadcrumbs, blocks),
self.render_maybe_body(&container.contents, breadcrumbs, blocks)
};
}
}
DustTag::DTPartial(partial) => {
if partial.params.is_empty() {
let rendered_content =
@ -200,28 +199,238 @@ impl<'a> DustRenderer<'a> {
return Ok(rendered_content);
}
}
DustTag::DTInlinePartial(named_block) => {
DustTag::DTInlinePartial(_named_block) => {
// Inline partials are blank during rendering (they get injected into blocks)
return Ok("".to_owned());
}
DustTag::DTBlock(named_block) => {
match blocks.get_block(named_block.name) {
None => match &named_block.contents {
None => return Ok("".to_owned()),
Some(body) => {
let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
return Ok(rendered_content);
}
},
Some(interior) => match interior {
None => return Ok("".to_owned()),
Some(body) => {
let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
return Ok(rendered_content);
}
},
return match blocks.get_block(named_block.name) {
None => self.render_maybe_body(&named_block.contents, breadcrumbs, blocks),
Some(interior) => self.render_maybe_body(interior, breadcrumbs, blocks),
};
}
DustTag::DTHelperEquals(parameterized_block) => {
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
// Special case: when comparing two RVPaths, if the
// path is equal then dust assumes the values are
// equal (otherwise, non-scalar values are
// automatically not equal)
if Self::are_paths_identical(&param_map) {
return match &parameterized_block.contents {
None => Ok("".to_owned()),
Some(body) => {
let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
Ok(rendered_content)
}
};
}
let left_side: Result<&dyn ContextElement, WalkError> =
match Self::get_rval(breadcrumbs, &param_map, "key") {
None => return Ok("".to_owned()),
Some(res) => res,
};
let right_side: Result<&dyn ContextElement, WalkError> =
Self::get_rval(breadcrumbs, &param_map, "value")
.unwrap_or(Err(WalkError::CantWalk));
if left_side == right_side {
return self.render_maybe_body(
&parameterized_block.contents,
breadcrumbs,
blocks,
);
} else {
return self.render_maybe_body(
&parameterized_block.else_contents,
breadcrumbs,
blocks,
);
}
}
DustTag::DTHelperNotEquals(parameterized_block) => {
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
// Special case: when comparing two RVPaths, if the
// path is equal then dust assumes the values are
// equal (otherwise, non-scalar values are
// automatically not equal)
if Self::are_paths_identical(&param_map) {
return match &parameterized_block.else_contents {
None => Ok("".to_owned()),
Some(body) => {
let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
Ok(rendered_content)
}
};
}
let left_side: Result<&dyn ContextElement, WalkError> =
match Self::get_rval(breadcrumbs, &param_map, "key") {
None => return Ok("".to_owned()),
Some(res) => res,
};
let right_side: Result<&dyn ContextElement, WalkError> =
Self::get_rval(breadcrumbs, &param_map, "value")
.unwrap_or(Err(WalkError::CantWalk));
if left_side != right_side {
return self.render_maybe_body(
&parameterized_block.contents,
breadcrumbs,
blocks,
);
} else {
return self.render_maybe_body(
&parameterized_block.else_contents,
breadcrumbs,
blocks,
);
}
}
DustTag::DTHelperGreaterThan(parameterized_block) => {
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
let left_side: Result<&dyn ContextElement, WalkError> =
match Self::get_rval(breadcrumbs, &param_map, "key") {
None => return Ok("".to_owned()),
Some(res) => res,
};
let right_side: Result<&dyn ContextElement, WalkError> =
Self::get_rval(breadcrumbs, &param_map, "value")
.unwrap_or(Err(WalkError::CantWalk));
match (left_side, right_side) {
(Err(_), _) | (_, Err(_)) => {
return self.render_maybe_body(
&parameterized_block.else_contents,
breadcrumbs,
blocks,
)
}
(Ok(left_side_unwrapped), Ok(right_side_unwrapped)) => {
if left_side_unwrapped > right_side_unwrapped {
return self.render_maybe_body(
&parameterized_block.contents,
breadcrumbs,
blocks,
);
} else {
return self.render_maybe_body(
&parameterized_block.else_contents,
breadcrumbs,
blocks,
);
}
}
}
}
DustTag::DTHelperGreaterThanOrEquals(parameterized_block) => {
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
let left_side: Result<&dyn ContextElement, WalkError> =
match Self::get_rval(breadcrumbs, &param_map, "key") {
None => return Ok("".to_owned()),
Some(res) => res,
};
let right_side: Result<&dyn ContextElement, WalkError> =
Self::get_rval(breadcrumbs, &param_map, "value")
.unwrap_or(Err(WalkError::CantWalk));
match (left_side, right_side) {
(Err(_), _) | (_, Err(_)) => {
return self.render_maybe_body(
&parameterized_block.else_contents,
breadcrumbs,
blocks,
)
}
(Ok(left_side_unwrapped), Ok(right_side_unwrapped)) => {
return match left_side_unwrapped.partial_cmp(&right_side_unwrapped) {
Some(Ordering::Greater) | Some(Ordering::Equal) | None => self
.render_maybe_body(
&parameterized_block.contents,
breadcrumbs,
blocks,
),
Some(Ordering::Less) => self.render_maybe_body(
&parameterized_block.else_contents,
breadcrumbs,
blocks,
),
};
}
}
}
DustTag::DTHelperLessThan(parameterized_block) => {
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
let left_side: Result<&dyn ContextElement, WalkError> =
match Self::get_rval(breadcrumbs, &param_map, "key") {
None => return Ok("".to_owned()),
Some(res) => res,
};
let right_side: Result<&dyn ContextElement, WalkError> =
Self::get_rval(breadcrumbs, &param_map, "value")
.unwrap_or(Err(WalkError::CantWalk));
match (left_side, right_side) {
(Err(_), _) | (_, Err(_)) => {
return self.render_maybe_body(
&parameterized_block.else_contents,
breadcrumbs,
blocks,
)
}
(Ok(left_side_unwrapped), Ok(right_side_unwrapped)) => {
if left_side_unwrapped < right_side_unwrapped {
return self.render_maybe_body(
&parameterized_block.contents,
breadcrumbs,
blocks,
);
} else {
return self.render_maybe_body(
&parameterized_block.else_contents,
breadcrumbs,
blocks,
);
}
}
}
}
DustTag::DTHelperLessThanOrEquals(parameterized_block) => {
let param_map: HashMap<&str, &RValue<'a>> =
Self::get_rval_map(&parameterized_block.params);
let left_side: Result<&dyn ContextElement, WalkError> =
match Self::get_rval(breadcrumbs, &param_map, "key") {
None => return Ok("".to_owned()),
Some(res) => res,
};
let right_side: Result<&dyn ContextElement, WalkError> =
Self::get_rval(breadcrumbs, &param_map, "value")
.unwrap_or(Err(WalkError::CantWalk));
match (left_side, right_side) {
(Err(_), _) | (_, Err(_)) => {
return self.render_maybe_body(
&parameterized_block.else_contents,
breadcrumbs,
blocks,
)
}
(Ok(left_side_unwrapped), Ok(right_side_unwrapped)) => {
return match left_side_unwrapped.partial_cmp(&right_side_unwrapped) {
Some(Ordering::Less) | Some(Ordering::Equal) | None => self
.render_maybe_body(
&parameterized_block.contents,
breadcrumbs,
blocks,
),
Some(Ordering::Greater) => self.render_maybe_body(
&parameterized_block.else_contents,
breadcrumbs,
blocks,
),
};
}
}
}
_ => (), // TODO: Implement the rest
}
Ok("".to_owned())
@ -232,7 +441,6 @@ impl<'a> DustRenderer<'a> {
/// If the value is falsey, and therefore should render the else
/// block, this will return an empty vector.
fn get_loop_elements<'b>(
&'a self,
walk_result: Result<&'b dyn ContextElement, WalkError>,
) -> Vec<&'b dyn ContextElement> {
match walk_result {
@ -240,6 +448,41 @@ impl<'a> DustRenderer<'a> {
Ok(walk_target) => walk_target.get_loop_elements(),
}
}
fn are_paths_identical<'b>(param_map: &HashMap<&str, &RValue<'b>>) -> bool {
match (param_map.get("key"), param_map.get("value")) {
(None, _) => false,
(_, None) => false,
(Some(key_rval), Some(value_rval)) => match (key_rval, value_rval) {
(RValue::RVPath(key_path), RValue::RVPath(value_path)) => {
key_path.keys == value_path.keys
}
_ => false,
},
}
}
fn get_rval_map<'b>(params: &'b Vec<KVPair<'b>>) -> HashMap<&'b str, &'b RValue<'b>> {
params
.iter()
.map(|pair: &KVPair<'b>| (pair.key, &pair.value))
.collect()
}
fn get_rval<'b>(
breadcrumbs: &Vec<&'b dyn ContextElement>,
param_map: &HashMap<&str, &'b RValue<'b>>,
key: &str,
) -> Option<Result<&'b dyn ContextElement, WalkError>> {
match param_map.get(key) {
None => None,
Some(rval) => match rval {
RValue::RVString(text) => Some(Ok(text)),
RValue::RVPath(path) => Some(walk_path(breadcrumbs, &path.keys)),
RValue::RVPositiveInteger(num) => Some(Ok(num)),
},
}
}
}
#[cfg(test)]
@ -249,88 +492,76 @@ mod tests {
use crate::renderer::context_element::Loopable;
use crate::renderer::context_element::Renderable;
use crate::renderer::context_element::Walkable;
use crate::renderer::CompareContextElement;
impl ContextElement for u32 {}
impl ContextElement for &str {}
impl<I: ContextElement> ContextElement for HashMap<&str, I> {}
impl<I: 'static + ContextElement + Clone> ContextElement for HashMap<String, I> {}
impl Renderable for u32 {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
// TODO: handle the filters
Ok(self.to_string())
}
}
impl Renderable for &str {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
// TODO: handle the filters
Ok(self.to_string())
}
}
impl<I: ContextElement> Renderable for HashMap<&str, I> {
impl<I: ContextElement> Renderable for HashMap<String, I> {
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
// TODO: handle the filters
Ok("[object Object]".to_owned())
}
}
impl<I: ContextElement> Walkable for HashMap<&str, I> {
impl<I: ContextElement> Walkable for HashMap<String, I> {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
let child = self.get(segment).ok_or(WalkError::CantWalk)?;
Ok(child)
}
}
impl Walkable for &str {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
Err(WalkError::CantWalk)
}
}
impl Walkable for u32 {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
Err(WalkError::CantWalk)
}
}
impl Loopable for &str {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
if self.is_empty() {
Vec::new()
} else {
vec![self]
}
}
}
impl Loopable for u32 {
impl<I: 'static + ContextElement + Clone> Loopable for HashMap<String, I> {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
vec![self]
}
}
impl<I: ContextElement> Loopable for HashMap<&str, I> {
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
vec![self]
impl<I: 'static + ContextElement + Clone> CompareContextElement for HashMap<String, I> {
fn equals(&self, other: &dyn ContextElement) -> bool {
false
}
}
#[test]
fn test_walk_path() {
let context: HashMap<&str, &str> =
[("cat", "kitty"), ("dog", "doggy"), ("tiger", "murderkitty")]
let context: HashMap<String, String> = [
("cat".to_string(), "kitty".to_string()),
("dog".to_string(), "doggy".to_string()),
("tiger".to_string(), "murderkitty".to_string()),
]
.iter()
.cloned()
.collect();
let number_context: HashMap<&str, u32> = [("cat", 1), ("dog", 2), ("tiger", 3)]
let number_context: HashMap<String, u64> = [
("cat".to_string(), 1),
("dog".to_string(), 2),
("tiger".to_string(), 3),
]
.iter()
.cloned()
.collect();
let deep_context: HashMap<&str, HashMap<&str, &str>> = [
("cat", [("food", "meat")].iter().cloned().collect()),
("dog", [("food", "meat")].iter().cloned().collect()),
("tiger", [("food", "people")].iter().cloned().collect()),
let deep_context: HashMap<String, HashMap<String, String>> = [
(
"cat".to_string(),
[("food".to_string(), "meat".to_string())]
.iter()
.cloned()
.collect(),
),
(
"dog".to_string(),
[("food".to_string(), "meat".to_string())]
.iter()
.cloned()
.collect(),
),
(
"tiger".to_string(),
[("food".to_string(), "people".to_string())]
.iter()
.cloned()
.collect(),
),
]
.iter()
.cloned()

View File

@ -51,3 +51,23 @@ pub fn walk_path<'a>(
}
Err(WalkError::CantWalk)
}
pub fn owned_walk_path<'a>(
breadcrumbs: &'a Vec<Box<dyn ContextElement>>,
path: &Vec<String>,
) -> Result<&'a dyn ContextElement, WalkError> {
let path_reference: Vec<&str> = path.iter().map(|p| &p[..]).collect();
for context in breadcrumbs.iter().rev() {
match walk_path_from_single_level(context.as_ref(), &path_reference) {
// If no walking was done at all, keep looping
WalkResult::NoWalk => {}
// If we partially walked then stop trying to find
// anything
WalkResult::PartialWalk => {
return Err(WalkError::CantWalk);
}
WalkResult::FullyWalked(new_context) => return Ok(new_context),
}
}
Err(WalkError::CantWalk)
}