handlebars-5.1.2/.cargo_vcs_info.json0000644000000001360000000000100131440ustar { "git": { "sha1": "47bc650833c6f4c57e31bec2df1290b7eab9f53e" }, "path_in_vcs": "" }handlebars-5.1.2/.github/FUNDING.yml000064400000000000000000000006701046102023000151140ustar 00000000000000github: ["sunng87"] patreon: open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: Sunng issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username handlebars-5.1.2/.github/dependabot.yml000064400000000000000000000002211046102023000161170ustar 00000000000000version: 2 updates: - package-ecosystem: cargo directory: "/" schedule: interval: daily time: "13:00" open-pull-requests-limit: 10 handlebars-5.1.2/.github/workflows/main.yml000064400000000000000000000041741046102023000170060ustar 00000000000000name: CI on: schedule: [{cron: "0 13 * * *"}] push: branches: - master pull_request: jobs: test: name: Test runs-on: ${{ matrix.os }} strategy: matrix: build: [stable, beta, nightly, macos, windows] include: - build: stable os: ubuntu-latest rust: stable - build: beta os: ubuntu-latest rust: beta - build: nightly os: ubuntu-latest rust: nightly - build: macos os: macos-latest rust: stable - build: windows os: windows-latest rust: stable steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} components: clippy override: true - name: Lint run: cargo clippy --all-features -- -D warnings - name: Build and run tests run: cargo test --all-features - name: Install Tarpaulin if: matrix.build == 'stable' uses: actions-rs/install@v0.1 with: crate: cargo-tarpaulin version: 0.22.0 use-tool-cache: true - name: Coverage if: matrix.build == 'stable' run: cargo tarpaulin --all-features -o Lcov --output-dir ./coverage - name: Coveralls if: matrix.build == 'stable' uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} rustfmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: toolchain: stable components: rustfmt override: true - run: cargo fmt -- --check msrv: name: MSRV runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: toolchain: "1.72" override: true - run: cargo build --all-features security_audit: name: rust_audit runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/audit-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} handlebars-5.1.2/.github/workflows/update-playground.yml000064400000000000000000000015461046102023000215260ustar 00000000000000name: Build Playground on: push: branches: - master paths: - '**.rs' - 'playground/**' workflow_dispatch: permissions: contents: write jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: toolchain: stable - name: Install wasm-pack run: cargo install wasm-pack - name: Build wasm run: | cd playground wasm-pack build - uses: actions/setup-node@v3 with: node-version: 16 cache: 'npm' cache-dependency-path: playground/www/package-lock.json - name: Build web run: | cd playground/www npm install npm run build - name: Publish dist directory uses: JamesIves/github-pages-deploy-action@v4 with: folder: playground/www/dist handlebars-5.1.2/.gitignore000064400000000000000000000002221046102023000137200ustar 00000000000000# Compiled files *.o *.so *.rlib *.dll *.bk # Executables *.exe # Generated by Cargo /target/ Cargo.lock # Generated by oprofile oprofile_data handlebars-5.1.2/.travis.yml000064400000000000000000000016261046102023000140520ustar 00000000000000language: rust env: - TASK=test RUSTFLAGS="-D warnings" CXX=clang++ matrix: include: - rust: nightly - rust: stable name: clippy-lint before_script: - rustup component add clippy script: - cargo clippy --all-features -- -D warnings - rust: stable name: fmt-check before_script: - rustup component add rustfmt script: - cargo fmt -- --check - rust: beta - rust: stable - rust: 1.45.0 script: - cargo build - rust: nightly name: minimal-versions script: - cargo -Z minimal-versions build --verbose - rust: nightly os: windows script: - cargo $TASK --verbose - cargo $TASK --no-default-features --features no_logging --verbose - cargo $TASK --no-default-features --features dir_source --verbose - cargo $TASK --no-default-features --features script_helper --verbose handlebars-5.1.2/CHANGELOG.md000064400000000000000000000575551046102023000135660ustar 00000000000000# Change Log ## [5.1.2](https://github.com/sunng87/handlebars-rust/compare/5.1.1...5.1.2) - 2024-03-24 * [Changed] Improved error message and syntax rule naming [#638] * [Changed] Updated `heck` to 0.5 [#635] ## ~~[5.1.1](https://github.com/sunng87/handlebars-rust/compare/5.1.0...5.1.1) - 2024-01-18~~ Yanked * [Changed] Turned off pub access of `chain` in `HelperTemplate` ## [5.1.0](https://github.com/sunng87/handlebars-rust/compare/5.0.0...5.1.0) - 2024-01-17 * [Added] Chained `else if` block support [#629] ## [5.0.0](https://github.com/sunng87/handlebars-rust/compare/4.3.4...5.0.0) - 2023-12-31 * [Added] public mutable access to local variables in `BlockContext` [#533] * [Changed] Simplified lifetime specifiers for `Helper`, `ScopedJson` and some other related types and functions. [#532] * [Changed] Updated `TemplateError` to reduce its size. Direct field access is removed in favor of access methods * [Changed] Introducing `RenderErrorReason` for typed render error * [Changed] Changed `register_template_directory` api for more customizations #[610] * [Changed] Updated rust-embed to 8.0 ## [4.3.4](https://github.com/sunng87/handlebars-rust/compare/4.3.3...4.3.4) - 2022-09-11 * [Added] New `write_fmt` function for `Output` [#522] * [Added] `reason()` method for `TemplateError` to access underlying reason, this replaces original direct `.reason` access. * [Changed] Direct access to `TemplateError`'s `reason` field is depreacted will be removed in future. ## [4.3.3](https://github.com/sunng87/handlebars-rust/compare/4.3.2...4.3.3) - 2022-07-20 * [Fixed] Disable partial expression indentation with `{{~> partial}}` to bring behavior closer in line with original javascript version. [#518] * [Fixed] Support for using partial context together with partial parameters [#520] ## [4.3.2](https://github.com/sunng87/handlebars-rust/compare/4.3.1...4.3.2) - 2022-07-14 * [Added] Render functions that reuse `Context` for custom `std::io::Write`: `render_with_context_to_write` and `render_template_with_context_to_write` ## [4.3.1](https://github.com/sunng87/handlebars-rust/compare/4.3.0...4.3.1) - 2022-06-09 * [Added] Added support for `{{~{variable}~}}` syntax [#509] ## [4.3.0](https://github.com/sunng87/handlebars-rust/compare/4.2.2...4.3.0) - 2022-05-18 * [Changed] update MSRV to 1.57 as rhai requires * [Fixed] Reimplemented indent support for partial expression `{{> partial}}`, which is introduced in 4.2.0. The new implementation is aligned with original javascript version, that every text line generated from partial are indented as `{{> partial}}` does. `prevent_indent` will turn-off this feature. [#505] * [Changed] changed error support library from quick_error to thiserror ## [4.2.2](https://github.com/sunng87/handlebars-rust/compare/4.2.1...4.2.2) - 2022-03-09 * [Fixed] Block param scope leaked into partials [#496] * [Changed] Use Rust 2021 edition and update MSRV to 1.56 ## [4.2.1](https://github.com/sunng87/handlebars-rust/compare/4.2.0...4.2.1) - 2022-01-17 * [Fixed] Nested partial `@partial-block` referencing issue [#488] * [Fixed] Docs generation on docs.rs for `rust-embed` feature ## [4.2.0](https://github.com/sunng87/handlebars-rust/compare/4.1.6...4.2.0) - 2022-01-05 * [Added] RustEmbed support for loading templates from [#484] * [Fixed] Parser support for variables begins with digit [#479] * [Changed] Keep indent whitespaces for partial expression `{{> partial}}` as default in handlebarsjs. A new option `prevent_indent` is provided on `Handlebars` to turn off this behaviour. [#486] * [Changed] Update MSRV to 1.51 due to dependency changes ## [4.1.6](https://github.com/sunng87/handlebars-rust/compare/4.1.5...4.1.6) - 2021-12-03 * [Added] Create `Context` from owned `serde_json::Value` [#477] ## [4.1.5](https://github.com/sunng87/handlebars-rust/compare/4.1.4...4.1.5) - 2021-11-17 * [Fixed] Single-quote string literal is supported, again [#475] ## [4.1.4](https://github.com/sunng87/handlebars-rust/compare/4.1.3...4.1.4) - 2021-11-06 * [Fixed] Corrected empty line stripping strategy [#473] ## [4.1.3](https://github.com/sunng87/handlebars-rust/compare/4.1.2...4.1.3) - 2021-09-10 * [Added] `@last` variable for `each` block with object [#466] * [Fixed] Missing whitespaces behind expression [#468] ## [4.1.2](https://github.com/sunng87/handlebars-rust/compare/4.1.1...4.1.2) - 2021-08-11 * [Added] Support for generic types in `handlebars_helper!`. * [Added] Getter and setter for rhai `Engine` from registry. * [Fixed] Improve doc for `dev_mode` that it has to be enabled before adding templates. ## [4.1.1](https://github.com/sunng87/handlebars-rust/compare/4.1.0...4.1.1) - 2021-07-31 * [Changed] Update rhai to 1.0 [#455] * [Fixed] Empty line stripping for partial include statement, and other corner cases [#458] ## [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` * [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` handlebars-5.1.2/Cargo.lock0000644000001232510000000000100111230ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 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.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "const-random", "getrandom", "once_cell", "version_check", "zerocopy", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anyhow" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "ascii" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bstr" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", ] [[package]] name = "bumpalo" version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytemuck" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chunked_transfer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "const-random" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ "const-random-macro", ] [[package]] name = "const-random-macro" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ "getrandom", "once_cell", "tiny-keccak", ] [[package]] name = "cpp_demangle" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" dependencies = [ "cfg-if", ] [[package]] name = "cpufeatures" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools 0.10.5", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools 0.10.5", ] [[package]] name = "crossbeam-deque" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "debugid" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "uuid", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "either" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "env_logger" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", "log", "regex", "termcolor", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fastrand" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "findshlibs" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" dependencies = [ "cc", "lazy_static", "libc", "winapi", ] [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "globset" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", "log", "regex-automata", "regex-syntax", ] [[package]] name = "half" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "handlebars" version = "5.1.2" dependencies = [ "criterion", "env_logger", "heck 0.5.0", "log", "pest", "pest_derive", "pprof", "rhai", "rust-embed", "serde", "serde_derive", "serde_json", "tempfile", "thiserror", "time", "tiny_http", "walkdir", ] [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ "windows-sys", ] [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "inferno" version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" dependencies = [ "ahash", "indexmap", "is-terminal", "itoa", "log", "num-format", "once_cell", "quick-xml", "rgb", "str_stack", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "is-terminal" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", "libc", "windows-sys", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 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.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" dependencies = [ "libc", ] [[package]] name = "miniz_oxide" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "multimap" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "nix" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-format" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ "arrayvec", "itoa", ] [[package]] name = "num-traits" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] [[package]] name = "object" version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.48.5", ] [[package]] name = "pest" version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" dependencies = [ "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" dependencies = [ "pest", "pest_generator", ] [[package]] name = "pest_generator" version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", "syn", ] [[package]] name = "pest_meta" version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" dependencies = [ "once_cell", "pest", "sha2", ] [[package]] name = "petgraph" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", "indexmap", ] [[package]] name = "plotters" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "pprof" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef5c97c51bd34c7e742402e216abdeb44d415fbe6ae41d56b114723e953711cb" dependencies = [ "backtrace", "cfg-if", "findshlibs", "inferno", "libc", "log", "nix", "once_cell", "parking_lot", "prost", "prost-build", "prost-derive", "sha2", "smallvec", "symbolic-demangle", "tempfile", "thiserror", ] [[package]] name = "prettyplease" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro2" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "prost" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", "prost-derive", ] [[package]] name = "prost-build" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" dependencies = [ "bytes", "heck 0.4.1", "itertools 0.11.0", "log", "multimap", "once_cell", "petgraph", "prettyplease", "prost", "prost-types", "regex", "syn", "tempfile", "which", ] [[package]] name = "prost-derive" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", "itertools 0.11.0", "proc-macro2", "quote", "syn", ] [[package]] name = "prost-types" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ "prost", ] [[package]] name = "quick-xml" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" dependencies = [ "memchr", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rgb" version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" dependencies = [ "bytemuck", ] [[package]] name = "rhai" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6273372244d04a8a4b0bec080ea1e710403e88c5d9d83f9808b2bfa64f0982a" dependencies = [ "ahash", "bitflags 2.5.0", "instant", "num-traits", "once_cell", "rhai_codegen", "serde", "smallvec", "smartstring", "thin-vec", ] [[package]] name = "rhai_codegen" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9db7f8dc4c9d48183a17ce550574c42995252b82d267eaca3fcd1b979159856c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "rust-embed" version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" dependencies = [ "rust-embed-impl", "rust-embed-utils", "walkdir", ] [[package]] name = "rust-embed-impl" version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", "syn", "walkdir", ] [[package]] name = "rust-embed-utils" version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" dependencies = [ "globset", "sha2", "walkdir", ] [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "ryu" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[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 = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" dependencies = [ "serde", ] [[package]] name = "smartstring" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" dependencies = [ "autocfg", "serde", "static_assertions", "version_check", ] [[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 = "12.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cccfffbc6bb3bb2d3a26cd2077f4d055f6808d266f9d4d158797a4c60510dfe" dependencies = [ "debugid", "memmap2", "stable_deref_trait", "uuid", ] [[package]] name = "symbolic-demangle" version = "12.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a99812da4020a67e76c4eb41f08c87364c14170495ff780f30dd519c221a68" dependencies = [ "cpp_demangle", "rustc-demangle", "symbolic-common", ] [[package]] name = "syn" version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", "rustix", "windows-sys", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "thin-vec" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" dependencies = [ "serde", ] [[package]] name = "thiserror" version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tiny-keccak" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ "crunchy", ] [[package]] name = "tiny_http" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" dependencies = [ "ascii", "chunked_transfer", "httpdate", "log", ] [[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 = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "uuid" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "which" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", "home", "once_cell", "rustix", ] [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 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" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.4", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ "windows_aarch64_gnullvm 0.52.4", "windows_aarch64_msvc 0.52.4", "windows_i686_gnu 0.52.4", "windows_i686_msvc 0.52.4", "windows_x86_64_gnu 0.52.4", "windows_x86_64_gnullvm 0.52.4", "windows_x86_64_msvc 0.52.4", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "zerocopy" version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", "syn", ] handlebars-5.1.2/Cargo.toml0000644000000047140000000000100111500ustar # 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 are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.72" name = "handlebars" version = "5.1.2" authors = ["Ning Sun "] 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", "rust-embed", ] rustdoc-args = [ "--cfg", "docsrs", ] [lib] name = "handlebars" path = "src/lib.rs" [[bin]] name = "handlebars-cli" path = "src/cli.rs" [[example]] name = "script" required-features = ["script_helper"] [[bench]] name = "bench" harness = false [dependencies.heck] version = "0.5" optional = true [dependencies.log] version = "0.4.0" [dependencies.pest] version = "2.1.0" [dependencies.pest_derive] version = "2.1.0" [dependencies.rhai] version = "1.16.1" features = [ "sync", "serde", ] optional = true [dependencies.rust-embed] version = "8.0.0" features = ["include-exclude"] optional = true [dependencies.serde] version = "1.0.0" [dependencies.serde_json] version = "1.0.39" [dependencies.thiserror] version = "1" [dependencies.walkdir] version = "2.2.3" optional = true [dev-dependencies.criterion] version = "0.5" [dev-dependencies.env_logger] version = "0.10" [dev-dependencies.serde_derive] version = "1.0.75" [dev-dependencies.tempfile] version = "3.0.0" [dev-dependencies.time] version = "0.3.7" features = [ "serde", "formatting", "parsing", ] [dev-dependencies.tiny_http] version = "0.12" [features] default = [] dir_source = ["walkdir"] no_logging = [] script_helper = ["rhai"] string_helpers = ["heck"] [target."cfg(unix)".dev-dependencies.pprof] version = "0.13" features = [ "flamegraph", "prost-codec", ] [badges.maintenance] status = "actively-developed" handlebars-5.1.2/Cargo.toml.orig000064400000000000000000000032231046102023000146230ustar 00000000000000[package] name = "handlebars" version = "5.1.2" authors = ["Ning Sun "] description = "Handlebars templating implemented in Rust." license = "MIT" keywords = ["handlebars", "templating", "web"] categories = ["template-engine", "web-programming"] homepage = "https://github.com/sunng87/handlebars-rust" repository = "https://github.com/sunng87/handlebars-rust" documentation = "https://docs.rs/crate/handlebars/" readme = "README.md" edition = "2021" rust-version = "1.72" [lib] name = "handlebars" path = "src/lib.rs" [[bin]] name = "handlebars-cli" path = "src/cli.rs" [dependencies] log = { version = "0.4.0" } thiserror = "1" pest = "2.1.0" pest_derive = "2.1.0" serde = "1.0.0" serde_json = "1.0.39" walkdir = { version = "2.2.3", optional = true } rhai = { version = "1.16.1", optional = true, features = ["sync", "serde"] } rust-embed = { version = "8.0.0", optional = true, features = ["include-exclude"] } heck = { version = "0.5", optional = true } [dev-dependencies] env_logger = "0.10" serde_derive = "1.0.75" tempfile = "3.0.0" criterion = "0.5" tiny_http = "0.12" time = { version = "0.3.7", features = ["serde", "formatting", "parsing"]} [target.'cfg(unix)'.dev-dependencies] pprof = { version = "0.13", features = ["flamegraph", "prost-codec"] } [features] dir_source = ["walkdir"] script_helper = ["rhai"] no_logging = [] default = [] string_helpers = ["heck"] [badges] maintenance = { status = "actively-developed" } [[bench]] name = "bench" harness = false [package.metadata.docs.rs] features = ["dir_source", "script_helper", "rust-embed"] rustdoc-args = ["--cfg", "docsrs"] [[example]] name = "script" required-features = ["script_helper"] handlebars-5.1.2/LICENSE000064400000000000000000000020641046102023000127430ustar 00000000000000The 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. handlebars-5.1.2/README.md000064400000000000000000000172211046102023000132160ustar 00000000000000handlebars-rust =============== [Handlebars templating language](https://handlebarsjs.com) implemented in Rust and for Rust. [![CI](https://github.com/sunng87/handlebars-rust/actions/workflows/main.yml/badge.svg)](https://github.com/sunng87/handlebars-rust/actions/workflows/main.yml) [![Coverage Status](https://coveralls.io/repos/github/sunng87/handlebars-rust/badge.svg?branch=master)](https://coveralls.io/github/sunng87/handlebars-rust?branch=master) [![](https://img.shields.io/crates/v/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/) [![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; use std::error::Error; fn main() -> Result<(), Box> { 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 * [helper_macro](https://github.com/sunng87/handlebars-rust/blob/master/examples/helper_macro.rs) demos usage of `handlebars_helper!` to simplify helper development * [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. ### Web Playground We have github action to compile latest `master` branch into WebAssembly and serve it on [github pages](https://sunng87.github.io/handlebars-rust/). You can test and verify your template with both handlebars-rust and handlebarjs. ## Minimum Rust Version Policy Handlebars will track Rust nightly and stable channel. When dropping support for previous stable versions, I will bump **patch** version and clarify in CHANGELOG. ## Docs [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_extras.rs#L6) (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/3.2/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. #### Fully scriptable With [rhai](https://github.com/rhaiscript/rhai) script support, you can implement your own helper with the scripting language. Together with the template lanaguage itself, template development can be fully scriptable without changing rust code. ## 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/templating/handlebars/src/main.rs) * Tide: [tide-handlebars](https://github.com/No9/tide-handlebars) * Axum: [axum-template](https://github.com/Altair-Bueno/axum-template) ### 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. handlebars-5.1.2/benches/bench.rs000064400000000000000000000140021046102023000147650ustar 00000000000000#[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>, } #[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, dummy: Vec, } #[derive(Serialize)] struct NestedRowWrapper { parent: Vec>, } static SOURCE: &'static str = " {{year}}

