liquid 3.0.6 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +98 -58
  3. data/README.md +31 -0
  4. data/lib/liquid/block.rb +31 -124
  5. data/lib/liquid/block_body.rb +75 -59
  6. data/lib/liquid/condition.rb +23 -22
  7. data/lib/liquid/context.rb +50 -46
  8. data/lib/liquid/document.rb +19 -9
  9. data/lib/liquid/drop.rb +17 -16
  10. data/lib/liquid/errors.rb +20 -24
  11. data/lib/liquid/expression.rb +15 -3
  12. data/lib/liquid/extensions.rb +13 -7
  13. data/lib/liquid/file_system.rb +11 -11
  14. data/lib/liquid/forloop_drop.rb +42 -0
  15. data/lib/liquid/i18n.rb +5 -5
  16. data/lib/liquid/interrupts.rb +1 -2
  17. data/lib/liquid/lexer.rb +6 -4
  18. data/lib/liquid/locales/en.yml +5 -1
  19. data/lib/liquid/parse_context.rb +37 -0
  20. data/lib/liquid/parser_switching.rb +4 -4
  21. data/lib/liquid/profiler/hooks.rb +7 -7
  22. data/lib/liquid/profiler.rb +18 -19
  23. data/lib/liquid/range_lookup.rb +16 -1
  24. data/lib/liquid/resource_limits.rb +23 -0
  25. data/lib/liquid/standardfilters.rb +121 -61
  26. data/lib/liquid/strainer.rb +14 -7
  27. data/lib/liquid/tablerowloop_drop.rb +62 -0
  28. data/lib/liquid/tag.rb +9 -8
  29. data/lib/liquid/tags/assign.rb +17 -4
  30. data/lib/liquid/tags/break.rb +0 -3
  31. data/lib/liquid/tags/capture.rb +1 -1
  32. data/lib/liquid/tags/case.rb +19 -12
  33. data/lib/liquid/tags/comment.rb +2 -2
  34. data/lib/liquid/tags/cycle.rb +6 -6
  35. data/lib/liquid/tags/decrement.rb +1 -4
  36. data/lib/liquid/tags/for.rb +95 -75
  37. data/lib/liquid/tags/if.rb +49 -44
  38. data/lib/liquid/tags/ifchanged.rb +0 -2
  39. data/lib/liquid/tags/include.rb +61 -52
  40. data/lib/liquid/tags/raw.rb +32 -4
  41. data/lib/liquid/tags/table_row.rb +12 -30
  42. data/lib/liquid/tags/unless.rb +3 -4
  43. data/lib/liquid/template.rb +42 -54
  44. data/lib/liquid/tokenizer.rb +31 -0
  45. data/lib/liquid/utils.rb +52 -8
  46. data/lib/liquid/variable.rb +46 -45
  47. data/lib/liquid/variable_lookup.rb +7 -5
  48. data/lib/liquid/version.rb +1 -1
  49. data/lib/liquid.rb +9 -7
  50. data/test/integration/assign_test.rb +8 -8
  51. data/test/integration/blank_test.rb +14 -14
  52. data/test/integration/context_test.rb +2 -2
  53. data/test/integration/document_test.rb +19 -0
  54. data/test/integration/drop_test.rb +42 -40
  55. data/test/integration/error_handling_test.rb +99 -46
  56. data/test/integration/filter_test.rb +60 -20
  57. data/test/integration/hash_ordering_test.rb +9 -9
  58. data/test/integration/output_test.rb +26 -27
  59. data/test/integration/parsing_quirks_test.rb +15 -13
  60. data/test/integration/render_profiling_test.rb +20 -20
  61. data/test/integration/security_test.rb +9 -7
  62. data/test/integration/standard_filter_test.rb +179 -40
  63. data/test/integration/tags/break_tag_test.rb +1 -2
  64. data/test/integration/tags/continue_tag_test.rb +0 -1
  65. data/test/integration/tags/for_tag_test.rb +133 -98
  66. data/test/integration/tags/if_else_tag_test.rb +75 -77
  67. data/test/integration/tags/include_tag_test.rb +34 -30
  68. data/test/integration/tags/increment_tag_test.rb +10 -11
  69. data/test/integration/tags/raw_tag_test.rb +7 -1
  70. data/test/integration/tags/standard_tag_test.rb +121 -122
  71. data/test/integration/tags/statements_test.rb +3 -5
  72. data/test/integration/tags/table_row_test.rb +20 -19
  73. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  74. data/test/integration/template_test.rb +190 -49
  75. data/test/integration/trim_mode_test.rb +525 -0
  76. data/test/integration/variable_test.rb +23 -13
  77. data/test/test_helper.rb +33 -5
  78. data/test/unit/block_unit_test.rb +8 -5
  79. data/test/unit/condition_unit_test.rb +86 -77
  80. data/test/unit/context_unit_test.rb +48 -57
  81. data/test/unit/file_system_unit_test.rb +3 -3
  82. data/test/unit/i18n_unit_test.rb +2 -2
  83. data/test/unit/lexer_unit_test.rb +11 -8
  84. data/test/unit/parser_unit_test.rb +2 -2
  85. data/test/unit/regexp_unit_test.rb +1 -1
  86. data/test/unit/strainer_unit_test.rb +80 -1
  87. data/test/unit/tag_unit_test.rb +7 -2
  88. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  89. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  90. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  91. data/test/unit/template_unit_test.rb +14 -5
  92. data/test/unit/tokenizer_unit_test.rb +24 -7
  93. data/test/unit/variable_unit_test.rb +60 -43
  94. metadata +19 -14
  95. data/lib/liquid/module_ex.rb +0 -62
  96. data/lib/liquid/token.rb +0 -18
  97. data/test/unit/module_ex_unit_test.rb +0 -87
  98. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -3,21 +3,26 @@ module Liquid
