Merge branch 'dust_helpers' into render
This commit is contained in:
commit
974ce96f14
@ -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"]
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
15
js/test_cases/helpers_eq/README.md
Normal file
15
js/test_cases/helpers_eq/README.md
Normal 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.
|
60
js/test_cases/helpers_eq/input1.json
Normal file
60
js/test_cases/helpers_eq/input1.json
Normal 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"
|
||||
]
|
||||
}
|
28
js/test_cases/helpers_eq/main.dust
Normal file
28
js/test_cases/helpers_eq/main.dust
Normal 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}
|
37
js/test_cases/helpers_gt/README.md
Normal file
37
js/test_cases/helpers_gt/README.md
Normal 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
|
72
js/test_cases/helpers_gt/input1.json
Normal file
72
js/test_cases/helpers_gt/input1.json
Normal 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"
|
||||
]
|
||||
}
|
42
js/test_cases/helpers_gt/main.dust
Normal file
42
js/test_cases/helpers_gt/main.dust
Normal 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}
|
72
js/test_cases/helpers_gte/input1.json
Normal file
72
js/test_cases/helpers_gte/input1.json
Normal 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"
|
||||
]
|
||||
}
|
41
js/test_cases/helpers_gte/main.dust
Normal file
41
js/test_cases/helpers_gte/main.dust
Normal 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}
|
72
js/test_cases/helpers_lt/input1.json
Normal file
72
js/test_cases/helpers_lt/input1.json
Normal 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"
|
||||
]
|
||||
}
|
41
js/test_cases/helpers_lt/main.dust
Normal file
41
js/test_cases/helpers_lt/main.dust
Normal 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}
|
72
js/test_cases/helpers_lte/input1.json
Normal file
72
js/test_cases/helpers_lte/input1.json
Normal 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"
|
||||
]
|
||||
}
|
41
js/test_cases/helpers_lte/main.dust
Normal file
41
js/test_cases/helpers_lte/main.dust
Normal 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}
|
60
js/test_cases/helpers_ne/input1.json
Normal file
60
js/test_cases/helpers_ne/input1.json
Normal 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"
|
||||
]
|
||||
}
|
28
js/test_cases/helpers_ne/main.dust
Normal file
28
js/test_cases/helpers_ne/main.dust
Normal 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}
|
119
src/bin.rs
119
src/bin.rs
@ -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()
|
||||
}
|
||||
|
@ -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!(
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ pub enum RenderError {
|
||||
TemplateNotFound(String),
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum WalkError {
|
||||
CantWalk,
|
||||
}
|
||||
|
@ -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 ¶meterized_block.contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
match ¶meterized_block.else_contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
}
|
||||
DustTag::DTHelperNotEquals(parameterized_block) => {
|
||||
match ¶meterized_block.contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
match ¶meterized_block.else_contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
}
|
||||
DustTag::DTHelperGreaterThan(parameterized_block) => {
|
||||
match ¶meterized_block.contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
match ¶meterized_block.else_contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
}
|
||||
DustTag::DTHelperLessThan(parameterized_block) => {
|
||||
match ¶meterized_block.contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
match ¶meterized_block.else_contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
}
|
||||
DustTag::DTHelperGreaterThanOrEquals(parameterized_block) => {
|
||||
match ¶meterized_block.contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
match ¶meterized_block.else_contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
}
|
||||
DustTag::DTHelperLessThanOrEquals(parameterized_block) => {
|
||||
match ¶meterized_block.contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
match ¶meterized_block.else_contents {
|
||||
None => (),
|
||||
Some(body) => extract_inline_partials_from_body(blocks, &body),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(¶meterized_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(¶m_map) {
|
||||
return match ¶meterized_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, ¶m_map, "key") {
|
||||
None => return Ok("".to_owned()),
|
||||
Some(res) => res,
|
||||
};
|
||||
let right_side: Result<&dyn ContextElement, WalkError> =
|
||||
Self::get_rval(breadcrumbs, ¶m_map, "value")
|
||||
.unwrap_or(Err(WalkError::CantWalk));
|
||||
if left_side == right_side {
|
||||
return self.render_maybe_body(
|
||||
¶meterized_block.contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
);
|
||||
} else {
|
||||
return self.render_maybe_body(
|
||||
¶meterized_block.else_contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
);
|
||||
}
|
||||
}
|
||||
DustTag::DTHelperNotEquals(parameterized_block) => {
|
||||
let param_map: HashMap<&str, &RValue<'a>> =
|
||||
Self::get_rval_map(¶meterized_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(¶m_map) {
|
||||
return match ¶meterized_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, ¶m_map, "key") {
|
||||
None => return Ok("".to_owned()),
|
||||
Some(res) => res,
|
||||
};
|
||||
let right_side: Result<&dyn ContextElement, WalkError> =
|
||||
Self::get_rval(breadcrumbs, ¶m_map, "value")
|
||||
.unwrap_or(Err(WalkError::CantWalk));
|
||||
if left_side != right_side {
|
||||
return self.render_maybe_body(
|
||||
¶meterized_block.contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
);
|
||||
} else {
|
||||
return self.render_maybe_body(
|
||||
¶meterized_block.else_contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
);
|
||||
}
|
||||
}
|
||||
DustTag::DTHelperGreaterThan(parameterized_block) => {
|
||||
let param_map: HashMap<&str, &RValue<'a>> =
|
||||
Self::get_rval_map(¶meterized_block.params);
|
||||
let left_side: Result<&dyn ContextElement, WalkError> =
|
||||
match Self::get_rval(breadcrumbs, ¶m_map, "key") {
|
||||
None => return Ok("".to_owned()),
|
||||
Some(res) => res,
|
||||
};
|
||||
let right_side: Result<&dyn ContextElement, WalkError> =
|
||||
Self::get_rval(breadcrumbs, ¶m_map, "value")
|
||||
.unwrap_or(Err(WalkError::CantWalk));
|
||||
match (left_side, right_side) {
|
||||
(Err(_), _) | (_, Err(_)) => {
|
||||
return self.render_maybe_body(
|
||||
¶meterized_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(
|
||||
¶meterized_block.contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
);
|
||||
} else {
|
||||
return self.render_maybe_body(
|
||||
¶meterized_block.else_contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DustTag::DTHelperGreaterThanOrEquals(parameterized_block) => {
|
||||
let param_map: HashMap<&str, &RValue<'a>> =
|
||||
Self::get_rval_map(¶meterized_block.params);
|
||||
let left_side: Result<&dyn ContextElement, WalkError> =
|
||||
match Self::get_rval(breadcrumbs, ¶m_map, "key") {
|
||||
None => return Ok("".to_owned()),
|
||||
Some(res) => res,
|
||||
};
|
||||
let right_side: Result<&dyn ContextElement, WalkError> =
|
||||
Self::get_rval(breadcrumbs, ¶m_map, "value")
|
||||
.unwrap_or(Err(WalkError::CantWalk));
|
||||
match (left_side, right_side) {
|
||||
(Err(_), _) | (_, Err(_)) => {
|
||||
return self.render_maybe_body(
|
||||
¶meterized_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(
|
||||
¶meterized_block.contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
),
|
||||
Some(Ordering::Less) => self.render_maybe_body(
|
||||
¶meterized_block.else_contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
DustTag::DTHelperLessThan(parameterized_block) => {
|
||||
let param_map: HashMap<&str, &RValue<'a>> =
|
||||
Self::get_rval_map(¶meterized_block.params);
|
||||
let left_side: Result<&dyn ContextElement, WalkError> =
|
||||
match Self::get_rval(breadcrumbs, ¶m_map, "key") {
|
||||
None => return Ok("".to_owned()),
|
||||
Some(res) => res,
|
||||
};
|
||||
let right_side: Result<&dyn ContextElement, WalkError> =
|
||||
Self::get_rval(breadcrumbs, ¶m_map, "value")
|
||||
.unwrap_or(Err(WalkError::CantWalk));
|
||||
match (left_side, right_side) {
|
||||
(Err(_), _) | (_, Err(_)) => {
|
||||
return self.render_maybe_body(
|
||||
¶meterized_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(
|
||||
¶meterized_block.contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
);
|
||||
} else {
|
||||
return self.render_maybe_body(
|
||||
¶meterized_block.else_contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DustTag::DTHelperLessThanOrEquals(parameterized_block) => {
|
||||
let param_map: HashMap<&str, &RValue<'a>> =
|
||||
Self::get_rval_map(¶meterized_block.params);
|
||||
let left_side: Result<&dyn ContextElement, WalkError> =
|
||||
match Self::get_rval(breadcrumbs, ¶m_map, "key") {
|
||||
None => return Ok("".to_owned()),
|
||||
Some(res) => res,
|
||||
};
|
||||
let right_side: Result<&dyn ContextElement, WalkError> =
|
||||
Self::get_rval(breadcrumbs, ¶m_map, "value")
|
||||
.unwrap_or(Err(WalkError::CantWalk));
|
||||
match (left_side, right_side) {
|
||||
(Err(_), _) | (_, Err(_)) => {
|
||||
return self.render_maybe_body(
|
||||
¶meterized_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(
|
||||
¶meterized_block.contents,
|
||||
breadcrumbs,
|
||||
blocks,
|
||||
),
|
||||
Some(Ordering::Greater) => self.render_maybe_body(
|
||||
¶meterized_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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user