mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-29 07:05:12 -07:00
All tests pass on Ruby 1.9.1
Signed-off-by: Tobias Lütke <[email protected]>
This commit is contained in:
committed by
Tobias Lütke
parent
8d27864845
commit
ed1b542abf
+23
-23
@@ -1,19 +1,19 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
class Block < Tag
|
class Block < Tag
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
@nodelist ||= []
|
@nodelist ||= []
|
||||||
@nodelist.clear
|
@nodelist.clear
|
||||||
|
|
||||||
while token = tokens.shift
|
while token = tokens.shift
|
||||||
|
|
||||||
case token
|
case token
|
||||||
when /^#{TagStart}/
|
when /^#{TagStart}/
|
||||||
if token =~ /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
|
if token =~ /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
|
||||||
|
|
||||||
# if we found the proper block delimitor just end parsing here and let the outer block
|
# if we found the proper block delimitor just end parsing here and let the outer block
|
||||||
# proceed
|
# proceed
|
||||||
if block_delimiter == $1
|
if block_delimiter == $1
|
||||||
end_tag
|
end_tag
|
||||||
return
|
return
|
||||||
@@ -23,10 +23,10 @@ module Liquid
|
|||||||
if tag = Template.tags[$1]
|
if tag = Template.tags[$1]
|
||||||
@nodelist << tag.new($1, $2, tokens)
|
@nodelist << tag.new($1, $2, tokens)
|
||||||
else
|
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
|
# pass it to the current block for special handling or error reporting
|
||||||
unknown_tag($1, $2, tokens)
|
unknown_tag($1, $2, tokens)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
|
raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
|
||||||
end
|
end
|
||||||
@@ -37,19 +37,19 @@ module Liquid
|
|||||||
else
|
else
|
||||||
@nodelist << token
|
@nodelist << token
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Make sure that its ok to end parsing in the current block.
|
# Make sure that its ok to end parsing in the current block.
|
||||||
# Effectively this method will throw and exception unless the current block is
|
# Effectively this method will throw and exception unless the current block is
|
||||||
# of type Document
|
# of type Document
|
||||||
assert_missing_delimitation!
|
assert_missing_delimitation!
|
||||||
end
|
end
|
||||||
|
|
||||||
def end_tag
|
def end_tag
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, params, tokens)
|
def unknown_tag(tag, params, tokens)
|
||||||
case tag
|
case tag
|
||||||
when 'else'
|
when 'else'
|
||||||
raise SyntaxError, "#{block_name} tag does not expect else tag"
|
raise SyntaxError, "#{block_name} tag does not expect else tag"
|
||||||
when 'end'
|
when 'end'
|
||||||
@@ -61,7 +61,7 @@ module Liquid
|
|||||||
|
|
||||||
def block_delimiter
|
def block_delimiter
|
||||||
"end#{block_name}"
|
"end#{block_name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def block_name
|
def block_name
|
||||||
@tag_name
|
@tag_name
|
||||||
@@ -77,7 +77,7 @@ module Liquid
|
|||||||
def render(context)
|
def render(context)
|
||||||
render_all(@nodelist, context)
|
render_all(@nodelist, context)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def assert_missing_delimitation!
|
def assert_missing_delimitation!
|
||||||
@@ -86,12 +86,12 @@ module Liquid
|
|||||||
|
|
||||||
def render_all(list, context)
|
def render_all(list, context)
|
||||||
list.collect do |token|
|
list.collect do |token|
|
||||||
begin
|
begin
|
||||||
token.respond_to?(:render) ? token.render(context) : token
|
token.respond_to?(:render) ? token.render(context) : token
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
context.handle_error(e)
|
context.handle_error(e)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Document < Block
|
class Document < Block
|
||||||
# we don't need markup to open this block
|
# we don't need markup to open this block
|
||||||
def initialize(tokens)
|
def initialize(tokens)
|
||||||
parse(tokens)
|
parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
# There isn't a real delimter
|
# There isn't a real delimter
|
||||||
def block_delimiter
|
def block_delimiter
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Document blocks don't need to be terminated since they are not actually opened
|
# Document blocks don't need to be terminated since they are not actually opened
|
||||||
def assert_missing_delimitation!
|
def assert_missing_delimitation!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+27
-27
@@ -1,7 +1,7 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class TableRow < Block
|
class TableRow < Block
|
||||||
Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)/
|
Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)/
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, tokens)
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@variable_name = $1
|
@variable_name = $1
|
||||||
@@ -13,62 +13,62 @@ module Liquid
|
|||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
|
raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
collection = context[@collection_name] or return ''
|
collection = context[@collection_name] or return ''
|
||||||
|
|
||||||
if @attributes['limit'] or @attributes['offset']
|
if @attributes['limit'] or @attributes['offset']
|
||||||
limit = context[@attributes['limit']] || -1
|
limit = context[@attributes['limit']] || -1
|
||||||
offset = context[@attributes['offset']] || 0
|
offset = context[@attributes['offset']] || 0
|
||||||
collection = collection[offset.to_i..(limit.to_i + offset.to_i - 1)]
|
collection = collection[offset.to_i..(limit.to_i + offset.to_i - 1)]
|
||||||
end
|
end
|
||||||
|
|
||||||
length = collection.length
|
length = collection.length
|
||||||
|
|
||||||
cols = context[@attributes['cols']].to_i
|
cols = context[@attributes['cols']].to_i
|
||||||
|
|
||||||
row = 1
|
row = 1
|
||||||
col = 0
|
col = 0
|
||||||
|
|
||||||
result = ["<tr class=\"row1\">\n"]
|
result = ["<tr class=\"row1\">\n"]
|
||||||
context.stack do
|
context.stack do
|
||||||
|
|
||||||
collection.each_with_index do |item, index|
|
collection.each_with_index do |item, index|
|
||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
context['tablerowloop'] = {
|
context['tablerowloop'] = {
|
||||||
'length' => length,
|
'length' => length,
|
||||||
'index' => index + 1,
|
'index' => index + 1,
|
||||||
'index0' => index,
|
'index0' => index,
|
||||||
'col' => col + 1,
|
'col' => col + 1,
|
||||||
'col0' => col,
|
'col0' => col,
|
||||||
'index0' => index,
|
'index0' => index,
|
||||||
'rindex' => length - index,
|
'rindex' => length - index,
|
||||||
'rindex0' => length - index -1,
|
'rindex0' => length - index -1,
|
||||||
'first' => (index == 0),
|
'first' => (index == 0),
|
||||||
'last' => (index == length - 1),
|
'last' => (index == length - 1),
|
||||||
'col_first' => (col == 0),
|
'col_first' => (col == 0),
|
||||||
'col_last' => (col == cols - 1)
|
'col_last' => (col == cols - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
col += 1
|
col += 1
|
||||||
|
|
||||||
result << ["<td class=\"col#{col}\">"] + render_all(@nodelist, context) + ['</td>']
|
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
|
col = 0
|
||||||
row += 1
|
row += 1
|
||||||
result << ["</tr>\n<tr class=\"row#{row}\">"]
|
result << ["</tr>\n<tr class=\"row#{row}\">"]
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
result + ["</tr>\n"]
|
result + ["</tr>\n"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('tablerow', TableRow)
|
Template.register_tag('tablerow', TableRow)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
# end
|
# end
|
||||||
# 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
|
# in the class <YourClass>::LiquidDropClass
|
||||||
#
|
#
|
||||||
# class SomeClass::LiquidDropClass
|
# class SomeClass::LiquidDropClass
|
||||||
@@ -37,11 +37,11 @@
|
|||||||
# output:
|
# output:
|
||||||
# 'this comes from an allowed method and this from another allowed method'
|
# '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.
|
# association models.
|
||||||
#
|
#
|
||||||
class Module
|
class Module
|
||||||
|
|
||||||
def liquid_methods(*allowed_methods)
|
def liquid_methods(*allowed_methods)
|
||||||
drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
|
drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
|
||||||
define_method :to_liquid do
|
define_method :to_liquid do
|
||||||
@@ -58,5 +58,5 @@ class Module
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ module Liquid
|
|||||||
# 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:
|
class Strainer < parent_object #:nodoc:
|
||||||
INTERNAL_METHOD = /^__/
|
INTERNAL_METHOD = /^__/
|
||||||
@@required_methods = Set.new([:__send__, :respond_to?, :extend, :methods, :class, :object_id])
|
@@required_methods = Set.new([:__id__, :__send__, :respond_to?, :extend, :methods, :class, :object_id])
|
||||||
|
|
||||||
@@filters = {}
|
@@filters = {}
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -1,26 +1,26 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
class Tag
|
class Tag
|
||||||
attr_accessor :nodelist
|
attr_accessor :nodelist
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, tokens)
|
||||||
@tag_name = tag_name
|
@tag_name = tag_name
|
||||||
@markup = markup
|
@markup = markup
|
||||||
parse(tokens)
|
parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
self.class.name.downcase
|
self.class.name.downcase
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
''
|
''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# Capture stores the result of a block into a variable without rendering it inplace.
|
# Capture stores the result of a block into a variable without rendering it inplace.
|
||||||
#
|
#
|
||||||
# {% capture heading %}
|
# {% capture heading %}
|
||||||
@@ -8,28 +8,28 @@ module Liquid
|
|||||||
# ...
|
# ...
|
||||||
# <h1>{{ monkeys }}</h1>
|
# <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.
|
# in a sidebar or footer.
|
||||||
#
|
#
|
||||||
class Capture < Block
|
class Capture < Block
|
||||||
Syntax = /(\w+)/
|
Syntax = /(\w+)/
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, tokens)
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@to = $1
|
@to = $1
|
||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
|
raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
output = super
|
output = super
|
||||||
context[@to] = output.to_s
|
context[@to] = output.join
|
||||||
''
|
''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('capture', Capture)
|
Template.register_tag('capture', Capture)
|
||||||
end
|
end
|
||||||
|
|||||||
+51
-51
@@ -1,120 +1,120 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# Templates are central to liquid.
|
# Templates are central to liquid.
|
||||||
# Interpretating templates is a two step process. First you compile the
|
# Interpretating templates is a two step process. First you compile the
|
||||||
# source code you got. During compile time some extensive error checking is performed.
|
# source code you got. During compile time some extensive error checking is performed.
|
||||||
# your code should expect to get some SyntaxErrors.
|
# your code should expect to get some SyntaxErrors.
|
||||||
#
|
#
|
||||||
# After you have a compiled template you can then <tt>render</tt> it.
|
# 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.
|
# You can use a compiled template over and over again and keep it cached.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
#
|
#
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# template = Liquid::Template.parse(source)
|
# template = Liquid::Template.parse(source)
|
||||||
# template.render('user_name' => 'bob')
|
# template.render('user_name' => 'bob')
|
||||||
#
|
#
|
||||||
class Template
|
class Template
|
||||||
attr_accessor :root
|
attr_accessor :root
|
||||||
@@file_system = BlankFileSystem.new
|
@@file_system = BlankFileSystem.new
|
||||||
|
|
||||||
class <<self
|
class << self
|
||||||
def file_system
|
def file_system
|
||||||
@@file_system
|
@@file_system
|
||||||
end
|
end
|
||||||
|
|
||||||
def file_system=(obj)
|
def file_system=(obj)
|
||||||
@@file_system = obj
|
@@file_system = obj
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_tag(name, klass)
|
def register_tag(name, klass)
|
||||||
tags[name.to_s] = klass
|
tags[name.to_s] = klass
|
||||||
end
|
end
|
||||||
|
|
||||||
def tags
|
def tags
|
||||||
@tags ||= {}
|
@tags ||= {}
|
||||||
end
|
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
|
# to all liquid views. Good for registering the standard library
|
||||||
def register_filter(mod)
|
def register_filter(mod)
|
||||||
Strainer.global_filter(mod)
|
Strainer.global_filter(mod)
|
||||||
end
|
end
|
||||||
|
|
||||||
# creates a new <tt>Template</tt> object from liquid source code
|
# creates a new <tt>Template</tt> object from liquid source code
|
||||||
def parse(source)
|
def parse(source)
|
||||||
template = Template.new
|
template = Template.new
|
||||||
template.parse(source)
|
template.parse(source)
|
||||||
template
|
template
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
|
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
|
||||||
def initialize
|
def initialize
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse source code.
|
# Parse source code.
|
||||||
# Returns self for easy chaining
|
# Returns self for easy chaining
|
||||||
def parse(source)
|
def parse(source)
|
||||||
@root = Document.new(tokenize(source))
|
@root = Document.new(tokenize(source))
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def registers
|
def registers
|
||||||
@registers ||= {}
|
@registers ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def assigns
|
def assigns
|
||||||
@assigns ||= {}
|
@assigns ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors
|
def errors
|
||||||
@errors ||= []
|
@errors ||= []
|
||||||
end
|
end
|
||||||
|
|
||||||
# Render takes a hash with local variables.
|
# 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>
|
# with <tt>Template.register_filter</tt>
|
||||||
#
|
#
|
||||||
# Following options can be passed:
|
# Following options can be passed:
|
||||||
#
|
#
|
||||||
# * <tt>filters</tt> : array with local filters
|
# * <tt>filters</tt> : array with local filters
|
||||||
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
|
# * <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
|
# filters and tags and might be useful to integrate liquid more with its host application
|
||||||
#
|
#
|
||||||
def render(*args)
|
def render(*args)
|
||||||
return '' if @root.nil?
|
return '' if @root.nil?
|
||||||
|
|
||||||
context = case args.first
|
context = case args.first
|
||||||
when Liquid::Context
|
when Liquid::Context
|
||||||
args.shift
|
args.shift
|
||||||
when Hash
|
when Hash
|
||||||
self.assigns.merge!(args.shift)
|
self.assigns.merge!(args.shift)
|
||||||
Context.new(assigns, registers, @rethrow_errors)
|
Context.new(assigns, registers, @rethrow_errors)
|
||||||
when nil
|
when nil
|
||||||
Context.new(assigns, registers, @rethrow_errors)
|
Context.new(assigns, registers, @rethrow_errors)
|
||||||
else
|
else
|
||||||
raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
|
raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
|
||||||
end
|
end
|
||||||
|
|
||||||
case args.last
|
case args.last
|
||||||
when Hash
|
when Hash
|
||||||
options = args.pop
|
options = args.pop
|
||||||
|
|
||||||
if options[:registers].is_a?(Hash)
|
if options[:registers].is_a?(Hash)
|
||||||
self.registers.merge!(options[:registers])
|
self.registers.merge!(options[:registers])
|
||||||
end
|
end
|
||||||
|
|
||||||
if options[:filters]
|
if options[:filters]
|
||||||
context.add_filters(options[:filters])
|
context.add_filters(options[:filters])
|
||||||
end
|
end
|
||||||
|
|
||||||
when Module
|
when Module
|
||||||
context.add_filters(args.pop)
|
context.add_filters(args.pop)
|
||||||
when Array
|
when Array
|
||||||
context.add_filters(args.pop)
|
context.add_filters(args.pop)
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# render the nodelist.
|
# render the nodelist.
|
||||||
# for performance reasons we get a array back here. join will make a string out of it
|
# for performance reasons we get a array back here. join will make a string out of it
|
||||||
@@ -123,24 +123,24 @@ module Liquid
|
|||||||
@errors = context.errors
|
@errors = context.errors
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render!(*args)
|
def render!(*args)
|
||||||
@rethrow_errors = true; render(*args)
|
@rethrow_errors = true; render(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
|
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
|
||||||
def tokenize(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?
|
return [] if source.to_s.empty?
|
||||||
tokens = source.split(TemplateParser)
|
tokens = source.split(TemplateParser)
|
||||||
|
|
||||||
# removes the rogue empty element at the beginning of the array
|
# 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
|
tokens
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user