3
3
  #
4
4
  # Example:
5
5
  #
6
- # c = Condition.new('1', '==', '1')
6
+ # c = Condition.new(1, '==', 1)
7
7
  # c.evaluate #=> true
8
8
  #
9
9
  class Condition #:nodoc:
10
10
  @@operators = {
11
- '=='.freeze => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
12
- '!='.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
13
- '<>'.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
11
+ '=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
12
+ '!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
13
+ '<>'.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
14
14
  '<'.freeze => :<,
15
15
  '>'.freeze => :>,
16
16
  '>='.freeze => :>=,
17
17
  '<='.freeze => :<=,
18
- 'contains'.freeze => lambda { |cond, left, right|
19
- left && right && left.respond_to?(:include?) ? left.include?(right) : false
20
- }
18
+ 'contains'.freeze => lambda do |cond, left, right|
19
+ if left && right && left.respond_to?(:include?)
20
+ right = right.to_s if left.is_a?(String)
21
+ left.include?(right)
22
+ else
23
+ false
24
+ end
25
+ end
21
26
  }
22
27
 
23
28
  def self.operators
@@ -73,17 +78,17 @@ module Liquid
73
78
  private
74
79
 
75
80
  def equal_variables(left, right)
76
- if left.is_a?(Symbol)
77
- if right.respond_to?(left)
78
- return right.send(left.to_s)
81
+ if left.is_a?(Liquid::Expression::MethodLiteral)
82
+ if right.respond_to?(left.method_name)
83
+ return right.send(left.method_name)
79
84
  else
80
85
  return nil
81
86
  end
82
87
  end
83
88
 
84
- if right.is_a?(Symbol)
85
- if left.respond_to?(right)
86
- return left.send(right.to_s)
89
+ if right.is_a?(Liquid::Expression::MethodLiteral)
90
+ if left.respond_to?(right.method_name)
91
+ return left.send(right.method_name)
87
92
  else
88
93
  return nil
89
94
  end
@@ -96,36 +101,32 @@ module Liquid
96
101
  # If the operator is empty this means that the decision statement is just
97
102
  # a single variable. We can just poll this variable from the context and
98
103
  # return this as the result.
