From 69bc84b7774f7404067c1d553535f61a462d7313 Mon Sep 17 00:00:00 2001 From: James MacAulay Date: Mon, 12 May 2008 17:11:42 -0400 Subject: [PATCH 1/5] Added filters for basic arithmetic --- lib/liquid/standardfilters.rb | 20 ++++++++++++++++++++ test/standard_filter_test.rb | 16 ++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 99a7078b..8aad420f 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -155,6 +155,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/test/standard_filter_test.rb b/test/standard_filter_test.rb index 06106c37..2765fc45 100644 --- a/test/standard_filter_test.rb +++ b/test/standard_filter_test.rb @@ -118,9 +118,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 From 282786d7e2deb728f82977d4db38b0f7a05e7e76 Mon Sep 17 00:00:00 2001 From: James MacAulay Date: Wed, 15 Oct 2008 15:06:58 -0400 Subject: [PATCH 2/5] if blocks work with filtered variables --- lib/liquid.rb | 9 +++++++-- lib/liquid/context.rb | 7 +++++++ lib/liquid/tags/if.rb | 2 +- lib/liquid/variable.rb | 4 ++-- test/if_else_test.rb | 7 +++++++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/liquid.rb b/lib/liquid.rb index 12b8f23f..642598db 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -22,7 +22,7 @@ $LOAD_PATH.unshift(File.dirname(__FILE__)) module Liquid - FilterSperator = /\|/ + FilterSeparator = /\|/ ArgumentSeparator = ',' FilterArgumentSeparator = ':' VariableAttributeSeparator = '.' @@ -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})/ TemplateParser = /(#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd})/ VariableParser = /\[[^\]]+\]|#{VariableSegment}+/ 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/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 16d7f99f..5074d1f6 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -19,8 +19,8 @@ module Liquid @filters = [] if match = markup.match(/\s*(#{QuotedFragment})/) @name = match[1] - if markup.match(/#{FilterSperator}\s*(.*)/) - filters = Regexp.last_match(1).split(/#{FilterSperator}/) + if markup.match(/#{FilterSeparator}\s*(.*)/) + filters = Regexp.last_match(1).split(/#{FilterSeparator}/) filters.each do |f| if matches = f.match(/\s*(\w+)/) diff --git a/test/if_else_test.rb b/test/if_else_test.rb index 5daef3f1..538c7850 100644 --- a/test/if_else_test.rb +++ b/test/if_else_test.rb @@ -112,6 +112,13 @@ 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 %}') + end + def test_syntax_error_no_variable assert_raise(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}')} end From edf7b5577b5aa60567335dff1e6bc1d2568eea68 Mon Sep 17 00:00:00 2001 From: James MacAulay Date: Wed, 15 Oct 2008 15:16:29 -0400 Subject: [PATCH 3/5] filtered variables for assign, case, and cycle --- lib/liquid/tags/assign.rb | 2 +- lib/liquid/tags/case.rb | 4 ++-- lib/liquid/tags/cycle.rb | 6 +++--- test/assign_test.rb | 11 +++++++++++ test/if_else_test.rb | 12 ++++++++++++ 5 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 test/assign_test.rb 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/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 538c7850..8e52d00a 100644 --- a/test/if_else_test.rb +++ b/test/if_else_test.rb @@ -117,6 +117,18 @@ class IfElseTest < Test::Unit::TestCase 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 From 3d8efe04a743d6fb4c3bc8d198f630f9896b853e Mon Sep 17 00:00:00 2001 From: James MacAulay Date: Fri, 24 Oct 2008 14:50:59 -0400 Subject: [PATCH 4/5] filtered variables in for loops --- lib/liquid/tags/for.rb | 2 +- test/standard_tag_test.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) 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/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) From 36860eab570f448a7bc11875665f0642ed0e462d Mon Sep 17 00:00:00 2001 From: James MacAulay Date: Tue, 28 Oct 2008 10:36:05 -0400 Subject: [PATCH 5/5] added array sorting and mapping on properties --- lib/liquid/standardfilters.rb | 22 ++++++++++++++++++++-- test/standard_filter_test.rb | 7 +++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 8aad420f..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 = '') diff --git a/test/standard_filter_test.rb b/test/standard_filter_test.rb index 2765fc45..54b776f3 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