mirror of
https://github.com/Shopify/liquid.git
synced 2025-09-21 00:00:32 -04:00
Changed implementation of For in such a way that it only depends on the existence of a each method. This allows drops to simply implement each for enumeration
This commit is contained in:
parent
7f58cbf82d
commit
4c0cfae0b7
@ -90,8 +90,7 @@ module Liquid
|
||||
token.respond_to?(:render) ? token.render(context) : token
|
||||
rescue Exception => e
|
||||
context.handle_error(e)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -60,6 +60,7 @@ module Liquid
|
||||
|
||||
# push new local scope on the stack. use <tt>Context#stack</tt> instead
|
||||
def push
|
||||
raise StackLevelError, "Nesting too deep" if @scopes.length > 100
|
||||
@scopes.unshift({})
|
||||
end
|
||||
|
||||
|
@ -7,4 +7,5 @@ module Liquid
|
||||
class FileSystemError < Error; end
|
||||
class StandardError < Error; end
|
||||
class SyntaxError < Error; end
|
||||
class StackLevelError < Error; end
|
||||
end
|
@ -64,35 +64,31 @@ module Liquid
|
||||
collection = context[@collection_name]
|
||||
collection = collection.to_a if collection.is_a?(Range)
|
||||
|
||||
return '' if collection.nil? or collection.empty?
|
||||
|
||||
range = (0..collection.length)
|
||||
|
||||
if @attributes['limit'] or @attributes['offset']
|
||||
offset = 0
|
||||
if @attributes['offset'] == 'continue'
|
||||
offset = context.registers[:for][@name]
|
||||
else
|
||||
offset = context[@attributes['offset']] || 0
|
||||
end
|
||||
limit = context[@attributes['limit']]
|
||||
|
||||
range_end = limit ? offset + limit : collection.length
|
||||
range = (offset..range_end-1)
|
||||
|
||||
# Save the range end in the registers so that future calls to
|
||||
# offset:continue have something to pick up
|
||||
context.registers[:for][@name] = range_end
|
||||
return '' unless collection.respond_to?(:each)
|
||||
|
||||
from = if @attributes['offset'] == 'continue'
|
||||
context.registers[:for][@name].to_i
|
||||
else
|
||||
context[@attributes['offset']].to_i
|
||||
end
|
||||
|
||||
result = []
|
||||
segment = collection[range]
|
||||
return '' if segment.nil?
|
||||
|
||||
context.stack do
|
||||
length = segment.length
|
||||
|
||||
limit = context[@attributes['limit']]
|
||||
to = limit ? limit.to_i + from : nil
|
||||
|
||||
|
||||
segment = slice_collection_using_each(collection, from, to)
|
||||
|
||||
segment.each_with_index do |item, index|
|
||||
return '' if segment.empty?
|
||||
|
||||
result = []
|
||||
|
||||
length = segment.length
|
||||
|
||||
# Store our progress through the collection for the continue flag
|
||||
context.registers[:for][@name] = from + segment.length
|
||||
|
||||
context.stack do
|
||||
segment.each_with_index do |item, index|
|
||||
context[@variable_name] = item
|
||||
context['forloop'] = {
|
||||
'name' => @name,
|
||||
@ -103,15 +99,32 @@ module Liquid
|
||||
'rindex0' => length - index -1,
|
||||
'first' => (index == 0),
|
||||
'last' => (index == length - 1) }
|
||||
|
||||
|
||||
result << render_all(@nodelist, context)
|
||||
end
|
||||
end
|
||||
|
||||
# Store position of last element we rendered. This allows us to do
|
||||
|
||||
result
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def slice_collection_using_each(collection, from, to)
|
||||
segments = []
|
||||
index = 0
|
||||
yielded = 0
|
||||
collection.each do |item|
|
||||
|
||||
if to && to <= index
|
||||
break
|
||||
end
|
||||
|
||||
if from <= index
|
||||
segments << item
|
||||
end
|
||||
|
||||
index += 1
|
||||
end
|
||||
|
||||
segments
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('for', For)
|
||||
|
@ -83,7 +83,7 @@ module Liquid
|
||||
# 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
|
||||
@ -107,17 +107,17 @@ module Liquid
|
||||
|
||||
if options[:filters]
|
||||
context.add_filters(options[:filters])
|
||||
end
|
||||
end
|
||||
|
||||
when Module
|
||||
context.add_filters(args.pop)
|
||||
when Array
|
||||
context.add_filters(args.pop)
|
||||
end
|
||||
|
||||
|
||||
# render the nodelist.
|
||||
# for performance reasons we get a array back here. to_s will make a string out of it
|
||||
|
||||
begin
|
||||
# render the nodelist.
|
||||
# for performance reasons we get a array back here. join will make a string out of it
|
||||
@root.render(context).join
|
||||
ensure
|
||||
@errors = context.errors
|
||||
|
@ -59,6 +59,16 @@ class ProductDrop < Liquid::Drop
|
||||
def callmenot
|
||||
"protected"
|
||||
end
|
||||
end
|
||||
|
||||
class EnumerableDrop < Liquid::Drop
|
||||
include Enumerable
|
||||
|
||||
def each
|
||||
yield 1
|
||||
yield 2
|
||||
yield 3
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -132,6 +142,10 @@ class DropsTest < Test::Unit::TestCase
|
||||
|
||||
def test_access_context_from_drop
|
||||
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1,2,3])
|
||||
end
|
||||
|
||||
def test_enumerable_drop
|
||||
assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render('collection' => EnumerableDrop.new)
|
||||
end
|
||||
|
||||
|
||||
|
@ -57,8 +57,23 @@ class ErrorHandlingTest < Test::Unit::TestCase
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def test_missing_endtag
|
||||
|
||||
assert_nothing_raised do
|
||||
|
||||
template = Liquid::Template.parse(' {% for a in b %} ... ')
|
||||
assert_equal ' Liquid error: Unknown operator =! ', template.render
|
||||
|
||||
assert_equal 1, template.errors.size
|
||||
assert_equal Liquid::SyntaxError, template.errors.first.class
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def test_unrecognized_operator
|
||||
|
||||
assert_nothing_raised do
|
||||
|
@ -96,9 +96,10 @@ class IncludeTagTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
Liquid::Template.file_system = infinite_file_system.new
|
||||
|
||||
assert_match /-{552}Liquid error: stack level too deep$/,
|
||||
Template.parse("{% include 'loop' %}").render
|
||||
|
||||
assert_raise(Liquid::StackLevelError) do
|
||||
Template.parse("{% include 'loop' %}").render!
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
@ -104,11 +104,14 @@ HERE
|
||||
assert_template_result('12','{%for i in array limit:2 %}{{ i }}{%endfor%}',assigns)
|
||||
assert_template_result('1234','{%for i in array limit:4 %}{{ i }}{%endfor%}',assigns)
|
||||
assert_template_result('3456','{%for i in array limit:4 offset:2 %}{{ i }}{%endfor%}',assigns)
|
||||
assert_template_result('3456','{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}',assigns)
|
||||
|
||||
assert_template_result('3456','{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}',assigns)
|
||||
end
|
||||
|
||||
def test_dynamic_variable_limiting
|
||||
assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
|
||||
assigns['limit'] = 2
|
||||
assigns['offset'] = 2
|
||||
assert_template_result('34','{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}',assigns)
|
||||
assert_template_result('34','{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}',assigns)
|
||||
end
|
||||
|
||||
def test_nested_for
|
||||
|
Loading…
x
Reference in New Issue
Block a user