diff --git a/minijinja/src/filters.rs b/minijinja/src/filters.rs index ec05c94d..cfb247de 100644 --- a/minijinja/src/filters.rs +++ b/minijinja/src/filters.rs @@ -636,7 +636,7 @@ mod builtins { if count == 0 { return Err(Error::new(ErrorKind::InvalidOperation, "count cannot be 0")); } - let items = ok!(value.try_iter()).collect::>(); + let items = ok!(value.try_iter_owned()).collect::>(); let len = items.len(); let items_per_slice = len / count; let slices_with_extra = len % count; @@ -688,7 +688,7 @@ mod builtins { let mut rv = Vec::new(); let mut tmp = Vec::with_capacity(count); - for item in ok!(value.try_iter()) { + for item in ok!(value.try_iter_owned()) { if tmp.len() == count { rv.push(Value::from(mem::replace( &mut tmp, diff --git a/minijinja/src/key/mod.rs b/minijinja/src/key/mod.rs index d345a5aa..c0fc3363 100644 --- a/minijinja/src/key/mod.rs +++ b/minijinja/src/key/mod.rs @@ -269,7 +269,7 @@ pub mod key_interning { let v = Value::from_serializable(&vec![m.clone(), m.clone(), m.clone()]); - for value in v.try_iter().unwrap() { + for value in v.try_iter_owned().unwrap() { match value.0 { ValueRepr::Map(m, _) => { let k = m.iter().next().unwrap().0; diff --git a/minijinja/src/value/mod.rs b/minijinja/src/value/mod.rs index abd03bbe..c063d5ac 100644 --- a/minijinja/src/value/mod.rs +++ b/minijinja/src/value/mod.rs @@ -73,6 +73,7 @@ use std::cmp::Ordering; use std::collections::BTreeMap; use std::convert::TryFrom; use std::fmt; +use std::marker::PhantomData; use std::sync::atomic::{self, AtomicBool, AtomicUsize}; use std::sync::Arc; @@ -367,45 +368,40 @@ impl Value { /// Creates a value from something that can be serialized. /// + /// This is the method that MiniJinja will generally use whenever a serializable + /// object is passed to one of the APIs that internally want to create a value. + /// For instance this is what [`context!`](crate::context) and + /// [`render`](crate::Template::render) will use. + /// /// During serialization of the value, [`serializing_for_value`] will return /// `true` which makes it possible to customize serialization for MiniJinja. /// For more information see [`serializing_for_value`]. + /// + /// ``` + /// # use minijinja::value::Value; + /// let val = Value::from_serializable(&vec![1, 2, 3]); + /// ``` pub fn from_serializable(value: &T) -> Value { with_internal_serialization(|| Serialize::serialize(value, ValueSerializer).unwrap()) } /// Creates a value from a safe string. + /// + /// A safe string is one that will bypass auto escaping. For instance if you + /// want to have the template engine render some HTML without the user having to + /// supply the `|safe` filter, you can use a value of this type instead. + /// + /// ``` + /// # use minijinja::value::Value; + /// let val = Value::from_safe_string("note".into()); + /// ``` pub fn from_safe_string(value: String) -> Value { ValueRepr::String(Arc::new(value), StringType::Safe).into() } - /// Creates a value from a reference counted dynamic object. - pub(crate) fn from_rc_object(value: Arc) -> Value { - ValueRepr::Dynamic(value as Arc).into() - } - /// Creates a value from a dynamic object. - pub fn from_object(value: T) -> Value { - Value::from_rc_object(Arc::new(value)) - } - - /// Creates a callable value from a function. - pub fn from_function(f: F) -> Value - where - // the crazy bounds here exist to enable borrowing in closures - F: functions::Function - + for<'a> functions::Function>::Output>, - Rv: FunctionResult, - Args: for<'a> FunctionArgs<'a>, - { - functions::BoxedFunction::new(f).to_value() - } - - /// Returns some reference to the boxed object if it is of type `T`, or None if it isn’t. /// - /// This is basically the "reverse" of [`from_object`](Self::from_object). - /// - /// # Example + /// For more information see [`Object`]. /// /// ```rust /// # use minijinja::value::{Value, Object}; @@ -424,23 +420,33 @@ impl Value { /// /// impl Object for Thing {} /// - /// let x_value = Value::from_object(Thing { id: 42 }); - /// let thing = x_value.downcast_object_ref::().unwrap(); - /// assert_eq!(thing.id, 42); + /// let val = Value::from_object(Thing { id: 42 }); /// ``` - pub fn downcast_object_ref(&self) -> Option<&T> { - if let ValueRepr::Dynamic(ref obj) = self.0 { - if (**obj).type_id() == TypeId::of::() { - unsafe { - let raw: *const (dyn Object) = Arc::as_ptr(obj); - return (raw as *const u8 as *const T).as_ref(); - } - } - } - None + pub fn from_object(value: T) -> Value { + Value::from_rc_object(Arc::new(value)) + } + + /// Creates a callable value from a function. + /// + /// ``` + /// # use minijinja::value::Value; + /// let pow = Value::from_function(|a: u32| a * a); + /// ``` + pub fn from_function(f: F) -> Value + where + // the crazy bounds here exist to enable borrowing in closures + F: functions::Function + + for<'a> functions::Function>::Output>, + Rv: FunctionResult, + Args: for<'a> FunctionArgs<'a>, + { + functions::BoxedFunction::new(f).to_value() } - /// Returns the value kind. + /// Returns the kind of the value. + /// + /// This can be used to determine what's in the value before trying to + /// perform operations on it. pub fn kind(&self) -> ValueKind { match self.0 { ValueRepr::Undefined => ValueKind::Undefined, @@ -462,32 +468,6 @@ impl Value { matches!(self.0, ValueRepr::Map(_, MapType::Kwargs)) } - /// If the value is a string, return it. - pub fn as_str(&self) -> Option<&str> { - match &self.0 { - ValueRepr::String(ref s, _) => Some(s.as_str()), - _ => None, - } - } - - /// Returns the bytes of this value if they exist. - pub fn as_bytes(&self) -> Option<&[u8]> { - match &self.0 { - ValueRepr::String(ref s, _) => Some(s.as_bytes()), - ValueRepr::Bytes(ref b) => Some(&b[..]), - _ => None, - } - } - - /// Like `as_str` but always stringifies the value. - #[allow(unused)] - pub(crate) fn to_cowstr(&self) -> Cow<'_, str> { - match &self.0 { - ValueRepr::String(ref s, _) => Cow::Borrowed(s.as_str()), - _ => Cow::Owned(self.to_string()), - } - } - /// Is this value true? pub fn is_true(&self) -> bool { match self.0 { @@ -522,7 +502,51 @@ impl Value { matches!(&self.0, ValueRepr::None) } + /// If the value is a string, return it. + pub fn as_str(&self) -> Option<&str> { + match &self.0 { + ValueRepr::String(ref s, _) => Some(s.as_str()), + _ => None, + } + } + + /// Returns the bytes of this value if they exist. + pub fn as_bytes(&self) -> Option<&[u8]> { + match &self.0 { + ValueRepr::String(ref s, _) => Some(s.as_bytes()), + ValueRepr::Bytes(ref b) => Some(&b[..]), + _ => None, + } + } + + /// If the value is a sequence it's returned as slice. + /// + /// ``` + /// # use minijinja::value::Value; + /// let seq = Value::from(vec![1u32, 2, 3, 4]); + /// let slice = seq.as_slice().unwrap(); + /// assert_eq!(slice.len(), 4); + /// ``` + pub fn as_slice(&self) -> Result<&[Value], Error> { + match self.0 { + ValueRepr::Undefined | ValueRepr::None => Ok(&[][..]), + ValueRepr::Seq(ref v) => Ok(&v[..]), + _ => Err(Error::new( + ErrorKind::InvalidOperation, + format!("value of type {} is not a sequence", self.kind()), + )), + } + } + /// Returns the length of the contained value. + /// + /// Values without a length will return `None`. + /// + /// ``` + /// # use minijinja::value::Value; + /// let seq = Value::from(vec![1, 2, 3, 4]); + /// assert_eq!(seq.len(), Some(4)); + /// ``` pub fn len(&self) -> Option { match self.0 { ValueRepr::String(ref s, _) => Some(s.chars().count()), @@ -538,6 +562,17 @@ impl Value { /// This this returns [`UNDEFINED`](Self::UNDEFINED) when an invalid key is /// resolved. An error is returned when if the value does not contain an object /// that has attributes. + /// + /// ``` + /// # use minijinja::value::Value; + /// # fn test() -> Result<(), minijinja::Error> { + /// let ctx = minijinja::context! { + /// foo => "Foo" + /// }; + /// let value = ctx.get_attr("foo")?; + /// assert_eq!(value.to_string(), "Foo"); + /// # Ok(()) } + /// ``` pub fn get_attr(&self, key: &str) -> Result { let value = match self.0 { ValueRepr::Map(ref items, _) => { @@ -556,6 +591,13 @@ impl Value { /// Looks up an index of the value. /// /// This is a shortcut for [`get_item`](Self::get_item). + /// + /// ``` + /// # use minijinja::value::Value; + /// let seq = Value::from(vec![0u32, 1, 2]); + /// let value = seq.get_item_by_index(1).unwrap(); + /// assert_eq!(value.try_into().ok(), Some(1)); + /// ``` pub fn get_item_by_index(&self, idx: usize) -> Result { self.get_item(&Value(ValueRepr::U64(idx as _))) } @@ -566,6 +608,15 @@ impl Value { /// a string key this can be any key. For instance this can be used to /// index into sequences. Like [`get_attr`](Self::get_attr) this returns /// [`UNDEFINED`](Self::UNDEFINED) when an invalid key is looked up. + /// + /// ``` + /// # use minijinja::value::Value; + /// let ctx = minijinja::context! { + /// foo => "Foo", + /// }; + /// let value = ctx.get_item(&Value::from("foo")).unwrap(); + /// assert_eq!(value.to_string(), "Foo"); + /// ``` pub fn get_item(&self, key: &Value) -> Result { if let ValueRepr::Undefined = self.0 { Err(Error::from(ErrorKind::UndefinedError)) @@ -574,6 +625,76 @@ impl Value { } } + /// Iterates over the value. + /// + /// Depending on the [`kind`](Self::kind) of the value the iterator + /// has a different behavior. + /// + /// * [`ValueKind::Map`]: the iterator yields the keys of the map. + /// * [`ValueKind::Seq`]: the iterator yields the items in the sequence. + /// * [`ValueKind::None`] / [`ValueKind::Undefined`]: the iterator is empty. + /// + /// ``` + /// # use minijinja::value::Value; + /// # fn test() -> Result<(), minijinja::Error> { + /// let value = Value::from({ + /// let mut m = std::collections::BTreeMap::new(); + /// m.insert("foo", 42); + /// m.insert("bar", 23); + /// m + /// }); + /// for key in value.try_iter()? { + /// let value = value.get_item(&key)?; + /// println!("{} = {}", key, value); + /// } + /// # Ok(()) } + /// ``` + pub fn try_iter(&self) -> Result, Error> { + self.try_iter_owned().map(|inner| Iter { + _marker: PhantomData, + inner, + }) + } + + /// Returns some reference to the boxed object if it is of type `T`, or None if it isn’t. + /// + /// This is basically the "reverse" of [`from_object`](Self::from_object). + /// + /// # Example + /// + /// ```rust + /// # use minijinja::value::{Value, Object}; + /// use std::fmt; + /// + /// #[derive(Debug)] + /// struct Thing { + /// id: usize, + /// } + /// + /// impl fmt::Display for Thing { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// fmt::Debug::fmt(self, f) + /// } + /// } + /// + /// impl Object for Thing {} + /// + /// let x_value = Value::from_object(Thing { id: 42 }); + /// let thing = x_value.downcast_object_ref::().unwrap(); + /// assert_eq!(thing.id, 42); + /// ``` + pub fn downcast_object_ref(&self) -> Option<&T> { + if let ValueRepr::Dynamic(ref obj) = self.0 { + if (**obj).type_id() == TypeId::of::() { + unsafe { + let raw: *const (dyn Object) = Arc::as_ptr(obj); + return (raw as *const u8 as *const T).as_ref(); + } + } + } + None + } + fn get_item_opt(&self, key: &Value) -> Option { let key = some!(Key::from_borrowed_value(key).ok()); @@ -600,18 +721,6 @@ impl Value { None } - /// If the value is a sequence it's returned as slice. - pub fn as_slice(&self) -> Result<&[Value], Error> { - match self.0 { - ValueRepr::Undefined | ValueRepr::None => Ok(&[][..]), - ValueRepr::Seq(ref v) => Ok(&v[..]), - _ => Err(Error::new( - ErrorKind::InvalidOperation, - format!("value of type {} is not a sequence", self.kind()), - )), - } - } - /// Calls the value directly. pub(crate) fn call(&self, state: &State, args: &[Value]) -> Result { if let ValueRepr::Dynamic(ref dy) = self.0 { @@ -624,6 +733,15 @@ impl Value { } } + /// Like `as_str` but always stringifies the value. + #[allow(unused)] + pub(crate) fn to_cowstr(&self) -> Cow<'_, str> { + match &self.0 { + ValueRepr::String(ref s, _) => Cow::Borrowed(s.as_str()), + _ => Cow::Owned(self.to_string()), + } + } + /// Calls a method on the value. pub(crate) fn call_method( &self, @@ -646,6 +764,11 @@ impl Value { )) } + /// Creates a value from a reference counted dynamic object. + pub(crate) fn from_rc_object(value: Arc) -> Value { + ValueRepr::Dynamic(value as Arc).into() + } + pub(crate) fn try_into_key(self) -> Result { match self.0 { ValueRepr::Bool(val) => Ok(Key::Bool(val)), @@ -680,8 +803,8 @@ impl Value { } } - /// Iterates over the value. - pub(crate) fn try_iter(&self) -> Result { + /// Iterates over the value without holding a reference. + pub(crate) fn try_iter_owned(&self) -> Result { let (iter_state, len) = match self.0 { ValueRepr::None | ValueRepr::Undefined => (ValueIteratorState::Empty, 0), ValueRepr::Seq(ref seq) => (ValueIteratorState::Seq(0, Arc::clone(seq)), seq.len()), @@ -704,7 +827,7 @@ impl Value { )) } }; - Ok(ValueIterator { iter_state, len }) + Ok(OwnedValueIterator { iter_state, len }) } } @@ -758,12 +881,27 @@ impl Serialize for Value { } } -pub(crate) struct ValueIterator { +/// Iterates over a value. +pub struct Iter<'a> { + _marker: PhantomData<&'a Value>, + inner: OwnedValueIterator, +} + +impl<'a> Iterator for Iter<'a> { + type Item = Value; + + #[inline(always)] + fn next(&mut self) -> Option { + self.inner.next() + } +} + +pub(crate) struct OwnedValueIterator { iter_state: ValueIteratorState, len: usize, } -impl Iterator for ValueIterator { +impl Iterator for OwnedValueIterator { type Item = Value; fn next(&mut self) -> Option { @@ -778,9 +916,9 @@ impl Iterator for ValueIterator { } } -impl ExactSizeIterator for ValueIterator {} +impl ExactSizeIterator for OwnedValueIterator {} -impl fmt::Debug for ValueIterator { +impl fmt::Debug for OwnedValueIterator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ValueIterator").finish() } diff --git a/minijinja/src/vm/context.rs b/minijinja/src/vm/context.rs index 425190d2..cc3606d4 100644 --- a/minijinja/src/vm/context.rs +++ b/minijinja/src/vm/context.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use crate::environment::Environment; use crate::error::{Error, ErrorKind}; -use crate::value::{Value, ValueIterator}; +use crate::value::{OwnedValueIterator, Value}; use crate::vm::loop_object::Loop; type Locals<'env> = BTreeMap<&'env str, Value>; @@ -22,7 +22,7 @@ pub(crate) struct LoopState { // first item is the target jump instruction, the second argument // tells us if we need to end capturing. pub(crate) current_recursion_jump: Option<(usize, bool)>, - pub(crate) iterator: ValueIterator, + pub(crate) iterator: OwnedValueIterator, pub(crate) object: Arc, } diff --git a/minijinja/src/vm/mod.rs b/minijinja/src/vm/mod.rs index 8b2aa474..3abdbf47 100644 --- a/minijinja/src/vm/mod.rs +++ b/minijinja/src/vm/mod.rs @@ -723,7 +723,7 @@ impl<'env> Vm<'env> { pc: usize, current_recursion_jump: Option<(usize, bool)>, ) -> Result<(), Error> { - let iterator = ok!(iterable.try_iter()); + let iterator = ok!(iterable.try_iter_owned()); let len = iterator.len(); let depth = state .ctx