diff --git a/.travis.yml b/.travis.yml index 5cd8fd783..4a02b1384 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,11 +19,11 @@ matrix: script: - cargo check --tests - env: CLIPPY - rust: nightly-2018-07-17 + rust: nightly-2018-11-10 install: - rustup component add clippy-preview script: - - cargo clippy --all-features -- -D clippy + - cargo clippy --all-features env: global: diff --git a/benches/liquid.rs b/benches/liquid.rs index 667dc3164..650d14fbb 100644 --- a/benches/liquid.rs +++ b/benches/liquid.rs @@ -30,10 +30,10 @@ static VARIABLE_ONLY: &'static str = "{{product.name}}"; static VARIABLE_ONLY_OBJECT: &'static str = " username: bob product: - - name: Moto G - - manufacturer: Motorola - - summary: A phone - - price: 100 + name: Moto G + manufacturer: Motorola + summary: A phone + price: 100 "; #[bench] @@ -67,7 +67,7 @@ static ITERATE: &'static str = "
  • {{team.name}}: {{team.score}}
  • - {{/each}} + {% endfor %} "; diff --git a/liquid-compiler/src/parser.rs b/liquid-compiler/src/parser.rs index 7dc5fa23d..e264ca2cf 100644 --- a/liquid-compiler/src/parser.rs +++ b/liquid-compiler/src/parser.rs @@ -4,7 +4,6 @@ //! but should be ignored for simple usage. use std::collections::HashSet; -use std::iter::FromIterator; use std::slice::Iter; use liquid_interpreter::Expression; @@ -308,7 +307,7 @@ pub fn split_block<'a>( ) -> (&'a [Element], Option>) { // construct a fast-lookup cache of the delimiters, as we're going to be // consulting the delimiter list a *lot*. - let delims: HashSet<&str> = HashSet::from_iter(delimiters.iter().map(|x| *x)); + let delims: HashSet<&str> = delimiters.iter().cloned().collect(); let mut stack: Vec = Vec::new(); for (i, t) in tokens.iter().enumerate() { diff --git a/liquid-compiler/src/token.rs b/liquid-compiler/src/token.rs index 9a5cbce7b..b78e08f39 100644 --- a/liquid-compiler/src/token.rs +++ b/liquid-compiler/src/token.rs @@ -2,6 +2,7 @@ use std::fmt; use liquid_interpreter::Expression; use liquid_interpreter::Variable; +use liquid_value::Scalar; use liquid_value::Value; use super::error::Result; @@ -79,11 +80,11 @@ impl Token { Token::StringLiteral(ref s) => Ok(Expression::with_literal(s.to_owned())), Token::BooleanLiteral(b) => Ok(Expression::with_literal(b)), Token::Identifier(ref id) => { - let mut var = Variable::default(); - var.extend( - id.split('.') - .map(|s| Expression::with_literal(s.to_owned())), + let mut path = id.split('.').map(|s| Scalar::new(s.to_owned())); + let mut var = Variable::with_literal( + path.next().expect("there should always be at least one"), ); + var.extend(path); Ok(Expression::Variable(var)) } ref x => Err(unexpected_token_error( @@ -132,7 +133,7 @@ mod test { let ctx = Context::new(); let t = Token::StringLiteral("hello".to_owned()); assert_eq!( - t.to_arg().unwrap().evaluate(&ctx).unwrap(), + *t.to_arg().unwrap().evaluate(&ctx).unwrap(), Value::scalar("hello") ); } @@ -141,7 +142,7 @@ mod test { fn evaluate_handles_number_literals() { let ctx = Context::new(); assert_eq!( - Token::FloatLiteral(42f64) + *Token::FloatLiteral(42f64) .to_arg() .unwrap() .evaluate(&ctx) @@ -151,7 +152,7 @@ mod test { let ctx = Context::new(); assert_eq!( - Token::IntegerLiteral(42i32) + *Token::IntegerLiteral(42i32) .to_arg() .unwrap() .evaluate(&ctx) @@ -164,7 +165,7 @@ mod test { fn evaluate_handles_boolean_literals() { let ctx = Context::new(); assert_eq!( - Token::BooleanLiteral(true) + *Token::BooleanLiteral(true) .to_arg() .unwrap() .evaluate(&ctx) @@ -173,7 +174,7 @@ mod test { ); assert_eq!( - Token::BooleanLiteral(false) + *Token::BooleanLiteral(false) .to_arg() .unwrap() .evaluate(&ctx) @@ -187,7 +188,7 @@ mod test { let mut ctx = Context::new(); ctx.stack_mut().set_global("var0", Value::scalar(42f64)); assert_eq!( - Token::Identifier("var0".to_owned()) + *Token::Identifier("var0".to_owned()) .to_arg() .unwrap() .evaluate(&ctx) diff --git a/liquid-interpreter/src/context.rs b/liquid-interpreter/src/context.rs index 0292d382f..21f9073dd 100644 --- a/liquid-interpreter/src/context.rs +++ b/liquid-interpreter/src/context.rs @@ -3,7 +3,8 @@ use std::collections::HashMap; use std::sync; use error::{Error, Result}; -use value::{Object, Path, Value}; +use itertools; +use value::{Object, PathRef, Scalar, Value}; use super::Expression; use super::Globals; @@ -83,7 +84,11 @@ where impl<'a, 'g> CycleState<'a, 'g> { /// See `cycle` tag. - pub fn cycle_element(&mut self, name: &str, values: &[Expression]) -> Result { + pub fn cycle_element<'c>( + &'c mut self, + name: &str, + values: &'c [Expression], + ) -> Result<&'c Value> { let index = self.context.cycles.cycle_index(name, values.len()); if index >= values.len() { return Err(Error::with_msg( @@ -156,45 +161,74 @@ impl<'g> Stack<'g> { /// /// This method will panic if popping the topmost frame results in an /// empty stack. Given that a context is created with a top-level stack - /// frame already in place, empyting the stack should never happen in a + /// frame already in place, emptying the stack should never happen in a /// well-formed program. fn pop_frame(&mut self) { if self.stack.pop().is_none() { - panic!("Pop leaves empty stack") + panic!("Unbalanced push/pop, leaving the stack empty.") }; } /// Recursively index into the stack. - pub fn get(&self, path: &Path) -> Result<&Value> { - let mut indexes = path.iter(); - let key = indexes - .next() - .ok_or_else(|| Error::with_msg("No variable provided"))?; + pub fn try_get(&self, path: PathRef) -> Option<&Value> { + let frame = self.find_path_frame(path)?; + + frame.try_get_variable(path) + } + + /// Recursively index into the stack. + pub fn get(&self, path: PathRef) -> Result<&Value> { + let frame = self.find_path_frame(path).ok_or_else(|| { + let key = path + .iter() + .next() + .cloned() + .unwrap_or_else(|| Scalar::new("nil")); + let globals = itertools::join(self.globals().iter(), ", "); + Error::with_msg("Unknown variable") + .context("requested variable", key.to_str().into_owned()) + .context("available variables", globals) + })?; + + frame.get_variable(path) + } + + fn globals(&self) -> Vec<&str> { + let mut globals = self.globals.map(|g| g.globals()).unwrap_or_default(); + for frame in self.stack.iter() { + globals.extend(frame.globals()); + } + globals.sort(); + globals.dedup(); + globals + } + + fn find_path_frame<'a>(&'a self, path: PathRef) -> Option<&'a Globals> { + let key = path.iter().next()?; let key = key.to_str(); - let value = self.get_root(key.as_ref())?; - - indexes.fold(Ok(value), |value, index| { - let value = value?; - let child = value.get(index); - let child = child.ok_or_else(|| { - Error::with_msg("Unknown index") - .context("variable", format!("{}", path)) - .context("index", format!("{}", index)) - })?; - Ok(child) - }) + self.find_frame(key.as_ref()) } - fn get_root<'a>(&'a self, name: &str) -> Result<&'a Value> { + fn find_frame<'a>(&'a self, name: &str) -> Option<&'a Globals> { for frame in self.stack.iter().rev() { - if let Some(rval) = frame.get(name) { - return Ok(rval); + if frame.contains_global(name) { + return Some(frame); } } - self.globals - .ok_or_else(|| Error::with_msg("Unknown variable").context("variable", name.to_owned())) - .and_then(|g| g.get(name)) - .or_else(|err| self.get_index(name).ok_or_else(|| err)) + + if self + .globals + .map(|g| g.contains_global(name)) + .unwrap_or(false) + { + return self.globals; + } + + if self.indexes.contains_global(name) { + return Some(&self.indexes); + } + + None } /// Used by increment and decrement tags @@ -391,22 +425,19 @@ mod test { use value::Scalar; #[test] - fn stack_get_root() { + fn stack_find_frame() { let mut ctx = Context::new(); ctx.stack_mut().set_global("number", Value::scalar(42f64)); - assert_eq!( - ctx.stack().get_root("number").unwrap(), - &Value::scalar(42f64) - ); + assert!(ctx.stack().find_frame("number").is_some(),); } #[test] - fn stack_get_root_failure() { + fn stack_find_frame_failure() { let mut ctx = Context::new(); let mut post = Object::new(); post.insert("number".into(), Value::scalar(42f64)); ctx.stack_mut().set_global("post", Value::Object(post)); - assert!(ctx.stack().get_root("post.number").is_err()); + assert!(ctx.stack().find_frame("post.number").is_none()); } #[test] @@ -415,29 +446,30 @@ mod test { let mut post = Object::new(); post.insert("number".into(), Value::scalar(42f64)); ctx.stack_mut().set_global("post", Value::Object(post)); - let indexes = vec![Scalar::new("post"), Scalar::new("number")] - .into_iter() - .collect(); + let indexes = [Scalar::new("post"), Scalar::new("number")]; assert_eq!(ctx.stack().get(&indexes).unwrap(), &Value::scalar(42f64)); } #[test] fn scoped_variables() { + let test_path = [Scalar::new("test")]; + let global_path = [Scalar::new("global")]; + let mut ctx = Context::new(); ctx.stack_mut().set_global("test", Value::scalar(42f64)); - assert_eq!(ctx.stack().get_root("test").unwrap(), &Value::scalar(42f64)); + assert_eq!(ctx.stack().get(&test_path).unwrap(), &Value::scalar(42f64)); ctx.run_in_scope(|new_scope| { // assert that values are chained to the parent scope assert_eq!( - new_scope.stack().get_root("test").unwrap(), + new_scope.stack().get(&test_path).unwrap(), &Value::scalar(42f64) ); // set a new local value, and assert that it overrides the previous value new_scope.stack_mut().set("test", Value::scalar(3.14f64)); assert_eq!( - new_scope.stack().get_root("test").unwrap(), + new_scope.stack().get(&test_path).unwrap(), &Value::scalar(3.14f64) ); @@ -448,9 +480,9 @@ mod test { }); // assert that the value has reverted to the old one - assert_eq!(ctx.stack().get_root("test").unwrap(), &Value::scalar(42f64)); + assert_eq!(ctx.stack().get(&test_path).unwrap(), &Value::scalar(42f64)); assert_eq!( - ctx.stack().get_root("global").unwrap(), + ctx.stack().get(&global_path).unwrap(), &Value::scalar("some value") ); } diff --git a/liquid-interpreter/src/expression.rs b/liquid-interpreter/src/expression.rs index 01dba4e6e..5cc2ef2b3 100644 --- a/liquid-interpreter/src/expression.rs +++ b/liquid-interpreter/src/expression.rs @@ -39,12 +39,24 @@ impl Expression { } /// Convert to a `Value`. - pub fn evaluate(&self, context: &Context) -> Result { + pub fn try_evaluate<'c>(&'c self, context: &'c Context) -> Option<&'c Value> { let val = match *self { - Expression::Literal(ref x) => x.clone(), + Expression::Literal(ref x) => &x, + Expression::Variable(ref x) => { + let path = x.try_evaluate(context)?; + context.stack().try_get(&path)? + } + }; + Some(val) + } + + /// Convert to a `Value`. + pub fn evaluate<'c>(&'c self, context: &'c Context) -> Result<&'c Value> { + let val = match *self { + Expression::Literal(ref x) => x, Expression::Variable(ref x) => { let path = x.evaluate(context)?; - context.stack().get(&path)?.clone() + context.stack().get(&path)? } }; Ok(val) diff --git a/liquid-interpreter/src/filter_chain.rs b/liquid-interpreter/src/filter_chain.rs index 30a84f064..94bf2f482 100644 --- a/liquid-interpreter/src/filter_chain.rs +++ b/liquid-interpreter/src/filter_chain.rs @@ -54,7 +54,7 @@ impl FilterChain { /// Process `Value` expression within `context`'s stack. pub fn evaluate(&self, context: &Context) -> Result { // take either the provided value or the value from the provided variable - let mut entry = self.entry.evaluate(context)?; + let mut entry = self.entry.evaluate(context)?.to_owned(); // apply all specified filters for filter in &self.filters { @@ -65,7 +65,7 @@ impl FilterChain { let arguments: Result> = filter .arguments .iter() - .map(|a| a.evaluate(context)) + .map(|a| Ok(a.evaluate(context)?.to_owned())) .collect(); let arguments = arguments?; entry = f diff --git a/liquid-interpreter/src/globals.rs b/liquid-interpreter/src/globals.rs index d4394b5ff..12457ca22 100644 --- a/liquid-interpreter/src/globals.rs +++ b/liquid-interpreter/src/globals.rs @@ -1,18 +1,97 @@ use std::fmt; use error::{Error, Result}; +use itertools; use value::Object; +use value::PathRef; use value::Value; /// Immutable view into a template's global variables. pub trait Globals: fmt::Debug { - /// Access a global variable. - fn get<'a>(&'a self, name: &str) -> Result<&'a Value>; + /// Check if global variable exists. + fn contains_global(&self, name: &str) -> bool; + + /// Enumerate all globals + fn globals(&self) -> Vec<&str>; + + /// Check if variable exists. + /// + /// Notes to implementers: + /// - Don't forget to reverse-index on negative array indexes + /// - Don't forget about arr.first, arr.last. + fn contains_variable(&self, path: PathRef) -> bool; + + /// Access a variable. + /// + /// Notes to implementers: + /// - Don't forget to reverse-index on negative array indexes + /// - Don't forget about arr.first, arr.last. + fn try_get_variable<'a>(&'a self, path: PathRef) -> Option<&'a Value>; + + /// Access a variable. + /// + /// Notes to implementers: + /// - Don't forget to reverse-index on negative array indexes + /// - Don't forget about arr.first, arr.last. + fn get_variable<'a>(&'a self, path: PathRef) -> Result<&'a Value>; } impl Globals for Object { - fn get<'a>(&'a self, name: &str) -> Result<&'a Value> { - self.get(name) - .ok_or_else(|| Error::with_msg("Unknown variable").context("variable", name.to_owned())) + fn contains_global(&self, name: &str) -> bool { + self.contains_key(name) + } + + fn globals(&self) -> Vec<&str> { + self.keys().map(|s| s.as_ref()).collect() + } + + fn contains_variable(&self, path: PathRef) -> bool { + get_variable_option(self, path).is_some() } + + fn try_get_variable<'a>(&'a self, path: PathRef) -> Option<&'a Value> { + get_variable_option(self, path) + } + + fn get_variable<'a>(&'a self, path: PathRef) -> Result<&'a Value> { + if let Some(res) = self.try_get_variable(path) { + return Ok(res); + } else { + for cur_idx in 1..path.len() { + let subpath_end = path.len() - cur_idx; + let subpath = &path[0..subpath_end]; + if let Some(parent) = self.try_get_variable(subpath) { + let subpath = itertools::join(subpath.iter(), "."); + let requested = &path[subpath_end]; + let available = itertools::join(parent.keys(), ", "); + return Err(Error::with_msg("Unknown index") + .context("variable", subpath) + .context("requested index", format!("{}", requested)) + .context("available indexes", available)); + } + } + + let requested = path + .get(0) + .expect("`Path` guarantees at least one element") + .to_str() + .into_owned(); + let available = itertools::join(self.keys(), ", "); + return Err(Error::with_msg("Unknown variable") + .context("requested variable", requested) + .context("available variables", available)); + } + } +} + +fn get_variable_option<'o>(obj: &'o Object, path: PathRef) -> Option<&'o Value> { + let mut indexes = path.iter(); + let key = indexes.next()?; + let key = key.to_str(); + let value = obj.get(key.as_ref())?; + + indexes.fold(Some(value), |value, index| { + let value = value?; + value.get(index) + }) } diff --git a/liquid-interpreter/src/variable.rs b/liquid-interpreter/src/variable.rs index 3edfe6b25..406457e08 100644 --- a/liquid-interpreter/src/variable.rs +++ b/liquid-interpreter/src/variable.rs @@ -10,67 +10,74 @@ use super::Expression; use super::Renderable; /// A `Value` reference. -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Variable { - path: Vec, + variable: Scalar, + indexes: Vec, } impl Variable { /// Create a `Value` reference. pub fn with_literal>(value: S) -> Self { - let expr = Expression::with_literal(value); - let path = vec![expr]; - Self { path } + Self { + variable: value.into(), + indexes: Default::default(), + } } /// Append a literal. pub fn push_literal>(mut self, value: S) -> Self { - self.path.push(Expression::with_literal(value)); + self.indexes.push(Expression::with_literal(value)); self } /// Convert to a `Path`. - pub fn evaluate(&self, context: &Context) -> Result { - let path: Result = self - .path - .iter() - .map(|e| e.evaluate(context)) - .map(|v| { - let v = v?; - let s = v - .as_scalar() - .ok_or_else(|| Error::with_msg(format!("Expected scalar, found `{}`", v)))? - .clone(); - Ok(s) - }).collect(); - path + pub fn try_evaluate<'c>(&'c self, context: &'c Context) -> Option> { + let mut path = Path::with_index(self.variable.clone()); + path.reserve(self.indexes.len()); + for expr in &self.indexes { + let v = expr.try_evaluate(context)?; + let s = v.as_scalar()?.as_ref(); + path.push(s); + } + Some(path) + } + + /// Convert to a `Path`. + pub fn evaluate<'c>(&'c self, context: &'c Context) -> Result> { + let mut path = Path::with_index(self.variable.as_ref()); + path.reserve(self.indexes.len()); + for expr in &self.indexes { + let v = expr.evaluate(context)?; + let s = v + .as_scalar() + .ok_or_else(|| Error::with_msg(format!("Expected scalar, found `{}`", v)))? + .as_ref(); + path.push(s); + } + Ok(path) } } impl Extend for Variable { fn extend>(&mut self, iter: T) { let path = iter.into_iter().map(Expression::with_literal); - self.path.extend(path); + self.indexes.extend(path); } } impl Extend for Variable { fn extend>(&mut self, iter: T) { let path = iter.into_iter(); - self.path.extend(path); + self.indexes.extend(path); } } impl fmt::Display for Variable { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut iter = self.path.iter(); - let head = iter.next(); - match head { - Some(head) => write!(f, "{}", head)?, - None => return Ok(()), - } - for index in iter { - write!(f, "[\"{}\"]", index)?; + write!(f, "{}", self.variable)?; + for index in self.indexes.iter() { + write!(f, "[{}]", index)?; } Ok(()) } diff --git a/liquid-value/src/path.rs b/liquid-value/src/path.rs index bef0fadcb..9bbe018ce 100644 --- a/liquid-value/src/path.rs +++ b/liquid-value/src/path.rs @@ -1,59 +1,79 @@ use std::fmt; -use std::iter; use std::slice; use itertools; -use super::Scalar; +use super::ScalarCow; /// Path to a value in an `Object`. -#[derive(Clone, Debug, Default, PartialEq)] -pub struct Path { - indexes: Vec, -} - -impl Path { - /// Create a `Path` from iterator of `Scalar`s - pub fn new>(indexes: T) -> Self { - let indexes = indexes.into_iter().collect(); - Self { indexes } - } +/// +/// There is guaranteed always at least one element. +#[derive(Clone, Debug, PartialEq)] +pub struct Path<'s>(Vec>); +impl<'s> Path<'s> { /// Create a `Value` reference. - pub fn with_index>(value: I) -> Self { + pub fn with_index>>(value: I) -> Self { let indexes = vec![value.into()]; - Self { indexes } + Path(indexes) } /// Append an index. - pub fn push>(mut self, value: I) -> Self { - self.indexes.push(value.into()); - self + pub fn push>>(&mut self, value: I) { + self.0.push(value.into()); + } + + /// Reserves capacity for at least `additional` more elements to be inserted + /// in the given `Path`. The `Path` may reserve more space to avoid + /// frequent reallocations. After calling `reserve`, capacity will be + /// greater than or equal to `self.len() + additional`. Does nothing if + /// capacity is already sufficient. + pub fn reserve(&mut self, additional: usize) { + self.0.reserve(additional); } /// Access the `Value` reference. - pub fn iter(&self) -> ScalarIter { - ScalarIter(self.indexes.iter()) + pub fn iter(&self) -> PathIter { + PathIter(self.0.iter()) + } + + /// Extracts a slice containing the entire vector. + #[inline] + pub fn as_slice(&self) -> &[ScalarCow<'s>] { + self.0.as_slice() } } -impl Extend for Path { - fn extend>(&mut self, iter: T) { - self.indexes.extend(iter); +impl<'s> Extend> for Path<'s> { + fn extend>>(&mut self, iter: T) { + self.0.extend(iter); } } -impl iter::FromIterator for Path { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let indexes = iter.into_iter().collect(); - Self { indexes } +impl<'s> ::std::ops::Deref for Path<'s> { + type Target = [ScalarCow<'s>]; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 } } -impl fmt::Display for Path { +impl<'s> ::std::borrow::Borrow<[ScalarCow<'s>]> for Path<'s> { + #[inline] + fn borrow(&self) -> &[ScalarCow<'s>] { + self + } +} + +impl<'s> AsRef<[ScalarCow<'s>]> for Path<'s> { + #[inline] + fn as_ref(&self) -> &[ScalarCow<'s>] { + self + } +} + +impl<'s> fmt::Display for Path<'s> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let data = itertools::join(self.iter(), "."); write!(f, "{}", data) @@ -62,13 +82,13 @@ impl fmt::Display for Path { /// Iterate over indexes in a `Value`'s `Path`. #[derive(Debug)] -pub struct ScalarIter<'i>(slice::Iter<'i, Scalar>); +pub struct PathIter<'i, 's: 'i>(slice::Iter<'i, ScalarCow<'s>>); -impl<'i> Iterator for ScalarIter<'i> { - type Item = &'i Scalar; +impl<'i, 's: 'i> Iterator for PathIter<'i, 's> { + type Item = &'i ScalarCow<'s>; #[inline] - fn next(&mut self) -> Option<&'i Scalar> { + fn next(&mut self) -> Option<&'i ScalarCow<'s>> { self.0.next() } @@ -82,3 +102,13 @@ impl<'i> Iterator for ScalarIter<'i> { self.0.count() } } + +impl<'i, 's: 'i> ExactSizeIterator for PathIter<'i, 's> { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +/// Path to a value in an `Object`. +pub type PathRef<'p, 's> = &'p [ScalarCow<'s>]; diff --git a/liquid-value/src/scalar.rs b/liquid-value/src/scalar.rs index d31790c64..c7ee6c299 100644 --- a/liquid-value/src/scalar.rs +++ b/liquid-value/src/scalar.rs @@ -9,53 +9,75 @@ pub type Date = chrono::DateTime; /// A Liquid scalar value #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Scalar(ScalarEnum); +pub struct ScalarCow<'s>(ScalarCowEnum<'s>); + +/// A Liquid scalar value +pub type Scalar = ScalarCow<'static>; /// An enum to represent different value types #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(untagged)] -enum ScalarEnum { +enum ScalarCowEnum<'s> { Integer(i32), Float(f64), Bool(bool), #[serde(with = "friendly_date")] Date(Date), - Str(borrow::Cow<'static, str>), + Str(borrow::Cow<'s, str>), } -impl Scalar { - /// Convert a value into a `Scalar`. +impl<'s> ScalarCow<'s> { + /// Convert a value into a `ScalarCow`. pub fn new>(value: T) -> Self { value.into() } + /// Create an owned version of the value. + pub fn into_owned(self) -> Self { + match self.0 { + ScalarCowEnum::Str(x) => Scalar::new(x.into_owned()), + _ => self, + } + } + + /// Create a reference to the value. + pub fn as_ref<'r: 's>(&'r self) -> ScalarCow<'r> { + match self.0 { + ScalarCowEnum::Integer(x) => Scalar::new(x), + ScalarCowEnum::Float(x) => Scalar::new(x), + ScalarCowEnum::Bool(x) => Scalar::new(x), + ScalarCowEnum::Date(x) => Scalar::new(x), + ScalarCowEnum::Str(ref x) => Scalar::new(x.as_ref()), + } + } + /// Interpret as a string. pub fn to_str(&self) -> borrow::Cow { match self.0 { - ScalarEnum::Integer(ref x) => borrow::Cow::Owned(x.to_string()), - ScalarEnum::Float(ref x) => borrow::Cow::Owned(x.to_string()), - ScalarEnum::Bool(ref x) => borrow::Cow::Owned(x.to_string()), - ScalarEnum::Date(ref x) => borrow::Cow::Owned(x.format(DATE_FORMAT).to_string()), - ScalarEnum::Str(ref x) => borrow::Cow::Borrowed(x.as_ref()), + ScalarCowEnum::Integer(ref x) => borrow::Cow::Owned(x.to_string()), + ScalarCowEnum::Float(ref x) => borrow::Cow::Owned(x.to_string()), + ScalarCowEnum::Bool(ref x) => borrow::Cow::Owned(x.to_string()), + ScalarCowEnum::Date(ref x) => borrow::Cow::Owned(x.format(DATE_FORMAT).to_string()), + ScalarCowEnum::Str(ref x) => borrow::Cow::Borrowed(x.as_ref()), } } /// Convert to a string. pub fn into_string(self) -> String { match self.0 { - ScalarEnum::Integer(x) => x.to_string(), - ScalarEnum::Float(x) => x.to_string(), - ScalarEnum::Bool(x) => x.to_string(), - ScalarEnum::Date(x) => x.to_string(), - ScalarEnum::Str(x) => x.into_owned(), + ScalarCowEnum::Integer(x) => x.to_string(), + ScalarCowEnum::Float(x) => x.to_string(), + ScalarCowEnum::Bool(x) => x.to_string(), + ScalarCowEnum::Date(x) => x.to_string(), + ScalarCowEnum::Str(x) => x.into_owned(), } } /// Interpret as an integer, if possible pub fn to_integer(&self) -> Option { match self.0 { - ScalarEnum::Integer(ref x) => Some(*x), - ScalarEnum::Str(ref x) => x.parse::().ok(), + ScalarCowEnum::Integer(ref x) => Some(*x), + ScalarCowEnum::Str(ref x) => x.parse::().ok(), _ => None, } } @@ -63,9 +85,9 @@ impl Scalar { /// Interpret as a float, if possible pub fn to_float(&self) -> Option { match self.0 { - ScalarEnum::Integer(ref x) => Some(f64::from(*x)), - ScalarEnum::Float(ref x) => Some(*x), - ScalarEnum::Str(ref x) => x.parse::().ok(), + ScalarCowEnum::Integer(ref x) => Some(f64::from(*x)), + ScalarCowEnum::Float(ref x) => Some(*x), + ScalarCowEnum::Str(ref x) => x.parse::().ok(), _ => None, } } @@ -73,7 +95,7 @@ impl Scalar { /// Interpret as a bool, if possible pub fn to_bool(&self) -> Option { match self.0 { - ScalarEnum::Bool(ref x) => Some(*x), + ScalarCowEnum::Bool(ref x) => Some(*x), _ => None, } } @@ -81,8 +103,8 @@ impl Scalar { /// Interpret as a date, if possible pub fn to_date(&self) -> Option { match self.0 { - ScalarEnum::Date(ref x) => Some(*x), - ScalarEnum::Str(ref x) => parse_date(x.as_ref()), + ScalarCowEnum::Date(ref x) => Some(*x), + ScalarCowEnum::Str(ref x) => parse_date(x.as_ref()), _ => None, } } @@ -91,7 +113,7 @@ impl Scalar { pub fn is_truthy(&self) -> bool { // encode Ruby truthiness: all values except false and nil are true match self.0 { - ScalarEnum::Bool(ref x) => *x, + ScalarCowEnum::Bool(ref x) => *x, _ => true, } } @@ -100,8 +122,8 @@ impl Scalar { pub fn is_default(&self) -> bool { // encode Ruby truthiness: all values except false and nil are true match self.0 { - ScalarEnum::Bool(ref x) => !*x, - ScalarEnum::Str(ref x) => x.is_empty(), + ScalarCowEnum::Bool(ref x) => !*x, + ScalarCowEnum::Str(ref x) => x.is_empty(), _ => false, } } @@ -109,106 +131,118 @@ impl Scalar { /// Report the data type (generally for error reporting). pub fn type_name(&self) -> &'static str { match self.0 { - ScalarEnum::Integer(_) => "whole number", - ScalarEnum::Float(_) => "fractional number", - ScalarEnum::Bool(_) => "boolean", - ScalarEnum::Date(_) => "date", - ScalarEnum::Str(_) => "string", + ScalarCowEnum::Integer(_) => "whole number", + ScalarCowEnum::Float(_) => "fractional number", + ScalarCowEnum::Bool(_) => "boolean", + ScalarCowEnum::Date(_) => "date", + ScalarCowEnum::Str(_) => "string", } } } -impl From for Scalar { +impl<'s> From for ScalarCow<'s> { fn from(s: i32) -> Self { - Scalar { - 0: ScalarEnum::Integer(s), + ScalarCow { + 0: ScalarCowEnum::Integer(s), } } } -impl From for Scalar { +impl<'s> From for ScalarCow<'s> { fn from(s: f64) -> Self { - Scalar { - 0: ScalarEnum::Float(s), + ScalarCow { + 0: ScalarCowEnum::Float(s), } } } -impl From for Scalar { +impl<'s> From for ScalarCow<'s> { fn from(s: bool) -> Self { - Scalar { - 0: ScalarEnum::Bool(s), + ScalarCow { + 0: ScalarCowEnum::Bool(s), } } } -impl From for Scalar { +impl<'s> From for ScalarCow<'s> { fn from(s: Date) -> Self { - Scalar { - 0: ScalarEnum::Date(s), + ScalarCow { + 0: ScalarCowEnum::Date(s), } } } -impl From for Scalar { +impl<'s> From for ScalarCow<'s> { fn from(s: String) -> Self { - Scalar { - 0: ScalarEnum::Str(s.into()), + ScalarCow { + 0: ScalarCowEnum::Str(s.into()), + } + } +} + +impl<'s> From<&'s String> for ScalarCow<'s> { + fn from(s: &'s String) -> ScalarCow<'s> { + ScalarCow { + 0: ScalarCowEnum::Str(s.as_str().into()), } } } -impl<'a> From<&'a String> for Scalar { - fn from(s: &String) -> Self { - Scalar { - 0: ScalarEnum::Str(s.to_owned().into()), +impl<'s> From<&'s str> for ScalarCow<'s> { + fn from(s: &'s str) -> Self { + ScalarCow { + 0: ScalarCowEnum::Str(s.into()), } } } -impl From<&'static str> for Scalar { - fn from(s: &'static str) -> Self { - Scalar { - 0: ScalarEnum::Str(s.into()), +impl<'s> From> for ScalarCow<'s> { + fn from(s: borrow::Cow<'s, str>) -> Self { + ScalarCow { + 0: ScalarCowEnum::Str(s), } } } -impl PartialEq for Scalar { +impl<'s> PartialEq> for ScalarCow<'s> { fn eq(&self, other: &Self) -> bool { match (&self.0, &other.0) { - (&ScalarEnum::Integer(x), &ScalarEnum::Integer(y)) => x == y, - (&ScalarEnum::Integer(x), &ScalarEnum::Float(y)) => (f64::from(x)) == y, - (&ScalarEnum::Float(x), &ScalarEnum::Integer(y)) => x == (f64::from(y)), - (&ScalarEnum::Float(x), &ScalarEnum::Float(y)) => x == y, - (&ScalarEnum::Bool(x), &ScalarEnum::Bool(y)) => x == y, - (&ScalarEnum::Date(x), &ScalarEnum::Date(y)) => x == y, - (&ScalarEnum::Str(ref x), &ScalarEnum::Str(ref y)) => x == y, + (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Integer(y)) => x == y, + (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Float(y)) => (f64::from(x)) == y, + (&ScalarCowEnum::Float(x), &ScalarCowEnum::Integer(y)) => x == (f64::from(y)), + (&ScalarCowEnum::Float(x), &ScalarCowEnum::Float(y)) => x == y, + (&ScalarCowEnum::Bool(x), &ScalarCowEnum::Bool(y)) => x == y, + (&ScalarCowEnum::Date(x), &ScalarCowEnum::Date(y)) => x == y, + (&ScalarCowEnum::Str(ref x), &ScalarCowEnum::Str(ref y)) => x == y, // encode Ruby truthiness: all values except false and nil are true - (_, &ScalarEnum::Bool(b)) | (&ScalarEnum::Bool(b), _) => b, + (_, &ScalarCowEnum::Bool(b)) | (&ScalarCowEnum::Bool(b), _) => b, _ => false, } } } -impl Eq for Scalar {} +impl<'s> Eq for ScalarCow<'s> {} -impl PartialOrd for Scalar { +impl<'s> PartialOrd> for ScalarCow<'s> { fn partial_cmp(&self, other: &Self) -> Option { match (&self.0, &other.0) { - (&ScalarEnum::Integer(x), &ScalarEnum::Integer(y)) => x.partial_cmp(&y), - (&ScalarEnum::Integer(x), &ScalarEnum::Float(y)) => (f64::from(x)).partial_cmp(&y), - (&ScalarEnum::Float(x), &ScalarEnum::Integer(y)) => x.partial_cmp(&(f64::from(y))), - (&ScalarEnum::Float(x), &ScalarEnum::Float(y)) => x.partial_cmp(&y), - (&ScalarEnum::Bool(x), &ScalarEnum::Bool(y)) => x.partial_cmp(&y), - (&ScalarEnum::Date(x), &ScalarEnum::Date(y)) => x.partial_cmp(&y), - (&ScalarEnum::Str(ref x), &ScalarEnum::Str(ref y)) => x.partial_cmp(y), + (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Integer(y)) => x.partial_cmp(&y), + (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Float(y)) => { + (f64::from(x)).partial_cmp(&y) + } + (&ScalarCowEnum::Float(x), &ScalarCowEnum::Integer(y)) => { + x.partial_cmp(&(f64::from(y))) + } + (&ScalarCowEnum::Float(x), &ScalarCowEnum::Float(y)) => x.partial_cmp(&y), + (&ScalarCowEnum::Bool(x), &ScalarCowEnum::Bool(y)) => x.partial_cmp(&y), + (&ScalarCowEnum::Date(x), &ScalarCowEnum::Date(y)) => x.partial_cmp(&y), + (&ScalarCowEnum::Str(ref x), &ScalarCowEnum::Str(ref y)) => x.partial_cmp(y), _ => None, } } } -impl fmt::Display for Scalar { +impl<'s> fmt::Display for ScalarCow<'s> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let data = self.to_str(); write!(f, "{}", data) @@ -260,8 +294,8 @@ fn parse_date(s: &str) -> Option { mod test { use super::*; - static TRUE: Scalar = Scalar(ScalarEnum::Bool(true)); - static FALSE: Scalar = Scalar(ScalarEnum::Bool(false)); + static TRUE: ScalarCow = ScalarCow(ScalarCowEnum::Bool(true)); + static FALSE: ScalarCow = ScalarCow(ScalarCowEnum::Bool(false)); #[test] fn test_to_str_bool() { @@ -270,22 +304,22 @@ mod test { #[test] fn test_to_str_integer() { - let val: Scalar = 42i32.into(); + let val: ScalarCow = 42i32.into(); assert_eq!(val.to_str(), "42"); } #[test] fn test_to_str_float() { - let val: Scalar = 42f64.into(); + let val: ScalarCow = 42f64.into(); assert_eq!(val.to_str(), "42"); - let val: Scalar = 42.34.into(); + let val: ScalarCow = 42.34.into(); assert_eq!(val.to_str(), "42.34"); } #[test] fn test_to_str_str() { - let val: Scalar = "foobar".into(); + let val: ScalarCow = "foobar".into(); assert_eq!(val.to_str(), "foobar"); } @@ -296,28 +330,28 @@ mod test { #[test] fn test_to_integer_integer() { - let val: Scalar = 42i32.into(); + let val: ScalarCow = 42i32.into(); assert_eq!(val.to_integer(), Some(42i32)); } #[test] fn test_to_integer_float() { - let val: Scalar = 42f64.into(); + let val: ScalarCow = 42f64.into(); assert_eq!(val.to_integer(), None); - let val: Scalar = 42.34.into(); + let val: ScalarCow = 42.34.into(); assert_eq!(val.to_integer(), None); } #[test] fn test_to_integer_str() { - let val: Scalar = "foobar".into(); + let val: ScalarCow = "foobar".into(); assert_eq!(val.to_integer(), None); - let val: Scalar = "42.34".into(); + let val: ScalarCow = "42.34".into(); assert_eq!(val.to_integer(), None); - let val: Scalar = "42".into(); + let val: ScalarCow = "42".into(); assert_eq!(val.to_integer(), Some(42)); } @@ -328,28 +362,28 @@ mod test { #[test] fn test_to_float_integer() { - let val: Scalar = 42i32.into(); + let val: ScalarCow = 42i32.into(); assert_eq!(val.to_float(), Some(42f64)); } #[test] fn test_to_float_float() { - let val: Scalar = 42f64.into(); + let val: ScalarCow = 42f64.into(); assert_eq!(val.to_float(), Some(42f64)); - let val: Scalar = 42.34.into(); + let val: ScalarCow = 42.34.into(); assert_eq!(val.to_float(), Some(42.34)); } #[test] fn test_to_float_str() { - let val: Scalar = "foobar".into(); + let val: ScalarCow = "foobar".into(); assert_eq!(val.to_float(), None); - let val: Scalar = "42.34".into(); + let val: ScalarCow = "42.34".into(); assert_eq!(val.to_float(), Some(42.34)); - let val: Scalar = "42".into(); + let val: ScalarCow = "42".into(); assert_eq!(val.to_float(), Some(42f64)); } @@ -360,35 +394,35 @@ mod test { #[test] fn test_to_bool_integer() { - let val: Scalar = 42i32.into(); + let val: ScalarCow = 42i32.into(); assert_eq!(val.to_bool(), None); } #[test] fn test_to_bool_float() { - let val: Scalar = 42f64.into(); + let val: ScalarCow = 42f64.into(); assert_eq!(val.to_bool(), None); - let val: Scalar = 42.34.into(); + let val: ScalarCow = 42.34.into(); assert_eq!(val.to_bool(), None); } #[test] fn test_to_bool_str() { - let val: Scalar = "foobar".into(); + let val: ScalarCow = "foobar".into(); assert_eq!(val.to_bool(), None); - let val: Scalar = "true".into(); + let val: ScalarCow = "true".into(); assert_eq!(val.to_bool(), None); - let val: Scalar = "false".into(); + let val: ScalarCow = "false".into(); assert_eq!(val.to_bool(), None); } #[test] fn integer_equality() { - let val: Scalar = 42i32.into(); - let zero: Scalar = 0i32.into(); + let val: ScalarCow = 42i32.into(); + let zero: ScalarCow = 0i32.into(); assert_eq!(val, val); assert_eq!(zero, zero); assert!(val != zero); @@ -397,8 +431,8 @@ mod test { #[test] fn integers_have_ruby_truthiness() { - let val: Scalar = 42i32.into(); - let zero: Scalar = 0i32.into(); + let val: ScalarCow = 42i32.into(); + let zero: ScalarCow = 0i32.into(); assert_eq!(TRUE, val); assert_eq!(val, TRUE); assert!(val.is_truthy()); @@ -410,8 +444,8 @@ mod test { #[test] fn float_equality() { - let val: Scalar = 42f64.into(); - let zero: Scalar = 0f64.into(); + let val: ScalarCow = 42f64.into(); + let zero: ScalarCow = 0f64.into(); assert_eq!(val, val); assert_eq!(zero, zero); assert!(val != zero); @@ -420,8 +454,8 @@ mod test { #[test] fn floats_have_ruby_truthiness() { - let val: Scalar = 42f64.into(); - let zero: Scalar = 0f64.into(); + let val: ScalarCow = 42f64.into(); + let zero: ScalarCow = 0f64.into(); assert_eq!(TRUE, val); assert_eq!(val, TRUE); assert!(val.is_truthy()); @@ -447,9 +481,9 @@ mod test { #[test] fn string_equality() { - let alpha: Scalar = "alpha".into(); - let beta: Scalar = "beta".into(); - let empty: Scalar = "".into(); + let alpha: ScalarCow = "alpha".into(); + let beta: ScalarCow = "beta".into(); + let empty: ScalarCow = "".into(); assert_eq!(alpha, alpha); assert_eq!(empty, empty); assert!(alpha != beta); @@ -459,8 +493,8 @@ mod test { #[test] fn strings_have_ruby_truthiness() { // all strings in ruby are true - let alpha: Scalar = "alpha".into(); - let empty: Scalar = "".into(); + let alpha: ScalarCow = "alpha".into(); + let empty: ScalarCow = "".into(); assert_eq!(TRUE, alpha); assert_eq!(alpha, TRUE); assert!(alpha.is_truthy()); diff --git a/liquid-value/src/values.rs b/liquid-value/src/values.rs index 57c783737..733b270fd 100644 --- a/liquid-value/src/values.rs +++ b/liquid-value/src/values.rs @@ -9,6 +9,7 @@ use std::collections::BTreeMap; use std::collections::HashMap; use super::Scalar; +use super::ScalarCow; #[cfg(feature = "object_sorted")] type MapImpl = BTreeMap; @@ -77,6 +78,14 @@ impl Value { } } + /// Extracts the scalar value if it is a scalar. + pub fn into_scalar(self) -> Option { + match self { + Value::Scalar(s) => Some(s), + _ => None, + } + } + /// Tests whether this value is a scalar pub fn is_scalar(&self) -> bool { self.as_scalar().is_some() @@ -98,6 +107,14 @@ impl Value { } } + /// Extracts the array value if it is an array. + pub fn into_array(self) -> Option { + match self { + Value::Array(s) => Some(s), + _ => None, + } + } + /// Tests whether this value is an array pub fn is_array(&self) -> bool { self.as_array().is_some() @@ -119,11 +136,27 @@ impl Value { } } + /// Extracts the object value if it is a object. + pub fn into_object(self) -> Option { + match self { + Value::Object(s) => Some(s), + _ => None, + } + } + /// Tests whether this value is an object pub fn is_object(&self) -> bool { self.as_object().is_some() } + /// Extracts the nil value if it is nil + pub fn as_nil(&self) -> Option<()> { + match *self { + Value::Nil => Some(()), + _ => None, + } + } + /// Tests whether this value is nil pub fn is_nil(&self) -> bool { match *self { @@ -163,20 +196,52 @@ impl Value { } /// Access a contained `Value`. - pub fn get<'i, I: Into<&'i Scalar>>(&self, index: I) -> Option<&Self> { - let index: &Scalar = index.into(); - self.get_index(index) + pub fn contains_key(&self, index: &Scalar) -> bool { + match *self { + Value::Array(ref x) => { + if let Some(index) = index.to_integer() { + let index = convert_index(index, x.len()); + index < x.len() + } else { + match &*index.to_str() { + "first" | "last" => true, + _ => false, + } + } + } + Value::Object(ref x) => x.contains_key(index.to_str().as_ref()), + _ => false, + } + } + + /// Keys available for lookup. + pub fn keys(&self) -> Keys { + let v = match *self { + Value::Array(ref x) => { + let start: i32 = 0; + let end = x.len() as i32; + let mut keys: Vec<_> = (start..end).map(Scalar::new).collect(); + keys.push(Scalar::new("first")); + keys.push(Scalar::new("last")); + keys + } + Value::Object(ref x) => x + .keys() + .map(|s| match *s { + borrow::Cow::Borrowed(s) => Scalar::new(s), + borrow::Cow::Owned(ref s) => Scalar::new(s.to_owned()), + }).collect(), + _ => vec![], + }; + Keys(v.into_iter()) } - fn get_index(&self, index: &Scalar) -> Option<&Self> { + /// Access a contained `Value`. + pub fn get<'s>(&'s self, index: &ScalarCow) -> Option<&'s Self> { match *self { Value::Array(ref x) => { if let Some(index) = index.to_integer() { - let index = if 0 <= index { - index as isize - } else { - (x.len() as isize) + (index as isize) - }; + let index = convert_index(index, x.len()); x.get(index as usize) } else { match &*index.to_str() { @@ -192,6 +257,43 @@ impl Value { } } +/// Iterator over a `Value`s keys. +#[derive(Debug)] +pub struct Keys(::std::vec::IntoIter); + +impl Iterator for Keys { + type Item = Scalar; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + #[inline] + fn count(self) -> usize { + self.0.count() + } +} + +impl ExactSizeIterator for Keys { + #[inline] + fn len(&self) -> usize { + self.0.len() + } +} + +fn convert_index(index: i32, max_size: usize) -> usize { + let index = index as isize; + let max_size = max_size as isize; + let index = if 0 <= index { index } else { max_size + index }; + index as usize +} + impl Default for Value { fn default() -> Self { Self::nil() diff --git a/src/bin/liquid-dbg/main.rs b/src/bin/liquid-dbg/main.rs index c489d104b..473f3073d 100644 --- a/src/bin/liquid-dbg/main.rs +++ b/src/bin/liquid-dbg/main.rs @@ -1,11 +1,5 @@ -#![doc(html_root_url = "https://cobalt-org.github.io/liquid-rust/")] -// Deny warnings, except in dev mode -#![deny(warnings)] -// #![deny(missing_docs)] -#![cfg_attr(feature = "dev", warn(warnings))] // Allow zero pointers for lazy_static. Otherwise clippy will complain. #![allow(unknown_lints)] -#![allow(zero_ptr)] #[macro_use] extern crate clap; @@ -89,7 +83,7 @@ fn run() -> Result<()> { .author(crate_authors!()) .arg(option("input", "LIQUID").required(true)) .arg(option("output", "TXT")) - .arg(option("context", "TOML")) + .arg(option("context", "YAML")) .arg(option("include-root", "PATH")) .get_matches_safe()?; diff --git a/src/tags/assign_tag.rs b/src/tags/assign_tag.rs index cfd817e95..5c8711e05 100644 --- a/src/tags/assign_tag.rs +++ b/src/tags/assign_tag.rs @@ -165,10 +165,7 @@ mod test { let output = template.render(&mut context).unwrap(); assert_eq!( - context - .stack() - .get(&vec![Scalar::new("freestyle")].into_iter().collect()) - .unwrap(), + context.stack().get(&[Scalar::new("freestyle")]).unwrap(), &Value::scalar(false) ); assert_eq!(output, ""); @@ -189,10 +186,7 @@ mod test { let output = template.render(&mut context).unwrap(); assert_eq!( - context - .stack() - .get(&vec![Scalar::new("freestyle")].into_iter().collect()) - .unwrap(), + context.stack().get(&[Scalar::new("freestyle")]).unwrap(), &Value::scalar(true) ); assert_eq!(output, "

    Freestyle!

    "); diff --git a/src/tags/capture_block.rs b/src/tags/capture_block.rs index d7c1e91db..49501fe4b 100644 --- a/src/tags/capture_block.rs +++ b/src/tags/capture_block.rs @@ -95,9 +95,7 @@ mod test { let output = template.render(&mut ctx).unwrap(); assert_eq!( - ctx.stack() - .get(&vec![Scalar::new("attribute_name")].into_iter().collect()) - .unwrap(), + ctx.stack().get(&[Scalar::new("attribute_name")]).unwrap(), &Value::scalar("potato-42-color") ); assert_eq!(output, ""); diff --git a/src/tags/case_block.rs b/src/tags/case_block.rs index 3afbb020a..b7fc1df35 100644 --- a/src/tags/case_block.rs +++ b/src/tags/case_block.rs @@ -27,7 +27,7 @@ impl CaseOption { fn evaluate(&self, value: &Value, context: &Context) -> Result { for a in &self.args { let v = a.evaluate(context)?; - if v == *value { + if *v == *value { return Ok(true); } } @@ -54,7 +54,7 @@ impl Case { impl Renderable for Case { fn render_to(&self, writer: &mut Write, context: &mut Context) -> Result<()> { - let value = self.target.evaluate(context)?; + let value = self.target.evaluate(context)?.to_owned(); for case in &self.cases { if case.evaluate(&value, context)? { return case diff --git a/src/tags/cycle_tag.rs b/src/tags/cycle_tag.rs index 4ddc2cb1e..e75486090 100644 --- a/src/tags/cycle_tag.rs +++ b/src/tags/cycle_tag.rs @@ -27,8 +27,8 @@ impl Cycle { impl Renderable for Cycle { fn render_to(&self, writer: &mut Write, context: &mut Context) -> Result<()> { - let value = context - .cycles() + let mut cycles = context.cycles(); + let value = cycles .cycle_element(&self.name, &self.values) .trace_with(|| self.trace())?; write!(writer, "{}", value).chain("Failed to render")?; diff --git a/src/tags/for_block.rs b/src/tags/for_block.rs index 862e61d08..06bbeafb8 100644 --- a/src/tags/for_block.rs +++ b/src/tags/for_block.rs @@ -152,13 +152,12 @@ impl For { fn get_array(context: &Context, array_id: &Expression) -> Result> { let array = array_id.evaluate(context)?; match array { - Value::Array(x) => Ok(x), + Value::Array(x) => Ok(x.to_owned()), Value::Object(x) => { let x = x .iter() - .map(|(k, v)| { - Value::Array(vec![Value::scalar(k.as_ref().to_owned()), v.to_owned()]) - }).collect(); + .map(|(k, v)| Value::Array(vec![Value::scalar(k.clone()), v.to_owned()])) + .collect(); Ok(x) } x => Err(unexpected_value_error("array", Some(x.type_name()))), diff --git a/src/tags/if_block.rs b/src/tags/if_block.rs index da8bb9c16..e3018cecd 100644 --- a/src/tags/if_block.rs +++ b/src/tags/if_block.rs @@ -53,7 +53,7 @@ struct ExistenceCondition { impl ExistenceCondition { pub fn evaluate(&self, context: &Context) -> Result { - let a = self.lh.evaluate(context).unwrap_or_default(); + let a = self.lh.try_evaluate(context).cloned().unwrap_or_default(); Ok(a.is_truthy()) } } @@ -108,14 +108,18 @@ struct Conditional { } fn contains_check(a: &Value, b: &Value) -> Result { - let b = b.to_str(); - match *a { - Value::Scalar(ref val) => Ok(val.to_str().contains(b.as_ref())), - Value::Object(ref obj) => Ok(obj.contains_key(b.as_ref())), + Value::Scalar(ref val) => { + let b = b.to_str(); + Ok(val.to_str().contains(b.as_ref())) + } + Value::Object(_) => { + let b = b.as_scalar(); + let check = b.map(|b| a.contains_key(b)).unwrap_or(false); + Ok(check) + } Value::Array(ref arr) => { for elem in arr { - let elem = elem.to_str(); if elem == b { return Ok(true); }