diff options
Diffstat (limited to 'vendor/handlebars')
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(¶m); + + 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(), ®.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(¶ms, &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!(""<>&", 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!(""<>&", 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(), + "<p></p>".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("<"), + '>' => output.push_str(">"), + '"' => output.push_str("""), + '&' => output.push_str("&"), + '\'' => output.push_str("'"), + '`' => output.push_str("`"), + '=' => output.push_str("="), + _ => 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(), + "<html>" + ); + + 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" |