diff --git a/CHANGELOG.md b/CHANGELOG.md index 772c8028..b5b89878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ All notable changes to MiniJinja are documented here. - Added `indent` filter. (#151) - Added the `map`, `select` / `selectattr` and `reject` / `rejectattr` filters. - Added `safe` / `escaped` test. +- Strings now have the same iteration behavior as in Jinja2. (#152) ## Breaking Changes diff --git a/minijinja/src/filters.rs b/minijinja/src/filters.rs index 63375b8a..fefb7983 100644 --- a/minijinja/src/filters.rs +++ b/minijinja/src/filters.rs @@ -593,15 +593,10 @@ mod builtins { /// an empty list is returned. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn list(value: Value) -> Result { - if let Some(s) = value.as_str() { - Ok(Value::from(s.chars().map(Value::from).collect::>())) - } else { - let iter = ok!(value.try_iter().map_err(|err| { - Error::new(ErrorKind::InvalidOperation, "cannot convert value to list") - .with_source(err) - })); - Ok(Value::from(iter.collect::>())) - } + let iter = ok!(value.try_iter().map_err(|err| { + Error::new(ErrorKind::InvalidOperation, "cannot convert value to list").with_source(err) + })); + Ok(Value::from(iter.collect::>())) } /// Converts the value into a boolean value. diff --git a/minijinja/src/value/mod.rs b/minijinja/src/value/mod.rs index 786e5c1c..139825e5 100644 --- a/minijinja/src/value/mod.rs +++ b/minijinja/src/value/mod.rs @@ -923,6 +923,10 @@ impl Value { pub(crate) fn try_iter_owned(&self) -> Result { let (iter_state, len) = match self.0 { ValueRepr::None | ValueRepr::Undefined => (ValueIteratorState::Empty, 0), + ValueRepr::String(ref s, _) => ( + ValueIteratorState::Chars(0, Arc::clone(s)), + s.chars().count(), + ), ValueRepr::Seq(ref seq) => (ValueIteratorState::Seq(0, Arc::clone(seq)), seq.len()), #[cfg(feature = "preserve_order")] ValueRepr::Map(ref items, _) => { @@ -1093,6 +1097,7 @@ impl fmt::Debug for OwnedValueIterator { enum ValueIteratorState { Empty, + Chars(usize, Arc), Seq(usize, Arc>), StaticStr(usize, &'static [&'static str]), ArcStr(usize, Vec>), @@ -1107,6 +1112,12 @@ impl ValueIteratorState { fn advance_state(&mut self) -> Option { match self { ValueIteratorState::Empty => None, + ValueIteratorState::Chars(offset, ref s) => { + s.as_str()[*offset..].chars().next().map(|c| { + *offset += c.len_utf8(); + Value::from(c) + }) + } ValueIteratorState::Seq(idx, items) => items .get(*idx) .map(|x| { diff --git a/minijinja/tests/inputs/loop_over_string.txt b/minijinja/tests/inputs/loop_over_string.txt new file mode 100644 index 00000000..525aa774 --- /dev/null +++ b/minijinja/tests/inputs/loop_over_string.txt @@ -0,0 +1,5 @@ +{} +--- +{% for char in "abcdefg" -%} + {{ char }} +{% endfor %} diff --git a/minijinja/tests/snapshots/test_templates__vm@loop_over_string.txt.snap b/minijinja/tests/snapshots/test_templates__vm@loop_over_string.txt.snap new file mode 100644 index 00000000..9b3c3929 --- /dev/null +++ b/minijinja/tests/snapshots/test_templates__vm@loop_over_string.txt.snap @@ -0,0 +1,15 @@ +--- +source: minijinja/tests/test_templates.rs +description: "{% for char in \"abcdefg\" -%}\n {{ char }}\n{% endfor %}" +info: {} +input_file: minijinja/tests/inputs/loop_over_string.txt +--- +a +b +c +d +e +f +g + +