diff --git a/lib/liquid.rb b/lib/liquid.rb
index a58575a1..363a6338 100644
--- a/lib/liquid.rb
+++ b/lib/liquid.rb
@@ -33,7 +33,12 @@ module Liquid
VariableStart = /\{\{/
VariableEnd = /\}\}/
VariableIncompleteEnd = /\}\}?/
- QuotedFragment = /"[^"]+"|'[^']+'|[^\s,|]+/
+ QuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|]+/
+ StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|,\:,\,]+/
+ FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
+ OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
+ SpacelessFilter = /#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
+ Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
AnyStartingTag = /\{\{|\{\%/
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb
index 69b76643..d1ca1092 100644
--- a/lib/liquid/context.rb
+++ b/lib/liquid/context.rb
@@ -133,6 +133,9 @@ module Liquid
:blank?
when 'empty'
:empty?
+ # filtered variables
+ when SpacelessFilter
+ filtered_variable(key)
# Single quoted strings
when /^'(.*)'$/
$1.to_s
@@ -221,5 +224,9 @@ module Liquid
object
end
+
+ def filtered_variable(markup)
+ Variable.new(markup).render(self)
+ end
end
end
diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb
index 99a7078b..254ac72a 100644
--- a/lib/liquid/standardfilters.rb
+++ b/lib/liquid/standardfilters.rb
@@ -63,9 +63,27 @@ module Liquid
end
# Sort elements of the array
- def sort(input)
- [input].flatten.sort
+ # provide optional property with which to sort an array of hashes or drops
+ def sort(input, property = nil)
+ ary = [input].flatten
+ if property.nil?
+ ary.sort
+ elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
+ ary.sort {|a,b| a[property] <=> b[property] }
+ elsif ary.first.respond_to?(property)
+ ary.sort {|a,b| a.send(property) <=> b.send(property) }
+ end
end
+
+ # map/collect on a given property
+ def map(input, property)
+ ary = [input].flatten
+ if ary.first.respond_to?('[]') and !ary.first[property].nil?
+ ary.map {|e| e[property] }
+ elsif ary.first.respond_to?(property)
+ ary.map {|e| e.send(property) }
+ end
+ end
# Replace occurrences of a string with another
def replace(input, string, replacement = '')
@@ -155,6 +173,26 @@ module Liquid
array.last if array.respond_to?(:last)
end
+ # addition
+ def plus(input, operand)
+ input + operand if input.respond_to?('+')
+ end
+
+ # subtraction
+ def minus(input, operand)
+ input - operand if input.respond_to?('-')
+ end
+
+ # multiplication
+ def times(input, operand)
+ input * operand if input.respond_to?('*')
+ end
+
+ # division
+ def divided_by(input, operand)
+ input / operand if input.respond_to?('/')
+ end
+
end
Template.register_filter(StandardFilters)
diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb
index a68cf30d..7b48b84f 100644
--- a/lib/liquid/tags/assign.rb
+++ b/lib/liquid/tags/assign.rb
@@ -9,7 +9,7 @@ module Liquid
# {{ monkey }}
#
class Assign < Tag
- Syntax = /(#{VariableSignature}+)\s*=\s*(#{QuotedFragment}+)/
+ Syntax = /(#{VariableSignature}+)\s*=\s*(#{Expression}+)/
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb
index 0733c51a..f6f24d4d 100644
--- a/lib/liquid/tags/case.rb
+++ b/lib/liquid/tags/case.rb
@@ -1,7 +1,7 @@
module Liquid
class Case < Block
- Syntax = /(#{QuotedFragment})/
- WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/
+ Syntax = /(#{Expression})/
+ WhenSyntax = /(#{Expression})(?:(?:\s+or\s+|\s*\,\s*)(#{Expression}.*))?/
def initialize(tag_name, markup, tokens)
@blocks = []
diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb
index a34f29af..71965ca2 100644
--- a/lib/liquid/tags/cycle.rb
+++ b/lib/liquid/tags/cycle.rb
@@ -13,8 +13,8 @@ module Liquid
#
Item five
#
class Cycle < Tag
- SimpleSyntax = /#{QuotedFragment}/
- NamedSyntax = /(#{QuotedFragment})\s*\:\s*(.*)/
+ SimpleSyntax = /#{Expression}/
+ NamedSyntax = /(#{Expression})\s*\:\s*(.*)/
def initialize(tag_name, markup, tokens)
case markup
@@ -49,7 +49,7 @@ module Liquid
def variables_from_string(markup)
markup.split(',').collect do |var|
- var =~ /\s*(#{QuotedFragment})\s*/
+ var =~ /\s*(#{Expression})\s*/
$1 ? $1 : nil
end.compact
end
diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb
index 5fb9d812..4ff86713 100644
--- a/lib/liquid/tags/for.rb
+++ b/lib/liquid/tags/for.rb
@@ -42,7 +42,7 @@ module Liquid
# forloop.last:: Returns true if the item is the last item.
#
class For < Block
- Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)\s*(reversed)?/
+ Syntax = /(\w+)\s+in\s+(#{Expression}+)\s*(reversed)?/
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb
index 8ff47088..a84d5677 100644
--- a/lib/liquid/tags/if.rb
+++ b/lib/liquid/tags/if.rb
@@ -13,7 +13,7 @@ module Liquid
#
class If < Block
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
- Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/
+ Syntax = /(#{Expression})\s*([=!<>a-z_]+)?\s*(#{Expression})?/
def initialize(tag_name, markup, tokens)
diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb
index d16a2168..cf585186 100644
--- a/lib/liquid/variable.rb
+++ b/lib/liquid/variable.rb
@@ -21,8 +21,7 @@ module Liquid
@name = match[1]
if markup.match(/#{FilterSeparator}\s*(.*)/)
filters = Regexp.last_match(1).split(/#{FilterSeparator}/)
-
- filters.each do |f|
+ filters.each do |f|
if matches = f.match(/\s*(\w+)/)
filtername = matches[1]
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/).flatten
diff --git a/test/assign_test.rb b/test/assign_test.rb
new file mode 100644
index 00000000..15d00e53
--- /dev/null
+++ b/test/assign_test.rb
@@ -0,0 +1,11 @@
+require File.dirname(__FILE__) + '/helper'
+
+class IfElseTest < Test::Unit::TestCase
+ include Liquid
+
+ def test_with_filtered_expressions
+ assert_template_result('foo','{% assign foo = values|sort|last %}{{ foo }}', 'values' => %w{foo bar baz})
+ assert_template_result('foo','{% assign sorted = values|sort %}{{ sorted | last }}', 'values' => %w{foo bar baz})
+ end
+
+end
\ No newline at end of file
diff --git a/test/if_else_test.rb b/test/if_else_test.rb
index 5daef3f1..8e52d00a 100644
--- a/test/if_else_test.rb
+++ b/test/if_else_test.rb
@@ -112,6 +112,25 @@ class IfElseTest < Test::Unit::TestCase
assert_template_result('elsif','{% if false %}if{% elsif true %}elsif{% endif %}')
end
+ def test_with_filtered_expressions
+ assert_template_result('yes','{% if "BLAH"|downcase == "blah" %}yes{% endif %}')
+ assert_template_result('yes','{% if "FOO BAR"|truncatewords:1,"--" == "FOO--" %}yes{% endif %}')
+ assert_template_result('yes','{% if "FOO BAR"|truncatewords:1,"--"|downcase == "foo--" %}yes{% endif %}')
+ assert_template_result('yes','{% if "foo--" == "FOO BAR"|truncatewords:1,"--"|downcase %}yes{% endif %}')
+ # array transformation, to make sure we aren't converting arrays to strings somewhere along the way:
+ assert_template_result('yes','{% if values|sort == sorted %}yes{% endif %}', 'values' => %w{foo bar baz}, 'sorted' => %w{bar baz foo})
+ end
+
+ def test_allow_no_spaces_in_filtered_expressions
+ assert_template_result('','{% if "foo--" == "FOO BAR" |truncatewords:1,"--"|downcase %}yes{% endif %}')
+ assert_template_result('','{% if "foo--" == "FOO BAR"| truncatewords:1,"--"|downcase %}yes{% endif %}')
+ assert_template_result('','{% if "foo--" == "FOO BAR"|truncatewords :1,"--"|downcase %}yes{% endif %}')
+ assert_template_result('','{% if "foo--" == "FOO BAR"|truncatewords: 1,"--"|downcase %}yes{% endif %}')
+ assert_template_result('','{% if "foo--" == "FOO BAR"|truncatewords:1 ,"--"|downcase %}yes{% endif %}')
+ assert_template_result('','{% if "foo--" == "FOO BAR"|truncatewords:1, "--"|downcase %}yes{% endif %}')
+ assert_template_result('','{% if "foo--" == "FOO BAR"|truncatewords:1,"--" |downcase %}yes{% endif %}')
+ end
+
def test_syntax_error_no_variable
assert_raise(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}')}
end
diff --git a/test/standard_filter_test.rb b/test/standard_filter_test.rb
index 9d62ecd7..5e34924a 100644
--- a/test/standard_filter_test.rb
+++ b/test/standard_filter_test.rb
@@ -67,6 +67,13 @@ class StandardFiltersTest < Test::Unit::TestCase
def test_sort
assert_equal [1,2,3,4], @filters.sort([4,3,2,1])
+ assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
+ end
+
+ def test_map
+ assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
+ assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
+ 'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
end
def test_date
@@ -118,9 +125,25 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_template_result "a
\nb
\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc"
end
+ def test_plus
+ assert_template_result "2", "{{ 1 | plus:1 }}"
+ assert_template_result "11", "{{ '1' | plus:'1' }}"
+ end
+ def test_minus
+ assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
+ end
+ def test_times
+ assert_template_result "12", "{{ 3 | times:4 }}"
+ assert_template_result "foofoofoofoo", "{{ 'foo' | times:4 }}"
+ end
+ def test_divided_by
+ assert_template_result "4", "{{ 12 | divided_by:3 }}"
+ assert_template_result "4", "{{ 14 | divided_by:3 }}"
+ assert_template_result "5", "{{ 15 | divided_by:3 }}"
+ end
end
diff --git a/test/standard_tag_test.rb b/test/standard_tag_test.rb
index 0b9dc8ac..aae09bda 100644
--- a/test/standard_tag_test.rb
+++ b/test/standard_tag_test.rb
@@ -99,6 +99,10 @@ HERE
assert_template_result('+--', '{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}', assigns)
end
+ def test_for_with_filtered_expressions
+ assert_template_result('abc','{% for letter in letters|sort %}{{ letter }}{% endfor %}', 'letters' => %w{c b a})
+ end
+
def test_limiting
assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
assert_template_result('12','{%for i in array limit:2 %}{{ i }}{%endfor%}',assigns)