rejects variables like a/b in for loops, closes #150

This commit is contained in:
Peter Schröder
2013-06-11 17:22:33 -04:00
parent 94ff457744
commit a2df5a421d
2 changed files with 32 additions and 26 deletions
+26 -26
View File
@@ -1,6 +1,6 @@
module Liquid module Liquid
# "For" iterates over an array or collection. # "For" iterates over an array or collection.
# Several useful variables are available to you within the loop. # Several useful variables are available to you within the loop.
# #
# == Basic usage: # == Basic usage:
@@ -22,7 +22,7 @@ module Liquid
# #
# {% for item in collection limit:5 offset:10 %} # {% for item in collection limit:5 offset:10 %}
# {{ item.name }} # {{ item.name }}
# {% end %} # {% end %}
# #
# To reverse the for loop simply use {% for item in collection reversed %} # To reverse the for loop simply use {% for item in collection reversed %}
# #
@@ -31,7 +31,7 @@ module Liquid
# forloop.name:: 'item-collection' # forloop.name:: 'item-collection'
# forloop.length:: Length of the loop # forloop.length:: Length of the loop
# forloop.index:: The current item's position in the collection; # forloop.index:: The current item's position in the collection;
# forloop.index starts at 1. # forloop.index starts at 1.
# This is helpful for non-programmers who start believe # This is helpful for non-programmers who start believe
# the first item in an array is 1, not 0. # the first item in an array is 1, not 0.
# forloop.index0:: The current item's position in the collection # forloop.index0:: The current item's position in the collection
@@ -43,19 +43,19 @@ module Liquid
# forloop.first:: Returns true if the item is the first item. # forloop.first:: Returns true if the item is the first item.
# forloop.last:: Returns true if the item is the last item. # forloop.last:: Returns true if the item is the last item.
# #
class For < Block class For < Block
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o Syntax = /\A(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
if markup =~ Syntax if markup =~ Syntax
@variable_name = $1 @variable_name = $1
@collection_name = $2 @collection_name = $2
@name = "#{$1}-#{$2}" @name = "#{$1}-#{$2}"
@reversed = $3 @reversed = $3
@attributes = {} @attributes = {}
markup.scan(TagAttributes) do |key, value| markup.scan(TagAttributes) do |key, value|
@attributes[key] = value @attributes[key] = value
end end
else else
raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]") raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
end end
@@ -68,51 +68,51 @@ module Liquid
return super unless tag == 'else' return super unless tag == 'else'
@nodelist = @else_block = [] @nodelist = @else_block = []
end end
def render(context) def render(context)
context.registers[:for] ||= Hash.new(0) context.registers[:for] ||= Hash.new(0)
collection = context[@collection_name] collection = context[@collection_name]
collection = collection.to_a if collection.is_a?(Range) collection = collection.to_a if collection.is_a?(Range)
# Maintains Ruby 1.8.7 String#each behaviour on 1.9 # Maintains Ruby 1.8.7 String#each behaviour on 1.9
return render_else(context) unless iterable?(collection) return render_else(context) unless iterable?(collection)
from = if @attributes['offset'] == 'continue' from = if @attributes['offset'] == 'continue'
context.registers[:for][@name].to_i context.registers[:for][@name].to_i
else else
context[@attributes['offset']].to_i context[@attributes['offset']].to_i
end end
limit = context[@attributes['limit']] limit = context[@attributes['limit']]
to = limit ? limit.to_i + from : nil to = limit ? limit.to_i + from : nil
segment = Utils.slice_collection_using_each(collection, from, to) segment = Utils.slice_collection_using_each(collection, from, to)
return render_else(context) if segment.empty? return render_else(context) if segment.empty?
segment.reverse! if @reversed segment.reverse! if @reversed
result = '' result = ''
length = segment.length length = segment.length
# Store our progress through the collection for the continue flag # Store our progress through the collection for the continue flag
context.registers[:for][@name] = from + segment.length context.registers[:for][@name] = from + segment.length
context.stack do context.stack do
segment.each_with_index do |item, index| segment.each_with_index do |item, index|
context[@variable_name] = item context[@variable_name] = item
context['forloop'] = { context['forloop'] = {
'name' => @name, 'name' => @name,
'length' => length, 'length' => length,
'index' => index + 1, 'index' => index + 1,
'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) }
result << render_all(@for_block, context) result << render_all(@for_block, context)
@@ -124,8 +124,8 @@ module Liquid
end end
end end
end end
result result
end end
private private
+6
View File
@@ -281,4 +281,10 @@ HERE
def test_blank_string_not_iterable def test_blank_string_not_iterable
assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '') assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
end end
def test_bad_variable_naming_in_for_loop
assert_raise(Liquid::SyntaxError) do
Liquid::Template.parse('{% for a/b in x %}{% endfor %}')
end
end
end end