CSL {{year}}

    {{#each teams}}
  • {{name}}: {{score}}
  • {{/each}}
"; fn make_data() -> BTreeMap { 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 = (1..1000) .map(|i| DataWrapper { v: format!("n={}", i), }) .collect(); let dummy: Vec = (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 = (1..1000) .map(|i| DataWrapper { v: format!("n={}", i), }) .collect(); let dummy: Vec = (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> = (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); handlebars-5.1.2/build-wasm.sh000075500000000000000000000001421046102023000143340ustar 00000000000000#!/bin/sh cargo build --target wasm32-wasi cp target/wasm32-wasi/debug/handlebars-cli.wasm wasm/ handlebars-5.1.2/examples/decorator/template.hbs000064400000000000000000000006521046102023000200500ustar 00000000000000{{*format_suffix "分"}} CSL {{year}}

CSL {{year}}

    {{#each teams as |t|}}
  • {{~log @index~}} {{t.name}}: {{format t.pts ~}}
  • {{/each}}
{{*set version="v3.0"}}

Rendered by Handlebars {{version}} from {{engine}} data.

handlebars-5.1.2/examples/decorator.rs000064400000000000000000000120271046102023000161040ustar 00000000000000extern 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, RenderErrorReason, }; // 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(RenderErrorReason::ParamNotFoundForIndex("format", 0))?; write!(out, "{} pts", param.value().render())?; 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(RenderErrorReason::ParamNotFoundForIndex("format", 0))?; write!(out, "{} {}", param.value().render(), suffix)?; 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(RenderErrorReason::Other("Cannot extend non-object data".to_owned()).into()) } } // 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( RenderErrorReason::ParamTypeMismatchForName("rank", "0".to_string(), "u64".to_string()), )? as usize; let total = h .param(1) .as_ref() .and_then(|v| v.value().as_array()) .map(|arr| arr.len()) .ok_or(RenderErrorReason::ParamTypeMismatchForName( "rank", "1".to_string(), "array".to_string(), ))?; 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 { 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> { 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(()) } handlebars-5.1.2/examples/dev_mode/template.hbs000064400000000000000000000002161046102023000176440ustar 00000000000000 My Laptop

My current laptop is {{brand}}: {{model}}

handlebars-5.1.2/examples/dev_mode.rs000064400000000000000000000017451046102023000157110ustar 00000000000000use std::sync::Arc; use handlebars::Handlebars; use serde_json::json; use tiny_http::{Response, Server}; fn handlebars() -> Handlebars<'static> { 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(); reg } fn main() { let hbs = Arc::new(handlebars()); let server = Server::http("127.0.0.1:3030").expect("Failed to start demo server."); println!("Edit ./examples/dev_mode/template.hbs and request http://localhost:3030 to see the change on the fly."); for req in server.incoming_requests() { let result = hbs .render("tpl", &json!({"model": "t14s", "brand": "Thinkpad"})) .unwrap_or_else(|e| e.to_string()); req.respond(Response::from_string(result)).unwrap(); } } handlebars-5.1.2/examples/error/error.hbs000064400000000000000000000007031046102023000165320ustar 00000000000000{{! this is an invalid template }} 中超联赛 {{year}}

CSL {{year}}

    {{#each teams as |t| ~}}
  • {{~log @index~}} {{t.name}}: {{format t.pts ~}}
  • {{! mismatched helper close tag }} {{/arch~}}

Rendered by Handlebars from {{engine}} data.

handlebars-5.1.2/examples/error/template.hbs000064400000000000000000000007151046102023000172170ustar 00000000000000 中超联赛 {{year}}

CSL {{year}}

    {{#each teams as |t|}} {{! ranking_label will produce a render error when first parameter is not a number }}
  • {{~log @index~}} {{t.name}}: {{format t.pts ~}}
  • {{/each}}

Rendered by Handlebars from {{engine}} data.

handlebars-5.1.2/examples/error.rs000064400000000000000000000042111046102023000152470ustar 00000000000000extern crate env_logger; extern crate handlebars; #[macro_use] extern crate serde_json; use std::error::Error as StdError; use handlebars::{ Context, Handlebars, Helper, Output, RenderContext, RenderError, RenderErrorReason, }; use thiserror::Error; #[derive(Debug, Error)] pub enum HelperError { #[error("db error")] DbError, #[error("api error")] ApiError, } /// A helper that raise error according to parameters pub fn error_helper( h: &Helper, _: &Handlebars, _: &Context, _: &mut RenderContext, _: &mut dyn Output, ) -> Result<(), RenderError> { let param = h .param(0) .ok_or(RenderErrorReason::ParamNotFoundForIndex("error", 0))?; match param.value().as_str() { Some("db") => Err(RenderErrorReason::NestedError(Box::new(HelperError::DbError)).into()), Some("api") => Err(RenderErrorReason::NestedError(Box::new(HelperError::ApiError)).into()), _ => Ok(()), } } fn main() -> Result<(), Box> { 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()); // process error generated in helper handlebars.register_helper("err", Box::new(error_helper)); let e2 = handlebars .render_template("{{err \"db\"}}", &json!({})) .unwrap_err(); // get nested error to from `RenderError` match e2.source().and_then(|e| e.downcast_ref::()) { Some(HelperError::DbError) => { println!("Detected error from helper: db error",) } _ => {} } Ok(()) } handlebars-5.1.2/examples/helper_macro.rs000064400000000000000000000040061046102023000165600ustar 00000000000000use std::error::Error; use handlebars::{handlebars_helper, Handlebars, JsonRender}; use serde_json::{json, Value}; use time::format_description::parse; use time::OffsetDateTime; // define a helper using helper // a date format helper accept an `OffsetDateTime` as parameter handlebars_helper!(date: |dt: OffsetDateTime| dt.format(&parse("[year]-[month]-[day]").unwrap()).unwrap()); // a helper returns number of provided parameters handlebars_helper!(nargs: |*args| args.len()); // a helper joins all values, using both hash and parameters handlebars_helper!(join: |{sep:str=","}, *args| args.iter().map(|a| a.render()).collect::>().join(sep) ); handlebars_helper!(isdefined: |v: Value| !v.is_null()); // a helper provides format handlebars_helper!(date2: |dt: OffsetDateTime, {fmt:str = "[year]-[month]-[day]"}| dt.format(&parse(fmt).unwrap()).unwrap() ); fn main() -> Result<(), Box> { // create the handlebars registry let mut handlebars = Handlebars::new(); handlebars.register_helper("date", Box::new(date)); handlebars.register_helper("date2", Box::new(date2)); handlebars.register_helper("nargs", Box::new(nargs)); handlebars.register_helper("join", Box::new(join)); handlebars.register_helper("isdefined", Box::new(isdefined)); let data = OffsetDateTime::now_utc(); println!("{}", handlebars.render_template("{{date this}}", &data)?); println!("{}", handlebars.render_template("{{date2 this}}", &data)?); println!( "{}", handlebars.render_template("{{date2 this fmt=\"[day]/[month]/[year]\"}}", &data)? ); println!("{}", handlebars.render_template("{{nargs 1 2 3 4}}", &())?); println!( "{}", handlebars.render_template("{{join 1 2 3 4 sep=\"|\" }}", &())? ); println!( "{}", handlebars.render_template( r#"{{isdefined a}} {{isdefined b}} {{#if (isdefined a)}}a{{/if}} {{#if (isdefined b)}}b{{/if}}"#, &json!({"a": 1}) )? ); Ok(()) } handlebars-5.1.2/examples/partials/base0.hbs000064400000000000000000000001711046102023000170600ustar 00000000000000 {{title}}

Derived from base0.hbs

{{> page}} handlebars-5.1.2/examples/partials/base1.hbs000064400000000000000000000001711046102023000170610ustar 00000000000000 {{title}}

Derived from base1.hbs

{{> page}} handlebars-5.1.2/examples/partials/template2.hbs000064400000000000000000000001601046102023000177610ustar 00000000000000{{#*inline "page"}}

Rendered in partial, parent is {{parent}}

{{/inline}} {{> (lookup this "parent")}} handlebars-5.1.2/examples/partials.rs000064400000000000000000000016341046102023000157430ustar 00000000000000extern crate env_logger; extern crate handlebars; use handlebars::Handlebars; use serde_json::json; use std::error::Error; fn main() -> Result<(), Box> { 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 = json!({ "title": "example 0", "parent": "base0" }); let data1 = json!({ "title": "example 1", "parent": "base1" }); println!("Page 0"); println!("{}", handlebars.render("template", &data0)?); println!("======================================================="); println!("Page 1"); println!("{}", handlebars.render("template", &data1)?); Ok(()) } handlebars-5.1.2/examples/quick.rs000064400000000000000000000007471046102023000152440ustar 00000000000000use std::error::Error; use handlebars::Handlebars; use serde_json::json; fn main() -> Result<(), Box> { 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(()) } handlebars-5.1.2/examples/render/template.hbs000064400000000000000000000005611046102023000173440ustar 00000000000000 中超联赛 {{year}}

CSL {{year}}

    {{#each teams as |t|}}
  • {{~log @index~}} {{t.name}}: {{format t.pts ~}}
  • {{/each}}

Rendered by Handlebars from {{engine}} data.

handlebars-5.1.2/examples/render.rs000064400000000000000000000064711046102023000154070ustar 00000000000000extern 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, RenderErrorReason, }; // 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(RenderErrorReason::ParamNotFoundForIndex("format", 0))?; write!(out, "{} pts", param.value().render())?; 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( RenderErrorReason::ParamTypeMismatchForName("rank", "0".to_string(), "u64".to_string()), )? as usize; let total = h .param(1) .as_ref() .and_then(|v| v.value().as_array()) .map(|arr| arr.len()) .ok_or(RenderErrorReason::ParamTypeMismatchForName( "rank", "1".to_string(), "array".to_string(), ))?; 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 { 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> { 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(()) } handlebars-5.1.2/examples/render_cli/simple.hbs000064400000000000000000000000161046102023000176440ustar 00000000000000Data: {{ . }} handlebars-5.1.2/examples/render_file/template.hbs000064400000000000000000000006171046102023000203450ustar 00000000000000 中超联赛 {{year}}

CSL {{year}}

    {{#each teams as |t| }}
  • {{~log @index~}} {{!-- I'm comment --}} {{t.name}}: {{format t.pts ~}}
  • {{/each}}

Rendered by Handlebars from {{engine}} data.

handlebars-5.1.2/examples/render_file.rs000064400000000000000000000067701046102023000164100ustar 00000000000000#![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, RenderErrorReason, }; // 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(RenderErrorReason::ParamNotFoundForIndex("format", 0))?; write!(out, "{} pts", param.value().render())?; 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( RenderErrorReason::ParamTypeMismatchForName("rank", "0".to_string(), "u64".to_string()), )? as usize; let total = h .param(1) .as_ref() .and_then(|v| v.value().as_array()) .map(|arr| arr.len()) .ok_or(RenderErrorReason::ParamTypeMismatchForName( "rank", "1".to_string(), "array".to_string(), ))?; 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 { 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> { 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(()) } handlebars-5.1.2/examples/script/goals.rhai000064400000000000000000000000441046102023000170260ustar 00000000000000let goals = params[0]; goals.len() handlebars-5.1.2/examples/script/template.hbs000064400000000000000000000003261046102023000173700ustar 00000000000000Bundesliga Match Day {{#each this as |match|}} {{#each match as |team|}} {{team.name}} - {{score team.goals}} {{#each team.goals as |scorer|}} > {{scorer}} {{/each}} {{/each}} --- {{/each}} handlebars-5.1.2/examples/script.rs000064400000000000000000000014771046102023000154350ustar 00000000000000#![allow(unused_imports)] use handlebars::Handlebars; use std::error::Error; #[macro_use] extern crate serde_json; fn main() -> Result<(), Box> { 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(()) } handlebars-5.1.2/profile.sh000075500000000000000000000001101046102023000137230ustar 00000000000000#!/bin/sh RUSTCFLAGS=-g cargo bench --bench bench -- --profile-time 15 handlebars-5.1.2/release.toml000064400000000000000000000005521046102023000142530ustar 00000000000000sign-commit = true sign-tag = 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}}"}, ] handlebars-5.1.2/rustfmt.toml000064400000000000000000000000271046102023000143340ustar 00000000000000format_strings = false handlebars-5.1.2/src/block.rs000064400000000000000000000074261046102023000141740ustar 00000000000000use 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), // an actual value holder Value(Json), } impl BlockParamHolder { pub fn value(v: Json) -> BlockParamHolder { BlockParamHolder::Value(v) } pub fn path(r: Vec) -> 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) -> 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<'rc> { /// the base_path of current block scope base_path: Vec, /// the base_value of current block scope, when the block is using a /// constant or derived value as block base base_value: Option, /// current block context variables block_params: BlockParams<'rc>, /// local variables in current context local_variables: LocalVars, } impl<'rc> BlockContext<'rc> { /// create a new `BlockContext` with default data pub fn new() -> BlockContext<'rc> { 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 mutable access to the local variables pub fn local_variables_mut(&mut self) -> &mut LocalVars { &mut self.local_variables } /// 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 { &self.base_path } /// borrow a mutable reference to the base path pub fn base_path_mut(&mut self) -> &mut Vec { &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<'rc>) { self.block_params = block_params; } } handlebars-5.1.2/src/cli.rs000064400000000000000000000022461046102023000136440ustar 00000000000000use 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); } } } handlebars-5.1.2/src/context.rs000064400000000000000000000334201046102023000145570ustar 00000000000000use 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, RenderErrorReason}; use crate::grammar::Rule; use crate::json::path::*; use crate::json::value::ScopedJson; use crate::util::extend; pub type Object = HashMap; /// 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), // relative path and path root RelativePath(Vec), // relative path against block param value BlockParamValue(Vec, &'a Json), // relative path against derived value, LocalValue(Vec, &'a Json), } fn parse_json_visitor<'a>( relative_path: &[PathSeg], block_contexts: &'a VecDeque>, 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, RenderError> { let result = match d { Some(Json::Array(l)) => p .parse::() .map(|idx_u| l.get(idx_u)) .map_err(|_| RenderErrorReason::InvalidJsonIndex(p.to_owned()))?, Some(Json::Object(m)) => m.get(p), Some(_) => None, None => None, }; Ok(result) } fn get_in_block_params<'a>( block_contexts: &'a VecDeque>, p: &str, ) -> Option<(&'a BlockParamHolder, &'a Vec)> { 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(e: T) -> Result { to_value(e) .map_err(|e| RenderErrorReason::SerdeError(e).into()) .map(|d| Context { data: d }) } /// Navigate the context with relative path and block scopes pub(crate) fn navigate<'rc>( &'rc self, relative_path: &[PathSeg], block_contexts: &VecDeque>, ) -> Result, 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); 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 } } impl From for Context { fn from(data: Json) -> Context { Context { 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, 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, } #[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 = json!({ "this_name": "the_value" }); 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(&self, _: S) -> Result 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() ); } } handlebars-5.1.2/src/decorators/inline.rs000064400000000000000000000034241046102023000165170ustar 00000000000000use crate::context::Context; use crate::decorators::{DecoratorDef, DecoratorResult}; use crate::error::RenderError; use crate::registry::Registry; use crate::render::{Decorator, RenderContext}; use crate::RenderErrorReason; #[derive(Clone, Copy)] pub struct InlineDecorator; fn get_name<'reg: 'rc, 'rc>(d: &Decorator<'rc>) -> Result { d.param(0) .ok_or_else(|| RenderErrorReason::ParamNotFoundForIndex("inline", 0).into()) .and_then(|v| { v.value() .as_str() .map(|v| v.to_owned()) .ok_or_else(|| RenderErrorReason::InvalidParamType("String").into()) }) } impl DecoratorDef for InlineDecorator { fn call<'reg: 'rc, 'rc>( &self, d: &Decorator<'rc>, _: &'reg Registry<'reg>, _: &'rc Context, rc: &mut RenderContext<'reg, 'rc>, ) -> DecoratorResult { let name = get_name(d)?; let template = d .template() .ok_or(RenderErrorReason::BlockContentRequired)?; 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()); } } handlebars-5.1.2/src/decorators/mod.rs000064400000000000000000000220631046102023000160200ustar 00000000000000use 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<'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<'rc>, &'reg Registry<'reg>, &'rc Context, &mut RenderContext<'reg, 'rc>, ) -> DecoratorResult, > DecoratorDef for F { fn call<'reg: 'rc, 'rc>( &'reg self, d: &Decorator<'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 = json!({ "hello": "world" }); 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 = json!({ "hello": "world" }); 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> { write!( out, "{}m", h.param(0) .as_ref() .map(|v| v.value()) .unwrap_or(&to_json(0)) )?; 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> { write!( out, "{}{}", h.param(0) .as_ref() .map(|v| v.value()) .unwrap_or(&to_json(0)), new_unit )?; 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() ); } } handlebars-5.1.2/src/error.rs000064400000000000000000000240141046102023000142230ustar 00000000000000use std::error::Error as StdError; use std::fmt::{self, Write}; use std::io::Error as IOError; use std::string::FromUtf8Error; use serde_json::error::Error as SerdeError; use thiserror::Error; #[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)] pub struct RenderError { pub template_name: Option, pub line_no: Option, pub column_no: Option, reason: Box, unimplemented: bool, // backtrace: Backtrace, } impl fmt::Display for RenderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let desc = self.reason.to_string(); 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, desc ), _ => write!(f, "{}", desc), } } } impl From for RenderError { fn from(e: IOError) -> RenderError { RenderErrorReason::IOError(e).into() } } impl From for RenderError { fn from(e: FromUtf8Error) -> Self { RenderErrorReason::Utf8Error(e).into() } } impl From for RenderError { fn from(e: TemplateError) -> Self { RenderErrorReason::TemplateError(e).into() } } /// Template rendering error #[derive(Debug, Error)] pub enum RenderErrorReason { #[error("Template not found {0}")] TemplateNotFound(String), #[error("Failed to parse template {0}")] TemplateError( #[from] #[source] TemplateError, ), #[error("Failed to access variable in strict mode {0:?}")] MissingVariable(Option), #[error("Partial not found {0}")] PartialNotFound(String), #[error("Helper not found {0}")] HelperNotFound(String), #[error("Helper/Decorator {0} param at index {1} required but not found")] ParamNotFoundForIndex(&'static str, usize), #[error("Helper/Decorator {0} param with name {1} required but not found")] ParamNotFoundForName(&'static str, String), #[error("Helper/Decorator {0} param with name {1} type mismatch for {2}")] ParamTypeMismatchForName(&'static str, String, String), #[error("Helper/Decorator {0} hash with name {1} type mismatch for {2}")] HashTypeMismatchForName(&'static str, String, String), #[error("Decorator not found {0}")] DecoratorNotFound(String), #[error("Can not include current template in partial")] CannotIncludeSelf, #[error("Invalid logging level: {0}")] InvalidLoggingLevel(String), #[error("Invalid param type, {0} expected")] InvalidParamType(&'static str), #[error("Block content required")] BlockContentRequired, #[error("Invalid json path {0}")] InvalidJsonPath(String), #[error("Cannot access array/vector with string index, {0}")] InvalidJsonIndex(String), #[error("Failed to access JSON data: {0}")] SerdeError( #[from] #[source] SerdeError, ), #[error("IO Error: {0}")] IOError( #[from] #[source] IOError, ), #[error("FromUtf8Error: {0}")] Utf8Error( #[from] #[source] FromUtf8Error, ), #[error("Nested error: {0}")] NestedError(#[source] Box), #[cfg(feature = "script_helper")] #[error("Cannot convert data to Rhai dynamic: {0}")] ScriptValueError( #[from] #[source] Box, ), #[cfg(feature = "script_helper")] #[error("Failed to load rhai script: {0}")] ScriptLoadError( #[from] #[source] ScriptError, ), #[error("Unimplemented")] Unimplemented, #[error("{0}")] Other(String), } impl From for RenderError { fn from(e: RenderErrorReason) -> RenderError { RenderError { template_name: None, line_no: None, column_no: None, reason: Box::new(e), unimplemented: false, } } } impl RenderError { #[deprecated(since = "5.0.0", note = "Use RenderErrorReason instead")] pub fn new>(desc: T) -> RenderError { RenderErrorReason::Other(desc.as_ref().to_string()).into() } pub fn strict_error(path: Option<&String>) -> RenderError { RenderErrorReason::MissingVariable(path.map(|p| p.to_owned())).into() } #[deprecated(since = "5.0.0", note = "Use RenderErrorReason::NestedError instead")] pub fn from_error(_error_info: &str, cause: E) -> RenderError where E: StdError + Send + Sync + 'static, { RenderErrorReason::NestedError(Box::new(cause)).into() } #[inline] pub(crate) fn is_unimplemented(&self) -> bool { matches!(*self.reason, RenderErrorReason::Unimplemented) } /// Get `RenderErrorReason` for this error pub fn reason(&self) -> &RenderErrorReason { self.reason.as_ref() } } impl StdError for RenderError { fn source(&self) -> Option<&(dyn StdError + 'static)> { Some(self.reason()) } } /// Template parsing error #[derive(Debug, Error)] pub enum TemplateErrorReason { #[error("helper {0:?} was opened, but {1:?} is closing")] MismatchingClosedHelper(String, String), #[error("decorator {0:?} was opened, but {1:?} is closing")] MismatchingClosedDecorator(String, String), #[error("invalid handlebars syntax: {0}")] InvalidSyntax(String), #[error("invalid parameter {0:?}")] InvalidParam(String), #[error("nested subexpression is not supported")] NestedSubexpression, #[error("Template \"{1}\": {0}")] IoError(IOError, String), #[cfg(feature = "dir_source")] #[error("Walk dir error: {err}")] WalkdirError { #[from] err: WalkdirError, }, } /// Error on parsing template. #[derive(Debug, Error)] pub struct TemplateError { reason: Box, template_name: Option, line_no: Option, column_no: Option, segment: Option, } impl TemplateError { #[allow(deprecated)] pub fn of(e: TemplateErrorReason) -> TemplateError { TemplateError { reason: Box::new(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 } /// Get underlying reason for the error pub fn reason(&self) -> &TemplateErrorReason { &self.reason } /// Get the line number and column number of this error pub fn pos(&self) -> Option<(usize, usize)> { match (self.line_no, self.column_no) { (Some(line_no), Some(column_no)) => Some((line_no, column_no)), _ => None, } } /// Get template name of this error /// Returns `None` when the template has no associated name pub fn name(&self) -> Option<&String> { self.template_name.as_ref() } } 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 for TemplateError { fn from(e: WalkdirError) -> TemplateError { TemplateError::of(TemplateErrorReason::from(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 { let _ = writeln!(&mut buf, "{line_count:4} | {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(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")] #[derive(Debug, Error)] pub enum ScriptError { #[error(transparent)] IoError(#[from] IOError), #[error(transparent)] ParseError(#[from] ParseError), } #[cfg(test)] mod test { use super::*; #[test] fn test_source_error() { let reason = RenderErrorReason::TemplateNotFound("unnamed".to_owned()); let render_error = RenderError::from(reason); let reason2 = render_error.source().unwrap(); assert!(matches!( reason2.downcast_ref::().unwrap(), RenderErrorReason::TemplateNotFound(_) )); } } handlebars-5.1.2/src/grammar.pest000064400000000000000000000150771046102023000150600ustar 00000000000000//! Pest grammar for handlebars templating 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+)? ~ !symbol_char } json_char_double_quote = { !("\"" | "\\") ~ ANY | "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t") | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4}) } json_char_single_quote = { !("'" | "\\") ~ ANY | "\\" ~ ("'" | "\\" | "/" | "b" | "f" | "n" | "r" | "t") | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4}) } string_inner_double_quote = @{ json_char_double_quote* } string_inner_single_quote = @{ json_char_single_quote* } string_literal = ${ ("\"" ~ string_inner_double_quote ~ "\"") | ("'" ~ string_inner_single_quote ~ "'") } 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 } helper_parameter = { !(keywords ~ !symbol_char) ~ (literal | reference | subexpression) } hash = { identifier ~ "=" ~ helper_parameter } block_param = { "as" ~ "|" ~ identifier ~ identifier? ~ "|"} exp_line = _{ identifier ~ (hash|helper_parameter)* ~ block_param?} partial_exp_line = _{ ((partial_identifier|name) ~ (hash|helper_parameter)*) } subexpression = { "(" ~ ((identifier ~ (hash|helper_parameter)+) | reference) ~ ")" } leading_tilde_to_omit_whitespace = { "~" } trailing_tilde_to_omit_whitespace = { "~" } expression = { !(invert_tag|invert_chain_tag) ~ "{{" ~ leading_tilde_to_omit_whitespace? ~ ((identifier ~ (hash|helper_parameter)+) | name ) ~ trailing_tilde_to_omit_whitespace? ~ "}}" } html_expression_triple_bracket_legacy = _{ "{{{" ~ leading_tilde_to_omit_whitespace? ~ ((identifier ~ (hash|helper_parameter)+) | name ) ~ trailing_tilde_to_omit_whitespace? ~ "}}}" } html_expression_triple_bracket = _{ "{{" ~ leading_tilde_to_omit_whitespace? ~ "{" ~ ((identifier ~ (hash|helper_parameter)+) | name ) ~ "}" ~ trailing_tilde_to_omit_whitespace? ~ "}}" } amp_expression = _{ "{{" ~ leading_tilde_to_omit_whitespace? ~ "&" ~ name ~ trailing_tilde_to_omit_whitespace? ~ "}}" } html_expression = { (html_expression_triple_bracket_legacy | html_expression_triple_bracket) | amp_expression } decorator_expression = { "{{" ~ leading_tilde_to_omit_whitespace? ~ "*" ~ exp_line ~ trailing_tilde_to_omit_whitespace? ~ "}}" } partial_expression = { "{{" ~ leading_tilde_to_omit_whitespace? ~ ">" ~ partial_exp_line ~ trailing_tilde_to_omit_whitespace? ~ "}}" } invert_tag_item = { "else"|"^" } invert_tag = { !escape ~ "{{" ~ leading_tilde_to_omit_whitespace? ~ invert_tag_item ~ trailing_tilde_to_omit_whitespace? ~ "}}" } invert_chain_tag = { !escape ~ "{{" ~ leading_tilde_to_omit_whitespace? ~ invert_tag_item ~ exp_line ~ trailing_tilde_to_omit_whitespace? ~ "}}" } helper_block_start = { "{{" ~ leading_tilde_to_omit_whitespace? ~ "#" ~ exp_line ~ trailing_tilde_to_omit_whitespace? ~ "}}" } helper_block_end = { "{{" ~ leading_tilde_to_omit_whitespace? ~ "/" ~ identifier ~ trailing_tilde_to_omit_whitespace? ~ "}}" } helper_block = _{ helper_block_start ~ template ~ (invert_chain_tag ~ template)* ~ (invert_tag ~ template)? ~ helper_block_end } decorator_block_start = { "{{" ~ leading_tilde_to_omit_whitespace? ~ "#" ~ "*" ~ exp_line ~ trailing_tilde_to_omit_whitespace? ~ "}}" } decorator_block_end = { "{{" ~ leading_tilde_to_omit_whitespace? ~ "/" ~ identifier ~ trailing_tilde_to_omit_whitespace? ~ "}}" } decorator_block = _{ decorator_block_start ~ template ~ decorator_block_end } partial_block_start = { "{{" ~ leading_tilde_to_omit_whitespace? ~ "#" ~ ">" ~ partial_exp_line ~ trailing_tilde_to_omit_whitespace? ~ "}}" } partial_block_end = { "{{" ~ leading_tilde_to_omit_whitespace? ~ "/" ~ partial_identifier ~ trailing_tilde_to_omit_whitespace? ~ "}}" } partial_block = _{ partial_block_start ~ template ~ partial_block_end } raw_block_start = { "{{{{" ~ leading_tilde_to_omit_whitespace? ~ exp_line ~ trailing_tilde_to_omit_whitespace? ~ "}}}}" } raw_block_end = { "{{{{" ~ leading_tilde_to_omit_whitespace? ~ "/" ~ identifier ~ trailing_tilde_to_omit_whitespace? ~ "}}}}" } 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 = _{ helper_parameter ~ 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 } handlebars-5.1.2/src/grammar.rs000064400000000000000000000221541046102023000145230ustar 00000000000000// const _GRAMMAR: &'static str = include_str!("grammar.pest"); #[derive(Parser)] #[grammar = "grammar.pest"] pub struct HandlebarsParser; #[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![ "

helloworld

", 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 = "

{{hello}}

"; 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::helper_parameter, 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!["{{!-- {{this.title}} --}}", "{{! -- 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\"}}", "{{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='literal with space'}}", "{{exp key=\"literal with space\"}}", "{{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}}}", "{{{~ html}}}", "{{{html ~}}}", "{{{~ html ~}}}", "{{~{ html }~}}", "{{~{ html }}}", "{{{ html }~}}", ]; 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}}", "{{#if}}hello{{else if}}world{{else}}test{{/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); } } } handlebars-5.1.2/src/helpers/block_util.rs000064400000000000000000000006311046102023000166620ustar 00000000000000use crate::block::BlockContext; use crate::json::value::PathAndJson; pub(crate) fn create_block<'rc>(param: &PathAndJson<'rc>) -> BlockContext<'rc> { let mut block = BlockContext::new(); if let Some(new_path) = param.context_path() { block.base_path_mut().clone_from(new_path) } else { // use clone for now block.set_base_value(param.value().clone()); } block } handlebars-5.1.2/src/helpers/helper_each.rs000064400000000000000000000457441046102023000170100ustar 00000000000000use 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; use crate::RenderErrorReason; fn update_block_context( block: &mut BlockContext<'_>, base_path: Option<&Vec>, relative_path: String, is_first: bool, value: &Json, ) { if let Some(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<'rc>( block: &mut BlockContext<'rc>, h: &Helper<'rc>, base_path: Option<&Vec>, 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<'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(RenderErrorReason::ParamNotFoundForIndex("each", 0))?; 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 len = obj.len(); let obj_path = value.context_path(); for (i, (k, v)) in obj.iter().enumerate() { if let Some(ref mut block) = rc.block_mut() { let is_first = i == 0usize; let is_last = i == len - 1; let key = to_json(k); block.set_local_var("first", to_json(is_first)); block.set_local_var("last", to_json(is_last)); 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)?; } 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::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}}|{{@last}}|{{@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 = BTreeMap::new(); m.insert("ftp".to_string(), 21); m.insert("gopher".to_string(), 70); m.insert("http".to_string(), 80); let r1 = handlebars.render("t1", &m); assert_eq!( r1.ok().unwrap(), "true|false|ftp:21|false|false|gopher:70|false|true|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 = json!({ "a": [] }); let r0 = handlebars.render("t0", &m1).unwrap(); assert_eq!(r0, "empty"); let m2 = json!({ "b": [] }); 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 = json!({ "a": [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 = json!({ "ftp": 21, "http": 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 = json!({ "ftp": 21, "http": 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 = json!({ "a": { "b": [{"c": [1]}] }, "d": 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 = json!({ "typearg": ["T"], "variant": ["1", "2"] }); let r0 = handlebars.render("t0", &data); assert_eq!(r0.ok().unwrap(), "templatetemplate".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::>()); 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::>()); 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_render_array_without_trailig_commas() { let reg = Registry::new(); let template = "Array: {{array}}"; let input = json!({"array": [1, 2, 3]}); let rendered = reg.render_template(template, &input); assert_eq!("Array: [1, 2, 3]", rendered.unwrap()); } #[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#"
    {{#each a}} {{!-- comment --}}
  • {{this}}
  • {{/each}}
"#; assert_eq!( r#"
  • 0
  • 1
"#, reg.render_template(tpl, &json!({"a": [0, 1]})).unwrap() ); } } handlebars-5.1.2/src/helpers/helper_extras.rs000064400000000000000000000066661046102023000174160ustar 00000000000000//! 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"); } } handlebars-5.1.2/src/helpers/helper_if.rs000064400000000000000000000144351046102023000164770ustar 00000000000000use crate::context::Context; use crate::helpers::{HelperDef, HelperResult}; use crate::json::value::JsonTruthy; use crate::output::Output; use crate::registry::Registry; use crate::render::{Helper, RenderContext, Renderable}; use crate::RenderErrorReason; #[derive(Clone, Copy)] pub struct IfHelper { positive: bool, } impl HelperDef for IfHelper { fn call<'reg: 'rc, 'rc>( &self, h: &Helper<'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(RenderErrorReason::ParamNotFoundForIndex("if", 0))?; 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(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_else_chain() { let handlebars = Registry::new(); assert_eq!( "0".to_owned(), handlebars .render_template("{{#if a}}1{{else if b}}2{{else}}0{{/if}}", &json!({"d": 1})) .unwrap() ); } #[test] fn test_if_else_chain2() { let handlebars = Registry::new(); assert_eq!( "3".to_owned(), handlebars .render_template( "{{#if a}}1{{else if b}}2{{else if c}}3{{else if d}}4{{else}}0{{/if}}", &json!({"c": 1, "d":1}) ) .unwrap() ); } #[test] fn test_if_else_chain3() { let handlebars = Registry::new(); assert_eq!( "4".to_owned(), handlebars .render_template( "{{#if a}}1{{else if b}}2{{else if c}}3{{else if d}}4{{/if}}", &json!({"d":1}) ) .unwrap() ); } #[test] fn test_if_else_chain4() { let handlebars = Registry::new(); assert_eq!( "1".to_owned(), handlebars .render_template( "{{#if a}}1{{else if b}}2{{else if c}}3{{else if d}}4{{/if}}", &json!({"a":1}) ) .unwrap() ); } #[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!( "yes\r\n", hbs.render_template("{{#if a}}\r\nyes\r\n{{/if}}\r\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() ); assert_eq!( r#"yes foo bar baz"#, hbs.render_template( r#"yes {{#if true}} foo bar {{/if}} baz"#, &json!({}) ) .unwrap() ); assert_eq!( r#" foo bar baz"#, hbs.render_template( r#" {{#if true}} foo bar {{/if}} baz"#, &json!({}) ) .unwrap() ); } } handlebars-5.1.2/src/helpers/helper_log.rs000064400000000000000000000065061046102023000166620ustar 00000000000000use crate::context::Context; 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 crate::RenderErrorReason; #[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<'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::>() .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(RenderErrorReason::InvalidLoggingLevel(level.to_string()).into()); } Ok(()) } } #[cfg(feature = "no_logging")] impl HelperDef for LogHelper { fn call<'reg: 'rc, 'rc>( &self, _: &Helper<'rc>, _: &Registry<'reg>, _: &Context, _: &mut RenderContext<'reg, 'rc>, _: &mut dyn Output, ) -> HelperResult { Ok(()) } } pub static LOG_HELPER: LogHelper = LogHelper; #[cfg(test)] mod test { use crate::registry::Registry; #[test] #[cfg(not(feature = "no_logging"))] fn test_log_helper() { let mut handlebars = Registry::new(); assert!(handlebars .register_template_string("t0", "{{log this level=\"warn\"}}") .is_ok()); assert!(handlebars .register_template_string("t1", "{{log this level=\"hello\"}}") .is_ok()); assert!(handlebars .register_template_string("t2", "{{log this}}") .is_ok()); let r0 = handlebars.render("t0", &true); assert!(r0.is_ok()); let r1 = handlebars.render("t1", &true); assert!(r1.is_err()); let r2 = handlebars.render("t2", &true); assert!(r2.is_ok()); } #[test] #[cfg(feature = "no_logging")] fn test_log_helper() { let mut handlebars = Registry::new(); assert!(handlebars .register_template_string("t0", "{{log this level=\"warn\"}}") .is_ok()); assert!(handlebars .register_template_string("t1", "{{log this level=\"hello\"}}") .is_ok()); assert!(handlebars .register_template_string("t2", "{{log this}}") .is_ok()); let r0 = handlebars.render("t0", &true); assert!(r0.is_ok()); let r1 = handlebars.render("t1", &true); assert!(r1.is_ok()); let r2 = handlebars.render("t2", &true); assert!(r2.is_ok()); } } handlebars-5.1.2/src/helpers/helper_lookup.rs000064400000000000000000000064001046102023000174030ustar 00000000000000use 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}; use crate::RenderErrorReason; #[derive(Clone, Copy)] pub struct LookupHelper; impl HelperDef for LookupHelper { fn call_inner<'reg: 'rc, 'rc>( &self, h: &Helper<'rc>, r: &'reg Registry<'reg>, _: &'rc Context, _: &mut RenderContext<'reg, 'rc>, ) -> Result, RenderError> { let collection_value = h .param(0) .ok_or(RenderErrorReason::ParamNotFoundForIndex("lookup", 0))?; let index = h .param(1) .ok_or(RenderErrorReason::ParamNotFoundForIndex("lookup", 1))?; let value = match *collection_value.value() { Json::Array(ref v) => index.value().as_u64().and_then(|u| v.get(u as usize)), Json::Object(ref m) => index.value().as_str().and_then(|k| m.get(k)), _ => None, }; if r.strict_mode() && value.is_none() { Err(RenderError::strict_error(None)) } else { Ok(value.unwrap_or(&Json::Null).clone().into()) } } } pub static LOOKUP_HELPER: LookupHelper = LookupHelper; #[cfg(test)] mod test { use crate::registry::Registry; #[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 m = json!({"v1": [1,2,3], "v2": [9,8,7]}); let m2 = json!({ "kk": {"a": "world"} }); 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()); assert!(handlebars.render_template("{{lookup}}", &m).is_err()); assert!(handlebars.render_template("{{lookup v1}}", &m).is_err()); assert_eq!( handlebars.render_template("{{lookup null 1}}", &m).unwrap(), "" ); assert_eq!( handlebars.render_template("{{lookup v1 3}}", &m).unwrap(), "" ); } #[test] fn test_strict_lookup() { let mut hbs = Registry::new(); assert_eq!( hbs.render_template("{{lookup kk 1}}", &json!({"kk": []})) .unwrap(), "" ); assert!(hbs .render_template("{{lookup kk 0}}", &json!({ "kk": [null] })) .is_ok()); hbs.set_strict_mode(true); assert!(hbs .render_template("{{lookup kk 1}}", &json!({"kk": []})) .is_err()); assert!(hbs .render_template("{{lookup kk 0}}", &json!({ "kk": [null] })) .is_ok()); } } handlebars-5.1.2/src/helpers/helper_raw.rs000064400000000000000000000021251046102023000166630ustar 00000000000000use 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<'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"); } } handlebars-5.1.2/src/helpers/helper_with.rs000064400000000000000000000200411046102023000170420ustar 00000000000000use 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}; use crate::RenderErrorReason; #[derive(Clone, Copy)] pub struct WithHelper; impl HelperDef for WithHelper { fn call<'reg: 'rc, 'rc>( &self, h: &Helper<'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(RenderErrorReason::ParamNotFoundForIndex("with", 0))?; if param.value().is_truthy(false) { let mut block = create_block(param); if let Some(block_param) = h.block_param() { let mut params = BlockParams::new(); if param.context_path().is_some() { params.add_path(block_param, Vec::with_capacity(0))?; } else { params.add_value(block_param, param.value().clone())?; } block.set_block_params(params); } rc.push_block(block); if let Some(t) = h.template() { t.render(r, ctx, rc, out)?; }; rc.pop_block(); Ok(()) } else if let Some(t) = h.inverse() { t.render(r, ctx, rc, out) } else if r.strict_mode() { Err(RenderError::strict_error(param.relative_path())) } else { Ok(()) } } } pub static WITH_HELPER: WithHelper = WithHelper; #[cfg(test)] mod test { use crate::registry::Registry; #[derive(Serialize)] struct Address { city: String, country: String, } #[derive(Serialize)] struct Person { name: String, age: i16, addr: Address, titles: Vec, } #[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 = json!({ "a": { "b": [{"c": [1]}] }, "d": 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" ); } } handlebars-5.1.2/src/helpers/mod.rs000064400000000000000000000231141046102023000153130ustar 00000000000000use crate::context::Context; use crate::error::{RenderError, RenderErrorReason}; 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 dyn 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<'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<'rc>, _: &'reg Registry<'reg>, _: &'rc Context, _: &mut RenderContext<'reg, 'rc>, ) -> Result, RenderError> { Err(RenderErrorReason::Unimplemented.into()) } /// 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<'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<'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<'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; #[cfg(feature = "string_helpers")] pub(crate) mod string_helpers; // pub type HelperDef = for <'a, 'b, 'c> Fn<(&'a Context, &'b Helper, &'b Registry, &'c mut RenderContext), Result>; // // pub fn helper_dummy (ctx: &Context, h: &Helper, r: &Registry, rc: &mut RenderContext) -> Result { // 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<'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(); write!(out, "{}:{}", h.name(), v.value().render())?; if h.is_block() { 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> { write!(out, "{}{}", h.name(), h.param(0).unwrap().value())?; Ok(()) }, ), ); handlebars.register_helper( "foo", Box::new( |h: &Helper<'_>, _: &Registry<'_>, _: &Context, _: &mut RenderContext<'_, '_>, out: &mut dyn Output| -> Result<(), RenderError> { write!(out, "{}", h.hash_get("value").unwrap().value().render())?; 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()); } } handlebars-5.1.2/src/helpers/scripting.rs000064400000000000000000000064331046102023000165430ustar 00000000000000use std::collections::{BTreeMap, HashMap}; use crate::context::Context; use crate::error::{RenderError, RenderErrorReason}; 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<'rc>], hash: &BTreeMap<&'reg str, PathAndJson<'rc>>, engine: &Engine, script: &AST, ) -> Result, RenderError> { let params: Dynamic = to_dynamic(params.iter().map(|p| p.value()).collect::>()) .map_err(RenderErrorReason::from)?; let hash: Dynamic = to_dynamic( hash.iter() .map(|(k, v)| ((*k).to_owned(), v.value())) .collect::>(), ) .map_err(RenderErrorReason::from)?; let mut scope = Scope::new(); scope.push_dynamic("params", params); scope.push_dynamic("hash", hash); let result = engine .eval_ast_with_scope::(&mut scope, script) .map_err(RenderErrorReason::from)?; let result_json: Json = from_dynamic(&result).map_err(RenderErrorReason::from)?; Ok(ScopedJson::Derived(result_json)) } impl HelperDef for ScriptHelper { fn call_inner<'reg: 'rc, 'rc>( &self, h: &Helper<'rc>, reg: &'reg Registry<'reg>, _ctx: &'rc Context, _rc: &mut RenderContext<'reg, 'rc>, ) -> Result, 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::(&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 mut hash = BTreeMap::new(); hash.insert( "me", PathAndJson::new(None, ScopedJson::Derived(json!("no"))), ); hash.insert( "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); } } handlebars-5.1.2/src/helpers/string_helpers/mod.rs000064400000000000000000000106771046102023000203550ustar 00000000000000//! A Set of string utilities often required during code generation. //! //! See also: [`heck`](https://docs.rs/heck/latest/heck) //! //! `lowerCamelCase`: Convert a string to lowerCamelCase //! `upperCamelCase`: Convert a string to UpperCamelCase //! `snakeCase`: Convert a string to snake_case //! `kebabCase`: Conver a string to kebab-case //! `shoutySnakeCase`: Convert a string to SHOUTY_SNAKE_CASE //! `shoutyKebabCase`: Convert a string to SHOUTY-KEBAB-CASE //! `titleCase`: Convert a string to Title Case //! `trainCase`: Convert a string to Train-Case use heck::{ ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToTrainCase, ToUpperCamelCase, }; macro_rules! define_case_helper { ($helper_fn_name: ident, $heck_fn_name:ident) => { pub(crate) fn $helper_fn_name( h: &crate::render::Helper<'_>, _: &crate::Handlebars<'_>, _: &crate::context::Context, _rc: &mut crate::render::RenderContext<'_, '_>, out: &mut dyn crate::output::Output, ) -> crate::helpers::HelperResult { let param = h.param(0).and_then(|v| v.value().as_str()).ok_or_else(|| { crate::error::RenderErrorReason::ParamTypeMismatchForName( stringify!($helper_fn_name), "0".to_owned(), "string".to_owned(), ) })?; out.write(param.$heck_fn_name().as_ref())?; Ok(()) } }; } define_case_helper!(lower_camel_case, to_lower_camel_case); define_case_helper!(upper_camel_case, to_upper_camel_case); define_case_helper!(snake_case, to_snake_case); define_case_helper!(shouty_snake_case, to_shouty_snake_case); define_case_helper!(kebab_case, to_kebab_case); define_case_helper!(shouty_kebab_case, to_shouty_kebab_case); define_case_helper!(title_case, to_title_case); define_case_helper!(train_case, to_train_case); #[cfg(test)] mod tests { macro_rules! define_case_helpers_test_cases { ($template_fn_name:literal, $helper_tc_fn_name:ident, $(($tc_input:literal, $tc_expected:literal),)+) => { #[test] fn $helper_tc_fn_name() { let hbs = crate::registry::Registry::new(); let test_cases = vec![$(($tc_input, $tc_expected)),+]; for tc in test_cases { let result = hbs.render_template( concat!("{{", $template_fn_name, " data}}"), &json!({"data": tc.0})); assert!(result.is_ok(), "{}", result.err().unwrap()); assert_eq!(result.unwrap(), tc.1.to_string()); } } } } define_case_helpers_test_cases!( "lowerCamelCase", test_lower_camel_case, ("lower camel case", "lowerCamelCase"), ("lower-camel-case", "lowerCamelCase"), ("lower_camel_case", "lowerCamelCase"), ); define_case_helpers_test_cases!( "upperCamelCase", test_upper_camel_case, ("upper camel case", "UpperCamelCase"), ("upper-camel-case", "UpperCamelCase"), ("upper_camel_case", "UpperCamelCase"), ); define_case_helpers_test_cases!( "snakeCase", test_snake_case, ("snake case", "snake_case"), ("snake-case", "snake_case"), ); define_case_helpers_test_cases!( "kebabCase", test_kebab_case, ("kebab case", "kebab-case"), ("kebab_case", "kebab-case"), ); define_case_helpers_test_cases!( "shoutySnakeCase", test_shouty_snake_case, ("shouty snake case", "SHOUTY_SNAKE_CASE"), ("shouty snake-case", "SHOUTY_SNAKE_CASE"), ); define_case_helpers_test_cases!( "shoutyKebabCase", test_shouty_kebab_case, ("shouty kebab case", "SHOUTY-KEBAB-CASE"), ("shouty_kebab_case", "SHOUTY-KEBAB-CASE"), ); define_case_helpers_test_cases!("titleCase", test_title_case, ("title case", "Title Case"),); define_case_helpers_test_cases!("trainCase", test_train_case, ("train case", "Train-Case"),); #[test] fn test_invalid_input() { use crate::error::RenderErrorReason; let hbs = crate::registry::Registry::new(); let err = hbs .render_template("{{snakeCase 1}}", &json!({})) .unwrap_err(); assert!(matches!( err.reason(), RenderErrorReason::ParamTypeMismatchForName(_, _, _) )); } } handlebars-5.1.2/src/json/mod.rs000064400000000000000000000000531046102023000146170ustar 00000000000000pub(crate) mod path; pub(crate) mod value; handlebars-5.1.2/src/json/path.rs000064400000000000000000000075071046102023000150070ustar 00000000000000use std::iter::Peekable; use pest::iterators::Pair; use pest::Parser; use crate::error::RenderError; use crate::grammar::{HandlebarsParser, Rule}; use crate::RenderErrorReason; #[derive(PartialEq, Eq, 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, Eq, Clone, Debug)] pub enum Path { Relative((Vec, String)), Local((usize, String, String)), } impl Path { pub(crate) fn new(raw: &str, segs: Vec) -> 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 { 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(|_| RenderErrorReason::InvalidJsonPath(raw.to_owned()))? } 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.first().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, limit: usize) -> Vec where I: Iterator>, { 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, 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) => {} _ => {} } } } handlebars-5.1.2/src/json/value.rs000064400000000000000000000125551046102023000151660ustar 00000000000000use 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, Clone)] pub enum ScopedJson<'rc> { Constant(&'rc Json), Derived(Json), // represents a json reference to context value, its full path Context(&'rc Json, Vec), Missing, } impl<'rc> ScopedJson<'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<'rc> { let v = self.as_json(); ScopedJson::Derived(v.clone()) } pub fn context_path(&self) -> Option<&Vec> { match self { ScopedJson::Context(_, ref p) => Some(p), _ => None, } } } impl<'reg: 'rc, 'rc> From for ScopedJson<'rc> { fn from(v: Json) -> ScopedJson<'rc> { ScopedJson::Derived(v) } } /// Json wrapper that holds the Json value and reference path information /// #[derive(Debug, Clone)] pub struct PathAndJson<'rc> { relative_path: Option, value: ScopedJson<'rc>, } impl<'rc> PathAndJson<'rc> { pub fn new(relative_path: Option, value: ScopedJson<'rc>) -> PathAndJson<'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> { 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, value) in a.iter().enumerate() { buf.push_str(value.render().as_ref()); if i < a.len() - 1 { buf.push_str(", "); } } buf.push(']'); buf } Json::Object(_) => "[object]".to_owned(), } } } /// Convert any serializable data into Serde Json type pub fn to_json(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 = "

Hello world

\n

"; 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).is_truthy(false)); assert!(!json!(None as Option).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)); } handlebars-5.1.2/src/lib.rs000064400000000000000000000404371046102023000136470ustar 00000000000000#![doc(html_root_url = "https://docs.rs/handlebars/5.1.2")] #![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/3.2/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. //! //! 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> { //! 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(()) //! # } //! ``` //! //! #### Additional features for loading template from //! //! * Feature `dir_source` enables template loading //! `register_templates_directory` from given directory. //! * Feature `rust-embed` enables template loading //! `register_embed_templates` from embedded resources in rust struct //! generated with `RustEmbed`. //! //! ### 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> { //! 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> { //! 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::*; //! //! // 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> { //! 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(RenderErrorReason::ParamNotFoundForIndex("closure-helper", 0))?; //! //! 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. //! //! ### String (or Case) Helpers //! //! [Handlebars] supports helpers for converting string cases for example converting a value to //! 'camelCase or 'kebab-case' etc. This can be useful during generating code using Handlebars. //! This can be enabled by selecting the feature-flag `string_helpers`. Currently the case //! conversions from the [`heck`](https://docs.rs/heck/latest/heck) crate are supported. //! //! ``` //! # #[cfg(feature = "string_helpers")] { //! # use std::error::Error; //! # extern crate handlebars; //! use handlebars::Handlebars; //! //! # fn main() -> Result<(), Box> { //! //! let mut handlebars = Handlebars::new(); //! //! let data = serde_json::json!({"value": "lower camel case"}); //! assert_eq!(handlebars.render_template("This is {{lowerCamelCase value}}", &data)?, //! "This is lowerCamelCase".to_owned()); //! # Ok(()) //! # } //! # } //! ``` //! #![allow(dead_code, clippy::upper_case_acronyms)] #![warn(rust_2018_idioms)] #![recursion_limit = "200"] #[cfg(not(feature = "no_logging"))] #[macro_use] extern crate log; #[macro_use] extern crate pest_derive; #[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, RenderErrorReason, TemplateError, TemplateErrorReason}; pub use self::helpers::{HelperDef, HelperResult}; pub use self::json::path::Path; pub use self::json::value::{to_json, JsonRender, JsonTruthy, PathAndJson, ScopedJson}; pub use self::local_vars::LocalVars; pub use self::output::{Output, StringOutput}; #[cfg(feature = "dir_source")] pub use self::registry::DirectorySourceOptions; 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; handlebars-5.1.2/src/local_vars.rs000064400000000000000000000016651046102023000152260ustar 00000000000000use std::collections::BTreeMap; use serde_json::value::Value as Json; #[derive(Default, Debug, Clone)] pub struct LocalVars { first: Option, last: Option, index: Option, key: Option, extra: BTreeMap, } 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), } } } handlebars-5.1.2/src/macros.rs000064400000000000000000000151141046102023000143570ustar 00000000000000/// 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$(<$($gen:ty),+>)?),* $($(,)?{$($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<'rc>, r: &'reg $crate::Handlebars<'reg>, _: &'rc $crate::Context, _: &mut $crate::RenderContext<'reg, 'rc>, ) -> std::result::Result<$crate::ScopedJson<'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::RenderErrorReason::ParamNotFoundForName(stringify!($struct_name), stringify!($name).to_string())) .and_then(|x| $crate::handlebars_helper!(@as_json_value x, $tpe$(<$($gen),+>)?) .ok_or_else(|| $crate::RenderErrorReason::ParamTypeMismatchForName(stringify!($struct_name), stringify!($name).to_string(), stringify!($tpe$(<$($gen),+>)?).to_string()).into()) )?; param_idx += 1; )* $( $( let $hash_name = h.hash_get(stringify!($hash_name)) .map(|x| x.value()) .map(|x| $crate::handlebars_helper!(@as_json_value x, $hash_tpe) .ok_or_else(|| $crate::RenderErrorReason::HashTypeMismatchForName( stringify!($struct_name), stringify!($hash_name).to_string(), stringify!($hash_tpe).to_string() )) ) .unwrap_or_else(|| Ok($dft_val))?; )* )? $(let $args = h.params().iter().map(|x| x.value()).collect::>();)? $(let $kwargs = h.hash().iter().map(|(k, v)| (k.to_owned(), v.value())).collect::>();)? 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) }; (@as_json_value $x:ident, $tpe:tt$(<$($gen:ty),+>)?) => { serde_json::from_value::<$tpe$(<$($gen),+>)?>($x.clone()).ok() }; } #[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)*) => {}; } } handlebars-5.1.2/src/output.rs000064400000000000000000000035731046102023000144410ustar 00000000000000use 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>; /// Designed to be used with `write!` macro. /// for backward compatibility and to avoid breakage the default implementation /// uses `format!` this may be not what you want. fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> Result<(), IOError> { // Check if there is nothing to format to avoid allocation on case like // write!(out, "hey")?; if let Some(content) = args.as_str() { self.write(content) } else { self.write(&std::fmt::format(args)) } } } pub struct WriteOutput { write: W, } impl Output for WriteOutput { fn write(&mut self, seg: &str) -> Result<(), IOError> { self.write.write_all(seg.as_bytes()) } fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> Result<(), IOError> { self.write.write_fmt(args) } } impl WriteOutput { pub fn new(write: W) -> WriteOutput { WriteOutput { write } } } pub struct StringOutput { buf: Vec, } impl Output for StringOutput { fn write(&mut self, seg: &str) -> Result<(), IOError> { self.buf.extend_from_slice(seg.as_bytes()); Ok(()) } fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> Result<(), IOError> { self.buf.write_fmt(args) } } impl StringOutput { pub fn new() -> StringOutput { StringOutput { buf: Vec::with_capacity(8 * 1024), } } pub fn into_string(self) -> Result { String::from_utf8(self.buf) } } impl Default for StringOutput { fn default() -> Self { StringOutput::new() } } handlebars-5.1.2/src/partial.rs000064400000000000000000000456731046102023000145440ustar 00000000000000use 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; use crate::RenderErrorReason; 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<'rc>, name: &str, ) -> Result>, RenderError> { if let Some(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<'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(RenderErrorReason::CannotIncludeSelf.into()); } 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(); // if tname == PARTIAL_BLOCK let is_partial_block = tname == PARTIAL_BLOCK; // add partial block depth there are consecutive partial // blocks in the stack. if is_partial_block { local_rc.inc_partial_block_depth(); } else { // depth cannot be lower than 0, which is guaranted in the // `dec_partial_block_depth` method local_rc.dec_partial_block_depth(); } let mut block_created = false; // create context if param given if let Some(base_path) = d.param(0).and_then(|p| p.context_path()) { // path given, update base_path let mut block_inner = BlockContext::new(); *block_inner.base_path_mut() = base_path.to_vec(); // because block is moved here, we need another bool variable to track // its status for later cleanup block_created = true; // clear blocks to prevent block params from parent // template to be leaked into partials // see `test_partial_context_issue_495` for the case. local_rc.clear_blocks(); local_rc.push_block(block_inner); } if !d.hash().is_empty() { // hash given, update base_value let hash_ctx = d .hash() .iter() .map(|(k, v)| (*k, v.value())) .collect::>(); // create block if we didn't (no param provided for partial expression) if !block_created { let block_inner = if let Some(block) = local_rc.block() { // reuse current block information, including base_path and // base_value if any block.clone() } else { BlockContext::new() }; local_rc.clear_blocks(); local_rc.push_block(block_inner); } // evaluate context within current block, this includes block // context provided by partial expression parameter let merged_context = merge_json( local_rc.evaluate2(ctx, &Path::current())?.as_json(), &hash_ctx, ); // update the base value, there must be a block for this so it's // also safe to unwrap. if let Some(block) = local_rc.block_mut() { block.set_base_value(merged_context); } } // @partial-block if let Some(pb) = d.template() { local_rc.push_partial_block(pb); } // indent local_rc.set_indent_string(d.indent()); let result = t.render(r, ctx, &mut local_rc, out); // cleanup if block_created { local_rc.pop_block(); } if d.template().is_some() { local_rc.pop_partial_block(); } result } else { Err(RenderErrorReason::PartialNotFound(tname.to_owned()).into()) } } #[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", &json!({"a": "2"})).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 teset_partial_context_with_both_hash_and_param() { let mut hbs = Registry::new(); hbs.register_template_string("one", "This is a test. {{> two this name=\"fred\" }}") .unwrap(); hbs.register_template_string("two", "Lets test {{name}} and {{root_name}}") .unwrap(); assert_eq!( "This is a test. Lets test fred and tom", hbs.render("one", &json!({"root_name": "tom"})).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_partial_block() { let mut handlebars = Registry::new(); let template1 = "{{> @partial-block }}"; let template2 = "{{#> t1 }}{{> @partial-block }}{{/ 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!("Hello", 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," ); } #[test] fn line_stripping_with_inline_and_partial() { let tpl0 = r#"{{#*inline "foo"}}foo {{/inline}} {{> foo}} {{> foo}} {{> foo}}"#; let tpl1 = r#"{{#*inline "foo"}}foo{{/inline}} {{> foo}} {{> foo}} {{> foo}}"#; let hbs = Registry::new(); assert_eq!( r#"foo foo foo "#, hbs.render_template(tpl0, &json!({})).unwrap() ); assert_eq!( r#" foofoofoo"#, hbs.render_template(tpl1, &json!({})).unwrap() ); } #[test] fn test_partial_indent() { let outer = r#" {{> inner inner_solo}} {{#each inners}} {{> inner}} {{/each}} {{#each inners}} {{> inner}} {{/each}} "#; let inner = r#"name: {{name}} "#; let mut hbs = Registry::new(); hbs.register_template_string("inner", inner).unwrap(); hbs.register_template_string("outer", outer).unwrap(); let result = hbs .render( "outer", &json!({ "inner_solo": {"name": "inner_solo"}, "inners": [ {"name": "hello"}, {"name": "there"} ] }), ) .unwrap(); assert_eq!( result, r#" name: inner_solo name: hello name: there name: hello name: there "# ); } // Rule::partial_expression should not trim leading indent by default #[test] fn test_partial_prevent_indent() { let outer = r#" {{> inner inner_solo}} {{#each inners}} {{> inner}} {{/each}} {{#each inners}} {{> inner}} {{/each}} "#; let inner = r#"name: {{name}} "#; let mut hbs = Registry::new(); hbs.set_prevent_indent(true); hbs.register_template_string("inner", inner).unwrap(); hbs.register_template_string("outer", outer).unwrap(); let result = hbs .render( "outer", &json!({ "inner_solo": {"name": "inner_solo"}, "inners": [ {"name": "hello"}, {"name": "there"} ] }), ) .unwrap(); assert_eq!( result, r#" name: inner_solo name: hello name: there name: hello name: there "# ); } #[test] fn test_nested_partials() { let mut hb = Registry::new(); hb.register_template_string("partial", "{{> @partial-block}}") .unwrap(); hb.register_template_string( "index", r#"{{#>partial}} Yo {{#>partial}} Yo 2 {{/partial}} {{/partial}}"#, ) .unwrap(); assert_eq!( r#" Yo Yo 2 "#, hb.render("index", &()).unwrap() ); hb.register_template_string("partial2", "{{> @partial-block}}") .unwrap(); let r2 = hb .render_template( r#"{{#> partial}} {{#> partial2}} :( {{/partial2}} {{/partial}}"#, &(), ) .unwrap(); assert_eq!(":(\n", r2); } #[test] fn test_partial_context_issue_495() { let mut hb = Registry::new(); hb.register_template_string( "t1", r#"{{~#*inline "displayName"~}} Template:{{name}} {{/inline}} {{#each data as |name|}} Name:{{name}} {{>displayName name="aaaa"}} {{/each}}"#, ) .unwrap(); hb.register_template_string( "t1", r#"{{~#*inline "displayName"~}} Template:{{this}} {{/inline}} {{#each data as |name|}} Name:{{name}} {{>displayName}} {{/each}}"#, ) .unwrap(); let data = json!({ "data": ["hudel", "test"] }); assert_eq!( r#"Name:hudel Template:hudel Name:test Template:test "#, hb.render("t1", &data).unwrap() ); } #[test] fn test_multiline_partial_indent() { let mut hb = Registry::new(); hb.register_template_string( "t1", r#"{{#*inline "thepartial"}} inner first line inner second line {{/inline}} {{> thepartial}} outer third line"#, ) .unwrap(); assert_eq!( r#" inner first line inner second line outer third line"#, hb.render("t1", &()).unwrap() ); hb.register_template_string( "t2", r#"{{#*inline "thepartial"}}inner first line inner second line {{/inline}} {{> thepartial}} outer third line"#, ) .unwrap(); assert_eq!( r#" inner first line inner second line outer third line"#, hb.render("t2", &()).unwrap() ); hb.register_template_string( "t3", r#"{{#*inline "thepartial"}}{{a}}{{/inline}} {{> thepartial}} outer third line"#, ) .unwrap(); assert_eq!( r#" inner first line inner second lineouter third line"#, hb.render("t3", &json!({"a": "inner first line\ninner second line"})) .unwrap() ); hb.register_template_string( "t4", r#"{{#*inline "thepartial"}} inner first line inner second line {{/inline}} {{~> thepartial}} outer third line"#, ) .unwrap(); assert_eq!( r#" inner first line inner second line outer third line"#, hb.render("t4", &()).unwrap() ); let mut hb2 = Registry::new(); hb2.set_prevent_indent(true); hb2.register_template_string( "t1", r#"{{#*inline "thepartial"}} inner first line inner second line {{/inline}} {{> thepartial}} outer third line"#, ) .unwrap(); assert_eq!( r#" inner first line inner second line outer third line"#, hb2.render("t1", &()).unwrap() ) } #[test] fn test_issue_534() { let t1 = "{{title}}"; let t2 = "{{#each modules}}{{> (lookup this \"module\") content name=0}}{{/each}}"; let data = json!({ "modules": [ {"module": "t1", "content": {"title": "foo"}}, {"module": "t1", "content": {"title": "bar"}}, ] }); let mut hbs = Registry::new(); hbs.register_template_string("t1", t1).unwrap(); hbs.register_template_string("t2", t2).unwrap(); assert_eq!("foobar", hbs.render("t2", &data).unwrap()); } #[test] fn test_partial_not_found() { let t1 = "{{> bar}}"; let hbs = Registry::new(); assert!(hbs.render_template(&t1, &()).is_err()); } } handlebars-5.1.2/src/registry.rs000064400000000000000000001331331046102023000147450ustar 00000000000000use 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, RenderErrorReason, 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, TemplateOptions}; #[cfg(feature = "dir_source")] use walkdir::WalkDir; #[cfg(feature = "script_helper")] use rhai::Engine; #[cfg(feature = "script_helper")] use crate::helpers::scripting::ScriptHelper; #[cfg(feature = "rust-embed")] use crate::sources::LazySource; #[cfg(feature = "rust-embed")] use rust_embed::RustEmbed; /// 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 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, helpers: HashMap>, decorators: HashMap>, escape_fn: EscapeFn, strict_mode: bool, dev_mode: bool, prevent_indent: bool, #[cfg(feature = "script_helper")] pub(crate) engine: Arc, template_sources: HashMap + Send + Sync + 'reg>>, #[cfg(feature = "script_helper")] script_sources: HashMap + 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 = "script_helper")] fn rhai_engine() -> Engine { Engine::new() } /// Options for importing template files from a directory. #[cfg(feature = "dir_source")] pub struct DirectorySourceOptions { /// The name extension for template files pub tpl_extension: String, /// Whether to include hidden files (file name that starts with `.`) pub hidden: bool, /// Whether to include temporary files (file name that starts with `#`) pub temporary: bool, } #[cfg(feature = "dir_source")] impl DirectorySourceOptions { fn ignore_file(&self, name: &str) -> bool { self.ignored_as_hidden_file(name) || self.ignored_as_temporary_file(name) } #[inline] fn ignored_as_hidden_file(&self, name: &str) -> bool { !self.hidden && name.starts_with('.') } #[inline] fn ignored_as_temporary_file(&self, name: &str) -> bool { !self.temporary && name.starts_with('#') } } #[cfg(feature = "dir_source")] impl Default for DirectorySourceOptions { fn default() -> Self { DirectorySourceOptions { tpl_extension: ".hbs".to_owned(), hidden: false, temporary: false, } } } 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, prevent_indent: 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)); #[cfg(feature = "string_helpers")] self.register_string_helpers(); 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 /// friendly 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 /// friendly features, that may affect its performance. /// /// **Note that you have to enable dev mode before adding templates to /// the registry**. Otherwise it won't take effect at all. pub fn set_dev_mode(&mut self, enabled: bool) { self.dev_mode = enabled; // clear template source when disabling dev mode if !enabled { self.template_sources.clear(); } } /// Enable or disable indent for partial include tag `{{>}}` /// /// By default handlebars keeps indent whitespaces for partial /// include tag, to change this behaviour, set this toggle to `true`. pub fn set_prevent_indent(&mut self, enable: bool) { self.prevent_indent = enable; } /// Return state for `prevent_indent` option, default to `false`. pub fn prevent_indent(&self) -> bool { self.prevent_indent } /// 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. /// /// Dev mode doesn't apply for pre-compiled template because it's lifecycle /// is not managed by the registry. 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( &mut self, name: &str, tpl_str: S, ) -> Result<(), TemplateError> where S: AsRef, { let template = Template::compile2( tpl_str.as_ref(), TemplateOptions { name: Some(name.to_owned()), prevent_indent: self.prevent_indent, }, )?; 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(&mut self, name: &str, partial_str: S) -> Result<(), TemplateError> where S: AsRef, { self.register_template_string(name, partial_str) } /// Register a template from a path on file system /// /// If dev mode is enabled, the registry will keep reading the template file /// from file system everytime it's visited. pub fn register_template_file

( &mut self, name: &str, tpl_path: P, ) -> Result<(), TemplateError> where P: AsRef, { 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 by default. /// Set `DirectorySourceOptions` to something other than `DirectorySourceOptions::default()` to adjust this. /// All registered templates will use their relative path to determine their template name. /// For example, when `dir_path` is `templates/` and `DirectorySourceOptions.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. /// /// When dev_mode is enabled, like with `register_template_file`, templates are reloaded /// from the file system every time they're visited. #[cfg(feature = "dir_source")] #[cfg_attr(docsrs, doc(cfg(feature = "dir_source")))] pub fn register_templates_directory

( &mut self, dir_path: P, options: DirectorySourceOptions, ) -> Result<(), TemplateError> where P: AsRef, { let dir_path = dir_path.as_ref(); let walker = WalkDir::new(dir_path); let dir_iter = walker .min_depth(1) .into_iter() .filter_map(|e| e.ok().map(|e| e.into_path())) // Checks if extension matches .filter(|tpl_path| { tpl_path .to_string_lossy() .ends_with(options.tpl_extension.as_str()) }) // Rejects any hidden or temporary files. .filter(|tpl_path| { tpl_path .file_stem() .map(|stem| !options.ignore_file(&stem.to_string_lossy())) .unwrap_or(false) }) .filter_map(|tpl_path| { tpl_path .strip_prefix(dir_path) .ok() .map(|tpl_canonical_name| { let tpl_name = tpl_canonical_name .components() .map(|component| component.as_os_str().to_string_lossy()) .collect::>() .join("/"); tpl_name .strip_suffix(options.tpl_extension.as_str()) .map(|s| s.to_owned()) .unwrap_or(tpl_name) }) .map(|tpl_canonical_name| (tpl_canonical_name, tpl_path)) }); for (tpl_canonical_name, tpl_path) in dir_iter { self.register_template_file(&tpl_canonical_name, &tpl_path)?; } Ok(()) } /// Register templates using a /// [RustEmbed](https://github.com/pyros2097/rust-embed) type /// Calls register_embed_templates_with_extension with empty extension. /// /// File names from embed struct are used as template name. /// /// ```skip /// #[derive(RustEmbed)] /// #[folder = "templates"] /// #[include = "*.hbs"] /// struct Assets; /// /// let mut hbs = Handlebars::new(); /// hbs.register_embed_templates::(); /// ``` /// #[cfg(feature = "rust-embed")] #[cfg_attr(docsrs, doc(cfg(feature = "rust-embed")))] pub fn register_embed_templates(&mut self) -> Result<(), TemplateError> where E: RustEmbed, { self.register_embed_templates_with_extension::("") } /// Register templates using a /// [RustEmbed](https://github.com/pyros2097/rust-embed) type /// * `tpl_extension`: the template file extension /// /// File names from embed struct are used as template name, but extension is stripped. /// /// When dev_mode enabled templates is reloaded /// from embed struct everytime it's visied. /// /// ```skip /// #[derive(RustEmbed)] /// #[folder = "templates"] /// struct Assets; /// /// let mut hbs = Handlebars::new(); /// hbs.register_embed_templates_with_extension::(".hbs"); /// ``` /// #[cfg(feature = "rust-embed")] #[cfg_attr(docsrs, doc(cfg(feature = "rust-embed")))] pub fn register_embed_templates_with_extension( &mut self, tpl_extension: &str, ) -> Result<(), TemplateError> where E: RustEmbed, { for file_name in E::iter().filter(|x| x.ends_with(tpl_extension)) { let tpl_name = file_name .strip_suffix(tpl_extension) .unwrap_or(&file_name) .to_owned(); let source = LazySource::new(move || { E::get(&file_name) .map(|file| file.data.to_vec()) .and_then(|data| String::from_utf8(data).ok()) }); let tpl_content = source .load() .map_err(|e| (e, "Template load error".to_owned()))?; self.register_template_string(&tpl_name, &tpl_content)?; if self.dev_mode { self.template_sources.insert(tpl_name, Arc::new(source)); } } Ok(()) } /// Remove a template from the registry pub fn unregister_template(&mut self, name: &str) { self.templates.remove(name); self.template_sources.remove(name); } /// Register a helper pub fn register_helper(&mut self, name: &str, def: Box) { 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 /// /// When dev mode is enable, script file is reloaded from original file /// everytime it is called. #[cfg(feature = "script_helper")] #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))] pub fn register_script_helper_file

( &mut self, name: &str, script_path: P, ) -> Result<(), ScriptError> where P: AsRef, { 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) } /// Borrow a read-only reference to current rhai engine #[cfg(feature = "script_helper")] #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))] pub fn engine(&self) -> &Engine { self.engine.as_ref() } /// Set a custom rhai engine for the registry. /// /// *Note that* you need to set custom engine before adding scripts. #[cfg(feature = "script_helper")] #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))] pub fn set_engine(&mut self, engine: Engine) { self.engine = Arc::new(engine); } /// Register a decorator pub fn register_decorator( &mut self, name: &str, def: Box, ) { 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 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.as_ref() } /// 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, 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::compile2( tpl_str.as_ref(), TemplateOptions { name: Some(name.to_owned()), prevent_indent: self.prevent_indent, }, ) }) .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, RenderError> { if let Some(result) = self.get_or_load_template_optional(name) { result } else { Err(RenderErrorReason::TemplateNotFound(name.to_owned()).into()) } } /// Return a registered helper #[inline] pub(crate) fn get_or_load_helper( &'reg self, name: &str, ) -> Result>, 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; Ok(Some(helper.into())) }) .map_err(|e| RenderError::from(RenderErrorReason::from(e))); } 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 #[inline] 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 /// /// **Note that** in dev mode, the template returned from this method may /// not reflect its latest state. This method doesn't try to reload templates /// from its source. pub fn get_templates(&self) -> &HashMap { &self.templates } /// Unregister all templates pub fn clear_templates(&mut self) { self.templates.clear(); self.template_sources.clear(); } #[inline] fn render_to_output( &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(&self, name: &str, data: &T) -> Result 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 { 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 data to the `std::io::Write` pub fn render_to_write(&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 registered template using reusable `Context`, and write data to /// the `std::io::Write` pub fn render_with_context_to_write( &self, name: &str, ctx: &Context, writer: W, ) -> Result<(), RenderError> where W: Write, { let mut output = WriteOutput::new(writer); self.render_to_output(name, ctx, &mut output) } /// Render a template string using current registry without registering it pub fn render_template(&self, template_string: &str, data: &T) -> Result 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 reusable context data pub fn render_template_with_context( &self, template_string: &str, ctx: &Context, ) -> Result { let tpl = Template::compile2( template_string, TemplateOptions { prevent_indent: self.prevent_indent, ..Default::default() }, ) .map_err(RenderError::from)?; 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 resuable context, and write data into /// `std::io::Write` pub fn render_template_with_context_to_write( &self, template_string: &str, ctx: &Context, writer: W, ) -> Result<(), RenderError> where W: Write, { let tpl = Template::compile2( template_string, TemplateOptions { prevent_indent: self.prevent_indent, ..Default::default() }, ) .map_err(RenderError::from)?; let mut render_context = RenderContext::new(None); let mut out = WriteOutput::new(writer); tpl.render(self, ctx, &mut render_context, &mut out) } /// Render a template string using current registry without registering it pub fn render_template_to_write( &self, template_string: &str, data: &T, writer: W, ) -> Result<(), RenderError> where T: Serialize, W: Write, { let ctx = Context::wraps(data)?; self.render_template_with_context_to_write(template_string, &ctx, writer) } #[cfg(feature = "string_helpers")] #[inline] fn register_string_helpers(&mut self) { use helpers::string_helpers::{ kebab_case, lower_camel_case, shouty_kebab_case, shouty_snake_case, snake_case, title_case, train_case, upper_camel_case, }; self.register_helper("lowerCamelCase", Box::new(lower_camel_case)); self.register_helper("upperCamelCase", Box::new(upper_camel_case)); self.register_helper("snakeCase", Box::new(snake_case)); self.register_helper("kebabCase", Box::new(kebab_case)); self.register_helper("shoutySnakeCase", Box::new(shouty_snake_case)); self.register_helper("shoutyKebabCase", Box::new(shouty_kebab_case)); self.register_helper("titleCase", Box::new(title_case)); self.register_helper("trainCase", Box::new(train_case)); } } #[cfg(test)] mod test { use crate::context::Context; use crate::error::{RenderError, RenderErrorReason}; 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<'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", "

").is_ok()); let tpl = Template::compile("

").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 #[cfg(feature = "string_helpers")] let string_helpers = 8; #[cfg(not(feature = "string_helpers"))] let string_helpers = 0; assert_eq!( r.helpers.len(), num_helpers + num_boolean_helpers + num_custom_helpers + string_helpers ); } #[test] #[cfg(feature = "dir_source")] fn test_register_templates_directory() { use std::fs::DirBuilder; use crate::registry::DirectorySourceOptions; 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, "

Hello {{world}}!

").unwrap(); let file2_path = dir.path().join("t2.hbs"); let mut file2: File = File::create(&file2_path).unwrap(); writeln!(file2, "

Hola {{world}}!

").unwrap(); let file3_path = dir.path().join("t3.hbs"); let mut file3: File = File::create(&file3_path).unwrap(); writeln!(file3, "

Hallo {{world}}!

").unwrap(); let file4_path = dir.path().join(".t4.hbs"); let mut file4: File = File::create(&file4_path).unwrap(); writeln!(file4, "

Hallo {{world}}!

").unwrap(); r.register_templates_directory(dir.path(), DirectorySourceOptions::default()) .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, "

Hello {{world}}!

").unwrap(); let file2_path = dir.path().join("t5.erb"); let mut file2: File = File::create(&file2_path).unwrap(); writeln!(file2, "

Hello {{% world %}}!

").unwrap(); let file3_path = dir.path().join("t6.html"); let mut file3: File = File::create(&file3_path).unwrap(); writeln!(file3, "

Hello world!

").unwrap(); r.register_templates_directory(dir.path(), DirectorySourceOptions::default()) .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, "

Bonjour {{world}}!

").unwrap(); let file2_path = dir.path().join("portugese/t8.hbs"); let mut file2: File = File::create(&file2_path).unwrap(); writeln!(file2, "

Ola {{world}}!

").unwrap(); let file3_path = dir.path().join("italian/t9.hbs"); let mut file3: File = File::create(&file3_path).unwrap(); writeln!(file3, "

Ciao {{world}}!

").unwrap(); r.register_templates_directory(dir.path(), DirectorySourceOptions::default()) .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, "

Bonjour {{world}}!

").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(dir_path, DirectorySourceOptions::default()) .unwrap(); assert_eq!(r.templates.len(), 8); assert_eq!(r.templates.contains_key("t10"), true); drop(file1); dir.close().unwrap(); } { let dir = tempdir().unwrap(); let mut r = Registry::new(); let file1_path = dir.path().join("t11.hbs.html"); let mut file1: File = File::create(&file1_path).unwrap(); writeln!(file1, "

Bonjour {{world}}!

").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( dir_path, DirectorySourceOptions { tpl_extension: ".hbs.html".to_owned(), ..Default::default() }, ) .unwrap(); assert_eq!(r.templates.len(), 1); assert_eq!(r.templates.contains_key("t11"), true); drop(file1); dir.close().unwrap(); } { let dir = tempdir().unwrap(); let mut r = Registry::new(); assert_eq!(r.templates.len(), 0); let file1_path = dir.path().join(".t12.hbs"); let mut file1: File = File::create(&file1_path).unwrap(); writeln!(file1, "

Hello {{world}}!

").unwrap(); r.register_templates_directory( dir.path(), DirectorySourceOptions { hidden: true, ..Default::default() }, ) .unwrap(); assert_eq!(r.templates.len(), 1); assert_eq!(r.templates.contains_key(".t12"), true); drop(file1); dir.close().unwrap(); } } #[test] fn test_render_to_write() { let mut r = Registry::new(); assert!(r.register_template_string("index", "

").is_ok()); let mut sw = StringWriter::new(); { r.render_to_write("index", &(), &mut sw).ok().unwrap(); } assert_eq!("

".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); assert_eq!( match render_error.reason() { RenderErrorReason::MissingVariable(path) => path.as_ref().unwrap(), _ => unreachable!(), }, "the_key_never_exists" ); 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); assert_eq!( match render_error2.reason() { RenderErrorReason::MissingVariable(path) => path.as_ref().unwrap(), _ => unreachable!(), }, "this.[3]" ) } use crate::json::value::ScopedJson; struct GenMissingHelper; impl HelperDef for GenMissingHelper { fn call_inner<'reg: 'rc, 'rc>( &self, _: &Helper<'rc>, _: &'reg Registry<'reg>, _: &'rc Context, _: &mut RenderContext<'reg, 'rc>, ) -> Result, 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": "bold"})) .unwrap(), "bold" ); assert_eq!( reg.render_template("{{ &a }}", &json!({"a": "bold"})) .unwrap(), "bold" ); } #[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::from(data)).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, "

Hello {{{{name}}}}!

").unwrap(); } reg.register_template_file("t1", &file1_path).unwrap(); assert_eq!( reg.render("t1", &json!({"name": "Alex"})).unwrap(), "

Hello Alex!

" ); { let mut file1: File = File::create(&file1_path).unwrap(); write!(file1, "

Privet {{{{name}}}}!

").unwrap(); } assert_eq!( reg.render("t1", &json!({"name": "Alex"})).unwrap(), "

Privet Alex!

" ); 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(); } #[test] #[cfg(feature = "script_helper")] fn test_engine_access() { use rhai::Engine; let mut registry = Registry::new(); let mut eng = Engine::new(); eng.set_max_string_size(1000); registry.set_engine(eng); assert_eq!(1000, registry.engine().max_string_size()); } } handlebars-5.1.2/src/render.rs000064400000000000000000001136601046102023000143570ustar 00000000000000use 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::registry::Registry; use crate::support; use crate::template::TemplateElement::*; use crate::template::{ BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement, TemplateMapping, }; use crate::{partial, RenderErrorReason}; 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>, blocks: VecDeque>, // copy-on-write context modified_context: Option>, } #[derive(Clone)] pub struct RenderContextInner<'reg: 'rc, 'rc> { partials: BTreeMap, partial_block_stack: VecDeque<&'reg Template>, partial_block_depth: isize, local_helpers: BTreeMap>, /// current template name current_template: Option<&'rc String>, /// root template name root_template: Option<&'reg String>, disable_escape: bool, indent_string: Option<&'reg String>, } impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> { /// Create a render context 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, indent_string: None, }); let mut blocks = VecDeque::with_capacity(5); blocks.push_front(BlockContext::new()); let modified_context = None; RenderContext { inner, blocks, modified_context, } } 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<'rc>) { 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(); } pub(crate) fn clear_blocks(&mut self) { self.blocks.clear(); } /// Borrow a reference to current block context pub fn block(&self) -> Option<&BlockContext<'rc>> { 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<'rc>> { 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> { 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, RenderError> { let path = Path::parse(relative_path)?; self.evaluate2(context, &path) } pub(crate) fn evaluate2( &self, context: &'rc Context, path: &Path, ) -> Result, 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: &'rc 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) { let depth = &mut self.inner_mut().partial_block_depth; if *depth > 0 { *depth -= 1; } } pub(crate) fn set_indent_string(&mut self, indent: Option<&'reg String>) { self.inner_mut().indent_string = indent; } #[inline] pub(crate) fn get_indent_string(&self) -> Option<&'reg String> { self.inner.indent_string } /// 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, ) { 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> { 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<&'rc String> { self.inner().current_template } /// Set the current template name. pub fn set_current_template_name(&mut self, name: Option<&'rc 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("partial_block_depth", &self.partial_block_depth) .field("root_template", &self.root_template) .field("current_template", &self.current_template) .field("disable_escape", &self.disable_escape) .finish() } } /// Render-time Helper data when using in a helper definition #[derive(Debug, Clone)] pub struct Helper<'rc> { name: Cow<'rc, str>, params: Vec>, hash: BTreeMap<&'rc str, PathAndJson<'rc>>, template: Option<&'rc Template>, inverse: Option<&'rc Template>, block_param: Option<&'rc BlockParam>, block: bool, } impl<'reg: 'rc, 'rc> Helper<'rc> { fn try_from_template( ht: &'rc HelperTemplate, registry: &'reg Registry<'reg>, context: &'rc Context, render_context: &mut RenderContext<'reg, 'rc>, ) -> Result, 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> { &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(RenderErrorReason::ParamNotFoundForIndex("myhelper", 0)); /// // .. /// Ok(()) /// } /// ``` pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> { self.params.get(idx) } /// Returns hash, resolved within the context pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'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(RenderErrorReason::ParamNotFoundForIndex("my_helper", 0)); /// // .. /// Ok(()) /// } /// ``` pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'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<&'rc Template> { self.template } /// Returns the template of `else` branch if any pub fn inverse(&self) -> Option<&'rc 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<&'rc 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<(&'rc str, &'rc 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<'rc> { name: Cow<'rc, str>, params: Vec>, hash: BTreeMap<&'rc str, PathAndJson<'rc>>, template: Option<&'rc Template>, indent: Option<&'rc String>, } impl<'reg: 'rc, 'rc> Decorator<'rc> { fn try_from_template( dt: &'rc DecoratorTemplate, registry: &'reg Registry<'reg>, context: &'rc Context, render_context: &mut RenderContext<'reg, 'rc>, ) -> Result, 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(), indent: dt.indent.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> { &self.params } /// Returns nth helper param, resolved within the context pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> { self.params.get(idx) } /// Returns hash, resolved within the context pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> { &self.hash } /// Return hash value of a given key, resolved within the context pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> { self.hash.get(key) } /// Returns the default inner template if any pub fn template(&self) -> Option<&'rc Template> { self.template } pub fn indent(&self) -> Option<&'rc String> { self.indent } } /// Render trait pub trait Renderable { /// render into RenderContext's `writer` fn render<'reg: 'rc, 'rc>( &'rc 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>( &'rc self, registry: &'reg Registry<'reg>, ctx: &'rc Context, rc: &mut RenderContext<'reg, 'rc>, ) -> Result { let mut so = StringOutput::new(); self.render(registry, ctx, rc, &mut so)?; so.into_string() .map_err(|e| RenderErrorReason::from(e).into()) } } /// Evaluate decorator pub trait Evaluable { fn eval<'reg: 'rc, 'rc>( &'rc 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<'rc>, r: &'reg Registry<'reg>, ctx: &'rc Context, rc: &mut RenderContext<'reg, 'rc>, ) -> Result, 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>( &'rc self, registry: &'reg Registry<'reg>, ctx: &'rc Context, rc: &mut RenderContext<'reg, 'rc>, ) -> Result, 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>( &'rc self, registry: &'reg Registry<'reg>, ctx: &'rc Context, rc: &mut RenderContext<'reg, 'rc>, ) -> Result, 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(|| { RenderErrorReason::HelperNotFound(name.to_string()).into() }) .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)) } } _ => unreachable!(), }, } } } impl Renderable for Template { fn render<'reg: 'rc, 'rc>( &'rc 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.clone_from(&self.name); } e })?; } Ok(()) } } impl Evaluable for Template { fn eval<'reg: 'rc, 'rc>( &'rc 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.clone_from(&self.name); 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: &'rc 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(|| RenderErrorReason::HelperNotFound(h.name().to_owned()).into()) .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 } } #[inline] fn indent_aware_write( v: &str, rc: &RenderContext<'_, '_>, out: &mut dyn Output, ) -> Result<(), RenderError> { if let Some(indent) = rc.get_indent_string() { out.write(support::str::with_indent(v, indent).as_ref())?; } else { out.write(v.as_ref())?; } Ok(()) } impl Renderable for TemplateElement { fn render<'reg: 'rc, 'rc>( &'rc self, registry: &'reg Registry<'reg>, ctx: &'rc Context, rc: &mut RenderContext<'reg, 'rc>, out: &mut dyn Output, ) -> Result<(), RenderError> { match self { RawString(ref v) => indent_aware_write(v.as_ref(), rc, out), 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); indent_aware_write(output.as_ref(), rc, out) } } } 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>( &'rc 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(RenderErrorReason::DecoratorNotFound(di.name().to_owned()).into()), } } _ => Ok(()), } } } #[cfg(test)] mod test { use std::collections::BTreeMap; use super::{Helper, RenderContext, Renderable}; use crate::block::BlockContext; use crate::context::Context; use crate::error::RenderError; use crate::json::path::Path; use crate::json::value::JsonRender; use crate::output::{Output, StringOutput}; use crate::registry::Registry; use crate::template::TemplateElement::*; use crate::template::{HelperTemplate, Template, TemplateElement}; #[test] fn test_raw_string() { let r = Registry::new(); let raw_string = RawString("

hello world

".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(), "

hello world

".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 = BTreeMap::new(); let value = "

".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 = 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 = BTreeMap::new(); let value = "world".to_string(); m.insert("hello".to_string(), value); let ctx = Context::wraps(&m).unwrap(); let elements: Vec = vec![ RawString("

".to_string()), Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths( &["hello"], )))), RawString("

".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(), "

world

".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(&h.param(0).unwrap().value().render()) .map(|_| ()) .map_err(RenderError::from) }, ), ); let mut sw = StringWriter::new(); let mut m: BTreeMap = 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 = BTreeMap::new(); let name = "invalid_template"; assert!(r .register_template_string(name, "

\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", "{{> layout}}") .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, "content"); } #[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(); write!(out, "{} not resolved", name)?; Ok(()) }, ), ); assert_eq!( r.render("t1", &json!({"name": "Alex"})).unwrap(), "Output name: first_name not resolved" ); } #[test] fn test_identifiers_starting_with_numbers() { let mut r = Registry::new(); assert!(r .register_template_string("r1", "{{#if 0a}}true{{/if}}") .is_ok()); let r1 = r.render("r1", &json!({"0a": true})).unwrap(); assert_eq!(r1, "true"); assert!(r.register_template_string("r2", "{{eq 1a 1}}").is_ok()); let r2 = r.render("r2", &json!({"1a": 2, "a": 1})).unwrap(); assert_eq!(r2, "false"); assert!(r .register_template_string("r3", "0: {{0}} {{#if (eq 0 true)}}resolved from context{{/if}}\n1a: {{1a}} {{#if (eq 1a true)}}resolved from context{{/if}}\n2_2: {{2_2}} {{#if (eq 2_2 true)}}resolved from context{{/if}}") // YUP it is just eq that barfs! is if handled specially? maybe this test should go nearer to specific helpers that fail? .is_ok()); let r3 = r .render("r3", &json!({"0": true, "1a": true, "2_2": true})) .unwrap(); assert_eq!( r3, "0: true \n1a: true resolved from context\n2_2: true resolved from context" ); // these should all be errors: assert!(r.register_template_string("r4", "{{eq 1}}").is_ok()); assert!(r.register_template_string("r5", "{{eq a1}}").is_ok()); assert!(r.register_template_string("r6", "{{eq 1a}}").is_ok()); assert!(r.render("r4", &()).is_err()); assert!(r.render("r5", &()).is_err()); assert!(r.render("r6", &()).is_err()); } } handlebars-5.1.2/src/sources.rs000064400000000000000000000022121046102023000145510ustar 00000000000000use std::fs::File; use std::io::{BufReader, Error as IOError, ErrorKind, Read}; use std::path::PathBuf; pub(crate) trait Source { type Item; type Error; fn load(&self) -> Result; } 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 { let mut reader = BufReader::new(File::open(&self.path)?); let mut buf = String::new(); reader.read_to_string(&mut buf)?; Ok(buf) } } pub(crate) struct LazySource Option> { loader: F, } impl Option> LazySource { pub(crate) fn new(loader: F) -> LazySource { LazySource { loader } } } impl Option> Source for LazySource { type Item = String; type Error = IOError; fn load(&self) -> Result { (self.loader)().ok_or(IOError::new(ErrorKind::Other, "Source load error")) } } handlebars-5.1.2/src/support.rs000064400000000000000000000070711046102023000146120ustar 00000000000000pub mod str { use std::io::{Result, Write}; #[derive(Debug)] pub struct StringWriter { buf: Vec, } 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 { 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 } /// add indent for lines but last pub fn with_indent(s: &str, indent: &str) -> String { let mut output = String::new(); let mut it = s.chars().peekable(); while let Some(c) = it.next() { output.push(c); // check if c is not the last character, we don't append // indent for last line break if c == '\n' && it.peek().is_some() { output.push_str(indent); } } output } #[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' } #[inline] pub(crate) fn strip_first_newline(s: &str) -> &str { if let Some(s) = s.strip_prefix("\r\n") { s } else if let Some(s) = s.strip_prefix('\n') { s } else { s } } pub(crate) fn find_trailing_whitespace_chars(s: &str) -> Option<&str> { let trimmed = s.trim_end_matches(whitespace_matcher); if trimmed.len() == s.len() { None } else { Some(&s[trimmed.len()..]) } } pub(crate) fn ends_with_empty_line(text: &str) -> bool { let s = text.trim_end_matches(whitespace_matcher); // also matches when text is just whitespaces s.ends_with(newline_matcher) || s.is_empty() } 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 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()); } } } handlebars-5.1.2/src/template.rs000064400000000000000000001501551046102023000147130ustar 00000000000000use std::collections::{HashMap, VecDeque}; 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::{HandlebarsParser, Rule}; use crate::json::path::{parse_json_path_from_iter, Path}; use crate::support; use self::TemplateElement::*; #[derive(PartialEq, Eq, Clone, Debug)] pub struct TemplateMapping(pub usize, pub usize); /// A handlebars template #[derive(PartialEq, Eq, Clone, Debug, Default)] pub struct Template { pub name: Option, pub elements: Vec, pub mapping: Vec, } #[derive(Default)] pub(crate) struct TemplateOptions { pub(crate) prevent_indent: bool, pub(crate) name: Option, } impl TemplateOptions { fn name(&self) -> String { self.name .as_ref() .cloned() .unwrap_or_else(|| "Unnamed".to_owned()) } } #[derive(PartialEq, Eq, Clone, Debug)] pub struct Subexpression { // we use box here avoid resursive struct definition pub element: Box, } impl Subexpression { pub fn new( name: Parameter, params: Vec, hash: HashMap, ) -> Subexpression { Subexpression { element: Box::new(Expression(Box::new(HelperTemplate { name, params, hash, template: None, inverse: None, block_param: None, block: false, chain: 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> { match *self.as_element() { Expression(ref ht) => Some(&ht.params), _ => None, } } pub fn hash(&self) -> Option<&HashMap> { match *self.as_element() { Expression(ref ht) => Some(&ht.hash), _ => None, } } } #[derive(PartialEq, Eq, Clone, Debug)] pub enum BlockParam { Single(Parameter), Pair((Parameter, Parameter)), } #[derive(PartialEq, Eq, Clone, Debug)] pub struct ExpressionSpec { pub name: Parameter, pub params: Vec, pub hash: HashMap, pub block_param: Option, pub omit_pre_ws: bool, pub omit_pro_ws: bool, } #[derive(PartialEq, Eq, 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, Eq, Clone, Debug)] pub struct HelperTemplate { pub name: Parameter, pub params: Vec, pub hash: HashMap, pub block_param: Option, pub template: Option