99
- return context[left] if op == nil
104
+ return context.evaluate(left) if op.nil?
100
105
 
101
- left = context[left]
102
- right = context[right]
106
+ left = context.evaluate(left)
107
+ right = context.evaluate(right)
103
108
 
104
109
  operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
105
110
 
106
111
  if operation.respond_to?(:call)
107
112
  operation.call(self, left, right)
108
- elsif left.respond_to?(operation) and right.respond_to?(operation)
113
+ elsif left.respond_to?(operation) && right.respond_to?(operation)
109
114
  begin
110
115
  left.send(operation, right)
111
116
  rescue ::ArgumentError => e
112
117
  raise Liquid::ArgumentError.new(e.message)
113
118
  end
114
- else
115
- nil
116
119
  end
117
120
  end
118
121
  end
119
122
 
120
-
121
123
  class ElseCondition < Condition
122
124
  def else?
123
125
  true
124
126
  end
125
127
 
126
- def evaluate(context)
128
+ def evaluate(_context)
127
129
  true
128
130
  end
129
131
  end
130
-
131
132
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Context keeps the variable stack and resolves variables, as well as keywords
4
3
  #
5
4
  # context['variable'] = 'testing'
@@ -14,41 +13,32 @@ module Liquid
14
13
  # context['bob'] #=> nil class Context
15
14
  class Context
16
15
  attr_reader :scopes, :errors, :registers, :environments, :resource_limits
17
- attr_accessor :exception_handler
16
+ attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
18
17
 
19
18
  def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
20
19
  @environments = [environments].flatten
21
20
  @scopes = [(outer_scope || {})]
22
21
  @registers = registers
23
22
  @errors = []
24
- @resource_limits = resource_limits || Template.default_resource_limits.dup
25
- @resource_limits[:render_score_current] = 0
26
- @resource_limits[:assign_score_current] = 0
27
- @parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
23
+ @partial = false
24
+ @strict_variables = false
25
+ @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
28
26
  squash_instance_assigns_with_environments
29
27
 
30
28
  @this_stack_used = false
31
29
 
30
+ self.exception_renderer = Template.default_exception_renderer
32
31
  if rethrow_errors
33
- self.exception_handler = ->(e) { true }
32
+ self.exception_renderer = ->(e) { raise }
34
33
  end
35
34
 
36
35
  @interrupts = []
37
36
  @filters = []
37
+ @global_filter = nil
38
38
  end
39
39
 
40
- def increment_used_resources(key, obj)
41
- @resource_limits[key] += if obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash)
42
- obj.length
43
- else
44
- 1
45
- end
46
- end
47
-
48
- def resource_limits_reached?
49
- (@resource_limits[:render_length_limit] && @resource_limits[:render_length_current] > @resource_limits[:render_length_limit]) ||
50
- (@resource_limits[:render_score_limit] && @resource_limits[:render_score_current] > @resource_limits[:render_score_limit] ) ||
51
- (@resource_limits[:assign_score_limit] && @resource_limits[:assign_score_current] > @resource_limits[:assign_score_limit] )
40
+ def warnings
41
+ @warnings ||= []
52
42
  end
53
43
 
54
44
  def strainer
@@ -65,8 +55,12 @@ module Liquid
65
55
  @strainer = nil
66
56
  end
67
57
 
58
+ def apply_global_filter(obj)
59
+ global_filter.nil? ? obj : global_filter.call(obj)
60
+ end
61
+
68
62
  # are there any not handled interrupts?
69
- def has_interrupt?
63
+ def interrupt?
70
64
  !@interrupts.empty?
71
65
  end
72
66
 
@@ -80,15 +74,12 @@ module Liquid
80
74
  @interrupts.pop
81
75
  end
82
76
 
83
-
84
- def handle_error(e, token=nil)
85
- if e.is_a?(Liquid::Error)
86
- e.set_line_number_from_token(token)
87
- end
88
-
77
+ def handle_error(e, line_number = nil, raw_token = nil)
78
+ e = internal_error unless e.is_a?(Liquid::Error)
79
+ e.template_name ||= template_name
80
+ e.line_number ||= line_number
89
81
  errors.push(e)
