summaryrefslogtreecommitdiffstats
path: root/vendor/handlebars
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /vendor/handlebars
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/handlebars')
-rw-r--r--vendor/handlebars/.cargo-checksum.json1
-rw-r--r--vendor/handlebars/CHANGELOG.md442
-rw-r--r--vendor/handlebars/Cargo.lock2175
-rw-r--r--vendor/handlebars/Cargo.toml98
-rw-r--r--vendor/handlebars/LICENSE22
-rw-r--r--vendor/handlebars/README.md196
-rw-r--r--vendor/handlebars/benches/bench.rs236
-rwxr-xr-xvendor/handlebars/build-wasm.sh4
-rw-r--r--vendor/handlebars/examples/decorator.rs195
-rw-r--r--vendor/handlebars/examples/decorator/template.hbs21
-rw-r--r--vendor/handlebars/examples/dev_mode.rs27
-rw-r--r--vendor/handlebars/examples/dev_mode/template.hbs8
-rw-r--r--vendor/handlebars/examples/error.rs40
-rw-r--r--vendor/handlebars/examples/error/error.hbs21
-rw-r--r--vendor/handlebars/examples/error/template.hbs19
-rw-r--r--vendor/handlebars/examples/partials.rs35
-rw-r--r--vendor/handlebars/examples/partials/base0.hbs7
-rw-r--r--vendor/handlebars/examples/partials/base1.hbs7
-rw-r--r--vendor/handlebars/examples/partials/template2.hbs4
-rw-r--r--vendor/handlebars/examples/quick.rs18
-rw-r--r--vendor/handlebars/examples/render.rs135
-rw-r--r--vendor/handlebars/examples/render/template.hbs18
-rw-r--r--vendor/handlebars/examples/render_cli/simple.hbs1
-rw-r--r--vendor/handlebars/examples/render_file.rs140
-rw-r--r--vendor/handlebars/examples/render_file/template.hbs19
-rw-r--r--vendor/handlebars/examples/script.rs39
-rw-r--r--vendor/handlebars/examples/script/goals.rhai3
-rw-r--r--vendor/handlebars/examples/script/template.hbs10
-rwxr-xr-xvendor/handlebars/profile.sh3
-rw-r--r--vendor/handlebars/release.toml6
-rw-r--r--vendor/handlebars/rustfmt.toml1
-rw-r--r--vendor/handlebars/src/block.rs126
-rw-r--r--vendor/handlebars/src/cli.rs51
-rw-r--r--vendor/handlebars/src/context.rs453
-rw-r--r--vendor/handlebars/src/decorators/inline.rs64
-rw-r--r--vendor/handlebars/src/decorators/mod.rs300
-rw-r--r--vendor/handlebars/src/error.rs272
-rw-r--r--vendor/handlebars/src/grammar.pest127
-rw-r--r--vendor/handlebars/src/grammar.rs372
-rw-r--r--vendor/handlebars/src/helpers/block_util.rs17
-rw-r--r--vendor/handlebars/src/helpers/helper_each.rs593
-rw-r--r--vendor/handlebars/src/helpers/helper_extras.rs112
-rw-r--r--vendor/handlebars/src/helpers/helper_if.rs151
-rw-r--r--vendor/handlebars/src/helpers/helper_log.rs72
-rw-r--r--vendor/handlebars/src/helpers/helper_lookup.rs104
-rw-r--r--vendor/handlebars/src/helpers/helper_raw.rs44
-rw-r--r--vendor/handlebars/src/helpers/helper_with.rs276
-rw-r--r--vendor/handlebars/src/helpers/mod.rs291
-rw-r--r--vendor/handlebars/src/helpers/scripting.rs111
-rw-r--r--vendor/handlebars/src/json/mod.rs2
-rw-r--r--vendor/handlebars/src/json/path.rs138
-rw-r--r--vendor/handlebars/src/json/value.rs202
-rw-r--r--vendor/handlebars/src/lib.rs417
-rw-r--r--vendor/handlebars/src/local_vars.rs37
-rw-r--r--vendor/handlebars/src/macros.rs185
-rw-r--r--vendor/handlebars/src/output.rs48
-rw-r--r--vendor/handlebars/src/partial.rs333
-rw-r--r--vendor/handlebars/src/registry.rs1092
-rw-r--r--vendor/handlebars/src/render.rs1119
-rw-r--r--vendor/handlebars/src/sources.rs34
-rw-r--r--vendor/handlebars/src/support.rs76
-rw-r--r--vendor/handlebars/src/template.rs1254
-rw-r--r--vendor/handlebars/src/util.rs25
-rw-r--r--vendor/handlebars/tests/block_context.rs106
-rw-r--r--vendor/handlebars/tests/data_helper.rs29
-rw-r--r--vendor/handlebars/tests/escape.rs43
-rw-r--r--vendor/handlebars/tests/helper_function_lifetime.rs36
-rw-r--r--vendor/handlebars/tests/helper_macro.rs76
-rw-r--r--vendor/handlebars/tests/helper_with_space.rs36
-rw-r--r--vendor/handlebars/tests/root_var.rs21
-rw-r--r--vendor/handlebars/tests/subexpression.rs169
-rw-r--r--vendor/handlebars/tests/template_names.rs35
-rw-r--r--vendor/handlebars/wasm/LICENSE22
-rw-r--r--vendor/handlebars/wasm/README.md6
-rw-r--r--vendor/handlebars/wasm/wapm.toml18
75 files changed, 13046 insertions, 0 deletions
diff --git a/vendor/handlebars/.cargo-checksum.json b/vendor/handlebars/.cargo-checksum.json
new file mode 100644
index 000000000..d41808a41
--- /dev/null
+++ b/vendor/handlebars/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"ab776bc63c83489640577a611f7df181de9a3191bf6e5fb449012fd5f6b5e770","Cargo.lock":"fb81e47543e26596ba74d5c165e26f70ec1532911dd638dc6bee65e572b09182","Cargo.toml":"5c6beb2539b6d5e038a5beeead3ed4c044b9be7fdcd4fab60b5a16ce31121bdf","LICENSE":"63b308fad3db82dc12067a8d7ff4b672fae97d12d0b4b3bb59179e27d640b3a4","README.md":"e9dfd89edf514c3613b5b2f47c05f2ba48bf1c9afee8eca1ba395887c8a85c9d","benches/bench.rs":"276ac8c581db35dbdc00e9e4cc1e2e1940fe95522a8f5a19ab088a2c68da61ed","build-wasm.sh":"9f7226d7d0768fe0fba97f698642a4c13a600468ba7d57abcfad9285efd9f4ce","examples/decorator.rs":"010762d337c64a8ade120c66ccb97ac176653d2fc1265c6f77c35d2a7693176f","examples/decorator/template.hbs":"76fd24f08b28b469529f4727321c7cc940198abb5c1aa24d7a6dd2ab42bdda66","examples/dev_mode.rs":"8e28b0f354e14006481faba76f32401a18476472b2e4c9faeea54f222980a9ba","examples/dev_mode/template.hbs":"4a5e1b81565572e26986d890e4a431953b81893a99ce07cef303e366dd84243c","examples/error.rs":"85219ee57a7bc739185862d04c7eff203743ddf730b940f92492894e3ebb5746","examples/error/error.hbs":"4e8fac806d444a5bc41436fe0a51b4d7850c7645f8996db7a99a821c88622eef","examples/error/template.hbs":"48313b522ddb0dd4285874b96d1b705b841dc141514f440790e904a262955951","examples/partials.rs":"e102f0bdacb80a08ac9ae3c92c415991095a1a2142d55420457706823a67fba0","examples/partials/base0.hbs":"6674275ef68faaeab8a4128da0983c9d732d7552f1791c2e3563fa8c5c66fb5a","examples/partials/base1.hbs":"97b1089705f5a21a6f344190e26133d920d56d4a7fdc1fa2fb7359a720bfe182","examples/partials/template2.hbs":"6f83bc48b774a3b83f9ce5a54bf014fb03514720f9e32834dc68000d06a17618","examples/quick.rs":"96df03fc1742504afae34c6da9da05206adc41f771406da2825b41725329cb2d","examples/render.rs":"eadc1aff65d96c7f95c948fbccfb79981276075f83e19a22214fe0772ef17496","examples/render/template.hbs":"2512ef3362379633790985fe2c40e7efab3ad0f431b7cbec5b7cc644dbbef8f5","examples/render_cli/simple.hbs":"a2e0df51e7c1a25c4e9e1bbe0f4911c7c86200d358fc6c9195b61e89adddc970","examples/render_file.rs":"f5b8e788ac6a71a170c5b315120e8779896df8d9b290fb3d785f51307f5d3c45","examples/render_file/template.hbs":"8ec8d0b61a996391ad7f8b6a914f84d0ac06c62e6d7c9c5fa054ca5e2c64a52e","examples/script.rs":"df869a15d6cd8c478f74582018c54e20545b9451e84cdec2f8bb71c31a618da0","examples/script/goals.rhai":"d756fd7d39f11fae148a6e11d23160da0cdac91c29f716dcb854c07a7db09c80","examples/script/template.hbs":"1f39785157a112bc5c4e94db1361c45a73b024bc8bd0fea0a9d5da8102afb310","profile.sh":"b8e80c59bc12fca93daa67c09001c1c045e594e532831cc957c9ac313f8b2390","release.toml":"968ff7e0a0f8b99d0b91a06cb007fcee55427ccbd05012b46202829336287151","rustfmt.toml":"8e99dabd6aadb112f9f9ed8709e7388b57bf43d19cd08342c093c870534a3f97","src/block.rs":"da38a572a95c3858685e4af82a7e1920c6b3311acf8c118c53c8531b091de391","src/cli.rs":"63aed9a8fb2135ee03bd2b49330be20d2018fe3c36265c14dbdc0da9b73cb6d0","src/context.rs":"5007324a0655daa994e1b64f0eed160e67e4a4d8fb02d21eca4c549d5dcea7c2","src/decorators/inline.rs":"73b8ba6ed98dbf8b3bca228796f47c9139d49f1a1c8d5b4603cc6354098b56c2","src/decorators/mod.rs":"322d4d692331a1e3a2806780cbe1e7a3a82b343486f1182d2e8a017a8bf11085","src/error.rs":"7da2635fdb0005437d8c4a7cb889336d0039665d7627544f600d473f5b90eab8","src/grammar.pest":"aed7ff83953551d48e4e94edc25d51f7e418ec79b62df1ca8f7e728444430d0b","src/grammar.rs":"848d1b56bc1d887e8c42836c37d41b2a634ec6eb9e3880be7dde31d84a38460d","src/helpers/block_util.rs":"11a295215f9c0d14b89f97161468425405386214f050bad07683669a162f9aa1","src/helpers/helper_each.rs":"75b536114f954287936e9f02a54b0c182144e2e6a4ad0970e66cc644cc8f3a0f","src/helpers/helper_extras.rs":"0575a1518eba51cafe91ec2b09666e1c987c9a9bc98cb00193d1e0d203a8b681","src/helpers/helper_if.rs":"37c81b7000948f601398b7163b37397020d191642021077bd7eb15c181632d32","src/helpers/helper_log.rs":"272f7c1f873b97974cce156c04281f2bd8c44ccc885b4c59dd2b43e8d7cc6ef6","src/helpers/helper_lookup.rs":"a9f000e36d0e9339930b3d94dabd4f123cdbdaa85e8d77d15e4a772a99ffe9d4","src/helpers/helper_raw.rs":"6b0b5b3774a1d5c3ebf30b1e0b8cac825f6ffb8a18cb9e08bd40786258e4b597","src/helpers/helper_with.rs":"9964ec02b56ad8fb6f3aa9abb535c0d3f6f2cf3eaeba59a5afd8687ea6df2209","src/helpers/mod.rs":"31eefbd1f302b852e43a3aa8432ce4faad04db37eba7fc1bfa8cf9c25967b899","src/helpers/scripting.rs":"6b9e7e08bdbf3e2716bb82b69e0f10efd4de1174842742215aba18fb2cc507e2","src/json/mod.rs":"ef2fc8fe98e9761e2e2a4b2d2293bb29cdb689db2f44f939fc61c48c6b52f0a7","src/json/path.rs":"818bc5f002839a529ffe5d5f8eddd815ac3808a5f9ce3bac79f1f71f5702599b","src/json/value.rs":"390ea4fa8b5131a3d00ff1c80d06359220f16d851a5e836192026ea32acbb02e","src/lib.rs":"ecbb4099b2c0cc77e746083181a8ed32b7f76671aa8e54d079eb4196b24d167c","src/local_vars.rs":"91d3a16bbfabe2eccecaa9a0dd866ebe88d70ce438d2f18581d1b7d231ba0644","src/macros.rs":"86eed922319644176e8464023455b78440b8e5d0d9b13a0ce61b2edf0f10984e","src/output.rs":"10c28874f78cddba70b24eb984a849c7750b207ac605f2aa46f2abe02846b3a2","src/partial.rs":"ddcd49436b6c653b3e5db44a997c5cd9006755dd1e6c07aa5996fa495aecd382","src/registry.rs":"56d613a72ca9e4ad15953cf5b74bf6df912e81f8f008780bebe93375fb1569ed","src/render.rs":"cec805cd1396bc87f1c2bcd9d45b8c6236a65cb99c6d45f14199fab99b94d38d","src/sources.rs":"46989eb76f36edff9dcb5d27488e8bd9c7ff1749f368a70e855f4808c58d7bcf","src/support.rs":"b3d1d99440182548ff9bfcffbd9a7e08507f740ed68382f9c74ab0a92a79eab7","src/template.rs":"61625e52f43c9df92fce25f626db6c61b31a42310c06a8158b13b9f960339e12","src/util.rs":"2ebc845de5ca47455442a1b6bd664565a52e29928e82c526fbfe56ed5196410f","tests/block_context.rs":"4dce11312d2b6c0c5c2f3104516e5567e5a370611e08f4055fa3653293580385","tests/data_helper.rs":"a7f9d1d42d5415ba1dba459840067acc1294f6f6057d9a5a13d9f7e19469e850","tests/escape.rs":"a4bd25c4268a306dc09dc17806f86895c4932753a585219902f0dd85c7e65cba","tests/helper_function_lifetime.rs":"635cbd0b44742539bdf36e803a19c6a1a698399f8b765415d53f28df1acfbc2b","tests/helper_macro.rs":"4660cfb782c03e70f536bedbb2ed5afed5e8fe841d77c1812e4b2ef55d81a6a6","tests/helper_with_space.rs":"c64952ea031f964e558cb323df1bf731bb7c0bf7b226142cea61afa2fd4efbfd","tests/root_var.rs":"fda5b30bac1e692fb010f9f98e052d5a525420fece9dcc3821a4e5aeee3a8841","tests/subexpression.rs":"b36ab31adf45272ca984c0c5ed9497820ca072a8805444d69bd089b766786bb5","tests/template_names.rs":"4bbe6b48c974ae7ea1e61806794d846e2f0e8505e1568c5305fa2364dceef68a","wasm/LICENSE":"63b308fad3db82dc12067a8d7ff4b672fae97d12d0b4b3bb59179e27d640b3a4","wasm/README.md":"d67bad452550d34e9317fb8f4884b8bc07939fbb0dfdcf1be6bfce52a6851fa6","wasm/wapm.toml":"bdb286ccaa18c5c8aa0d2db25755aba9b0e3e83a59fc4d2334c1854933000ea1"},"package":"72a0ffab8c36d0436114310c7e10b59b3307e650ddfabf6d006028e29a70c6e6"} \ No newline at end of file
diff --git a/vendor/handlebars/CHANGELOG.md b/vendor/handlebars/CHANGELOG.md
new file mode 100644
index 000000000..62045f53e
--- /dev/null
+++ b/vendor/handlebars/CHANGELOG.md
@@ -0,0 +1,442 @@
+# Change Log
+
+## [4.1.0](https://github.com/sunng87/handlebars-rust/compare/4.0.1...4.1.0) - 2021-07-05
+
+* [Added] export `StringOutput` as requested in #442
+* [Changed] strict mode now applies to our helper macro `handlebars_helper!` and
+ built-in helpers based on it.
+* [Fixed] Line stripping feature for standalone statment introduced in #404 is now
+ aligned with handlebarsjs. #448
+
+## [4.0.1](https://github.com/sunng87/handlebars-rust/compare/4.0.0...4.0.1) - 2021-06-15
+
+* [Fixed] Each block render error with empty array or object [#445]
+
+## [4.0.0](https://github.com/sunng87/handlebars-rust/compare/3.4.0...4.0.0) - 2021-05-25
+
+* [Added] `dev_mode` for registry: templates and scripts loaded from file are always
+ reloaded when dev mode enabled [#395]
+* [Added] Registry is now `Clone` [#395]
+* [Added] New built-in helper `len` [#421]
+* [Changed] Updated `rhai` to 0.19 and then 0.20 [#391]
+* [Changed] `#each` helper now renders else block for non-iterable data [#380]
+* [Changed] `TemplateError` and `ScriptError` is now a cause of `RenderError` [#395]
+* [Changed] Empty lines around block helpers are now stripped [#404]
+* [Changed] **Breaking** `RenderContext::get_partial` now returns `Option<&Template>`
+* [Changed] **Breaking** Capitalize names like `HtmlExpression` and `IoError` based on clippy recommendations [#424]
+* [Changed] **Breaking** Improved return type of `call_inner` from `HelperDef` to avoid misleading [#437]
+* [Fixed] reference starts with `null`, `true` and `false` were parsed incorrectly [#382]
+* [Fixed] dir source path separator bug on windows [#389] [#405]
+* [Fixed] stack overflow with nested `@partial-block` [#401]
+* [Fixed] value access issue when upper block has a base value [#419]
+* [Fixed] escape rules for Json string literal [#423]
+* [Fixed] **Breaking** zero-arity subexpressions support [#433]
+ Zero-arity subexpression no longer resolved as variable. The behaviour is now aligned with handlebarsjs.
+ For instance, `{{(parent)}}` can no longer access `parent` field of the context object, use
+ `{{lookup this "parent"}}` instead. This change applies to partial inclusion, too.
+* [Removed] **Breaking** option to disable source map is removed [#395]
+* [Removed] **Breaking** `TemplateFileError` and `TemplateRenderError` are removed and merged into
+ `TemplateError` and `RenderError` [#395]
+
+## [3.5.5](https://github.com/sunng87/handlebars-rust/compare/3.5.4...3.5.5) - 2021-05-03
+
+* [Fixed] Panic on reporting invalid tag name [#427]
+
+## [3.5.4](https://github.com/sunng87/handlebars-rust/compare/3.5.3...3.5.4) - 2021-03-27
+
+* [Fixed] Json string literal with escape char [#422]
+
+## [3.5.3](https://github.com/sunng87/handlebars-rust/compare/3.5.2...3.5.3) - 2021-02-20
+
+* [Fixed] value access issue when upper block has a base value [#419]
+
+## [3.5.2](https://github.com/sunng87/handlebars-rust/compare/3.5.1...3.5.2) - 2020-12-29
+
+* [Fixed] allow `/` as trailing separator on Windows, backported from master [#405]
+
+## [3.5.1](https://github.com/sunng87/handlebars-rust/compare/3.5.0...3.5.1) - 2020-10-25
+
+* [Fixed] dir source path separator bug on windows [#389]
+
+## [3.5.0](https://github.com/sunng87/handlebars-rust/compare/3.4.0...3.5.0) - 2020-09-23
+
+* [Changed] `#each` helper now renders else block for non-iterable data [#380]
+* [Fixed] reference starts with `null`, `true` and `false` were parsed incorrectly [#382]
+
+## [3.4.0](https://github.com/sunng87/handlebars-rust/compare/3.3.0...3.4.0) - 2020-08-14
+
+* [Added] Debug log that can be turned on by using envlog or other implementation, to trace data resolution during rendering [#369]
+* [Fixed] Derived value as block context base value [#343, #353]
+* [Fixed] Partial name aligned with handlebars.js, added support for `.`, escape `[]` and string `''` name
+* [Changed] HTML escape aligned with handlebars.js, added `=`, `\` and ``` [#366]
+* [Changed] Update rhai to 0.18 [#370]
+* [Fixed] Result of simple helper is now escaped [#373]
+
+## [3.3.0](https://github.com/sunng87/handlebars-rust/compare/3.2.1...3.3.0) - 2020-07-18
+
+* [Added] Added two new APIs to reuse `Context` for rendering [#352]
+* [Changed] Update rhai to 0.17 [#354]
+* [Fixed] Fixed mustache.js html expression support, which is "&" instead of "$"
+
+## [3.2.1](https://github.com/sunng87/handlebars-rust/compare/3.2.0...3.2.1) - 2020-06-28
+
+* [Fixed] block context leak introduced in 3.2.0, #346 [#349]
+
+## [3.2.0](https://github.com/sunng87/handlebars-rust/compare/3.1.0...3.2.0) - 2020-06-28
+
+* [Added] API to register an pre-processed template [#331]
+* [Added] Helper macro now has support for named argument and helepr hash [#338]
+* [Added] Added support for `$` expression that is part of mustache.js [#339]
+* [Changed] Update rhai to 0.15 [#330]
+* [Fixed] else block for `each` [#344]
+
+## [3.1.0](https://github.com/sunng87/handlebars-rust/compare/3.0.1...3.1.0) - 2020-06-01
+
+* [Added] All new rhai script helper
+* [Added] multiple parameter support for log helper
+* [Fixed] helper lookup priority
+* [Changed] `Send` and `Sync` are not required for RenderContext local helper [#319]
+* [Fixed] partial block when using path as name [#321]
+
+## [3.0.1](https://github.com/sunng87/handlebars-rust/compare/3.0.0...3.0.1) - 2020-01-25
+
+* [Fixed] Slash in partial path causing syntax error #313
+
+## [3.0.0](https://github.com/sunng87/handlebars-rust/compare/2.0.3...3.0.0) - 2020-01-24
+
+* [Changed] Added lifetime specifier to `Handlebars` structure allowing helper definition to have non-static borrowed data #282
+* [Changed] Removed hashbrown dependency #279
+* [Changed] Features has been reorganized. `dir_source` were turned off by default. #289
+* [Changed] Refactored `RenderContext` API to improve performance up to 5x over `2.0`
+* [Added] Add new `BlockContext` API for helper developer to store block scope state #307
+* [Fixed] `RenderError` should be `Send` and `Sync` #304
+
+## [2.0.4](https://github.com/sunng87/handlebars-rust/compare/2.0.3...2.0.4) - 2020-01-06
+
+* [Fixed] `RenderError` should be `Send` and `Sync` #304
+
+## [2.0.3](https://github.com/sunng87/handlebars-rust/compare/2.0.2...2.0.3) - 2020-01-04
+
+* [Fixed] deprecated warnings on rust 1.42 nightly, due to changes in
+ `Error` trait
+
+## [2.0.2](https://github.com/sunng87/handlebars-rust/compare/2.0.1...2.0.2) - 2019-09-06
+
+* [Changed] Extended `eq` and `ne` helper for all json types #287
+* [Changed] Removed `regex` and `lazy_static` crate to optimize dependency tree
+
+## [2.0.1](https://github.com/sunng87/handlebars-rust/compare/2.0.0...2.0.1) - 2019-07-12
+* [Changed] Fixed issue with block context #275
+* [Changed] Added support for array index in block context #276
+* [Changed] Deprecated RenderContext `concat_path`
+* [Changed] Update hashbrown to 0.5.0
+
+## [2.0.0](https://github.com/sunng87/handlebars-rust/compare/2.0.0-beta3...2.0.0) - 2019-07-02
+* [Changed] Fixed more dyn trait warnings
+* [Changed] #80 Fixed support for zero-param helper
+* [Changed] Changed minimum Rust version to 1.32 as required by
+ getrandom crate
+
+## [2.0.0-beta.3](https://github.com/sunng87/handlebars-rust/compare/2.0.0-beta1...2.0.0-beta.3) - 2019-06-24
+
+* [Changed] Block parameter revamp, fixed cases for #260 and #264
+* [Changed] #265 Fixed block parameter order in `each` helper
+* [Changed] #266 Accept any JSON value in boolean helpers
+* [Changed] `RenderContext` API update, `evaluate_absolute` removed,
+ use `@root` instead
+
+## [2.0.0-beta.1](https://github.com/sunng87/handlebars-rust/compare/1.1.0...2.0.0-beta.1) - 2019-03-16
+
+* [Changed] Everything changed in yanked 1.2.0
+* [Changed] With Pest updated to 2.1, our minimal rust version is set
+ to 1.31
+* [Changed] Using hashbrown `HashMap` internally and externally,
+ performance improvement up to 10%
+* [Changed] strict mode also apply to return value of helper expression
+
+## [1.2.0](https://github.com/sunng87/handlebars-rust/compare/1.1.0...1.2.0) - 2018-12-15
+
+*This release is yanked.*
+
+* [Changed] Using rust 2018 edition
+* [Changed] Improve strict mode and only raise error when accessing
+ missing fields in expression
+* [Changed] Improved `get_helper` and `get_decorator` return type
+
+## [1.1.0](https://github.com/sunng87/handlebars-rust/compare/1.0.5...1.1.0) - 2018-10-24
+
+* [Added] New option `includeZero` for `if` helper
+* [Added] New option `level` for `log` helper
+* [Changed] Updated Pest to 2.0, moving minimal Rust version to 1.30
+
+## [1.0.5](https://github.com/sunng87/handlebars-rust/compare/1.0.4...1.0.5) - 2018-10-04
+
+* [Changed] Added feature `no_logging` for using handlebars in a
+ logging provider.
+
+## [1.0.4](https://github.com/sunng87/handlebars-rust/compare/1.0.3...1.0.4) - 2018-09-21
+
+* [Changed] Fixed build on wasm
+* [Changed] Added support for single-quote Json string literal
+
+## [1.0.3](https://github.com/sunng87/handlebars-rust/compare/1.0.2...1.0.3) - 2018-08-29
+
+* [Changed] Fixed build on Rust 1.23.0
+
+## [1.0.2](https://github.com/sunng87/handlebars-rust/compare/1.0.1...1.0.2) - 2018-08-27
+
+* [Changed] Update minimal dependency versions
+
+## [1.0.1](https://github.com/sunng87/handlebars-rust/compare/1.0.0...1.0.1) - 2018-08-16
+
+* [Changed] Added hidden/temp file filter to directory register
+
+## [1.0.0](https://github.com/sunng87/handlebars-rust/compare/0.32.4...1.0.0) - 2018-07-18
+
+* [Changed] Helper API finalized and new output API
+* [Changed] New internal value API, reduced clone cost
+* [Added] Helper macro
+* [Added] New built-in helpers: `gt`, `lt` and some more
+* [Added] Register template folder
+
+## [0.32.4](https://github.com/sunng87/handlebars-rust/compare/0.32.3...0.32.4) - 2018-05-23
+
+* [Changed] Keep compatibility with pre-1.26 rust by removing `impl
+ Trait` on parameters
+
+## [0.32.3](https://github.com/sunng87/handlebars-rust/compare/0.32.2...0.32.3) - 2018-05-21
+
+* [Changed] Fixed escape syntax
+
+## [0.32.2](https://github.com/sunng87/handlebars-rust/compare/0.32.1...0.32.2) - 2018-05-09
+
+* [Changed] Fixed issue with processing handlebars comment
+
+## [0.32.1](https://github.com/sunng87/handlebars-rust/compare/0.32.0...0.32.1) - 2018-05-02
+
+* [Changed] Regex 1.0
+
+## [0.32.0](https://github.com/sunng87/handlebars-rust/compare/0.30.1...0.32.0) - 2018-02-16
+
+* [Added] Strict mode that raises `RenderError` on accessing
+ non-existed field or array index.
+
+## [0.31.0](https://github.com/sunng87/handlebars-rust/compare/0.30.1...0.31.0) - 2018-02-09
+* [Changed] Fixed handlebars comment support, added html comment output
+* [Changed] Removed some wasted string clones
+
+## [0.30.1](https://github.com/sunng87/handlebars-rust/compare/0.30.0...0.30.1) - 2018-01-31
+* [Changed] Added `Debug` for public types
+
+## [0.30.0](https://github.com/sunng87/handlebars-rust/compare/0.30.0-beta.5...0.30.0) - 2018-01-21
+* [Changed] Use pest 1.0
+
+## [0.30.0-beta.5](https://github.com/sunng87/handlebars-rust/compare/0.30.0-beta.4...0.30.0-beta.5) - 2018-01-19
+
+* [Changed] Improve `TemplateError` display. Now includes a segment of
+ template string.
+* [Changed] Updated `lazy_static` to 1.0
+* [Changed] Renamed some render functions names.
+
+## [0.30.0-beta.4](https://github.com/sunng87/handlebars-rust/compare/0.30.0-beta.3...0.30.0-beta.4) - 2017-11-20
+* [Changed] Added `Sync` to the nested error of `RenderError`
+
+## [0.30.0-beta.3](https://github.com/sunng87/handlebars-rust/compare/0.30.0-beta.2...0.30.0-beta.3) - 2017-11-16
+* [Changed] Fixed issue `template_render` methods doesn't respect `source_map` setting
+
+## [0.30.0-beta.2](https://github.com/sunng87/handlebars-rust/compare/0.30.0-beta.1...0.30.0-beta.2) - 2017-10-07
+* [Changed] Fixed parsing keywords like `as`
+
+## [0.30.0-beta.1](https://github.com/sunng87/handlebars-rust/compare/0.29.1...0.30.0-beta.1) - 2017-10-03
+
+* [Changed] Upgrade pest to 1.0
+* [Changed] Fixed template parsing issue when parameter starts with "as"
+* [Changed] Added new HelperDef function to return JSON value
+* [Changed] Added support for @root
+
+## [0.29.1](https://github.com/sunng87/handlebars-rust/compare/0.29.0...0.29.1) - 2017-09-01
+
+* [Changed] Remove `debug!` logging from render to avoid conflict when
+ using handlebars as logging backend
+
+## [0.29.0](https://github.com/sunng87/handlebars-rust/compare/0.28.3...0.29.0) - 2017-08-23
+
+* [Changed] Align JSON path with original JavaScript implementation
+
+## [0.28.3](https://github.com/sunng87/handlebars-rust/compare/0.28.2...0.28.3) - 2017-08-02
+
+* [Changed] fixed support for escape, again
+
+## [0.28.2](https://github.com/sunng87/handlebars-rust/compare/0.28.1...0.28.2) - 2017-08-01
+
+* [Changed] Fixed support for escape `\\{{`. [#170](https://github.com/sunng87/handlebars-rust/issues/170)
+
+## [0.28.1](https://github.com/sunng87/handlebars-rust/compare/0.28.0...0.28.1) - 2017-07-16
+
+* [Changed] Mark `RenderError` with `Send` trait
+
+## [0.28.0](https://github.com/sunng87/handlebars-rust/compare/0.27.0...0.28.0) - 2017-07-15
+
+* [Changed] Fixed performance issue discussed in [#166](https://github.com/sunng87/handlebars-rust/issues/166)
+* [Added] Added error cause `RenderError`
+
+## [0.27.0](https://github.com/sunng87/handlebars-rust/compare/0.26.2...0.27.0) - 2017-06-03
+
+* [Changed] `partial_legacy` is dropped
+* [Changed] `context.navigate` now returns a `Result<&Json,RenderError>`. Error is raised when
+ given path cannot be not parsed.
+* [Changed] removed `context::extend` because it's like to ruin your context outside the helper.
+* [Changed] `RenderContext` now owns `Context`, you can host a new Context for particular block
+ helper.
+* [Changed] Added some convenience functions to `RenderContext`. However, `RenderContext` may
+ still change in future release.
+
+## [0.26.1](https://github.com/sunng87/handlebars-rust/compare/0.25.3...0.26.1) - 2017-04-23
+
+* [Changed] Updated to Serde 1.0
+* [Changed] Dropped rustc_serialize, serde is now the default type system
+
+## [0.25.3](https://github.com/sunng87/handlebars-rust/compare/0.25.2...0.25.3) - 2017-04-19
+
+* [Changed] Fixed path up [#147](https://github.com/sunng87/handlebars-rust/issues/147)
+* [Changed] Fixed duplicated template inclusion [#146](https://github.com/sunng87/handlebars-rust/issues/146)
+
+## [0.25.2](https://github.com/sunng87/handlebars-rust/compare/0.25.1...0.25.2) - 2017-03-22
+
+* [Changed] Fixed bug when including two partials with same name [#143](https://github.com/sunng87/handlebars-rust/issues/143)
+
+## [0.25.1](https://github.com/sunng87/handlebars-rust/compare/0.25.0...0.25.1) - 2017-02-21
+
+* [Added] Added support for braces escaping`\{{var}}`.
+
+## [0.25.0](https://github.com/sunng87/handlebars-rust/compare/0.24.2...0.25.0) - 2017-01-28
+
+* [Changed] Updated serde family to 0.9.x
+* [Added] Added `to_json` function to convert data to `Json` or `Value`
+
+## [0.24.2](https://github.com/sunng87/handlebars-rust/compare/0.24.1...0.24.2) - 2017-01-28
+
+* [Added] Added support for `{{> @partial-block}}`
+
+## [0.24.1](https://github.com/sunng87/handlebars-rust/compare/0.24.0...0.24.1) - 2016-12-30
+
+* [Changed] Updated `regex` crate to 0.2, fixed WebAssembly support
+* [Changed] Fixed error reporting in partial.
+
+## [0.24.0](https://github.com/sunng87/handlebars-rust/compare/0.23.0...0.24.0) - 2016-12-30
+
+* [Added] Decorator support: change context data and helpers during rendering
+* [Changed] (**Breaking**) Helper trait changed, `Context` parameter no longer
+ available, use `render_context.context()` instead.
+* [Changed] (**Breaking**) Refactored Handlebars APIs, `Template` and
+ `Context` are no longer exposed in public API.
+* [Changed] Docs updated.
+
+## [0.23.0](https://github.com/sunng87/handlebars-rust/compare/0.22.0...0.23.0) - 2016-12-12
+
+* [Changed] `partial4` is now default. Use `partial_legacy` for previous version of template inheritance.
+* [Changed] Corrected subexpression behavior. Subexpression result is treated as string.
+* [Changed] Improved performance for render: better escape function and string writer buffer.
+
+## [0.22.0](https://github.com/sunng87/handlebars-rust/compare/0.21.1...0.22.0) - 2016-10-29
+
+* [Changed] Improved error reporting. Fixed display for several error
+ types.
+* [Changed] Dropped regex and lazystatic as dependency.
+* [Changed] Examples refined.
+
+## [0.21.1](https://github.com/sunng87/handlebars-rust/compare/0.21.0...0.21.1) - 2016-10-09
+
+* [Changed] Fixed
+ [#106](https://github.com/sunng87/handlebars-rust/issue/106), when
+ property name contains `this`, it doesn't work
+
+## [0.21.0](https://github.com/sunng87/handlebars-rust/compare/0.20.5...0.21.0) - 2016-09-27
+
+* [Added] Block params support
+ [#101](https://github.com/sunng87/handlebars-rust/pull/101)
+* [Added] New partial syntax [#103](https://github.com/sunng87/handlebars-rust/pull/103)
+* [Changed] Rewrite path parser, better support for `../`
+ [#105](https://github.com/sunng87/handlebars-rust/pull/105)
+
+## [0.20.5](https://github.com/sunng87/handlebars-rust/compare/0.20.5...0.20.4) - 2016-08-27
+
+* [Changed] Fixed issue for using [] in expression
+ [#100](https://github.com/sunng87/handlebars-rust/issue/100)
+
+## [0.20.4](https://github.com/sunng87/handlebars-rust/compare/0.20.4...0.20.3) - 2016-08-27
+
+* [Changed] Fixed error message for partials
+ [#98](https://github.com/sunng87/handlebars-rust/issue/98)
+* [Added] Added support for `else` in `each` block
+ [#99](https://github.com/sunng87/handlebars-rust/issue/99)
+
+## [0.20.3](https://github.com/sunng87/handlebars-rust/compare/0.20.3...0.20.2) - 2016-08-14
+
+* [Changed] Fixed `with` used inside `each` block [#97](https://github.com/sunng87/handlebars-rust/pull/97)
+
+## [0.20.2](https://github.com/sunng87/handlebars-rust/compare/0.20.2...0.20.0) - 2016-08-07
+
+* [Changed] Allowed dash character in reference
+ [#94](https://github.com/sunng87/handlebars-rust/pull/94)
+* [Changed] Fixed path error in nested each helpers [#95](https://github.com/sunng87/handlebars-rust/pull/95)
+
+## [0.20.0](https://github.com/sunng87/handlebars-rust/compare/0.20.0...0.19.1) - 2016-07-31
+
+* [Changed] Updated serde to 0.8
+
+## [0.19.1](https://github.com/sunng87/handlebars-rust/compare/0.19.1...0.19.0) - 2016-07-26
+
+* [Changed] Fixed `../` path visitor bug in nested `#each`
+ [#93](https://github.com/sunng87/handlebars-rust/issues/93)
+* [Changed] Rollback 0.19.0 change for `#if`
+
+## [0.19.0] - 2016-07-24
+
+* [Changed] changed `&Path` to `AsRef<Path>`
+* [Changed] Fixed "../" path visitor in `#each` and `#if`.
+* [Added] `set_local_path_root` and `get_local_path_root` for
+ `RenderContext`.
+
+## [0.18.2] - 2016-07-11
+
+* [Changed] Disable `rustc_type` when `serde_type` enabled.
+
+## [0.18.1] - 2016-07-04
+
+* [Changed] Allow `-` char in reference.
+
+## [0.18.0] - 2016-06-25
+
+* [Changed] Rewrite template parser with pest.
+
+## [0.17.0] - 2016-06-05
+
+* [Added] JSON literals as helper param or hash, and subexpression
+ return value.
+* [Added] RenderError now reports template name, line and column
+ number. Enabled by default. This behavior can be disabled via
+ `registry.source_map_enable(false)` on production.
+* [Changed] Helper API **break change**: `param(..)` and `hash(...)`
+ now returns a `ContextJson` as value which contains path as well as
+ parsed Json value. No need to call `ctx.navigate(...)` any more.
+* [Removed] `to_string` of `Template` and `TemplateElement` which is
+ unnecessary and contains issue
+
+## [0.16.1] - 2016-05-15
+
+* [Removed] `num` crate dependency which is unnecessary
+
+## [0.16.0] - 2016-03-18
+
+* [Added] new APIs to render template string/files without
+ registering to Registry
+* [Added] new handlebars raw helper syntax
+
+## [0.15.0] - 2016-03-01
+
+* [Changed] update serde libraries to 0.7.x
+
+## [0.14.0] - 2016-02-08
+
+* [Added] new API: `register_template_file`
diff --git a/vendor/handlebars/Cargo.lock b/vendor/handlebars/Cargo.lock
new file mode 100644
index 000000000..8fc6db06f
--- /dev/null
+++ b/vendor/handlebars/Cargo.lock
@@ -0,0 +1,2175 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "796540673305a66d127804eef19ad696f1f204b8c1025aaca4958c17eab32877"
+dependencies = [
+ "getrandom 0.2.3",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "ahash"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98"
+dependencies = [
+ "getrandom 0.2.3",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
+
+[[package]]
+name = "arrayvec"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
+dependencies = [
+ "nodrop",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "backtrace"
+version = "0.3.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7815ea54e4d821e791162e078acbebfd6d8c8939cd559c9335dceb1c8ca7282"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
+]
+
+[[package]]
+name = "bstr"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
+dependencies = [
+ "lazy_static",
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
+name = "buf_redux"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
+dependencies = [
+ "memchr",
+ "safemem",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
+
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
+[[package]]
+name = "bytemuck"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9966d2ab714d0f785dbac0a0396251a35280aeb42413281617d0209ab4898435"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
+
+[[package]]
+name = "bytes"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
+
+[[package]]
+name = "cast"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57cdfa5d50aad6cb4d44dcab6101a7f79925bd59d82ca42f38a9856a28865374"
+dependencies = [
+ "rustc_version",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "bitflags",
+ "textwrap",
+ "unicode-width",
+]
+
+[[package]]
+name = "cpp_demangle"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44919ecaf6f99e8e737bc239408931c9a01e9a6c74814fee8242dd2506b65390"
+dependencies = [
+ "cfg-if 1.0.0",
+ "glob",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "criterion"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23"
+dependencies = [
+ "atty",
+ "cast",
+ "clap",
+ "criterion-plot",
+ "csv",
+ "itertools 0.10.1",
+ "lazy_static",
+ "num-traits",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_cbor",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
+dependencies = [
+ "cast",
+ "itertools 0.9.0",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+ "lazy_static",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
+dependencies = [
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
+[[package]]
+name = "csv"
+version = "1.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
+dependencies = [
+ "bstr",
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "debugid"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91cf5a8c2f2097e2a32627123508635d47ce10563d999ec1a95addf08b502ba"
+dependencies = [
+ "uuid",
+]
+
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "env_logger"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
+[[package]]
+name = "fixedbitset"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
+
+[[package]]
+name = "futures-io"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282"
+
+[[package]]
+name = "futures-task"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
+
+[[package]]
+name = "futures-util"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
+dependencies = [
+ "autocfg",
+ "futures-core",
+ "futures-sink",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.10.2+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "gimli"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "h2"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726"
+dependencies = [
+ "bytes 1.0.1",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "half"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
+
+[[package]]
+name = "handlebars"
+version = "4.1.0"
+dependencies = [
+ "criterion",
+ "env_logger",
+ "log",
+ "maplit",
+ "pest",
+ "pest_derive",
+ "pprof",
+ "quick-error 2.0.1",
+ "rhai",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tempfile",
+ "tokio",
+ "walkdir",
+ "warp",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
+[[package]]
+name = "headers"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0b7591fb62902706ae8e7aaff416b1b0fa2c0fd0878b46dc13baa3712d8a855"
+dependencies = [
+ "base64",
+ "bitflags",
+ "bytes 1.0.1",
+ "headers-core",
+ "http",
+ "mime",
+ "sha-1 0.9.6",
+ "time",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+dependencies = [
+ "http",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "http"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
+dependencies = [
+ "bytes 1.0.1",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9"
+dependencies = [
+ "bytes 1.0.1",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68"
+
+[[package]]
+name = "httpdate"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "0.14.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83"
+dependencies = [
+ "bytes 1.0.1",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "inferno"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c3cbcc228d2ad2e99328c2b19f38d80ec387ca6a29f778e40e32ca9f25448c3"
+dependencies = [
+ "ahash 0.6.3",
+ "atty",
+ "indexmap",
+ "itoa",
+ "lazy_static",
+ "log",
+ "num-format",
+ "quick-xml",
+ "rgb",
+ "str_stack",
+]
+
+[[package]]
+name = "input_buffer"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413"
+dependencies = [
+ "bytes 1.0.1",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "itertools"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+
+[[package]]
+name = "js-sys"
+version = "0.3.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
+
+[[package]]
+name = "lock_api"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
+[[package]]
+name = "matches"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
+
+[[package]]
+name = "memchr"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
+
+[[package]]
+name = "memmap"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
+dependencies = [
+ "adler",
+ "autocfg",
+]
+
+[[package]]
+name = "mio"
+version = "0.7.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "multimap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
+
+[[package]]
+name = "multipart"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050aeedc89243f5347c3e237e3e13dc76fbe4ae3742a57b94dc14f69acf76d4"
+dependencies = [
+ "buf_redux",
+ "httparse",
+ "log",
+ "mime",
+ "mime_guess",
+ "quick-error 1.2.3",
+ "rand 0.7.3",
+ "safemem",
+ "tempfile",
+ "twoway",
+]
+
+[[package]]
+name = "nix"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if 0.1.10",
+ "libc",
+ "void",
+]
+
+[[package]]
+name = "nodrop"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-format"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465"
+dependencies = [
+ "arrayvec",
+ "itoa",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.25.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a38f2be3697a57b4060074ff41b44c16870d916ad7877c17696e063257482bc7"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+
+[[package]]
+name = "oorandom"
+version = "11.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
+dependencies = [
+ "cfg-if 1.0.0",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pest"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+dependencies = [
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
+dependencies = [
+ "maplit",
+ "pest",
+ "sha-1 0.8.2",
+]
+
+[[package]]
+name = "petgraph"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "plotters"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "pprof"
+version = "0.3.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "937e4766a8d473f9dd3eb318c654dec77d6715a87ab50081d6e5cfceea73c105"
+dependencies = [
+ "backtrace",
+ "inferno",
+ "lazy_static",
+ "libc",
+ "log",
+ "nix",
+ "parking_lot",
+ "prost",
+ "prost-build",
+ "prost-derive",
+ "symbolic-demangle",
+ "tempfile",
+ "thiserror",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "prost"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce49aefe0a6144a45de32927c77bd2859a5f7677b55f220ae5b744e87389c212"
+dependencies = [
+ "bytes 0.5.6",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b10678c913ecbd69350e8535c3aef91a8676c0773fc1d7b95cdd196d7f2f26"
+dependencies = [
+ "bytes 0.5.6",
+ "heck",
+ "itertools 0.8.2",
+ "log",
+ "multimap",
+ "petgraph",
+ "prost",
+ "prost-types",
+ "tempfile",
+ "which",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537aa19b95acde10a12fec4301466386f757403de4cd4e5b4fa78fb5ecb18f72"
+dependencies = [
+ "anyhow",
+ "itertools 0.8.2",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1834f67c0697c001304b75be76f67add9c89742eda3a085ad8ee0bb38c3417aa"
+dependencies = [
+ "bytes 0.5.6",
+ "prost",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
+[[package]]
+name = "quick-xml"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26aab6b48e2590e4a64d1ed808749ba06257882b461d01ca71baeb747074a6dd"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc 0.2.0",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.2",
+ "rand_hc 0.3.0",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.2",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
+dependencies = [
+ "getrandom 0.2.3",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
+dependencies = [
+ "rand_core 0.6.2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
+dependencies = [
+ "autocfg",
+ "crossbeam-deque",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "lazy_static",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "rgb"
+version = "0.8.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fddb3b23626145d1776addfc307e1a1851f60ef6ca64f376bcb889697144cf0"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "rhai"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d862e125236e74d2a5033e88b9cb9404010a6a39516a388f570975494fe73bce"
+dependencies = [
+ "ahash 0.7.4",
+ "instant",
+ "num-traits",
+ "rhai_codegen",
+ "serde",
+ "smallvec",
+ "smartstring",
+]
+
+[[package]]
+name = "rhai_codegen"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "643fd67d19aafd45a9e335afe4183dc58ba0cc6a1f43fbe34c7d92c041cdcafc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce"
+
+[[package]]
+name = "rustc_version"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "semver"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
+dependencies = [
+ "pest",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_cbor"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622"
+dependencies = [
+ "half",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug 0.2.3",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
+
+[[package]]
+name = "smallvec"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
+
+[[package]]
+name = "smartstring"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ada87540bf8ef4cf8a1789deb175626829bb59b1fefd816cf7f7f55efcdbae9"
+dependencies = [
+ "static_assertions",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "str_stack"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb"
+
+[[package]]
+name = "symbolic-common"
+version = "8.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f8e101b55bbcf228c855fa34fc4312e4f58b4a3251f1298bc0f97b71557815d"
+dependencies = [
+ "debugid",
+ "memmap",
+ "stable_deref_trait",
+ "uuid",
+]
+
+[[package]]
+name = "symbolic-demangle"
+version = "8.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e257e28c2cbcf60a0c21089d32ff8b6cdc7efa6125b13693d29e8986aa1cd99"
+dependencies = [
+ "cpp_demangle",
+ "rustc-demangle",
+ "symbolic-common",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "rand 0.8.3",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aea337f72e96efe29acc234d803a5981cd9a2b6ed21655cd7fc21cfe021e8ec7"
+dependencies = [
+ "autocfg",
+ "bytes 1.0.1",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "tokio-macros",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8864d706fdb3cc0843a49647ac892720dac98a6eeb818b77190592cf4994066"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1a5f475f1b9d077ea1017ecbc60890fda8e54942d680ca0b1d2b47cfa2d861b"
+dependencies = [
+ "futures-util",
+ "log",
+ "pin-project",
+ "tokio",
+ "tungstenite",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592"
+dependencies = [
+ "bytes 1.0.1",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
+dependencies = [
+ "cfg-if 1.0.0",
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "tungstenite"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24"
+dependencies = [
+ "base64",
+ "byteorder",
+ "bytes 1.0.1",
+ "http",
+ "httparse",
+ "input_buffer",
+ "log",
+ "rand 0.8.3",
+ "sha-1 0.9.6",
+ "url",
+ "utf-8",
+]
+
+[[package]]
+name = "twoway"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "typenum"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
+dependencies = [
+ "matches",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "warp"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "332d47745e9a0c38636dbd454729b147d16bd1ed08ae67b3ab281c4506771054"
+dependencies = [
+ "bytes 1.0.1",
+ "futures",
+ "headers",
+ "http",
+ "hyper",
+ "log",
+ "mime",
+ "mime_guess",
+ "multipart",
+ "percent-encoding",
+ "pin-project",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-stream",
+ "tokio-tungstenite",
+ "tokio-util",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
+
+[[package]]
+name = "web-sys"
+version = "0.3.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "which"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/vendor/handlebars/Cargo.toml b/vendor/handlebars/Cargo.toml
new file mode 100644
index 000000000..f46e0bd13
--- /dev/null
+++ b/vendor/handlebars/Cargo.toml
@@ -0,0 +1,98 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "handlebars"
+version = "4.1.0"
+authors = ["Ning Sun <sunng@pm.me>"]
+description = "Handlebars templating implemented in Rust."
+homepage = "https://github.com/sunng87/handlebars-rust"
+documentation = "https://docs.rs/crate/handlebars/"
+readme = "README.md"
+keywords = ["handlebars", "templating", "web"]
+categories = ["template-engine", "web-programming"]
+license = "MIT"
+repository = "https://github.com/sunng87/handlebars-rust"
+[package.metadata.docs.rs]
+features = ["dir_source", "script_helper"]
+rustdoc-args = ["--cfg", "docsrs"]
+
+[lib]
+name = "handlebars"
+path = "src/lib.rs"
+
+[[bin]]
+name = "handlebars-cli"
+path = "src/cli.rs"
+
+[[bench]]
+name = "bench"
+harness = false
+[dependencies.log]
+version = "0.4.0"
+
+[dependencies.pest]
+version = "2.1.0"
+
+[dependencies.pest_derive]
+version = "2.1.0"
+
+[dependencies.quick-error]
+version = "2.0"
+
+[dependencies.rhai]
+version = "0.20"
+features = ["sync", "serde"]
+optional = true
+
+[dependencies.serde]
+version = "1.0.0"
+
+[dependencies.serde_json]
+version = "1.0.39"
+
+[dependencies.walkdir]
+version = "2.2.3"
+optional = true
+[dev-dependencies.criterion]
+version = "0.3"
+
+[dev-dependencies.env_logger]
+version = "0.8"
+
+[dev-dependencies.maplit]
+version = "1.0.0"
+
+[dev-dependencies.serde_derive]
+version = "1.0.75"
+
+[dev-dependencies.tempfile]
+version = "3.0.0"
+
+[dev-dependencies.tokio]
+version = "1"
+features = ["macros", "rt-multi-thread"]
+
+[dev-dependencies.warp]
+version = "0.3"
+
+[features]
+default = []
+dir_source = ["walkdir"]
+no_logging = []
+script_helper = ["rhai"]
+[target."cfg(unix)".dev-dependencies.pprof]
+version = "0.3.13"
+features = ["flamegraph", "protobuf"]
+[badges.maintenance]
+status = "actively-developed"
diff --git a/vendor/handlebars/LICENSE b/vendor/handlebars/LICENSE
new file mode 100644
index 000000000..384d0c996
--- /dev/null
+++ b/vendor/handlebars/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Ning Sun
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/handlebars/README.md b/vendor/handlebars/README.md
new file mode 100644
index 000000000..4a9c41827
--- /dev/null
+++ b/vendor/handlebars/README.md
@@ -0,0 +1,196 @@
+handlebars-rust
+===============
+
+[Handlebars templating language](https://handlebarsjs.com) implemented
+in Rust and for Rust.
+
+Handlebars-rust is the template engine that renders the official Rust website
+[rust-lang.org](https://www.rust-lang.org), [its
+book](https://doc.rust-lang.org/book/).
+
+[![Build Status](https://travis-ci.org/sunng87/handlebars-rust.svg?branch=master)](https://travis-ci.org/sunng87/handlebars-rust)
+[![](https://meritbadge.herokuapp.com/handlebars)](https://crates.io/crates/handlebars)
+[![](https://img.shields.io/crates/d/handlebars.svg)](https://crates.io/crates/handlebars)
+[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
+[![Docs](https://docs.rs/handlebars/badge.svg)](https://docs.rs/crate/handlebars/)
+![rustc](https://img.shields.io/badge/rustc-1.50+-lightgray.svg)
+[![Donate](https://img.shields.io/badge/donate-liberapay-yellow.svg)](https://liberapay.com/Sunng/donate)
+
+## Getting Started
+
+### Quick Start
+
+```rust
+use handlebars::Handlebars;
+use serde_json::json;
+
+fn main() -> Result<(), Box<dyn Error>> {
+ let mut reg = Handlebars::new();
+ // render without register
+ println!(
+ "{}",
+ reg.render_template("Hello {{name}}", &json!({"name": "foo"}))?
+ );
+
+ // register template using given name
+ reg.register_template_string("tpl_1", "Good afternoon, {{name}}")?;
+ println!("{}", reg.render("tpl_1", &json!({"name": "foo"}))?);
+
+ Ok(())
+}
+```
+
+### Code Example
+
+If you are not familiar with [handlebars language
+syntax](https://handlebarsjs.com), it is recommended to walk through
+their introduction first.
+
+Examples are provided in source tree to demo usage of various api.
+
+* [quick](https://github.com/sunng87/handlebars-rust/blob/master/examples/quick.rs)
+ the very basic example of registry and render apis
+* [render](https://github.com/sunng87/handlebars-rust/blob/master/examples/render.rs)
+ how to define custom helpers with function, trait impl or macro, and also how
+ to use custom helpers.
+* [render_file](https://github.com/sunng87/handlebars-rust/blob/master/examples/render_file.rs)
+ similar to render, but render to file instead of string
+* [partials](https://github.com/sunng87/handlebars-rust/blob/master/examples/partials.rs)
+ template inheritance with handlebars
+* [decorator](https://github.com/sunng87/handlebars-rust/blob/master/examples/decorator.rs)
+ how to use decorator to change data or define custom helper
+* [script](https://github.com/sunng87/handlebars-rust/blob/master/examples/script.rs)
+ how to define custom helper with rhai scripting language,
+ just like using javascript for handlebarsjs
+* [error](https://github.com/sunng87/handlebars-rust/blob/master/examples/error.rs)
+ simple case for error
+* [dev_mode](https://github.com/sunng87/handlebars-rust/blob/master/examples/dev_mode.rs)
+ a web server hosts handlebars in `dev_mode`, you can edit the template and see the change
+ without restarting your server.
+
+## Minimum Rust Version Policy
+
+Handlebars will track Rust nightly and stable channel. When dropping
+support for previous stable versions, I will bump **major** version
+and clarify in CHANGELOG.
+
+## Document
+
+[Rust doc](https://docs.rs/crate/handlebars/).
+
+## Changelog
+
+Changelog is available in the source tree named as `CHANGELOG.md`.
+
+## Contributor Guide
+
+Any contribution to this library is welcomed. To get started into
+development, I have several [Help
+Wanted](https://github.com/sunng87/handlebars-rust/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)
+issues, with the difficulty level labeled. When running into any problem,
+feel free to contact me on github.
+
+I'm always looking for maintainers to work together on this library,
+let me know (via email or anywhere in the issue tracker) if you
+want to join.
+
+## Why (this) Handlebars?
+
+Handlebars is a real-world templating system that you can use to build
+your application without pain.
+
+### Features
+
+#### Isolation of Rust and HTML
+
+This library doesn't attempt to use some macro magic to allow you to
+write your template within your rust code. I admit that it's fun to do
+that but it doesn't fit real-world use cases.
+
+#### Limited but essential control structures built-in
+
+Only essential control directives `if` and `each` are built-in. This
+prevents you from putting too much application logic into your template.
+
+#### Extensible helper system
+
+You can write your own helper with Rust! It can be a block helper or
+inline helper. Put your logic into the helper and don't repeat
+yourself.
+
+A helper can be as a simple as a Rust function like:
+
+```rust
+handlebars_helper!(hex: |v: i64| format!("0x{:x}", v));
+
+/// register the helper
+handlebars.register_helper("hex", Box::new(hex));
+```
+
+And using it in your template:
+
+```handlebars
+{{hex 16}}
+```
+
+By default, handlebars-rust ships [additional helpers](https://github.com/sunng87/handlebars-rust/blob/master/src/helpers/helper_boolean.rs#L5)
+(compared with original js version)
+that is useful when working with `if`.
+
+With `script_helper` feature flag enabled, you can also create helpers
+using [rhai](https://github.com/jonathandturner/rhai) script, just like JavaScript
+for handlebars-js. This feature was in early stage. Its API was limited at the
+moment, and can change in future.
+
+#### Template inheritance
+
+Every time I look into a templating system, I will investigate its
+support for [template
+inheritance](https://docs.djangoproject.com/en/1.9/ref/templates/language/#template-inheritance).
+
+Template include is not sufficient for template reuse. In most cases
+you will need a skeleton of page as parent (header, footer, etc.), and
+embed your page into this parent.
+
+You can find a real example of template inheritance in
+`examples/partials.rs` and templates used by this file.
+
+#### Auto-reload in dev mode
+
+By turning on `dev_mode`, handlebars auto reloads any template and scripts that
+loaded from files or directory. This can be handy for template development.
+
+#### WebAssembly compatible
+
+Handlebars 3.0 can be used in WebAssembly projects.
+
+## Related Projects
+
+### Web frameworks
+
+* Iron: [handlebars-iron](https://github.com/sunng87/handlebars-iron)
+* Rocket: [rocket/contrib](https://api.rocket.rs/v0.4/rocket_contrib/templates/index.html)
+* Warp: [handlebars
+ example](https://github.com/seanmonstar/warp/blob/master/examples/handlebars_template.rs)
+* Tower-web: [Built-in](https://github.com/carllerche/tower-web)
+* Actix: [handlebars
+ example](https://github.com/actix/examples/blob/master/template_engines/handlebars/src/main.rs)
+* Tide: [tide-handlebars](https://github.com/No9/tide-handlebars)
+
+### Adopters
+
+The
+[adopters](https://github.com/sunng87/handlebars-rust/wiki/Adopters)
+page lists projects that uses handlebars for part of their
+functionalities.
+
+### Extensions
+
+The
+[extensions](https://github.com/sunng87/handlebars-rust/wiki/Extensions)
+page has libraries that provide additional helpers, decorators and
+outputs to handlebars-rust, and you can use in your own projects.
+
+## License
+
+This library (handlebars-rust) is open sourced under the MIT License.
diff --git a/vendor/handlebars/benches/bench.rs b/vendor/handlebars/benches/bench.rs
new file mode 100644
index 000000000..f5665c754
--- /dev/null
+++ b/vendor/handlebars/benches/bench.rs
@@ -0,0 +1,236 @@
+#[macro_use]
+extern crate criterion;
+#[macro_use]
+extern crate serde_derive;
+
+use criterion::Criterion;
+use handlebars::{to_json, Context, Handlebars, Template};
+use serde_json::value::Value as Json;
+use std::collections::BTreeMap;
+
+#[cfg(unix)]
+use criterion::profiler::Profiler;
+#[cfg(unix)]
+use pprof::protos::Message;
+#[cfg(unix)]
+use pprof::ProfilerGuard;
+
+#[cfg(unix)]
+use std::fs::{create_dir_all, File};
+#[cfg(unix)]
+use std::io::Write;
+#[cfg(unix)]
+use std::path::Path;
+
+#[cfg(unix)]
+#[derive(Default)]
+struct CpuProfiler<'a> {
+ guard: Option<ProfilerGuard<'a>>,
+}
+
+#[cfg(unix)]
+impl<'a> Profiler for CpuProfiler<'a> {
+ fn start_profiling(&mut self, _benchmark_id: &str, benchmark_dir: &Path) {
+ create_dir_all(&benchmark_dir).unwrap();
+
+ let guard = ProfilerGuard::new(100).unwrap();
+ self.guard = Some(guard);
+ }
+
+ fn stop_profiling(&mut self, benchmark_id: &str, benchmark_dir: &Path) {
+ if let Ok(ref report) = self.guard.as_ref().unwrap().report().build() {
+ let fg_file_name = benchmark_dir.join(format!("{}.svg", benchmark_id));
+ let fg_file = File::create(fg_file_name).unwrap();
+ report.flamegraph(fg_file).unwrap();
+
+ let pb_file_name = benchmark_dir.join(format!("{}.pb", benchmark_id));
+ let mut pb_file = File::create(pb_file_name).unwrap();
+ let profile = report.pprof().unwrap();
+
+ let mut content = Vec::new();
+ profile.encode(&mut content).unwrap();
+ pb_file.write_all(&content).unwrap();
+ };
+
+ self.guard = None;
+ }
+}
+
+#[cfg(unix)]
+fn profiled() -> Criterion {
+ Criterion::default().with_profiler(CpuProfiler::default())
+}
+
+#[derive(Serialize)]
+struct DataWrapper {
+ v: String,
+}
+
+#[derive(Serialize)]
+struct RowWrapper {
+ real: Vec<DataWrapper>,
+ dummy: Vec<DataWrapper>,
+}
+
+#[derive(Serialize)]
+struct NestedRowWrapper {
+ parent: Vec<Vec<DataWrapper>>,
+}
+
+static SOURCE: &'static str = "<html>
+ <head>
+ <title>{{year}}</title>
+ </head>
+ <body>
+ <h1>CSL {{year}}</h1>
+ <ul>
+ {{#each teams}}
+ <li class=\"{{#if @first}}champion{{/if}}\">
+ <b>{{name}}</b>: {{score}}
+ </li>
+ {{/each}}
+ </ul>
+ </body>
+</html>";
+
+fn make_data() -> BTreeMap<String, Json> {
+ let mut data = BTreeMap::new();
+
+ data.insert("year".to_string(), to_json("2015"));
+
+ let mut teams = Vec::new();
+
+ for v in vec![
+ ("Jiangsu", 43u16),
+ ("Beijing", 27u16),
+ ("Guangzhou", 22u16),
+ ("Shandong", 12u16),
+ ]
+ .iter()
+ {
+ let (name, score) = *v;
+ let mut t = BTreeMap::new();
+ t.insert("name".to_string(), to_json(name));
+ t.insert("score".to_string(), to_json(score));
+ teams.push(t)
+ }
+
+ data.insert("teams".to_string(), to_json(&teams));
+ data
+}
+
+fn parse_template(c: &mut Criterion) {
+ c.bench_function("parse_template", move |b| {
+ b.iter(|| Template::compile(SOURCE).ok().unwrap())
+ });
+}
+
+fn render_template(c: &mut Criterion) {
+ let mut handlebars = Handlebars::new();
+ handlebars
+ .register_template_string("table", SOURCE)
+ .ok()
+ .expect("Invalid template format");
+
+ let ctx = Context::wraps(make_data()).unwrap();
+ c.bench_function("render_template", move |b| {
+ b.iter(|| handlebars.render_with_context("table", &ctx).ok().unwrap())
+ });
+}
+
+fn large_loop_helper(c: &mut Criterion) {
+ let mut handlebars = Handlebars::new();
+ handlebars
+ .register_template_string("test", "BEFORE\n{{#each real}}{{this.v}}{{/each}}AFTER")
+ .ok()
+ .expect("Invalid template format");
+
+ let real: Vec<DataWrapper> = (1..1000)
+ .map(|i| DataWrapper {
+ v: format!("n={}", i),
+ })
+ .collect();
+ let dummy: Vec<DataWrapper> = (1..1000)
+ .map(|i| DataWrapper {
+ v: format!("n={}", i),
+ })
+ .collect();
+ let rows = RowWrapper { real, dummy };
+
+ let ctx = Context::wraps(&rows).unwrap();
+ c.bench_function("large_loop_helper", move |b| {
+ b.iter(|| handlebars.render_with_context("test", &ctx).ok().unwrap())
+ });
+}
+
+fn large_loop_helper_with_context_creation(c: &mut Criterion) {
+ let mut handlebars = Handlebars::new();
+ handlebars
+ .register_template_string("test", "BEFORE\n{{#each real}}{{this.v}}{{/each}}AFTER")
+ .ok()
+ .expect("Invalid template format");
+
+ let real: Vec<DataWrapper> = (1..1000)
+ .map(|i| DataWrapper {
+ v: format!("n={}", i),
+ })
+ .collect();
+ let dummy: Vec<DataWrapper> = (1..1000)
+ .map(|i| DataWrapper {
+ v: format!("n={}", i),
+ })
+ .collect();
+ let rows = RowWrapper { real, dummy };
+
+ c.bench_function("large_loop_helper_with_context_creation", move |b| {
+ b.iter(|| handlebars.render("test", &rows).ok().unwrap())
+ });
+}
+
+fn large_nested_loop(c: &mut Criterion) {
+ let mut handlebars = Handlebars::new();
+ handlebars
+ .register_template_string(
+ "test",
+ "BEFORE\n{{#each parent as |child|}}{{#each child}}{{this.v}}{{/each}}{{/each}}AFTER",
+ )
+ .ok()
+ .expect("Invalid template format");
+
+ let parent: Vec<Vec<DataWrapper>> = (1..100)
+ .map(|_| {
+ (1..10)
+ .map(|v| DataWrapper {
+ v: format!("v={}", v),
+ })
+ .collect()
+ })
+ .collect();
+
+ let rows = NestedRowWrapper { parent };
+
+ let ctx = Context::wraps(&rows).unwrap();
+ c.bench_function("large_nested_loop", move |b| {
+ b.iter(|| handlebars.render_with_context("test", &ctx).ok().unwrap())
+ });
+}
+
+#[cfg(unix)]
+criterion_group!(
+ name = benches;
+ config = profiled();
+ targets = parse_template, render_template, large_loop_helper, large_loop_helper_with_context_creation,
+ large_nested_loop
+);
+
+#[cfg(not(unix))]
+criterion_group!(
+ benches,
+ parse_template,
+ render_template,
+ large_loop_helper,
+ large_loop_helper_with_context_creation,
+ large_nested_loop
+);
+
+criterion_main!(benches);
diff --git a/vendor/handlebars/build-wasm.sh b/vendor/handlebars/build-wasm.sh
new file mode 100755
index 000000000..d1c39854a
--- /dev/null
+++ b/vendor/handlebars/build-wasm.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+cargo build --target wasm32-wasi
+cp target/wasm32-wasi/debug/handlebars-cli.wasm wasm/
diff --git a/vendor/handlebars/examples/decorator.rs b/vendor/handlebars/examples/decorator.rs
new file mode 100644
index 000000000..81aa6fe8d
--- /dev/null
+++ b/vendor/handlebars/examples/decorator.rs
@@ -0,0 +1,195 @@
+extern crate env_logger;
+extern crate handlebars;
+#[macro_use]
+extern crate serde_derive;
+extern crate serde_json;
+use std::error::Error;
+
+use serde_json::value::{Map, Value as Json};
+
+use handlebars::{
+ to_json, Context, Decorator, Handlebars, Helper, JsonRender, Output, RenderContext, RenderError,
+};
+
+// default format helper
+fn format_helper(
+ h: &Helper,
+ _: &Handlebars,
+ _: &Context,
+ _: &mut RenderContext,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ // get parameter from helper or throw an error
+ let param = h
+ .param(0)
+ .ok_or(RenderError::new("Param 0 is required for format helper."))?;
+ let rendered = format!("{} pts", param.value().render());
+ out.write(rendered.as_ref())?;
+ Ok(())
+}
+
+// a decorator registers helpers
+fn format_decorator(
+ d: &Decorator,
+ _: &Handlebars,
+ _: &Context,
+ rc: &mut RenderContext,
+) -> Result<(), RenderError> {
+ let suffix = d
+ .param(0)
+ .map(|v| v.value().render())
+ .unwrap_or("".to_owned());
+ rc.register_local_helper(
+ "format",
+ Box::new(
+ move |h: &Helper,
+ _: &Handlebars,
+ _: &Context,
+ _: &mut RenderContext,
+ out: &mut dyn Output| {
+ // get parameter from helper or throw an error
+ let param = h
+ .param(0)
+ .ok_or(RenderError::new("Param 0 is required for format helper."))?;
+ let rendered = format!("{} {}", param.value().render(), suffix);
+ out.write(rendered.as_ref())?;
+ Ok(())
+ },
+ ),
+ );
+ Ok(())
+}
+
+// a decorator mutates current context data
+fn set_decorator(
+ d: &Decorator,
+ _: &Handlebars,
+ ctx: &Context,
+ rc: &mut RenderContext,
+) -> Result<(), RenderError> {
+ // get the input of decorator
+ let data_to_set = d.hash();
+ // retrieve the json value in current context
+ let ctx_data = ctx.data();
+
+ if let Json::Object(m) = ctx_data {
+ let mut new_ctx_data = m.clone();
+
+ for (k, v) in data_to_set {
+ new_ctx_data.insert(k.to_string(), v.value().clone());
+ }
+
+ rc.set_context(Context::wraps(new_ctx_data)?);
+ Ok(())
+ } else {
+ Err(RenderError::new("Cannot extend non-object data"))
+ }
+}
+
+// another custom helper
+fn rank_helper(
+ h: &Helper,
+ _: &Handlebars,
+ _: &Context,
+ _: &mut RenderContext,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ let rank = h
+ .param(0)
+ .and_then(|v| v.value().as_u64())
+ .ok_or(RenderError::new(
+ "Param 0 with u64 type is required for rank helper.",
+ ))? as usize;
+ let total = h
+ .param(1)
+ .as_ref()
+ .and_then(|v| v.value().as_array())
+ .map(|arr| arr.len())
+ .ok_or(RenderError::new(
+ "Param 1 with array type is required for rank helper",
+ ))?;
+ if rank == 0 {
+ out.write("champion")?;
+ } else if rank >= total - 2 {
+ out.write("relegation")?;
+ } else if rank <= 2 {
+ out.write("acl")?;
+ }
+ Ok(())
+}
+
+static TYPES: &'static str = "serde_json";
+
+// define some data
+#[derive(Serialize)]
+pub struct Team {
+ name: String,
+ pts: u16,
+}
+
+// produce some data
+pub fn make_data() -> Map<String, Json> {
+ let mut data = Map::new();
+
+ data.insert("year".to_string(), to_json("2015"));
+
+ let teams = vec![
+ Team {
+ name: "Jiangsu Suning".to_string(),
+ pts: 43u16,
+ },
+ Team {
+ name: "Shanghai SIPG".to_string(),
+ pts: 39u16,
+ },
+ Team {
+ name: "Hebei CFFC".to_string(),
+ pts: 27u16,
+ },
+ Team {
+ name: "Guangzhou Evergrand".to_string(),
+ pts: 22u16,
+ },
+ Team {
+ name: "Shandong Luneng".to_string(),
+ pts: 12u16,
+ },
+ Team {
+ name: "Beijing Guoan".to_string(),
+ pts: 7u16,
+ },
+ Team {
+ name: "Hangzhou Greentown".to_string(),
+ pts: 7u16,
+ },
+ Team {
+ name: "Shanghai Shenhua".to_string(),
+ pts: 4u16,
+ },
+ ];
+
+ data.insert("teams".to_string(), to_json(&teams));
+ data.insert("engine".to_string(), to_json(TYPES));
+ data
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+ env_logger::init();
+ // create the handlebars registry
+ let mut handlebars = Handlebars::new();
+
+ // register template from a file and assign a name to it
+ // deal with errors
+ handlebars.register_template_file("table", "./examples/decorator/template.hbs")?;
+
+ // register some custom helpers
+ handlebars.register_helper("format", Box::new(format_helper));
+ handlebars.register_helper("ranking_label", Box::new(rank_helper));
+ handlebars.register_decorator("format_suffix", Box::new(format_decorator));
+ handlebars.register_decorator("set", Box::new(set_decorator));
+
+ // make data and render it
+ let data = make_data();
+ println!("{}", handlebars.render("table", &data)?);
+ Ok(())
+}
diff --git a/vendor/handlebars/examples/decorator/template.hbs b/vendor/handlebars/examples/decorator/template.hbs
new file mode 100644
index 000000000..5b965182b
--- /dev/null
+++ b/vendor/handlebars/examples/decorator/template.hbs
@@ -0,0 +1,21 @@
+{{*format_suffix "分"}}
+
+<html>
+ <head>
+ <title>CSL {{year}}</title>
+ </head>
+ <body>
+ <h1>CSL {{year}}</h1>
+ <ul>
+ {{#each teams as |t|}}
+ <li class="{{ranking_label @index ../teams}}">
+ {{~log @index~}}
+ <b>{{t.name}}</b>: {{format t.pts ~}}
+ </li>
+ {{/each}}
+ </ul>
+
+ {{*set version="v3.0"}}
+ <p>Rendered by Handlebars {{version}} from {{engine}} data.</p>
+ </body>
+</html>
diff --git a/vendor/handlebars/examples/dev_mode.rs b/vendor/handlebars/examples/dev_mode.rs
new file mode 100644
index 000000000..99017474f
--- /dev/null
+++ b/vendor/handlebars/examples/dev_mode.rs
@@ -0,0 +1,27 @@
+use std::sync::Arc;
+
+use handlebars::Handlebars;
+use serde_json::json;
+use warp::{self, Filter};
+
+#[tokio::main]
+async fn main() {
+ let mut reg = Handlebars::new();
+ // enable dev mode for template reloading
+ reg.set_dev_mode(true);
+ // register a template from the file
+ // modified the file after the server starts to see things changing
+ reg.register_template_file("tpl", "./examples/dev_mode/template.hbs")
+ .unwrap();
+
+ let hbs = Arc::new(reg);
+ let route = warp::get().map(move || {
+ let result = hbs
+ .render("tpl", &json!({"model": "t14s", "brand": "Thinkpad"}))
+ .unwrap_or_else(|e| e.to_string());
+ warp::reply::html(result)
+ });
+
+ println!("Edit ./examples/dev_mode/template.hbs and request http://localhost:3030 to see the change on the run.");
+ warp::serve(route).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/vendor/handlebars/examples/dev_mode/template.hbs b/vendor/handlebars/examples/dev_mode/template.hbs
new file mode 100644
index 000000000..19bb80657
--- /dev/null
+++ b/vendor/handlebars/examples/dev_mode/template.hbs
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>My Laptop</title>
+ </head>
+ <body>
+ <p>My current laptop is {{brand}}: <b>{{model}}</b></p>
+ </body>
+</html>
diff --git a/vendor/handlebars/examples/error.rs b/vendor/handlebars/examples/error.rs
new file mode 100644
index 000000000..3fb874e2b
--- /dev/null
+++ b/vendor/handlebars/examples/error.rs
@@ -0,0 +1,40 @@
+extern crate env_logger;
+extern crate handlebars;
+#[macro_use]
+extern crate serde_json;
+
+use std::error::Error;
+
+use handlebars::Handlebars;
+
+fn main() -> Result<(), Box<dyn Error>> {
+ env_logger::init();
+ let mut handlebars = Handlebars::new();
+
+ // template not found
+ println!(
+ "{}",
+ handlebars
+ .register_template_file("notfound", "./examples/error/notfound.hbs")
+ .unwrap_err()
+ );
+
+ // an invalid templat
+ println!(
+ "{}",
+ handlebars
+ .register_template_file("error", "./examples/error/error.hbs")
+ .unwrap_err()
+ );
+
+ // render error
+ let e1 = handlebars
+ .render_template("{{#if}}", &json!({}))
+ .unwrap_err();
+ let be1 = Box::new(e1);
+ println!("{}", be1);
+ println!("{}", be1.source().unwrap());
+ println!("{:?}", be1.source().unwrap().source());
+
+ Ok(())
+}
diff --git a/vendor/handlebars/examples/error/error.hbs b/vendor/handlebars/examples/error/error.hbs
new file mode 100644
index 000000000..89537e71e
--- /dev/null
+++ b/vendor/handlebars/examples/error/error.hbs
@@ -0,0 +1,21 @@
+{{! this is an invalid template }}
+
+<html>
+ <head>
+ <title>中超联赛 {{year}}</title>
+ </head>
+ <body>
+ <h1>CSL {{year}}</h1>
+ <ul>
+ {{#each teams as |t| ~}}
+ <li class="{{ranking_label true ../teams}}">
+ {{~log @index~}}
+ <b>{{t.name}}</b>: {{format t.pts ~}}
+ </li>
+ {{! mismatched helper close tag }}
+ {{/arch~}}
+ </ul>
+
+ <p>Rendered by Handlebars from {{engine}} data.</p>
+ </body>
+</html>
diff --git a/vendor/handlebars/examples/error/template.hbs b/vendor/handlebars/examples/error/template.hbs
new file mode 100644
index 000000000..9ed5b7bde
--- /dev/null
+++ b/vendor/handlebars/examples/error/template.hbs
@@ -0,0 +1,19 @@
+<html>
+ <head>
+ <title>中超联赛 {{year}}</title>
+ </head>
+ <body>
+ <h1>CSL {{year}}</h1>
+ <ul>
+ {{#each teams as |t|}}
+ {{! ranking_label will produce a render error when first parameter is not a number }}
+ <li class="{{ranking_label true ../teams}}">
+ {{~log @index~}}
+ <b>{{t.name}}</b>: {{format t.pts ~}}
+ </li>
+ {{/each}}
+ </ul>
+
+ <p>Rendered by Handlebars from {{engine}} data.</p>
+ </body>
+</html>
diff --git a/vendor/handlebars/examples/partials.rs b/vendor/handlebars/examples/partials.rs
new file mode 100644
index 000000000..80b20c2f7
--- /dev/null
+++ b/vendor/handlebars/examples/partials.rs
@@ -0,0 +1,35 @@
+extern crate env_logger;
+extern crate handlebars;
+#[macro_use]
+extern crate maplit;
+
+use handlebars::Handlebars;
+use std::error::Error;
+
+fn main() -> Result<(), Box<dyn Error>> {
+ env_logger::init();
+ let mut handlebars = Handlebars::new();
+
+ handlebars.register_template_file("template", "./examples/partials/template2.hbs")?;
+
+ handlebars.register_template_file("base0", "./examples/partials/base0.hbs")?;
+ handlebars.register_template_file("base1", "./examples/partials/base1.hbs")?;
+
+ let data0 = btreemap! {
+ "title".to_string() => "example 0".to_string(),
+ "parent".to_string() => "base0".to_string()
+ };
+ let data1 = btreemap! {
+ "title".to_string() => "example 1".to_string(),
+ "parent".to_string() => "base1".to_string()
+ };
+
+ println!("Page 0");
+ println!("{}", handlebars.render("template", &data0)?);
+ println!("=======================================================");
+
+ println!("Page 1");
+ println!("{}", handlebars.render("template", &data1)?);
+
+ Ok(())
+}
diff --git a/vendor/handlebars/examples/partials/base0.hbs b/vendor/handlebars/examples/partials/base0.hbs
new file mode 100644
index 000000000..34b38e763
--- /dev/null
+++ b/vendor/handlebars/examples/partials/base0.hbs
@@ -0,0 +1,7 @@
+<html>
+ <head>{{title}}</head>
+ <body>
+ <div><h1>Derived from base0.hbs</h1></div>
+ {{> page}}
+ </body>
+</html>
diff --git a/vendor/handlebars/examples/partials/base1.hbs b/vendor/handlebars/examples/partials/base1.hbs
new file mode 100644
index 000000000..8f771dfad
--- /dev/null
+++ b/vendor/handlebars/examples/partials/base1.hbs
@@ -0,0 +1,7 @@
+<html>
+ <head>{{title}}</head>
+ <body>
+ <div><h1>Derived from base1.hbs</h1></div>
+ {{> page}}
+ </body>
+</html>
diff --git a/vendor/handlebars/examples/partials/template2.hbs b/vendor/handlebars/examples/partials/template2.hbs
new file mode 100644
index 000000000..934416eb7
--- /dev/null
+++ b/vendor/handlebars/examples/partials/template2.hbs
@@ -0,0 +1,4 @@
+{{#*inline "page"}}
+ <p>Rendered in partial, parent is {{parent}}</p>
+{{/inline}}
+{{> (lookup this "parent")}}
diff --git a/vendor/handlebars/examples/quick.rs b/vendor/handlebars/examples/quick.rs
new file mode 100644
index 000000000..ff1c94ca6
--- /dev/null
+++ b/vendor/handlebars/examples/quick.rs
@@ -0,0 +1,18 @@
+use std::error::Error;
+
+use handlebars::Handlebars;
+use serde_json::json;
+
+fn main() -> Result<(), Box<dyn Error>> {
+ let mut reg = Handlebars::new();
+ // render without register
+ println!(
+ "{}",
+ reg.render_template("Hello {{name}}", &json!({"name": "foo"}))?
+ );
+
+ // register template using given name
+ reg.register_template_string("tpl_1", "Good afternoon, {{name}}")?;
+ println!("{}", reg.render("tpl_1", &json!({"name": "foo"}))?);
+ Ok(())
+}
diff --git a/vendor/handlebars/examples/render.rs b/vendor/handlebars/examples/render.rs
new file mode 100644
index 000000000..112ec504d
--- /dev/null
+++ b/vendor/handlebars/examples/render.rs
@@ -0,0 +1,135 @@
+extern crate env_logger;
+extern crate handlebars;
+
+#[macro_use]
+extern crate serde_derive;
+extern crate serde_json;
+
+use serde_json::value::{Map, Value as Json};
+use std::error::Error;
+
+use handlebars::{
+ to_json, Context, Handlebars, Helper, JsonRender, Output, RenderContext, RenderError,
+};
+
+// define a custom helper
+fn format_helper(
+ h: &Helper,
+ _: &Handlebars,
+ _: &Context,
+ _: &mut RenderContext,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ // get parameter from helper or throw an error
+ let param = h
+ .param(0)
+ .ok_or(RenderError::new("Param 0 is required for format helper."))?;
+ let rendered = format!("{} pts", param.value().render());
+ out.write(rendered.as_ref())?;
+ Ok(())
+}
+
+// another custom helper
+fn rank_helper(
+ h: &Helper,
+ _: &Handlebars,
+ _: &Context,
+ _: &mut RenderContext,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ let rank = h
+ .param(0)
+ .and_then(|v| v.value().as_u64())
+ .ok_or(RenderError::new(
+ "Param 0 with u64 type is required for rank helper.",
+ ))? as usize;
+ let total = h
+ .param(1)
+ .as_ref()
+ .and_then(|v| v.value().as_array())
+ .map(|arr| arr.len())
+ .ok_or(RenderError::new(
+ "Param 1 with array type is required for rank helper",
+ ))?;
+ if rank == 0 {
+ out.write("champion")?;
+ } else if rank >= total - 2 {
+ out.write("relegation")?;
+ } else if rank <= 2 {
+ out.write("acl")?;
+ }
+ Ok(())
+}
+
+static TYPES: &'static str = "serde_json";
+
+// define some data
+#[derive(Serialize)]
+pub struct Team {
+ name: String,
+ pts: u16,
+}
+
+// produce some data
+pub fn make_data() -> Map<String, Json> {
+ let mut data = Map::new();
+
+ data.insert("year".to_string(), to_json("2015"));
+
+ let teams = vec![
+ Team {
+ name: "Jiangsu Suning".to_string(),
+ pts: 43u16,
+ },
+ Team {
+ name: "Shanghai SIPG".to_string(),
+ pts: 39u16,
+ },
+ Team {
+ name: "Hebei CFFC".to_string(),
+ pts: 27u16,
+ },
+ Team {
+ name: "Guangzhou Evergrand".to_string(),
+ pts: 22u16,
+ },
+ Team {
+ name: "Shandong Luneng".to_string(),
+ pts: 12u16,
+ },
+ Team {
+ name: "Beijing Guoan".to_string(),
+ pts: 7u16,
+ },
+ Team {
+ name: "Hangzhou Greentown".to_string(),
+ pts: 7u16,
+ },
+ Team {
+ name: "Shanghai Shenhua".to_string(),
+ pts: 4u16,
+ },
+ ];
+
+ data.insert("teams".to_string(), to_json(&teams));
+ data.insert("engine".to_string(), to_json(TYPES));
+ data
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+ env_logger::init();
+ // create the handlebars registry
+ let mut handlebars = Handlebars::new();
+
+ // register template from a file and assign a name to it
+ handlebars.register_template_file("table", "./examples/render/template.hbs")?;
+
+ // register some custom helpers
+ handlebars.register_helper("format", Box::new(format_helper));
+ handlebars.register_helper("ranking_label", Box::new(rank_helper));
+
+ // make data and render it
+ let data = make_data();
+ println!("{}", handlebars.render("table", &data)?);
+ Ok(())
+}
diff --git a/vendor/handlebars/examples/render/template.hbs b/vendor/handlebars/examples/render/template.hbs
new file mode 100644
index 000000000..2a62d014f
--- /dev/null
+++ b/vendor/handlebars/examples/render/template.hbs
@@ -0,0 +1,18 @@
+<html>
+ <head>
+ <title>中超联赛 {{year}}</title>
+ </head>
+ <body>
+ <h1>CSL {{year}}</h1>
+ <ul>
+ {{#each teams as |t|}}
+ <li class="{{ranking_label @index ../teams}}">
+ {{~log @index~}}
+ <b>{{t.name}}</b>: {{format t.pts ~}}
+ </li>
+ {{/each}}
+ </ul>
+
+ <p>Rendered by Handlebars from {{engine}} data.</p>
+ </body>
+</html>
diff --git a/vendor/handlebars/examples/render_cli/simple.hbs b/vendor/handlebars/examples/render_cli/simple.hbs
new file mode 100644
index 000000000..6b9e01247
--- /dev/null
+++ b/vendor/handlebars/examples/render_cli/simple.hbs
@@ -0,0 +1 @@
+Data: {{ . }}
diff --git a/vendor/handlebars/examples/render_file.rs b/vendor/handlebars/examples/render_file.rs
new file mode 100644
index 000000000..7b7599672
--- /dev/null
+++ b/vendor/handlebars/examples/render_file.rs
@@ -0,0 +1,140 @@
+#![allow(unused_imports, dead_code)]
+extern crate env_logger;
+extern crate handlebars;
+extern crate serde;
+#[macro_use]
+extern crate serde_derive;
+extern crate serde_json;
+use serde::Serialize;
+use serde_json::value::{self, Map, Value as Json};
+
+use std::error::Error;
+use std::fs::File;
+use std::io::{Read, Write};
+
+use handlebars::{
+ to_json, Context, Handlebars, Helper, JsonRender, Output, RenderContext, RenderError,
+};
+
+// define a custom helper
+fn format_helper(
+ h: &Helper,
+ _: &Handlebars,
+ _: &Context,
+ _: &mut RenderContext,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ let param = h
+ .param(0)
+ .ok_or(RenderError::new("Param 0 is required for format helper."))?;
+ let rendered = format!("{} pts", param.value().render());
+ out.write(rendered.as_ref())?;
+ Ok(())
+}
+
+// another custom helper
+fn rank_helper(
+ h: &Helper,
+ _: &Handlebars,
+ _: &Context,
+ _: &mut RenderContext,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ let rank = h
+ .param(0)
+ .and_then(|ref v| v.value().as_u64())
+ .ok_or(RenderError::new(
+ "Param 0 with u64 type is required for rank helper.",
+ ))? as usize;
+ let total = h
+ .param(1)
+ .as_ref()
+ .and_then(|v| v.value().as_array())
+ .map(|arr| arr.len())
+ .ok_or(RenderError::new(
+ "Param 1 with array type is required for rank helper",
+ ))?;
+ if rank == 0 {
+ out.write("champion")?;
+ } else if rank >= total - 2 {
+ out.write("relegation")?;
+ } else if rank <= 2 {
+ out.write("acl")?;
+ }
+ Ok(())
+}
+
+static TYPES: &'static str = "serde_json";
+
+// define some data
+#[derive(Serialize)]
+pub struct Team {
+ name: String,
+ pts: u16,
+}
+
+// produce some data
+pub fn make_data() -> Map<String, Json> {
+ let mut data = Map::new();
+
+ data.insert("year".to_string(), to_json("2015"));
+
+ let teams = vec![
+ Team {
+ name: "Jiangsu Suning".to_string(),
+ pts: 43u16,
+ },
+ Team {
+ name: "Shanghai SIPG".to_string(),
+ pts: 39u16,
+ },
+ Team {
+ name: "Hebei CFFC".to_string(),
+ pts: 27u16,
+ },
+ Team {
+ name: "Guangzhou Evergrand".to_string(),
+ pts: 22u16,
+ },
+ Team {
+ name: "Shandong Luneng".to_string(),
+ pts: 12u16,
+ },
+ Team {
+ name: "Beijing Guoan".to_string(),
+ pts: 7u16,
+ },
+ Team {
+ name: "Hangzhou Greentown".to_string(),
+ pts: 7u16,
+ },
+ Team {
+ name: "Shanghai Shenhua".to_string(),
+ pts: 4u16,
+ },
+ ];
+
+ data.insert("teams".to_string(), to_json(&teams));
+ data.insert("engine".to_string(), to_json(TYPES));
+ data
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+ env_logger::init();
+ let mut handlebars = Handlebars::new();
+
+ handlebars.register_helper("format", Box::new(format_helper));
+ handlebars.register_helper("ranking_label", Box::new(rank_helper));
+ // handlebars.register_helper("format", Box::new(FORMAT_HELPER));
+
+ let data = make_data();
+
+ handlebars
+ .register_template_file("template", "./examples/render_file/template.hbs")
+ .unwrap();
+
+ let mut output_file = File::create("target/table.html")?;
+ handlebars.render_to_write("template", &data, &mut output_file)?;
+ println!("target/table.html generated");
+ Ok(())
+}
diff --git a/vendor/handlebars/examples/render_file/template.hbs b/vendor/handlebars/examples/render_file/template.hbs
new file mode 100644
index 000000000..84a0d6bc0
--- /dev/null
+++ b/vendor/handlebars/examples/render_file/template.hbs
@@ -0,0 +1,19 @@
+<html>
+ <head>
+ <title>中超联赛 {{year}}</title>
+ </head>
+ <body>
+ <h1>CSL {{year}}</h1>
+ <ul>
+ {{#each teams as |t| }}
+ <li class="{{ranking_label @index ../teams}}">
+ {{~log @index~}}
+ {{!-- I'm comment --}}
+ <b>{{t.name}}</b>: {{format t.pts ~}}
+ </li>
+ {{/each}}
+ </ul>
+
+ <p>Rendered by Handlebars from {{engine}} data.</p>
+ </body>
+</html>
diff --git a/vendor/handlebars/examples/script.rs b/vendor/handlebars/examples/script.rs
new file mode 100644
index 000000000..bedd426cd
--- /dev/null
+++ b/vendor/handlebars/examples/script.rs
@@ -0,0 +1,39 @@
+#![allow(unused_imports)]
+
+use handlebars::Handlebars;
+use std::error::Error;
+#[macro_use]
+extern crate serde_json;
+
+#[cfg(feature = "script_helper")]
+fn main() -> Result<(), Box<dyn Error>> {
+ let mut handlebars = Handlebars::new();
+
+ handlebars.register_template_file("tpl", "./examples/script/template.hbs")?;
+ handlebars.register_script_helper_file("score", "./examples/script/goals.rhai")?;
+
+ let data = json! {[
+ [{
+ "name": "Dortmund",
+ "goals": ["Haaland", "Guerreiro", "Hazard", "Guerreiro"]
+ }, {
+ "name": "Schalke",
+ "goals": []
+ }],
+ [{
+ "name": "RB Leipzig",
+ "goals": ["Poulsen"]
+ }, {
+ "name": "SC Feriburg",
+ "goals": ["Gulde"]
+ }]
+ ]};
+ println!("{}", handlebars.render("tpl", &data)?);
+ Ok(())
+}
+
+#[cfg(not(feature = "script_helper"))]
+fn main() -> Result<(), Box<dyn Error>> {
+ println!("Please enable feature flag script_helper for this example");
+ Ok(())
+}
diff --git a/vendor/handlebars/examples/script/goals.rhai b/vendor/handlebars/examples/script/goals.rhai
new file mode 100644
index 000000000..65828202e
--- /dev/null
+++ b/vendor/handlebars/examples/script/goals.rhai
@@ -0,0 +1,3 @@
+let goals = params[0];
+
+goals.len()
diff --git a/vendor/handlebars/examples/script/template.hbs b/vendor/handlebars/examples/script/template.hbs
new file mode 100644
index 000000000..c04253c72
--- /dev/null
+++ b/vendor/handlebars/examples/script/template.hbs
@@ -0,0 +1,10 @@
+Bundesliga Match Day
+{{#each this as |match|}}
+ {{#each match as |team|}}
+ {{team.name}} - {{score team.goals}}
+ {{#each team.goals as |scorer|}}
+ > {{scorer}}
+ {{/each}}
+ {{/each}}
+ ---
+{{/each}}
diff --git a/vendor/handlebars/profile.sh b/vendor/handlebars/profile.sh
new file mode 100755
index 000000000..5862c87e4
--- /dev/null
+++ b/vendor/handlebars/profile.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+RUSTCFLAGS=-g cargo bench --bench bench -- --profile-time 15
diff --git a/vendor/handlebars/release.toml b/vendor/handlebars/release.toml
new file mode 100644
index 000000000..e0f365aa1
--- /dev/null
+++ b/vendor/handlebars/release.toml
@@ -0,0 +1,6 @@
+sign-commit = true
+pre-release-replacements = [
+ {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", prerelease=false},
+ {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", prerelease=false},
+ {file="src/lib.rs", search="https://docs.rs/handlebars/[a-z0-9\\.-]+", replace="https://docs.rs/handlebars/{{version}}"},
+]
diff --git a/vendor/handlebars/rustfmt.toml b/vendor/handlebars/rustfmt.toml
new file mode 100644
index 000000000..828232f4a
--- /dev/null
+++ b/vendor/handlebars/rustfmt.toml
@@ -0,0 +1 @@
+format_strings = false
diff --git a/vendor/handlebars/src/block.rs b/vendor/handlebars/src/block.rs
new file mode 100644
index 000000000..1c520bf5b
--- /dev/null
+++ b/vendor/handlebars/src/block.rs
@@ -0,0 +1,126 @@
+use std::collections::BTreeMap;
+
+use serde_json::value::Value as Json;
+
+use crate::error::RenderError;
+use crate::local_vars::LocalVars;
+
+#[derive(Clone, Debug)]
+pub enum BlockParamHolder {
+ // a reference to certain context value
+ Path(Vec<String>),
+ // an actual value holder
+ Value(Json),
+}
+
+impl BlockParamHolder {
+ pub fn value(v: Json) -> BlockParamHolder {
+ BlockParamHolder::Value(v)
+ }
+
+ pub fn path(r: Vec<String>) -> BlockParamHolder {
+ BlockParamHolder::Path(r)
+ }
+}
+
+/// A map holds block parameters. The parameter can be either a value or a reference
+#[derive(Clone, Debug, Default)]
+pub struct BlockParams<'reg> {
+ data: BTreeMap<&'reg str, BlockParamHolder>,
+}
+
+impl<'reg> BlockParams<'reg> {
+ /// Create a empty block parameter map.
+ pub fn new() -> BlockParams<'reg> {
+ BlockParams::default()
+ }
+
+ /// Add a path reference as the parameter. The `path` is a vector of path
+ /// segments the relative to current block's base path.
+ pub fn add_path(&mut self, k: &'reg str, path: Vec<String>) -> Result<(), RenderError> {
+ self.data.insert(k, BlockParamHolder::path(path));
+ Ok(())
+ }
+
+ /// Add a value as parameter.
+ pub fn add_value(&mut self, k: &'reg str, v: Json) -> Result<(), RenderError> {
+ self.data.insert(k, BlockParamHolder::value(v));
+ Ok(())
+ }
+
+ /// Get a block parameter by its name.
+ pub fn get(&self, k: &str) -> Option<&BlockParamHolder> {
+ self.data.get(k)
+ }
+}
+
+/// A data structure holds contextual data for current block scope.
+#[derive(Debug, Clone, Default)]
+pub struct BlockContext<'reg> {
+ /// the base_path of current block scope
+ base_path: Vec<String>,
+ /// the base_value of current block scope, when the block is using a
+ /// constant or derived value as block base
+ base_value: Option<Json>,
+ /// current block context variables
+ block_params: BlockParams<'reg>,
+ /// local variables in current context
+ local_variables: LocalVars,
+}
+
+impl<'reg> BlockContext<'reg> {
+ /// create a new `BlockContext` with default data
+ pub fn new() -> BlockContext<'reg> {
+ BlockContext::default()
+ }
+
+ /// set a local variable into current scope
+ pub fn set_local_var(&mut self, name: &str, value: Json) {
+ self.local_variables.put(name, value);
+ }
+
+ /// get a local variable from current scope
+ pub fn get_local_var(&self, name: &str) -> Option<&Json> {
+ self.local_variables.get(name)
+ }
+
+ /// borrow a reference to current scope's base path
+ /// all paths inside this block will be relative to this path
+ pub fn base_path(&self) -> &Vec<String> {
+ &self.base_path
+ }
+
+ /// borrow a mutable reference to the base path
+ pub fn base_path_mut(&mut self) -> &mut Vec<String> {
+ &mut self.base_path
+ }
+
+ /// borrow the base value
+ pub fn base_value(&self) -> Option<&Json> {
+ self.base_value.as_ref()
+ }
+
+ /// set the base value
+ pub fn set_base_value(&mut self, value: Json) {
+ self.base_value = Some(value);
+ }
+
+ /// Get a block parameter from this block.
+ /// Block parameters needed to be supported by the block helper.
+ /// The typical syntax for block parameter is:
+ ///
+ /// ```skip
+ /// {{#myblock param1 as |block_param1|}}
+ /// ...
+ /// {{/myblock}}
+ /// ```
+ ///
+ pub fn get_block_param(&self, block_param_name: &str) -> Option<&BlockParamHolder> {
+ self.block_params.get(block_param_name)
+ }
+
+ /// Set a block parameter into this block.
+ pub fn set_block_params(&mut self, block_params: BlockParams<'reg>) {
+ self.block_params = block_params;
+ }
+}
diff --git a/vendor/handlebars/src/cli.rs b/vendor/handlebars/src/cli.rs
new file mode 100644
index 000000000..571900da2
--- /dev/null
+++ b/vendor/handlebars/src/cli.rs
@@ -0,0 +1,51 @@
+use std::env;
+use std::fs;
+use std::process;
+use std::str::FromStr;
+
+use serde_json::value::Value as Json;
+
+use handlebars::Handlebars;
+
+fn usage() -> ! {
+ eprintln!("Usage: handlebars-cli template.hbs '{{\"json\": \"data\"}}'");
+ process::exit(1);
+}
+
+fn parse_json(text: &str) -> Json {
+ let result = if let Some(text) = text.strip_prefix('@') {
+ fs::read_to_string(text).unwrap()
+ } else {
+ text.to_owned()
+ };
+ match Json::from_str(&result) {
+ Ok(json) => json,
+ Err(_) => usage(),
+ }
+}
+
+fn main() {
+ let mut args = env::args();
+ args.next(); // skip own filename
+ let (filename, json) = match (args.next(), args.next()) {
+ (Some(filename), Some(json)) => (filename, json),
+ _ => usage(),
+ };
+ let data = parse_json(&json);
+
+ let mut handlebars = Handlebars::new();
+
+ handlebars
+ .register_template_file(&filename, &filename)
+ .ok()
+ .unwrap();
+ match handlebars.render(&filename, &data) {
+ Ok(data) => {
+ println!("{}", data);
+ }
+ Err(e) => {
+ println!("Error rendering {}: {}", filename, e);
+ process::exit(2);
+ }
+ }
+}
diff --git a/vendor/handlebars/src/context.rs b/vendor/handlebars/src/context.rs
new file mode 100644
index 000000000..10e15fd90
--- /dev/null
+++ b/vendor/handlebars/src/context.rs
@@ -0,0 +1,453 @@
+use std::collections::{HashMap, VecDeque};
+
+use serde::Serialize;
+use serde_json::value::{to_value, Map, Value as Json};
+
+use crate::block::{BlockContext, BlockParamHolder};
+use crate::error::RenderError;
+use crate::grammar::Rule;
+use crate::json::path::*;
+use crate::json::value::ScopedJson;
+use crate::util::extend;
+
+pub type Object = HashMap<String, Json>;
+
+/// The context wrap data you render on your templates.
+///
+#[derive(Debug, Clone)]
+pub struct Context {
+ data: Json,
+}
+
+#[derive(Debug)]
+enum ResolvedPath<'a> {
+ // FIXME: change to borrowed when possible
+ // full path
+ AbsolutePath(Vec<String>),
+ // relative path and path root
+ RelativePath(Vec<String>),
+ // relative path against block param value
+ BlockParamValue(Vec<String>, &'a Json),
+ // relative path against derived value,
+ LocalValue(Vec<String>, &'a Json),
+}
+
+fn parse_json_visitor<'a, 'reg>(
+ relative_path: &[PathSeg],
+ block_contexts: &'a VecDeque<BlockContext<'reg>>,
+ always_for_absolute_path: bool,
+) -> ResolvedPath<'a> {
+ let mut path_context_depth: i64 = 0;
+ let mut with_block_param = None;
+ let mut from_root = false;
+
+ // peek relative_path for block param, @root and "../../"
+ for path_seg in relative_path {
+ match path_seg {
+ PathSeg::Named(the_path) => {
+ if let Some((holder, base_path)) = get_in_block_params(&block_contexts, the_path) {
+ with_block_param = Some((holder, base_path));
+ }
+ break;
+ }
+ PathSeg::Ruled(the_rule) => match the_rule {
+ Rule::path_root => {
+ from_root = true;
+ break;
+ }
+ Rule::path_up => path_context_depth += 1,
+ _ => break,
+ },
+ }
+ }
+
+ let mut path_stack = Vec::with_capacity(relative_path.len() + 5);
+ match with_block_param {
+ Some((BlockParamHolder::Value(ref value), _)) => {
+ merge_json_path(&mut path_stack, &relative_path[1..]);
+ ResolvedPath::BlockParamValue(path_stack, value)
+ }
+ Some((BlockParamHolder::Path(ref paths), base_path)) => {
+ extend(&mut path_stack, base_path);
+ if !paths.is_empty() {
+ extend(&mut path_stack, paths);
+ }
+ merge_json_path(&mut path_stack, &relative_path[1..]);
+
+ ResolvedPath::AbsolutePath(path_stack)
+ }
+ None => {
+ if path_context_depth > 0 {
+ let blk = block_contexts
+ .get(path_context_depth as usize)
+ .or_else(|| block_contexts.front());
+
+ if let Some(base_value) = blk.and_then(|blk| blk.base_value()) {
+ merge_json_path(&mut path_stack, relative_path);
+ ResolvedPath::LocalValue(path_stack, base_value)
+ } else {
+ if let Some(base_path) = blk.map(|blk| blk.base_path()) {
+ extend(&mut path_stack, base_path);
+ }
+ merge_json_path(&mut path_stack, relative_path);
+ ResolvedPath::AbsolutePath(path_stack)
+ }
+ } else if from_root {
+ merge_json_path(&mut path_stack, relative_path);
+ ResolvedPath::AbsolutePath(path_stack)
+ } else if always_for_absolute_path {
+ if let Some(base_value) = block_contexts.front().and_then(|blk| blk.base_value()) {
+ merge_json_path(&mut path_stack, relative_path);
+ ResolvedPath::LocalValue(path_stack, base_value)
+ } else {
+ if let Some(base_path) = block_contexts.front().map(|blk| blk.base_path()) {
+ extend(&mut path_stack, base_path);
+ }
+ merge_json_path(&mut path_stack, relative_path);
+ ResolvedPath::AbsolutePath(path_stack)
+ }
+ } else {
+ merge_json_path(&mut path_stack, relative_path);
+ ResolvedPath::RelativePath(path_stack)
+ }
+ }
+ }
+}
+
+fn get_data<'a>(d: Option<&'a Json>, p: &str) -> Result<Option<&'a Json>, RenderError> {
+ let result = match d {
+ Some(&Json::Array(ref l)) => p.parse::<usize>().map(|idx_u| l.get(idx_u))?,
+ Some(&Json::Object(ref m)) => m.get(p),
+ Some(_) => None,
+ None => None,
+ };
+ Ok(result)
+}
+
+fn get_in_block_params<'a, 'reg>(
+ block_contexts: &'a VecDeque<BlockContext<'reg>>,
+ p: &str,
+) -> Option<(&'a BlockParamHolder, &'a Vec<String>)> {
+ for bc in block_contexts {
+ let v = bc.get_block_param(p);
+ if v.is_some() {
+ return v.map(|v| (v, bc.base_path()));
+ }
+ }
+
+ None
+}
+
+pub(crate) fn merge_json(base: &Json, addition: &HashMap<&str, &Json>) -> Json {
+ let mut base_map = match base {
+ Json::Object(ref m) => m.clone(),
+ _ => Map::new(),
+ };
+
+ for (k, v) in addition.iter() {
+ base_map.insert(k.to_string(), (*v).clone());
+ }
+
+ Json::Object(base_map)
+}
+
+impl Context {
+ /// Create a context with null data
+ pub fn null() -> Context {
+ Context { data: Json::Null }
+ }
+
+ /// Create a context with given data
+ pub fn wraps<T: Serialize>(e: T) -> Result<Context, RenderError> {
+ to_value(e)
+ .map_err(RenderError::from)
+ .map(|d| Context { data: d })
+ }
+
+ /// Navigate the context with relative path and block scopes
+ pub(crate) fn navigate<'reg, 'rc>(
+ &'rc self,
+ relative_path: &[PathSeg],
+ block_contexts: &VecDeque<BlockContext<'reg>>,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ // always use absolute at the moment until we get base_value lifetime issue fixed
+ let resolved_visitor = parse_json_visitor(&relative_path, block_contexts, true);
+
+ // debug logging
+ debug!("Accessing context value: {:?}", resolved_visitor);
+
+ match resolved_visitor {
+ ResolvedPath::AbsolutePath(paths) => {
+ let mut ptr = Some(self.data());
+ for p in paths.iter() {
+ ptr = get_data(ptr, p)?;
+ }
+
+ Ok(ptr
+ .map(|v| ScopedJson::Context(v, paths))
+ .unwrap_or_else(|| ScopedJson::Missing))
+ }
+ ResolvedPath::RelativePath(_paths) => {
+ // relative path is disabled for now
+ unreachable!()
+ // let mut ptr = block_contexts.front().and_then(|blk| blk.base_value());
+ // for p in paths.iter() {
+ // ptr = get_data(ptr, p)?;
+ // }
+
+ // Ok(ptr
+ // .map(|v| ScopedJson::Context(v, paths))
+ // .unwrap_or_else(|| ScopedJson::Missing))
+ }
+ ResolvedPath::BlockParamValue(paths, value)
+ | ResolvedPath::LocalValue(paths, value) => {
+ let mut ptr = Some(value);
+ for p in paths.iter() {
+ ptr = get_data(ptr, p)?;
+ }
+ Ok(ptr
+ .map(|v| ScopedJson::Derived(v.clone()))
+ .unwrap_or_else(|| ScopedJson::Missing))
+ }
+ }
+ }
+
+ /// Return the Json data wrapped in context
+ pub fn data(&self) -> &Json {
+ &self.data
+ }
+
+ /// Return the mutable reference to Json data wrapped in context
+ pub fn data_mut(&mut self) -> &mut Json {
+ &mut self.data
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::block::{BlockContext, BlockParams};
+ use crate::context::{self, Context};
+ use crate::error::RenderError;
+ use crate::json::path::Path;
+ use crate::json::value::{self, ScopedJson};
+ use serde_json::value::Map;
+ use std::collections::{HashMap, VecDeque};
+
+ fn navigate_from_root<'reg, 'rc>(
+ ctx: &'rc Context,
+ path: &str,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ let relative_path = Path::parse(path).unwrap();
+ ctx.navigate(relative_path.segs().unwrap(), &VecDeque::new())
+ }
+
+ #[derive(Serialize)]
+ struct Address {
+ city: String,
+ country: String,
+ }
+
+ #[derive(Serialize)]
+ struct Person {
+ name: String,
+ age: i16,
+ addr: Address,
+ titles: Vec<String>,
+ }
+
+ #[test]
+ fn test_render() {
+ let v = "hello";
+ let ctx = Context::wraps(&v.to_string()).unwrap();
+ assert_eq!(
+ navigate_from_root(&ctx, "this").unwrap().render(),
+ v.to_string()
+ );
+ }
+
+ #[test]
+ fn test_navigation() {
+ let addr = Address {
+ city: "Beijing".to_string(),
+ country: "China".to_string(),
+ };
+
+ let person = Person {
+ name: "Ning Sun".to_string(),
+ age: 27,
+ addr,
+ titles: vec!["programmer".to_string(), "cartographer".to_string()],
+ };
+
+ let ctx = Context::wraps(&person).unwrap();
+ assert_eq!(
+ navigate_from_root(&ctx, "./addr/country").unwrap().render(),
+ "China".to_string()
+ );
+ assert_eq!(
+ navigate_from_root(&ctx, "addr.[country]").unwrap().render(),
+ "China".to_string()
+ );
+
+ let v = true;
+ let ctx2 = Context::wraps(&v).unwrap();
+ assert_eq!(
+ navigate_from_root(&ctx2, "this").unwrap().render(),
+ "true".to_string()
+ );
+
+ assert_eq!(
+ navigate_from_root(&ctx, "titles.[0]").unwrap().render(),
+ "programmer".to_string()
+ );
+
+ assert_eq!(
+ navigate_from_root(&ctx, "age").unwrap().render(),
+ "27".to_string()
+ );
+ }
+
+ #[test]
+ fn test_this() {
+ let mut map_with_this = Map::new();
+ map_with_this.insert("this".to_string(), value::to_json("hello"));
+ map_with_this.insert("age".to_string(), value::to_json(5usize));
+ let ctx1 = Context::wraps(&map_with_this).unwrap();
+
+ let mut map_without_this = Map::new();
+ map_without_this.insert("age".to_string(), value::to_json(4usize));
+ let ctx2 = Context::wraps(&map_without_this).unwrap();
+
+ assert_eq!(
+ navigate_from_root(&ctx1, "this").unwrap().render(),
+ "[object]".to_owned()
+ );
+ assert_eq!(
+ navigate_from_root(&ctx2, "age").unwrap().render(),
+ "4".to_owned()
+ );
+ }
+
+ #[test]
+ fn test_merge_json() {
+ let map = json!({ "age": 4 });
+ let s = "hello".to_owned();
+ let mut hash = HashMap::new();
+ let v = value::to_json("h1");
+ hash.insert("tag", &v);
+
+ let ctx_a1 = Context::wraps(&context::merge_json(&map, &hash)).unwrap();
+ assert_eq!(
+ navigate_from_root(&ctx_a1, "age").unwrap().render(),
+ "4".to_owned()
+ );
+ assert_eq!(
+ navigate_from_root(&ctx_a1, "tag").unwrap().render(),
+ "h1".to_owned()
+ );
+
+ let ctx_a2 = Context::wraps(&context::merge_json(&value::to_json(s), &hash)).unwrap();
+ assert_eq!(
+ navigate_from_root(&ctx_a2, "this").unwrap().render(),
+ "[object]".to_owned()
+ );
+ assert_eq!(
+ navigate_from_root(&ctx_a2, "tag").unwrap().render(),
+ "h1".to_owned()
+ );
+ }
+
+ #[test]
+ fn test_key_name_with_this() {
+ let m = btreemap! {
+ "this_name".to_string() => "the_value".to_string()
+ };
+ let ctx = Context::wraps(&m).unwrap();
+ assert_eq!(
+ navigate_from_root(&ctx, "this_name").unwrap().render(),
+ "the_value".to_string()
+ );
+ }
+
+ use serde::ser::Error as SerdeError;
+ use serde::{Serialize, Serializer};
+
+ struct UnserializableType {}
+
+ impl Serialize for UnserializableType {
+ fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ Err(SerdeError::custom("test"))
+ }
+ }
+
+ #[test]
+ fn test_serialize_error() {
+ let d = UnserializableType {};
+ assert!(Context::wraps(&d).is_err());
+ }
+
+ #[test]
+ fn test_root() {
+ let m = json!({
+ "a" : {
+ "b" : {
+ "c" : {
+ "d" : 1
+ }
+ }
+ },
+ "b": 2
+ });
+ let ctx = Context::wraps(&m).unwrap();
+ let mut block = BlockContext::new();
+ *block.base_path_mut() = ["a".to_owned(), "b".to_owned()].to_vec();
+
+ let mut blocks = VecDeque::new();
+ blocks.push_front(block);
+
+ assert_eq!(
+ ctx.navigate(&Path::parse("@root/b").unwrap().segs().unwrap(), &blocks)
+ .unwrap()
+ .render(),
+ "2".to_string()
+ );
+ }
+
+ #[test]
+ fn test_block_params() {
+ let m = json!([{
+ "a": [1, 2]
+ }, {
+ "b": [2, 3]
+ }]);
+
+ let ctx = Context::wraps(&m).unwrap();
+ let mut block_params = BlockParams::new();
+ block_params
+ .add_path("z", ["0".to_owned(), "a".to_owned()].to_vec())
+ .unwrap();
+ block_params.add_value("t", json!("good")).unwrap();
+
+ let mut block = BlockContext::new();
+ block.set_block_params(block_params);
+
+ let mut blocks = VecDeque::new();
+ blocks.push_front(block);
+
+ assert_eq!(
+ ctx.navigate(&Path::parse("z.[1]").unwrap().segs().unwrap(), &blocks)
+ .unwrap()
+ .render(),
+ "2".to_string()
+ );
+ assert_eq!(
+ ctx.navigate(&Path::parse("t").unwrap().segs().unwrap(), &blocks)
+ .unwrap()
+ .render(),
+ "good".to_string()
+ );
+ }
+}
diff --git a/vendor/handlebars/src/decorators/inline.rs b/vendor/handlebars/src/decorators/inline.rs
new file mode 100644
index 000000000..373abbc7b
--- /dev/null
+++ b/vendor/handlebars/src/decorators/inline.rs
@@ -0,0 +1,64 @@
+use crate::context::Context;
+use crate::decorators::{DecoratorDef, DecoratorResult};
+use crate::error::RenderError;
+use crate::registry::Registry;
+use crate::render::{Decorator, RenderContext};
+
+#[derive(Clone, Copy)]
+pub struct InlineDecorator;
+
+fn get_name<'reg: 'rc, 'rc>(d: &Decorator<'reg, 'rc>) -> Result<String, RenderError> {
+ d.param(0)
+ .ok_or_else(|| RenderError::new("Param required for decorator \"inline\""))
+ .and_then(|v| {
+ v.value()
+ .as_str()
+ .map(|v| v.to_owned())
+ .ok_or_else(|| RenderError::new("inline name must be string"))
+ })
+}
+
+impl DecoratorDef for InlineDecorator {
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ d: &Decorator<'reg, 'rc>,
+ _: &'reg Registry<'reg>,
+ _: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> DecoratorResult {
+ let name = get_name(d)?;
+
+ let template = d
+ .template()
+ .ok_or_else(|| RenderError::new("inline should have a block"))?;
+
+ rc.set_partial(name, template);
+ Ok(())
+ }
+}
+
+pub static INLINE_DECORATOR: InlineDecorator = InlineDecorator;
+
+#[cfg(test)]
+mod test {
+ use crate::context::Context;
+ use crate::registry::Registry;
+ use crate::render::{Evaluable, RenderContext};
+ use crate::template::Template;
+
+ #[test]
+ fn test_inline() {
+ let t0 =
+ Template::compile("{{#*inline \"hello\"}}the hello world inline partial.{{/inline}}")
+ .ok()
+ .unwrap();
+
+ let hbs = Registry::new();
+
+ let ctx = Context::null();
+ let mut rc = RenderContext::new(None);
+ t0.elements[0].eval(&hbs, &ctx, &mut rc).unwrap();
+
+ assert!(rc.get_partial(&"hello".to_owned()).is_some());
+ }
+}
diff --git a/vendor/handlebars/src/decorators/mod.rs b/vendor/handlebars/src/decorators/mod.rs
new file mode 100644
index 000000000..b8bad900f
--- /dev/null
+++ b/vendor/handlebars/src/decorators/mod.rs
@@ -0,0 +1,300 @@
+use crate::context::Context;
+use crate::error::RenderError;
+use crate::registry::Registry;
+use crate::render::{Decorator, RenderContext};
+
+pub use self::inline::INLINE_DECORATOR;
+
+pub type DecoratorResult = Result<(), RenderError>;
+
+/// Decorator Definition
+///
+/// Implement this trait to define your own decorators. Currently decorator
+/// shares same definition with helper.
+///
+/// In handlebars, it is recommended to use decorator to change context data and update helper
+/// definition.
+/// ## Updating context data
+///
+/// In decorator, you can change some context data you are about to render.
+///
+/// ```
+/// use handlebars::*;
+///
+/// fn update_data<'reg: 'rc, 'rc>(_: &Decorator, _: &Handlebars, ctx: &Context, rc: &mut RenderContext)
+/// -> Result<(), RenderError> {
+/// // modify json object
+/// let mut new_ctx = ctx.clone();
+/// {
+/// let mut data = new_ctx.data_mut();
+/// if let Some(ref mut m) = data.as_object_mut() {
+/// m.insert("hello".to_string(), to_json("world"));
+/// }
+/// }
+/// rc.set_context(new_ctx);
+/// Ok(())
+/// }
+///
+/// ```
+///
+/// ## Define local helper
+///
+/// You can override behavior of a helper from position of decorator to the end of template.
+///
+/// ```
+/// use handlebars::*;
+///
+/// fn override_helper(_: &Decorator, _: &Handlebars, _: &Context, rc: &mut RenderContext)
+/// -> Result<(), RenderError> {
+/// let new_helper = |h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output|
+/// -> Result<(), RenderError> {
+/// // your helper logic
+/// Ok(())
+/// };
+/// rc.register_local_helper("distance", Box::new(new_helper));
+/// Ok(())
+/// }
+/// ```
+///
+pub trait DecoratorDef {
+ fn call<'reg: 'rc, 'rc>(
+ &'reg self,
+ d: &Decorator<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> DecoratorResult;
+}
+
+/// Implement DecoratorDef for bare function so we can use function as decorator
+impl<
+ F: for<'reg, 'rc> Fn(
+ &Decorator<'reg, 'rc>,
+ &'reg Registry<'reg>,
+ &'rc Context,
+ &mut RenderContext<'reg, 'rc>,
+ ) -> DecoratorResult,
+ > DecoratorDef for F
+{
+ fn call<'reg: 'rc, 'rc>(
+ &'reg self,
+ d: &Decorator<'reg, 'rc>,
+ reg: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> DecoratorResult {
+ (*self)(d, reg, ctx, rc)
+ }
+}
+
+mod inline;
+
+#[cfg(test)]
+mod test {
+ use crate::context::Context;
+ use crate::error::RenderError;
+ use crate::json::value::{as_string, to_json};
+ use crate::output::Output;
+ use crate::registry::Registry;
+ use crate::render::{Decorator, Helper, RenderContext};
+
+ #[test]
+ fn test_register_decorator() {
+ let mut handlebars = Registry::new();
+ handlebars
+ .register_template_string("t0", "{{*foo}}".to_string())
+ .unwrap();
+
+ let data = btreemap! {
+ "hello".to_string() => "world".to_string()
+ };
+
+ assert!(handlebars.render("t0", &data).is_err());
+
+ handlebars.register_decorator(
+ "foo",
+ Box::new(
+ |_: &Decorator<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>|
+ -> Result<(), RenderError> { Ok(()) },
+ ),
+ );
+ assert_eq!(handlebars.render("t0", &data).ok().unwrap(), "".to_string());
+ }
+
+ // updating context data disabled for now
+ #[test]
+ fn test_update_data_with_decorator() {
+ let mut handlebars = Registry::new();
+ handlebars
+ .register_template_string("t0", "{{hello}}{{*foo}}{{hello}}".to_string())
+ .unwrap();
+
+ let data = btreemap! {
+ "hello".to_string() => "world".to_string()
+ };
+
+ handlebars.register_decorator(
+ "foo",
+ Box::new(
+ |_: &Decorator<'_, '_>,
+ _: &Registry<'_>,
+ ctx: &Context,
+ rc: &mut RenderContext<'_, '_>|
+ -> Result<(), RenderError> {
+ // modify json object
+ let mut new_ctx = ctx.clone();
+ {
+ let data = new_ctx.data_mut();
+ if let Some(ref mut m) = data.as_object_mut().as_mut() {
+ m.insert("hello".to_string(), to_json("war"));
+ }
+ }
+ rc.set_context(new_ctx);
+ Ok(())
+ },
+ ),
+ );
+
+ assert_eq!(
+ handlebars.render("t0", &data).ok().unwrap(),
+ "worldwar".to_string()
+ );
+
+ let data2 = 0;
+ handlebars.register_decorator(
+ "bar",
+ Box::new(
+ |d: &Decorator<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ rc: &mut RenderContext<'_, '_>|
+ -> Result<(), RenderError> {
+ // modify value
+ let v = d
+ .param(0)
+ .and_then(|v| Context::wraps(v.value()).ok())
+ .unwrap_or(Context::null());
+ rc.set_context(v);
+ Ok(())
+ },
+ ),
+ );
+ handlebars
+ .register_template_string("t1", "{{this}}{{*bar 1}}{{this}}".to_string())
+ .unwrap();
+ assert_eq!(
+ handlebars.render("t1", &data2).ok().unwrap(),
+ "01".to_string()
+ );
+
+ handlebars
+ .register_template_string(
+ "t2",
+ "{{this}}{{*bar \"string_literal\"}}{{this}}".to_string(),
+ )
+ .unwrap();
+ assert_eq!(
+ handlebars.render("t2", &data2).ok().unwrap(),
+ "0string_literal".to_string()
+ );
+
+ handlebars
+ .register_template_string("t3", "{{this}}{{*bar}}{{this}}".to_string())
+ .unwrap();
+ assert_eq!(
+ handlebars.render("t3", &data2).ok().unwrap(),
+ "0".to_string()
+ );
+ }
+
+ #[test]
+ fn test_local_helper_with_decorator() {
+ let mut handlebars = Registry::new();
+ handlebars
+ .register_template_string(
+ "t0",
+ "{{distance 4.5}},{{*foo \"miles\"}}{{distance 10.1}},{{*bar}}{{distance 3.4}}"
+ .to_string(),
+ )
+ .unwrap();
+
+ handlebars.register_helper(
+ "distance",
+ Box::new(
+ |h: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> {
+ let s = format!(
+ "{}m",
+ h.param(0)
+ .as_ref()
+ .map(|v| v.value())
+ .unwrap_or(&to_json(0))
+ );
+ out.write(s.as_ref())?;
+ Ok(())
+ },
+ ),
+ );
+ handlebars.register_decorator(
+ "foo",
+ Box::new(
+ |d: &Decorator<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ rc: &mut RenderContext<'_, '_>|
+ -> Result<(), RenderError> {
+ let new_unit = d
+ .param(0)
+ .as_ref()
+ .and_then(|v| as_string(v.value()))
+ .unwrap_or("")
+ .to_owned();
+ let new_helper = move |h: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> {
+ let s = format!(
+ "{}{}",
+ h.param(0)
+ .as_ref()
+ .map(|v| v.value())
+ .unwrap_or(&to_json(0)),
+ new_unit
+ );
+ out.write(s.as_ref())?;
+ Ok(())
+ };
+
+ rc.register_local_helper("distance", Box::new(new_helper));
+ Ok(())
+ },
+ ),
+ );
+ handlebars.register_decorator(
+ "bar",
+ Box::new(
+ |_: &Decorator<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ rc: &mut RenderContext<'_, '_>|
+ -> Result<(), RenderError> {
+ rc.unregister_local_helper("distance");
+ Ok(())
+ },
+ ),
+ );
+ assert_eq!(
+ handlebars.render("t0", &0).ok().unwrap(),
+ "4.5m,10.1miles,3.4m".to_owned()
+ );
+ }
+}
diff --git a/vendor/handlebars/src/error.rs b/vendor/handlebars/src/error.rs
new file mode 100644
index 000000000..f4721623f
--- /dev/null
+++ b/vendor/handlebars/src/error.rs
@@ -0,0 +1,272 @@
+use std::error::Error;
+use std::fmt;
+use std::io::Error as IOError;
+use std::num::ParseIntError;
+use std::string::FromUtf8Error;
+
+use serde_json::error::Error as SerdeError;
+#[cfg(feature = "dir_source")]
+use walkdir::Error as WalkdirError;
+
+#[cfg(feature = "script_helper")]
+use rhai::{EvalAltResult, ParseError};
+
+/// Error when rendering data on template.
+#[derive(Debug, Default)]
+pub struct RenderError {
+ pub desc: String,
+ pub template_name: Option<String>,
+ pub line_no: Option<usize>,
+ pub column_no: Option<usize>,
+ cause: Option<Box<dyn Error + Send + Sync + 'static>>,
+ unimplemented: bool,
+}
+
+impl fmt::Display for RenderError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ match (self.line_no, self.column_no) {
+ (Some(line), Some(col)) => write!(
+ f,
+ "Error rendering \"{}\" line {}, col {}: {}",
+ self.template_name.as_deref().unwrap_or("Unnamed template"),
+ line,
+ col,
+ self.desc
+ ),
+ _ => write!(f, "{}", self.desc),
+ }
+ }
+}
+
+impl Error for RenderError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ self.cause
+ .as_ref()
+ .map(|e| e.as_ref() as &(dyn Error + 'static))
+ }
+}
+
+impl From<IOError> for RenderError {
+ fn from(e: IOError) -> RenderError {
+ RenderError::from_error("Cannot generate output.", e)
+ }
+}
+
+impl From<SerdeError> for RenderError {
+ fn from(e: SerdeError) -> RenderError {
+ RenderError::from_error("Failed to access JSON data.", e)
+ }
+}
+
+impl From<FromUtf8Error> for RenderError {
+ fn from(e: FromUtf8Error) -> RenderError {
+ RenderError::from_error("Failed to generate bytes.", e)
+ }
+}
+
+impl From<ParseIntError> for RenderError {
+ fn from(e: ParseIntError) -> RenderError {
+ RenderError::from_error("Cannot access array/vector with string index.", e)
+ }
+}
+
+impl From<TemplateError> for RenderError {
+ fn from(e: TemplateError) -> RenderError {
+ RenderError::from_error("Failed to parse template.", e)
+ }
+}
+
+#[cfg(feature = "script_helper")]
+impl From<Box<EvalAltResult>> for RenderError {
+ fn from(e: Box<EvalAltResult>) -> RenderError {
+ RenderError::from_error("Cannot convert data to Rhai dynamic", e)
+ }
+}
+
+#[cfg(feature = "script_helper")]
+impl From<ScriptError> for RenderError {
+ fn from(e: ScriptError) -> RenderError {
+ RenderError::from_error("Failed to load rhai script", e)
+ }
+}
+
+impl RenderError {
+ pub fn new<T: AsRef<str>>(desc: T) -> RenderError {
+ RenderError {
+ desc: desc.as_ref().to_owned(),
+ ..Default::default()
+ }
+ }
+
+ pub(crate) fn unimplemented() -> RenderError {
+ RenderError {
+ unimplemented: true,
+ ..Default::default()
+ }
+ }
+
+ pub fn strict_error(path: Option<&String>) -> RenderError {
+ let msg = match path {
+ Some(path) => format!("Variable {:?} not found in strict mode.", path),
+ None => "Value is missing in strict mode".to_owned(),
+ };
+ RenderError::new(&msg)
+ }
+
+ pub fn from_error<E>(error_info: &str, cause: E) -> RenderError
+ where
+ E: Error + Send + Sync + 'static,
+ {
+ let mut e = RenderError::new(error_info);
+ e.cause = Some(Box::new(cause));
+
+ e
+ }
+
+ #[inline]
+ pub(crate) fn is_unimplemented(&self) -> bool {
+ self.unimplemented
+ }
+}
+
+quick_error! {
+/// Template parsing error
+ #[derive(Debug)]
+ pub enum TemplateErrorReason {
+ MismatchingClosedHelper(open: String, closed: String) {
+ display("helper {:?} was opened, but {:?} is closing",
+ open, closed)
+ }
+ MismatchingClosedDecorator(open: String, closed: String) {
+ display("decorator {:?} was opened, but {:?} is closing",
+ open, closed)
+ }
+ InvalidSyntax {
+ display("invalid handlebars syntax.")
+ }
+ InvalidParam (param: String) {
+ display("invalid parameter {:?}", param)
+ }
+ NestedSubexpression {
+ display("nested subexpression is not supported")
+ }
+ IoError(err: IOError, name: String) {
+ display("Template \"{}\": {}", name, err)
+ }
+ #[cfg(feature = "dir_source")]
+ WalkdirError(err: WalkdirError) {
+ display("Walk dir error: {}", err)
+ }
+ }
+}
+
+/// Error on parsing template.
+#[derive(Debug)]
+pub struct TemplateError {
+ pub reason: TemplateErrorReason,
+ pub template_name: Option<String>,
+ pub line_no: Option<usize>,
+ pub column_no: Option<usize>,
+ segment: Option<String>,
+}
+
+impl TemplateError {
+ pub fn of(e: TemplateErrorReason) -> TemplateError {
+ TemplateError {
+ reason: e,
+ template_name: None,
+ line_no: None,
+ column_no: None,
+ segment: None,
+ }
+ }
+
+ pub fn at(mut self, template_str: &str, line_no: usize, column_no: usize) -> TemplateError {
+ self.line_no = Some(line_no);
+ self.column_no = Some(column_no);
+ self.segment = Some(template_segment(template_str, line_no, column_no));
+ self
+ }
+
+ pub fn in_template(mut self, name: String) -> TemplateError {
+ self.template_name = Some(name);
+ self
+ }
+}
+
+impl Error for TemplateError {}
+
+impl From<(IOError, String)> for TemplateError {
+ fn from(err_info: (IOError, String)) -> TemplateError {
+ let (e, name) = err_info;
+ TemplateError::of(TemplateErrorReason::IoError(e, name))
+ }
+}
+
+#[cfg(feature = "dir_source")]
+impl From<WalkdirError> for TemplateError {
+ fn from(e: WalkdirError) -> TemplateError {
+ TemplateError::of(TemplateErrorReason::WalkdirError(e))
+ }
+}
+
+fn template_segment(template_str: &str, line: usize, col: usize) -> String {
+ let range = 3;
+ let line_start = if line >= range { line - range } else { 0 };
+ let line_end = line + range;
+
+ let mut buf = String::new();
+ for (line_count, line_content) in template_str.lines().enumerate() {
+ if line_count >= line_start && line_count <= line_end {
+ buf.push_str(&format!("{:4} | {}\n", line_count, line_content));
+ if line_count == line - 1 {
+ buf.push_str(" |");
+ for c in 0..line_content.len() {
+ if c != col {
+ buf.push('-');
+ } else {
+ buf.push('^');
+ }
+ }
+ buf.push('\n');
+ }
+ }
+ }
+
+ buf
+}
+
+impl fmt::Display for TemplateError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ match (self.line_no, self.column_no, &self.segment) {
+ (Some(line), Some(col), &Some(ref seg)) => writeln!(
+ f,
+ "Template error: {}\n --> Template error in \"{}\":{}:{}\n |\n{} |\n = reason: {}",
+ self.reason,
+ self.template_name
+ .as_ref()
+ .unwrap_or(&"Unnamed template".to_owned()),
+ line,
+ col,
+ seg,
+ self.reason
+ ),
+ _ => write!(f, "{}", self.reason),
+ }
+ }
+}
+
+#[cfg(feature = "script_helper")]
+quick_error! {
+ #[derive(Debug)]
+ pub enum ScriptError {
+ IoError(err: IOError) {
+ from()
+ source(err)
+ }
+ ParseError(err: ParseError) {
+ from()
+ source(err)
+ }
+ }
+}
diff --git a/vendor/handlebars/src/grammar.pest b/vendor/handlebars/src/grammar.pest
new file mode 100644
index 000000000..250d9d213
--- /dev/null
+++ b/vendor/handlebars/src/grammar.pest
@@ -0,0 +1,127 @@
+WHITESPACE = _{ " "|"\t"|"\n"|"\r" }
+keywords = { "as" | "else" }
+
+escape = @{ ("\\" ~ "{{" ~ "{{"?) | ("\\" ~ "\\"+ ~ &"{{") }
+raw_text = ${ ( escape | (!"{{" ~ ANY) )+ }
+raw_block_text = ${ ( escape | (!"{{{{" ~ ANY) )* }
+
+literal = { string_literal |
+ array_literal |
+ object_literal |
+ number_literal |
+ null_literal |
+ boolean_literal }
+
+null_literal = @{ "null" ~ !symbol_char }
+boolean_literal = @{ ("true"|"false") ~ !symbol_char }
+number_literal = @{ "-"? ~ ASCII_DIGIT+ ~ "."? ~ ASCII_DIGIT* ~ ("E" ~ "-"? ~ ASCII_DIGIT+)? }
+json_char = {
+ !("\"" | "\\") ~ ANY
+ | "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
+ | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
+}
+string_inner = @{ json_char* }
+string_literal = ${ "\"" ~ string_inner ~ "\"" }
+array_literal = { "[" ~ literal? ~ ("," ~ literal)* ~ "]" }
+object_literal = { "{" ~ (string_literal ~ ":" ~ literal)?
+ ~ ("," ~ string_literal ~ ":" ~ literal)* ~ "}" }
+
+symbol_char = _{ASCII_ALPHANUMERIC|"-"|"_"|"$"|'\u{80}'..'\u{7ff}'|'\u{800}'..'\u{ffff}'|'\u{10000}'..'\u{10ffff}'}
+partial_symbol_char = _{ASCII_ALPHANUMERIC|"-"|"_"|'\u{80}'..'\u{7ff}'|'\u{800}'..'\u{ffff}'|'\u{10000}'..'\u{10ffff}'|"/"|"."}
+path_char = _{ "/" }
+
+identifier = @{ symbol_char+ }
+partial_identifier = @{ partial_symbol_char+ | ("[" ~ ANY+ ~ "]") | ("'" ~ (!"'" ~ ("\\'" | ANY))+ ~ "'") }
+reference = ${ path_inline }
+
+name = _{ subexpression | reference }
+
+param = { !(keywords ~ !symbol_char) ~ (literal | reference | subexpression) }
+hash = { identifier ~ "=" ~ param }
+block_param = { "as" ~ "|" ~ identifier ~ identifier? ~ "|"}
+exp_line = _{ identifier ~ (hash|param)* ~ block_param?}
+partial_exp_line = _{ ((partial_identifier|name) ~ (hash|param)*) }
+
+subexpression = { "(" ~ ((identifier ~ (hash|param)+) | reference) ~ ")" }
+
+pre_whitespace_omitter = { "~" }
+pro_whitespace_omitter = { "~" }
+
+expression = { !invert_tag ~ "{{" ~ pre_whitespace_omitter? ~
+ ((identifier ~ (hash|param)+) | name )
+ ~ pro_whitespace_omitter? ~ "}}" }
+html_expression_triple_bracket = _{ "{{{" ~ pre_whitespace_omitter? ~
+ ((identifier ~ (hash|param)+) | name ) ~
+ pro_whitespace_omitter? ~ "}}}" }
+amp_expression = _{ "{{" ~ pre_whitespace_omitter? ~ "&" ~ name ~
+ pro_whitespace_omitter? ~ "}}" }
+html_expression = { html_expression_triple_bracket | amp_expression }
+
+decorator_expression = { "{{" ~ pre_whitespace_omitter? ~ "*" ~ exp_line ~
+pro_whitespace_omitter? ~ "}}" }
+partial_expression = { "{{" ~ pre_whitespace_omitter? ~ ">" ~ partial_exp_line
+ ~ pro_whitespace_omitter? ~ "}}" }
+
+invert_tag_item = { "else"|"^" }
+invert_tag = { !escape ~ "{{" ~ pre_whitespace_omitter? ~ invert_tag_item
+ ~ pro_whitespace_omitter? ~ "}}" }
+helper_block_start = { "{{" ~ pre_whitespace_omitter? ~ "#" ~ exp_line ~
+ pro_whitespace_omitter? ~ "}}" }
+helper_block_end = { "{{" ~ pre_whitespace_omitter? ~ "/" ~ identifier ~
+ pro_whitespace_omitter? ~ "}}" }
+helper_block = _{ helper_block_start ~ template ~
+ (invert_tag ~ template)? ~ helper_block_end }
+
+decorator_block_start = { "{{" ~ pre_whitespace_omitter? ~ "#" ~ "*"
+ ~ exp_line ~ pro_whitespace_omitter? ~ "}}" }
+decorator_block_end = { "{{" ~ pre_whitespace_omitter? ~ "/" ~ identifier ~
+ pro_whitespace_omitter? ~ "}}" }
+decorator_block = _{ decorator_block_start ~ template ~
+ decorator_block_end }
+
+partial_block_start = { "{{" ~ pre_whitespace_omitter? ~ "#" ~ ">"
+ ~ partial_exp_line ~ pro_whitespace_omitter? ~ "}}" }
+partial_block_end = { "{{" ~ pre_whitespace_omitter? ~ "/" ~ partial_identifier ~
+ pro_whitespace_omitter? ~ "}}" }
+partial_block = _{ partial_block_start ~ template ~ partial_block_end }
+
+raw_block_start = { "{{{{" ~ pre_whitespace_omitter? ~ exp_line ~
+ pro_whitespace_omitter? ~ "}}}}" }
+raw_block_end = { "{{{{" ~ pre_whitespace_omitter? ~ "/" ~ identifier ~
+ pro_whitespace_omitter? ~ "}}}}" }
+raw_block = _{ raw_block_start ~ raw_block_text ~ raw_block_end }
+
+hbs_comment = { "{{!" ~ "--" ~ (!"--}}" ~ ANY)* ~ "--" ~ "}}" }
+hbs_comment_compact = { "{{!" ~ (!"}}" ~ ANY)* ~ "}}" }
+
+template = { (
+ raw_text |
+ expression |
+ html_expression |
+ helper_block |
+ raw_block |
+ hbs_comment |
+ hbs_comment_compact |
+ decorator_expression |
+ decorator_block |
+ partial_expression |
+ partial_block )* }
+
+parameter = _{ param ~ EOI }
+handlebars = _{ template ~ EOI }
+
+// json path visitor
+// Disallowed chars: Whitespace ! " # % & ' ( ) * + , . / ; < = > @ [ \ ] ^ ` { | } ~
+
+path_id = @{ symbol_char+ }
+
+path_raw_id = { (!"]" ~ ANY)* }
+path_sep = _{ "/" | "." }
+path_up = { ".." }
+path_key = _{ "[" ~ path_raw_id ~ "]" }
+path_root = { "@root" }
+path_current = _{ "this" ~ path_sep | "./" }
+path_item = _{ path_id|path_key }
+path_local = { "@" }
+path_inline = ${ path_current? ~ (path_root ~ path_sep)? ~ path_local? ~ (path_up ~ path_sep)* ~ path_item ~ (path_sep ~ path_item)* }
+path = _{ path_inline ~ EOI }
diff --git a/vendor/handlebars/src/grammar.rs b/vendor/handlebars/src/grammar.rs
new file mode 100644
index 000000000..1fd292ce1
--- /dev/null
+++ b/vendor/handlebars/src/grammar.rs
@@ -0,0 +1,372 @@
+// const _GRAMMAR: &'static str = include_str!("grammar.pest");
+
+#[derive(Parser)]
+#[grammar = "grammar.pest"]
+pub struct HandlebarsParser;
+
+#[inline]
+pub(crate) fn whitespace_matcher(c: char) -> bool {
+ c == ' ' || c == '\t'
+}
+
+#[inline]
+pub(crate) fn newline_matcher(c: char) -> bool {
+ c == '\n' || c == '\r'
+}
+
+pub(crate) fn ends_with_empty_line(text: &str) -> bool {
+ text.trim_end_matches(whitespace_matcher)
+ .ends_with(newline_matcher)
+}
+
+pub(crate) fn starts_with_empty_line(text: &str) -> bool {
+ text.trim_start_matches(whitespace_matcher)
+ .starts_with(newline_matcher)
+}
+
+#[cfg(test)]
+mod test {
+ use super::{HandlebarsParser, Rule};
+ use pest::Parser;
+
+ macro_rules! assert_rule {
+ ($rule:expr, $in:expr) => {
+ assert_eq!(
+ HandlebarsParser::parse($rule, $in)
+ .unwrap()
+ .last()
+ .unwrap()
+ .as_span()
+ .end(),
+ $in.len()
+ );
+ };
+ }
+
+ macro_rules! assert_not_rule {
+ ($rule:expr, $in:expr) => {
+ assert!(
+ HandlebarsParser::parse($rule, $in).is_err()
+ || HandlebarsParser::parse($rule, $in)
+ .unwrap()
+ .last()
+ .unwrap()
+ .as_span()
+ .end()
+ != $in.len()
+ );
+ };
+ }
+
+ macro_rules! assert_rule_match {
+ ($rule:expr, $in:expr) => {
+ assert!(HandlebarsParser::parse($rule, $in).is_ok());
+ };
+ }
+
+ #[test]
+ fn test_raw_text() {
+ let s = vec![
+ "<h1> helloworld </h1> ",
+ r"hello\{{world}}",
+ r"hello\{{#if world}}nice\{{/if}}",
+ r"hello \{{{{raw}}}}hello\{{{{/raw}}}}",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::raw_text, i);
+ }
+
+ let s_not_escape = vec![r"\\{{hello}}"];
+ for i in s_not_escape.iter() {
+ assert_not_rule!(Rule::raw_text, i);
+ }
+ }
+
+ #[test]
+ fn test_raw_block_text() {
+ let s = "<h1> {{hello}} </h1>";
+ assert_rule!(Rule::raw_block_text, s);
+ }
+
+ #[test]
+ fn test_reference() {
+ let s = vec![
+ "a",
+ "abc",
+ "../a",
+ "a.b",
+ "@abc",
+ "a.[abc]",
+ "aBc.[abc]",
+ "abc.[0].[nice]",
+ "some-name",
+ "this.[0].ok",
+ "this.[$id]",
+ "[$id]",
+ "$id",
+ "this.[null]",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::reference, i);
+ }
+ }
+
+ #[test]
+ fn test_name() {
+ let s = vec!["if", "(abc)"];
+ for i in s.iter() {
+ assert_rule!(Rule::name, i);
+ }
+ }
+
+ #[test]
+ fn test_param() {
+ let s = vec!["hello", "\"json literal\"", "nullable", "truestory"];
+ for i in s.iter() {
+ assert_rule!(Rule::param, i);
+ }
+ }
+
+ #[test]
+ fn test_hash() {
+ let s = vec![
+ "hello=world",
+ "hello=\"world\"",
+ "hello=(world)",
+ "hello=(world 0)",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::hash, i);
+ }
+ }
+
+ #[test]
+ fn test_json_literal() {
+ let s = vec![
+ "\"json string\"",
+ "\"quot: \\\"\"",
+ "[]",
+ "[\"hello\"]",
+ "[1,2,3,4,true]",
+ "{\"hello\": \"world\"}",
+ "{}",
+ "{\"a\":1, \"b\":2 }",
+ "\"nullable\"",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::literal, i);
+ }
+ }
+
+ #[test]
+ fn test_comment() {
+ let s = vec!["{{!-- <hello {{ a-b c-d}} {{d-c}} ok --}}",
+ "{{!--
+ <li><a href=\"{{up-dir nest-count}}{{base-url}}index.html\">{{this.title}}</a></li>
+ --}}",
+ "{{! -- good --}}"];
+ for i in s.iter() {
+ assert_rule!(Rule::hbs_comment, i);
+ }
+ let s2 = vec!["{{! hello }}", "{{! test me }}"];
+ for i in s2.iter() {
+ assert_rule!(Rule::hbs_comment_compact, i);
+ }
+ }
+
+ #[test]
+ fn test_subexpression() {
+ let s = vec!["(sub)", "(sub 0)", "(sub a=1)"];
+ for i in s.iter() {
+ assert_rule!(Rule::subexpression, i);
+ }
+ }
+
+ #[test]
+ fn test_expression() {
+ let s = vec![
+ "{{exp}}",
+ "{{(exp)}}",
+ "{{../exp}}",
+ "{{exp 1}}",
+ "{{exp \"literal\"}}",
+ "{{exp \"literal with space\"}}",
+ r#"{{exp "literal with escape \\\\"}}"#,
+ "{{exp ref}}",
+ "{{exp (sub)}}",
+ "{{exp (sub 123)}}",
+ "{{exp []}}",
+ "{{exp {}}}",
+ "{{exp key=1}}",
+ "{{exp key=ref}}",
+ "{{exp key=(sub)}}",
+ "{{exp key=(sub 0)}}",
+ "{{exp key=(sub 0 key=1)}}",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::expression, i);
+ }
+ }
+
+ #[test]
+ fn test_identifier_with_dash() {
+ let s = vec!["{{exp-foo}}"];
+ for i in s.iter() {
+ assert_rule!(Rule::expression, i);
+ }
+ }
+
+ #[test]
+ fn test_html_expression() {
+ let s = vec![
+ "{{{html}}}",
+ "{{{(html)}}}",
+ "{{{(html)}}}",
+ "{{&html}}",
+ "{{{html 1}}}",
+ "{{{html p=true}}}",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::html_expression, i);
+ }
+ }
+
+ #[test]
+ fn test_helper_start() {
+ let s = vec![
+ "{{#if hello}}",
+ "{{#if (hello)}}",
+ "{{#if hello=world}}",
+ "{{#if hello hello=world}}",
+ "{{#if []}}",
+ "{{#if {}}}",
+ "{{#if}}",
+ "{{~#if hello~}}",
+ "{{#each people as |person|}}",
+ "{{#each-obj obj as |val key|}}",
+ "{{#each assets}}",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::helper_block_start, i);
+ }
+ }
+
+ #[test]
+ fn test_helper_end() {
+ let s = vec!["{{/if}}", "{{~/if}}", "{{~/if ~}}", "{{/if ~}}"];
+ for i in s.iter() {
+ assert_rule!(Rule::helper_block_end, i);
+ }
+ }
+
+ #[test]
+ fn test_helper_block() {
+ let s = vec![
+ "{{#if hello}}hello{{/if}}",
+ "{{#if true}}hello{{/if}}",
+ "{{#if nice ok=1}}hello{{/if}}",
+ "{{#if}}hello{{else}}world{{/if}}",
+ "{{#if}}hello{{^}}world{{/if}}",
+ "{{#if}}{{#if}}hello{{/if}}{{/if}}",
+ "{{#if}}hello{{~else}}world{{/if}}",
+ "{{#if}}hello{{else~}}world{{/if}}",
+ "{{#if}}hello{{~^~}}world{{/if}}",
+ "{{#if}}{{/if}}",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::helper_block, i);
+ }
+ }
+
+ #[test]
+ fn test_raw_block() {
+ let s = vec![
+ "{{{{if hello}}}}good {{hello}}{{{{/if}}}}",
+ "{{{{if hello}}}}{{#if nice}}{{/if}}{{{{/if}}}}",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::raw_block, i);
+ }
+ }
+
+ #[test]
+ fn test_block_param() {
+ let s = vec!["as |person|", "as |val key|"];
+ for i in s.iter() {
+ assert_rule!(Rule::block_param, i);
+ }
+ }
+
+ #[test]
+ fn test_path() {
+ let s = vec![
+ "a",
+ "a.b.c.d",
+ "a.[0].[1].[2]",
+ "a.[abc]",
+ "a/v/c.d.s",
+ "a.[0]/b/c/d",
+ "a.[bb c]/b/c/d",
+ "a.[0].[#hello]",
+ "../a/b.[0].[1]",
+ "this.[0]/[1]/this/a",
+ "./this_name",
+ "./goo/[/bar]",
+ "a.[你好]",
+ "a.[10].[#comment]",
+ "a.[]", // empty key
+ "./[/foo]",
+ "[foo]",
+ "@root/a/b",
+ "nullable",
+ ];
+ for i in s.iter() {
+ assert_rule_match!(Rule::path, i);
+ }
+ }
+
+ #[test]
+ fn test_decorator_expression() {
+ let s = vec!["{{* ssh}}", "{{~* ssh}}"];
+ for i in s.iter() {
+ assert_rule!(Rule::decorator_expression, i);
+ }
+ }
+
+ #[test]
+ fn test_decorator_block() {
+ let s = vec![
+ "{{#* inline}}something{{/inline}}",
+ "{{~#* inline}}hello{{/inline}}",
+ "{{#* inline \"partialname\"}}something{{/inline}}",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::decorator_block, i);
+ }
+ }
+
+ #[test]
+ fn test_partial_expression() {
+ let s = vec![
+ "{{> hello}}",
+ "{{> (hello)}}",
+ "{{~> hello a}}",
+ "{{> hello a=1}}",
+ "{{> (hello) a=1}}",
+ "{{> hello.world}}",
+ "{{> [a83?f4+.3]}}",
+ "{{> 'anif?.bar'}}",
+ ];
+ for i in s.iter() {
+ assert_rule!(Rule::partial_expression, i);
+ }
+ }
+
+ #[test]
+ fn test_partial_block() {
+ let s = vec!["{{#> hello}}nice{{/hello}}"];
+ for i in s.iter() {
+ assert_rule!(Rule::partial_block, i);
+ }
+ }
+}
diff --git a/vendor/handlebars/src/helpers/block_util.rs b/vendor/handlebars/src/helpers/block_util.rs
new file mode 100644
index 000000000..6971fdd8a
--- /dev/null
+++ b/vendor/handlebars/src/helpers/block_util.rs
@@ -0,0 +1,17 @@
+use crate::block::BlockContext;
+use crate::json::value::PathAndJson;
+
+pub(crate) fn create_block<'reg: 'rc, 'rc>(
+ param: &'rc PathAndJson<'reg, 'rc>,
+) -> BlockContext<'reg> {
+ let mut block = BlockContext::new();
+
+ if let Some(new_path) = param.context_path() {
+ *block.base_path_mut() = new_path.clone();
+ } else {
+ // use clone for now
+ block.set_base_value(param.value().clone());
+ }
+
+ block
+}
diff --git a/vendor/handlebars/src/helpers/helper_each.rs b/vendor/handlebars/src/helpers/helper_each.rs
new file mode 100644
index 000000000..4b76e7ce7
--- /dev/null
+++ b/vendor/handlebars/src/helpers/helper_each.rs
@@ -0,0 +1,593 @@
+use serde_json::value::Value as Json;
+
+use super::block_util::create_block;
+use crate::block::{BlockContext, BlockParams};
+use crate::context::Context;
+use crate::error::RenderError;
+use crate::helpers::{HelperDef, HelperResult};
+use crate::json::value::to_json;
+use crate::output::Output;
+use crate::registry::Registry;
+use crate::render::{Helper, RenderContext, Renderable};
+use crate::util::copy_on_push_vec;
+
+fn update_block_context<'reg>(
+ block: &mut BlockContext<'reg>,
+ base_path: Option<&Vec<String>>,
+ relative_path: String,
+ is_first: bool,
+ value: &Json,
+) {
+ if let Some(ref p) = base_path {
+ if is_first {
+ *block.base_path_mut() = copy_on_push_vec(p, relative_path);
+ } else if let Some(ptr) = block.base_path_mut().last_mut() {
+ *ptr = relative_path;
+ }
+ } else {
+ block.set_base_value(value.clone());
+ }
+}
+
+fn set_block_param<'reg: 'rc, 'rc>(
+ block: &mut BlockContext<'reg>,
+ h: &Helper<'reg, 'rc>,
+ base_path: Option<&Vec<String>>,
+ k: &Json,
+ v: &Json,
+) -> Result<(), RenderError> {
+ if let Some(bp_val) = h.block_param() {
+ let mut params = BlockParams::new();
+ if base_path.is_some() {
+ params.add_path(bp_val, Vec::with_capacity(0))?;
+ } else {
+ params.add_value(bp_val, v.clone())?;
+ }
+
+ block.set_block_params(params);
+ } else if let Some((bp_val, bp_key)) = h.block_param_pair() {
+ let mut params = BlockParams::new();
+ if base_path.is_some() {
+ params.add_path(bp_val, Vec::with_capacity(0))?;
+ } else {
+ params.add_value(bp_val, v.clone())?;
+ }
+ params.add_value(bp_key, k.clone())?;
+
+ block.set_block_params(params);
+ }
+
+ Ok(())
+}
+
+#[derive(Clone, Copy)]
+pub struct EachHelper;
+
+impl HelperDef for EachHelper {
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> HelperResult {
+ let value = h
+ .param(0)
+ .ok_or_else(|| RenderError::new("Param not found for helper \"each\""))?;
+
+ let template = h.template();
+
+ match template {
+ Some(t) => match *value.value() {
+ Json::Array(ref list)
+ if !list.is_empty() || (list.is_empty() && h.inverse().is_none()) =>
+ {
+ let block_context = create_block(&value);
+ rc.push_block(block_context);
+
+ let len = list.len();
+
+ let array_path = value.context_path();
+
+ for (i, v) in list.iter().enumerate().take(len) {
+ if let Some(ref mut block) = rc.block_mut() {
+ let is_first = i == 0usize;
+ let is_last = i == len - 1;
+
+ let index = to_json(i);
+ block.set_local_var("first", to_json(is_first));
+ block.set_local_var("last", to_json(is_last));
+ block.set_local_var("index", index.clone());
+
+ update_block_context(block, array_path, i.to_string(), is_first, &v);
+ set_block_param(block, h, array_path, &index, &v)?;
+ }
+
+ t.render(r, ctx, rc, out)?;
+ }
+
+ rc.pop_block();
+ Ok(())
+ }
+ Json::Object(ref obj)
+ if !obj.is_empty() || (obj.is_empty() && h.inverse().is_none()) =>
+ {
+ let block_context = create_block(&value);
+ rc.push_block(block_context);
+
+ let mut is_first = true;
+ let obj_path = value.context_path();
+
+ for (k, v) in obj.iter() {
+ if let Some(ref mut block) = rc.block_mut() {
+ let key = to_json(k);
+
+ block.set_local_var("first", to_json(is_first));
+ block.set_local_var("key", key.clone());
+
+ update_block_context(block, obj_path, k.to_string(), is_first, &v);
+ set_block_param(block, h, obj_path, &key, &v)?;
+ }
+
+ t.render(r, ctx, rc, out)?;
+
+ if is_first {
+ is_first = false;
+ }
+ }
+
+ rc.pop_block();
+ Ok(())
+ }
+ _ => {
+ if let Some(else_template) = h.inverse() {
+ else_template.render(r, ctx, rc, out)
+ } else if r.strict_mode() {
+ Err(RenderError::strict_error(value.relative_path()))
+ } else {
+ Ok(())
+ }
+ }
+ },
+ None => Ok(()),
+ }
+ }
+}
+
+pub static EACH_HELPER: EachHelper = EachHelper;
+
+#[cfg(test)]
+mod test {
+ use crate::json::value::to_json;
+ use crate::registry::Registry;
+ use serde_json::value::Value as Json;
+ use std::collections::BTreeMap;
+ use std::str::FromStr;
+
+ #[test]
+ fn test_empty_each() {
+ let mut hbs = Registry::new();
+ hbs.set_strict_mode(true);
+
+ let data = json!({
+ "a": [ ],
+ });
+
+ let template = "{{#each a}}each{{/each}}";
+ assert_eq!(hbs.render_template(template, &data).unwrap(), "");
+ }
+
+ #[test]
+ fn test_each() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string(
+ "t0",
+ "{{#each this}}{{@first}}|{{@last}}|{{@index}}:{{this}}|{{/each}}"
+ )
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t1", "{{#each this}}{{@first}}|{{@key}}:{{this}}|{{/each}}")
+ .is_ok());
+
+ let r0 = handlebars.render("t0", &vec![1u16, 2u16, 3u16]);
+ assert_eq!(
+ r0.ok().unwrap(),
+ "true|false|0:1|false|false|1:2|false|true|2:3|".to_string()
+ );
+
+ let mut m: BTreeMap<String, u16> = BTreeMap::new();
+ m.insert("ftp".to_string(), 21);
+ m.insert("http".to_string(), 80);
+ let r1 = handlebars.render("t1", &m);
+ assert_eq!(r1.ok().unwrap(), "true|ftp:21|false|http:80|".to_string());
+ }
+
+ #[test]
+ fn test_each_with_parent() {
+ let json_str = r#"{"a":{"a":99,"c":[{"d":100},{"d":200}]}}"#;
+
+ let data = Json::from_str(json_str).unwrap();
+ // println!("data: {}", data);
+ let mut handlebars = Registry::new();
+
+ // previously, to access the parent in an each block,
+ // a user would need to specify ../../b, as the path
+ // that is computed includes the array index: ./a.c.[0]
+ assert!(handlebars
+ .register_template_string("t0", "{{#each a.c}} d={{d}} b={{../a.a}} {{/each}}")
+ .is_ok());
+
+ let r1 = handlebars.render("t0", &data);
+ assert_eq!(r1.ok().unwrap(), " d=100 b=99 d=200 b=99 ".to_string());
+ }
+
+ #[test]
+ fn test_nested_each_with_parent() {
+ let json_str = r#"{"a": [{"b": [{"d": 100}], "c": 200}]}"#;
+
+ let data = Json::from_str(json_str).unwrap();
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string(
+ "t0",
+ "{{#each a}}{{#each b}}{{d}}:{{../c}}{{/each}}{{/each}}"
+ )
+ .is_ok());
+
+ let r1 = handlebars.render("t0", &data);
+ assert_eq!(r1.ok().unwrap(), "100:200".to_string());
+ }
+
+ #[test]
+ fn test_nested_each() {
+ let json_str = r#"{"a": [{"b": true}], "b": [[1, 2, 3],[4, 5]]}"#;
+
+ let data = Json::from_str(json_str).unwrap();
+
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string(
+ "t0",
+ "{{#each b}}{{#if ../a}}{{#each this}}{{this}}{{/each}}{{/if}}{{/each}}"
+ )
+ .is_ok());
+
+ let r1 = handlebars.render("t0", &data);
+ assert_eq!(r1.ok().unwrap(), "12345".to_string());
+ }
+
+ #[test]
+ fn test_nested_array() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#each this.[0]}}{{this}}{{/each}}")
+ .is_ok());
+
+ let r0 = handlebars.render("t0", &(vec![vec![1, 2, 3]]));
+
+ assert_eq!(r0.ok().unwrap(), "123".to_string());
+ }
+
+ #[test]
+ fn test_empty_key() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#each this}}{{@key}}-{{value}}\n{{/each}}")
+ .is_ok());
+
+ let r0 = handlebars
+ .render(
+ "t0",
+ &json!({
+ "foo": {
+ "value": "bar"
+ },
+ "": {
+ "value": "baz"
+ }
+ }),
+ )
+ .unwrap();
+
+ let mut r0_sp: Vec<_> = r0.split('\n').collect();
+ r0_sp.sort();
+
+ assert_eq!(r0_sp, vec!["", "-baz", "foo-bar"]);
+ }
+
+ #[test]
+ fn test_each_else() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#each a}}1{{else}}empty{{/each}}")
+ .is_ok());
+ let m1 = btreemap! {
+ "a".to_string() => Vec::<String>::new(),
+ };
+ let r0 = handlebars.render("t0", &m1).unwrap();
+ assert_eq!(r0, "empty");
+
+ let m2 = btreemap! {
+ "b".to_string() => Vec::<String>::new()
+ };
+ let r1 = handlebars.render("t0", &m2).unwrap();
+ assert_eq!(r1, "empty");
+ }
+
+ #[test]
+ fn test_block_param() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#each a as |i|}}{{i}}{{/each}}")
+ .is_ok());
+ let m1 = btreemap! {
+ "a".to_string() => vec![1,2,3,4,5]
+ };
+ let r0 = handlebars.render("t0", &m1).unwrap();
+ assert_eq!(r0, "12345");
+ }
+
+ #[test]
+ fn test_each_object_block_param() {
+ let mut handlebars = Registry::new();
+ let template = "{{#each this as |v k|}}\
+ {{#with k as |inner_k|}}{{inner_k}}{{/with}}:{{v}}|\
+ {{/each}}";
+ assert!(handlebars.register_template_string("t0", template).is_ok());
+
+ let m = btreemap! {
+ "ftp".to_string() => 21,
+ "http".to_string() => 80
+ };
+ let r0 = handlebars.render("t0", &m);
+ assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
+ }
+
+ #[test]
+ fn test_each_object_block_param2() {
+ let mut handlebars = Registry::new();
+ let template = "{{#each this as |v k|}}\
+ {{#with v as |inner_v|}}{{k}}:{{inner_v}}{{/with}}|\
+ {{/each}}";
+
+ assert!(handlebars.register_template_string("t0", template).is_ok());
+
+ let m = btreemap! {
+ "ftp".to_string() => 21,
+ "http".to_string() => 80
+ };
+ let r0 = handlebars.render("t0", &m);
+ assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
+ }
+
+ fn test_nested_each_with_path_ups() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string(
+ "t0",
+ "{{#each a.b}}{{#each c}}{{../../d}}{{/each}}{{/each}}"
+ )
+ .is_ok());
+
+ let data = btreemap! {
+ "a".to_string() => to_json(&btreemap! {
+ "b".to_string() => vec![btreemap!{"c".to_string() => vec![1]}]
+ }),
+ "d".to_string() => to_json(&1)
+ };
+
+ let r0 = handlebars.render("t0", &data);
+ assert_eq!(r0.ok().unwrap(), "1".to_string());
+ }
+
+ #[test]
+ fn test_nested_each_with_path_up_this() {
+ let mut handlebars = Registry::new();
+ let template = "{{#each variant}}{{#each ../typearg}}\
+ {{#if @first}}template<{{/if}}{{this}}{{#if @last}}>{{else}},{{/if}}\
+ {{/each}}{{/each}}";
+ assert!(handlebars.register_template_string("t0", template).is_ok());
+ let data = btreemap! {
+ "typearg".to_string() => vec!["T".to_string()],
+ "variant".to_string() => vec!["1".to_string(), "2".to_string()]
+ };
+ let r0 = handlebars.render("t0", &data);
+ assert_eq!(r0.ok().unwrap(), "template<T>template<T>".to_string());
+ }
+
+ #[test]
+ fn test_key_iteration_with_unicode() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
+ .is_ok());
+ let data = json!({
+ "normal": 1,
+ "你好": 2,
+ "#special key": 3,
+ "😂": 4,
+ "me.dot.key": 5
+ });
+ let r0 = handlebars.render("t0", &data).ok().unwrap();
+ assert!(r0.contains("normal: 1"));
+ assert!(r0.contains("你好: 2"));
+ assert!(r0.contains("#special key: 3"));
+ assert!(r0.contains("😂: 4"));
+ assert!(r0.contains("me.dot.key: 5"));
+ }
+
+ #[test]
+ fn test_base_path_after_each() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#each a}}{{this}}{{/each}} {{b}}")
+ .is_ok());
+ let data = json!({
+ "a": [1, 2, 3, 4],
+ "b": "good",
+ });
+
+ let r0 = handlebars.render("t0", &data).ok().unwrap();
+
+ assert_eq!("1234 good", r0);
+ }
+
+ #[test]
+ fn test_else_context() {
+ let reg = Registry::new();
+ let template = "{{#each list}}A{{else}}{{foo}}{{/each}}";
+ let input = json!({"list": [], "foo": "bar"});
+ let rendered = reg.render_template(template, &input).unwrap();
+ assert_eq!("bar", rendered);
+ }
+
+ #[test]
+ fn test_block_context_leak() {
+ let reg = Registry::new();
+ let template = "{{#each list}}{{#each inner}}{{this}}{{/each}}{{foo}}{{/each}}";
+ let input = json!({"list": [{"inner": [], "foo": 1}, {"inner": [], "foo": 2}]});
+ let rendered = reg.render_template(template, &input).unwrap();
+ assert_eq!("12", rendered);
+ }
+
+ #[test]
+ fn test_derived_array_as_block_params() {
+ handlebars_helper!(range: |x: u64| (0..x).collect::<Vec<u64>>());
+ let mut reg = Registry::new();
+ reg.register_helper("range", Box::new(range));
+ let template = "{{#each (range 3) as |i|}}{{i}}{{/each}}";
+ let input = json!(0);
+ let rendered = reg.render_template(template, &input).unwrap();
+ assert_eq!("012", rendered);
+ }
+
+ #[test]
+ fn test_derived_object_as_block_params() {
+ handlebars_helper!(point: |x: u64, y: u64| json!({"x":x, "y":y}));
+ let mut reg = Registry::new();
+ reg.register_helper("point", Box::new(point));
+ let template = "{{#each (point 0 1) as |i|}}{{i}}{{/each}}";
+ let input = json!(0);
+ let rendered = reg.render_template(template, &input).unwrap();
+ assert_eq!("01", rendered);
+ }
+
+ #[test]
+ fn test_derived_array_without_block_param() {
+ handlebars_helper!(range: |x: u64| (0..x).collect::<Vec<u64>>());
+ let mut reg = Registry::new();
+ reg.register_helper("range", Box::new(range));
+ let template = "{{#each (range 3)}}{{this}}{{/each}}";
+ let input = json!(0);
+ let rendered = reg.render_template(template, &input).unwrap();
+ assert_eq!("012", rendered);
+ }
+
+ #[test]
+ fn test_derived_object_without_block_params() {
+ handlebars_helper!(point: |x: u64, y: u64| json!({"x":x, "y":y}));
+ let mut reg = Registry::new();
+ reg.register_helper("point", Box::new(point));
+ let template = "{{#each (point 0 1)}}{{this}}{{/each}}";
+ let input = json!(0);
+ let rendered = reg.render_template(template, &input).unwrap();
+ assert_eq!("01", rendered);
+ }
+
+ #[test]
+ fn test_non_iterable() {
+ let reg = Registry::new();
+ let template = "{{#each this}}each block{{else}}else block{{/each}}";
+ let input = json!("strings aren't iterable");
+ let rendered = reg.render_template(template, &input).unwrap();
+ assert_eq!("else block", rendered);
+ }
+
+ #[test]
+ fn test_recursion() {
+ let mut reg = Registry::new();
+ assert!(reg
+ .register_template_string(
+ "walk",
+ "(\
+ {{#each this}}\
+ {{#if @key}}{{@key}}{{else}}{{@index}}{{/if}}: \
+ {{this}} \
+ {{> walk this}}, \
+ {{/each}}\
+ )",
+ )
+ .is_ok());
+
+ let input = json!({
+ "array": [42, {"wow": "cool"}, [[]]],
+ "object": { "a": { "b": "c", "d": ["e"] } },
+ "string": "hi"
+ });
+ let expected_output = "(\
+ array: [42, [object], [[], ], ] (\
+ 0: 42 (), \
+ 1: [object] (wow: cool (), ), \
+ 2: [[], ] (0: [] (), ), \
+ ), \
+ object: [object] (\
+ a: [object] (\
+ b: c (), \
+ d: [e, ] (0: e (), ), \
+ ), \
+ ), \
+ string: hi (), \
+ )";
+
+ let rendered = reg.render("walk", &input).unwrap();
+ assert_eq!(expected_output, rendered);
+ }
+
+ #[test]
+ fn test_strict_each() {
+ let mut reg = Registry::new();
+
+ assert!(reg
+ .render_template("{{#each data}}{{/each}}", &json!({}))
+ .is_ok());
+ assert!(reg
+ .render_template("{{#each data}}{{/each}}", &json!({"data": 24}))
+ .is_ok());
+
+ reg.set_strict_mode(true);
+
+ assert!(reg
+ .render_template("{{#each data}}{{/each}}", &json!({}))
+ .is_err());
+ assert!(reg
+ .render_template("{{#each data}}{{/each}}", &json!({"data": 24}))
+ .is_err());
+ assert!(reg
+ .render_template("{{#each data}}{{else}}food{{/each}}", &json!({}))
+ .is_ok());
+ assert!(reg
+ .render_template("{{#each data}}{{else}}food{{/each}}", &json!({"data": 24}))
+ .is_ok());
+ }
+
+ #[test]
+ fn newline_stripping_for_each() {
+ let reg = Registry::new();
+
+ let tpl = r#"<ul>
+ {{#each a}}
+ {{!-- comment --}}
+ <li>{{this}}</li>
+ {{/each}}
+</ul>"#;
+ assert_eq!(
+ r#"<ul>
+ <li>0</li>
+ <li>1</li>
+</ul>"#,
+ reg.render_template(tpl, &json!({"a": [0, 1]})).unwrap()
+ );
+ }
+}
diff --git a/vendor/handlebars/src/helpers/helper_extras.rs b/vendor/handlebars/src/helpers/helper_extras.rs
new file mode 100644
index 000000000..fe8f7a7d3
--- /dev/null
+++ b/vendor/handlebars/src/helpers/helper_extras.rs
@@ -0,0 +1,112 @@
+//! Helpers for boolean operations
+use serde_json::Value as Json;
+
+use crate::json::value::JsonTruthy;
+
+handlebars_helper!(eq: |x: Json, y: Json| x == y);
+handlebars_helper!(ne: |x: Json, y: Json| x != y);
+handlebars_helper!(gt: |x: i64, y: i64| x > y);
+handlebars_helper!(gte: |x: i64, y: i64| x >= y);
+handlebars_helper!(lt: |x: i64, y: i64| x < y);
+handlebars_helper!(lte: |x: i64, y: i64| x <= y);
+handlebars_helper!(and: |x: Json, y: Json| x.is_truthy(false) && y.is_truthy(false));
+handlebars_helper!(or: |x: Json, y: Json| x.is_truthy(false) || y.is_truthy(false));
+handlebars_helper!(not: |x: Json| !x.is_truthy(false));
+handlebars_helper!(len: |x: Json| {
+ match x {
+ Json::Array(a) => a.len(),
+ Json::Object(m) => m.len(),
+ Json::String(s) => s.len(),
+ _ => 0
+ }
+});
+
+#[cfg(test)]
+mod test_conditions {
+ fn test_condition(condition: &str, expected: bool) {
+ let handlebars = crate::Handlebars::new();
+
+ let result = handlebars
+ .render_template(
+ &format!(
+ "{{{{#if {condition}}}}}lorem{{{{else}}}}ipsum{{{{/if}}}}",
+ condition = condition
+ ),
+ &json!({}),
+ )
+ .unwrap();
+ assert_eq!(&result, if expected { "lorem" } else { "ipsum" });
+ }
+
+ #[test]
+ fn foo() {
+ test_condition("(gt 5 3)", true);
+ test_condition("(gt 3 5)", false);
+ test_condition("(or (gt 3 5) (gt 5 3))", true);
+ test_condition("(not [])", true);
+ test_condition("(and null 4)", false);
+ }
+
+ #[test]
+ fn test_eq() {
+ test_condition("(eq 5 5)", true);
+ test_condition("(eq 5 6)", false);
+ test_condition(r#"(eq "foo" "foo")"#, true);
+ test_condition(r#"(eq "foo" "Foo")"#, false);
+ test_condition(r#"(eq [5] [5])"#, true);
+ test_condition(r#"(eq [5] [4])"#, false);
+ test_condition(r#"(eq 5 "5")"#, false);
+ test_condition(r#"(eq 5 [5])"#, false);
+ }
+
+ #[test]
+ fn test_ne() {
+ test_condition("(ne 5 6)", true);
+ test_condition("(ne 5 5)", false);
+ test_condition(r#"(ne "foo" "foo")"#, false);
+ test_condition(r#"(ne "foo" "Foo")"#, true);
+ }
+
+ #[test]
+ fn nested_conditions() {
+ let handlebars = crate::Handlebars::new();
+
+ let result = handlebars
+ .render_template("{{#if (gt 5 3)}}lorem{{else}}ipsum{{/if}}", &json!({}))
+ .unwrap();
+ assert_eq!(&result, "lorem");
+
+ let result = handlebars
+ .render_template(
+ "{{#if (not (gt 5 3))}}lorem{{else}}ipsum{{/if}}",
+ &json!({}),
+ )
+ .unwrap();
+ assert_eq!(&result, "ipsum");
+ }
+
+ #[test]
+ fn test_len() {
+ let handlebars = crate::Handlebars::new();
+
+ let result = handlebars
+ .render_template("{{len value}}", &json!({"value": [1,2,3]}))
+ .unwrap();
+ assert_eq!(&result, "3");
+
+ let result = handlebars
+ .render_template("{{len value}}", &json!({"value": {"a" :1, "b": 2}}))
+ .unwrap();
+ assert_eq!(&result, "2");
+
+ let result = handlebars
+ .render_template("{{len value}}", &json!({"value": "tomcat"}))
+ .unwrap();
+ assert_eq!(&result, "6");
+
+ let result = handlebars
+ .render_template("{{len value}}", &json!({"value": 3}))
+ .unwrap();
+ assert_eq!(&result, "0");
+ }
+}
diff --git a/vendor/handlebars/src/helpers/helper_if.rs b/vendor/handlebars/src/helpers/helper_if.rs
new file mode 100644
index 000000000..5a6e42fc0
--- /dev/null
+++ b/vendor/handlebars/src/helpers/helper_if.rs
@@ -0,0 +1,151 @@
+use crate::context::Context;
+use crate::error::RenderError;
+use crate::helpers::{HelperDef, HelperResult};
+use crate::json::value::JsonTruthy;
+use crate::output::Output;
+use crate::registry::Registry;
+use crate::render::{Helper, RenderContext, Renderable};
+
+#[derive(Clone, Copy)]
+pub struct IfHelper {
+ positive: bool,
+}
+
+impl HelperDef for IfHelper {
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> HelperResult {
+ let param = h
+ .param(0)
+ .ok_or_else(|| RenderError::new("Param not found for helper \"if\""))?;
+ let include_zero = h
+ .hash_get("includeZero")
+ .and_then(|v| v.value().as_bool())
+ .unwrap_or(false);
+
+ let mut value = param.value().is_truthy(include_zero);
+
+ if !self.positive {
+ value = !value;
+ }
+
+ let tmpl = if value { h.template() } else { h.inverse() };
+ match tmpl {
+ Some(ref t) => t.render(r, ctx, rc, out),
+ None => Ok(()),
+ }
+ }
+}
+
+pub static IF_HELPER: IfHelper = IfHelper { positive: true };
+pub static UNLESS_HELPER: IfHelper = IfHelper { positive: false };
+
+#[cfg(test)]
+mod test {
+ use crate::helpers::WITH_HELPER;
+ use crate::registry::Registry;
+ use serde_json::value::Value as Json;
+ use std::str::FromStr;
+
+ #[test]
+ fn test_if() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#if this}}hello{{/if}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t1", "{{#unless this}}hello{{else}}world{{/unless}}")
+ .is_ok());
+
+ let r0 = handlebars.render("t0", &true);
+ assert_eq!(r0.ok().unwrap(), "hello".to_string());
+
+ let r1 = handlebars.render("t1", &true);
+ assert_eq!(r1.ok().unwrap(), "world".to_string());
+
+ let r2 = handlebars.render("t0", &false);
+ assert_eq!(r2.ok().unwrap(), "".to_string());
+ }
+
+ #[test]
+ fn test_if_context() {
+ let json_str = r#"{"a":{"b":99,"c":{"d": true}}}"#;
+ let data = Json::from_str(json_str).unwrap();
+
+ let mut handlebars = Registry::new();
+ handlebars.register_helper("with", Box::new(WITH_HELPER));
+ assert!(handlebars
+ .register_template_string("t0", "{{#if a.c.d}}hello {{a.b}}{{/if}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string(
+ "t1",
+ "{{#with a}}{{#if c.d}}hello {{../a.b}}{{/if}}{{/with}}"
+ )
+ .is_ok());
+
+ let r0 = handlebars.render("t0", &data);
+ assert_eq!(r0.unwrap(), "hello 99".to_string());
+
+ let r1 = handlebars.render("t1", &data);
+ assert_eq!(r1.unwrap(), "hello 99".to_string());
+ }
+
+ #[test]
+ fn test_if_include_zero() {
+ use std::f64;
+ let handlebars = Registry::new();
+
+ assert_eq!(
+ "0".to_owned(),
+ handlebars
+ .render_template("{{#if a}}1{{else}}0{{/if}}", &json!({"a": 0}))
+ .unwrap()
+ );
+ assert_eq!(
+ "1".to_owned(),
+ handlebars
+ .render_template(
+ "{{#if a includeZero=true}}1{{else}}0{{/if}}",
+ &json!({"a": 0})
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ "0".to_owned(),
+ handlebars
+ .render_template(
+ "{{#if a includeZero=true}}1{{else}}0{{/if}}",
+ &json!({ "a": f64::NAN })
+ )
+ .unwrap()
+ );
+ }
+
+ #[test]
+ fn test_invisible_line_stripping() {
+ let hbs = Registry::new();
+ assert_eq!(
+ "yes\n",
+ hbs.render_template("{{#if a}}\nyes\n{{/if}}\n", &json!({"a": true}))
+ .unwrap()
+ );
+
+ assert_eq!(
+ "x\ny",
+ hbs.render_template("{{#if a}}x{{/if}}\ny", &json!({"a": true}))
+ .unwrap()
+ );
+
+ assert_eq!(
+ "y\nz",
+ hbs.render_template("{{#if a}}\nx\n{{^}}\ny\n{{/if}}\nz", &json!({"a": false}))
+ .unwrap()
+ );
+ }
+}
diff --git a/vendor/handlebars/src/helpers/helper_log.rs b/vendor/handlebars/src/helpers/helper_log.rs
new file mode 100644
index 000000000..5821efa35
--- /dev/null
+++ b/vendor/handlebars/src/helpers/helper_log.rs
@@ -0,0 +1,72 @@
+use crate::context::Context;
+#[cfg(not(feature = "no_logging"))]
+use crate::error::RenderError;
+use crate::helpers::{HelperDef, HelperResult};
+#[cfg(not(feature = "no_logging"))]
+use crate::json::value::JsonRender;
+use crate::output::Output;
+use crate::registry::Registry;
+use crate::render::{Helper, RenderContext};
+#[cfg(not(feature = "no_logging"))]
+use log::Level;
+#[cfg(not(feature = "no_logging"))]
+use std::str::FromStr;
+
+#[derive(Clone, Copy)]
+pub struct LogHelper;
+
+#[cfg(not(feature = "no_logging"))]
+impl HelperDef for LogHelper {
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ _: &'reg Registry<'reg>,
+ _: &'rc Context,
+ _: &mut RenderContext<'reg, 'rc>,
+ _: &mut dyn Output,
+ ) -> HelperResult {
+ let param_to_log = h
+ .params()
+ .iter()
+ .map(|p| {
+ if let Some(ref relative_path) = p.relative_path() {
+ format!("{}: {}", relative_path, p.value().render())
+ } else {
+ p.value().render()
+ }
+ })
+ .collect::<Vec<String>>()
+ .join(", ");
+
+ let level = h
+ .hash_get("level")
+ .and_then(|v| v.value().as_str())
+ .unwrap_or("info");
+
+ if let Ok(log_level) = Level::from_str(level) {
+ log!(log_level, "{}", param_to_log)
+ } else {
+ return Err(RenderError::new(&format!(
+ "Unsupported logging level {}",
+ level
+ )));
+ }
+ Ok(())
+ }
+}
+
+#[cfg(feature = "no_logging")]
+impl HelperDef for LogHelper {
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ _: &Helper<'reg, 'rc>,
+ _: &Registry<'reg>,
+ _: &Context,
+ _: &mut RenderContext<'reg, 'rc>,
+ _: &mut dyn Output,
+ ) -> HelperResult {
+ Ok(())
+ }
+}
+
+pub static LOG_HELPER: LogHelper = LogHelper;
diff --git a/vendor/handlebars/src/helpers/helper_lookup.rs b/vendor/handlebars/src/helpers/helper_lookup.rs
new file mode 100644
index 000000000..bf887debe
--- /dev/null
+++ b/vendor/handlebars/src/helpers/helper_lookup.rs
@@ -0,0 +1,104 @@
+use serde_json::value::Value as Json;
+
+use crate::context::Context;
+use crate::error::RenderError;
+use crate::helpers::HelperDef;
+use crate::json::value::ScopedJson;
+use crate::registry::Registry;
+use crate::render::{Helper, RenderContext};
+
+#[derive(Clone, Copy)]
+pub struct LookupHelper;
+
+impl HelperDef for LookupHelper {
+ fn call_inner<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ _: &'rc Context,
+ _: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ let collection_value = h
+ .param(0)
+ .ok_or_else(|| RenderError::new("Param not found for helper \"lookup\""))?;
+ let index = h
+ .param(1)
+ .ok_or_else(|| RenderError::new("Insufficient params for helper \"lookup\""))?;
+
+ let value = match *collection_value.value() {
+ Json::Array(ref v) => index
+ .value()
+ .as_u64()
+ .and_then(|u| v.get(u as usize))
+ .unwrap_or(&Json::Null),
+ Json::Object(ref m) => index
+ .value()
+ .as_str()
+ .and_then(|k| m.get(k))
+ .unwrap_or(&Json::Null),
+ _ => &Json::Null,
+ };
+ if r.strict_mode() && value.is_null() {
+ Err(RenderError::strict_error(None))
+ } else {
+ Ok(value.clone().into())
+ }
+ }
+}
+
+pub static LOOKUP_HELPER: LookupHelper = LookupHelper;
+
+#[cfg(test)]
+mod test {
+ use crate::registry::Registry;
+
+ use std::collections::BTreeMap;
+
+ #[test]
+ fn test_lookup() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#each v1}}{{lookup ../v2 @index}}{{/each}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t1", "{{#each v1}}{{lookup ../v2 1}}{{/each}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t2", "{{lookup kk \"a\"}}")
+ .is_ok());
+
+ let mut m: BTreeMap<String, Vec<u16>> = BTreeMap::new();
+ m.insert("v1".to_string(), vec![1u16, 2u16, 3u16]);
+ m.insert("v2".to_string(), vec![9u16, 8u16, 7u16]);
+
+ let m2 = btreemap! {
+ "kk".to_string() => btreemap!{"a".to_string() => "world".to_string()}
+ };
+
+ let r0 = handlebars.render("t0", &m);
+ assert_eq!(r0.ok().unwrap(), "987".to_string());
+
+ let r1 = handlebars.render("t1", &m);
+ assert_eq!(r1.ok().unwrap(), "888".to_string());
+
+ let r2 = handlebars.render("t2", &m2);
+ assert_eq!(r2.ok().unwrap(), "world".to_string());
+ }
+
+ #[test]
+ fn test_strict_lookup() {
+ let mut hbs = Registry::new();
+
+ assert_eq!(
+ hbs.render_template("{{lookup kk 1}}", &json!({"kk": []}))
+ .unwrap(),
+ ""
+ );
+
+ hbs.set_strict_mode(true);
+
+ assert!(hbs
+ .render_template("{{lookup kk 1}}", &json!({"kk": []}))
+ .is_err());
+ }
+}
diff --git a/vendor/handlebars/src/helpers/helper_raw.rs b/vendor/handlebars/src/helpers/helper_raw.rs
new file mode 100644
index 000000000..55aef6bfc
--- /dev/null
+++ b/vendor/handlebars/src/helpers/helper_raw.rs
@@ -0,0 +1,44 @@
+use crate::context::Context;
+use crate::helpers::{HelperDef, HelperResult};
+use crate::output::Output;
+use crate::registry::Registry;
+use crate::render::{Helper, RenderContext, Renderable};
+
+#[derive(Clone, Copy)]
+pub struct RawHelper;
+
+impl HelperDef for RawHelper {
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> HelperResult {
+ let tpl = h.template();
+ if let Some(t) = tpl {
+ t.render(r, ctx, rc, out)
+ } else {
+ Ok(())
+ }
+ }
+}
+
+pub static RAW_HELPER: RawHelper = RawHelper;
+
+#[cfg(test)]
+mod test {
+ use crate::registry::Registry;
+
+ #[test]
+ fn test_raw_helper() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "a{{{{raw}}}}{{content}}{{else}}hello{{{{/raw}}}}")
+ .is_ok());
+
+ let r = handlebars.render("t0", &());
+ assert_eq!(r.ok().unwrap(), "a{{content}}{{else}}hello");
+ }
+}
diff --git a/vendor/handlebars/src/helpers/helper_with.rs b/vendor/handlebars/src/helpers/helper_with.rs
new file mode 100644
index 000000000..c4d31cd0e
--- /dev/null
+++ b/vendor/handlebars/src/helpers/helper_with.rs
@@ -0,0 +1,276 @@
+use super::block_util::create_block;
+use crate::block::BlockParams;
+use crate::context::Context;
+use crate::error::RenderError;
+use crate::helpers::{HelperDef, HelperResult};
+use crate::json::value::JsonTruthy;
+use crate::output::Output;
+use crate::registry::Registry;
+use crate::render::{Helper, RenderContext, Renderable};
+
+#[derive(Clone, Copy)]
+pub struct WithHelper;
+
+impl HelperDef for WithHelper {
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> HelperResult {
+ let param = h
+ .param(0)
+ .ok_or_else(|| RenderError::new("Param not found for helper \"with\""))?;
+
+ if param.value().is_truthy(false) {
+ let mut block = create_block(&param);
+
+ if let Some(block_param) = h.block_param() {
+ let mut params = BlockParams::new();
+ if param.context_path().is_some() {
+ params.add_path(block_param, Vec::with_capacity(0))?;
+ } else {
+ params.add_value(block_param, param.value().clone())?;
+ }
+
+ block.set_block_params(params);
+ }
+
+ rc.push_block(block);
+
+ if let Some(t) = h.template() {
+ t.render(r, ctx, rc, out)?;
+ };
+
+ rc.pop_block();
+ Ok(())
+ } else if let Some(t) = h.inverse() {
+ t.render(r, ctx, rc, out)
+ } else if r.strict_mode() {
+ Err(RenderError::strict_error(param.relative_path()))
+ } else {
+ Ok(())
+ }
+ }
+}
+
+pub static WITH_HELPER: WithHelper = WithHelper;
+
+#[cfg(test)]
+mod test {
+ use crate::json::value::to_json;
+ use crate::registry::Registry;
+
+ #[derive(Serialize)]
+ struct Address {
+ city: String,
+ country: String,
+ }
+
+ #[derive(Serialize)]
+ struct Person {
+ name: String,
+ age: i16,
+ addr: Address,
+ titles: Vec<String>,
+ }
+
+ #[test]
+ fn test_with() {
+ let addr = Address {
+ city: "Beijing".to_string(),
+ country: "China".to_string(),
+ };
+
+ let person = Person {
+ name: "Ning Sun".to_string(),
+ age: 27,
+ addr,
+ titles: vec!["programmer".to_string(), "cartographier".to_string()],
+ };
+
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#with addr}}{{city}}{{/with}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t1", "{{#with notfound}}hello{{else}}world{{/with}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t2", "{{#with addr/country}}{{this}}{{/with}}")
+ .is_ok());
+
+ let r0 = handlebars.render("t0", &person);
+ assert_eq!(r0.ok().unwrap(), "Beijing".to_string());
+
+ let r1 = handlebars.render("t1", &person);
+ assert_eq!(r1.ok().unwrap(), "world".to_string());
+
+ let r2 = handlebars.render("t2", &person);
+ assert_eq!(r2.ok().unwrap(), "China".to_string());
+ }
+
+ #[test]
+ fn test_with_block_param() {
+ let addr = Address {
+ city: "Beijing".to_string(),
+ country: "China".to_string(),
+ };
+
+ let person = Person {
+ name: "Ning Sun".to_string(),
+ age: 27,
+ addr,
+ titles: vec!["programmer".to_string(), "cartographier".to_string()],
+ };
+
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#with addr as |a|}}{{a.city}}{{/with}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t1", "{{#with notfound as |c|}}hello{{else}}world{{/with}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t2", "{{#with addr/country as |t|}}{{t}}{{/with}}")
+ .is_ok());
+
+ let r0 = handlebars.render("t0", &person);
+ assert_eq!(r0.ok().unwrap(), "Beijing".to_string());
+
+ let r1 = handlebars.render("t1", &person);
+ assert_eq!(r1.ok().unwrap(), "world".to_string());
+
+ let r2 = handlebars.render("t2", &person);
+ assert_eq!(r2.ok().unwrap(), "China".to_string());
+ }
+
+ #[test]
+ fn test_with_in_each() {
+ let addr = Address {
+ city: "Beijing".to_string(),
+ country: "China".to_string(),
+ };
+
+ let person = Person {
+ name: "Ning Sun".to_string(),
+ age: 27,
+ addr,
+ titles: vec!["programmer".to_string(), "cartographier".to_string()],
+ };
+
+ let addr2 = Address {
+ city: "Beijing".to_string(),
+ country: "China".to_string(),
+ };
+
+ let person2 = Person {
+ name: "Ning Sun".to_string(),
+ age: 27,
+ addr: addr2,
+ titles: vec!["programmer".to_string(), "cartographier".to_string()],
+ };
+
+ let people = vec![person, person2];
+
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string(
+ "t0",
+ "{{#each this}}{{#with addr}}{{city}}{{/with}}{{/each}}"
+ )
+ .is_ok());
+ assert!(handlebars
+ .register_template_string(
+ "t1",
+ "{{#each this}}{{#with addr}}{{../age}}{{/with}}{{/each}}"
+ )
+ .is_ok());
+ assert!(handlebars
+ .register_template_string(
+ "t2",
+ "{{#each this}}{{#with addr}}{{@../index}}{{/with}}{{/each}}"
+ )
+ .is_ok());
+
+ let r0 = handlebars.render("t0", &people);
+ assert_eq!(r0.ok().unwrap(), "BeijingBeijing".to_string());
+
+ let r1 = handlebars.render("t1", &people);
+ assert_eq!(r1.ok().unwrap(), "2727".to_string());
+
+ let r2 = handlebars.render("t2", &people);
+ assert_eq!(r2.ok().unwrap(), "01".to_string());
+ }
+
+ #[test]
+ fn test_path_up() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{#with a}}{{#with b}}{{../../d}}{{/with}}{{/with}}")
+ .is_ok());
+ let data = btreemap! {
+ "a".to_string() => to_json(&btreemap! {
+ "b".to_string() => vec![btreemap!{"c".to_string() => vec![1]}]
+ }),
+ "d".to_string() => to_json(1)
+ };
+
+ let r0 = handlebars.render("t0", &data);
+ assert_eq!(r0.ok().unwrap(), "1".to_string());
+ }
+
+ #[test]
+ fn test_else_context() {
+ let reg = Registry::new();
+ let template = "{{#with list}}A{{else}}{{foo}}{{/with}}";
+ let input = json!({"list": [], "foo": "bar"});
+ let rendered = reg.render_template(template, &input).unwrap();
+ assert_eq!("bar", rendered);
+ }
+
+ #[test]
+ fn test_derived_value() {
+ let hb = Registry::new();
+ let data = json!({"a": {"b": {"c": "d"}}});
+ let template = "{{#with (lookup a.b \"c\")}}{{this}}{{/with}}";
+ assert_eq!("d", hb.render_template(template, &data).unwrap());
+ }
+
+ #[test]
+ fn test_nested_derived_value() {
+ let hb = Registry::new();
+ let data = json!({"a": {"b": {"c": "d"}}});
+ let template = "{{#with (lookup a \"b\")}}{{#with this}}{{c}}{{/with}}{{/with}}";
+ assert_eq!("d", hb.render_template(template, &data).unwrap());
+ }
+
+ #[test]
+ fn test_strict_with() {
+ let mut hb = Registry::new();
+
+ assert_eq!(
+ hb.render_template("{{#with name}}yes{{/with}}", &json!({}))
+ .unwrap(),
+ ""
+ );
+ assert_eq!(
+ hb.render_template("{{#with name}}yes{{else}}no{{/with}}", &json!({}))
+ .unwrap(),
+ "no"
+ );
+
+ hb.set_strict_mode(true);
+
+ assert!(hb
+ .render_template("{{#with name}}yes{{/with}}", &json!({}))
+ .is_err());
+ assert_eq!(
+ hb.render_template("{{#with name}}yes{{else}}no{{/with}}", &json!({}))
+ .unwrap(),
+ "no"
+ );
+ }
+}
diff --git a/vendor/handlebars/src/helpers/mod.rs b/vendor/handlebars/src/helpers/mod.rs
new file mode 100644
index 000000000..bfd50c1f4
--- /dev/null
+++ b/vendor/handlebars/src/helpers/mod.rs
@@ -0,0 +1,291 @@
+use crate::context::Context;
+use crate::error::RenderError;
+use crate::json::value::ScopedJson;
+use crate::output::Output;
+use crate::registry::Registry;
+use crate::render::{do_escape, Helper, RenderContext};
+
+pub use self::helper_each::EACH_HELPER;
+pub use self::helper_if::{IF_HELPER, UNLESS_HELPER};
+pub use self::helper_log::LOG_HELPER;
+pub use self::helper_lookup::LOOKUP_HELPER;
+pub use self::helper_raw::RAW_HELPER;
+pub use self::helper_with::WITH_HELPER;
+
+/// A type alias for `Result<(), RenderError>`
+pub type HelperResult = Result<(), RenderError>;
+
+/// Helper Definition
+///
+/// Implement `HelperDef` to create custom helpers. You can retrieve useful information from these arguments.
+///
+/// * `&Helper`: current helper template information, contains name, params, hashes and nested template
+/// * `&Registry`: the global registry, you can find templates by name from registry
+/// * `&Context`: the whole data to render, in most case you can use data from `Helper`
+/// * `&mut RenderContext`: you can access data or modify variables (starts with @)/partials in render context, for example, @index of #each. See its document for detail.
+/// * `&mut dyn Output`: where you write output to
+///
+/// By default, you can use a bare function as a helper definition because we have supported unboxed_closure. If you have stateful or configurable helper, you can create a struct to implement `HelperDef`.
+///
+/// ## Define an inline helper
+///
+/// ```
+/// use handlebars::*;
+///
+/// fn upper(h: &Helper<'_, '_>, _: &Handlebars<'_>, _: &Context, rc: &mut RenderContext<'_, '_>, out: &mut Output)
+/// -> HelperResult {
+/// // get parameter from helper or throw an error
+/// let param = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
+/// out.write(param.to_uppercase().as_ref())?;
+/// Ok(())
+/// }
+/// ```
+///
+/// ## Define block helper
+///
+/// Block helper is like `#if` or `#each` which has a inner template and an optional *inverse* template (the template in else branch). You can access the inner template by `helper.template()` and `helper.inverse()`. In most cases you will just call `render` on it.
+///
+/// ```
+/// use handlebars::*;
+///
+/// fn dummy_block<'reg, 'rc>(
+/// h: &Helper<'reg, 'rc>,
+/// r: &'reg Handlebars<'reg>,
+/// ctx: &'rc Context,
+/// rc: &mut RenderContext<'reg, 'rc>,
+/// out: &mut dyn Output,
+/// ) -> HelperResult {
+/// h.template()
+/// .map(|t| t.render(r, ctx, rc, out))
+/// .unwrap_or(Ok(()))
+/// }
+/// ```
+///
+/// ## Define helper function using macro
+///
+/// In most cases you just need some simple function to call from templates. We have a `handlebars_helper!` macro to simplify the job.
+///
+/// ```
+/// use handlebars::*;
+///
+/// handlebars_helper!(plus: |x: i64, y: i64| x + y);
+///
+/// let mut hbs = Handlebars::new();
+/// hbs.register_helper("plus", Box::new(plus));
+/// ```
+///
+pub trait HelperDef {
+ /// A simplified api to define helper
+ ///
+ /// To implement your own `call_inner`, you will return a new `ScopedJson`
+ /// which has a JSON value computed from current context.
+ ///
+ /// ### Calling from subexpression
+ ///
+ /// When calling the helper as a subexpression, the value and its type can
+ /// be received by upper level helpers.
+ ///
+ /// Note that the value can be `json!(null)` which is treated as `false` in
+ /// helpers like `if` and rendered as empty string.
+ fn call_inner<'reg: 'rc, 'rc>(
+ &self,
+ _: &Helper<'reg, 'rc>,
+ _: &'reg Registry<'reg>,
+ _: &'rc Context,
+ _: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ Err(RenderError::unimplemented())
+ }
+
+ /// A complex version of helper interface.
+ ///
+ /// This function offers `Output`, which you can write custom string into
+ /// and render child template. Helpers like `if` and `each` are implemented
+ /// with this. Because the data written into `Output` are typically without
+ /// type information. So helpers defined by this function are not composable.
+ ///
+ /// ### Calling from subexpression
+ ///
+ /// Although helpers defined by this are not composable, when called from
+ /// subexpression, handlebars tries to parse the string output as JSON to
+ /// re-build its type. This can be buggy with numrical and other literal values.
+ /// So it is not recommended to use these helpers in subexpression.
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> HelperResult {
+ match self.call_inner(h, r, ctx, rc) {
+ Ok(result) => {
+ if r.strict_mode() && result.is_missing() {
+ Err(RenderError::strict_error(None))
+ } else {
+ // auto escape according to settings
+ let output = do_escape(r, rc, result.render());
+ out.write(output.as_ref())?;
+ Ok(())
+ }
+ }
+ Err(e) => {
+ if e.is_unimplemented() {
+ // default implementation, do nothing
+ Ok(())
+ } else {
+ Err(e)
+ }
+ }
+ }
+ }
+}
+
+/// implement HelperDef for bare function so we can use function as helper
+impl<
+ F: for<'reg, 'rc> Fn(
+ &Helper<'reg, 'rc>,
+ &'reg Registry<'reg>,
+ &'rc Context,
+ &mut RenderContext<'reg, 'rc>,
+ &mut dyn Output,
+ ) -> HelperResult,
+ > HelperDef for F
+{
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> HelperResult {
+ (*self)(h, r, ctx, rc, out)
+ }
+}
+
+mod block_util;
+mod helper_each;
+pub(crate) mod helper_extras;
+mod helper_if;
+mod helper_log;
+mod helper_lookup;
+mod helper_raw;
+mod helper_with;
+#[cfg(feature = "script_helper")]
+pub(crate) mod scripting;
+
+// pub type HelperDef = for <'a, 'b, 'c> Fn<(&'a Context, &'b Helper, &'b Registry, &'c mut RenderContext), Result<String, RenderError>>;
+//
+// pub fn helper_dummy (ctx: &Context, h: &Helper, r: &Registry, rc: &mut RenderContext) -> Result<String, RenderError> {
+// h.template().unwrap().render(ctx, r, rc).unwrap()
+// }
+//
+#[cfg(test)]
+mod test {
+ use std::collections::BTreeMap;
+
+ use crate::context::Context;
+ use crate::error::RenderError;
+ use crate::helpers::HelperDef;
+ use crate::json::value::JsonRender;
+ use crate::output::Output;
+ use crate::registry::Registry;
+ use crate::render::{Helper, RenderContext, Renderable};
+
+ #[derive(Clone, Copy)]
+ struct MetaHelper;
+
+ impl HelperDef for MetaHelper {
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> Result<(), RenderError> {
+ let v = h.param(0).unwrap();
+
+ if !h.is_block() {
+ let output = format!("{}:{}", h.name(), v.value().render());
+ out.write(output.as_ref())?;
+ } else {
+ let output = format!("{}:{}", h.name(), v.value().render());
+ out.write(output.as_ref())?;
+ out.write("->")?;
+ h.template().unwrap().render(r, ctx, rc, out)?;
+ };
+ Ok(())
+ }
+ }
+
+ #[test]
+ fn test_meta_helper() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{foo this}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t1", "{{#bar this}}nice{{/bar}}")
+ .is_ok());
+
+ let meta_helper = MetaHelper;
+ handlebars.register_helper("helperMissing", Box::new(meta_helper));
+ handlebars.register_helper("blockHelperMissing", Box::new(meta_helper));
+
+ let r0 = handlebars.render("t0", &true);
+ assert_eq!(r0.ok().unwrap(), "foo:true".to_string());
+
+ let r1 = handlebars.render("t1", &true);
+ assert_eq!(r1.ok().unwrap(), "bar:true->nice".to_string());
+ }
+
+ #[test]
+ fn test_helper_for_subexpression() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t2", "{{foo value=(bar 0)}}")
+ .is_ok());
+
+ handlebars.register_helper(
+ "helperMissing",
+ Box::new(
+ |h: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> {
+ let output = format!("{}{}", h.name(), h.param(0).unwrap().value());
+ out.write(output.as_ref())?;
+ Ok(())
+ },
+ ),
+ );
+ handlebars.register_helper(
+ "foo",
+ Box::new(
+ |h: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> {
+ let output = format!("{}", h.hash_get("value").unwrap().value().render());
+ out.write(output.as_ref())?;
+ Ok(())
+ },
+ ),
+ );
+
+ let mut data = BTreeMap::new();
+ // handlebars should never try to lookup this value because
+ // subexpressions are now resolved as string literal
+ data.insert("bar0".to_string(), true);
+
+ let r2 = handlebars.render("t2", &data);
+
+ assert_eq!(r2.ok().unwrap(), "bar0".to_string());
+ }
+}
diff --git a/vendor/handlebars/src/helpers/scripting.rs b/vendor/handlebars/src/helpers/scripting.rs
new file mode 100644
index 000000000..cec3b9763
--- /dev/null
+++ b/vendor/handlebars/src/helpers/scripting.rs
@@ -0,0 +1,111 @@
+use std::collections::{BTreeMap, HashMap};
+
+use crate::context::Context;
+use crate::error::RenderError;
+use crate::helpers::HelperDef;
+use crate::json::value::{PathAndJson, ScopedJson};
+use crate::registry::Registry;
+use crate::render::{Helper, RenderContext};
+
+use rhai::serde::{from_dynamic, to_dynamic};
+use rhai::{Dynamic, Engine, Scope, AST};
+
+use serde_json::value::Value as Json;
+
+pub(crate) struct ScriptHelper {
+ pub(crate) script: AST,
+}
+
+#[inline]
+fn call_script_helper<'reg: 'rc, 'rc>(
+ params: &[PathAndJson<'reg, 'rc>],
+ hash: &BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
+ engine: &Engine,
+ script: &AST,
+) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ let params: Dynamic = to_dynamic(params.iter().map(|p| p.value()).collect::<Vec<&Json>>())?;
+
+ let hash: Dynamic = to_dynamic(
+ hash.iter()
+ .map(|(k, v)| ((*k).to_owned(), v.value()))
+ .collect::<HashMap<String, &Json>>(),
+ )?;
+
+ let mut scope = Scope::new();
+ scope.push_dynamic("params", params);
+ scope.push_dynamic("hash", hash);
+
+ let result = engine
+ .eval_ast_with_scope::<Dynamic>(&mut scope, script)
+ .map_err(RenderError::from)?;
+
+ let result_json: Json = from_dynamic(&result)?;
+
+ Ok(ScopedJson::Derived(result_json))
+}
+
+impl HelperDef for ScriptHelper {
+ fn call_inner<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ reg: &'reg Registry<'reg>,
+ _ctx: &'rc Context,
+ _rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ call_script_helper(h.params(), h.hash(), &reg.engine, &self.script)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::json::value::{PathAndJson, ScopedJson};
+ use rhai::Engine;
+
+ #[test]
+ fn test_dynamic_convert() {
+ let j0 = json! {
+ [{"name": "tomcat"}, {"name": "jetty"}]
+ };
+
+ let d0 = to_dynamic(&j0).unwrap();
+ assert_eq!("array", d0.type_name());
+
+ let j1 = json!({
+ "name": "tomcat",
+ "value": 4000,
+ });
+
+ let d1 = to_dynamic(&j1).unwrap();
+ assert_eq!("map", d1.type_name());
+ }
+
+ #[test]
+ fn test_to_json() {
+ let d0 = Dynamic::from("tomcat".to_owned());
+
+ assert_eq!(
+ Json::String("tomcat".to_owned()),
+ from_dynamic::<Json>(&d0).unwrap()
+ );
+ }
+
+ #[test]
+ fn test_script_helper_value_access() {
+ let engine = Engine::new();
+
+ let script = "let plen = len(params); let p0 = params[0]; let hlen = len(hash); let hme = hash[\"me\"]; plen + \",\" + p0 + \",\" + hlen + \",\" + hme";
+ let ast = engine.compile(&script).unwrap();
+
+ let params = vec![PathAndJson::new(None, ScopedJson::Derived(json!(true)))];
+ let hash = btreemap! {
+ "me" => PathAndJson::new(None, ScopedJson::Derived(json!("no"))),
+ "you" => PathAndJson::new(None, ScopedJson::Derived(json!("yes"))),
+ };
+
+ let result = call_script_helper(&params, &hash, &engine, &ast)
+ .unwrap()
+ .render();
+ assert_eq!("1,true,2,no", &result);
+ }
+}
diff --git a/vendor/handlebars/src/json/mod.rs b/vendor/handlebars/src/json/mod.rs
new file mode 100644
index 000000000..85f783d0c
--- /dev/null
+++ b/vendor/handlebars/src/json/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod path;
+pub(crate) mod value;
diff --git a/vendor/handlebars/src/json/path.rs b/vendor/handlebars/src/json/path.rs
new file mode 100644
index 000000000..8270371b2
--- /dev/null
+++ b/vendor/handlebars/src/json/path.rs
@@ -0,0 +1,138 @@
+use std::iter::Peekable;
+
+use pest::iterators::Pair;
+use pest::Parser;
+
+use crate::error::RenderError;
+use crate::grammar::{HandlebarsParser, Rule};
+
+#[derive(PartialEq, Clone, Debug)]
+pub enum PathSeg {
+ Named(String),
+ Ruled(Rule),
+}
+
+/// Represents the Json path in templates.
+///
+/// It can be either a local variable like `@first`, `../@index`,
+/// or a normal relative path like `a/b/c`.
+#[derive(PartialEq, Clone, Debug)]
+pub enum Path {
+ Relative((Vec<PathSeg>, String)),
+ Local((usize, String, String)),
+}
+
+impl Path {
+ pub(crate) fn new(raw: &str, segs: Vec<PathSeg>) -> Path {
+ if let Some((level, name)) = get_local_path_and_level(&segs) {
+ Path::Local((level, name, raw.to_owned()))
+ } else {
+ Path::Relative((segs, raw.to_owned()))
+ }
+ }
+
+ pub fn parse(raw: &str) -> Result<Path, RenderError> {
+ HandlebarsParser::parse(Rule::path, raw)
+ .map(|p| {
+ let parsed = p.flatten();
+ let segs = parse_json_path_from_iter(&mut parsed.peekable(), raw.len());
+ Ok(Path::new(raw, segs))
+ })
+ .map_err(|_| RenderError::new("Invalid JSON path"))?
+ }
+
+ pub(crate) fn raw(&self) -> &str {
+ match self {
+ Path::Relative((_, ref raw)) => raw,
+ Path::Local((_, _, ref raw)) => raw,
+ }
+ }
+
+ pub(crate) fn current() -> Path {
+ Path::Relative((Vec::with_capacity(0), "".to_owned()))
+ }
+
+ // for test only
+ pub(crate) fn with_named_paths(name_segs: &[&str]) -> Path {
+ let segs = name_segs
+ .iter()
+ .map(|n| PathSeg::Named((*n).to_string()))
+ .collect();
+ Path::Relative((segs, name_segs.join("/")))
+ }
+
+ // for test only
+ pub(crate) fn segs(&self) -> Option<&[PathSeg]> {
+ match self {
+ Path::Relative((segs, _)) => Some(segs),
+ _ => None,
+ }
+ }
+}
+
+fn get_local_path_and_level(paths: &[PathSeg]) -> Option<(usize, String)> {
+ paths.get(0).and_then(|seg| {
+ if seg == &PathSeg::Ruled(Rule::path_local) {
+ let mut level = 0;
+ while paths.get(level + 1)? == &PathSeg::Ruled(Rule::path_up) {
+ level += 1;
+ }
+ if let Some(PathSeg::Named(name)) = paths.get(level + 1) {
+ Some((level, name.clone()))
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ })
+}
+
+pub(crate) fn parse_json_path_from_iter<'a, I>(it: &mut Peekable<I>, limit: usize) -> Vec<PathSeg>
+where
+ I: Iterator<Item = Pair<'a, Rule>>,
+{
+ let mut path_stack = Vec::with_capacity(5);
+ while let Some(n) = it.peek() {
+ let span = n.as_span();
+ if span.end() > limit {
+ break;
+ }
+
+ match n.as_rule() {
+ Rule::path_root => {
+ path_stack.push(PathSeg::Ruled(Rule::path_root));
+ }
+ Rule::path_local => {
+ path_stack.push(PathSeg::Ruled(Rule::path_local));
+ }
+ Rule::path_up => {
+ path_stack.push(PathSeg::Ruled(Rule::path_up));
+ }
+ Rule::path_id | Rule::path_raw_id => {
+ let name = n.as_str();
+ if name != "this" {
+ path_stack.push(PathSeg::Named(name.to_string()));
+ }
+ }
+ _ => {}
+ }
+
+ it.next();
+ }
+
+ path_stack
+}
+
+pub(crate) fn merge_json_path(path_stack: &mut Vec<String>, relative_path: &[PathSeg]) {
+ for seg in relative_path {
+ match seg {
+ PathSeg::Named(ref s) => {
+ path_stack.push(s.to_owned());
+ }
+ PathSeg::Ruled(Rule::path_root) => {}
+ PathSeg::Ruled(Rule::path_up) => {}
+ _ => {}
+ }
+ }
+}
diff --git a/vendor/handlebars/src/json/value.rs b/vendor/handlebars/src/json/value.rs
new file mode 100644
index 000000000..863eab0b8
--- /dev/null
+++ b/vendor/handlebars/src/json/value.rs
@@ -0,0 +1,202 @@
+use serde::Serialize;
+use serde_json::value::{to_value, Value as Json};
+
+pub(crate) static DEFAULT_VALUE: Json = Json::Null;
+
+/// A JSON wrapper designed for handlebars internal use case
+///
+/// * Constant: the JSON value hardcoded into template
+/// * Context: the JSON value referenced in your provided data context
+/// * Derived: the owned JSON value computed during rendering process
+///
+#[derive(Debug)]
+pub enum ScopedJson<'reg: 'rc, 'rc> {
+ Constant(&'reg Json),
+ Derived(Json),
+ // represents a json reference to context value, its full path
+ Context(&'rc Json, Vec<String>),
+ Missing,
+}
+
+impl<'reg: 'rc, 'rc> ScopedJson<'reg, 'rc> {
+ /// get the JSON reference
+ pub fn as_json(&self) -> &Json {
+ match self {
+ ScopedJson::Constant(j) => j,
+ ScopedJson::Derived(ref j) => j,
+ ScopedJson::Context(j, _) => j,
+ _ => &DEFAULT_VALUE,
+ }
+ }
+
+ pub fn render(&self) -> String {
+ self.as_json().render()
+ }
+
+ pub fn is_missing(&self) -> bool {
+ matches!(self, ScopedJson::Missing)
+ }
+
+ pub fn into_derived(self) -> ScopedJson<'reg, 'rc> {
+ let v = self.as_json();
+ ScopedJson::Derived(v.clone())
+ }
+
+ pub fn context_path(&self) -> Option<&Vec<String>> {
+ match self {
+ ScopedJson::Context(_, ref p) => Some(p),
+ _ => None,
+ }
+ }
+}
+
+impl<'reg: 'rc, 'rc> From<Json> for ScopedJson<'reg, 'rc> {
+ fn from(v: Json) -> ScopedJson<'reg, 'rc> {
+ ScopedJson::Derived(v)
+ }
+}
+
+/// Json wrapper that holds the Json value and reference path information
+///
+#[derive(Debug)]
+pub struct PathAndJson<'reg, 'rc> {
+ relative_path: Option<String>,
+ value: ScopedJson<'reg, 'rc>,
+}
+
+impl<'reg: 'rc, 'rc> PathAndJson<'reg, 'rc> {
+ pub fn new(
+ relative_path: Option<String>,
+ value: ScopedJson<'reg, 'rc>,
+ ) -> PathAndJson<'reg, 'rc> {
+ PathAndJson {
+ relative_path,
+ value,
+ }
+ }
+
+ /// Returns relative path when the value is referenced
+ /// If the value is from a literal, the path is `None`
+ pub fn relative_path(&self) -> Option<&String> {
+ self.relative_path.as_ref()
+ }
+
+ /// Returns full path to this value if any
+ pub fn context_path(&self) -> Option<&Vec<String>> {
+ self.value.context_path()
+ }
+
+ /// Returns the value
+ pub fn value(&self) -> &Json {
+ self.value.as_json()
+ }
+
+ /// Test if value is missing
+ pub fn is_value_missing(&self) -> bool {
+ self.value.is_missing()
+ }
+
+ pub fn render(&self) -> String {
+ self.value.render()
+ }
+}
+
+/// Render Json data with default format
+pub trait JsonRender {
+ fn render(&self) -> String;
+}
+
+pub trait JsonTruthy {
+ fn is_truthy(&self, include_zero: bool) -> bool;
+}
+
+impl JsonRender for Json {
+ fn render(&self) -> String {
+ match *self {
+ Json::String(ref s) => s.to_string(),
+ Json::Bool(i) => i.to_string(),
+ Json::Number(ref n) => n.to_string(),
+ Json::Null => "".to_owned(),
+ Json::Array(ref a) => {
+ let mut buf = String::new();
+ buf.push('[');
+ for i in a.iter() {
+ buf.push_str(i.render().as_ref());
+ buf.push_str(", ");
+ }
+ buf.push(']');
+ buf
+ }
+ Json::Object(_) => "[object]".to_owned(),
+ }
+ }
+}
+
+/// Convert any serializable data into Serde Json type
+pub fn to_json<T>(src: T) -> Json
+where
+ T: Serialize,
+{
+ to_value(src).unwrap_or_default()
+}
+
+pub fn as_string(src: &Json) -> Option<&str> {
+ src.as_str()
+}
+
+impl JsonTruthy for Json {
+ fn is_truthy(&self, include_zero: bool) -> bool {
+ match *self {
+ Json::Bool(ref i) => *i,
+ Json::Number(ref n) => {
+ if include_zero {
+ n.as_f64().map(|f| !f.is_nan()).unwrap_or(false)
+ } else {
+ // there is no inifity in json/serde_json
+ n.as_f64().map(|f| f.is_normal()).unwrap_or(false)
+ }
+ }
+ Json::Null => false,
+ Json::String(ref i) => !i.is_empty(),
+ Json::Array(ref i) => !i.is_empty(),
+ Json::Object(ref i) => !i.is_empty(),
+ }
+ }
+}
+
+#[test]
+fn test_json_render() {
+ let raw = "<p>Hello world</p>\n<p thing=\"hello\"</p>";
+ let thing = Json::String(raw.to_string());
+
+ assert_eq!(raw, thing.render());
+}
+
+#[test]
+fn test_json_number_truthy() {
+ use std::f64;
+ assert!(json!(16i16).is_truthy(false));
+ assert!(json!(16i16).is_truthy(true));
+
+ assert!(json!(0i16).is_truthy(true));
+ assert!(!json!(0i16).is_truthy(false));
+
+ assert!(json!(1.0f64).is_truthy(false));
+ assert!(json!(1.0f64).is_truthy(true));
+
+ assert!(json!(Some(16i16)).is_truthy(false));
+ assert!(json!(Some(16i16)).is_truthy(true));
+
+ assert!(!json!(None as Option<i16>).is_truthy(false));
+ assert!(!json!(None as Option<i16>).is_truthy(true));
+
+ assert!(!json!(f64::NAN).is_truthy(false));
+ assert!(!json!(f64::NAN).is_truthy(true));
+
+ // there is no infinity in json/serde_json
+ // assert!(json!(f64::INFINITY).is_truthy(false));
+ // assert!(json!(f64::INFINITY).is_truthy(true));
+
+ // assert!(json!(f64::NEG_INFINITY).is_truthy(false));
+ // assert!(json!(f64::NEG_INFINITY).is_truthy(true));
+}
diff --git a/vendor/handlebars/src/lib.rs b/vendor/handlebars/src/lib.rs
new file mode 100644
index 000000000..7e0aac830
--- /dev/null
+++ b/vendor/handlebars/src/lib.rs
@@ -0,0 +1,417 @@
+#![doc(html_root_url = "https://docs.rs/handlebars/4.1.0")]
+#![cfg_attr(docsrs, feature(doc_cfg))]
+//! # Handlebars
+//!
+//! [Handlebars](http://handlebarsjs.com/) is a modern and extensible templating solution originally created in the JavaScript world. It's used by many popular frameworks like [Ember.js](http://emberjs.com) and Chaplin. It's also ported to some other platforms such as [Java](https://github.com/jknack/handlebars.java).
+//!
+//! And this is handlebars Rust implementation, designed for general purpose text generation.
+//!
+//! ## Quick Start
+//!
+//! ```
+//! use std::collections::BTreeMap;
+//! use handlebars::Handlebars;
+//!
+//! fn main() {
+//! // create the handlebars registry
+//! let mut handlebars = Handlebars::new();
+//!
+//! // register the template. The template string will be verified and compiled.
+//! let source = "hello {{world}}";
+//! assert!(handlebars.register_template_string("t1", source).is_ok());
+//!
+//! // Prepare some data.
+//! //
+//! // The data type should implements `serde::Serialize`
+//! let mut data = BTreeMap::new();
+//! data.insert("world".to_string(), "世界!".to_string());
+//! assert_eq!(handlebars.render("t1", &data).unwrap(), "hello 世界!");
+//! }
+//! ```
+//!
+//! In this example, we created a template registry and registered a template named `t1`.
+//! Then we rendered a `BTreeMap` with an entry of key `world`, the result is just what
+//! we expected.
+//!
+//! I recommend you to walk through handlebars.js' [intro page](http://handlebarsjs.com)
+//! if you are not quite familiar with the template language itself.
+//!
+//! ## Features
+//!
+//! Handlebars is a real-world templating system that you can use to build
+//! your application without pain.
+//!
+//! ### Isolation of Rust and HTML
+//!
+//! This library doesn't attempt to use some macro magic to allow you to
+//! write your template within your rust code. I admit that it's fun to do
+//! that but it doesn't fit real-world use cases.
+//!
+//! ### Limited but essential control structures built-in
+//!
+//! Only essential control directives `if` and `each` are built-in. This
+//! prevents you from putting too much application logic into your template.
+//!
+//! ### Extensible helper system
+//!
+//! Helper is the control system of handlebars language. In the original JavaScript
+//! version, you can implement your own helper with JavaScript.
+//!
+//! Handlebars-rust offers similar mechanism that custom helper can be defined with
+//! rust function, or [rhai](https://github.com/jonathandturner/rhai) script.
+//!
+//! The built-in helpers like `if` and `each` were written with these
+//! helper APIs and the APIs are fully available to developers.
+//!
+//! ### Auto-reload in dev mode
+//!
+//! By turning on `dev_mode`, handlebars auto reloads any template and scripts that
+//! loaded from files or directory. This can be handy for template development.
+//!
+//! ### Template inheritance
+//!
+//! Every time I look into a templating system, I will investigate its
+//! support for [template inheritance][t].
+//!
+//! [t]: https://docs.djangoproject.com/en/1.9/ref/templates/language/#template-inheritance
+//!
+//! Template include is not sufficient for template reuse. In most cases
+//! you will need a skeleton of page as parent (header, footer, etc.), and
+//! embed your page into this parent.
+//!
+//! You can find a real example of template inheritance in
+//! `examples/partials.rs` and templates used by this file.
+//!
+//! ### Strict mode
+//!
+//! Handlebars, the language designed to work with JavaScript, has no
+//! strict restriction on accessing nonexistent fields or indexes. It
+//! generates empty strings for such cases. However, in Rust we want to be
+//! a little stricter sometimes.
+//!
+//! By enabling `strict_mode` on handlebars:
+//!
+//! ```
+//! # use handlebars::Handlebars;
+//! # let mut handlebars = Handlebars::new();
+//! handlebars.set_strict_mode(true);
+//! ```
+//!
+//! You will get a `RenderError` when accessing fields that do not exist.
+//!
+//! ## Limitations
+//!
+//! ### Compatibility with original JavaScript version
+//!
+//! This implementation is **not fully compatible** with the original JavaScript version.
+//!
+//! First of all, mustache blocks are not supported. I suggest you to use `#if` and `#each` for
+//! the same functionality.
+//!
+//! There are some other minor features missing:
+//!
+//! * Chained else [#12](https://github.com/sunng87/handlebars-rust/issues/12)
+//!
+//! Feel free to file an issue on [github](https://github.com/sunng87/handlebars-rust/issues) if
+//! you find missing features.
+//!
+//! ### Types
+//!
+//! As a static typed language, it's a little verbose to use handlebars.
+//! Handlebars templating language is designed against JSON data type. In rust,
+//! we will convert user's structs, vectors or maps into Serde-Json's `Value` type
+//! in order to use in templates. You have to make sure your data implements the
+//! `Serialize` trait from the [Serde](https://serde.rs) project.
+//!
+//! ## Usage
+//!
+//! ### Template Creation and Registration
+//!
+//! Templates are created from `String`s and registered to `Handlebars` with a name.
+//!
+//! ```
+//! # extern crate handlebars;
+//!
+//! use handlebars::Handlebars;
+//!
+//! # fn main() {
+//! let mut handlebars = Handlebars::new();
+//! let source = "hello {{world}}";
+//!
+//! assert!(handlebars.register_template_string("t1", source).is_ok())
+//! # }
+//! ```
+//!
+//! On registration, the template is parsed, compiled and cached in the registry. So further
+//! usage will benefit from the one-time work. Also features like include, inheritance
+//! that involves template reference requires you to register those template first with
+//! a name so the registry can find it.
+//!
+//! If you template is small or just to experiment, you can use `render_template` API
+//! without registration.
+//!
+//! ```
+//! # use std::error::Error;
+//! use handlebars::Handlebars;
+//! use std::collections::BTreeMap;
+//!
+//! # fn main() -> Result<(), Box<Error>> {
+//! let mut handlebars = Handlebars::new();
+//! let source = "hello {{world}}";
+//!
+//! let mut data = BTreeMap::new();
+//! data.insert("world".to_string(), "世界!".to_string());
+//! assert_eq!(handlebars.render_template(source, &data)?, "hello 世界!".to_owned());
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! ### Rendering Something
+//!
+//! Since handlebars is originally based on JavaScript type system. It supports dynamic features like duck-typing, truthy/falsey values. But for a static language like Rust, this is a little difficult. As a solution, we are using the `serde_json::value::Value` internally for data rendering.
+//!
+//! That means, if you want to render something, you have to ensure the data type implements the `serde::Serialize` trait. Most rust internal types already have that trait. Use `#derive[Serialize]` for your types to generate default implementation.
+//!
+//! You can use default `render` function to render a template into `String`. From 0.9, there's `render_to_write` to render text into anything of `std::io::Write`.
+//!
+//! ```
+//! # use std::error::Error;
+//! # #[macro_use]
+//! # extern crate serde_derive;
+//! # extern crate handlebars;
+//!
+//! use handlebars::Handlebars;
+//!
+//! #[derive(Serialize)]
+//! struct Person {
+//! name: String,
+//! age: i16,
+//! }
+//!
+//! # fn main() -> Result<(), Box<Error>> {
+//! let source = "Hello, {{name}}";
+//!
+//! let mut handlebars = Handlebars::new();
+//! assert!(handlebars.register_template_string("hello", source).is_ok());
+//!
+//!
+//! let data = Person {
+//! name: "Ning Sun".to_string(),
+//! age: 27
+//! };
+//! assert_eq!(handlebars.render("hello", &data)?, "Hello, Ning Sun".to_owned());
+//! # Ok(())
+//! # }
+//! #
+//! ```
+//!
+//! Or if you don't need the template to be cached or referenced by other ones, you can
+//! simply render it without registering.
+//!
+//! ```
+//! # use std::error::Error;
+//! # #[macro_use]
+//! # extern crate serde_derive;
+//! # extern crate handlebars;
+//! use handlebars::Handlebars;
+//! # #[derive(Serialize)]
+//! # struct Person {
+//! # name: String,
+//! # age: i16,
+//! # }
+//!
+//! # fn main() -> Result<(), Box<dyn Error>> {
+//! let source = "Hello, {{name}}";
+//!
+//! let mut handlebars = Handlebars::new();
+//!
+//! let data = Person {
+//! name: "Ning Sun".to_string(),
+//! age: 27
+//! };
+//! assert_eq!(handlebars.render_template("Hello, {{name}}", &data)?,
+//! "Hello, Ning Sun".to_owned());
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! #### Escaping
+//!
+//! As per the handlebars spec, output using `{{expression}}` is escaped by default (to be precise, the characters `&"<>` are replaced by their respective html / xml entities). However, since the use cases of a rust template engine are probably a bit more diverse than those of a JavaScript one, this implementation allows the user to supply a custom escape function to be used instead. For more information see the `EscapeFn` type and `Handlebars::register_escape_fn()` method. In particular, `no_escape()` can be used as the escape function if no escaping at all should be performed.
+//!
+//! ### Custom Helper
+//!
+//! Handlebars is nothing without helpers. You can also create your own helpers with rust. Helpers in handlebars-rust are custom struct implements the `HelperDef` trait, concretely, the `call` function. For your convenience, most of stateless helpers can be implemented as bare functions.
+//!
+//! ```
+//! use std::io::Write;
+//! # use std::error::Error;
+//! use handlebars::{Handlebars, HelperDef, RenderContext, Helper, Context, JsonRender, HelperResult, Output, RenderError};
+//!
+//! // implement by a structure impls HelperDef
+//! #[derive(Clone, Copy)]
+//! struct SimpleHelper;
+//!
+//! impl HelperDef for SimpleHelper {
+//! fn call<'reg: 'rc, 'rc>(&self, h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
+//! let param = h.param(0).unwrap();
+//!
+//! out.write("1st helper: ")?;
+//! out.write(param.value().render().as_ref())?;
+//! Ok(())
+//! }
+//! }
+//!
+//! // implement via bare function
+//! fn another_simple_helper (h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output) -> HelperResult {
+//! let param = h.param(0).unwrap();
+//!
+//! out.write("2nd helper: ")?;
+//! out.write(param.value().render().as_ref())?;
+//! Ok(())
+//! }
+//!
+//!
+//! # fn main() -> Result<(), Box<dyn Error>> {
+//! let mut handlebars = Handlebars::new();
+//! handlebars.register_helper("simple-helper", Box::new(SimpleHelper));
+//! handlebars.register_helper("another-simple-helper", Box::new(another_simple_helper));
+//! // via closure
+//! handlebars.register_helper("closure-helper",
+//! Box::new(|h: &Helper, r: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output| -> HelperResult {
+//! let param = h.param(0).ok_or(RenderError::new("param not found"))?;
+//!
+//! out.write("3rd helper: ")?;
+//! out.write(param.value().render().as_ref())?;
+//! Ok(())
+//! }));
+//!
+//! let tpl = "{{simple-helper 1}}\n{{another-simple-helper 2}}\n{{closure-helper 3}}";
+//! assert_eq!(handlebars.render_template(tpl, &())?,
+//! "1st helper: 1\n2nd helper: 2\n3rd helper: 3".to_owned());
+//! # Ok(())
+//! # }
+//!
+//! ```
+//!
+//! Data available to helper can be found in [Helper](struct.Helper.html). And there are more
+//! examples in [HelperDef](trait.HelperDef.html) page.
+//!
+//! You can learn more about helpers by looking into source code of built-in helpers.
+//!
+//!
+//! ### Script Helper
+//!
+//! Like our JavaScript counterparts, handlebars allows user to define simple helpers with
+//! a scripting language, [rhai](https://docs.rs/crate/rhai/). This can be enabled by
+//! turning on `script_helper` feature flag.
+//!
+//! A sample script:
+//!
+//! ```handlebars
+//! {{percent 0.34 label="%"}}
+//! ```
+//!
+//! ```rhai
+//! // percent.rhai
+//! // get first parameter from `params` array
+//! let value = params[0];
+//! // get key value pair `label` from `hash` map
+//! let label = hash["label"];
+//!
+//! // compute the final string presentation
+//! (value * 100).to_string() + label
+//! ```
+//!
+//! A runnable [example](https://github.com/sunng87/handlebars-rust/blob/master/examples/script.rs) can be find in the repo.
+//!
+//! #### Built-in Helpers
+//!
+//! * `{{{{raw}}}} ... {{{{/raw}}}}` escape handlebars expression within the block
+//! * `{{#if ...}} ... {{else}} ... {{/if}}` if-else block
+//! (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#if) on how to use this helper.)
+//! * `{{#unless ...}} ... {{else}} .. {{/unless}}` if-not-else block
+//! (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#unless) on how to use this helper.)
+//! * `{{#each ...}} ... {{/each}}` iterates over an array or object. Handlebars-rust doesn't support mustache iteration syntax so use `each` instead.
+//! (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#each) on how to use this helper.)
+//! * `{{#with ...}} ... {{/with}}` change current context. Similar to `{{#each}}`, used for replace corresponding mustache syntax.
+//! (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#with) on how to use this helper.)
+//! * `{{lookup ... ...}}` get value from array by `@index` or `@key`
+//! (See [the handlebarjs documentation](https://handlebarsjs.com/guide/builtin-helpers.html#lookup) on how to use this helper.)
+//! * `{{> ...}}` include template by its name
+//! * `{{log ...}}` log value with rust logger, default level: INFO. Currently you cannot change the level.
+//! * Boolean helpers that can be used in `if` as subexpression, for example `{{#if (gt 2 1)}} ...`:
+//! * `eq`
+//! * `ne`
+//! * `gt`
+//! * `gte`
+//! * `lt`
+//! * `lte`
+//! * `and`
+//! * `or`
+//! * `not`
+//! * `{{len ...}}` returns length of array/object/string
+//!
+//! ### Template inheritance
+//!
+//! Handlebars.js' partial system is fully supported in this implementation.
+//! Check [example](https://github.com/sunng87/handlebars-rust/blob/master/examples/partials.rs#L49) for details.
+//!
+//!
+
+#![allow(dead_code, clippy::upper_case_acronyms)]
+#![warn(rust_2018_idioms)]
+#![recursion_limit = "200"]
+
+#[cfg(not(feature = "no_logging"))]
+#[macro_use]
+extern crate log;
+
+#[cfg(test)]
+#[macro_use]
+extern crate maplit;
+#[macro_use]
+extern crate pest_derive;
+#[macro_use]
+extern crate quick_error;
+#[cfg(test)]
+#[macro_use]
+extern crate serde_derive;
+
+#[allow(unused_imports)]
+#[macro_use]
+extern crate serde_json;
+
+pub use self::block::{BlockContext, BlockParams};
+pub use self::context::Context;
+pub use self::decorators::DecoratorDef;
+pub use self::error::{RenderError, TemplateError};
+pub use self::helpers::{HelperDef, HelperResult};
+pub use self::json::path::Path;
+pub use self::json::value::{to_json, JsonRender, PathAndJson, ScopedJson};
+pub use self::output::{Output, StringOutput};
+pub use self::registry::{html_escape, no_escape, EscapeFn, Registry as Handlebars};
+pub use self::render::{Decorator, Evaluable, Helper, RenderContext, Renderable};
+pub use self::template::Template;
+
+#[doc(hidden)]
+pub use self::serde_json::Value as JsonValue;
+
+#[macro_use]
+mod macros;
+mod block;
+mod context;
+mod decorators;
+mod error;
+mod grammar;
+mod helpers;
+mod json;
+mod local_vars;
+mod output;
+mod partial;
+mod registry;
+mod render;
+mod sources;
+mod support;
+pub mod template;
+mod util;
diff --git a/vendor/handlebars/src/local_vars.rs b/vendor/handlebars/src/local_vars.rs
new file mode 100644
index 000000000..2a34b1f16
--- /dev/null
+++ b/vendor/handlebars/src/local_vars.rs
@@ -0,0 +1,37 @@
+use std::collections::BTreeMap;
+
+use serde_json::value::Value as Json;
+
+#[derive(Default, Debug, Clone)]
+pub(crate) struct LocalVars {
+ first: Option<Json>,
+ last: Option<Json>,
+ index: Option<Json>,
+ key: Option<Json>,
+
+ extra: BTreeMap<String, Json>,
+}
+
+impl LocalVars {
+ pub fn put(&mut self, key: &str, value: Json) {
+ match key {
+ "first" => self.first = Some(value),
+ "last" => self.last = Some(value),
+ "index" => self.index = Some(value),
+ "key" => self.key = Some(value),
+ _ => {
+ self.extra.insert(key.to_owned(), value);
+ }
+ }
+ }
+
+ pub fn get(&self, key: &str) -> Option<&Json> {
+ match key {
+ "first" => self.first.as_ref(),
+ "last" => self.last.as_ref(),
+ "index" => self.index.as_ref(),
+ "key" => self.key.as_ref(),
+ _ => self.extra.get(key),
+ }
+ }
+}
diff --git a/vendor/handlebars/src/macros.rs b/vendor/handlebars/src/macros.rs
new file mode 100644
index 000000000..14cb0152c
--- /dev/null
+++ b/vendor/handlebars/src/macros.rs
@@ -0,0 +1,185 @@
+/// Macro that allows you to quickly define a handlebars helper by passing a
+/// name and a closure.
+///
+/// There are several types of arguments available to closure:
+///
+/// * Parameters are mapped to closure arguments one by one. Any declared
+/// parameters are required
+/// * Hash are mapped as named arguments and declared in a bracket block.
+/// All named arguments are optional so default value is required.
+/// * An optional `*args` provides a vector of all helper parameters.
+/// * An optional `**kwargs` provides a map of all helper hash.
+///
+/// # Examples
+///
+/// ```rust
+/// #[macro_use] extern crate handlebars;
+/// #[macro_use] extern crate serde_json;
+///
+/// handlebars_helper!(is_above_10: |x: u64| x > 10);
+/// handlebars_helper!(is_above: |x: u64, { compare: u64 = 10 }| x > compare);
+///
+/// # fn main() {
+/// #
+/// let mut handlebars = handlebars::Handlebars::new();
+/// handlebars.register_helper("is-above-10", Box::new(is_above_10));
+/// handlebars.register_helper("is-above", Box::new(is_above));
+///
+/// let result = handlebars
+/// .render_template("{{#if (is-above-10 12)}}great!{{else}}okay{{/if}}", &json!({}))
+/// .unwrap();
+/// assert_eq!(&result, "great!");
+/// let result2 = handlebars
+/// .render_template("{{#if (is-above 12 compare=10)}}great!{{else}}okay{{/if}}", &json!({}))
+/// .unwrap();
+/// assert_eq!(&result2, "great!");
+/// # }
+/// ```
+
+#[macro_export]
+macro_rules! handlebars_helper {
+ ($struct_name:ident: |$($name:ident: $tpe:tt),*
+ $($(,)?{$($hash_name:ident: $hash_tpe:tt=$dft_val:literal),*})?
+ $($(,)?*$args:ident)?
+ $($(,)?**$kwargs:ident)?|
+ $body:expr ) => {
+ #[allow(non_camel_case_types)]
+ pub struct $struct_name;
+
+ impl $crate::HelperDef for $struct_name {
+ #[allow(unused_assignments)]
+ fn call_inner<'reg: 'rc, 'rc>(
+ &self,
+ h: &$crate::Helper<'reg, 'rc>,
+ r: &'reg $crate::Handlebars<'reg>,
+ _: &'rc $crate::Context,
+ _: &mut $crate::RenderContext<'reg, 'rc>,
+ ) -> Result<$crate::ScopedJson<'reg, 'rc>, $crate::RenderError> {
+ let mut param_idx = 0;
+
+ $(
+ let $name = h.param(param_idx)
+ .and_then(|x| {
+ if r.strict_mode() && x.is_value_missing() {
+ None
+ } else {
+ Some(x.value())
+ }
+ })
+ .ok_or_else(|| $crate::RenderError::new(&format!(
+ "`{}` helper: Couldn't read parameter {}",
+ stringify!($struct_name), stringify!($name),
+ )))
+ .and_then(|x|
+ handlebars_helper!(@as_json_value x, $tpe)
+ .ok_or_else(|| $crate::RenderError::new(&format!(
+ "`{}` helper: Couldn't convert parameter {} to type `{}`. \
+ It's {:?} as JSON. Got these params: {:?}",
+ stringify!($struct_name), stringify!($name), stringify!($tpe),
+ x, h.params(),
+ )))
+ )?;
+ param_idx += 1;
+ )*
+
+ $(
+ $(
+ let $hash_name = h.hash_get(stringify!($hash_name))
+ .map(|x| x.value())
+ .map(|x|
+ handlebars_helper!(@as_json_value x, $hash_tpe)
+ .ok_or_else(|| $crate::RenderError::new(&format!(
+ "`{}` helper: Couldn't convert hash {} to type `{}`. \
+ It's {:?} as JSON. Got these hash: {:?}",
+ stringify!($struct_name), stringify!($hash_name), stringify!($hash_tpe),
+ x, h.hash(),
+ )))
+ )
+ .unwrap_or_else(|| Ok($dft_val))?;
+ )*
+ )?
+
+ $(let $args = h.params().iter().map(|x| x.value()).collect::<Vec<&serde_json::Value>>();)?
+ $(let $kwargs = h.hash().iter().map(|(k, v)| (k.to_owned(), v.value())).collect::<std::collections::BTreeMap<&str, &serde_json::Value>>();)?
+
+ let result = $body;
+ Ok($crate::ScopedJson::Derived($crate::JsonValue::from(result)))
+ }
+ }
+ };
+
+ (@as_json_value $x:ident, object) => { $x.as_object() };
+ (@as_json_value $x:ident, array) => { $x.as_array() };
+ (@as_json_value $x:ident, str) => { $x.as_str() };
+ (@as_json_value $x:ident, i64) => { $x.as_i64() };
+ (@as_json_value $x:ident, u64) => { $x.as_u64() };
+ (@as_json_value $x:ident, f64) => { $x.as_f64() };
+ (@as_json_value $x:ident, bool) => { $x.as_bool() };
+ (@as_json_value $x:ident, null) => { $x.as_null() };
+ (@as_json_value $x:ident, Json) => { Some($x) };
+}
+
+#[cfg(feature = "no_logging")]
+#[macro_use]
+#[doc(hidden)]
+pub mod logging {
+ /// This macro is defined if the `logging` feature is set.
+ ///
+ /// It ignores all logging calls inside the library.
+ #[doc(hidden)]
+ #[macro_export]
+ macro_rules! debug {
+ (target: $target:expr, $($arg:tt)*) => {};
+ ($($arg:tt)*) => {};
+ }
+
+ /// This macro is defined if the `logging` feature is not set.
+ ///
+ /// It ignores all logging calls inside the library.
+ #[doc(hidden)]
+ #[macro_export]
+ macro_rules! error {
+ (target: $target:expr, $($arg:tt)*) => {};
+ ($($arg:tt)*) => {};
+ }
+
+ /// This macro is defined if the `logging` feature is not set.
+ ///
+ /// It ignores all logging calls inside the library.
+ #[doc(hidden)]
+ #[macro_export]
+ macro_rules! info {
+ (target: $target:expr, $($arg:tt)*) => {};
+ ($($arg:tt)*) => {};
+ }
+
+ /// This macro is defined if the `logging` feature is not set.
+ ///
+ /// It ignores all logging calls inside the library.
+ #[doc(hidden)]
+ #[macro_export]
+ macro_rules! log {
+ (target: $target:expr, $($arg:tt)*) => {};
+ ($($arg:tt)*) => {};
+ }
+
+ /// This macro is defined if the `logging` feature is not set.
+ ///
+ /// It ignores all logging calls inside the library.
+ #[doc(hidden)]
+ #[macro_export]
+ macro_rules! trace {
+ (target: $target:expr, $($arg:tt)*) => {};
+ ($($arg:tt)*) => {};
+ }
+
+ /// This macro is defined if the `logging` feature is not set.
+ ///
+ /// It ignores all logging calls inside the library.
+ #[doc(hidden)]
+ #[macro_export]
+ macro_rules! warn {
+ (target: $target:expr, $($arg:tt)*) => {};
+ ($($arg:tt)*) => {};
+ }
+}
diff --git a/vendor/handlebars/src/output.rs b/vendor/handlebars/src/output.rs
new file mode 100644
index 000000000..f1c5865a5
--- /dev/null
+++ b/vendor/handlebars/src/output.rs
@@ -0,0 +1,48 @@
+use std::io::{Error as IOError, Write};
+use std::string::FromUtf8Error;
+
+/// The Output API.
+///
+/// Handlebars uses this trait to define rendered output.
+pub trait Output {
+ fn write(&mut self, seg: &str) -> Result<(), IOError>;
+}
+
+pub struct WriteOutput<W: Write> {
+ write: W,
+}
+
+impl<W: Write> Output for WriteOutput<W> {
+ fn write(&mut self, seg: &str) -> Result<(), IOError> {
+ self.write.write_all(seg.as_bytes())
+ }
+}
+
+impl<W: Write> WriteOutput<W> {
+ pub fn new(write: W) -> WriteOutput<W> {
+ WriteOutput { write }
+ }
+}
+
+pub struct StringOutput {
+ buf: Vec<u8>,
+}
+
+impl Output for StringOutput {
+ fn write(&mut self, seg: &str) -> Result<(), IOError> {
+ self.buf.extend_from_slice(seg.as_bytes());
+ Ok(())
+ }
+}
+
+impl StringOutput {
+ pub fn new() -> StringOutput {
+ StringOutput {
+ buf: Vec::with_capacity(8 * 1024),
+ }
+ }
+
+ pub fn into_string(self) -> Result<String, FromUtf8Error> {
+ String::from_utf8(self.buf)
+ }
+}
diff --git a/vendor/handlebars/src/partial.rs b/vendor/handlebars/src/partial.rs
new file mode 100644
index 000000000..a472d5d14
--- /dev/null
+++ b/vendor/handlebars/src/partial.rs
@@ -0,0 +1,333 @@
+use std::borrow::Cow;
+use std::collections::HashMap;
+
+use serde_json::value::Value as Json;
+
+use crate::block::BlockContext;
+use crate::context::{merge_json, Context};
+use crate::error::RenderError;
+use crate::json::path::Path;
+use crate::output::Output;
+use crate::registry::Registry;
+use crate::render::{Decorator, Evaluable, RenderContext, Renderable};
+use crate::template::Template;
+
+pub(crate) const PARTIAL_BLOCK: &str = "@partial-block";
+
+fn find_partial<'reg: 'rc, 'rc: 'a, 'a>(
+ rc: &'a RenderContext<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ d: &Decorator<'reg, 'rc>,
+ name: &str,
+) -> Result<Option<Cow<'a, Template>>, RenderError> {
+ if let Some(ref partial) = rc.get_partial(name) {
+ return Ok(Some(Cow::Borrowed(partial)));
+ }
+
+ if let Some(tpl) = r.get_or_load_template_optional(name) {
+ return tpl.map(Option::Some);
+ }
+
+ if let Some(tpl) = d.template() {
+ return Ok(Some(Cow::Borrowed(tpl)));
+ }
+
+ Ok(None)
+}
+
+pub fn expand_partial<'reg: 'rc, 'rc>(
+ d: &Decorator<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ // try eval inline partials first
+ if let Some(t) = d.template() {
+ t.eval(r, ctx, rc)?;
+ }
+
+ let tname = d.name();
+ if rc.is_current_template(tname) {
+ return Err(RenderError::new("Cannot include self in >"));
+ }
+
+ // if tname == PARTIAL_BLOCK
+ let partial = find_partial(rc, r, d, tname)?;
+
+ if let Some(t) = partial {
+ // clone to avoid lifetime issue
+ // FIXME refactor this to avoid
+ let mut local_rc = rc.clone();
+ let is_partial_block = tname == PARTIAL_BLOCK;
+
+ if is_partial_block {
+ local_rc.inc_partial_block_depth();
+ }
+
+ let mut block_created = false;
+
+ if let Some(ref base_path) = d.param(0).and_then(|p| p.context_path()) {
+ // path given, update base_path
+ let mut block = BlockContext::new();
+ *block.base_path_mut() = base_path.to_vec();
+ block_created = true;
+ local_rc.push_block(block);
+ } else if !d.hash().is_empty() {
+ let mut block = BlockContext::new();
+ // hash given, update base_value
+ let hash_ctx = d
+ .hash()
+ .iter()
+ .map(|(k, v)| (*k, v.value()))
+ .collect::<HashMap<&str, &Json>>();
+
+ let merged_context = merge_json(
+ local_rc.evaluate2(ctx, &Path::current())?.as_json(),
+ &hash_ctx,
+ );
+ block.set_base_value(merged_context);
+ block_created = true;
+ local_rc.push_block(block);
+ }
+
+ // @partial-block
+ if let Some(pb) = d.template() {
+ local_rc.push_partial_block(pb);
+ }
+
+ let result = t.render(r, ctx, &mut local_rc, out);
+
+ // cleanup
+ if block_created {
+ local_rc.pop_block();
+ }
+
+ if is_partial_block {
+ local_rc.dec_partial_block_depth();
+ }
+
+ if d.template().is_some() {
+ local_rc.pop_partial_block();
+ }
+
+ result
+ } else {
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::context::Context;
+ use crate::error::RenderError;
+ use crate::output::Output;
+ use crate::registry::Registry;
+ use crate::render::{Helper, RenderContext};
+
+ #[test]
+ fn test() {
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("t0", "{{> t1}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t1", "{{this}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t2", "{{#> t99}}not there{{/t99}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t3", "{{#*inline \"t31\"}}{{this}}{{/inline}}{{> t31}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string(
+ "t4",
+ "{{#> t5}}{{#*inline \"nav\"}}navbar{{/inline}}{{/t5}}"
+ )
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t5", "include {{> nav}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("t6", "{{> t1 a}}")
+ .is_ok());
+ assert!(handlebars
+ .register_template_string(
+ "t7",
+ "{{#*inline \"t71\"}}{{a}}{{/inline}}{{> t71 a=\"world\"}}"
+ )
+ .is_ok());
+ assert!(handlebars.register_template_string("t8", "{{a}}").is_ok());
+ assert!(handlebars
+ .register_template_string("t9", "{{> t8 a=2}}")
+ .is_ok());
+
+ assert_eq!(handlebars.render("t0", &1).ok().unwrap(), "1".to_string());
+ assert_eq!(
+ handlebars.render("t2", &1).ok().unwrap(),
+ "not there".to_string()
+ );
+ assert_eq!(handlebars.render("t3", &1).ok().unwrap(), "1".to_string());
+ assert_eq!(
+ handlebars.render("t4", &1).ok().unwrap(),
+ "include navbar".to_string()
+ );
+ assert_eq!(
+ handlebars
+ .render("t6", &btreemap! {"a".to_string() => "2".to_string()})
+ .ok()
+ .unwrap(),
+ "2".to_string()
+ );
+ assert_eq!(
+ handlebars.render("t7", &1).ok().unwrap(),
+ "world".to_string()
+ );
+ assert_eq!(handlebars.render("t9", &1).ok().unwrap(), "2".to_string());
+ }
+
+ #[test]
+ fn test_include_partial_block() {
+ let t0 = "hello {{> @partial-block}}";
+ let t1 = "{{#> t0}}inner {{this}}{{/t0}}";
+
+ let mut handlebars = Registry::new();
+ assert!(handlebars.register_template_string("t0", t0).is_ok());
+ assert!(handlebars.register_template_string("t1", t1).is_ok());
+
+ let r0 = handlebars.render("t1", &true);
+ assert_eq!(r0.ok().unwrap(), "hello inner true".to_string());
+ }
+
+ #[test]
+ fn test_self_inclusion() {
+ let t0 = "hello {{> t1}} {{> t0}}";
+ let t1 = "some template";
+ let mut handlebars = Registry::new();
+ assert!(handlebars.register_template_string("t0", t0).is_ok());
+ assert!(handlebars.register_template_string("t1", t1).is_ok());
+
+ let r0 = handlebars.render("t0", &true);
+ assert!(r0.is_err());
+ }
+
+ #[test]
+ fn test_issue_143() {
+ let main_template = "one{{> two }}three{{> two }}";
+ let two_partial = "--- two ---";
+
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("template", main_template)
+ .is_ok());
+ assert!(handlebars
+ .register_template_string("two", two_partial)
+ .is_ok());
+
+ let r0 = handlebars.render("template", &true);
+ assert_eq!(r0.ok().unwrap(), "one--- two ---three--- two ---");
+ }
+
+ #[test]
+ fn test_hash_context_outscope() {
+ let main_template = "In: {{> p a=2}} Out: {{a}}";
+ let p_partial = "{{a}}";
+
+ let mut handlebars = Registry::new();
+ assert!(handlebars
+ .register_template_string("template", main_template)
+ .is_ok());
+ assert!(handlebars.register_template_string("p", p_partial).is_ok());
+
+ let r0 = handlebars.render("template", &true);
+ assert_eq!(r0.ok().unwrap(), "In: 2 Out: ");
+ }
+
+ #[test]
+ fn test_partial_context_hash() {
+ let mut hbs = Registry::new();
+ hbs.register_template_string("one", "This is a test. {{> two name=\"fred\" }}")
+ .unwrap();
+ hbs.register_template_string("two", "Lets test {{name}}")
+ .unwrap();
+ assert_eq!(
+ "This is a test. Lets test fred",
+ hbs.render("one", &0).unwrap()
+ );
+ }
+
+ #[test]
+ fn test_partial_subexpression_context_hash() {
+ let mut hbs = Registry::new();
+ hbs.register_template_string("one", "This is a test. {{> (x @root) name=\"fred\" }}")
+ .unwrap();
+ hbs.register_template_string("two", "Lets test {{name}}")
+ .unwrap();
+
+ hbs.register_helper(
+ "x",
+ Box::new(
+ |_: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> {
+ out.write("two")?;
+ Ok(())
+ },
+ ),
+ );
+ assert_eq!(
+ "This is a test. Lets test fred",
+ hbs.render("one", &0).unwrap()
+ );
+ }
+
+ #[test]
+ fn test_nested_partial_scope() {
+ let t = "{{#*inline \"pp\"}}{{a}} {{b}}{{/inline}}{{#each c}}{{> pp a=2}}{{/each}}";
+ let data = json!({"c": [{"b": true}, {"b": false}]});
+
+ let mut handlebars = Registry::new();
+ assert!(handlebars.register_template_string("t", t).is_ok());
+ let r0 = handlebars.render("t", &data);
+ assert_eq!(r0.ok().unwrap(), "2 true2 false");
+ }
+
+ #[test]
+ fn test_nested_partials() {
+ let mut handlebars = Registry::new();
+ let template1 = "<outer>{{> @partial-block }}</outer>";
+ let template2 = "{{#> t1 }}<inner>{{> @partial-block }}</inner>{{/ t1 }}";
+ let template3 = "{{#> t2 }}Hello{{/ t2 }}";
+
+ handlebars
+ .register_template_string("t1", &template1)
+ .unwrap();
+ handlebars
+ .register_template_string("t2", &template2)
+ .unwrap();
+
+ let page = handlebars.render_template(&template3, &json!({})).unwrap();
+ assert_eq!("<outer><inner>Hello</inner></outer>", page);
+ }
+
+ #[test]
+ fn test_up_to_partial_level() {
+ let outer = r#"{{>inner name="fruit:" vegetables=fruits}}"#;
+ let inner = "{{#each vegetables}}{{../name}} {{this}},{{/each}}";
+
+ let data = json!({ "fruits": ["carrot", "tomato"] });
+
+ let mut handlebars = Registry::new();
+ handlebars.register_template_string("outer", outer).unwrap();
+ handlebars.register_template_string("inner", inner).unwrap();
+
+ assert_eq!(
+ handlebars.render("outer", &data).unwrap(),
+ "fruit: carrot,fruit: tomato,"
+ );
+ }
+}
diff --git a/vendor/handlebars/src/registry.rs b/vendor/handlebars/src/registry.rs
new file mode 100644
index 000000000..e7f5f1cfd
--- /dev/null
+++ b/vendor/handlebars/src/registry.rs
@@ -0,0 +1,1092 @@
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::fmt::{self, Debug, Formatter};
+use std::io::{Error as IoError, Write};
+use std::path::Path;
+use std::sync::Arc;
+
+use serde::Serialize;
+
+use crate::context::Context;
+use crate::decorators::{self, DecoratorDef};
+#[cfg(feature = "script_helper")]
+use crate::error::ScriptError;
+use crate::error::{RenderError, TemplateError};
+use crate::helpers::{self, HelperDef};
+use crate::output::{Output, StringOutput, WriteOutput};
+use crate::render::{RenderContext, Renderable};
+use crate::sources::{FileSource, Source};
+use crate::support::str::{self, StringWriter};
+use crate::template::Template;
+
+#[cfg(feature = "dir_source")]
+use std::path;
+#[cfg(feature = "dir_source")]
+use walkdir::{DirEntry, WalkDir};
+
+#[cfg(feature = "script_helper")]
+use rhai::Engine;
+
+#[cfg(feature = "script_helper")]
+use crate::helpers::scripting::ScriptHelper;
+
+/// This type represents an *escape fn*, that is a function whose purpose it is
+/// to escape potentially problematic characters in a string.
+///
+/// An *escape fn* is represented as a `Box` to avoid unnecessary type
+/// parameters (and because traits cannot be aliased using `type`).
+pub type EscapeFn = Arc<dyn Fn(&str) -> String + Send + Sync>;
+
+/// The default *escape fn* replaces the characters `&"<>`
+/// with the equivalent html / xml entities.
+pub fn html_escape(data: &str) -> String {
+ str::escape_html(data)
+}
+
+/// `EscapeFn` that does not change anything. Useful when using in a non-html
+/// environment.
+pub fn no_escape(data: &str) -> String {
+ data.to_owned()
+}
+
+/// The single entry point of your Handlebars templates
+///
+/// It maintains compiled templates and registered helpers.
+#[derive(Clone)]
+pub struct Registry<'reg> {
+ templates: HashMap<String, Template>,
+
+ helpers: HashMap<String, Arc<dyn HelperDef + Send + Sync + 'reg>>,
+ decorators: HashMap<String, Arc<dyn DecoratorDef + Send + Sync + 'reg>>,
+
+ escape_fn: EscapeFn,
+ strict_mode: bool,
+ dev_mode: bool,
+ #[cfg(feature = "script_helper")]
+ pub(crate) engine: Arc<Engine>,
+
+ template_sources:
+ HashMap<String, Arc<dyn Source<Item = String, Error = IoError> + Send + Sync + 'reg>>,
+ #[cfg(feature = "script_helper")]
+ script_sources:
+ HashMap<String, Arc<dyn Source<Item = String, Error = IoError> + Send + Sync + 'reg>>,
+}
+
+impl<'reg> Debug for Registry<'reg> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
+ f.debug_struct("Handlebars")
+ .field("templates", &self.templates)
+ .field("helpers", &self.helpers.keys())
+ .field("decorators", &self.decorators.keys())
+ .field("strict_mode", &self.strict_mode)
+ .field("dev_mode", &self.dev_mode)
+ .finish()
+ }
+}
+
+impl<'reg> Default for Registry<'reg> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[cfg(feature = "dir_source")]
+fn filter_file(entry: &DirEntry, suffix: &str) -> bool {
+ let path = entry.path();
+
+ // ignore hidden files, emacs buffers and files with wrong suffix
+ !path.is_file()
+ || path
+ .file_name()
+ .map(|s| {
+ let ds = s.to_string_lossy();
+ ds.starts_with('.') || ds.starts_with('#') || !ds.ends_with(suffix)
+ })
+ .unwrap_or(true)
+}
+
+#[cfg(feature = "script_helper")]
+fn rhai_engine() -> Engine {
+ Engine::new()
+}
+
+impl<'reg> Registry<'reg> {
+ pub fn new() -> Registry<'reg> {
+ let r = Registry {
+ templates: HashMap::new(),
+ template_sources: HashMap::new(),
+ helpers: HashMap::new(),
+ decorators: HashMap::new(),
+ escape_fn: Arc::new(html_escape),
+ strict_mode: false,
+ dev_mode: false,
+ #[cfg(feature = "script_helper")]
+ engine: Arc::new(rhai_engine()),
+ #[cfg(feature = "script_helper")]
+ script_sources: HashMap::new(),
+ };
+
+ r.setup_builtins()
+ }
+
+ fn setup_builtins(mut self) -> Registry<'reg> {
+ self.register_helper("if", Box::new(helpers::IF_HELPER));
+ self.register_helper("unless", Box::new(helpers::UNLESS_HELPER));
+ self.register_helper("each", Box::new(helpers::EACH_HELPER));
+ self.register_helper("with", Box::new(helpers::WITH_HELPER));
+ self.register_helper("lookup", Box::new(helpers::LOOKUP_HELPER));
+ self.register_helper("raw", Box::new(helpers::RAW_HELPER));
+ self.register_helper("log", Box::new(helpers::LOG_HELPER));
+
+ self.register_helper("eq", Box::new(helpers::helper_extras::eq));
+ self.register_helper("ne", Box::new(helpers::helper_extras::ne));
+ self.register_helper("gt", Box::new(helpers::helper_extras::gt));
+ self.register_helper("gte", Box::new(helpers::helper_extras::gte));
+ self.register_helper("lt", Box::new(helpers::helper_extras::lt));
+ self.register_helper("lte", Box::new(helpers::helper_extras::lte));
+ self.register_helper("and", Box::new(helpers::helper_extras::and));
+ self.register_helper("or", Box::new(helpers::helper_extras::or));
+ self.register_helper("not", Box::new(helpers::helper_extras::not));
+ self.register_helper("len", Box::new(helpers::helper_extras::len));
+
+ self.register_decorator("inline", Box::new(decorators::INLINE_DECORATOR));
+ self
+ }
+
+ /// Enable or disable handlebars strict mode
+ ///
+ /// By default, handlebars renders empty string for value that
+ /// undefined or never exists. Since rust is a static type
+ /// language, we offer strict mode in handlebars-rust. In strict
+ /// mode, if you were to render a value that doesn't exist, a
+ /// `RenderError` will be raised.
+ pub fn set_strict_mode(&mut self, enabled: bool) {
+ self.strict_mode = enabled;
+ }
+
+ /// Return strict mode state, default is false.
+ ///
+ /// By default, handlebars renders empty string for value that
+ /// undefined or never exists. Since rust is a static type
+ /// language, we offer strict mode in handlebars-rust. In strict
+ /// mode, if you were access a value that doesn't exist, a
+ /// `RenderError` will be raised.
+ pub fn strict_mode(&self) -> bool {
+ self.strict_mode
+ }
+
+ /// Return dev mode state, default is false
+ ///
+ /// With dev mode turned on, handlebars enables a set of development
+ /// firendly features, that may affect its performance.
+ pub fn dev_mode(&self) -> bool {
+ self.dev_mode
+ }
+
+ /// Enable or disable dev mode
+ ///
+ /// With dev mode turned on, handlebars enables a set of development
+ /// firendly features, that may affect its performance.
+ pub fn set_dev_mode(&mut self, enabled: bool) {
+ self.dev_mode = enabled;
+ }
+
+ /// Register a `Template`
+ ///
+ /// This is infallible since the template has already been parsed and
+ /// insert cannot fail. If there is an existing template with this name it
+ /// will be overwritten.
+ pub fn register_template(&mut self, name: &str, tpl: Template) {
+ self.templates.insert(name.to_string(), tpl);
+ }
+
+ /// Register a template string
+ ///
+ /// Returns `TemplateError` if there is syntax error on parsing the template.
+ pub fn register_template_string<S>(
+ &mut self,
+ name: &str,
+ tpl_str: S,
+ ) -> Result<(), TemplateError>
+ where
+ S: AsRef<str>,
+ {
+ let template = Template::compile_with_name(tpl_str, name.to_owned())?;
+ self.register_template(name, template);
+ Ok(())
+ }
+
+ /// Register a partial string
+ ///
+ /// A named partial will be added to the registry. It will overwrite template with
+ /// same name. Currently a registered partial is just identical to a template.
+ pub fn register_partial<S>(&mut self, name: &str, partial_str: S) -> Result<(), TemplateError>
+ where
+ S: AsRef<str>,
+ {
+ self.register_template_string(name, partial_str)
+ }
+
+ /// Register a template from a path
+ pub fn register_template_file<P>(
+ &mut self,
+ name: &str,
+ tpl_path: P,
+ ) -> Result<(), TemplateError>
+ where
+ P: AsRef<Path>,
+ {
+ let source = FileSource::new(tpl_path.as_ref().into());
+ let template_string = source
+ .load()
+ .map_err(|err| TemplateError::from((err, name.to_owned())))?;
+
+ self.register_template_string(name, template_string)?;
+ if self.dev_mode {
+ self.template_sources
+ .insert(name.to_owned(), Arc::new(source));
+ }
+
+ Ok(())
+ }
+
+ /// Register templates from a directory
+ ///
+ /// * `tpl_extension`: the template file extension
+ /// * `dir_path`: the path of directory
+ ///
+ /// Hidden files and tempfile (starts with `#`) will be ignored. All registered
+ /// will use their relative name as template name. For example, when `dir_path` is
+ /// `templates/` and `tpl_extension` is `.hbs`, the file
+ /// `templates/some/path/file.hbs` will be registered as `some/path/file`.
+ ///
+ /// This method is not available by default.
+ /// You will need to enable the `dir_source` feature to use it.
+ #[cfg(feature = "dir_source")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "dir_source")))]
+ pub fn register_templates_directory<P>(
+ &mut self,
+ tpl_extension: &'static str,
+ dir_path: P,
+ ) -> Result<(), TemplateError>
+ where
+ P: AsRef<Path>,
+ {
+ let dir_path = dir_path.as_ref();
+
+ let prefix_len = if dir_path
+ .to_string_lossy()
+ .ends_with(|c| c == '\\' || c == '/')
+ // `/` will work on windows too so we still need to check
+ {
+ dir_path.to_string_lossy().len()
+ } else {
+ dir_path.to_string_lossy().len() + 1
+ };
+
+ let walker = WalkDir::new(dir_path);
+ let dir_iter = walker
+ .min_depth(1)
+ .into_iter()
+ .filter(|e| e.is_ok() && !filter_file(e.as_ref().unwrap(), tpl_extension));
+
+ for entry in dir_iter {
+ let entry = entry?;
+
+ let tpl_path = entry.path();
+ let tpl_file_path = entry.path().to_string_lossy();
+
+ let tpl_name = &tpl_file_path[prefix_len..tpl_file_path.len() - tpl_extension.len()];
+ // replace platform path separator with our internal one
+ let tpl_canonical_name = tpl_name.replace(path::MAIN_SEPARATOR, "/");
+ self.register_template_file(&tpl_canonical_name, &tpl_path)?;
+ }
+
+ Ok(())
+ }
+
+ /// Remove a template from the registry
+ pub fn unregister_template(&mut self, name: &str) {
+ self.templates.remove(name);
+ }
+
+ /// Register a helper
+ pub fn register_helper(&mut self, name: &str, def: Box<dyn HelperDef + Send + Sync + 'reg>) {
+ self.helpers.insert(name.to_string(), def.into());
+ }
+
+ /// Register a [rhai](https://docs.rs/rhai/) script as handlebars helper
+ ///
+ /// Currently only simple helpers are supported. You can do computation or
+ /// string formatting with rhai script.
+ ///
+ /// Helper parameters and hash are available in rhai script as array `params`
+ /// and map `hash`. Example script:
+ ///
+ /// ```handlebars
+ /// {{percent 0.34 label="%"}}
+ /// ```
+ ///
+ /// ```rhai
+ /// // percent.rhai
+ /// let value = params[0];
+ /// let label = hash["label"];
+ ///
+ /// (value * 100).to_string() + label
+ /// ```
+ ///
+ ///
+ #[cfg(feature = "script_helper")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))]
+ pub fn register_script_helper(&mut self, name: &str, script: &str) -> Result<(), ScriptError> {
+ let compiled = self.engine.compile(script)?;
+ let script_helper = ScriptHelper { script: compiled };
+ self.helpers
+ .insert(name.to_string(), Arc::new(script_helper));
+ Ok(())
+ }
+
+ /// Register a [rhai](https://docs.rs/rhai/) script from file
+ #[cfg(feature = "script_helper")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))]
+ pub fn register_script_helper_file<P>(
+ &mut self,
+ name: &str,
+ script_path: P,
+ ) -> Result<(), ScriptError>
+ where
+ P: AsRef<Path>,
+ {
+ let source = FileSource::new(script_path.as_ref().into());
+ let script = source.load()?;
+
+ self.script_sources
+ .insert(name.to_owned(), Arc::new(source));
+ self.register_script_helper(name, &script)
+ }
+
+ /// Register a decorator
+ pub fn register_decorator(
+ &mut self,
+ name: &str,
+ def: Box<dyn DecoratorDef + Send + Sync + 'reg>,
+ ) {
+ self.decorators.insert(name.to_string(), def.into());
+ }
+
+ /// Register a new *escape fn* to be used from now on by this registry.
+ pub fn register_escape_fn<F: 'static + Fn(&str) -> String + Send + Sync>(
+ &mut self,
+ escape_fn: F,
+ ) {
+ self.escape_fn = Arc::new(escape_fn);
+ }
+
+ /// Restore the default *escape fn*.
+ pub fn unregister_escape_fn(&mut self) {
+ self.escape_fn = Arc::new(html_escape);
+ }
+
+ /// Get a reference to the current *escape fn*.
+ pub fn get_escape_fn(&self) -> &dyn Fn(&str) -> String {
+ &*self.escape_fn
+ }
+
+ /// Return `true` if a template is registered for the given name
+ pub fn has_template(&self, name: &str) -> bool {
+ self.get_template(name).is_some()
+ }
+
+ /// Return a registered template,
+ pub fn get_template(&self, name: &str) -> Option<&Template> {
+ self.templates.get(name)
+ }
+
+ #[inline]
+ pub(crate) fn get_or_load_template_optional(
+ &'reg self,
+ name: &str,
+ ) -> Option<Result<Cow<'reg, Template>, RenderError>> {
+ if let (true, Some(source)) = (self.dev_mode, self.template_sources.get(name)) {
+ let r = source
+ .load()
+ .map_err(|e| TemplateError::from((e, name.to_owned())))
+ .and_then(|tpl_str| Template::compile_with_name(tpl_str, name.to_owned()))
+ .map(Cow::Owned)
+ .map_err(RenderError::from);
+ Some(r)
+ } else {
+ self.templates.get(name).map(|t| Ok(Cow::Borrowed(t)))
+ }
+ }
+
+ #[inline]
+ pub(crate) fn get_or_load_template(
+ &'reg self,
+ name: &str,
+ ) -> Result<Cow<'reg, Template>, RenderError> {
+ if let Some(result) = self.get_or_load_template_optional(name) {
+ result
+ } else {
+ Err(RenderError::new(format!("Template not found: {}", name)))
+ }
+ }
+
+ /// Return a registered helper
+ pub(crate) fn get_or_load_helper(
+ &'reg self,
+ name: &str,
+ ) -> Result<Option<Arc<dyn HelperDef + Send + Sync + 'reg>>, RenderError> {
+ #[cfg(feature = "script_helper")]
+ if let (true, Some(source)) = (self.dev_mode, self.script_sources.get(name)) {
+ return source
+ .load()
+ .map_err(ScriptError::from)
+ .and_then(|s| {
+ let helper = Box::new(ScriptHelper {
+ script: self.engine.compile(&s)?,
+ }) as Box<dyn HelperDef + Send + Sync>;
+ Ok(Some(helper.into()))
+ })
+ .map_err(RenderError::from);
+ }
+
+ Ok(self.helpers.get(name).cloned())
+ }
+
+ #[inline]
+ pub(crate) fn has_helper(&self, name: &str) -> bool {
+ self.helpers.contains_key(name)
+ }
+
+ /// Return a registered decorator
+ pub(crate) fn get_decorator(
+ &self,
+ name: &str,
+ ) -> Option<&(dyn DecoratorDef + Send + Sync + 'reg)> {
+ self.decorators.get(name).map(|v| v.as_ref())
+ }
+
+ /// Return all templates registered
+ pub fn get_templates(&self) -> &HashMap<String, Template> {
+ &self.templates
+ }
+
+ /// Unregister all templates
+ pub fn clear_templates(&mut self) {
+ self.templates.clear();
+ }
+
+ #[inline]
+ fn render_to_output<O>(
+ &self,
+ name: &str,
+ ctx: &Context,
+ output: &mut O,
+ ) -> Result<(), RenderError>
+ where
+ O: Output,
+ {
+ self.get_or_load_template(name).and_then(|t| {
+ let mut render_context = RenderContext::new(t.name.as_ref());
+ t.render(self, &ctx, &mut render_context, output)
+ })
+ }
+
+ /// Render a registered template with some data into a string
+ ///
+ /// * `name` is the template name you registered previously
+ /// * `data` is the data that implements `serde::Serialize`
+ ///
+ /// Returns rendered string or a struct with error information
+ pub fn render<T>(&self, name: &str, data: &T) -> Result<String, RenderError>
+ where
+ T: Serialize,
+ {
+ let mut output = StringOutput::new();
+ let ctx = Context::wraps(&data)?;
+ self.render_to_output(name, &ctx, &mut output)?;
+ output.into_string().map_err(RenderError::from)
+ }
+
+ /// Render a registered template with reused context
+ pub fn render_with_context(&self, name: &str, ctx: &Context) -> Result<String, RenderError> {
+ let mut output = StringOutput::new();
+ self.render_to_output(name, ctx, &mut output)?;
+ output.into_string().map_err(RenderError::from)
+ }
+
+ /// Render a registered template and write some data to the `std::io::Write`
+ pub fn render_to_write<T, W>(&self, name: &str, data: &T, writer: W) -> Result<(), RenderError>
+ where
+ T: Serialize,
+ W: Write,
+ {
+ let mut output = WriteOutput::new(writer);
+ let ctx = Context::wraps(data)?;
+ self.render_to_output(name, &ctx, &mut output)
+ }
+
+ /// Render a template string using current registry without registering it
+ pub fn render_template<T>(&self, template_string: &str, data: &T) -> Result<String, RenderError>
+ where
+ T: Serialize,
+ {
+ let mut writer = StringWriter::new();
+ self.render_template_to_write(template_string, data, &mut writer)?;
+ Ok(writer.into_string())
+ }
+
+ /// Render a template string using reused context data
+ pub fn render_template_with_context(
+ &self,
+ template_string: &str,
+ ctx: &Context,
+ ) -> Result<String, RenderError> {
+ let tpl = Template::compile(template_string)?;
+
+ let mut out = StringOutput::new();
+ {
+ let mut render_context = RenderContext::new(None);
+ tpl.render(self, &ctx, &mut render_context, &mut out)?;
+ }
+
+ out.into_string().map_err(RenderError::from)
+ }
+
+ /// Render a template string using current registry without registering it
+ pub fn render_template_to_write<T, W>(
+ &self,
+ template_string: &str,
+ data: &T,
+ writer: W,
+ ) -> Result<(), RenderError>
+ where
+ T: Serialize,
+ W: Write,
+ {
+ let tpl = Template::compile(template_string)?;
+ let ctx = Context::wraps(data)?;
+ let mut render_context = RenderContext::new(None);
+ let mut out = WriteOutput::new(writer);
+ tpl.render(self, &ctx, &mut render_context, &mut out)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::context::Context;
+ use crate::error::RenderError;
+ use crate::helpers::HelperDef;
+ use crate::output::Output;
+ use crate::registry::Registry;
+ use crate::render::{Helper, RenderContext, Renderable};
+ use crate::support::str::StringWriter;
+ use crate::template::Template;
+ use std::fs::File;
+ use std::io::Write;
+ use tempfile::tempdir;
+
+ #[derive(Clone, Copy)]
+ struct DummyHelper;
+
+ impl HelperDef for DummyHelper {
+ fn call<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> Result<(), RenderError> {
+ h.template().unwrap().render(r, ctx, rc, out)
+ }
+ }
+
+ static DUMMY_HELPER: DummyHelper = DummyHelper;
+
+ #[test]
+ fn test_registry_operations() {
+ let mut r = Registry::new();
+
+ assert!(r.register_template_string("index", "<h1></h1>").is_ok());
+
+ let tpl = Template::compile("<h2></h2>").unwrap();
+ r.register_template("index2", tpl);
+
+ assert_eq!(r.templates.len(), 2);
+
+ r.unregister_template("index");
+ assert_eq!(r.templates.len(), 1);
+
+ r.clear_templates();
+ assert_eq!(r.templates.len(), 0);
+
+ r.register_helper("dummy", Box::new(DUMMY_HELPER));
+
+ // built-in helpers plus 1
+ let num_helpers = 7;
+ let num_boolean_helpers = 10; // stuff like gt and lte
+ let num_custom_helpers = 1; // dummy from above
+ assert_eq!(
+ r.helpers.len(),
+ num_helpers + num_boolean_helpers + num_custom_helpers
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "dir_source")]
+ fn test_register_templates_directory() {
+ use std::fs::DirBuilder;
+
+ let mut r = Registry::new();
+ {
+ let dir = tempdir().unwrap();
+
+ assert_eq!(r.templates.len(), 0);
+
+ let file1_path = dir.path().join("t1.hbs");
+ let mut file1: File = File::create(&file1_path).unwrap();
+ writeln!(file1, "<h1>Hello {{world}}!</h1>").unwrap();
+
+ let file2_path = dir.path().join("t2.hbs");
+ let mut file2: File = File::create(&file2_path).unwrap();
+ writeln!(file2, "<h1>Hola {{world}}!</h1>").unwrap();
+
+ let file3_path = dir.path().join("t3.hbs");
+ let mut file3: File = File::create(&file3_path).unwrap();
+ writeln!(file3, "<h1>Hallo {{world}}!</h1>").unwrap();
+
+ let file4_path = dir.path().join(".t4.hbs");
+ let mut file4: File = File::create(&file4_path).unwrap();
+ writeln!(file4, "<h1>Hallo {{world}}!</h1>").unwrap();
+
+ r.register_templates_directory(".hbs", dir.path()).unwrap();
+
+ assert_eq!(r.templates.len(), 3);
+ assert_eq!(r.templates.contains_key("t1"), true);
+ assert_eq!(r.templates.contains_key("t2"), true);
+ assert_eq!(r.templates.contains_key("t3"), true);
+ assert_eq!(r.templates.contains_key("t4"), false);
+
+ drop(file1);
+ drop(file2);
+ drop(file3);
+
+ dir.close().unwrap();
+ }
+
+ {
+ let dir = tempdir().unwrap();
+
+ let file1_path = dir.path().join("t4.hbs");
+ let mut file1: File = File::create(&file1_path).unwrap();
+ writeln!(file1, "<h1>Hello {{world}}!</h1>").unwrap();
+
+ let file2_path = dir.path().join("t5.erb");
+ let mut file2: File = File::create(&file2_path).unwrap();
+ writeln!(file2, "<h1>Hello {{% world %}}!</h1>").unwrap();
+
+ let file3_path = dir.path().join("t6.html");
+ let mut file3: File = File::create(&file3_path).unwrap();
+ writeln!(file3, "<h1>Hello world!</h1>").unwrap();
+
+ r.register_templates_directory(".hbs", dir.path()).unwrap();
+
+ assert_eq!(r.templates.len(), 4);
+ assert_eq!(r.templates.contains_key("t4"), true);
+
+ drop(file1);
+ drop(file2);
+ drop(file3);
+
+ dir.close().unwrap();
+ }
+
+ {
+ let dir = tempdir().unwrap();
+
+ let _ = DirBuilder::new().create(dir.path().join("french")).unwrap();
+ let _ = DirBuilder::new()
+ .create(dir.path().join("portugese"))
+ .unwrap();
+ let _ = DirBuilder::new()
+ .create(dir.path().join("italian"))
+ .unwrap();
+
+ let file1_path = dir.path().join("french/t7.hbs");
+ let mut file1: File = File::create(&file1_path).unwrap();
+ writeln!(file1, "<h1>Bonjour {{world}}!</h1>").unwrap();
+
+ let file2_path = dir.path().join("portugese/t8.hbs");
+ let mut file2: File = File::create(&file2_path).unwrap();
+ writeln!(file2, "<h1>Ola {{world}}!</h1>").unwrap();
+
+ let file3_path = dir.path().join("italian/t9.hbs");
+ let mut file3: File = File::create(&file3_path).unwrap();
+ writeln!(file3, "<h1>Ciao {{world}}!</h1>").unwrap();
+
+ r.register_templates_directory(".hbs", dir.path()).unwrap();
+
+ assert_eq!(r.templates.len(), 7);
+ assert_eq!(r.templates.contains_key("french/t7"), true);
+ assert_eq!(r.templates.contains_key("portugese/t8"), true);
+ assert_eq!(r.templates.contains_key("italian/t9"), true);
+
+ drop(file1);
+ drop(file2);
+ drop(file3);
+
+ dir.close().unwrap();
+ }
+
+ {
+ let dir = tempdir().unwrap();
+
+ let file1_path = dir.path().join("t10.hbs");
+ let mut file1: File = File::create(&file1_path).unwrap();
+ writeln!(file1, "<h1>Bonjour {{world}}!</h1>").unwrap();
+
+ let mut dir_path = dir
+ .path()
+ .to_string_lossy()
+ .replace(std::path::MAIN_SEPARATOR, "/");
+ if !dir_path.ends_with("/") {
+ dir_path.push('/');
+ }
+ r.register_templates_directory(".hbs", dir_path).unwrap();
+
+ assert_eq!(r.templates.len(), 8);
+ assert_eq!(r.templates.contains_key("t10"), true);
+
+ drop(file1);
+ dir.close().unwrap();
+ }
+ }
+
+ #[test]
+ fn test_render_to_write() {
+ let mut r = Registry::new();
+
+ assert!(r.register_template_string("index", "<h1></h1>").is_ok());
+
+ let mut sw = StringWriter::new();
+ {
+ r.render_to_write("index", &(), &mut sw).ok().unwrap();
+ }
+
+ assert_eq!("<h1></h1>".to_string(), sw.into_string());
+ }
+
+ #[test]
+ fn test_escape_fn() {
+ let mut r = Registry::new();
+
+ let input = String::from("\"<>&");
+
+ r.register_template_string("test", String::from("{{this}}"))
+ .unwrap();
+
+ assert_eq!("&quot;&lt;&gt;&amp;", r.render("test", &input).unwrap());
+
+ r.register_escape_fn(|s| s.into());
+
+ assert_eq!("\"<>&", r.render("test", &input).unwrap());
+
+ r.unregister_escape_fn();
+
+ assert_eq!("&quot;&lt;&gt;&amp;", r.render("test", &input).unwrap());
+ }
+
+ #[test]
+ fn test_escape() {
+ let r = Registry::new();
+ let data = json!({"hello": "world"});
+
+ assert_eq!(
+ "{{hello}}",
+ r.render_template(r"\{{hello}}", &data).unwrap()
+ );
+
+ assert_eq!(
+ " {{hello}}",
+ r.render_template(r" \{{hello}}", &data).unwrap()
+ );
+
+ assert_eq!(r"\world", r.render_template(r"\\{{hello}}", &data).unwrap());
+ }
+
+ #[test]
+ fn test_strict_mode() {
+ let mut r = Registry::new();
+ assert!(!r.strict_mode());
+
+ r.set_strict_mode(true);
+ assert!(r.strict_mode());
+
+ let data = json!({
+ "the_only_key": "the_only_value"
+ });
+
+ assert!(r
+ .render_template("accessing the_only_key {{the_only_key}}", &data)
+ .is_ok());
+ assert!(r
+ .render_template("accessing non-exists key {{the_key_never_exists}}", &data)
+ .is_err());
+
+ let render_error = r
+ .render_template("accessing non-exists key {{the_key_never_exists}}", &data)
+ .unwrap_err();
+ assert_eq!(render_error.column_no.unwrap(), 26);
+
+ let data2 = json!([1, 2, 3]);
+ assert!(r
+ .render_template("accessing valid array index {{this.[2]}}", &data2)
+ .is_ok());
+ assert!(r
+ .render_template("accessing invalid array index {{this.[3]}}", &data2)
+ .is_err());
+ let render_error2 = r
+ .render_template("accessing invalid array index {{this.[3]}}", &data2)
+ .unwrap_err();
+ assert_eq!(render_error2.column_no.unwrap(), 31);
+ }
+
+ use crate::json::value::ScopedJson;
+ struct GenMissingHelper;
+ impl HelperDef for GenMissingHelper {
+ fn call_inner<'reg: 'rc, 'rc>(
+ &self,
+ _: &Helper<'reg, 'rc>,
+ _: &'reg Registry<'reg>,
+ _: &'rc Context,
+ _: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ Ok(ScopedJson::Missing)
+ }
+ }
+
+ #[test]
+ fn test_strict_mode_in_helper() {
+ let mut r = Registry::new();
+ r.set_strict_mode(true);
+
+ r.register_helper(
+ "check_missing",
+ Box::new(
+ |h: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ _: &mut dyn Output|
+ -> Result<(), RenderError> {
+ let value = h.param(0).unwrap();
+ assert!(value.is_value_missing());
+ Ok(())
+ },
+ ),
+ );
+
+ r.register_helper("generate_missing_value", Box::new(GenMissingHelper));
+
+ let data = json!({
+ "the_key_we_have": "the_value_we_have"
+ });
+ assert!(r
+ .render_template("accessing non-exists key {{the_key_we_dont_have}}", &data)
+ .is_err());
+ assert!(r
+ .render_template(
+ "accessing non-exists key from helper {{check_missing the_key_we_dont_have}}",
+ &data
+ )
+ .is_ok());
+ assert!(r
+ .render_template(
+ "accessing helper that generates missing value {{generate_missing_value}}",
+ &data
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn test_html_expression() {
+ let reg = Registry::new();
+ assert_eq!(
+ reg.render_template("{{{ a }}}", &json!({"a": "<b>bold</b>"}))
+ .unwrap(),
+ "<b>bold</b>"
+ );
+ assert_eq!(
+ reg.render_template("{{ &a }}", &json!({"a": "<b>bold</b>"}))
+ .unwrap(),
+ "<b>bold</b>"
+ );
+ }
+
+ #[test]
+ fn test_render_context() {
+ let mut reg = Registry::new();
+
+ let data = json!([0, 1, 2, 3]);
+
+ assert_eq!(
+ "0123",
+ reg.render_template_with_context(
+ "{{#each this}}{{this}}{{/each}}",
+ &Context::wraps(&data).unwrap()
+ )
+ .unwrap()
+ );
+
+ reg.register_template_string("t0", "{{#each this}}{{this}}{{/each}}")
+ .unwrap();
+ assert_eq!(
+ "0123",
+ reg.render_with_context("t0", &Context::wraps(&data).unwrap())
+ .unwrap()
+ );
+ }
+
+ #[test]
+ fn test_keys_starts_with_null() {
+ env_logger::init();
+ let reg = Registry::new();
+ let data = json!({
+ "optional": true,
+ "is_null": true,
+ "nullable": true,
+ "null": true,
+ "falsevalue": true,
+ });
+ assert_eq!(
+ "optional: true --> true",
+ reg.render_template(
+ "optional: {{optional}} --> {{#if optional }}true{{else}}false{{/if}}",
+ &data
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ "is_null: true --> true",
+ reg.render_template(
+ "is_null: {{is_null}} --> {{#if is_null }}true{{else}}false{{/if}}",
+ &data
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ "nullable: true --> true",
+ reg.render_template(
+ "nullable: {{nullable}} --> {{#if nullable }}true{{else}}false{{/if}}",
+ &data
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ "falsevalue: true --> true",
+ reg.render_template(
+ "falsevalue: {{falsevalue}} --> {{#if falsevalue }}true{{else}}false{{/if}}",
+ &data
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ "null: true --> false",
+ reg.render_template(
+ "null: {{null}} --> {{#if null }}true{{else}}false{{/if}}",
+ &data
+ )
+ .unwrap()
+ );
+ assert_eq!(
+ "null: true --> true",
+ reg.render_template(
+ "null: {{null}} --> {{#if this.[null]}}true{{else}}false{{/if}}",
+ &data
+ )
+ .unwrap()
+ );
+ }
+
+ #[test]
+ fn test_dev_mode_template_reload() {
+ let mut reg = Registry::new();
+ reg.set_dev_mode(true);
+ assert!(reg.dev_mode());
+
+ let dir = tempdir().unwrap();
+ let file1_path = dir.path().join("t1.hbs");
+ {
+ let mut file1: File = File::create(&file1_path).unwrap();
+ write!(file1, "<h1>Hello {{{{name}}}}!</h1>").unwrap();
+ }
+
+ reg.register_template_file("t1", &file1_path).unwrap();
+
+ assert_eq!(
+ reg.render("t1", &json!({"name": "Alex"})).unwrap(),
+ "<h1>Hello Alex!</h1>"
+ );
+
+ {
+ let mut file1: File = File::create(&file1_path).unwrap();
+ write!(file1, "<h1>Privet {{{{name}}}}!</h1>").unwrap();
+ }
+
+ assert_eq!(
+ reg.render("t1", &json!({"name": "Alex"})).unwrap(),
+ "<h1>Privet Alex!</h1>"
+ );
+
+ dir.close().unwrap();
+ }
+
+ #[test]
+ #[cfg(feature = "script_helper")]
+ fn test_script_helper() {
+ let mut reg = Registry::new();
+
+ reg.register_script_helper("acc", "params.reduce(|sum, x| x + sum, 0)")
+ .unwrap();
+
+ assert_eq!(
+ reg.render_template("{{acc 1 2 3 4}}", &json!({})).unwrap(),
+ "10"
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "script_helper")]
+ fn test_script_helper_dev_mode() {
+ let mut reg = Registry::new();
+ reg.set_dev_mode(true);
+
+ let dir = tempdir().unwrap();
+ let file1_path = dir.path().join("acc.rhai");
+ {
+ let mut file1: File = File::create(&file1_path).unwrap();
+ write!(file1, "params.reduce(|sum, x| x + sum, 0)").unwrap();
+ }
+
+ reg.register_script_helper_file("acc", &file1_path).unwrap();
+
+ assert_eq!(
+ reg.render_template("{{acc 1 2 3 4}}", &json!({})).unwrap(),
+ "10"
+ );
+
+ {
+ let mut file1: File = File::create(&file1_path).unwrap();
+ write!(file1, "params.reduce(|sum, x| x * sum, 1)").unwrap();
+ }
+
+ assert_eq!(
+ reg.render_template("{{acc 1 2 3 4}}", &json!({})).unwrap(),
+ "24"
+ );
+
+ dir.close().unwrap();
+ }
+}
diff --git a/vendor/handlebars/src/render.rs b/vendor/handlebars/src/render.rs
new file mode 100644
index 000000000..188ea221a
--- /dev/null
+++ b/vendor/handlebars/src/render.rs
@@ -0,0 +1,1119 @@
+use std::borrow::{Borrow, Cow};
+use std::collections::{BTreeMap, VecDeque};
+use std::fmt;
+use std::rc::Rc;
+
+use serde_json::value::Value as Json;
+
+use crate::block::BlockContext;
+use crate::context::Context;
+use crate::error::RenderError;
+use crate::helpers::HelperDef;
+use crate::json::path::Path;
+use crate::json::value::{JsonRender, PathAndJson, ScopedJson};
+use crate::output::{Output, StringOutput};
+use crate::partial;
+use crate::registry::Registry;
+use crate::template::TemplateElement::*;
+use crate::template::{
+ BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
+ TemplateMapping,
+};
+
+const HELPER_MISSING: &str = "helperMissing";
+const BLOCK_HELPER_MISSING: &str = "blockHelperMissing";
+
+/// The context of a render call
+///
+/// This context stores information of a render and a writer where generated
+/// content is written to.
+///
+#[derive(Clone, Debug)]
+pub struct RenderContext<'reg, 'rc> {
+ inner: Rc<RenderContextInner<'reg, 'rc>>,
+ blocks: VecDeque<BlockContext<'reg>>,
+ // copy-on-write context
+ modified_context: Option<Rc<Context>>,
+}
+
+#[derive(Clone)]
+pub struct RenderContextInner<'reg: 'rc, 'rc> {
+ partials: BTreeMap<String, &'reg Template>,
+ partial_block_stack: VecDeque<&'reg Template>,
+ partial_block_depth: isize,
+ local_helpers: BTreeMap<String, Rc<dyn HelperDef + Send + Sync + 'rc>>,
+ /// current template name
+ current_template: Option<&'reg String>,
+ /// root template name
+ root_template: Option<&'reg String>,
+ disable_escape: bool,
+}
+
+impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
+ /// Create a render context from a `Write`
+ pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
+ let inner = Rc::new(RenderContextInner {
+ partials: BTreeMap::new(),
+ partial_block_stack: VecDeque::new(),
+ partial_block_depth: 0,
+ local_helpers: BTreeMap::new(),
+ current_template: None,
+ root_template,
+ disable_escape: false,
+ });
+
+ let mut blocks = VecDeque::with_capacity(5);
+ blocks.push_front(BlockContext::new());
+
+ let modified_context = None;
+ RenderContext {
+ inner,
+ blocks,
+ modified_context,
+ }
+ }
+
+ // TODO: better name
+ pub(crate) fn new_for_block(&self) -> RenderContext<'reg, 'rc> {
+ let inner = self.inner.clone();
+
+ let mut blocks = VecDeque::with_capacity(2);
+ blocks.push_front(BlockContext::new());
+
+ let modified_context = self.modified_context.clone();
+
+ RenderContext {
+ inner,
+ blocks,
+ modified_context,
+ }
+ }
+
+ /// Push a block context into render context stack. This is typically
+ /// called when you entering a block scope.
+ pub fn push_block(&mut self, block: BlockContext<'reg>) {
+ self.blocks.push_front(block);
+ }
+
+ /// Pop and drop current block context.
+ /// This is typically called when leaving a block scope.
+ pub fn pop_block(&mut self) {
+ self.blocks.pop_front();
+ }
+
+ /// Borrow a reference to current block context
+ pub fn block(&self) -> Option<&BlockContext<'reg>> {
+ self.blocks.front()
+ }
+
+ /// Borrow a mutable reference to current block context in order to
+ /// modify some data.
+ pub fn block_mut(&mut self) -> Option<&mut BlockContext<'reg>> {
+ self.blocks.front_mut()
+ }
+
+ fn inner(&self) -> &RenderContextInner<'reg, 'rc> {
+ self.inner.borrow()
+ }
+
+ fn inner_mut(&mut self) -> &mut RenderContextInner<'reg, 'rc> {
+ Rc::make_mut(&mut self.inner)
+ }
+
+ /// Get the modified context data if any
+ pub fn context(&self) -> Option<Rc<Context>> {
+ self.modified_context.clone()
+ }
+
+ /// Set new context data into the render process.
+ /// This is typically called in decorators where user can modify
+ /// the data they were rendering.
+ pub fn set_context(&mut self, ctx: Context) {
+ self.modified_context = Some(Rc::new(ctx))
+ }
+
+ /// Evaluate a Json path in current scope.
+ ///
+ /// Typically you don't need to evaluate it by yourself.
+ /// The Helper and Decorator API will provide your evaluated value of
+ /// their parameters and hash data.
+ pub fn evaluate(
+ &self,
+ context: &'rc Context,
+ relative_path: &str,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ let path = Path::parse(relative_path)?;
+ self.evaluate2(context, &path)
+ }
+
+ pub(crate) fn evaluate2(
+ &self,
+ context: &'rc Context,
+ path: &Path,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ match path {
+ Path::Local((level, name, _)) => Ok(self
+ .get_local_var(*level, name)
+ .map(|v| ScopedJson::Derived(v.clone()))
+ .unwrap_or_else(|| ScopedJson::Missing)),
+ Path::Relative((segs, _)) => context.navigate(segs, &self.blocks),
+ }
+ }
+
+ /// Get registered partial in this render context
+ pub fn get_partial(&self, name: &str) -> Option<&Template> {
+ if name == partial::PARTIAL_BLOCK {
+ return self
+ .inner()
+ .partial_block_stack
+ .get(self.inner().partial_block_depth as usize)
+ .copied();
+ }
+ self.inner().partials.get(name).copied()
+ }
+
+ /// Register a partial for this context
+ pub fn set_partial(&mut self, name: String, partial: &'reg Template) {
+ self.inner_mut().partials.insert(name, partial);
+ }
+
+ pub(crate) fn push_partial_block(&mut self, partial: &'reg Template) {
+ self.inner_mut().partial_block_stack.push_front(partial);
+ }
+
+ pub(crate) fn pop_partial_block(&mut self) {
+ self.inner_mut().partial_block_stack.pop_front();
+ }
+
+ pub(crate) fn inc_partial_block_depth(&mut self) {
+ self.inner_mut().partial_block_depth += 1;
+ }
+
+ pub(crate) fn dec_partial_block_depth(&mut self) {
+ self.inner_mut().partial_block_depth -= 1;
+ }
+
+ /// Remove a registered partial
+ pub fn remove_partial(&mut self, name: &str) {
+ self.inner_mut().partials.remove(name);
+ }
+
+ fn get_local_var(&self, level: usize, name: &str) -> Option<&Json> {
+ self.blocks
+ .get(level)
+ .and_then(|blk| blk.get_local_var(&name))
+ }
+
+ /// Test if given template name is current template.
+ pub fn is_current_template(&self, p: &str) -> bool {
+ self.inner()
+ .current_template
+ .map(|s| s == p)
+ .unwrap_or(false)
+ }
+
+ /// Register a helper in this render context.
+ /// This is a feature provided by Decorator where you can create
+ /// temporary helpers.
+ pub fn register_local_helper(
+ &mut self,
+ name: &str,
+ def: Box<dyn HelperDef + Send + Sync + 'rc>,
+ ) {
+ self.inner_mut()
+ .local_helpers
+ .insert(name.to_string(), def.into());
+ }
+
+ /// Remove a helper from render context
+ pub fn unregister_local_helper(&mut self, name: &str) {
+ self.inner_mut().local_helpers.remove(name);
+ }
+
+ /// Attempt to get a helper from current render context.
+ pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + Send + Sync + 'rc>> {
+ self.inner().local_helpers.get(name).cloned()
+ }
+
+ #[inline]
+ fn has_local_helper(&self, name: &str) -> bool {
+ self.inner.local_helpers.contains_key(name)
+ }
+
+ /// Returns the current template name.
+ /// Note that the name can be vary from root template when you are rendering
+ /// from partials.
+ pub fn get_current_template_name(&self) -> Option<&'reg String> {
+ self.inner().current_template
+ }
+
+ /// Set the current template name.
+ pub fn set_current_template_name(&mut self, name: Option<&'reg String>) {
+ self.inner_mut().current_template = name;
+ }
+
+ /// Get root template name if any.
+ /// This is the template name that you call `render` from `Handlebars`.
+ pub fn get_root_template_name(&self) -> Option<&'reg String> {
+ self.inner().root_template
+ }
+
+ /// Get the escape toggle
+ pub fn is_disable_escape(&self) -> bool {
+ self.inner().disable_escape
+ }
+
+ /// Set the escape toggle.
+ /// When toggle is on, escape_fn will be called when rendering.
+ pub fn set_disable_escape(&mut self, disable: bool) {
+ self.inner_mut().disable_escape = disable
+ }
+}
+
+impl<'reg, 'rc> fmt::Debug for RenderContextInner<'reg, 'rc> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ f.debug_struct("RenderContextInner")
+ .field("partials", &self.partials)
+ .field("partial_block_stack", &self.partial_block_stack)
+ .field("root_template", &self.root_template)
+ .field("current_template", &self.current_template)
+ .field("disable_eacape", &self.disable_escape)
+ .finish()
+ }
+}
+
+/// Render-time Helper data when using in a helper definition
+#[derive(Debug)]
+pub struct Helper<'reg, 'rc> {
+ name: Cow<'reg, str>,
+ params: Vec<PathAndJson<'reg, 'rc>>,
+ hash: BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
+ template: Option<&'reg Template>,
+ inverse: Option<&'reg Template>,
+ block_param: Option<&'reg BlockParam>,
+ block: bool,
+}
+
+impl<'reg: 'rc, 'rc> Helper<'reg, 'rc> {
+ fn try_from_template(
+ ht: &'reg HelperTemplate,
+ registry: &'reg Registry<'reg>,
+ context: &'rc Context,
+ render_context: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<Helper<'reg, 'rc>, RenderError> {
+ let name = ht.name.expand_as_name(registry, context, render_context)?;
+ let mut pv = Vec::with_capacity(ht.params.len());
+ for p in &ht.params {
+ let r = p.expand(registry, context, render_context)?;
+ pv.push(r);
+ }
+
+ let mut hm = BTreeMap::new();
+ for (k, p) in &ht.hash {
+ let r = p.expand(registry, context, render_context)?;
+ hm.insert(k.as_ref(), r);
+ }
+
+ Ok(Helper {
+ name,
+ params: pv,
+ hash: hm,
+ template: ht.template.as_ref(),
+ inverse: ht.inverse.as_ref(),
+ block_param: ht.block_param.as_ref(),
+ block: ht.block,
+ })
+ }
+
+ /// Returns helper name
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ /// Returns all helper params, resolved within the context
+ pub fn params(&self) -> &Vec<PathAndJson<'reg, 'rc>> {
+ &self.params
+ }
+
+ /// Returns nth helper param, resolved within the context.
+ ///
+ /// ## Example
+ ///
+ /// To get the first param in `{{my_helper abc}}` or `{{my_helper 2}}`,
+ /// use `h.param(0)` in helper definition.
+ /// Variable `abc` is auto resolved in current context.
+ ///
+ /// ```
+ /// use handlebars::*;
+ ///
+ /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
+ /// let v = h.param(0).map(|v| v.value())
+ /// .ok_or(RenderError::new("param not found"));
+ /// // ..
+ /// Ok(())
+ /// }
+ /// ```
+ pub fn param(&self, idx: usize) -> Option<&PathAndJson<'reg, 'rc>> {
+ self.params.get(idx)
+ }
+
+ /// Returns hash, resolved within the context
+ pub fn hash(&self) -> &BTreeMap<&'reg str, PathAndJson<'reg, 'rc>> {
+ &self.hash
+ }
+
+ /// Return hash value of a given key, resolved within the context
+ ///
+ /// ## Example
+ ///
+ /// To get the first param in `{{my_helper v=abc}}` or `{{my_helper v=2}}`,
+ /// use `h.hash_get("v")` in helper definition.
+ /// Variable `abc` is auto resolved in current context.
+ ///
+ /// ```
+ /// use handlebars::*;
+ ///
+ /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
+ /// let v = h.hash_get("v").map(|v| v.value())
+ /// .ok_or(RenderError::new("param not found"));
+ /// // ..
+ /// Ok(())
+ /// }
+ /// ```
+ pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'reg, 'rc>> {
+ self.hash.get(key)
+ }
+
+ /// Returns the default inner template if the helper is a block helper.
+ ///
+ /// Typically you will render the template via: `template.render(registry, render_context)`
+ ///
+ pub fn template(&self) -> Option<&'reg Template> {
+ self.template
+ }
+
+ /// Returns the template of `else` branch if any
+ pub fn inverse(&self) -> Option<&'reg Template> {
+ self.inverse
+ }
+
+ /// Returns if the helper is a block one `{{#helper}}{{/helper}}` or not `{{helper 123}}`
+ pub fn is_block(&self) -> bool {
+ self.block
+ }
+
+ /// Returns if the helper has either a block param or block param pair
+ pub fn has_block_param(&self) -> bool {
+ self.block_param.is_some()
+ }
+
+ /// Returns block param if any
+ pub fn block_param(&self) -> Option<&'reg str> {
+ if let Some(&BlockParam::Single(Parameter::Name(ref s))) = self.block_param {
+ Some(s)
+ } else {
+ None
+ }
+ }
+
+ /// Return block param pair (for example |key, val|) if any
+ pub fn block_param_pair(&self) -> Option<(&'reg str, &'reg str)> {
+ if let Some(&BlockParam::Pair((Parameter::Name(ref s1), Parameter::Name(ref s2)))) =
+ self.block_param
+ {
+ Some((s1, s2))
+ } else {
+ None
+ }
+ }
+}
+
+/// Render-time Decorator data when using in a decorator definition
+#[derive(Debug)]
+pub struct Decorator<'reg, 'rc> {
+ name: Cow<'reg, str>,
+ params: Vec<PathAndJson<'reg, 'rc>>,
+ hash: BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
+ template: Option<&'reg Template>,
+}
+
+impl<'reg: 'rc, 'rc> Decorator<'reg, 'rc> {
+ fn try_from_template(
+ dt: &'reg DecoratorTemplate,
+ registry: &'reg Registry<'reg>,
+ context: &'rc Context,
+ render_context: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<Decorator<'reg, 'rc>, RenderError> {
+ let name = dt.name.expand_as_name(registry, context, render_context)?;
+
+ let mut pv = Vec::with_capacity(dt.params.len());
+ for p in &dt.params {
+ let r = p.expand(registry, context, render_context)?;
+ pv.push(r);
+ }
+
+ let mut hm = BTreeMap::new();
+ for (k, p) in &dt.hash {
+ let r = p.expand(registry, context, render_context)?;
+ hm.insert(k.as_ref(), r);
+ }
+
+ Ok(Decorator {
+ name,
+ params: pv,
+ hash: hm,
+ template: dt.template.as_ref(),
+ })
+ }
+
+ /// Returns helper name
+ pub fn name(&self) -> &str {
+ self.name.as_ref()
+ }
+
+ /// Returns all helper params, resolved within the context
+ pub fn params(&self) -> &Vec<PathAndJson<'reg, 'rc>> {
+ &self.params
+ }
+
+ /// Returns nth helper param, resolved within the context
+ pub fn param(&self, idx: usize) -> Option<&PathAndJson<'reg, 'rc>> {
+ self.params.get(idx)
+ }
+
+ /// Returns hash, resolved within the context
+ pub fn hash(&self) -> &BTreeMap<&'reg str, PathAndJson<'reg, 'rc>> {
+ &self.hash
+ }
+
+ /// Return hash value of a given key, resolved within the context
+ pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'reg, 'rc>> {
+ self.hash.get(key)
+ }
+
+ /// Returns the default inner template if any
+ pub fn template(&self) -> Option<&'reg Template> {
+ self.template
+ }
+}
+
+/// Render trait
+pub trait Renderable {
+ /// render into RenderContext's `writer`
+ fn render<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ context: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> Result<(), RenderError>;
+
+ /// render into string
+ fn renders<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<String, RenderError> {
+ let mut so = StringOutput::new();
+ self.render(registry, ctx, rc, &mut so)?;
+ so.into_string().map_err(RenderError::from)
+ }
+}
+
+/// Evaluate decorator
+pub trait Evaluable {
+ fn eval<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ context: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<(), RenderError>;
+}
+
+#[inline]
+fn call_helper_for_value<'reg: 'rc, 'rc>(
+ hd: &dyn HelperDef,
+ ht: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+) -> Result<PathAndJson<'reg, 'rc>, RenderError> {
+ match hd.call_inner(ht, r, ctx, rc) {
+ Ok(result) => Ok(PathAndJson::new(None, result)),
+ Err(e) => {
+ if e.is_unimplemented() {
+ // parse value from output
+ let mut so = StringOutput::new();
+
+ // here we don't want subexpression result escaped,
+ // so we temporarily disable it
+ let disable_escape = rc.is_disable_escape();
+ rc.set_disable_escape(true);
+
+ hd.call(ht, r, ctx, rc, &mut so)?;
+ rc.set_disable_escape(disable_escape);
+
+ let string = so.into_string().map_err(RenderError::from)?;
+ Ok(PathAndJson::new(
+ None,
+ ScopedJson::Derived(Json::String(string)),
+ ))
+ } else {
+ Err(e)
+ }
+ }
+ }
+}
+
+impl Parameter {
+ pub fn expand_as_name<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<Cow<'reg, str>, RenderError> {
+ match self {
+ Parameter::Name(ref name) => Ok(Cow::Borrowed(name)),
+ Parameter::Path(ref p) => Ok(Cow::Borrowed(p.raw())),
+ Parameter::Subexpression(_) => self
+ .expand(registry, ctx, rc)
+ .map(|v| v.value().render())
+ .map(Cow::Owned),
+ Parameter::Literal(ref j) => Ok(Cow::Owned(j.render())),
+ }
+ }
+
+ pub fn expand<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<PathAndJson<'reg, 'rc>, RenderError> {
+ match self {
+ Parameter::Name(ref name) => {
+ // FIXME: raise error when expanding with name?
+ Ok(PathAndJson::new(Some(name.to_owned()), ScopedJson::Missing))
+ }
+ Parameter::Path(ref path) => {
+ if let Some(rc_context) = rc.context() {
+ let result = rc.evaluate2(rc_context.borrow(), path)?;
+ Ok(PathAndJson::new(
+ Some(path.raw().to_owned()),
+ ScopedJson::Derived(result.as_json().clone()),
+ ))
+ } else {
+ let result = rc.evaluate2(ctx, path)?;
+ Ok(PathAndJson::new(Some(path.raw().to_owned()), result))
+ }
+ }
+ Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))),
+ Parameter::Subexpression(ref t) => match *t.as_element() {
+ Expression(ref ht) => {
+ let name = ht.name.expand_as_name(registry, ctx, rc)?;
+
+ let h = Helper::try_from_template(ht, registry, ctx, rc)?;
+ if let Some(ref d) = rc.get_local_helper(&name) {
+ call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)
+ } else {
+ let mut helper = registry.get_or_load_helper(&name)?;
+
+ if helper.is_none() {
+ helper = registry.get_or_load_helper(if ht.block {
+ BLOCK_HELPER_MISSING
+ } else {
+ HELPER_MISSING
+ })?;
+ }
+
+ helper
+ .ok_or_else(|| {
+ RenderError::new(format!("Helper not defined: {:?}", ht.name))
+ })
+ .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
+ }
+ }
+ _ => unreachable!(),
+ },
+ }
+ }
+}
+
+impl Renderable for Template {
+ fn render<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> Result<(), RenderError> {
+ rc.set_current_template_name(self.name.as_ref());
+ let iter = self.elements.iter();
+
+ for (idx, t) in iter.enumerate() {
+ t.render(registry, ctx, rc, out).map_err(|mut e| {
+ // add line/col number if the template has mapping data
+ if e.line_no.is_none() {
+ if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
+ e.line_no = Some(line);
+ e.column_no = Some(col);
+ }
+ }
+
+ if e.template_name.is_none() {
+ e.template_name = self.name.clone();
+ }
+
+ e
+ })?;
+ }
+ Ok(())
+ }
+}
+
+impl Evaluable for Template {
+ fn eval<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<(), RenderError> {
+ let iter = self.elements.iter();
+
+ for (idx, t) in iter.enumerate() {
+ t.eval(registry, ctx, rc).map_err(|mut e| {
+ if e.line_no.is_none() {
+ if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
+ e.line_no = Some(line);
+ e.column_no = Some(col);
+ }
+ }
+
+ e.template_name = self.name.clone();
+ e
+ })?;
+ }
+ Ok(())
+ }
+}
+
+fn helper_exists<'reg: 'rc, 'rc>(
+ name: &str,
+ reg: &Registry<'reg>,
+ rc: &RenderContext<'reg, 'rc>,
+) -> bool {
+ rc.has_local_helper(name) || reg.has_helper(name)
+}
+
+#[inline]
+fn render_helper<'reg: 'rc, 'rc>(
+ ht: &'reg HelperTemplate,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ let h = Helper::try_from_template(ht, registry, ctx, rc)?;
+ debug!(
+ "Rendering helper: {:?}, params: {:?}, hash: {:?}",
+ h.name(),
+ h.params(),
+ h.hash()
+ );
+ if let Some(ref d) = rc.get_local_helper(h.name()) {
+ d.call(&h, registry, ctx, rc, out)
+ } else {
+ let mut helper = registry.get_or_load_helper(h.name())?;
+
+ if helper.is_none() {
+ helper = registry.get_or_load_helper(if ht.block {
+ BLOCK_HELPER_MISSING
+ } else {
+ HELPER_MISSING
+ })?;
+ }
+
+ helper
+ .ok_or_else(|| RenderError::new(format!("Helper not defined: {:?}", h.name())))
+ .and_then(|d| d.call(&h, registry, ctx, rc, out))
+ }
+}
+
+pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: String) -> String {
+ if !rc.is_disable_escape() {
+ r.get_escape_fn()(&content)
+ } else {
+ content
+ }
+}
+
+impl Renderable for TemplateElement {
+ fn render<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> Result<(), RenderError> {
+ match *self {
+ RawString(ref v) => {
+ out.write(v.as_ref())?;
+ Ok(())
+ }
+ Expression(ref ht) | HtmlExpression(ref ht) => {
+ let is_html_expression = matches!(self, HtmlExpression(_));
+ if is_html_expression {
+ rc.set_disable_escape(true);
+ }
+
+ // test if the expression is to render some value
+ let result = if ht.is_name_only() {
+ let helper_name = ht.name.expand_as_name(registry, ctx, rc)?;
+ if helper_exists(&helper_name, registry, rc) {
+ render_helper(ht, registry, ctx, rc, out)
+ } else {
+ debug!("Rendering value: {:?}", ht.name);
+ let context_json = ht.name.expand(registry, ctx, rc)?;
+ if context_json.is_value_missing() {
+ if registry.strict_mode() {
+ Err(RenderError::strict_error(context_json.relative_path()))
+ } else {
+ // helper missing
+ if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? {
+ let h = Helper::try_from_template(ht, registry, ctx, rc)?;
+ hook.call(&h, registry, ctx, rc, out)
+ } else {
+ Ok(())
+ }
+ }
+ } else {
+ let rendered = context_json.value().render();
+ let output = do_escape(registry, rc, rendered);
+ out.write(output.as_ref())?;
+ Ok(())
+ }
+ }
+ } else {
+ // this is a helper expression
+ render_helper(ht, registry, ctx, rc, out)
+ };
+
+ if is_html_expression {
+ rc.set_disable_escape(false);
+ }
+
+ result
+ }
+ HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
+ DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
+ PartialExpression(ref dt) | PartialBlock(ref dt) => {
+ let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
+
+ partial::expand_partial(&di, registry, ctx, rc, out)
+ }
+ _ => Ok(()),
+ }
+ }
+}
+
+impl Evaluable for TemplateElement {
+ fn eval<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<(), RenderError> {
+ match *self {
+ DecoratorExpression(ref dt) | DecoratorBlock(ref dt) => {
+ let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
+ match registry.get_decorator(di.name()) {
+ Some(d) => d.call(&di, registry, ctx, rc),
+ None => Err(RenderError::new(format!(
+ "Decorator not defined: {:?}",
+ dt.name
+ ))),
+ }
+ }
+ _ => Ok(()),
+ }
+ }
+}
+
+#[test]
+fn test_raw_string() {
+ let r = Registry::new();
+ let raw_string = RawString("<h1>hello world</h1>".to_string());
+
+ let mut out = StringOutput::new();
+ let ctx = Context::null();
+ {
+ let mut rc = RenderContext::new(None);
+ raw_string.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
+ }
+ assert_eq!(
+ out.into_string().unwrap(),
+ "<h1>hello world</h1>".to_string()
+ );
+}
+
+#[test]
+fn test_expression() {
+ let r = Registry::new();
+ let element = Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
+ &["hello"],
+ ))));
+
+ let mut out = StringOutput::new();
+ let mut m: BTreeMap<String, String> = BTreeMap::new();
+ let value = "<p></p>".to_string();
+ m.insert("hello".to_string(), value);
+ let ctx = Context::wraps(&m).unwrap();
+ {
+ let mut rc = RenderContext::new(None);
+ element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
+ }
+
+ assert_eq!(
+ out.into_string().unwrap(),
+ "&lt;p&gt;&lt;/p&gt;".to_string()
+ );
+}
+
+#[test]
+fn test_html_expression() {
+ let r = Registry::new();
+ let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
+ &["hello"],
+ ))));
+
+ let mut out = StringOutput::new();
+ let mut m: BTreeMap<String, String> = BTreeMap::new();
+ let value = "world";
+ m.insert("hello".to_string(), value.to_string());
+ let ctx = Context::wraps(&m).unwrap();
+ {
+ let mut rc = RenderContext::new(None);
+ element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
+ }
+
+ assert_eq!(out.into_string().unwrap(), value.to_string());
+}
+
+#[test]
+fn test_template() {
+ let r = Registry::new();
+ let mut out = StringOutput::new();
+ let mut m: BTreeMap<String, String> = BTreeMap::new();
+ let value = "world".to_string();
+ m.insert("hello".to_string(), value);
+ let ctx = Context::wraps(&m).unwrap();
+
+ let elements: Vec<TemplateElement> = vec![
+ RawString("<h1>".to_string()),
+ Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
+ &["hello"],
+ )))),
+ RawString("</h1>".to_string()),
+ Comment("".to_string()),
+ ];
+
+ let template = Template {
+ elements,
+ name: None,
+ mapping: Vec::new(),
+ };
+
+ {
+ let mut rc = RenderContext::new(None);
+ template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
+ }
+
+ assert_eq!(out.into_string().unwrap(), "<h1>world</h1>".to_string());
+}
+
+#[test]
+fn test_render_context_promotion_and_demotion() {
+ use crate::json::value::to_json;
+ let mut render_context = RenderContext::new(None);
+ let mut block = BlockContext::new();
+
+ block.set_local_var("index", to_json(0));
+ render_context.push_block(block);
+
+ render_context.push_block(BlockContext::new());
+ assert_eq!(
+ render_context.get_local_var(1, "index").unwrap(),
+ &to_json(0)
+ );
+
+ render_context.pop_block();
+
+ assert_eq!(
+ render_context.get_local_var(0, "index").unwrap(),
+ &to_json(0)
+ );
+}
+
+#[test]
+fn test_render_subexpression_issue_115() {
+ use crate::support::str::StringWriter;
+
+ let mut r = Registry::new();
+ r.register_helper(
+ "format",
+ Box::new(
+ |h: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> {
+ out.write(format!("{}", h.param(0).unwrap().value().render()).as_ref())
+ .map(|_| ())
+ .map_err(RenderError::from)
+ },
+ ),
+ );
+
+ let mut sw = StringWriter::new();
+ let mut m: BTreeMap<String, String> = BTreeMap::new();
+ m.insert("a".to_string(), "123".to_string());
+
+ {
+ if let Err(e) = r.render_template_to_write("{{format (format a)}}", &m, &mut sw) {
+ panic!("{}", e);
+ }
+ }
+
+ assert_eq!(sw.into_string(), "123".to_string());
+}
+
+#[test]
+fn test_render_error_line_no() {
+ let mut r = Registry::new();
+ let m: BTreeMap<String, String> = BTreeMap::new();
+
+ let name = "invalid_template";
+ assert!(r
+ .register_template_string(name, "<h1>\n{{#if true}}\n {{#each}}{{/each}}\n{{/if}}")
+ .is_ok());
+
+ if let Err(e) = r.render(name, &m) {
+ assert_eq!(e.line_no.unwrap(), 3);
+ assert_eq!(e.column_no.unwrap(), 3);
+ assert_eq!(e.template_name, Some(name.to_owned()));
+ } else {
+ panic!("Error expected");
+ }
+}
+
+#[test]
+fn test_partial_failback_render() {
+ let mut r = Registry::new();
+
+ assert!(r
+ .register_template_string("parent", "<html>{{> layout}}</html>")
+ .is_ok());
+ assert!(r
+ .register_template_string(
+ "child",
+ "{{#*inline \"layout\"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}"
+ )
+ .is_ok());
+ assert!(r.register_template_string("seg", "1234").is_ok());
+
+ let r = r.render("child", &true).expect("should work");
+ assert_eq!(r, "<html>content</html>");
+}
+
+#[test]
+fn test_key_with_slash() {
+ let mut r = Registry::new();
+
+ assert!(r
+ .register_template_string("t", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
+ .is_ok());
+
+ let r = r.render("t", &json!({"/foo": "bar"})).unwrap();
+
+ assert_eq!(r, "/foo: bar\n");
+}
+
+#[test]
+fn test_comment() {
+ let r = Registry::new();
+
+ assert_eq!(
+ r.render_template("Hello {{this}} {{! test me }}", &0)
+ .unwrap(),
+ "Hello 0 "
+ );
+}
+
+#[test]
+fn test_zero_args_heler() {
+ let mut r = Registry::new();
+
+ r.register_helper(
+ "name",
+ Box::new(
+ |_: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> { out.write("N/A").map_err(Into::into) },
+ ),
+ );
+
+ r.register_template_string("t0", "Output name: {{name}}")
+ .unwrap();
+ r.register_template_string("t1", "Output name: {{first_name}}")
+ .unwrap();
+ r.register_template_string("t2", "Output name: {{./name}}")
+ .unwrap();
+
+ // when "name" is available in context, use context first
+ assert_eq!(
+ r.render("t0", &json!({"name": "Alex"})).unwrap(),
+ "Output name: N/A"
+ );
+
+ // when "name" is unavailable, call helper with same name
+ assert_eq!(
+ r.render("t2", &json!({"name": "Alex"})).unwrap(),
+ "Output name: Alex"
+ );
+
+ // output nothing when neither context nor helper available
+ assert_eq!(
+ r.render("t1", &json!({"name": "Alex"})).unwrap(),
+ "Output name: "
+ );
+
+ // generate error in strict mode for above case
+ r.set_strict_mode(true);
+ assert!(r.render("t1", &json!({"name": "Alex"})).is_err());
+
+ // output nothing when helperMissing was defined
+ r.set_strict_mode(false);
+ r.register_helper(
+ "helperMissing",
+ Box::new(
+ |h: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> {
+ let name = h.name();
+ out.write(&format!("{} not resolved", name))?;
+ Ok(())
+ },
+ ),
+ );
+ assert_eq!(
+ r.render("t1", &json!({"name": "Alex"})).unwrap(),
+ "Output name: first_name not resolved"
+ );
+}
diff --git a/vendor/handlebars/src/sources.rs b/vendor/handlebars/src/sources.rs
new file mode 100644
index 000000000..8c8b2ba57
--- /dev/null
+++ b/vendor/handlebars/src/sources.rs
@@ -0,0 +1,34 @@
+use std::fs::File;
+use std::io::{BufReader, Error as IOError, Read};
+use std::path::PathBuf;
+
+pub(crate) trait Source {
+ type Item;
+ type Error;
+
+ fn load(&self) -> Result<Self::Item, Self::Error>;
+}
+
+pub(crate) struct FileSource {
+ path: PathBuf,
+}
+
+impl FileSource {
+ pub(crate) fn new(path: PathBuf) -> FileSource {
+ FileSource { path }
+ }
+}
+
+impl Source for FileSource {
+ type Item = String;
+ type Error = IOError;
+
+ fn load(&self) -> Result<Self::Item, Self::Error> {
+ let mut reader = BufReader::new(File::open(&self.path)?);
+
+ let mut buf = String::new();
+ reader.read_to_string(&mut buf)?;
+
+ Ok(buf)
+ }
+}
diff --git a/vendor/handlebars/src/support.rs b/vendor/handlebars/src/support.rs
new file mode 100644
index 000000000..bd5564d32
--- /dev/null
+++ b/vendor/handlebars/src/support.rs
@@ -0,0 +1,76 @@
+pub mod str {
+ use std::io::{Result, Write};
+
+ #[derive(Debug)]
+ pub struct StringWriter {
+ buf: Vec<u8>,
+ }
+
+ impl Default for StringWriter {
+ fn default() -> Self {
+ Self::new()
+ }
+ }
+
+ impl StringWriter {
+ pub fn new() -> StringWriter {
+ StringWriter {
+ buf: Vec::with_capacity(8 * 1024),
+ }
+ }
+
+ pub fn into_string(self) -> String {
+ if let Ok(s) = String::from_utf8(self.buf) {
+ s
+ } else {
+ String::new()
+ }
+ }
+ }
+
+ impl Write for StringWriter {
+ fn write(&mut self, buf: &[u8]) -> Result<usize> {
+ self.buf.extend_from_slice(buf);
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> Result<()> {
+ Ok(())
+ }
+ }
+
+ /// See https://github.com/handlebars-lang/handlebars.js/blob/37411901da42200ced8e1a7fc2f67bf83526b497/lib/handlebars/utils.js#L1
+ pub fn escape_html(s: &str) -> String {
+ let mut output = String::new();
+ for c in s.chars() {
+ match c {
+ '<' => output.push_str("&lt;"),
+ '>' => output.push_str("&gt;"),
+ '"' => output.push_str("&quot;"),
+ '&' => output.push_str("&amp;"),
+ '\'' => output.push_str("&#x27;"),
+ '`' => output.push_str("&#x60;"),
+ '=' => output.push_str("&#x3D;"),
+ _ => output.push(c),
+ }
+ }
+ output
+ }
+
+ #[cfg(test)]
+ mod test {
+ use crate::support::str::StringWriter;
+ use std::io::Write;
+
+ #[test]
+ fn test_string_writer() {
+ let mut sw = StringWriter::new();
+
+ let _ = sw.write("hello".to_owned().into_bytes().as_ref());
+ let _ = sw.write("world".to_owned().into_bytes().as_ref());
+
+ let s = sw.into_string();
+ assert_eq!(s, "helloworld".to_string());
+ }
+ }
+}
diff --git a/vendor/handlebars/src/template.rs b/vendor/handlebars/src/template.rs
new file mode 100644
index 000000000..87f853799
--- /dev/null
+++ b/vendor/handlebars/src/template.rs
@@ -0,0 +1,1254 @@
+use std::collections::{HashMap, VecDeque};
+use std::convert::From;
+use std::iter::Peekable;
+use std::str::FromStr;
+
+use pest::error::LineColLocation;
+use pest::iterators::Pair;
+use pest::{Parser, Position, Span};
+use serde_json::value::Value as Json;
+
+use crate::error::{TemplateError, TemplateErrorReason};
+use crate::grammar::{self, HandlebarsParser, Rule};
+use crate::json::path::{parse_json_path_from_iter, Path};
+
+use self::TemplateElement::*;
+
+#[derive(PartialEq, Clone, Debug)]
+pub struct TemplateMapping(pub usize, pub usize);
+
+/// A handlebars template
+#[derive(PartialEq, Clone, Debug, Default)]
+pub struct Template {
+ pub name: Option<String>,
+ pub elements: Vec<TemplateElement>,
+ pub mapping: Vec<TemplateMapping>,
+}
+
+#[derive(PartialEq, Clone, Debug)]
+pub struct Subexpression {
+ // we use box here avoid resursive struct definition
+ pub element: Box<TemplateElement>,
+}
+
+impl Subexpression {
+ pub fn new(
+ name: Parameter,
+ params: Vec<Parameter>,
+ hash: HashMap<String, Parameter>,
+ ) -> Subexpression {
+ Subexpression {
+ element: Box::new(Expression(Box::new(HelperTemplate {
+ name,
+ params,
+ hash,
+ template: None,
+ inverse: None,
+ block_param: None,
+ block: false,
+ }))),
+ }
+ }
+
+ pub fn is_helper(&self) -> bool {
+ match *self.as_element() {
+ TemplateElement::Expression(ref ht) => !ht.is_name_only(),
+ _ => false,
+ }
+ }
+
+ pub fn as_element(&self) -> &TemplateElement {
+ self.element.as_ref()
+ }
+
+ pub fn name(&self) -> &str {
+ match *self.as_element() {
+ // FIXME: avoid unwrap here
+ Expression(ref ht) => ht.name.as_name().unwrap(),
+ _ => unreachable!(),
+ }
+ }
+
+ pub fn params(&self) -> Option<&Vec<Parameter>> {
+ match *self.as_element() {
+ Expression(ref ht) => Some(&ht.params),
+ _ => None,
+ }
+ }
+
+ pub fn hash(&self) -> Option<&HashMap<String, Parameter>> {
+ match *self.as_element() {
+ Expression(ref ht) => Some(&ht.hash),
+ _ => None,
+ }
+ }
+}
+
+#[derive(PartialEq, Clone, Debug)]
+pub enum BlockParam {
+ Single(Parameter),
+ Pair((Parameter, Parameter)),
+}
+
+#[derive(PartialEq, Clone, Debug)]
+pub struct ExpressionSpec {
+ pub name: Parameter,
+ pub params: Vec<Parameter>,
+ pub hash: HashMap<String, Parameter>,
+ pub block_param: Option<BlockParam>,
+ pub omit_pre_ws: bool,
+ pub omit_pro_ws: bool,
+}
+
+#[derive(PartialEq, Clone, Debug)]
+pub enum Parameter {
+ // for helper name only
+ Name(String),
+ // for expression, helper param and hash
+ Path(Path),
+ Literal(Json),
+ Subexpression(Subexpression),
+}
+
+#[derive(PartialEq, Clone, Debug)]
+pub struct HelperTemplate {
+ pub name: Parameter,
+ pub params: Vec<Parameter>,
+ pub hash: HashMap<String, Parameter>,
+ pub block_param: Option<BlockParam>,
+ pub template: Option<Template>,
+ pub inverse: Option<Template>,
+ pub block: bool,
+}
+
+impl HelperTemplate {
+ // test only
+ pub(crate) fn with_path(path: Path) -> HelperTemplate {
+ HelperTemplate {
+ name: Parameter::Path(path),
+ params: Vec::with_capacity(5),
+ hash: HashMap::new(),
+ block_param: None,
+ template: None,
+ inverse: None,
+ block: false,
+ }
+ }
+
+ pub(crate) fn is_name_only(&self) -> bool {
+ !self.block && self.params.is_empty() && self.hash.is_empty()
+ }
+}
+
+#[derive(PartialEq, Clone, Debug)]
+pub struct DecoratorTemplate {
+ pub name: Parameter,
+ pub params: Vec<Parameter>,
+ pub hash: HashMap<String, Parameter>,
+ pub template: Option<Template>,
+}
+
+impl Parameter {
+ pub fn as_name(&self) -> Option<&str> {
+ match self {
+ Parameter::Name(ref n) => Some(n),
+ Parameter::Path(ref p) => Some(p.raw()),
+ _ => None,
+ }
+ }
+
+ pub fn parse(s: &str) -> Result<Parameter, TemplateError> {
+ let parser = HandlebarsParser::parse(Rule::parameter, s)
+ .map_err(|_| TemplateError::of(TemplateErrorReason::InvalidParam(s.to_owned())))?;
+
+ let mut it = parser.flatten().peekable();
+ Template::parse_param(s, &mut it, s.len() - 1)
+ }
+
+ fn debug_name(&self) -> String {
+ if let Some(name) = self.as_name() {
+ name.to_owned()
+ } else {
+ format!("{:?}", self)
+ }
+ }
+}
+
+impl Template {
+ pub fn new() -> Template {
+ Template::default()
+ }
+
+ fn push_element(&mut self, e: TemplateElement, line: usize, col: usize) {
+ self.elements.push(e);
+ self.mapping.push(TemplateMapping(line, col));
+ }
+
+ fn parse_subexpression<'a, I>(
+ source: &'a str,
+ it: &mut Peekable<I>,
+ limit: usize,
+ ) -> Result<Parameter, TemplateError>
+ where
+ I: Iterator<Item = Pair<'a, Rule>>,
+ {
+ let espec = Template::parse_expression(source, it.by_ref(), limit)?;
+ Ok(Parameter::Subexpression(Subexpression::new(
+ espec.name,
+ espec.params,
+ espec.hash,
+ )))
+ }
+
+ fn parse_name<'a, I>(
+ source: &'a str,
+ it: &mut Peekable<I>,
+ _: usize,
+ ) -> Result<Parameter, TemplateError>
+ where
+ I: Iterator<Item = Pair<'a, Rule>>,
+ {
+ let name_node = it.next().unwrap();
+ let rule = name_node.as_rule();
+ let name_span = name_node.as_span();
+ match rule {
+ Rule::identifier | Rule::partial_identifier | Rule::invert_tag_item => {
+ Ok(Parameter::Name(name_span.as_str().to_owned()))
+ }
+ Rule::reference => {
+ let paths = parse_json_path_from_iter(it, name_span.end());
+ Ok(Parameter::Path(Path::new(name_span.as_str(), paths)))
+ }
+ Rule::subexpression => {
+ Template::parse_subexpression(source, it.by_ref(), name_span.end())
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ fn parse_param<'a, I>(
+ source: &'a str,
+ it: &mut Peekable<I>,
+ _: usize,
+ ) -> Result<Parameter, TemplateError>
+ where
+ I: Iterator<Item = Pair<'a, Rule>>,
+ {
+ let mut param = it.next().unwrap();
+ if param.as_rule() == Rule::param {
+ param = it.next().unwrap();
+ }
+ let param_rule = param.as_rule();
+ let param_span = param.as_span();
+ let result = match param_rule {
+ Rule::reference => {
+ let path_segs = parse_json_path_from_iter(it, param_span.end());
+ Parameter::Path(Path::new(param_span.as_str(), path_segs))
+ }
+ Rule::literal => {
+ let s = param_span.as_str();
+ if let Ok(json) = Json::from_str(s) {
+ Parameter::Literal(json)
+ } else {
+ Parameter::Name(s.to_owned())
+ }
+ }
+ Rule::subexpression => {
+ Template::parse_subexpression(source, it.by_ref(), param_span.end())?
+ }
+ _ => unreachable!(),
+ };
+
+ while let Some(n) = it.peek() {
+ let n_span = n.as_span();
+ if n_span.end() > param_span.end() {
+ break;
+ }
+ it.next();
+ }
+
+ Ok(result)
+ }
+
+ fn parse_hash<'a, I>(
+ source: &'a str,
+ it: &mut Peekable<I>,
+ limit: usize,
+ ) -> Result<(String, Parameter), TemplateError>
+ where
+ I: Iterator<Item = Pair<'a, Rule>>,
+ {
+ let name = it.next().unwrap();
+ let name_node = name.as_span();
+ // identifier
+ let key = name_node.as_str().to_owned();
+
+ let value = Template::parse_param(source, it.by_ref(), limit)?;
+ Ok((key, value))
+ }
+
+ fn parse_block_param<'a, I>(_: &'a str, it: &mut Peekable<I>, limit: usize) -> BlockParam
+ where
+ I: Iterator<Item = Pair<'a, Rule>>,
+ {
+ let p1_name = it.next().unwrap();
+ let p1_name_span = p1_name.as_span();
+ // identifier
+ let p1 = p1_name_span.as_str().to_owned();
+
+ let p2 = it.peek().and_then(|p2_name| {
+ let p2_name_span = p2_name.as_span();
+ if p2_name_span.end() <= limit {
+ Some(p2_name_span.as_str().to_owned())
+ } else {
+ None
+ }
+ });
+
+ if let Some(p2) = p2 {
+ it.next();
+ BlockParam::Pair((Parameter::Name(p1), Parameter::Name(p2)))
+ } else {
+ BlockParam::Single(Parameter::Name(p1))
+ }
+ }
+
+ fn parse_expression<'a, I>(
+ source: &'a str,
+ it: &mut Peekable<I>,
+ limit: usize,
+ ) -> Result<ExpressionSpec, TemplateError>
+ where
+ I: Iterator<Item = Pair<'a, Rule>>,
+ {
+ let mut params: Vec<Parameter> = Vec::new();
+ let mut hashes: HashMap<String, Parameter> = HashMap::new();
+ let mut omit_pre_ws = false;
+ let mut omit_pro_ws = false;
+ let mut block_param = None;
+
+ if it.peek().unwrap().as_rule() == Rule::pre_whitespace_omitter {
+ omit_pre_ws = true;
+ it.next();
+ }
+
+ let name = Template::parse_name(source, it.by_ref(), limit)?;
+
+ loop {
+ let rule;
+ let end;
+ if let Some(pair) = it.peek() {
+ let pair_span = pair.as_span();
+ if pair_span.end() < limit {
+ rule = pair.as_rule();
+ end = pair_span.end();
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+
+ it.next();
+
+ match rule {
+ Rule::param => {
+ params.push(Template::parse_param(source, it.by_ref(), end)?);
+ }
+ Rule::hash => {
+ let (key, value) = Template::parse_hash(source, it.by_ref(), end)?;
+ hashes.insert(key, value);
+ }
+ Rule::block_param => {
+ block_param = Some(Template::parse_block_param(source, it.by_ref(), end));
+ }
+ Rule::pro_whitespace_omitter => {
+ omit_pro_ws = true;
+ }
+ _ => {}
+ }
+ }
+ Ok(ExpressionSpec {
+ name,
+ params,
+ hash: hashes,
+ block_param,
+ omit_pre_ws,
+ omit_pro_ws,
+ })
+ }
+
+ fn remove_previous_whitespace(template_stack: &mut VecDeque<Template>) {
+ let t = template_stack.front_mut().unwrap();
+ if let Some(el) = t.elements.last_mut() {
+ if let RawString(ref mut text) = el {
+ *text = text.trim_end().to_owned();
+ }
+ }
+ }
+
+ fn process_standalone_statement(
+ template_stack: &mut VecDeque<Template>,
+ source: &str,
+ current_span: &Span<'_>,
+ ) -> bool {
+ let with_trailing_newline = grammar::starts_with_empty_line(&source[current_span.end()..]);
+
+ if with_trailing_newline {
+ let with_leading_newline =
+ grammar::ends_with_empty_line(&source[..current_span.start()]);
+
+ if with_leading_newline {
+ let t = template_stack.front_mut().unwrap();
+ // check the last element before current
+ if let Some(el) = t.elements.last_mut() {
+ if let RawString(ref mut text) = el {
+ // trim leading space for standalone statement
+ *text = text
+ .trim_end_matches(grammar::whitespace_matcher)
+ .to_owned();
+ }
+ }
+ }
+
+ // return true when the item is the first element in root template
+ current_span.start() == 0 || with_leading_newline
+ } else {
+ false
+ }
+ }
+
+ fn raw_string<'a>(
+ source: &'a str,
+ pair: Option<Pair<'a, Rule>>,
+ trim_start: bool,
+ trim_start_line: bool,
+ ) -> TemplateElement {
+ let mut s = String::from(source);
+
+ if let Some(pair) = pair {
+ // the source may contains leading space because of pest's limitation
+ // we calculate none space start here in order to correct the offset
+ let pair_span = pair.as_span();
+
+ let current_start = pair_span.start();
+ let span_length = pair_span.end() - current_start;
+ let leading_space_offset = s.len() - span_length;
+
+ // we would like to iterate pair reversely in order to remove certain
+ // index from our string buffer so here we convert the inner pairs to
+ // a vector.
+ for sub_pair in pair.into_inner().rev() {
+ // remove escaped backslash
+ if sub_pair.as_rule() == Rule::escape {
+ let escape_span = sub_pair.as_span();
+
+ let backslash_pos = escape_span.start();
+ let backslash_rel_pos = leading_space_offset + backslash_pos - current_start;
+ s.remove(backslash_rel_pos);
+ }
+ }
+ }
+
+ if trim_start {
+ RawString(s.trim_start().to_owned())
+ } else if trim_start_line {
+ RawString(
+ s.trim_start_matches(grammar::whitespace_matcher)
+ .trim_start_matches(grammar::newline_matcher)
+ .to_owned(),
+ )
+ } else {
+ RawString(s)
+ }
+ }
+
+ pub fn compile<'a>(source: &'a str) -> Result<Template, TemplateError> {
+ let mut helper_stack: VecDeque<HelperTemplate> = VecDeque::new();
+ let mut decorator_stack: VecDeque<DecoratorTemplate> = VecDeque::new();
+ let mut template_stack: VecDeque<Template> = VecDeque::new();
+
+ let mut omit_pro_ws = false;
+ // flag for newline removal of standalone statements
+ // this option is marked as true when standalone statement is detected
+ // then the leading whitespaces and newline of next rawstring will be trimed
+ let mut trim_line_requiered = false;
+
+ let parser_queue = HandlebarsParser::parse(Rule::handlebars, source).map_err(|e| {
+ let (line_no, col_no) = match e.line_col {
+ LineColLocation::Pos(line_col) => line_col,
+ LineColLocation::Span(line_col, _) => line_col,
+ };
+ TemplateError::of(TemplateErrorReason::InvalidSyntax).at(source, line_no, col_no)
+ })?;
+
+ // dbg!(parser_queue.clone().flatten());
+
+ // remove escape from our pair queue
+ let mut it = parser_queue
+ .flatten()
+ .filter(|p| {
+ // remove rules that should be silent but not for now due to pest limitation
+ !matches!(p.as_rule(), Rule::escape)
+ })
+ .peekable();
+ let mut end_pos: Option<Position<'_>> = None;
+ loop {
+ if let Some(pair) = it.next() {
+ let prev_end = end_pos.as_ref().map(|p| p.pos()).unwrap_or(0);
+ let rule = pair.as_rule();
+ let span = pair.as_span();
+
+ let is_trailing_string = rule != Rule::template
+ && span.start() != prev_end
+ && !omit_pro_ws
+ && rule != Rule::raw_text
+ && rule != Rule::raw_block_text;
+
+ if is_trailing_string {
+ // trailing string check
+ let (line_no, col_no) = span.start_pos().line_col();
+ if rule == Rule::raw_block_end {
+ let mut t = Template::new();
+ t.push_element(
+ Template::raw_string(
+ &source[prev_end..span.start()],
+ None,
+ false,
+ trim_line_requiered,
+ ),
+ line_no,
+ col_no,
+ );
+ template_stack.push_front(t);
+ } else {
+ let t = template_stack.front_mut().unwrap();
+ t.push_element(
+ Template::raw_string(
+ &source[prev_end..span.start()],
+ None,
+ false,
+ trim_line_requiered,
+ ),
+ line_no,
+ col_no,
+ );
+ }
+ }
+
+ let (line_no, col_no) = span.start_pos().line_col();
+ match rule {
+ Rule::template => {
+ template_stack.push_front(Template::new());
+ }
+ Rule::raw_text => {
+ // leading space fix
+ let start = if span.start() != prev_end {
+ prev_end
+ } else {
+ span.start()
+ };
+
+ let t = template_stack.front_mut().unwrap();
+ t.push_element(
+ Template::raw_string(
+ &source[start..span.end()],
+ Some(pair.clone()),
+ omit_pro_ws,
+ trim_line_requiered,
+ ),
+ line_no,
+ col_no,
+ );
+
+ // reset standalone statement marker
+ trim_line_requiered = false;
+ }
+ Rule::helper_block_start
+ | Rule::raw_block_start
+ | Rule::decorator_block_start
+ | Rule::partial_block_start => {
+ let exp = Template::parse_expression(source, it.by_ref(), span.end())?;
+
+ match rule {
+ Rule::helper_block_start | Rule::raw_block_start => {
+ let helper_template = HelperTemplate {
+ name: exp.name,
+ params: exp.params,
+ hash: exp.hash,
+ block_param: exp.block_param,
+ block: true,
+ template: None,
+ inverse: None,
+ };
+ helper_stack.push_front(helper_template);
+ }
+ Rule::decorator_block_start | Rule::partial_block_start => {
+ let decorator = DecoratorTemplate {
+ name: exp.name,
+ params: exp.params,
+ hash: exp.hash,
+ template: None,
+ };
+ decorator_stack.push_front(decorator);
+ }
+ _ => unreachable!(),
+ }
+
+ if exp.omit_pre_ws {
+ Template::remove_previous_whitespace(&mut template_stack);
+ }
+ omit_pro_ws = exp.omit_pro_ws;
+
+ // standalone statement check, it also removes leading whitespaces of
+ // previous rawstring when standalone statement detected
+ trim_line_requiered = Template::process_standalone_statement(
+ &mut template_stack,
+ source,
+ &span,
+ );
+
+ let t = template_stack.front_mut().unwrap();
+ t.mapping.push(TemplateMapping(line_no, col_no));
+ }
+ Rule::invert_tag => {
+ // hack: invert_tag structure is similar to ExpressionSpec, so I
+ // use it here to represent the data
+ let exp = Template::parse_expression(source, it.by_ref(), span.end())?;
+
+ if exp.omit_pre_ws {
+ Template::remove_previous_whitespace(&mut template_stack);
+ }
+ omit_pro_ws = exp.omit_pro_ws;
+
+ // standalone statement check, it also removes leading whitespaces of
+ // previous rawstring when standalone statement detected
+ trim_line_requiered = Template::process_standalone_statement(
+ &mut template_stack,
+ source,
+ &span,
+ );
+
+ let t = template_stack.pop_front().unwrap();
+ let h = helper_stack.front_mut().unwrap();
+ h.template = Some(t);
+ }
+ Rule::raw_block_text => {
+ let mut t = Template::new();
+ t.push_element(
+ Template::raw_string(
+ span.as_str(),
+ Some(pair.clone()),
+ omit_pro_ws,
+ trim_line_requiered,
+ ),
+ line_no,
+ col_no,
+ );
+ template_stack.push_front(t);
+ }
+ Rule::expression
+ | Rule::html_expression
+ | Rule::decorator_expression
+ | Rule::partial_expression
+ | Rule::helper_block_end
+ | Rule::raw_block_end
+ | Rule::decorator_block_end
+ | Rule::partial_block_end => {
+ let exp = Template::parse_expression(source, it.by_ref(), span.end())?;
+
+ if exp.omit_pre_ws {
+ Template::remove_previous_whitespace(&mut template_stack);
+ }
+ omit_pro_ws = exp.omit_pro_ws;
+
+ match rule {
+ Rule::expression | Rule::html_expression => {
+ let helper_template = HelperTemplate {
+ name: exp.name,
+ params: exp.params,
+ hash: exp.hash,
+ block_param: exp.block_param,
+ block: false,
+ template: None,
+ inverse: None,
+ };
+ let el = if rule == Rule::expression {
+ Expression(Box::new(helper_template))
+ } else {
+ HtmlExpression(Box::new(helper_template))
+ };
+ let t = template_stack.front_mut().unwrap();
+ t.push_element(el, line_no, col_no);
+ }
+ Rule::decorator_expression | Rule::partial_expression => {
+ let decorator = DecoratorTemplate {
+ name: exp.name,
+ params: exp.params,
+ hash: exp.hash,
+ template: None,
+ };
+ let el = if rule == Rule::decorator_expression {
+ DecoratorExpression(Box::new(decorator))
+ } else {
+ PartialExpression(Box::new(decorator))
+ };
+ let t = template_stack.front_mut().unwrap();
+ t.push_element(el, line_no, col_no);
+ }
+ Rule::helper_block_end | Rule::raw_block_end => {
+ // standalone statement check, it also removes leading whitespaces of
+ // previous rawstring when standalone statement detected
+ trim_line_requiered = Template::process_standalone_statement(
+ &mut template_stack,
+ source,
+ &span,
+ );
+
+ let mut h = helper_stack.pop_front().unwrap();
+ let close_tag_name = exp.name.as_name();
+ if h.name.as_name() == close_tag_name {
+ let prev_t = template_stack.pop_front().unwrap();
+ if h.template.is_some() {
+ h.inverse = Some(prev_t);
+ } else {
+ h.template = Some(prev_t);
+ }
+ let t = template_stack.front_mut().unwrap();
+ t.elements.push(HelperBlock(Box::new(h)));
+ } else {
+ return Err(TemplateError::of(
+ TemplateErrorReason::MismatchingClosedHelper(
+ h.name.debug_name(),
+ exp.name.debug_name(),
+ ),
+ )
+ .at(source, line_no, col_no));
+ }
+ }
+ Rule::decorator_block_end | Rule::partial_block_end => {
+ // standalone statement check, it also removes leading whitespaces of
+ // previous rawstring when standalone statement detected
+ trim_line_requiered = Template::process_standalone_statement(
+ &mut template_stack,
+ source,
+ &span,
+ );
+
+ let mut d = decorator_stack.pop_front().unwrap();
+ let close_tag_name = exp.name.as_name();
+ if d.name.as_name() == close_tag_name {
+ let prev_t = template_stack.pop_front().unwrap();
+ d.template = Some(prev_t);
+ let t = template_stack.front_mut().unwrap();
+ if rule == Rule::decorator_block_end {
+ t.elements.push(DecoratorBlock(Box::new(d)));
+ } else {
+ t.elements.push(PartialBlock(Box::new(d)));
+ }
+ } else {
+ return Err(TemplateError::of(
+ TemplateErrorReason::MismatchingClosedDecorator(
+ d.name.debug_name(),
+ exp.name.debug_name(),
+ ),
+ )
+ .at(source, line_no, col_no));
+ }
+ }
+ _ => unreachable!(),
+ }
+ }
+ Rule::hbs_comment_compact => {
+ trim_line_requiered = Template::process_standalone_statement(
+ &mut template_stack,
+ source,
+ &span,
+ );
+
+ let text = span
+ .as_str()
+ .trim_start_matches("{{!")
+ .trim_end_matches("}}");
+ let t = template_stack.front_mut().unwrap();
+ t.push_element(Comment(text.to_owned()), line_no, col_no);
+ }
+ Rule::hbs_comment => {
+ trim_line_requiered = Template::process_standalone_statement(
+ &mut template_stack,
+ source,
+ &span,
+ );
+
+ let text = span
+ .as_str()
+ .trim_start_matches("{{!--")
+ .trim_end_matches("--}}");
+ let t = template_stack.front_mut().unwrap();
+ t.push_element(Comment(text.to_owned()), line_no, col_no);
+ }
+ _ => {}
+ }
+
+ if rule != Rule::template {
+ end_pos = Some(span.end_pos());
+ }
+ } else {
+ let prev_end = end_pos.as_ref().map(|e| e.pos()).unwrap_or(0);
+ if prev_end < source.len() {
+ let text = &source[prev_end..source.len()];
+ // is some called in if check
+ let (line_no, col_no) = end_pos.unwrap().line_col();
+ let t = template_stack.front_mut().unwrap();
+ t.push_element(RawString(text.to_owned()), line_no, col_no);
+ }
+ let root_template = template_stack.pop_front().unwrap();
+ return Ok(root_template);
+ }
+ }
+ }
+
+ pub fn compile_with_name<S: AsRef<str>>(
+ source: S,
+ name: String,
+ ) -> Result<Template, TemplateError> {
+ match Template::compile(source.as_ref()) {
+ Ok(mut t) => {
+ t.name = Some(name);
+ Ok(t)
+ }
+ Err(e) => Err(e.in_template(name)),
+ }
+ }
+}
+
+#[derive(PartialEq, Clone, Debug)]
+pub enum TemplateElement {
+ RawString(String),
+ HtmlExpression(Box<HelperTemplate>),
+ Expression(Box<HelperTemplate>),
+ HelperBlock(Box<HelperTemplate>),
+ DecoratorExpression(Box<DecoratorTemplate>),
+ DecoratorBlock(Box<DecoratorTemplate>),
+ PartialExpression(Box<DecoratorTemplate>),
+ PartialBlock(Box<DecoratorTemplate>),
+ Comment(String),
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::error::TemplateErrorReason;
+
+ #[test]
+ fn test_parse_escaped_tag_raw_string() {
+ let source = r"foo \{{bar}}";
+ let t = Template::compile(source).ok().unwrap();
+ assert_eq!(t.elements.len(), 1);
+ assert_eq!(
+ *t.elements.get(0).unwrap(),
+ RawString("foo {{bar}}".to_string())
+ );
+ }
+
+ #[test]
+ fn test_pure_backslash_raw_string() {
+ let source = r"\\\\";
+ let t = Template::compile(source).ok().unwrap();
+ assert_eq!(t.elements.len(), 1);
+ assert_eq!(*t.elements.get(0).unwrap(), RawString(source.to_string()));
+ }
+
+ #[test]
+ fn test_parse_escaped_block_raw_string() {
+ let source = r"\{{{{foo}}}} bar";
+ let t = Template::compile(source).ok().unwrap();
+ assert_eq!(t.elements.len(), 1);
+ assert_eq!(
+ *t.elements.get(0).unwrap(),
+ RawString("{{{{foo}}}} bar".to_string())
+ );
+ }
+
+ #[test]
+ fn test_parse_template() {
+ let source = "<h1>{{title}} 你好</h1> {{{content}}}
+{{#if date}}<p>good</p>{{else}}<p>bad</p>{{/if}}<img>{{foo bar}}中文你好
+{{#unless true}}kitkat{{^}}lollipop{{/unless}}";
+ let t = Template::compile(source).ok().unwrap();
+
+ assert_eq!(t.elements.len(), 10);
+
+ assert_eq!(*t.elements.get(0).unwrap(), RawString("<h1>".to_string()));
+ assert_eq!(
+ *t.elements.get(1).unwrap(),
+ Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
+ &["title"]
+ ))))
+ );
+
+ assert_eq!(
+ *t.elements.get(3).unwrap(),
+ HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
+ &["content"],
+ ))))
+ );
+
+ match *t.elements.get(5).unwrap() {
+ HelperBlock(ref h) => {
+ assert_eq!(h.name.as_name().unwrap(), "if".to_string());
+ assert_eq!(h.params.len(), 1);
+ assert_eq!(h.template.as_ref().unwrap().elements.len(), 1);
+ }
+ _ => {
+ panic!("Helper expected here.");
+ }
+ };
+
+ match *t.elements.get(7).unwrap() {
+ Expression(ref h) => {
+ assert_eq!(h.name.as_name().unwrap(), "foo".to_string());
+ assert_eq!(h.params.len(), 1);
+ assert_eq!(
+ *(h.params.get(0).unwrap()),
+ Parameter::Path(Path::with_named_paths(&["bar"]))
+ )
+ }
+ _ => {
+ panic!("Helper expression here");
+ }
+ };
+
+ match *t.elements.get(9).unwrap() {
+ HelperBlock(ref h) => {
+ assert_eq!(h.name.as_name().unwrap(), "unless".to_string());
+ assert_eq!(h.params.len(), 1);
+ assert_eq!(h.inverse.as_ref().unwrap().elements.len(), 1);
+ }
+ _ => {
+ panic!("Helper expression here");
+ }
+ };
+ }
+
+ #[test]
+ fn test_parse_block_partial_path_identifier() {
+ let source = "{{#> foo/bar}}{{/foo/bar}}";
+ assert!(Template::compile(source).is_ok());
+ }
+
+ #[test]
+ fn test_parse_error() {
+ let source = "{{#ifequals name compare=\"hello\"}}\nhello\n\t{{else}}\ngood";
+
+ let terr = Template::compile(source).unwrap_err();
+
+ assert!(matches!(terr.reason, TemplateErrorReason::InvalidSyntax));
+ assert_eq!(terr.line_no.unwrap(), 4);
+ assert_eq!(terr.column_no.unwrap(), 5);
+ }
+
+ #[test]
+ fn test_subexpression() {
+ let source =
+ "{{foo (bar)}}{{foo (bar baz)}} hello {{#if (baz bar) then=(bar)}}world{{/if}}";
+ let t = Template::compile(source).ok().unwrap();
+
+ assert_eq!(t.elements.len(), 4);
+ match *t.elements.get(0).unwrap() {
+ Expression(ref h) => {
+ assert_eq!(h.name.as_name().unwrap(), "foo".to_owned());
+ assert_eq!(h.params.len(), 1);
+ if let &Parameter::Subexpression(ref t) = h.params.get(0).unwrap() {
+ assert_eq!(t.name(), "bar".to_owned());
+ } else {
+ panic!("Subexpression expected");
+ }
+ }
+ _ => {
+ panic!("Helper expression expected");
+ }
+ };
+
+ match *t.elements.get(1).unwrap() {
+ Expression(ref h) => {
+ assert_eq!(h.name.as_name().unwrap(), "foo".to_string());
+ assert_eq!(h.params.len(), 1);
+ if let &Parameter::Subexpression(ref t) = h.params.get(0).unwrap() {
+ assert_eq!(t.name(), "bar".to_owned());
+ if let Some(Parameter::Path(p)) = t.params().unwrap().get(0) {
+ assert_eq!(p, &Path::with_named_paths(&["baz"]));
+ } else {
+ panic!("non-empty param expected ");
+ }
+ } else {
+ panic!("Subexpression expected");
+ }
+ }
+ _ => {
+ panic!("Helper expression expected");
+ }
+ };
+
+ match *t.elements.get(3).unwrap() {
+ HelperBlock(ref h) => {
+ assert_eq!(h.name.as_name().unwrap(), "if".to_string());
+ assert_eq!(h.params.len(), 1);
+ assert_eq!(h.hash.len(), 1);
+
+ if let &Parameter::Subexpression(ref t) = h.params.get(0).unwrap() {
+ assert_eq!(t.name(), "baz".to_owned());
+ if let Some(Parameter::Path(p)) = t.params().unwrap().get(0) {
+ assert_eq!(p, &Path::with_named_paths(&["bar"]));
+ } else {
+ panic!("non-empty param expected ");
+ }
+ } else {
+ panic!("Subexpression expected (baz bar)");
+ }
+
+ if let &Parameter::Subexpression(ref t) = h.hash.get("then").unwrap() {
+ assert_eq!(t.name(), "bar".to_owned());
+ } else {
+ panic!("Subexpression expected (bar)");
+ }
+ }
+ _ => {
+ panic!("HelperBlock expected");
+ }
+ }
+ }
+
+ #[test]
+ fn test_white_space_omitter() {
+ let source = "hello~ {{~world~}} \n !{{~#if true}}else{{/if~}}";
+ let t = Template::compile(source).ok().unwrap();
+
+ assert_eq!(t.elements.len(), 4);
+
+ assert_eq!(t.elements[0], RawString("hello~".to_string()));
+ assert_eq!(
+ t.elements[1],
+ Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
+ &["world"]
+ ))))
+ );
+ assert_eq!(t.elements[2], RawString("!".to_string()));
+
+ let t2 = Template::compile("{{#if true}}1 {{~ else ~}} 2 {{~/if}}")
+ .ok()
+ .unwrap();
+ assert_eq!(t2.elements.len(), 1);
+ match t2.elements[0] {
+ HelperBlock(ref h) => {
+ assert_eq!(
+ h.template.as_ref().unwrap().elements[0],
+ RawString("1".to_string())
+ );
+ assert_eq!(
+ h.inverse.as_ref().unwrap().elements[0],
+ RawString("2".to_string())
+ );
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ fn test_unclosed_expression() {
+ let sources = ["{{invalid", "{{{invalid", "{{invalid}", "{{!hello"];
+ for s in sources.iter() {
+ let result = Template::compile(s.to_owned());
+ if let Err(e) = result {
+ match e.reason {
+ TemplateErrorReason::InvalidSyntax => {}
+ _ => {
+ panic!("Unexpected error type {}", e);
+ }
+ }
+ } else {
+ panic!("Undetected error");
+ }
+ }
+ }
+
+ #[test]
+ fn test_raw_helper() {
+ let source = "hello{{{{raw}}}}good{{night}}{{{{/raw}}}}world";
+ match Template::compile(source) {
+ Ok(t) => {
+ assert_eq!(t.elements.len(), 3);
+ assert_eq!(t.elements[0], RawString("hello".to_owned()));
+ assert_eq!(t.elements[2], RawString("world".to_owned()));
+ match t.elements[1] {
+ HelperBlock(ref h) => {
+ assert_eq!(h.name.as_name().unwrap(), "raw".to_owned());
+ if let Some(ref ht) = h.template {
+ assert_eq!(ht.elements.len(), 1);
+ assert_eq!(
+ *ht.elements.get(0).unwrap(),
+ RawString("good{{night}}".to_owned())
+ );
+ } else {
+ panic!("helper template not found");
+ }
+ }
+ _ => {
+ panic!("Unexpected element type");
+ }
+ }
+ }
+ Err(e) => {
+ panic!("{}", e);
+ }
+ }
+ }
+
+ #[test]
+ fn test_literal_parameter_parser() {
+ match Template::compile("{{hello 1 name=\"value\" valid=false ref=someref}}") {
+ Ok(t) => {
+ if let Expression(ref ht) = t.elements[0] {
+ assert_eq!(ht.params[0], Parameter::Literal(json!(1)));
+ assert_eq!(
+ ht.hash["name"],
+ Parameter::Literal(Json::String("value".to_owned()))
+ );
+ assert_eq!(ht.hash["valid"], Parameter::Literal(Json::Bool(false)));
+ assert_eq!(
+ ht.hash["ref"],
+ Parameter::Path(Path::with_named_paths(&["someref"]))
+ );
+ }
+ }
+ Err(e) => panic!("{}", e),
+ }
+ }
+
+ #[test]
+ fn test_template_mapping() {
+ match Template::compile("hello\n {{~world}}\n{{#if nice}}\n\thello\n{{/if}}") {
+ Ok(t) => {
+ assert_eq!(t.mapping.len(), t.elements.len());
+ assert_eq!(t.mapping[0], TemplateMapping(1, 1));
+ assert_eq!(t.mapping[1], TemplateMapping(2, 3));
+ assert_eq!(t.mapping[3], TemplateMapping(3, 1));
+ }
+ Err(e) => panic!("{}", e),
+ }
+ }
+
+ #[test]
+ fn test_whitespace_elements() {
+ let c = Template::compile(
+ " {{elem}}\n\t{{#if true}} \
+ {{/if}}\n{{{{raw}}}} {{{{/raw}}}}\n{{{{raw}}}}{{{{/raw}}}}\n",
+ );
+ let r = c.unwrap();
+ // the \n after last raw block is dropped by pest
+ assert_eq!(r.elements.len(), 9);
+ }
+
+ #[test]
+ fn test_block_param() {
+ match Template::compile("{{#each people as |person|}}{{person}}{{/each}}") {
+ Ok(t) => {
+ if let HelperBlock(ref ht) = t.elements[0] {
+ if let Some(BlockParam::Single(Parameter::Name(ref n))) = ht.block_param {
+ assert_eq!(n, "person");
+ } else {
+ panic!("block param expected.")
+ }
+ } else {
+ panic!("Helper block expected");
+ }
+ }
+ Err(e) => panic!("{}", e),
+ }
+
+ match Template::compile("{{#each people as |val key|}}{{person}}{{/each}}") {
+ Ok(t) => {
+ if let HelperBlock(ref ht) = t.elements[0] {
+ if let Some(BlockParam::Pair((
+ Parameter::Name(ref n1),
+ Parameter::Name(ref n2),
+ ))) = ht.block_param
+ {
+ assert_eq!(n1, "val");
+ assert_eq!(n2, "key");
+ } else {
+ panic!("helper block param expected.");
+ }
+ } else {
+ panic!("Helper block expected");
+ }
+ }
+ Err(e) => panic!("{}", e),
+ }
+ }
+
+ #[test]
+ fn test_decorator() {
+ match Template::compile("hello {{* ssh}} world") {
+ Err(e) => panic!("{}", e),
+ Ok(t) => {
+ if let DecoratorExpression(ref de) = t.elements[1] {
+ assert_eq!(de.name.as_name(), Some("ssh"));
+ assert_eq!(de.template, None);
+ }
+ }
+ }
+
+ match Template::compile("hello {{> ssh}} world") {
+ Err(e) => panic!("{}", e),
+ Ok(t) => {
+ if let PartialExpression(ref de) = t.elements[1] {
+ assert_eq!(de.name.as_name(), Some("ssh"));
+ assert_eq!(de.template, None);
+ }
+ }
+ }
+
+ match Template::compile("{{#*inline \"hello\"}}expand to hello{{/inline}}{{> hello}}") {
+ Err(e) => panic!("{}", e),
+ Ok(t) => {
+ if let DecoratorBlock(ref db) = t.elements[0] {
+ assert_eq!(db.name, Parameter::Name("inline".to_owned()));
+ assert_eq!(
+ db.params[0],
+ Parameter::Literal(Json::String("hello".to_owned()))
+ );
+ assert_eq!(
+ db.template.as_ref().unwrap().elements[0],
+ TemplateElement::RawString("expand to hello".to_owned())
+ );
+ }
+ }
+ }
+
+ match Template::compile("{{#> layout \"hello\"}}expand to hello{{/layout}}{{> hello}}") {
+ Err(e) => panic!("{}", e),
+ Ok(t) => {
+ if let PartialBlock(ref db) = t.elements[0] {
+ assert_eq!(db.name, Parameter::Name("layout".to_owned()));
+ assert_eq!(
+ db.params[0],
+ Parameter::Literal(Json::String("hello".to_owned()))
+ );
+ assert_eq!(
+ db.template.as_ref().unwrap().elements[0],
+ TemplateElement::RawString("expand to hello".to_owned())
+ );
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn test_panic_with_tag_name() {
+ let s = "{{#>(X)}}{{/X}}";
+ let result = Template::compile(s);
+ assert!(result.is_err());
+ assert_eq!("decorator \"Subexpression(Subexpression { element: Expression(HelperTemplate { name: Path(Relative(([Named(\\\"X\\\")], \\\"X\\\"))), params: [], hash: {}, block_param: None, template: None, inverse: None, block: false }) })\" was opened, but \"X\" is closing", format!("{}", result.unwrap_err().reason));
+ }
+}
diff --git a/vendor/handlebars/src/util.rs b/vendor/handlebars/src/util.rs
new file mode 100644
index 000000000..7bfca24ce
--- /dev/null
+++ b/vendor/handlebars/src/util.rs
@@ -0,0 +1,25 @@
+pub(crate) fn empty_or_none<T>(input: &[T]) -> Option<&[T]> {
+ if input.is_empty() {
+ None
+ } else {
+ Some(input)
+ }
+}
+
+#[inline]
+pub(crate) fn copy_on_push_vec<T>(input: &[T], el: T) -> Vec<T>
+where
+ T: Clone,
+{
+ let mut new_vec = Vec::with_capacity(input.len() + 1);
+ new_vec.extend_from_slice(input);
+ new_vec.push(el);
+ new_vec
+}
+
+#[inline]
+pub(crate) fn extend(base: &mut Vec<String>, slice: &[String]) {
+ for i in slice {
+ base.push(i.to_owned());
+ }
+}
diff --git a/vendor/handlebars/tests/block_context.rs b/vendor/handlebars/tests/block_context.rs
new file mode 100644
index 000000000..bbaa89a62
--- /dev/null
+++ b/vendor/handlebars/tests/block_context.rs
@@ -0,0 +1,106 @@
+use handlebars::Handlebars;
+use serde_json::json;
+
+#[test]
+fn test_partial_with_blocks() {
+ let hbs = Handlebars::new();
+
+ let data = json!({
+ "a": [
+ {"b": 1},
+ {"b": 2},
+ ],
+ });
+
+ let template = "{{#*inline \"test\"}}{{b}};{{/inline}}{{#each a as |z|}}{{> test z}}{{/each}}";
+ assert_eq!(hbs.render_template(template, &data).unwrap(), "1;2;");
+}
+
+#[test]
+fn test_root_with_blocks() {
+ let hbs = Handlebars::new();
+
+ let data = json!({
+ "a": [
+ {"b": 1},
+ {"b": 2},
+ ],
+ "b": 3,
+ });
+
+ let template =
+ "{{#*inline \"test\"}}{{b}}:{{@root.b}};{{/inline}}{{#each a}}{{> test}}{{/each}}";
+ assert_eq!(hbs.render_template(template, &data).unwrap(), "1:3;2:3;");
+}
+
+#[test]
+fn test_singular_and_pair_block_params() {
+ let hbs = Handlebars::new();
+
+ let data = json!([
+ {"value": 11},
+ {"value": 22},
+ ]);
+
+ let template =
+ "{{#each this as |b index|}}{{b.value}}{{#each this as |value key|}}:{{key}},{{/each}}{{/each}}";
+ assert_eq!(
+ hbs.render_template(template, &data).unwrap(),
+ "11:value,22:value,"
+ );
+}
+
+#[test]
+fn test_nested_each() {
+ let hbs = Handlebars::new();
+
+ let data = json!({
+ "classes": [
+ {
+ "methods": [
+ {"id": 1},
+ {"id": 2}
+ ]
+ },
+ {
+ "methods": [
+ {"id": 3},
+ {"id": 4}
+ ]
+ },
+ ],
+ });
+
+ let template = "{{#each classes as |class|}}{{#each class.methods as |method|}}{{method.id}};{{/each}}{{/each}}";
+ assert_eq!(hbs.render_template(template, &data).unwrap(), "1;2;3;4;");
+}
+
+#[test]
+fn test_referencing_block_param_from_upper_scope() {
+ let hbs = Handlebars::new();
+
+ let data = json!({
+ "classes": [
+ {
+ "methods": [
+ {"id": 1},
+ {"id": 2}
+ ],
+ "private": false
+ },
+ {
+ "methods": [
+ {"id": 3},
+ {"id": 4}
+ ],
+ "private": true
+ },
+ ],
+ });
+
+ let template = "{{#each classes as |class|}}{{#each class.methods as |method|}}{{class.private}}|{{method.id}};{{/each}}{{/each}}";
+ assert_eq!(
+ hbs.render_template(template, &data).unwrap(),
+ "false|1;false|2;true|3;true|4;"
+ );
+}
diff --git a/vendor/handlebars/tests/data_helper.rs b/vendor/handlebars/tests/data_helper.rs
new file mode 100644
index 000000000..7734b0d7a
--- /dev/null
+++ b/vendor/handlebars/tests/data_helper.rs
@@ -0,0 +1,29 @@
+use handlebars::*;
+use serde_json::json;
+
+struct HelperWithBorrowedData<'a>(&'a String);
+
+impl<'a> HelperDef for HelperWithBorrowedData<'a> {
+ fn call<'_reg: '_rc, '_rc>(
+ &self,
+ _: &Helper<'_reg, '_rc>,
+ _: &'_reg Handlebars,
+ _: &Context,
+ _: &mut RenderContext,
+ out: &mut dyn Output,
+ ) -> Result<(), RenderError> {
+ out.write(self.0).map_err(RenderError::from)
+ }
+}
+
+#[test]
+fn test_helper_with_ref_data() {
+ let s = "hello helper".to_owned();
+ let the_helper = HelperWithBorrowedData(&s);
+
+ let mut r = Handlebars::new();
+ r.register_helper("hello", Box::new(the_helper));
+
+ let s = r.render_template("Output: {{hello}}", &json!({})).unwrap();
+ assert_eq!(s, "Output: hello helper".to_owned());
+}
diff --git a/vendor/handlebars/tests/escape.rs b/vendor/handlebars/tests/escape.rs
new file mode 100644
index 000000000..563ab76b9
--- /dev/null
+++ b/vendor/handlebars/tests/escape.rs
@@ -0,0 +1,43 @@
+extern crate handlebars;
+
+#[macro_use]
+extern crate serde_json;
+
+use handlebars::{handlebars_helper, Handlebars};
+
+#[test]
+fn test_escape_216() {
+ let hbs = Handlebars::new();
+
+ let data = json!({
+ "FOO": "foo",
+ "BAR": "bar"
+ });
+
+ assert_eq!(
+ hbs.render_template(r"\\\\ {{FOO}} {{BAR}} {{FOO}}{{BAR}} {{FOO}}#{{BAR}} {{FOO}}//{{BAR}} {{FOO}}\\{{FOO}} {{FOO}}\\\\{{FOO}}\\\{{FOO}} \\\{{FOO}} \{{FOO}} \{{FOO}}", &data).unwrap(),
+ r"\\\\ foo bar foobar foo#bar foo//bar foo\foo foo\\\foo\\foo \\foo {{FOO}} {{FOO}}"
+ );
+}
+
+#[test]
+fn test_string_no_escape_422() {
+ let mut hbs = Handlebars::new();
+
+ handlebars_helper!(replace: |input: str, from: str, to: str| {
+ input.replace(from, to)
+ });
+ hbs.register_helper("replace", Box::new(replace));
+
+ assert_eq!(
+ r#"some\ path"#,
+ hbs.render_template(r#"{{replace "some/path" "/" "\\ " }}"#, &())
+ .unwrap()
+ );
+
+ assert_eq!(
+ r#"some\path"#,
+ hbs.render_template(r#"{{replace "some/path" "/" "\\" }}"#, &())
+ .unwrap()
+ );
+}
diff --git a/vendor/handlebars/tests/helper_function_lifetime.rs b/vendor/handlebars/tests/helper_function_lifetime.rs
new file mode 100644
index 000000000..fd21f3004
--- /dev/null
+++ b/vendor/handlebars/tests/helper_function_lifetime.rs
@@ -0,0 +1,36 @@
+use handlebars::*;
+
+fn ifcond<'reg, 'rc>(
+ h: &Helper<'reg, 'rc>,
+ handle: &'reg Handlebars,
+ ctx: &'rc Context,
+ render_ctx: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ let cond = h
+ .param(0)
+ .and_then(|ref v| v.value().as_bool())
+ .ok_or(RenderError::new("Ifcond takes a boolean !"))? as bool;
+ let temp = if cond { h.template() } else { h.inverse() };
+ match temp {
+ Some(t) => t.render(handle, ctx, render_ctx, out),
+ None => Ok(()),
+ }
+}
+
+#[test]
+fn test_helper() {
+ let mut handlebars = Handlebars::new();
+
+ // register some custom helpers
+ handlebars.register_helper("ifcond", Box::new(ifcond));
+
+ // make data and render it
+ let data = true;
+ assert_eq!(
+ "yes",
+ handlebars
+ .render_template("{{#ifcond this}}yes{{/ifcond}}", &data)
+ .unwrap()
+ );
+}
diff --git a/vendor/handlebars/tests/helper_macro.rs b/vendor/handlebars/tests/helper_macro.rs
new file mode 100644
index 000000000..9a8d2dc07
--- /dev/null
+++ b/vendor/handlebars/tests/helper_macro.rs
@@ -0,0 +1,76 @@
+#[macro_use]
+extern crate handlebars;
+#[macro_use]
+extern crate serde_json;
+
+use handlebars::Handlebars;
+
+handlebars_helper!(lower: |s: str| s.to_lowercase());
+handlebars_helper!(upper: |s: str| s.to_uppercase());
+handlebars_helper!(hex: |v: i64| format!("0x{:x}", v));
+handlebars_helper!(money: |v: i64, {cur: str="$"}| format!("{}{}.00", cur, v));
+handlebars_helper!(all_hash: |{cur: str="$"}| cur);
+handlebars_helper!(nargs: |*args| args.len());
+handlebars_helper!(has_a: |{a:i64 = 99}, **kwargs|
+ format!("{}, {}", a, kwargs.get("a").is_some()));
+handlebars_helper!(tag: |t: str| format!("<{}>", t));
+
+#[test]
+fn test_macro_helper() {
+ let mut hbs = Handlebars::new();
+
+ hbs.register_helper("lower", Box::new(lower));
+ hbs.register_helper("upper", Box::new(upper));
+ hbs.register_helper("hex", Box::new(hex));
+ hbs.register_helper("money", Box::new(money));
+ hbs.register_helper("nargs", Box::new(nargs));
+ hbs.register_helper("has_a", Box::new(has_a));
+ hbs.register_helper("tag", Box::new(tag));
+
+ let data = json!("Teixeira");
+
+ assert_eq!(
+ hbs.render_template("{{lower this}}", &data).unwrap(),
+ "teixeira"
+ );
+ assert_eq!(
+ hbs.render_template("{{upper this}}", &data).unwrap(),
+ "TEIXEIRA"
+ );
+ assert_eq!(hbs.render_template("{{hex 16}}", &()).unwrap(), "0x10");
+
+ assert_eq!(
+ hbs.render_template("{{money 5000}}", &()).unwrap(),
+ "$5000.00"
+ );
+ assert_eq!(
+ hbs.render_template("{{money 5000 cur=\"£\"}}", &())
+ .unwrap(),
+ "£5000.00"
+ );
+ assert_eq!(
+ hbs.render_template("{{nargs 1 1 1 1 1}}", &()).unwrap(),
+ "5"
+ );
+ assert_eq!(hbs.render_template("{{nargs}}", &()).unwrap(), "0");
+
+ assert_eq!(
+ hbs.render_template("{{has_a a=1 b=2}}", &()).unwrap(),
+ "1, true"
+ );
+
+ assert_eq!(
+ hbs.render_template("{{has_a x=1 b=2}}", &()).unwrap(),
+ "99, false"
+ );
+
+ assert_eq!(
+ hbs.render_template("{{tag \"html\"}}", &()).unwrap(),
+ "&lt;html&gt;"
+ );
+
+ assert_eq!(
+ hbs.render_template("{{{tag \"html\"}}}", &()).unwrap(),
+ "<html>"
+ );
+}
diff --git a/vendor/handlebars/tests/helper_with_space.rs b/vendor/handlebars/tests/helper_with_space.rs
new file mode 100644
index 000000000..5a55ab122
--- /dev/null
+++ b/vendor/handlebars/tests/helper_with_space.rs
@@ -0,0 +1,36 @@
+use handlebars::*;
+use serde_json::json;
+
+fn dump<'reg, 'rc>(
+ h: &Helper<'reg, 'rc>,
+ _: &'reg Handlebars,
+ _: &Context,
+ _: &mut RenderContext,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ assert_eq!(2, h.params().len());
+
+ let result = h
+ .params()
+ .iter()
+ .map(|p| p.value().render())
+ .collect::<Vec<String>>()
+ .join(", ");
+ out.write(&result)?;
+
+ Ok(())
+}
+
+#[test]
+fn test_helper_with_space_param() {
+ let mut r = Handlebars::new();
+ r.register_helper("echo", Box::new(dump));
+
+ let s = r
+ .render_template(
+ "Output: {{echo \"Mozilla Firefox\" \"Google Chrome\"}}",
+ &json!({}),
+ )
+ .unwrap();
+ assert_eq!(s, "Output: Mozilla Firefox, Google Chrome".to_owned());
+}
diff --git a/vendor/handlebars/tests/root_var.rs b/vendor/handlebars/tests/root_var.rs
new file mode 100644
index 000000000..fb310f6c1
--- /dev/null
+++ b/vendor/handlebars/tests/root_var.rs
@@ -0,0 +1,21 @@
+extern crate handlebars;
+#[macro_use]
+extern crate serde_json;
+
+use handlebars::Handlebars;
+
+#[test]
+fn test_root_var() {
+ let hbs = Handlebars::new();
+
+ let data = json!({
+ "a": [1, 2, 3, 4],
+ "b": "top"
+ });
+
+ assert_eq!(
+ hbs.render_template("{{#each a}}{{@root/b}}: {{this}};{{/each}}", &data)
+ .unwrap(),
+ "top: 1;top: 2;top: 3;top: 4;"
+ );
+}
diff --git a/vendor/handlebars/tests/subexpression.rs b/vendor/handlebars/tests/subexpression.rs
new file mode 100644
index 000000000..a0ef4090f
--- /dev/null
+++ b/vendor/handlebars/tests/subexpression.rs
@@ -0,0 +1,169 @@
+extern crate handlebars;
+#[macro_use]
+extern crate serde_json;
+
+use std::sync::atomic::{AtomicU16, Ordering};
+use std::sync::Arc;
+
+use handlebars::{Context, Handlebars, Helper, HelperDef, RenderContext, RenderError, ScopedJson};
+
+#[test]
+fn test_subexpression() {
+ let hbs = Handlebars::new();
+
+ let data = json!({"a": 1, "b": 0, "c": 2});
+
+ assert_eq!(
+ hbs.render_template("{{#if (gt a b)}}Success{{else}}Failed{{/if}}", &data)
+ .unwrap(),
+ "Success"
+ );
+
+ assert_eq!(
+ hbs.render_template("{{#if (gt a c)}}Success{{else}}Failed{{/if}}", &data)
+ .unwrap(),
+ "Failed"
+ );
+
+ assert_eq!(
+ hbs.render_template("{{#if (not (gt a c))}}Success{{else}}Failed{{/if}}", &data)
+ .unwrap(),
+ "Success"
+ );
+
+ assert_eq!(
+ hbs.render_template("{{#if (not (gt a b))}}Success{{else}}Failed{{/if}}", &data)
+ .unwrap(),
+ "Failed"
+ );
+
+ // no argument provided for not
+ assert!(hbs
+ .render_template("{{#if (not)}}Success{{else}}Failed{{/if}}", &data)
+ .is_err());
+
+ // json literal
+ assert_eq!(
+ hbs.render_template("{{#if (not true)}}Success{{else}}Failed{{/if}}", &data)
+ .unwrap(),
+ "Failed"
+ );
+ assert_eq!(
+ hbs.render_template("{{#if (not false)}}Success{{else}}Failed{{/if}}", &data)
+ .unwrap(),
+ "Success"
+ );
+}
+
+#[test]
+fn test_strict_mode() {
+ let mut hbs = Handlebars::new();
+ hbs.set_strict_mode(true);
+
+ let data = json!({"a": 1});
+
+ assert!(hbs
+ .render_template("{{#if (eq a 1)}}Success{{else}}Failed{{/if}}", &data)
+ .is_ok());
+ assert!(hbs
+ .render_template("{{#if (eq z 1)}}Success{{else}}Failed{{/if}}", &data)
+ .is_err())
+}
+
+#[test]
+fn invalid_json_path() {
+ // The data here is not important
+ let data = &Vec::<()>::new();
+
+ let hbs = Handlebars::new();
+
+ let error = hbs.render_template("{{x[]@this}}", &data).unwrap_err();
+
+ let expected = "Error rendering \"Unnamed template\" line 1, col 1: Helper not defined: \"x\"";
+
+ assert_eq!(format!("{}", error), expected);
+}
+
+struct MyHelper;
+
+impl HelperDef for MyHelper {
+ fn call_inner<'reg: 'rc, 'rc>(
+ &self,
+ _: &Helper<'reg, 'rc>,
+ _: &'reg Handlebars,
+ _: &'rc Context,
+ _: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ Ok(ScopedJson::Derived(json!({
+ "a": 1,
+ "b": 2,
+ })))
+ }
+}
+
+#[test]
+fn test_lookup_with_subexpression() {
+ let mut registry = Handlebars::new();
+ registry.register_helper("myhelper", Box::new(MyHelper {}));
+ registry
+ .register_template_string("t", "{{ lookup (myhelper) \"a\" }}")
+ .unwrap();
+
+ let result = registry.render("t", &json!({})).unwrap();
+
+ assert_eq!("1", result);
+}
+
+struct CallCounterHelper {
+ pub(crate) c: Arc<AtomicU16>,
+}
+
+impl HelperDef for CallCounterHelper {
+ fn call_inner<'reg: 'rc, 'rc>(
+ &self,
+ h: &Helper<'reg, 'rc>,
+ _: &'reg Handlebars,
+ _: &'rc Context,
+ _: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ // inc counter
+ self.c.fetch_add(1, Ordering::SeqCst);
+
+ if let Some(_) = h.param(0) {
+ Ok(json!({
+ "a": 1,
+ })
+ .into())
+ } else {
+ Ok(json!(null).into())
+ }
+ }
+}
+
+#[test]
+fn test_helper_call_count() {
+ let mut registry = Handlebars::new();
+
+ let counter = Arc::new(AtomicU16::new(0));
+ let helper = Box::new(CallCounterHelper { c: counter.clone() });
+
+ registry.register_helper("myhelper", helper);
+
+ registry
+ .render_template(
+ "{{#if (myhelper a)}}something{{else}}nothing{{/if}}",
+ &json!(null),
+ ) // If returns true
+ .unwrap();
+
+ assert_eq!(1, counter.load(Ordering::SeqCst));
+
+ registry
+ .render_template(
+ "{{#if (myhelper)}}something{{else}}nothing{{/if}}",
+ &json!(null),
+ ) // If returns false
+ .unwrap();
+
+ assert_eq!(2, counter.load(Ordering::SeqCst));
+}
diff --git a/vendor/handlebars/tests/template_names.rs b/vendor/handlebars/tests/template_names.rs
new file mode 100644
index 000000000..eea9e033e
--- /dev/null
+++ b/vendor/handlebars/tests/template_names.rs
@@ -0,0 +1,35 @@
+extern crate handlebars;
+#[macro_use]
+extern crate serde_json;
+
+use handlebars::Handlebars;
+
+#[test]
+fn test_walk_dir_template_name() {
+ let mut hbs = Handlebars::new();
+
+ let data = json!({
+ "a": [1, 2, 3, 4],
+ "b": "top"
+ });
+
+ hbs.register_template_string("foo/bar", "{{@root/b}}")
+ .unwrap();
+ assert_eq!(hbs.render_template("{{> foo/bar }}", &data).unwrap(), "top");
+}
+
+#[test]
+fn test_walk_dir_template_name_with_args() {
+ let mut hbs = Handlebars::new();
+
+ let data = json!({
+ "a": [1, 2, 3, 4],
+ "b": "top"
+ });
+
+ hbs.register_template_string("foo/bar", "{{this}}").unwrap();
+ assert_eq!(
+ hbs.render_template("{{> foo/bar b }}", &data).unwrap(),
+ "top"
+ );
+}
diff --git a/vendor/handlebars/wasm/LICENSE b/vendor/handlebars/wasm/LICENSE
new file mode 100644
index 000000000..384d0c996
--- /dev/null
+++ b/vendor/handlebars/wasm/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Ning Sun
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/handlebars/wasm/README.md b/vendor/handlebars/wasm/README.md
new file mode 100644
index 000000000..77670a518
--- /dev/null
+++ b/vendor/handlebars/wasm/README.md
@@ -0,0 +1,6 @@
+# handlebars wasm modules
+
+## cli
+
+A commandline tool to render handlebars with given json input.
+
diff --git a/vendor/handlebars/wasm/wapm.toml b/vendor/handlebars/wasm/wapm.toml
new file mode 100644
index 000000000..eca9c7887
--- /dev/null
+++ b/vendor/handlebars/wasm/wapm.toml
@@ -0,0 +1,18 @@
+[package]
+name = "sunng/handlebars"
+version = "1.0.0"
+description = "wasm packages for handlebars"
+license = "MIT"
+repository = "https://github.com/sunng87/handlebars-rust"
+
+[[module]]
+name = "handlebars-cli"
+source = "handlebars-cli.wasm"
+abi = "wasi"
+
+# [module.interfaces]
+# wasi = "0.0.0-unstable"
+
+[[command]]
+name = "handlebars-cli"
+module = "handlebars-cli"