Skip to content

Commit

Permalink
feat(filters): Allow the input to index
Browse files Browse the repository at this point in the history
This does not fix it so the filter arguments support indexing.

Fixes #207
  • Loading branch information
epage committed Oct 5, 2018
1 parent aaa38b0 commit ceccb9b
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sudo: false
language: rust
rust:
- 1.22.0 # Oldest supported version
- 1.27.0 # Oldest supported version
- stable
- beta
- nightly
Expand Down
44 changes: 40 additions & 4 deletions liquid-compiler/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::collections::HashSet;
use std::iter::FromIterator;
use std::slice::Iter;

use liquid_interpreter::Argument;
use liquid_interpreter::Renderable;
use liquid_interpreter::Text;
use liquid_interpreter::Variable;
Expand Down Expand Up @@ -61,7 +62,7 @@ fn parse_expression(tokens: &[Token], options: &LiquidOptions) -> Result<Box<Ren
if tokens.len() > 1 && (tokens[1] == Token::Dot || tokens[1] == Token::OpenSquare) =>
{
let indexes = parse_indexes(&tokens[1..])?;
let mut result = Variable::new(x.clone());
let mut result = Variable::with_index(x.clone());
result.extend(indexes);
Ok(Box::new(result))
}
Expand Down Expand Up @@ -121,11 +122,22 @@ pub fn parse_indexes(mut tokens: &[Token]) -> Result<Vec<Index>> {
/// used internally, from a list of Tokens. This is mostly useful
/// for correctly parsing complex expressions with filters.
pub fn parse_output(tokens: &[Token]) -> Result<FilterChain> {
let entry = tokens[0].to_arg()?;
let first_pipe = tokens
.iter()
.enumerate()
.filter_map(|(i, t)| if *t == Token::Pipe { Some(i) } else { None })
.next()
.unwrap_or_else(|| tokens.len());

let mut entry = tokens[0].to_arg()?;
if let Argument::Var(ref mut entry) = &mut entry {
let indexes = parse_indexes(&tokens[1..first_pipe])?;
entry.extend(indexes);
}
let tokens = &tokens[first_pipe..];

let mut filters = vec![];
let mut iter = tokens.iter().peekable();
iter.next();

while iter.peek() != None {
expect(&mut iter, &Token::Pipe)?;
Expand Down Expand Up @@ -339,7 +351,31 @@ mod test_parse_output {
assert_eq!(
result.unwrap(),
FilterChain::new(
Argument::Var(Variable::new("abc")),
Argument::Var(Variable::with_index("abc")),
vec![
FilterCall::new(
"def",
vec![
Argument::Val(Value::scalar("1")),
Argument::Val(Value::scalar(2.0)),
Argument::Val(Value::scalar("3")),
],
),
FilterCall::new("blabla", vec![]),
]
)
);
}

#[test]
fn parses_index() {
let tokens = granularize("abc[0] | def:'1',2,'3' | blabla").unwrap();

let result = parse_output(&tokens);
assert_eq!(
result.unwrap(),
FilterChain::new(
Argument::Var(Variable::with_index("abc").push_index(0)),
vec![
FilterCall::new(
"def",
Expand Down
19 changes: 16 additions & 3 deletions liquid-interpreter/src/variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,24 @@ pub struct Variable {

impl Variable {
/// Create a `Value` reference.
#[deprecated(since = "0.16.1", note = "please use `with_index` instead")]
pub fn new<I: Into<Index>>(value: I) -> Self {
let path = Path::with_index(value);
Self { path }
}

/// Create a `Value` reference.
pub fn with_index<I: Into<Index>>(value: I) -> Self {
let path = Path::with_index(value);
Self { path }
}

/// Append an `Index`.
pub fn push_index<I: Into<Index>>(mut self, value: I) -> Self {
self.path = self.path.push(value);
self
}

/// The path to the variable in the stack.
pub fn path(&self) -> &Path {
&self.path
Expand Down Expand Up @@ -62,7 +75,7 @@ mod test {
test_a: ["test"]
"#,
).unwrap();
let mut actual = Variable::new("test_a");
let mut actual = Variable::with_index("test_a");
let index = vec![Index::with_index(0)];
actual.extend(index);

Expand All @@ -78,7 +91,7 @@ test_a: ["test"]
test_a: ["test1", "test2"]
"#,
).unwrap();
let mut actual = Variable::new("test_a");
let mut actual = Variable::with_index("test_a");
let index = vec![Index::with_index(-1)];
actual.extend(index);

Expand All @@ -95,7 +108,7 @@ test_a:
- test_h: 5
"#,
).unwrap();
let mut actual = Variable::new("test_a");
let mut actual = Variable::with_index("test_a");
let index = vec![Index::with_index(0), Index::with_key("test_h")];
actual.extend(index);

Expand Down
6 changes: 6 additions & 0 deletions liquid-value/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ impl fmt::Display for Index {
}
}

impl From<isize> for Index {
fn from(k: isize) -> Self {
Self::with_index(k)
}
}

impl From<String> for Index {
fn from(k: String) -> Self {
Self::with_key(k)
Expand Down
6 changes: 6 additions & 0 deletions liquid-value/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ impl Path {
Self { indexes }
}

/// Append an index.
pub fn push<I: Into<Index>>(mut self, value: I) -> Self {
self.indexes.push(value.into());
self
}

/// Access the `Value` reference.
pub fn iter(&self) -> IndexIter {
IndexIter(self.indexes.iter())
Expand Down
65 changes: 64 additions & 1 deletion src/tags/assign_tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,70 @@ mod test {
}

#[test]
fn assignment_in_loop_persists_on_loop_exit() {
fn assign() {
let text = concat!("{% assign freestyle = false %}", "{{ freestyle }}");
let tokens = compiler::tokenize(text).unwrap();
let options = options();
let template = compiler::parse(&tokens, &options)
.map(interpreter::Template::new)
.unwrap();

let mut context = Context::new();
let output = template.render(&mut context).unwrap();
assert_eq!(output, "false");
}

#[test]
fn assign_array_indexing() {
let text = concat!("{% assign freestyle = tags[1] %}", "{{ freestyle }}");
let tokens = compiler::tokenize(text).unwrap();
let options = options();
let template = compiler::parse(&tokens, &options)
.map(interpreter::Template::new)
.unwrap();

let mut context = Context::new();
context.stack_mut().set_global(
"tags",
Value::Array(vec![
Value::scalar("alpha"),
Value::scalar("beta"),
Value::scalar("gamma"),
]),
);

let output = template.render(&mut context).unwrap();
assert_eq!(output, "beta");
}

#[test]
fn assign_object_indexing() {
let text = concat!(
r#"{% assign freestyle = tags["greek"] %}"#,
"{{ freestyle }}"
);
let tokens = compiler::tokenize(text).unwrap();
let options = options();
let template = compiler::parse(&tokens, &options)
.map(interpreter::Template::new)
.unwrap();

let mut context = Context::new();
context.stack_mut().set_global(
"tags",
Value::Object(
vec![("greek".into(), Value::scalar("alpha"))]
.into_iter()
.collect(),
),
);

let output = template.render(&mut context).unwrap();
assert_eq!(output, "alpha");
}

#[test]
fn assign_in_loop_persists_on_loop_exit() {
let text = concat!(
"{% assign freestyle = false %}",
"{% for t in tags %}{% if t == 'freestyle' %}",
Expand Down

0 comments on commit ceccb9b

Please sign in to comment.