90
- raise if exception_handler && exception_handler.call(e)
91
- Liquid::Error.render(e)
82
+ exception_renderer.call(e).to_s
92
83
  end
93
84
 
94
85
  def invoke(method, *args)
@@ -96,7 +87,7 @@ module Liquid
96
87
  end
97
88
 
98
89
  # Push new local scope on the stack. use <tt>Context#stack</tt> instead
99
- def push(new_scope={})
90
+ def push(new_scope = {})
100
91
  @scopes.unshift(new_scope)
101
92
  raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
102
93
  end
@@ -120,7 +111,7 @@ module Liquid
120
111
  # end
121
112
  #
122
113
  # context['var] #=> nil
123
- def stack(new_scope=nil)
114
+ def stack(new_scope = nil)
124
115
  old_stack_used = @this_stack_used
125
116
  if new_scope
126
117
  push(new_scope)
@@ -157,10 +148,10 @@ module Liquid
157
148
  # Example:
158
149
  # products == empty #=> products.empty?
159
150
  def [](expression)
160
- evaluate(@parsed_expression[expression])
151
+ evaluate(Expression.parse(expression))
161
152
  end
162
153
 
163
- def has_key?(key)
154
+ def key?(key)
164
155
  self[key] != nil
165
156
  end
166
157
 
@@ -170,10 +161,9 @@ module Liquid
170
161
 
171
162
  # Fetches an object starting at the local scope and then moving up the hierachy
172
163
  def find_variable(key)
173
-
174
164
  # This was changed from find() to find_index() because this is a very hot
175
165
  # path and find_index() is optimized in MRI to reduce object allocation
176
- index = @scopes.find_index { |s| s.has_key?(key) }
166
+ index = @scopes.find_index { |s| s.key?(key) }
177
167
  scope = @scopes[index] if index
178
168
 
179
169
  variable = nil
@@ -188,17 +178,23 @@ module Liquid
188
178
  end
189
179
  end
190
180
 
191
- scope ||= @environments.last || @scopes.last
192
- variable ||= lookup_and_evaluate(scope, key)
181
+ scope ||= @environments.last || @scopes.last
182
+ variable ||= lookup_and_evaluate(scope, key)
193
183
 
194
184
  variable = variable.to_liquid
195
185
  variable.context = self if variable.respond_to?(:context=)
196
186
 
197
- return variable
187
+ variable
198
188
  end
199
189
 
200
190
  def lookup_and_evaluate(obj, key)
201
- if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
191
+ if @strict_variables && obj.respond_to?(:key?) && !obj.key?(key)
192
+ raise Liquid::UndefinedVariable, "undefined variable #{key}"
193
+ end
194
+
195
+ value = obj[key]
196
+
197
+ if value.is_a?(Proc) && obj.respond_to?(:[]=)
202
198
  obj[key] = (value.arity == 0) ? value.call : value.call(self)
203
199
  else
204
200
  value
@@ -206,15 +202,23 @@ module Liquid
206
202
  end
207
203
 
208
204
  private
209
- def squash_instance_assigns_with_environments
210
- @scopes.last.each_key do |k|
211
- @environments.each do |env|
212
- if env.has_key?(k)
213
- scopes.last[k] = lookup_and_evaluate(env, k)
214
- break
215
- end
205
+
206
+ def internal_error
207
+ # raise and catch to set backtrace and cause on exception
208
+ raise Liquid::InternalError, 'internal'
209
+ rescue Liquid::InternalError => exc
210
+ exc
211
+ end
212
+
213
+ def squash_instance_assigns_with_environments
214
+ @scopes.last.each_key do |k|
215
+ @environments.each do |env|
216
+ if env.key?(k)
217
+ scopes.last[k] = lookup_and_evaluate(env, k)
218
+ break
216
219
  end
217
220
  end
218
- end # squash_instance_assigns_with_environments
221
+ end
222
+ end # squash_instance_assigns_with_environments
219
223
  end # Context
