Merge branch 'master' into liquid-utf8

Conflicts:
	lib/liquid/variable.rb
This commit is contained in:
Ozéias Sant'ana
2013-04-23 13:10:16 -03:00
12 changed files with 166 additions and 69 deletions
+2 -5
View File
@@ -39,6 +39,7 @@ module Liquid
filters.each do |f|
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
Strainer.add_known_filter(f)
strainer.extend(f)
end
end
@@ -71,11 +72,7 @@ module Liquid
end
def invoke(method, *args)
if strainer.respond_to?(method)
strainer.__send__(method, *args)
else
args.first
end
strainer.invoke(method, *args)
end
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
+12 -2
View File
@@ -1,3 +1,5 @@
require 'set'
module 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
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)
send(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)
else
before_method(method_or_key)
end
@@ -47,5 +49,13 @@ module Liquid
end
alias :[] :invoke_drop
private
# 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))
@invokable_methods.include?(method_name.to_s)
end
end
end
+25 -26
View File
@@ -2,24 +2,15 @@ require 'set'
module Liquid
parent_object = if defined? BlankObject
BlankObject
else
Object
end
# 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
class Strainer < parent_object #:nodoc:
INTERNAL_METHOD = /^__/
@@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?
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
# Context#add_filters or Template.register_filter
class Strainer #:nodoc:
@@filters = {}
@@known_filters = Set.new
@@known_methods = Set.new
def initialize(context)
@context = context
@@ -27,28 +18,36 @@ module Liquid
def self.global_filter(filter)
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
add_known_filter(filter)
@@filters[filter.name] = filter
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)
strainer = Strainer.new(context)
@@filters.each { |k,m| strainer.extend(m) }
strainer
end
def respond_to?(method, include_private = false)
method_name = method.to_s
return false if method_name =~ INTERNAL_METHOD
return false if @@required_methods.include?(method_name)
super
def invoke(method, *args)
if invokable?(method)
send(method, *args)
else
args.first
end
end
# remove all standard methods from the bucket so circumvent security
# problems
instance_methods.each do |m|
unless @@required_methods.include?(m.to_sym)
undef_method m
end
def invokable?(method)
@@known_methods.include?(method.to_s) && respond_to?(method)
end
end
end
+12 -5
View File
@@ -23,10 +23,10 @@ module Liquid
if match[2].match(/#{FilterSeparator}\s*(.*)/o)
filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f|
if matches = f.match(/\s*(#{WordRegex}+)/o)
if matches = f.match(/\s*(#{WordRegex}+)(?:\s*#{FilterArgumentSeparator}(.*))?/)
filtername = matches[1]
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/o).flatten
@filters << [filtername.to_sym, filterargs]
filterargs = matches[2].to_s.scan(/(?:\A|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
@filters << [filtername, filterargs]
end
end
end
@@ -36,9 +36,16 @@ module Liquid
def render(context)
return '' if @name.nil?
@filters.inject(context[@name]) do |output, filter|
filterargs = filter[1].to_a.collect do |a|
context[a]
filterargs = []
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
filterargs << keyword_args unless keyword_args.empty?
begin
output = context.invoke(filter[0], output, *filterargs)
rescue FilterNotFound