diff options
author | Elliott Hughes <enh@google.com> | 2021-04-09 17:54:22 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-04-09 17:54:22 +0000 |
commit | db6c0dddd5e828f9e30fd4dbe84872c0190faf64 (patch) | |
tree | 410da5262bed94fc3bb0595021f9a25c4ef3b798 | |
parent | 754b692aba854c2f4395ced0e2f70bdfafcd6982 (diff) | |
parent | 9be83c451531ccfc5d43c28905bd656119bc4178 (diff) | |
download | tinytemplate-android12L-d2-s8-release.tar.gz |
Upgrade rust/crates/tinytemplate to 1.2.1 am: a3bdae3ac7 am: 2043cb5371 am: 9be83c4515android-12.1.0_r9android-12.1.0_r8android-12.1.0_r7android-12.1.0_r26android-12.1.0_r25android-12.1.0_r24android-12.1.0_r23android-12.1.0_r22android-12.1.0_r21android-12.1.0_r20android-12.1.0_r19android-12.1.0_r18android-12.1.0_r17android-12.1.0_r16android-12.1.0_r15android-12.1.0_r14android-12.1.0_r13android-12.1.0_r12android-12.1.0_r11android-12.1.0_r10android-12.0.0_r32android-12.0.0_r29android-12.0.0_r28android-12.0.0_r27android-12.0.0_r26android-12.0.0_r21android-12.0.0_r20android-12.0.0_r19android-12.0.0_r18android-12.0.0_r16android12L-devandroid12L-d2-s8-releaseandroid12L-d2-s7-releaseandroid12L-d2-s6-releaseandroid12L-d2-s5-releaseandroid12L-d2-s4-releaseandroid12L-d2-s3-releaseandroid12L-d2-s2-releaseandroid12L-d2-s1-releaseandroid12L-d2-releaseandroid12-qpr3-s7-releaseandroid12-qpr3-s6-releaseandroid12-qpr3-s5-releaseandroid12-qpr3-s4-releaseandroid12-qpr3-s3-releaseandroid12-qpr3-s2-releaseandroid12-qpr3-s1-releaseandroid12-qpr3-releaseandroid12-qpr1-releaseandroid12-qpr1-d-s3-releaseandroid12-qpr1-d-s2-releaseandroid12-qpr1-d-s1-releaseandroid12-qpr1-d-releaseandroid12-dev
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/tinytemplate/+/1662429
Change-Id: I80fbf2c40976c5524a1e2adf1c56cbdf08c02766
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rwxr-xr-x | .github/workflows/ci.yml | 47 | ||||
-rwxr-xr-x | .travis.yml | 36 | ||||
-rw-r--r-- | Android.bp | 2 | ||||
-rwxr-xr-x | CHANGELOG.md | 14 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rwxr-xr-x | Cargo.toml.orig | 2 | ||||
-rw-r--r-- | METADATA | 10 | ||||
-rwxr-xr-x | README.md | 15 | ||||
-rwxr-xr-x | ci/install.sh | 9 | ||||
-rwxr-xr-x | ci/script.sh | 9 | ||||
-rwxr-xr-x | src/compiler.rs | 85 | ||||
-rwxr-xr-x | src/error.rs | 46 | ||||
-rwxr-xr-x | src/instruction.rs | 23 | ||||
-rwxr-xr-x | src/template.rs | 118 |
15 files changed, 304 insertions, 116 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 97e8581..12cd342 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,5 +1,5 @@ { "git": { - "sha1": "3833799e58db60b87ebfe5a1e2dbe4fc0729621a" + "sha1": "522605e72e04dbd6ededa29743a485d2fad29dd4" } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100755 index 0000000..287bc4e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +on: [push, pull_request]
+
+name: Continuous integration
+
+jobs:
+ ci:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ rust:
+ - stable
+ - beta
+ - nightly
+ - 1.36.0 # MSRV
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: actions/cache@v2
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: ${{ runner.os }}-cargo-${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }}
+
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: ${{ matrix.rust }}
+ override: true
+ components: rustfmt, clippy
+
+ - run: cargo test
+
+ - if: ${{ matrix.rust == 'stable' }}
+ run: cargo fmt --all -- --check
+
+ - if: ${{ matrix.rust == 'stable' }}
+ run: cargo clippy --all -- -D warnings
+
+ # Delete things that shouldn't be cached
+ - run: find ./target/debug -maxdepth 1 -type f -delete
+ - run: rm -rf ./target/debug/deps/tinytemplate*
+ - run: rm -rf ./target/debug/.fingerprint/tinytemplate*
+ - run: rm -f ./target/.rustc_info.json
+ - run: rm -rf ~/.cargo/registry/index/
diff --git a/.travis.yml b/.travis.yml deleted file mode 100755 index acd6984..0000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -sudo: true - -language: rust - -cache: cargo - -dist: xenial - -rust: - - stable - -os: - - linux - -matrix: - include: - - os: linux - - os: linux - rust: 1.36.0 - - os: linux - env: RUSTFMT=yes - - os: linux - env: CLIPPY=yes - -install: - - bash ci/install.sh - -script: - - bash ci/script.sh - -branches: - only: master - -notifications: - email: - on_success: never @@ -52,5 +52,5 @@ rust_library { // dependent_library ["feature_list"] // itoa-0.4.7 "default,std" // ryu-1.0.5 -// serde-1.0.124 "default,std" +// serde-1.0.125 "default,std" // serde_json-1.0.64 "default,std" diff --git a/CHANGELOG.md b/CHANGELOG.md index b02a229..2c757ac 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [1.2.1] - 2021-03-03 +### Fixed +- Fixed a compile error on some nightly compiler versions. + +## [1.2.0] - 2020-01-03 +### Fixed + - Fixed numeric values being truthy when zero, rather than when non-zero. (For real this time) +### Added + - Allow numeric indexes to be used in paths, to index into JSON arrays. + ## [1.1.0] - 2020-05-31 - Added `TinyTemplate::set_default_formatter` which, for example, allows to dissable HTML-scaping @@ -31,9 +41,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Initial release on Crates.io. -[Unreleased]: https://github.com/bheisler/TinyTemplate/compare/1.0.3...HEAD +[Unreleased]: https://github.com/bheisler/TinyTemplate/compare/1.2.0...HEAD [1.0.1]: https://github.com/bheisler/TinyTemplate/compare/1.0.0...1.0.1 [1.0.2]: https://github.com/bheisler/TinyTemplate/compare/1.0.1...1.0.2 [1.0.3]: https://github.com/bheisler/TinyTemplate/compare/1.0.2...1.0.3 [1.0.4]: https://github.com/bheisler/TinyTemplate/compare/1.0.3...1.0.4 [1.1.0]: https://github.com/bheisler/TinyTemplate/compare/1.0.4...1.1.0 +[1.2.0]: https://github.com/bheisler/TinyTemplate/compare/1.1.0...1.2.0 +[1.2.1]: https://github.com/bheisler/TinyTemplate/compare/1.2.0...1.2.1 @@ -12,7 +12,7 @@ [package] name = "tinytemplate" -version = "1.1.0" +version = "1.2.1" authors = ["Brook Heisler <brookheisler@gmail.com>"] description = "Simple, lightweight template engine" readme = "README.md" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 2c48a14..d69a280 100755 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "tinytemplate" -version = "1.1.0" +version = "1.2.1" authors = ["Brook Heisler <brookheisler@gmail.com>"] description = "Simple, lightweight template engine" @@ -7,13 +7,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/tinytemplate/tinytemplate-1.1.0.crate" + value: "https://static.crates.io/crates/tinytemplate/tinytemplate-1.2.1.crate" } - version: "1.1.0" + version: "1.2.1" license_type: NOTICE last_upgrade_date { - year: 2020 - month: 12 - day: 21 + year: 2021 + month: 4 + day: 2 } } @@ -9,12 +9,11 @@ </div>
<div align="center">
- <a href="https://travis-ci.org/bheisler/TinyTemplate">
- <img src="https://travis-ci.org/bheisler/TinyTemplate.svg?branch=master" alt="Travis-CI">
+ <a href="https://github.com/bheisler/TinyTemplate/actions">
+ <img src="https://github.com/bheisler/TinyTemplate/workflows/Continuous%20integration/badge.svg" alt="Continuous integration">
</a>
- |
<a href="https://crates.io/crates/tinytemplate">
- <img src="https://img.shields.io/crates/v/tinytemplate.svg" alt=Crates.io">
+ <img src="https://img.shields.io/crates/v/tinytemplate.svg" alt="Crates.io">
</a>
</div>
@@ -63,16 +62,14 @@ First, add TinyTemplate and serde-derive to your `Cargo.toml` file: ```toml
[dependencies]
-tinytemplate = "1.0"
-serde_derive = "1.0"
+tinytemplate = "1.1"
+serde = { version = "1.0", features = ["derive"] }
```
Then add this code to "src.rs":
```rust
-#[macro_use]
-extern crate serde_derive;
-extern crate tinytemplate;
+use serde::Serialize;
use tinytemplate::TinyTemplate;
use std::error::Error;
diff --git a/ci/install.sh b/ci/install.sh deleted file mode 100755 index 56bb887..0000000 --- a/ci/install.sh +++ /dev/null @@ -1,9 +0,0 @@ -set -ex - -if [ "$RUSTFMT" = "yes" ]; then - rustup component add rustfmt-preview -fi - -if [ "$CLIPPY" = "yes" ]; then - rustup component add clippy-preview -fi
\ No newline at end of file diff --git a/ci/script.sh b/ci/script.sh deleted file mode 100755 index 996013c..0000000 --- a/ci/script.sh +++ /dev/null @@ -1,9 +0,0 @@ -set -ex - -if [ "$RUSTFMT" = "yes" ]; then - cargo fmt --all -- --check -elif [ "$CLIPPY" = "yes" ]; then - cargo clippy --all -- -D warnings -else - cargo test -fi
\ No newline at end of file diff --git a/src/compiler.rs b/src/compiler.rs index 5b7e721..df37947 100755 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -6,7 +6,7 @@ /// template strings and generating the appropriate bytecode instructions.
use error::Error::*;
use error::{get_offset, Error, Result};
-use instruction::{Instruction, Path};
+use instruction::{Instruction, Path, PathStep};
/// The end point of a branch or goto instruction is not known.
const UNKNOWN: usize = ::std::usize::MAX;
@@ -185,9 +185,15 @@ impl<'template> TemplateCompiler<'template> { /// context.
fn parse_path(&self, text: &'template str) -> Result<Path<'template>> {
if !text.starts_with('@') {
- Ok(text.split('.').collect::<Vec<_>>())
+ Ok(text
+ .split('.')
+ .map(|s| match s.parse::<usize>() {
+ Ok(n) => PathStep::Index(s, n),
+ Err(_) => PathStep::Name(s),
+ })
+ .collect::<Vec<_>>())
} else if KNOWN_KEYWORDS.iter().any(|k| *k == text) {
- Ok(vec![text])
+ Ok(vec![PathStep::Name(text)])
} else {
Err(self.parse_error(text, format!("Invalid keyword name '{}'", text)))
}
@@ -432,7 +438,7 @@ mod test { let text = "{ foobar }";
let instructions = compile(text).unwrap();
assert_eq!(1, instructions.len());
- assert_eq!(&Value(vec!["foobar"]), &instructions[0]);
+ assert_eq!(&Value(vec![PathStep::Name("foobar")]), &instructions[0]);
}
#[test]
@@ -441,7 +447,7 @@ mod test { let instructions = compile(text).unwrap();
assert_eq!(1, instructions.len());
assert_eq!(
- &FormattedValue(vec!["foobar"], "my_formatter"),
+ &FormattedValue(vec![PathStep::Name("foobar")], "my_formatter"),
&instructions[0]
);
}
@@ -451,7 +457,25 @@ mod test { let text = "{ foo.bar }";
let instructions = compile(text).unwrap();
assert_eq!(1, instructions.len());
- assert_eq!(&Value(vec!["foo", "bar"]), &instructions[0]);
+ assert_eq!(
+ &Value(vec![PathStep::Name("foo"), PathStep::Name("bar")]),
+ &instructions[0]
+ );
+ }
+
+ #[test]
+ fn test_indexed_path() {
+ let text = "{ foo.0.bar }";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(
+ &Value(vec![
+ PathStep::Name("foo"),
+ PathStep::Index("0", 0),
+ PathStep::Name("bar")
+ ]),
+ &instructions[0]
+ );
}
#[test]
@@ -460,7 +484,7 @@ mod test { let instructions = compile(text).unwrap();
assert_eq!(3, instructions.len());
assert_eq!(&Literal("Hello "), &instructions[0]);
- assert_eq!(&Value(vec!["name"]), &instructions[1]);
+ assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[1]);
assert_eq!(&Literal(", how are you?"), &instructions[2]);
}
@@ -469,7 +493,10 @@ mod test { let text = "{{ if foo }}Hello!{{ endif }}";
let instructions = compile(text).unwrap();
assert_eq!(2, instructions.len());
- assert_eq!(&Branch(vec!["foo"], true, 2), &instructions[0]);
+ assert_eq!(
+ &Branch(vec![PathStep::Name("foo")], true, 2),
+ &instructions[0]
+ );
assert_eq!(&Literal("Hello!"), &instructions[1]);
}
@@ -478,7 +505,10 @@ mod test { let text = "{{ if not foo }}Hello!{{ endif }}";
let instructions = compile(text).unwrap();
assert_eq!(2, instructions.len());
- assert_eq!(&Branch(vec!["foo"], false, 2), &instructions[0]);
+ assert_eq!(
+ &Branch(vec![PathStep::Name("foo")], false, 2),
+ &instructions[0]
+ );
assert_eq!(&Literal("Hello!"), &instructions[1]);
}
@@ -487,7 +517,10 @@ mod test { let text = "{{ if foo }}Hello!{{ else }}Goodbye!{{ endif }}";
let instructions = compile(text).unwrap();
assert_eq!(4, instructions.len());
- assert_eq!(&Branch(vec!["foo"], true, 3), &instructions[0]);
+ assert_eq!(
+ &Branch(vec![PathStep::Name("foo")], true, 3),
+ &instructions[0]
+ );
assert_eq!(&Literal("Hello!"), &instructions[1]);
assert_eq!(&Goto(4), &instructions[2]);
assert_eq!(&Literal("Goodbye!"), &instructions[3]);
@@ -498,7 +531,10 @@ mod test { let text = "{{ with foo as bar }}Hello!{{ endwith }}";
let instructions = compile(text).unwrap();
assert_eq!(3, instructions.len());
- assert_eq!(&PushNamedContext(vec!["foo"], "bar"), &instructions[0]);
+ assert_eq!(
+ &PushNamedContext(vec![PathStep::Name("foo")], "bar"),
+ &instructions[0]
+ );
assert_eq!(&Literal("Hello!"), &instructions[1]);
assert_eq!(&PopContext, &instructions[2]);
}
@@ -509,11 +545,11 @@ mod test { let instructions = compile(text).unwrap();
assert_eq!(5, instructions.len());
assert_eq!(
- &PushIterationContext(vec!["bar", "baz"], "foo"),
+ &PushIterationContext(vec![PathStep::Name("bar"), PathStep::Name("baz")], "foo"),
&instructions[0]
);
assert_eq!(&Iterate(4), &instructions[1]);
- assert_eq!(&Value(vec!["foo"]), &instructions[2]);
+ assert_eq!(&Value(vec![PathStep::Name("foo")]), &instructions[2]);
assert_eq!(&Goto(1), &instructions[3]);
assert_eq!(&PopContext, &instructions[4]);
}
@@ -524,7 +560,7 @@ mod test { let instructions = compile(text).unwrap();
assert_eq!(3, instructions.len());
assert_eq!(&Literal("Hello,"), &instructions[0]);
- assert_eq!(&Value(vec!["name"]), &instructions[1]);
+ assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[1]);
assert_eq!(&Literal(", how are you?"), &instructions[2]);
}
@@ -534,9 +570,12 @@ mod test { let instructions = compile(text).unwrap();
assert_eq!(6, instructions.len());
assert_eq!(&Literal("Hello,"), &instructions[0]);
- assert_eq!(&Branch(vec!["name"], true, 5), &instructions[1]);
+ assert_eq!(
+ &Branch(vec![PathStep::Name("name")], true, 5),
+ &instructions[1]
+ );
assert_eq!(&Literal(""), &instructions[2]);
- assert_eq!(&Value(vec!["name"]), &instructions[3]);
+ assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[3]);
assert_eq!(&Literal(""), &instructions[4]);
assert_eq!(&Literal(", how are you?"), &instructions[5]);
}
@@ -564,8 +603,8 @@ mod test { let text = "{value -}{value} Hello";
let instructions = compile(text).unwrap();
assert_eq!(3, instructions.len());
- assert_eq!(&Value(vec!["value"]), &instructions[0]);
- assert_eq!(&Value(vec!["value"]), &instructions[1]);
+ assert_eq!(&Value(vec![PathStep::Name("value")]), &instructions[0]);
+ assert_eq!(&Value(vec![PathStep::Name("value")]), &instructions[1]);
assert_eq!(&Literal(" Hello"), &instructions[2]);
}
@@ -574,7 +613,13 @@ mod test { let text = "{{ call my_macro with foo.bar }}";
let instructions = compile(text).unwrap();
assert_eq!(1, instructions.len());
- assert_eq!(&Call("my_macro", vec!["foo", "bar"]), &instructions[0]);
+ assert_eq!(
+ &Call(
+ "my_macro",
+ vec![PathStep::Name("foo"), PathStep::Name("bar")]
+ ),
+ &instructions[0]
+ );
}
#[test]
@@ -584,7 +629,7 @@ mod test { assert_eq!(4, instructions.len());
assert_eq!(&Literal("body "), &instructions[0]);
assert_eq!(&Literal("{ \nfont-size: "), &instructions[1]);
- assert_eq!(&Value(vec!["fontsize"]), &instructions[2]);
+ assert_eq!(&Value(vec![PathStep::Name("fontsize")]), &instructions[2]);
assert_eq!(&Literal(" \n}"), &instructions[3]);
}
diff --git a/src/error.rs b/src/error.rs index 92f4890..730c648 100755 --- a/src/error.rs +++ b/src/error.rs @@ -41,7 +41,7 @@ pub enum Error { column: usize,
},
- #[doc(Hidden)]
+ #[doc(hidden)]
__NonExhaustive,
}
impl From<SerdeJsonError> for Error {
@@ -57,24 +57,50 @@ impl From<fmt::Error> for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
- Error::ParseError { msg, line, column } => write!(f, "Failed to parse the template (line {}, column {}). Reason: {}", line, column, msg),
+ Error::ParseError { msg, line, column } => write!(
+ f,
+ "Failed to parse the template (line {}, column {}). Reason: {}",
+ line, column, msg
+ ),
Error::RenderError { msg, line, column } => {
- write!(f, "Encountered rendering error on line {}, column {}. Reason: {}", line, column, msg)
+ write!(
+ f,
+ "Encountered rendering error on line {}, column {}. Reason: {}",
+ line, column, msg
+ )
}
- Error::SerdeError{ err } => {
+ Error::SerdeError { err } => {
write!(f, "Unexpected serde error while converting the context to a serde_json::Value. Error: {}", err)
}
Error::GenericError { msg } => {
write!(f, "{}", msg)
}
- Error::StdFormatError{ err } => {
- write!(f, "Unexpected formatting error: {}", err )
+ Error::StdFormatError { err } => {
+ write!(f, "Unexpected formatting error: {}", err)
}
- Error::CalledTemplateError{ name, err, line, column } => {
- write!(f, "Call to sub-template \"{}\" on line {}, column {} failed. Reason: {}", name, line, column, err)
+ Error::CalledTemplateError {
+ name,
+ err,
+ line,
+ column,
+ } => {
+ write!(
+ f,
+ "Call to sub-template \"{}\" on line {}, column {} failed. Reason: {}",
+ name, line, column, err
+ )
}
- Error::CalledFormatterError{ name, err, line, column } => {
- write!(f, "Call to value formatter \"{}\" on line {}, column {} failed. Reason: {}", name, line, column, err)
+ Error::CalledFormatterError {
+ name,
+ err,
+ line,
+ column,
+ } => {
+ write!(
+ f,
+ "Call to value formatter \"{}\" on line {}, column {} failed. Reason: {}",
+ name, line, column, err
+ )
}
Error::__NonExhaustive => unreachable!(),
}
diff --git a/src/instruction.rs b/src/instruction.rs index 9bb79e3..0e19814 100755 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -1,3 +1,5 @@ +use std::ops::Deref;
+
/// TinyTemplate implements a simple bytecode interpreter for its template engine. Instructions
/// for this interpreter are represented by the Instruction enum and typically contain various
/// parameters such as the path to context values or name strings.
@@ -7,11 +9,28 @@ /// slices from the template text. These string slices can then be appended directly to the output
/// string.
+/// Enum for a step in a path which optionally contains a parsed index.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub(crate) enum PathStep<'template> {
+ Name(&'template str),
+ Index(&'template str, usize),
+}
+impl<'template> Deref for PathStep<'template> {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ PathStep::Name(s) => s,
+ PathStep::Index(s, _) => s,
+ }
+ }
+}
+
/// Sequence of named steps used for looking up values in the context
-pub(crate) type Path<'template> = Vec<&'template str>;
+pub(crate) type Path<'template> = Vec<PathStep<'template>>;
/// Path, but as a slice.
-pub(crate) type PathSlice<'a, 'template> = &'a [&'template str];
+pub(crate) type PathSlice<'a, 'template> = &'a [PathStep<'template>];
/// Enum representing the bytecode instructions.
#[derive(Eq, PartialEq, Debug, Clone)]
diff --git a/src/template.rs b/src/template.rs index acc4b81..6f0162d 100755 --- a/src/template.rs +++ b/src/template.rs @@ -3,7 +3,7 @@ use compiler::TemplateCompiler; use error::Error::*; use error::*; -use instruction::{Instruction, PathSlice}; +use instruction::{Instruction, PathSlice, PathStep}; use serde_json::Value; use std::collections::HashMap; use std::fmt::Write; @@ -44,12 +44,12 @@ impl<'render, 'template> RenderContext<'render, 'template> { match stack_layer { ContextElement::Object(obj) => return self.lookup_in(path, obj), ContextElement::Named(name, obj) => { - if *name == path[0] { + if *name == &*path[0] { return self.lookup_in(&path[1..], obj); } } ContextElement::Iteration(name, obj, _, _, _) => { - if *name == path[0] { + if *name == &*path[0] { return self.lookup_in(&path[1..], obj); } } @@ -63,6 +63,15 @@ impl<'render, 'template> RenderContext<'render, 'template> { fn lookup_in(&self, path: PathSlice, object: &'render Value) -> Result<&'render Value> { let mut current = object; for step in path.iter() { + if let PathStep::Index(_, n) = step { + if let Some(next) = current.get(n) { + current = next; + continue; + } + } + + let step: &str = &*step; + match current.get(step) { Some(next) => current = next, None => return Err(lookup_error(self.original_text, step, path, current)), @@ -159,11 +168,12 @@ impl<'template> Template<'template> { program_counter += 1; } Instruction::Value(path) => { - let first = *path.first().unwrap(); + let first = path.first().unwrap(); if first.starts_with('@') { // Currently we just hard-code the special @-keywords and have special // lookup functions to use them because there are lifetime complexities with // looking up values that don't live for as long as the given context object. + let first: &str = &*first; match first { "@index" => { write!(output, "{}", render_context.lookup_index()?.0).unwrap() @@ -202,9 +212,10 @@ impl<'template> Template<'template> { program_counter += 1; } Instruction::Branch(path, negate, target) => { - let first = *path.first().unwrap(); + let first = path.first().unwrap(); let mut truthy = if first.starts_with('@') { - match first { + let first: &str = &*first; + match &*first { "@index" => render_context.lookup_index()?.0 != 0, "@first" => render_context.lookup_index()?.0 == 0, "@last" => { @@ -238,10 +249,10 @@ impl<'template> Template<'template> { Instruction::PushIterationContext(path, name) => { // We push a context with an invalid index and no value and then wait for the // following Iterate instruction to set the index and value properly. - let first = *path.first().unwrap(); + let first = path.first().unwrap(); let context_value = match first { - "@root" => render_context.lookup_root()?, - other if other.starts_with('@') => { + PathStep::Name("@root") => render_context.lookup_root()?, + PathStep::Name(other) if other.starts_with('@') => { return Err(not_iterable_error(self.original_text, path)) } _ => render_context.lookup(path)?, @@ -314,12 +325,12 @@ impl<'template> Template<'template> { Ok(()) } - fn value_is_truthy(&self, value: &Value, path: &[&str]) -> Result<bool> { + fn value_is_truthy(&self, value: &Value, path: PathSlice) -> Result<bool> { let truthy = match value { Value::Null => false, Value::Bool(b) => *b, Value::Number(n) => match n.as_f64() { - Some(float) => float == 0.0, + Some(float) => float != 0.0, None => { return Err(truthiness_error(self.original_text, path)); } @@ -845,4 +856,89 @@ mod test { .unwrap(); assert_eq!("foobar", &string); } + + #[test] + fn test_number_truthiness_zero() { + let template = compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}"); + let context = 0; + let context = ::serde_json::to_value(&context).unwrap(); + let template_registry = other_templates(); + let formatter_registry = formatters(); + let string = template + .render( + &context, + &template_registry, + &formatter_registry, + &default_formatter(), + ) + .unwrap(); + assert_eq!("not truthy", &string); + } + + #[test] + fn test_number_truthiness_one() { + let template = compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}"); + let context = 1; + let context = ::serde_json::to_value(&context).unwrap(); + let template_registry = other_templates(); + let formatter_registry = formatters(); + let string = template + .render( + &context, + &template_registry, + &formatter_registry, + &default_formatter(), + ) + .unwrap(); + assert_eq!("truthy", &string); + } + + #[test] + fn test_indexed_paths() { + #[derive(Serialize)] + struct Context { + foo: (usize, usize), + } + + let template = compile("{ foo.1 }{ foo.0 }"); + let context = Context { foo: (123, 456) }; + let context = ::serde_json::to_value(&context).unwrap(); + let template_registry = other_templates(); + let formatter_registry = formatters(); + let string = template + .render( + &context, + &template_registry, + &formatter_registry, + &default_formatter(), + ) + .unwrap(); + assert_eq!("456123", &string); + } + + #[test] + fn test_indexed_paths_fall_back_to_string_lookup() { + #[derive(Serialize)] + struct Context { + foo: HashMap<&'static str, usize>, + } + + let template = compile("{ foo.1 }{ foo.0 }"); + let mut foo = HashMap::new(); + foo.insert("0", 123); + foo.insert("1", 456); + let context = Context { foo }; + let context = ::serde_json::to_value(&context).unwrap(); + let template_registry = other_templates(); + let formatter_registry = formatters(); + let string = template + .render( + &context, + &template_registry, + &formatter_registry, + &default_formatter(), + ) + .unwrap(); + assert_eq!("456123", &string); + } } |