220
224
  end # Liquid
@@ -1,17 +1,27 @@
1
1
  module Liquid
2
- class Document < Block
3
- def self.parse(tokens, options={})
4
- # we don't need markup to open this block
5
- super(nil, nil, tokens, options)
2
+ class Document < BlockBody
3
+ def self.parse(tokens, parse_context)
4
+ doc = new
5
+ doc.parse(tokens, parse_context)
6
+ doc
6
7
  end
7
8
 
8
- # There isn't a real delimiter
9
- def block_delimiter
10
- []
9
+ def parse(tokens, parse_context)
10
+ super do |end_tag_name, end_tag_params|
11
+ unknown_tag(end_tag_name, parse_context) if end_tag_name
12
+ end
13
+ rescue SyntaxError => e
14
+ e.line_number ||= parse_context.line_number
15
+ raise
11
16
  end
12
17
 
13
- # Document blocks don't need to be terminated since they are not actually opened
14
- def assert_missing_delimitation!
18
+ def unknown_tag(tag, parse_context)
19
+ case tag
20
+ when 'else'.freeze, 'end'.freeze
21
+ raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag))
22
+ else
23
+ raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
24
+ end
15
25
  end
16
26
  end
17
27
  end
data/lib/liquid/drop.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'set'
2
2
 
3
3
  module Liquid
4
-
5
4
  # A drop in liquid is a class which allows you to export DOM like things to liquid.
6
5
  # Methods of drops are callable.
7
6
  # The main use for liquid drops is to implement lazy loaded objects.
@@ -19,28 +18,27 @@ module Liquid
19
18
  # tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
20
19
  # tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
21
20
  #
22
- # Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
23
- # catch all.
21
+ # Your drop can either implement the methods sans any parameters
22
+ # or implement the liquid_method_missing(name) method which is a catch all.
24
23
  class Drop
25
24
  attr_writer :context
26
25
 
27
- EMPTY_STRING = ''.freeze
28
-
29
26
  # Catch all for the method
30
- def before_method(method)
31
- nil
27
+ def liquid_method_missing(method)
28
+ return nil unless @context && @context.strict_variables
29
+ raise Liquid::UndefinedDropMethod, "undefined method #{method}"
32
30
  end
33
31
 
34
32
  # called by liquid to invoke a drop
35
33
  def invoke_drop(method_or_key)
36
- if method_or_key && method_or_key != EMPTY_STRING && self.class.invokable?(method_or_key)
34
+ if self.class.invokable?(method_or_key)
37
35
  send(method_or_key)
38
36
  else
39
- before_method(method_or_key)
37
+ liquid_method_missing(method_or_key)
40
38
  end
41
39
  end
42
40
 
43
- def has_key?(name)
41
+ def key?(_name)
44
42
  true
45
43
  end
46
44
 
@@ -56,22 +54,25 @@ module Liquid
56
54
  self.class.name
57
55
  end
58
56
 
59
- alias :[] :invoke_drop
60
-
61
- private
57
+ alias_method :[], :invoke_drop
62
58
 
63
59
  # Check for method existence without invoking respond_to?, which creates symbols
64
60
  def self.invokable?(method_name)
65
- unless @invokable_methods
61
+ invokable_methods.include?(method_name.to_s)
62
+ end
63
+
64
+ def self.invokable_methods
65
+ @invokable_methods ||= begin
66
66
  blacklist = Liquid::Drop.public_instance_methods + [:each]
67
+
67
68
  if include?(Enumerable)
68
69
  blacklist += Enumerable.public_instance_methods
69
70
  blacklist -= [:sort, :count, :first, :min, :max, :include?]
70
71
  end
72
+
71
73
  whitelist = [:to_liquid] + (public_instance_methods - blacklist)
72
- @invokable_methods = Set.new(whitelist.map(&:to_s))
74
+ Set.new(whitelist.map(&:to_s))
73
75
  end
74
- @invokable_methods.include?(method_name.to_s)
75
76
  end
76
77
  end
