mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-18 18:30:40 -07:00
Merge branch 'master' of [email protected]:tobi/liquid
Conflicts: lib/liquid.rb lib/liquid/context.rb lib/liquid/variable.rb test/standard_tag_test.rb
This commit is contained in:
+27
-16
@@ -5,32 +5,43 @@
|
||||
#
|
||||
# ActionView::Base::register_template_handler :liquid, LiquidView
|
||||
class LiquidView
|
||||
PROTECTED_ASSIGNS = %w( template_root response _session template_class action_name request_origin session template
|
||||
_response url _request _cookies variables_added _flash params _headers request cookies
|
||||
ignore_missing_templates flash _params logger before_filter_chain_aborted headers )
|
||||
PROTECTED_INSTANCE_VARIABLES = %w( @_request @controller @_first_render @_memoized__pick_template @view_paths
|
||||
@helpers @assigns_added @template @_render_stack @template_format @assigns )
|
||||
|
||||
def self.call(template)
|
||||
"LiquidView.new(self).render(template, local_assigns)"
|
||||
end
|
||||
|
||||
def initialize(action_view)
|
||||
@action_view = action_view
|
||||
def initialize(view)
|
||||
@view = view
|
||||
end
|
||||
|
||||
|
||||
def render(template, local_assigns_for_rails_less_than_2_1_0 = nil)
|
||||
@action_view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
|
||||
assigns = @action_view.assigns.dup
|
||||
def render(template, local_assigns = nil)
|
||||
@view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
|
||||
|
||||
# template is a Template object in Rails >=2.1.0, a source string previously.
|
||||
if template.respond_to? :source
|
||||
source = template.source
|
||||
local_assigns = template.locals
|
||||
# Rails 2.2 Template has source, but not locals
|
||||
if template.respond_to?(:source) && !template.respond_to?(:locals)
|
||||
assigns = (@view.instance_variables - PROTECTED_INSTANCE_VARIABLES).inject({}) do |hash, ivar|
|
||||
hash[ivar[1..-1]] = @view.instance_variable_get(ivar)
|
||||
hash
|
||||
end
|
||||
else
|
||||
source = template
|
||||
local_assigns = local_assigns_for_rails_less_than_2_1_0
|
||||
assigns = @view.assigns.reject{ |k,v| PROTECTED_ASSIGNS.include?(k) }
|
||||
end
|
||||
|
||||
if content_for_layout = @action_view.instance_variable_get("@content_for_layout")
|
||||
|
||||
source = template.respond_to?(:source) ? template.source : template
|
||||
local_assigns = (template.respond_to?(:locals) ? template.locals : local_assigns) || {}
|
||||
|
||||
if content_for_layout = @view.instance_variable_get("@content_for_layout")
|
||||
assigns['content_for_layout'] = content_for_layout
|
||||
end
|
||||
assigns.merge!(local_assigns)
|
||||
assigns.merge!(local_assigns.stringify_keys)
|
||||
|
||||
liquid = Liquid::Template.parse(source)
|
||||
liquid.render(assigns, :filters => [@action_view.controller.master_helper_module], :registers => {:action_view => @action_view, :controller => @action_view.controller})
|
||||
liquid.render(assigns, :filters => [@view.controller.master_helper_module], :registers => {:action_view => @view, :controller => @view.controller})
|
||||
end
|
||||
|
||||
def compilable?
|
||||
|
||||
+15
-27
@@ -22,41 +22,29 @@
|
||||
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
||||
|
||||
module Liquid
|
||||
|
||||
# Basic apperence:
|
||||
|
||||
# Tags {% look like this %}
|
||||
TagStart = /\{%/
|
||||
TagEnd = /%\}/
|
||||
|
||||
# Variables {{look}} like {{this}}
|
||||
FilterSeparator = /\|/
|
||||
ArgumentSeparator = ','
|
||||
FilterArgumentSeparator = ':'
|
||||
VariableAttributeSeparator = '.'
|
||||
TagStart = /\{\%/
|
||||
TagEnd = /\%\}/
|
||||
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
||||
VariableSegment = /[\w\-]/
|
||||
VariableStart = /\{\{/
|
||||
VariableEnd = /\}\}/
|
||||
|
||||
# Arguments are passed {% like: this %}
|
||||
FilterArgumentSeparator = ':'
|
||||
|
||||
# Hashes are separated {{ like.this }}
|
||||
VariableAttributeSeparator = '.'
|
||||
|
||||
# Filters are piped in {{ like | this }}
|
||||
FilterSeparator = /\|/
|
||||
|
||||
# Muliple arguments are {% separated: like, this %}
|
||||
ArgumentSeparator = ','
|
||||
|
||||
|
||||
# Lexical parsing regexped go below.
|
||||
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
||||
VariableSegment = /[\w\-]\??/
|
||||
VariableIncompleteEnd = /\}\}?/
|
||||
QuotedString = /"[^"]+"|'[^']+'/
|
||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/
|
||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/
|
||||
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}/
|
||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
|
||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+/
|
||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
|
||||
end
|
||||
|
||||
require 'liquid/drop'
|
||||
|
||||
+23
-23
@@ -1,19 +1,19 @@
|
||||
module Liquid
|
||||
|
||||
|
||||
class Block < Tag
|
||||
|
||||
def parse(tokens)
|
||||
@nodelist ||= []
|
||||
@nodelist.clear
|
||||
|
||||
while token = tokens.shift
|
||||
while token = tokens.shift
|
||||
|
||||
case token
|
||||
when /^#{TagStart}/
|
||||
when /^#{TagStart}/
|
||||
if token =~ /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
|
||||
|
||||
# if we found the proper block delimitor just end parsing here and let the outer block
|
||||
# proceed
|
||||
# proceed
|
||||
if block_delimiter == $1
|
||||
end_tag
|
||||
return
|
||||
@@ -23,10 +23,10 @@ module Liquid
|
||||
if tag = Template.tags[$1]
|
||||
@nodelist << tag.new($1, $2, tokens)
|
||||
else
|
||||
# this tag is not registered with the system
|
||||
# this tag is not registered with the system
|
||||
# pass it to the current block for special handling or error reporting
|
||||
unknown_tag($1, $2, tokens)
|
||||
end
|
||||
end
|
||||
else
|
||||
raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
|
||||
end
|
||||
@@ -37,19 +37,19 @@ module Liquid
|
||||
else
|
||||
@nodelist << token
|
||||
end
|
||||
end
|
||||
|
||||
# Make sure that its ok to end parsing in the current block.
|
||||
# Effectively this method will throw and exception unless the current block is
|
||||
# of type Document
|
||||
end
|
||||
|
||||
# Make sure that its ok to end parsing in the current block.
|
||||
# Effectively this method will throw and exception unless the current block is
|
||||
# of type Document
|
||||
assert_missing_delimitation!
|
||||
end
|
||||
|
||||
def end_tag
|
||||
end
|
||||
|
||||
def end_tag
|
||||
end
|
||||
|
||||
def unknown_tag(tag, params, tokens)
|
||||
case tag
|
||||
case tag
|
||||
when 'else'
|
||||
raise SyntaxError, "#{block_name} tag does not expect else tag"
|
||||
when 'end'
|
||||
@@ -61,7 +61,7 @@ module Liquid
|
||||
|
||||
def block_delimiter
|
||||
"end#{block_name}"
|
||||
end
|
||||
end
|
||||
|
||||
def block_name
|
||||
@tag_name
|
||||
@@ -77,7 +77,7 @@ module Liquid
|
||||
def render(context)
|
||||
render_all(@nodelist, context)
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
|
||||
def assert_missing_delimitation!
|
||||
@@ -86,12 +86,12 @@ module Liquid
|
||||
|
||||
def render_all(list, context)
|
||||
list.collect do |token|
|
||||
begin
|
||||
begin
|
||||
token.respond_to?(:render) ? token.render(context) : token
|
||||
rescue Exception => e
|
||||
rescue Exception => e
|
||||
context.handle_error(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+30
-33
@@ -3,7 +3,7 @@ module Liquid
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# c = Condition.new('1', '==', '1')
|
||||
# c = Condition.new('1', '==', '1')
|
||||
# c.evaluate #=> true
|
||||
#
|
||||
class Condition #:nodoc:
|
||||
@@ -17,33 +17,33 @@ module Liquid
|
||||
'<=' => :<=,
|
||||
'contains' => lambda { |cond, left, right| left.include?(right) },
|
||||
}
|
||||
|
||||
|
||||
def self.operators
|
||||
@@operators
|
||||
end
|
||||
|
||||
attr_reader :attachment
|
||||
attr_accessor :left, :operator, :right
|
||||
|
||||
|
||||
def initialize(left = nil, operator = nil, right = nil)
|
||||
@left, @operator, @right = left, operator, right
|
||||
@child_relation = nil
|
||||
@child_condition = nil
|
||||
end
|
||||
|
||||
|
||||
def evaluate(context = Context.new)
|
||||
result = interpret_condition(left, right, operator, context)
|
||||
|
||||
result = interpret_condition(left, right, operator, context)
|
||||
|
||||
case @child_relation
|
||||
when :or
|
||||
when :or
|
||||
result || @child_condition.evaluate(context)
|
||||
when :and
|
||||
when :and
|
||||
result && @child_condition.evaluate(context)
|
||||
else
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def or(condition)
|
||||
@child_relation, @child_condition = :or, condition
|
||||
end
|
||||
@@ -51,25 +51,25 @@ module Liquid
|
||||
def and(condition)
|
||||
@child_relation, @child_condition = :and, condition
|
||||
end
|
||||
|
||||
|
||||
def attach(attachment)
|
||||
@attachment = attachment
|
||||
end
|
||||
|
||||
|
||||
def else?
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def equal_variables(left, right)
|
||||
if left.is_a?(Symbol)
|
||||
if right.respond_to?(left)
|
||||
return right.send(left.to_s)
|
||||
return right.send(left.to_s)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@@ -77,47 +77,44 @@ module Liquid
|
||||
|
||||
if right.is_a?(Symbol)
|
||||
if left.respond_to?(right)
|
||||
return left.send(right.to_s)
|
||||
return left.send(right.to_s)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
left == right
|
||||
end
|
||||
left == right
|
||||
end
|
||||
|
||||
def interpret_condition(left, right, op, context)
|
||||
|
||||
# If the operator is empty this means that the decision statement is just
|
||||
# a single variable. We can just poll this variable from the context and
|
||||
# If the operator is empty this means that the decision statement is just
|
||||
# a single variable. We can just poll this variable from the context and
|
||||
# return this as the result.
|
||||
return context[left] if op == nil
|
||||
return context[left] if op == nil
|
||||
|
||||
left, right = context[left], context[right]
|
||||
|
||||
|
||||
operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
|
||||
|
||||
if operation.respond_to?(:call)
|
||||
operation.call(self, left, right)
|
||||
elsif left.respond_to?(operation) and right.respond_to?(operation)
|
||||
elsif left.respond_to?(operation) and right.respond_to?(operation)
|
||||
left.send(operation, right)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class ElseCondition < Condition
|
||||
|
||||
def else?
|
||||
def else?
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
def evaluate(context)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
+7
-11
@@ -56,7 +56,7 @@ module Liquid
|
||||
if strainer.respond_to?(method)
|
||||
strainer.__send__(method, *args)
|
||||
else
|
||||
args.first
|
||||
raise FilterNotFound, "Filter '#{method}' not found"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -156,16 +156,12 @@ module Liquid
|
||||
# fetches an object starting at the local scope and then moving up
|
||||
# the hierachy
|
||||
def find_variable(key)
|
||||
@scopes.each do |scope|
|
||||
if scope.has_key?(key)
|
||||
variable = scope[key]
|
||||
variable = scope[key] = variable.call(self) if variable.is_a?(Proc)
|
||||
variable = variable.to_liquid
|
||||
variable.context = self if variable.respond_to?(:context=)
|
||||
return variable
|
||||
end
|
||||
end
|
||||
nil
|
||||
scope = @scopes[0..-2].find { |s| s.has_key?(key) } || @scopes.last
|
||||
variable = scope[key]
|
||||
variable = scope[key] = variable.call(self) if variable.is_a?(Proc)
|
||||
variable = variable.to_liquid
|
||||
variable.context = self if variable.respond_to?(:context=)
|
||||
return variable
|
||||
end
|
||||
|
||||
# resolves namespaced queries gracefully.
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
module Liquid
|
||||
class Document < Block
|
||||
class Document < Block
|
||||
# we don't need markup to open this block
|
||||
def initialize(tokens)
|
||||
parse(tokens)
|
||||
end
|
||||
|
||||
# There isn't a real delimter
|
||||
end
|
||||
|
||||
# There isn't a real delimter
|
||||
def block_delimiter
|
||||
[]
|
||||
end
|
||||
|
||||
|
||||
# Document blocks don't need to be terminated since they are not actually opened
|
||||
def assert_missing_delimitation!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+18
-17
@@ -1,9 +1,9 @@
|
||||
module Liquid
|
||||
|
||||
|
||||
# A drop in liquid is a class which allows you to to export DOM like things to liquid
|
||||
# Methods of drops are callable.
|
||||
# The main use for liquid drops is the implement lazy loaded objects.
|
||||
# If you would like to make data available to the web designers which you don't want loaded unless needed then
|
||||
# Methods of drops are callable.
|
||||
# The main use for liquid drops is the implement lazy loaded objects.
|
||||
# If you would like to make data available to the web designers which you don't want loaded unless needed then
|
||||
# a drop is a great way to do that
|
||||
#
|
||||
# Example:
|
||||
@@ -13,38 +13,39 @@ module Liquid
|
||||
# Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
|
||||
# tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
|
||||
#
|
||||
# Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
|
||||
# tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
|
||||
# tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
|
||||
#
|
||||
# Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
|
||||
# catch all
|
||||
class Drop
|
||||
attr_writer :context
|
||||
|
||||
# Catch all for the method
|
||||
# Catch all for the method
|
||||
def before_method(method)
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
# called by liquid to invoke a drop
|
||||
def invoke_drop(method)
|
||||
if self.class.public_instance_methods.include?(method.to_s)
|
||||
send(method.to_sym)
|
||||
else
|
||||
def invoke_drop(method)
|
||||
# for backward compatibility with Ruby 1.8
|
||||
methods = self.class.public_instance_methods.map { |m| m.to_s }
|
||||
if methods.include?(method.to_s)
|
||||
send(method.to_sym)
|
||||
else
|
||||
before_method(method)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def has_key?(name)
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
|
||||
alias :[] :invoke_drop
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
+27
-27
@@ -1,7 +1,7 @@
|
||||
module Liquid
|
||||
class TableRow < Block
|
||||
Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)/
|
||||
|
||||
class TableRow < Block
|
||||
Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)/
|
||||
|
||||
def initialize(tag_name, markup, tokens)
|
||||
if markup =~ Syntax
|
||||
@variable_name = $1
|
||||
@@ -13,62 +13,62 @@ module Liquid
|
||||
else
|
||||
raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
|
||||
end
|
||||
|
||||
super
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def render(context)
|
||||
|
||||
def render(context)
|
||||
collection = context[@collection_name] or return ''
|
||||
|
||||
|
||||
if @attributes['limit'] or @attributes['offset']
|
||||
limit = context[@attributes['limit']] || -1
|
||||
offset = context[@attributes['offset']] || 0
|
||||
collection = collection[offset.to_i..(limit.to_i + offset.to_i - 1)]
|
||||
end
|
||||
|
||||
|
||||
length = collection.length
|
||||
|
||||
|
||||
cols = context[@attributes['cols']].to_i
|
||||
|
||||
row = 1
|
||||
col = 0
|
||||
|
||||
result = ["<tr class=\"row1\">\n"]
|
||||
context.stack do
|
||||
context.stack do
|
||||
|
||||
collection.each_with_index do |item, index|
|
||||
context[@variable_name] = item
|
||||
context['tablerowloop'] = {
|
||||
'length' => length,
|
||||
'index' => index + 1,
|
||||
'index0' => index,
|
||||
'col' => col + 1,
|
||||
'col0' => col,
|
||||
'index0' => index,
|
||||
'index' => index + 1,
|
||||
'index0' => index,
|
||||
'col' => col + 1,
|
||||
'col0' => col,
|
||||
'index0' => index,
|
||||
'rindex' => length - index,
|
||||
'rindex0' => length - index -1,
|
||||
'first' => (index == 0),
|
||||
'last' => (index == length - 1),
|
||||
'col_first' => (col == 0),
|
||||
'col_last' => (col == cols - 1)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
col += 1
|
||||
|
||||
|
||||
result << ["<td class=\"col#{col}\">"] + render_all(@nodelist, context) + ['</td>']
|
||||
|
||||
if col == cols and not (index == length - 1)
|
||||
if col == cols and not (index == length - 1)
|
||||
col = 0
|
||||
row += 1
|
||||
result << ["</tr>\n<tr class=\"row#{row}\">"]
|
||||
result << ["</tr>\n<tr class=\"row#{row}\">"]
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
result + ["</tr>\n"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('tablerow', TableRow)
|
||||
end
|
||||
|
||||
Template.register_tag('tablerow', TableRow)
|
||||
end
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# if you want to extend the drop to other methods you can defines more methods
|
||||
# if you want to extend the drop to other methods you can defines more methods
|
||||
# in the class <YourClass>::LiquidDropClass
|
||||
#
|
||||
# class SomeClass::LiquidDropClass
|
||||
@@ -37,11 +37,11 @@
|
||||
# output:
|
||||
# 'this comes from an allowed method and this from another allowed method'
|
||||
#
|
||||
# You can also chain associations, by adding the liquid_method call in the
|
||||
# You can also chain associations, by adding the liquid_method call in the
|
||||
# association models.
|
||||
#
|
||||
class Module
|
||||
|
||||
|
||||
def liquid_methods(*allowed_methods)
|
||||
drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
|
||||
define_method :to_liquid do
|
||||
@@ -58,5 +58,5 @@ class Module
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
+17
-18
@@ -1,52 +1,51 @@
|
||||
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.
|
||||
# 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.
|
||||
#
|
||||
# One of the strainer's responsibilities is to keep malicious method calls out
|
||||
# One of the strainer's responsibilities is to keep malicious method calls out
|
||||
class Strainer < parent_object #:nodoc:
|
||||
INTERNAL_METHOD = /^__/
|
||||
@@required_methods = Set.new([:__send__, :__id__, :respond_to?, :extend, :methods, :class])
|
||||
|
||||
INTERNAL_METHOD = /^__/
|
||||
@@required_methods = Set.new([:__id__, :__send__, :respond_to?, :extend, :methods, :class, :object_id])
|
||||
|
||||
@@filters = {}
|
||||
|
||||
|
||||
def initialize(context)
|
||||
@context = context
|
||||
end
|
||||
|
||||
|
||||
def self.global_filter(filter)
|
||||
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
|
||||
@@filters[filter.name] = filter
|
||||
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
|
||||
end
|
||||
|
||||
# remove all standard methods from the bucket so circumvent security
|
||||
# problems
|
||||
instance_methods.each do |m|
|
||||
unless @@required_methods.include?(m.to_sym)
|
||||
|
||||
# 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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+8
-8
@@ -1,26 +1,26 @@
|
||||
module Liquid
|
||||
|
||||
|
||||
class Tag
|
||||
attr_accessor :nodelist
|
||||
|
||||
|
||||
def initialize(tag_name, markup, tokens)
|
||||
@tag_name = tag_name
|
||||
@markup = markup
|
||||
parse(tokens)
|
||||
end
|
||||
|
||||
|
||||
def parse(tokens)
|
||||
end
|
||||
|
||||
|
||||
def name
|
||||
self.class.name.downcase
|
||||
end
|
||||
|
||||
|
||||
def render(context)
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Liquid
|
||||
|
||||
|
||||
# Capture stores the result of a block into a variable without rendering it inplace.
|
||||
#
|
||||
# {% capture heading %}
|
||||
@@ -8,28 +8,28 @@ module Liquid
|
||||
# ...
|
||||
# <h1>{{ monkeys }}</h1>
|
||||
#
|
||||
# Capture is useful for saving content for use later in your template, such as
|
||||
# Capture is useful for saving content for use later in your template, such as
|
||||
# in a sidebar or footer.
|
||||
#
|
||||
class Capture < Block
|
||||
Syntax = /(\w+)/
|
||||
|
||||
def initialize(tag_name, markup, tokens)
|
||||
def initialize(tag_name, markup, tokens)
|
||||
if markup =~ Syntax
|
||||
@to = $1
|
||||
else
|
||||
raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
|
||||
end
|
||||
|
||||
super
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def render(context)
|
||||
output = super
|
||||
context[@to] = output.to_s
|
||||
context[@to] = output.join
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Template.register_tag('capture', Capture)
|
||||
end
|
||||
end
|
||||
|
||||
+54
-53
@@ -1,120 +1,121 @@
|
||||
module Liquid
|
||||
|
||||
# Templates are central to liquid.
|
||||
# Interpretating templates is a two step process. First you compile the
|
||||
# source code you got. During compile time some extensive error checking is performed.
|
||||
# your code should expect to get some SyntaxErrors.
|
||||
# Templates are central to liquid.
|
||||
# Interpretating templates is a two step process. First you compile the
|
||||
# source code you got. During compile time some extensive error checking is performed.
|
||||
# your code should expect to get some SyntaxErrors.
|
||||
#
|
||||
# After you have a compiled template you can then <tt>render</tt> it.
|
||||
# You can use a compiled template over and over again and keep it cached.
|
||||
# After you have a compiled template you can then <tt>render</tt> it.
|
||||
# You can use a compiled template over and over again and keep it cached.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# template = Liquid::Template.parse(source)
|
||||
# template.render('user_name' => 'bob')
|
||||
#
|
||||
class Template
|
||||
attr_accessor :root
|
||||
@@file_system = BlankFileSystem.new
|
||||
|
||||
class <<self
|
||||
|
||||
class << self
|
||||
def file_system
|
||||
@@file_system
|
||||
end
|
||||
|
||||
|
||||
def file_system=(obj)
|
||||
@@file_system = obj
|
||||
end
|
||||
|
||||
def register_tag(name, klass)
|
||||
|
||||
def register_tag(name, klass)
|
||||
tags[name.to_s] = klass
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def tags
|
||||
@tags ||= {}
|
||||
end
|
||||
|
||||
# Pass a module with filter methods which should be available
|
||||
|
||||
# Pass a module with filter methods which should be available
|
||||
# to all liquid views. Good for registering the standard library
|
||||
def register_filter(mod)
|
||||
def register_filter(mod)
|
||||
Strainer.global_filter(mod)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# creates a new <tt>Template</tt> object from liquid source code
|
||||
def parse(source)
|
||||
template = Template.new
|
||||
template.parse(source)
|
||||
template
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
|
||||
def initialize
|
||||
end
|
||||
|
||||
# Parse source code.
|
||||
# Returns self for easy chaining
|
||||
|
||||
# Parse source code.
|
||||
# Returns self for easy chaining
|
||||
def parse(source)
|
||||
@root = Document.new(tokenize(source))
|
||||
self
|
||||
end
|
||||
|
||||
def registers
|
||||
|
||||
def registers
|
||||
@registers ||= {}
|
||||
end
|
||||
|
||||
|
||||
def assigns
|
||||
@assigns ||= {}
|
||||
end
|
||||
|
||||
|
||||
def errors
|
||||
@errors ||= []
|
||||
end
|
||||
|
||||
|
||||
# Render takes a hash with local variables.
|
||||
#
|
||||
# if you use the same filters over and over again consider registering them globally
|
||||
# if you use the same filters over and over again consider registering them globally
|
||||
# with <tt>Template.register_filter</tt>
|
||||
#
|
||||
#
|
||||
# Following options can be passed:
|
||||
#
|
||||
#
|
||||
# * <tt>filters</tt> : array with local filters
|
||||
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
|
||||
# filters and tags and might be useful to integrate liquid more with its host application
|
||||
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
|
||||
# filters and tags and might be useful to integrate liquid more with its host application
|
||||
#
|
||||
def render(*args)
|
||||
return '' if @root.nil?
|
||||
return '' if @root.nil?
|
||||
|
||||
context = case args.first
|
||||
when Liquid::Context
|
||||
args.shift
|
||||
when Hash
|
||||
self.assigns.merge!(args.shift)
|
||||
Context.new(assigns, registers, @rethrow_errors)
|
||||
a = args.shift
|
||||
assigns.each { |k,v| a[k] = v unless a.has_key?(k) }
|
||||
Context.new(a, registers, @rethrow_errors)
|
||||
when nil
|
||||
Context.new(assigns, registers, @rethrow_errors)
|
||||
Context.new(assigns.dup, registers, @rethrow_errors)
|
||||
else
|
||||
raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
|
||||
end
|
||||
|
||||
|
||||
case args.last
|
||||
when Hash
|
||||
options = args.pop
|
||||
|
||||
|
||||
if options[:registers].is_a?(Hash)
|
||||
self.registers.merge!(options[:registers])
|
||||
self.registers.merge!(options[:registers])
|
||||
end
|
||||
|
||||
if options[:filters]
|
||||
context.add_filters(options[:filters])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
when Module
|
||||
context.add_filters(args.pop)
|
||||
context.add_filters(args.pop)
|
||||
when Array
|
||||
context.add_filters(args.pop)
|
||||
context.add_filters(args.pop)
|
||||
end
|
||||
|
||||
|
||||
begin
|
||||
# render the nodelist.
|
||||
# for performance reasons we get a array back here. join will make a string out of it
|
||||
@@ -123,24 +124,24 @@ module Liquid
|
||||
@errors = context.errors
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def render!(*args)
|
||||
@rethrow_errors = true; render(*args)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
|
||||
def tokenize(source)
|
||||
source = source.source if source.respond_to?(:source)
|
||||
source = source.source if source.respond_to?(:source)
|
||||
return [] if source.to_s.empty?
|
||||
tokens = source.split(TemplateParser)
|
||||
|
||||
# removes the rogue empty element at the beginning of the array
|
||||
tokens.shift if tokens[0] and tokens[0].empty?
|
||||
tokens.shift if tokens[0] and tokens[0].empty?
|
||||
|
||||
tokens
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -35,8 +34,7 @@ module Liquid
|
||||
|
||||
def render(context)
|
||||
return '' if @name.nil?
|
||||
output = context[@name]
|
||||
@filters.inject(output) do |output, filter|
|
||||
@filters.inject(context[@name]) do |output, filter|
|
||||
filterargs = filter[1].to_a.collect do |a|
|
||||
context[a]
|
||||
end
|
||||
@@ -46,7 +44,6 @@ module Liquid
|
||||
raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
|
||||
end
|
||||
end
|
||||
output
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user