mirror of
https://github.com/kemko/liquid.git
synced 2026-01-06 10:15:40 +03:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
712d97e37d | ||
|
|
ca72282dff | ||
|
|
e8a3fd10d4 | ||
|
|
e77b1a09b6 | ||
|
|
73b39beef2 | ||
|
|
fc63219087 | ||
|
|
53b6db48e3 | ||
|
|
0bbc22b027 | ||
|
|
145920738b | ||
|
|
6eb1f174de | ||
|
|
2e71ce1efe | ||
|
|
8204c61e31 | ||
|
|
f7d1e1d0c1 | ||
|
|
28fd2222c8 | ||
|
|
9913895b81 | ||
|
|
d706db3bd7 | ||
|
|
38b4543bf1 | ||
|
|
1300210f05 | ||
|
|
a48e162237 | ||
|
|
7bcb565668 | ||
|
|
c3e6cde67f | ||
|
|
50bd34fd78 | ||
|
|
ee41b3f4a3 | ||
|
|
05d9976e16 |
30
History.md
30
History.md
@@ -1,5 +1,35 @@
|
|||||||
# Liquid Version History
|
# Liquid Version History
|
||||||
|
|
||||||
|
## 2.5.5 / 2014-01-10 / branch "2-5-stable"
|
||||||
|
|
||||||
|
Security fix, cherry-picked from master (4e14a65):
|
||||||
|
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
|
||||||
|
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
|
||||||
|
|
||||||
|
## 2.5.4 / 2013-11-11
|
||||||
|
|
||||||
|
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
|
||||||
|
|
||||||
|
## 2.5.3 / 2013-10-09
|
||||||
|
|
||||||
|
* #232, #234, #237: Fix map filter bugs [Florian Weingarten, fw42]
|
||||||
|
|
||||||
|
## 2.5.2 / 2013-09-03 / deleted
|
||||||
|
|
||||||
|
Yanked from rubygems, as it contained too many changes that broke compatibility. Those changes will be on following major releases.
|
||||||
|
|
||||||
|
## 2.5.1 / 2013-07-24
|
||||||
|
|
||||||
|
* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten, fw42]
|
||||||
|
|
||||||
|
## 2.5.0 / 2013-03-06
|
||||||
|
|
||||||
|
* Prevent Object methods from being called on drops
|
||||||
|
* Avoid symbol injection from liquid
|
||||||
|
* Added break and continue statements
|
||||||
|
* Fix filter parser for args without space separators
|
||||||
|
* Add support for filter keyword arguments
|
||||||
|
|
||||||
## 2.4.0 / 2012-08-03
|
## 2.4.0 / 2012-08-03
|
||||||
|
|
||||||
* Performance improvements
|
* Performance improvements
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ module Liquid
|
|||||||
|
|
||||||
filters.each do |f|
|
filters.each do |f|
|
||||||
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
|
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
|
||||||
|
Strainer.add_known_filter(f)
|
||||||
strainer.extend(f)
|
strainer.extend(f)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -71,11 +72,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def invoke(method, *args)
|
def invoke(method, *args)
|
||||||
if strainer.respond_to?(method)
|
strainer.invoke(method, *args)
|
||||||
strainer.__send__(method, *args)
|
|
||||||
else
|
|
||||||
args.first
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
require 'set'
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# A drop in liquid is a class which allows you to export DOM like things to liquid.
|
# A drop in liquid is a class which allows you to export DOM like things to liquid.
|
||||||
@@ -31,8 +33,8 @@ module Liquid
|
|||||||
|
|
||||||
# called by liquid to invoke a drop
|
# called by liquid to invoke a drop
|
||||||
def invoke_drop(method_or_key)
|
def invoke_drop(method_or_key)
|
||||||
if method_or_key && method_or_key != EMPTY_STRING && self.class.public_method_defined?(method_or_key.to_s.to_sym)
|
if method_or_key && method_or_key != EMPTY_STRING && self.class.invokable?(method_or_key)
|
||||||
send(method_or_key.to_s.to_sym)
|
send(method_or_key)
|
||||||
else
|
else
|
||||||
before_method(method_or_key)
|
before_method(method_or_key)
|
||||||
end
|
end
|
||||||
@@ -47,5 +49,13 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
alias :[] :invoke_drop
|
alias :[] :invoke_drop
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Check for method existence without invoking respond_to?, which creates symbols
|
||||||
|
def self.invokable?(method_name)
|
||||||
|
@invokable_methods ||= Set.new(["to_liquid"] + (public_instance_methods - Liquid::Drop.public_instance_methods).map(&:to_s))
|
||||||
|
@invokable_methods.include?(method_name.to_s)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -93,21 +93,26 @@ module Liquid
|
|||||||
# map/collect on a given property
|
# map/collect on a given property
|
||||||
def map(input, property)
|
def map(input, property)
|
||||||
ary = [input].flatten
|
ary = [input].flatten
|
||||||
if ary.first.respond_to?('[]') and !ary.first[property].nil?
|
ary.map do |e|
|
||||||
ary.map {|e| e[property] }
|
e = e.call if e.is_a?(Proc)
|
||||||
elsif ary.first.respond_to?(property)
|
e = e.to_liquid if e.respond_to?(:to_liquid)
|
||||||
ary.map {|e| e.send(property) }
|
|
||||||
|
if property == "to_liquid"
|
||||||
|
e
|
||||||
|
elsif e.respond_to?(:[])
|
||||||
|
e[property]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace occurrences of a string with another
|
# Replace occurrences of a string with another
|
||||||
def replace(input, string, replacement = '')
|
def replace(input, string, replacement = '')
|
||||||
input.to_s.gsub(string, replacement)
|
input.to_s.gsub(string, replacement.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace the first occurrences of a string with another
|
# Replace the first occurrences of a string with another
|
||||||
def replace_first(input, string, replacement = '')
|
def replace_first(input, string, replacement = '')
|
||||||
input.to_s.sub(string, replacement)
|
input.to_s.sub(string, replacement.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove a substring
|
# remove a substring
|
||||||
|
|||||||
@@ -2,24 +2,15 @@ require 'set'
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
parent_object = if defined? BlankObject
|
|
||||||
BlankObject
|
|
||||||
else
|
|
||||||
Object
|
|
||||||
end
|
|
||||||
|
|
||||||
# Strainer is the parent class for the filters system.
|
# Strainer is the parent class for the filters system.
|
||||||
# New filters are mixed into the strainer class which is then instanciated for each liquid template render run.
|
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
|
||||||
#
|
#
|
||||||
# One of the strainer's responsibilities is to keep malicious method calls out
|
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
|
||||||
class Strainer < parent_object #:nodoc:
|
# Context#add_filters or Template.register_filter
|
||||||
INTERNAL_METHOD = /^__/
|
class Strainer #:nodoc:
|
||||||
@@required_methods = Set.new([:__id__, :__send__, :respond_to?, :kind_of?, :extend, :methods, :singleton_methods, :class, :object_id])
|
|
||||||
|
|
||||||
# Ruby 1.9.2 introduces Object#respond_to_missing?, which is invoked by Object#respond_to?
|
|
||||||
@@required_methods << :respond_to_missing? if Object.respond_to? :respond_to_missing?
|
|
||||||
|
|
||||||
@@filters = {}
|
@@filters = {}
|
||||||
|
@@known_filters = Set.new
|
||||||
|
@@known_methods = Set.new
|
||||||
|
|
||||||
def initialize(context)
|
def initialize(context)
|
||||||
@context = context
|
@context = context
|
||||||
@@ -27,28 +18,36 @@ module Liquid
|
|||||||
|
|
||||||
def self.global_filter(filter)
|
def self.global_filter(filter)
|
||||||
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
|
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
|
||||||
|
add_known_filter(filter)
|
||||||
@@filters[filter.name] = filter
|
@@filters[filter.name] = filter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.add_known_filter(filter)
|
||||||
|
unless @@known_filters.include?(filter)
|
||||||
|
@@method_blacklist ||= Set.new(Strainer.instance_methods.map(&:to_s))
|
||||||
|
new_methods = filter.instance_methods.map(&:to_s)
|
||||||
|
new_methods.reject!{ |m| @@method_blacklist.include?(m) }
|
||||||
|
@@known_methods.merge(new_methods)
|
||||||
|
@@known_filters.add(filter)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.create(context)
|
def self.create(context)
|
||||||
strainer = Strainer.new(context)
|
strainer = Strainer.new(context)
|
||||||
@@filters.each { |k,m| strainer.extend(m) }
|
@@filters.each { |k,m| strainer.extend(m) }
|
||||||
strainer
|
strainer
|
||||||
end
|
end
|
||||||
|
|
||||||
def respond_to?(method, include_private = false)
|
def invoke(method, *args)
|
||||||
method_name = method.to_s
|
if invokable?(method)
|
||||||
return false if method_name =~ INTERNAL_METHOD
|
send(method, *args)
|
||||||
return false if @@required_methods.include?(method_name)
|
else
|
||||||
super
|
args.first
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove all standard methods from the bucket so circumvent security
|
def invokable?(method)
|
||||||
# problems
|
@@known_methods.include?(method.to_s) && respond_to?(method)
|
||||||
instance_methods.each do |m|
|
|
||||||
unless @@required_methods.include?(m.to_sym)
|
|
||||||
undef_method m
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ module Liquid
|
|||||||
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
|
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
|
||||||
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
|
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
|
||||||
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
||||||
|
BOOLEAN_OPERATORS = %w(and or)
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, tokens)
|
||||||
@blocks = []
|
@blocks = []
|
||||||
@@ -61,7 +62,8 @@ module Liquid
|
|||||||
raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
|
raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
|
||||||
|
|
||||||
new_condition = Condition.new($1, $2, $3)
|
new_condition = Condition.new($1, $2, $3)
|
||||||
new_condition.send(operator.to_sym, condition)
|
raise SyntaxError, "invalid boolean operator" unless BOOLEAN_OPERATORS.include?(operator)
|
||||||
|
new_condition.send(operator, condition)
|
||||||
condition = new_condition
|
condition = new_condition
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -71,8 +73,6 @@ module Liquid
|
|||||||
@blocks.push(block)
|
@blocks.push(block)
|
||||||
@nodelist = block.attach(Array.new)
|
@nodelist = block.attach(Array.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('if', If)
|
Template.register_tag('if', If)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module Liquid
|
|||||||
# {{ user | link }}
|
# {{ user | link }}
|
||||||
#
|
#
|
||||||
class Variable
|
class Variable
|
||||||
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?!(?:#{FilterSeparator}))(?:#{QuotedFragment}|\S+)\s*)+)/o
|
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
|
||||||
attr_accessor :filters, :name
|
attr_accessor :filters, :name
|
||||||
|
|
||||||
def initialize(markup)
|
def initialize(markup)
|
||||||
@@ -23,10 +23,10 @@ module Liquid
|
|||||||
if match[2].match(/#{FilterSeparator}\s*(.*)/o)
|
if match[2].match(/#{FilterSeparator}\s*(.*)/o)
|
||||||
filters = Regexp.last_match(1).scan(FilterParser)
|
filters = Regexp.last_match(1).scan(FilterParser)
|
||||||
filters.each do |f|
|
filters.each do |f|
|
||||||
if matches = f.match(/\s*(\w+)/)
|
if matches = f.match(/\s*(\w+)(?:\s*#{FilterArgumentSeparator}(.*))?/)
|
||||||
filtername = matches[1]
|
filtername = matches[1]
|
||||||
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/o).flatten
|
filterargs = matches[2].to_s.scan(/(?:\A|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
||||||
@filters << [filtername.to_sym, filterargs]
|
@filters << [filtername, filterargs]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -36,9 +36,16 @@ module Liquid
|
|||||||
def render(context)
|
def render(context)
|
||||||
return '' if @name.nil?
|
return '' if @name.nil?
|
||||||
@filters.inject(context[@name]) do |output, filter|
|
@filters.inject(context[@name]) do |output, filter|
|
||||||
filterargs = filter[1].to_a.collect do |a|
|
filterargs = []
|
||||||
context[a]
|
keyword_args = {}
|
||||||
|
filter[1].to_a.each do |a|
|
||||||
|
if matches = a.match(/\A#{TagAttributes}\z/o)
|
||||||
|
keyword_args[matches[1]] = context[matches[2]]
|
||||||
|
else
|
||||||
|
filterargs << context[a]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
filterargs << keyword_args unless keyword_args.empty?
|
||||||
begin
|
begin
|
||||||
output = context.invoke(filter[0], output, *filterargs)
|
output = context.invoke(filter[0], output, *filterargs)
|
||||||
rescue FilterNotFound
|
rescue FilterNotFound
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "liquid"
|
s.name = "liquid"
|
||||||
s.version = "2.4.1"
|
s.version = "2.5.5"
|
||||||
s.platform = Gem::Platform::RUBY
|
s.platform = Gem::Platform::RUBY
|
||||||
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
|
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||||
s.authors = ["Tobias Luetke"]
|
s.authors = ["Tobias Luetke"]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ require File.dirname(__FILE__) + '/theme_runner'
|
|||||||
profiler = ThemeRunner.new
|
profiler = ThemeRunner.new
|
||||||
|
|
||||||
Benchmark.bmbm do |x|
|
Benchmark.bmbm do |x|
|
||||||
x.report("parse & run:") { 10.times { profiler.run(false) } }
|
x.report("parse:") { 100.times { profiler.compile } }
|
||||||
|
x.report("parse & run:") { 100.times { profiler.run } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,33 @@ class ThemeRunner
|
|||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def compile
|
||||||
|
# Dup assigns because will make some changes to them
|
||||||
|
|
||||||
def run()
|
@tests.each do |liquid, layout, template_name|
|
||||||
|
|
||||||
|
tmpl = Liquid::Template.new
|
||||||
|
tmpl.parse(liquid)
|
||||||
|
tmpl = Liquid::Template.new
|
||||||
|
tmpl.parse(layout)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run
|
||||||
|
# Dup assigns because will make some changes to them
|
||||||
|
assigns = Database.tables.dup
|
||||||
|
|
||||||
|
@tests.each do |liquid, layout, template_name|
|
||||||
|
|
||||||
|
# Compute page_tempalte outside of profiler run, uninteresting to profiler
|
||||||
|
page_template = File.basename(template_name, File.extname(template_name))
|
||||||
|
compile_and_render(liquid, layout, assigns, page_template)
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def run_profile
|
||||||
RubyProf.measure_mode = RubyProf::WALL_TIME
|
RubyProf.measure_mode = RubyProf::WALL_TIME
|
||||||
|
|
||||||
# Dup assigns because will make some changes to them
|
# Dup assigns because will make some changes to them
|
||||||
|
|||||||
@@ -189,10 +189,10 @@ class ContextTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
context = Context.new
|
context = Context.new
|
||||||
methods_before = context.strainer.methods.map { |method| method.to_s }
|
assert_equal "Wookie", context.invoke("hi", "Wookie")
|
||||||
|
|
||||||
context.add_filters(filter)
|
context.add_filters(filter)
|
||||||
methods_after = context.strainer.methods.map { |method| method.to_s }
|
assert_equal "Wookie hi!", context.invoke("hi", "Wookie")
|
||||||
assert_equal (methods_before + ["hi"]).sort, methods_after.sort
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_add_item_in_outer_scope
|
def test_add_item_in_outer_scope
|
||||||
|
|||||||
@@ -71,23 +71,34 @@ class DropsTest < Test::Unit::TestCase
|
|||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_product_drop
|
def test_product_drop
|
||||||
|
|
||||||
assert_nothing_raised do
|
assert_nothing_raised do
|
||||||
tpl = Liquid::Template.parse( ' ' )
|
tpl = Liquid::Template.parse( ' ' )
|
||||||
tpl.render('product' => ProductDrop.new)
|
tpl.render('product' => ProductDrop.new)
|
||||||
end
|
end
|
||||||
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
|
def test_text_drop
|
||||||
output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render('product' => ProductDrop.new)
|
output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render('product' => ProductDrop.new)
|
||||||
assert_equal ' text1 ', output
|
assert_equal ' text1 ', output
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unknown_method
|
def test_unknown_method
|
||||||
output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render('product' => ProductDrop.new)
|
output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render('product' => ProductDrop.new)
|
||||||
assert_equal ' method: unknown ', output
|
assert_equal ' method: unknown ', output
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_integer_argument_drop
|
def test_integer_argument_drop
|
||||||
@@ -115,6 +126,13 @@ class DropsTest < Test::Unit::TestCase
|
|||||||
assert_equal ' ', output
|
assert_equal ' ', output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_object_methods_not_allowed
|
||||||
|
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
|
||||||
|
output = Liquid::Template.parse(" {{ product.#{method} }} ").render('product' => ProductDrop.new)
|
||||||
|
assert_equal ' ', output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_scope
|
def test_scope
|
||||||
assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render('context' => ContextDrop.new)
|
assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render('context' => ContextDrop.new)
|
||||||
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ module CanadianMoneyFilter
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module SubstituteFilter
|
||||||
|
def substitute(input, params={})
|
||||||
|
input.gsub(/%\{(\w+)\}/) { |match| params[$1] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class FiltersTest < Test::Unit::TestCase
|
class FiltersTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
@@ -92,6 +98,13 @@ class FiltersTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
assert_equal 1000, Variable.new("var | xyzzy").render(@context)
|
assert_equal 1000, Variable.new("var | xyzzy").render(@context)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_filter_with_keyword_arguments
|
||||||
|
@context['surname'] = 'john'
|
||||||
|
@context.add_filters(SubstituteFilter)
|
||||||
|
output = Variable.new(%! 'hello %{first_name}, %{last_name}' | substitute: first_name: surname, last_name: 'doe' !).render(@context)
|
||||||
|
assert_equal 'hello john, doe', output
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class FiltersInTemplate < Test::Unit::TestCase
|
class FiltersInTemplate < Test::Unit::TestCase
|
||||||
|
|||||||
@@ -38,4 +38,27 @@ class SecurityTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => SecurityFilter)
|
assert_equal expected, Template.parse(text).render(@assigns, :filters => SecurityFilter)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_does_not_add_filters_to_symbol_table
|
||||||
|
current_symbols = Symbol.all_symbols
|
||||||
|
|
||||||
|
test = %( {{ "some_string" | a_bad_filter }} )
|
||||||
|
|
||||||
|
template = Template.parse(test)
|
||||||
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
|
|
||||||
|
template.render
|
||||||
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_does_not_add_drop_methods_to_symbol_table
|
||||||
|
current_symbols = Symbol.all_symbols
|
||||||
|
|
||||||
|
drop = Drop.new
|
||||||
|
drop.invoke_drop("custom_method_1")
|
||||||
|
drop.invoke_drop("custom_method_2")
|
||||||
|
drop.invoke_drop("custom_method_3")
|
||||||
|
|
||||||
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
|
end
|
||||||
end # SecurityTest
|
end # SecurityTest
|
||||||
|
|||||||
@@ -4,6 +4,27 @@ class Filters
|
|||||||
include Liquid::StandardFilters
|
include Liquid::StandardFilters
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class TestThing
|
||||||
|
def initialize
|
||||||
|
@foo = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
"woot: #{@foo}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_liquid
|
||||||
|
@foo += 1
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestDrop < Liquid::Drop
|
||||||
|
def test
|
||||||
|
"testfoo"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class StandardFiltersTest < Test::Unit::TestCase
|
class StandardFiltersTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
@@ -86,6 +107,23 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
|
'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
|
||||||
end
|
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 }}').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_date
|
def test_date
|
||||||
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
|
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")
|
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
|
||||||
@@ -119,9 +157,9 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_replace
|
def test_replace
|
||||||
assert_equal 'b b b b', @filters.replace("a a a a", 'a', 'b')
|
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
|
||||||
assert_equal 'b a a a', @filters.replace_first("a a a a", 'a', 'b')
|
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
|
||||||
assert_template_result 'b a a a', "{{ 'a a a a' | replace_first: 'a', 'b' }}"
|
assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_remove
|
def test_remove
|
||||||
|
|||||||
@@ -3,23 +3,50 @@ require 'test_helper'
|
|||||||
class StrainerTest < Test::Unit::TestCase
|
class StrainerTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
|
module AccessScopeFilters
|
||||||
|
def public_filter
|
||||||
|
"public"
|
||||||
|
end
|
||||||
|
|
||||||
|
def private_filter
|
||||||
|
"private"
|
||||||
|
end
|
||||||
|
private :private_filter
|
||||||
|
end
|
||||||
|
|
||||||
|
Strainer.global_filter(AccessScopeFilters)
|
||||||
|
|
||||||
def test_strainer
|
def test_strainer
|
||||||
strainer = Strainer.create(nil)
|
strainer = Strainer.create(nil)
|
||||||
assert_equal false, strainer.respond_to?('__test__')
|
assert_equal 5, strainer.invoke('size', 'input')
|
||||||
assert_equal false, strainer.respond_to?('test')
|
assert_equal "public", strainer.invoke("public_filter")
|
||||||
assert_equal false, strainer.respond_to?('instance_eval')
|
|
||||||
assert_equal false, strainer.respond_to?('__send__')
|
|
||||||
assert_equal true, strainer.respond_to?('size') # from the standard lib
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_should_respond_to_two_parameters
|
def test_strainer_only_invokes_public_filter_methods
|
||||||
strainer = Strainer.create(nil)
|
strainer = Strainer.create(nil)
|
||||||
assert_equal true, strainer.respond_to?('size', false)
|
assert_equal false, strainer.invokable?('__test__')
|
||||||
|
assert_equal false, strainer.invokable?('test')
|
||||||
|
assert_equal false, strainer.invokable?('instance_eval')
|
||||||
|
assert_equal false, strainer.invokable?('__send__')
|
||||||
|
assert_equal true, strainer.invokable?('size') # from the standard lib
|
||||||
end
|
end
|
||||||
|
|
||||||
# Asserts that Object#respond_to_missing? is not being undefined in Ruby versions where it has been implemented
|
def test_strainer_returns_nil_if_no_filter_method_found
|
||||||
# Currently this method is only present in Ruby v1.9.2, or higher
|
strainer = Strainer.create(nil)
|
||||||
def test_object_respond_to_missing
|
assert_nil strainer.invoke("private_filter")
|
||||||
assert_equal Object.respond_to?(:respond_to_missing?), Strainer.create(nil).respond_to?(:respond_to_missing?)
|
assert_nil strainer.invoke("undef_the_filter")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_strainer_returns_first_argument_if_no_method_and_arguments_given
|
||||||
|
strainer = Strainer.create(nil)
|
||||||
|
assert_equal "password", strainer.invoke("undef_the_method", "password")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strainer_only_allows_methods_defined_in_filters
|
||||||
|
strainer = Strainer.create(nil)
|
||||||
|
assert_equal "1 + 1", strainer.invoke("instance_eval", "1 + 1")
|
||||||
|
assert_equal "puts", strainer.invoke("__send__", "puts", "Hi Mom")
|
||||||
|
assert_equal "has_method?", strainer.invoke("invoke", "has_method?", "invoke")
|
||||||
|
end
|
||||||
|
|
||||||
end # StrainerTest
|
end # StrainerTest
|
||||||
|
|||||||
@@ -157,4 +157,10 @@ class IfElseTagTest < Test::Unit::TestCase
|
|||||||
assert_template_result('yes',
|
assert_template_result('yes',
|
||||||
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
|
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_operators_are_whitelisted
|
||||||
|
assert_raise(SyntaxError) do
|
||||||
|
assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))
|
||||||
|
end
|
||||||
|
end
|
||||||
end # IfElseTest
|
end # IfElseTest
|
||||||
|
|||||||
@@ -11,67 +11,71 @@ class VariableTest < Test::Unit::TestCase
|
|||||||
def test_filters
|
def test_filters
|
||||||
var = Variable.new('hello | textileze')
|
var = Variable.new('hello | textileze')
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:textileze,[]]], var.filters
|
assert_equal [["textileze",[]]], var.filters
|
||||||
|
|
||||||
var = Variable.new('hello | textileze | paragraph')
|
var = Variable.new('hello | textileze | paragraph')
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:textileze,[]], [:paragraph,[]]], var.filters
|
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! hello | strftime: '%Y'!)
|
var = Variable.new(%! hello | strftime: '%Y'!)
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:strftime,["'%Y'"]]], var.filters
|
assert_equal [["strftime",["'%Y'"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! 'typo' | link_to: 'Typo', true !)
|
var = Variable.new(%! 'typo' | link_to: 'Typo', true !)
|
||||||
assert_equal %!'typo'!, var.name
|
assert_equal %!'typo'!, var.name
|
||||||
assert_equal [[:link_to,["'Typo'", "true"]]], var.filters
|
assert_equal [["link_to",["'Typo'", "true"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! 'typo' | link_to: 'Typo', false !)
|
var = Variable.new(%! 'typo' | link_to: 'Typo', false !)
|
||||||
assert_equal %!'typo'!, var.name
|
assert_equal %!'typo'!, var.name
|
||||||
assert_equal [[:link_to,["'Typo'", "false"]]], var.filters
|
assert_equal [["link_to",["'Typo'", "false"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! 'foo' | repeat: 3 !)
|
var = Variable.new(%! 'foo' | repeat: 3 !)
|
||||||
assert_equal %!'foo'!, var.name
|
assert_equal %!'foo'!, var.name
|
||||||
assert_equal [[:repeat,["3"]]], var.filters
|
assert_equal [["repeat",["3"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! 'foo' | repeat: 3, 3 !)
|
var = Variable.new(%! 'foo' | repeat: 3, 3 !)
|
||||||
assert_equal %!'foo'!, var.name
|
assert_equal %!'foo'!, var.name
|
||||||
assert_equal [[:repeat,["3","3"]]], var.filters
|
assert_equal [["repeat",["3","3"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! 'foo' | repeat: 3, 3, 3 !)
|
var = Variable.new(%! 'foo' | repeat: 3, 3, 3 !)
|
||||||
assert_equal %!'foo'!, var.name
|
assert_equal %!'foo'!, var.name
|
||||||
assert_equal [[:repeat,["3","3","3"]]], var.filters
|
assert_equal [["repeat",["3","3","3"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! hello | strftime: '%Y, okay?'!)
|
var = Variable.new(%! hello | strftime: '%Y, okay?'!)
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:strftime,["'%Y, okay?'"]]], var.filters
|
assert_equal [["strftime",["'%Y, okay?'"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! hello | things: "%Y, okay?", 'the other one'!)
|
var = Variable.new(%! hello | things: "%Y, okay?", 'the other one'!)
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:things,["\"%Y, okay?\"","'the other one'"]]], var.filters
|
assert_equal [["things",["\"%Y, okay?\"","'the other one'"]]], var.filters
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_filter_with_date_parameter
|
def test_filter_with_date_parameter
|
||||||
|
|
||||||
var = Variable.new(%! '2006-06-06' | date: "%m/%d/%Y"!)
|
var = Variable.new(%! '2006-06-06' | date: "%m/%d/%Y"!)
|
||||||
assert_equal "'2006-06-06'", var.name
|
assert_equal "'2006-06-06'", var.name
|
||||||
assert_equal [[:date,["\"%m/%d/%Y\""]]], var.filters
|
assert_equal [["date",["\"%m/%d/%Y\""]]], var.filters
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_filters_without_whitespace
|
def test_filters_without_whitespace
|
||||||
var = Variable.new('hello | textileze | paragraph')
|
var = Variable.new('hello | textileze | paragraph')
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:textileze,[]], [:paragraph,[]]], var.filters
|
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
|
||||||
|
|
||||||
var = Variable.new('hello|textileze|paragraph')
|
var = Variable.new('hello|textileze|paragraph')
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:textileze,[]], [:paragraph,[]]], var.filters
|
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
|
||||||
|
|
||||||
|
var = Variable.new("hello|replace:'foo','bar'|textileze")
|
||||||
|
assert_equal 'hello', var.name
|
||||||
|
assert_equal [["replace", ["'foo'", "'bar'"]], ["textileze", []]], var.filters
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_symbol
|
def test_symbol
|
||||||
var = Variable.new("http://disney.com/logo.gif | image: 'med' ")
|
var = Variable.new("http://disney.com/logo.gif | image: 'med' ")
|
||||||
assert_equal 'http://disney.com/logo.gif', var.name
|
assert_equal 'http://disney.com/logo.gif', var.name
|
||||||
assert_equal [[:image,["'med'"]]], var.filters
|
assert_equal [["image",["'med'"]]], var.filters
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_string_single_quoted
|
def test_string_single_quoted
|
||||||
@@ -103,6 +107,12 @@ class VariableTest < Test::Unit::TestCase
|
|||||||
var = Variable.new(%| test.test |)
|
var = Variable.new(%| test.test |)
|
||||||
assert_equal 'test.test', var.name
|
assert_equal 'test.test', var.name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_filter_with_keyword_arguments
|
||||||
|
var = Variable.new(%! hello | things: greeting: "world", farewell: 'goodbye'!)
|
||||||
|
assert_equal 'hello', var.name
|
||||||
|
assert_equal [['things',["greeting: \"world\"","farewell: 'goodbye'"]]], var.filters
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user