77
78
  end
data/lib/liquid/errors.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  module Liquid
2
2
  class Error < ::StandardError
3
3
  attr_accessor :line_number
4
+ attr_accessor :template_name
4
5
  attr_accessor :markup_context
5
6
 
6
- def to_s(with_prefix=true)
7
+ def to_s(with_prefix = true)
7
8
  str = ""
8
9
  str << message_prefix if with_prefix
9
10
  str << super()
@@ -16,20 +17,6 @@ module Liquid
16
17
  str
17
18
  end
18
19
 
19
- def set_line_number_from_token(token)
20
- return unless token.respond_to?(:line_number)
21
- return if self.line_number
22
- self.line_number = token.line_number
23
- end
24
-
25
- def self.render(e)
26
- if e.is_a?(Liquid::Error)
27
- e.to_s
28
- else
29
- "Liquid error: #{e.to_s}"
30
- end
31
- end
32
-
33
20
  private
34
21
 
35
22
  def message_prefix
@@ -41,7 +28,9 @@ module Liquid
41
28
  end
42
29
 
43
30
  if line_number
44
- str << " (line #{line_number})"
31
+ str << " ("
32
+ str << template_name << " " if template_name
33
+ str << "line " << line_number.to_s << ")"
45
34
  end
46
35
 
47
36
  str << ": "
@@ -49,12 +38,19 @@ module Liquid
49
38
  end
50
39
  end
51
40
 
52
- class ArgumentError < Error; end
53
- class ContextError < Error; end
54
- class FileSystemError < Error; end
55
- class StandardError < Error; end
56
- class SyntaxError < Error; end
57
- class StackLevelError < Error; end
58
- class TaintedError < Error; end
59
- class MemoryError < Error; end
41
+ ArgumentError = Class.new(Error)
42
+ ContextError = Class.new(Error)
43
+ FileSystemError = Class.new(Error)
44
+ StandardError = Class.new(Error)
45
+ SyntaxError = Class.new(Error)
46
+ StackLevelError = Class.new(Error)
47
+ TaintedError = Class.new(Error)
48
+ MemoryError = Class.new(Error)
49
+ ZeroDivisionError = Class.new(Error)
50
+ FloatDomainError = Class.new(Error)
51
+ UndefinedVariable = Class.new(Error)
52
+ UndefinedDropMethod = Class.new(Error)
53
+ UndefinedFilter = Class.new(Error)
54
+ MethodOverrideError = Class.new(Error)
55
+ InternalError = Class.new(Error)
60
56
  end
@@ -1,11 +1,24 @@
1
1
  module Liquid
2
2
  class Expression
3
+ class MethodLiteral
4
+ attr_reader :method_name, :to_s
5
+
6
+ def initialize(method_name, to_s)
7
+ @method_name = method_name
8
+ @to_s = to_s
9
+ end
10
+
11
+ def to_liquid
12
+ to_s
13
+ end
14
+ end
15
+
3
16
  LITERALS = {
4
17
  nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
5
18
  'true'.freeze => true,
6
19
  'false'.freeze => false,
7
- 'blank'.freeze => :blank?,
8
- 'empty'.freeze => :empty?
20
+ 'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
21
+ 'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
9
22
  }
10
23
 
11
24
  def self.parse(markup)
@@ -28,6 +41,5 @@ module Liquid
28
41
  end
29
42
  end
30
43
  end
31
-
32
44
  end
33
45
  end
@@ -7,44 +7,50 @@ class String # :nodoc:
7
7
  end
8
8
  end
9
9
 
10
- class Array # :nodoc:
10
+ class Array # :nodoc:
11
11
  def to_liquid
12
12
  self
13
13
  end
14
14
  end
15
15
 
16
- class Hash # :nodoc:
16
+ class Hash # :nodoc:
17
17
  def to_liquid
18
18
  self
19
19
  end
20
20
  end
21
21
 
22
- class Numeric # :nodoc:
22
+ class Numeric # :nodoc:
23
23
  def to_liquid
24
24
  self
25
25
  end
