Merge branch 'master' into recursive-parsing

This commit is contained in:
Tristan Hume 2013-08-22 10:39:08 -04:00
commit 14a17520de
7 changed files with 178 additions and 10 deletions

View File

@ -6,6 +6,11 @@ The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are
## 2.6.0 / Master branch (not yet released)
* ...
* Make sort filter work on enumerable drops, see #239 [Florian Weingarten, fw42]
* Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42]
* Make map filter work on enumerable drops, see #233 [Florian Weingarten, fw42]
* Fix security issue with map filter, see #230, #232, #234, #237 [Florian Weingarten, fw42]
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
* Bugfix for #106: fix example servlet [gnowoel]
* Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss, joliss]
* Bugfix for #114: strip_html filter supports style tags [James Allardice, jamesallardice]

View File

@ -4,6 +4,7 @@
* [Version history](History.md)
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
* [Liquid Wiki from Shopify](http://wiki.shopify.com/Liquid)
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
* [Website](http://liquidmarkup.org/)
## Introduction

View File

@ -44,6 +44,10 @@ module Liquid
true
end
def inspect
self.class.to_s
end
def to_liquid
self
end
@ -54,7 +58,16 @@ module Liquid
# Check for method existence without invoking respond_to?, which creates symbols
def self.invokable?(method_name)
@invokable_methods ||= Set.new((public_instance_methods - Liquid::Drop.public_instance_methods).map(&:to_s))
unless @invokable_methods
# Ruby 1.8 compatibility: call to_s on method names (which are strings in 1.8, but already symbols in 1.9)
blacklist = (Liquid::Drop.public_instance_methods + [:each]).map(&:to_s)
if include?(Enumerable)
blacklist += Enumerable.public_instance_methods.map(&:to_s)
blacklist -= [:sort, :count, :first, :min, :max, :include?].map(&:to_s)
end
whitelist = [:to_liquid] + (public_instance_methods.map(&:to_s) - blacklist.map(&:to_s))
@invokable_methods = Set.new(whitelist.map(&:to_s))
end
@invokable_methods.include?(method_name.to_s)
end
end

View File

@ -81,7 +81,7 @@ module Liquid
# Sort elements of the array
# provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil)
ary = [input].flatten
ary = flatten_if_necessary(input)
if property.nil?
ary.sort
elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
@ -99,11 +99,14 @@ module Liquid
# 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) }
flatten_if_necessary(input).map do |e|
e = e.call if e.is_a?(Proc)
if property == "to_liquid"
e
elsif e.respond_to?(:[])
e[property]
end
end
end
@ -244,6 +247,17 @@ module Liquid
private
def flatten_if_necessary(input)
ary = if input.is_a?(Array)
input.flatten
elsif input.kind_of?(Enumerable)
input
else
[input].flatten
end
ary.map{ |e| e.respond_to?(:to_liquid) ? e.to_liquid : e }
end
def to_number(obj)
case obj
when Float

View File

@ -12,6 +12,7 @@ Gem::Specification.new do |s|
s.authors = ["Tobias Luetke"]
s.email = ["tobi@leetsoft.com"]
s.homepage = "http://www.liquidmarkup.org"
s.license = "MIT"
#s.description = "A secure, non-evaling end user template engine with aesthetic markup."
s.required_rubygems_version = ">= 1.3.7"

View File

@ -55,11 +55,44 @@ class ProductDrop < Liquid::Drop
end
class EnumerableDrop < Liquid::Drop
def before_method(method)
method
end
def size
3
end
def first
1
end
def count
3
end
def min
1
end
def max
3
end
def each
yield 1
yield 2
yield 3
end
end
class RealEnumerableDrop < Liquid::Drop
include Enumerable
def before_method(method)
method
end
def each
yield 1
yield 2
@ -71,23 +104,34 @@ class DropsTest < Test::Unit::TestCase
include Liquid
def test_product_drop
assert_nothing_raised do
tpl = Liquid::Template.parse( ' ' )
tpl.render('product' => ProductDrop.new)
end
end
def test_drop_does_only_respond_to_whitelisted_methods
assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render('product' => ProductDrop.new)
end
def test_drops_respond_to_to_liquid
assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render('product' => ProductDrop.new)
assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render('product' => ProductDrop.new)
end
def test_text_drop
output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render('product' => ProductDrop.new)
assert_equal ' text1 ', output
end
def test_unknown_method
output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render('product' => ProductDrop.new)
assert_equal ' method: unknown ', output
end
def test_integer_argument_drop
@ -159,6 +203,33 @@ class DropsTest < Test::Unit::TestCase
assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new)
end
def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
["select", "each", "map", "cycle"].each do |method|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
end
end
def test_some_enumerable_methods_still_get_invoked
[ :count, :max ].each do |method|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
end
assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render('collection' => RealEnumerableDrop.new)
[ :min, :first ].each do |method|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
end
end
def test_empty_string_value_access
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => '')
end

View File

@ -6,6 +6,39 @@ class Filters
include Liquid::StandardFilters
end
class TestThing
def initialize
@foo = 0
end
def to_s
"woot: #{@foo}"
end
def [](whatever)
to_s
end
def to_liquid
@foo += 1
self
end
end
class TestDrop < Liquid::Drop
def test
"testfoo"
end
end
class TestEnumerable < Liquid::Drop
include Enumerable
def each(&block)
[ { "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 } ].each(&block)
end
end
class StandardFiltersTest < Test::Unit::TestCase
include Liquid
@ -97,6 +130,36 @@ class StandardFiltersTest < Test::Unit::TestCase
'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
end
def test_map_doesnt_call_arbitrary_stuff
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "__id__" }}').render
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "inspect" }}').render
end
def test_map_calls_to_liquid
t = TestThing.new
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | map: "whatever" }}').render("foo" => [t])
end
def test_sort_calls_to_liquid
t = TestThing.new
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
end
def test_map_over_proc
drop = TestDrop.new
p = Proc.new{ drop }
templ = '{{ procs | map: "test" }}'
assert_equal "testfoo", Liquid::Template.parse(templ).render("procs" => [p])
end
def test_map_works_on_enumerables
assert_equal "123", Liquid::Template.parse('{{ foo | map: "foo" }}').render!("foo" => TestEnumerable.new)
end
def test_sort_works_on_enumerables
assert_equal "213", Liquid::Template.parse('{{ foo | sort: "bar" | map: "foo" }}').render!("foo" => TestEnumerable.new)
end
def test_date
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")