aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2021-04-09 18:34:42 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-04-09 18:34:42 +0000
commitdc83778ae30ab88216912b6417cffffc125bbb16 (patch)
tree410da5262bed94fc3bb0595021f9a25c4ef3b798
parentc237c0540db7a997ded1841bbc93a4706251b2bd (diff)
parentdb6c0dddd5e828f9e30fd4dbe84872c0190faf64 (diff)
downloadtinytemplate-dc83778ae30ab88216912b6417cffffc125bbb16.tar.gz
Upgrade rust/crates/tinytemplate to 1.2.1 am: a3bdae3ac7 am: 2043cb5371 am: 9be83c4515 am: db6c0dddd5android-mainline-12.0.0_r99android-mainline-12.0.0_r98android-mainline-12.0.0_r97android-mainline-12.0.0_r96android-mainline-12.0.0_r95android-mainline-12.0.0_r94android-mainline-12.0.0_r93android-mainline-12.0.0_r92android-mainline-12.0.0_r91android-mainline-12.0.0_r90android-mainline-12.0.0_r9android-mainline-12.0.0_r89android-mainline-12.0.0_r88android-mainline-12.0.0_r87android-mainline-12.0.0_r86android-mainline-12.0.0_r85android-mainline-12.0.0_r84android-mainline-12.0.0_r83android-mainline-12.0.0_r82android-mainline-12.0.0_r81android-mainline-12.0.0_r80android-mainline-12.0.0_r8android-mainline-12.0.0_r79android-mainline-12.0.0_r78android-mainline-12.0.0_r77android-mainline-12.0.0_r76android-mainline-12.0.0_r75android-mainline-12.0.0_r74android-mainline-12.0.0_r73android-mainline-12.0.0_r72android-mainline-12.0.0_r71android-mainline-12.0.0_r70android-mainline-12.0.0_r7android-mainline-12.0.0_r69android-mainline-12.0.0_r68android-mainline-12.0.0_r67android-mainline-12.0.0_r66android-mainline-12.0.0_r65android-mainline-12.0.0_r64android-mainline-12.0.0_r63android-mainline-12.0.0_r62android-mainline-12.0.0_r61android-mainline-12.0.0_r60android-mainline-12.0.0_r6android-mainline-12.0.0_r59android-mainline-12.0.0_r58android-mainline-12.0.0_r57android-mainline-12.0.0_r56android-mainline-12.0.0_r53android-mainline-12.0.0_r52android-mainline-12.0.0_r51android-mainline-12.0.0_r50android-mainline-12.0.0_r5android-mainline-12.0.0_r49android-mainline-12.0.0_r48android-mainline-12.0.0_r47android-mainline-12.0.0_r46android-mainline-12.0.0_r45android-mainline-12.0.0_r44android-mainline-12.0.0_r43android-mainline-12.0.0_r42android-mainline-12.0.0_r41android-mainline-12.0.0_r40android-mainline-12.0.0_r39android-mainline-12.0.0_r38android-mainline-12.0.0_r37android-mainline-12.0.0_r35android-mainline-12.0.0_r34android-mainline-12.0.0_r33android-mainline-12.0.0_r32android-mainline-12.0.0_r31android-mainline-12.0.0_r30android-mainline-12.0.0_r3android-mainline-12.0.0_r29android-mainline-12.0.0_r28android-mainline-12.0.0_r27android-mainline-12.0.0_r26android-mainline-12.0.0_r25android-mainline-12.0.0_r24android-mainline-12.0.0_r23android-mainline-12.0.0_r22android-mainline-12.0.0_r21android-mainline-12.0.0_r20android-mainline-12.0.0_r2android-mainline-12.0.0_r19android-mainline-12.0.0_r18android-mainline-12.0.0_r17android-mainline-12.0.0_r16android-mainline-12.0.0_r15android-mainline-12.0.0_r14android-mainline-12.0.0_r13android-mainline-12.0.0_r126android-mainline-12.0.0_r125android-mainline-12.0.0_r124android-mainline-12.0.0_r123android-mainline-12.0.0_r122android-mainline-12.0.0_r121android-mainline-12.0.0_r120android-mainline-12.0.0_r12android-mainline-12.0.0_r119android-mainline-12.0.0_r118android-mainline-12.0.0_r117android-mainline-12.0.0_r116android-mainline-12.0.0_r115android-mainline-12.0.0_r114android-mainline-12.0.0_r113android-mainline-12.0.0_r110android-mainline-12.0.0_r11android-mainline-12.0.0_r109android-mainline-12.0.0_r108android-mainline-12.0.0_r107android-mainline-12.0.0_r106android-mainline-12.0.0_r105android-mainline-12.0.0_r104android-mainline-12.0.0_r103android-mainline-12.0.0_r102android-mainline-12.0.0_r101android-mainline-12.0.0_r100android-mainline-12.0.0_r10android-mainline-12.0.0_r1aml_wif_311811030aml_tz3_311312010aml_tet_311811050aml_sdk_311710000aml_pco_311011000aml_mpr_311911090aml_doc_310851020android12-mainline-wifi-releaseandroid12-mainline-tethering-releaseandroid12-mainline-statsd-releaseandroid12-mainline-sdkext-releaseandroid12-mainline-resolv-releaseandroid12-mainline-permission-releaseandroid12-mainline-neuralnetworks-releaseandroid12-mainline-networkstack-releaseandroid12-mainline-mediaprovider-releaseandroid12-mainline-media-swcodec-releaseandroid12-mainline-media-releaseandroid12-mainline-ipsec-releaseandroid12-mainline-extservices-releaseandroid12-mainline-documentsui-releaseandroid12-mainline-conscrypt-releaseandroid12-mainline-cellbroadcast-releaseandroid12-mainline-captiveportallogin-releaseandroid12-mainline-art-releaseandroid12-mainline-adbd-release
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/tinytemplate/+/1662429 Change-Id: I809c3f0b785269d4a910e9b77301c3f65b2ad81f
-rw-r--r--.cargo_vcs_info.json2
-rwxr-xr-x.github/workflows/ci.yml47
-rwxr-xr-x.travis.yml36
-rw-r--r--Android.bp2
-rwxr-xr-xCHANGELOG.md14
-rw-r--r--Cargo.toml2
-rwxr-xr-xCargo.toml.orig2
-rw-r--r--METADATA10
-rwxr-xr-xREADME.md15
-rwxr-xr-xci/install.sh9
-rwxr-xr-xci/script.sh9
-rwxr-xr-xsrc/compiler.rs85
-rwxr-xr-xsrc/error.rs46
-rwxr-xr-xsrc/instruction.rs23
-rwxr-xr-xsrc/template.rs118
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
diff --git a/Android.bp b/Android.bp
index 69240e3..290f0b5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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
diff --git a/Cargo.toml b/Cargo.toml
index 22f2454..7995565 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/METADATA b/METADATA
index caa4164..c4c386e 100644
--- a/METADATA
+++ b/METADATA
@@ -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
}
}
diff --git a/README.md b/README.md
index 186c5b5..c5c793f 100755
--- a/README.md
+++ b/README.md
@@ -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);
+ }
}