26
26
  end
27
27
 
28
- class Time # :nodoc:
28
+ class Range # :nodoc:
29
29
  def to_liquid
30
30
  self
31
31
  end
32
32
  end
33
33
 
34
- class DateTime < Date # :nodoc:
34
+ class Time # :nodoc:
35
35
  def to_liquid
36
36
  self
37
37
  end
38
38
  end
39
39
 
40
- class Date # :nodoc:
40
+ class DateTime < Date # :nodoc:
41
+ def to_liquid
42
+ self
43
+ end
44
+ end
45
+
46
+ class Date # :nodoc:
41
47
  def to_liquid
42
48
  self
43
49
  end
44
50
  end
45
51
 
46
52
  class TrueClass
47
- def to_liquid # :nodoc:
53
+ def to_liquid # :nodoc:
48
54
  self
49
55
  end
50
56
  end
@@ -8,13 +8,13 @@ module Liquid
8
8
  #
9
9
  # Example:
10
10
  #
11
- # Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
12
- # liquid = Liquid::Template.parse(template)
11
+ # Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
12
+ # liquid = Liquid::Template.parse(template)
13
13
  #
14
14
  # This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
15
15
  class BlankFileSystem
16
16
  # Called by Liquid to retrieve a template file
17
- def read_template_file(template_path, context)
17
+ def read_template_file(_template_path)
18
18
  raise FileSystemError, "This liquid context does not allow includes."
19
19
  end
20
20
  end
@@ -26,10 +26,10 @@ module Liquid
26
26
  #
27
27
  # Example:
28
28
  #
29
- # file_system = Liquid::LocalFileSystem.new("/some/path")
29
+ # file_system = Liquid::LocalFileSystem.new("/some/path")
30
30
  #
31
- # file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
32
- # file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
31
+ # file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
32
+ # file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
33
33
  #
34
34
  # Optionally in the second argument you can specify a custom pattern for template filenames.
35
35
  # The Kernel::sprintf format specification is used.
@@ -37,9 +37,9 @@ module Liquid
37
37
  #
38
38
  # Example:
39
39
  #
40
- # file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
40
+ # file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
41
41
  #
42
- # file_system.full_path("index") # => "/some/path/index.html"
42
+ # file_system.full_path("index") # => "/some/path/index.html"
43
43
  #
44
44
  class LocalFileSystem
45
45
  attr_accessor :root
@@ -49,9 +49,9 @@ module Liquid
49
49
  @pattern = pattern
50
50
  end
51
51
 
52
- def read_template_file(template_path, context)
52
+ def read_template_file(template_path)
53
53
  full_path = full_path(template_path)
54
- raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
54
+ raise FileSystemError, "No such template '#{template_path}'" unless File.exist?(full_path)
55
55
 
56
56
  File.read(full_path)
57
57
  end
@@ -65,7 +65,7 @@ module Liquid
65
65
  File.join(root, @pattern % template_path)
66
66
  end
67
67
 
68
- raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /\A#{File.expand_path(root)}/
68
+ raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path).start_with?(File.expand_path(root))
69
69
 
70
70
  full_path
71
71
  end
@@ -0,0 +1,42 @@
1
+ module Liquid
2
+ class ForloopDrop < Drop
3
+ def initialize(name, length, parentloop)
4
+ @name = name
5
+ @length = length
6
+ @parentloop = parentloop
7
+ @index = 0
8
+ end
9
+
10
+ attr_reader :name, :length, :parentloop
11
+
12
+ def index
13
+ @index + 1
14
+ end
15
+
16
+ def index0
17
+ @index
18
+ end
19
+
20
+ def rindex
21
+ @length - @index
22
+ end
23
+
24
+ def rindex0
25
+ @length - @index - 1
26
+ end
27
+
28
+ def first
29
+ @index == 0
30
+ end
31
+
32
+ def last
33
+ @index == @length - 1
34
+ end
35
+
36
+ protected
37
+
38
+ def increment!
39
+ @index += 1
40
+ end
41
+ end
42
+ end