liquid 2.1.3 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.dirname(__FILE__), 'test') unless $:.include? File.join(File.dirname(__FILE__), 'test')
3
+
2
4
  require 'rubygems'
3
5
  require 'rake'
4
6
  require 'rake/testtask'
@@ -7,9 +9,8 @@ require 'rake/gempackagetask'
7
9
  task :default => 'test'
8
10
 
9
11
  Rake::TestTask.new(:test) do |t|
10
- t.libs << "lib"
11
- t.libs << "test"
12
- t.pattern = 'test/*_test.rb'
12
+ t.libs << '.' << 'lib' << 'test'
13
+ t.pattern = 'test/lib/**/*_test.rb'
13
14
  t.verbose = false
14
15
  end
15
16
 
@@ -25,20 +26,18 @@ end
25
26
 
26
27
  namespace :profile do
27
28
 
28
-
29
29
  task :default => [:run]
30
-
30
+
31
31
  desc "Run the liquid profile/perforamce coverage"
32
32
  task :run do
33
-
33
+
34
34
  ruby "performance/shopify.rb"
35
-
35
+
36
36
  end
37
-
38
- desc "Run KCacheGrind"
37
+
38
+ desc "Run KCacheGrind"
39
39
  task :grind => :run do
40
40
  system "kcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
41
41
  end
42
+
42
43
  end
43
-
44
-
@@ -38,7 +38,7 @@ module Liquid
38
38
  StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|,\:,\,]+/
39
39
  FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
40
40
  OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
41
- SpacelessFilter = /#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
41
+ SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
42
42
  Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/
43
43
  TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
44
44
  AnyStartingTag = /\{\{|\{\%/
@@ -28,8 +28,9 @@ module Liquid
28
28
  @strainer ||= Strainer.create(self)
29
29
  end
30
30
 
31
- # adds filters to this context.
32
- # this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
31
+ # Adds filters to this context.
32
+ #
33
+ # Note that this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
33
34
  # for that
34
35
  def add_filters(filters)
35
36
  filters = [filters].flatten.compact
@@ -52,7 +53,6 @@ module Liquid
52
53
  end
53
54
  end
54
55
 
55
-
56
56
  def invoke(method, *args)
57
57
  if strainer.respond_to?(method)
58
58
  strainer.__send__(method, *args)
@@ -61,43 +61,44 @@ module Liquid
61
61
  end
62
62
  end
63
63
 
64
- # push new local scope on the stack. use <tt>Context#stack</tt> instead
64
+ # Push new local scope on the stack. use <tt>Context#stack</tt> instead
65
65
  def push(new_scope={})
66
66
  raise StackLevelError, "Nesting too deep" if @scopes.length > 100
67
67
  @scopes.unshift(new_scope)
68
68
  end
69
69
 
70
- # merge a hash of variables in the current local scope
70
+ # Merge a hash of variables in the current local scope
71
71
  def merge(new_scopes)
72
72
  @scopes[0].merge!(new_scopes)
73
73
  end
74
74
 
75
- # pop from the stack. use <tt>Context#stack</tt> instead
75
+ # Pop from the stack. use <tt>Context#stack</tt> instead
76
76
  def pop
77
77
  raise ContextError if @scopes.size == 1
78
78
  @scopes.shift
79
79
  end
80
80
 
81
- # pushes a new local scope on the stack, pops it at the end of the block
81
+ # Pushes a new local scope on the stack, pops it at the end of the block
82
82
  #
83
83
  # Example:
84
- #
85
84
  # context.stack do
86
85
  # context['var'] = 'hi'
87
86
  # end
88
- # context['var] #=> nil
89
87
  #
88
+ # context['var] #=> nil
90
89
  def stack(new_scope={},&block)
91
90
  result = nil
92
91
  push(new_scope)
92
+
93
93
  begin
94
94
  result = yield
95
95
  ensure
96
96
  pop
97
97
  end
98
+
98
99
  result
99
100
  end
100
-
101
+
101
102
  def clear_instance_assigns
102
103
  @scopes[0] = {}
103
104
  end
@@ -116,139 +117,133 @@ module Liquid
116
117
  end
117
118
 
118
119
  private
119
-
120
- # Look up variable, either resolve directly after considering the name. We can directly handle
121
- # Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and
122
- # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
123
- # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
124
- #
125
- # Example:
126
- #
127
- # products == empty #=> products.empty?
128
- #
129
- def resolve(key)
130
- case key
131
- when nil, 'nil', 'null', ''
132
- nil
133
- when 'true'
134
- true
135
- when 'false'
136
- false
137
- when 'blank'
138
- :blank?
139
- when 'empty'
140
- :empty?
141
- # Single quoted strings
142
- when /^'(.*)'$/
143
- $1.to_s
144
- # Double quoted strings
145
- when /^"(.*)"$/
146
- $1.to_s
147
- # Integer and floats
148
- when /^(\d+)$/
149
- $1.to_i
150
- # Ranges
151
- when /^\((\S+)\.\.(\S+)\)$/
152
- (resolve($1).to_i..resolve($2).to_i)
153
- # Floats
154
- when /^(\d[\d\.]+)$/
155
- $1.to_f
156
- else
157
- variable(key)
120
+ # Look up variable, either resolve directly after considering the name. We can directly handle
121
+ # Strings, digits, floats and booleans (true,false).
122
+ # If no match is made we lookup the variable in the current scope and
123
+ # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
124
+ # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
125
+ #
126
+ # Example:
127
+ # products == empty #=> products.empty?
128
+ def resolve(key)
129
+ case key
130
+ when nil, 'nil', 'null', ''
131
+ nil
132
+ when 'true'
133
+ true
134
+ when 'false'
135
+ false
136
+ when 'blank'
137
+ :blank?
138
+ when 'empty' # Single quoted strings
139
+ :empty?
140
+ when /^'(.*)'$/ # Double quoted strings
141
+ $1.to_s
142
+ when /^"(.*)"$/ # Integer and floats
143
+ $1.to_s
144
+ when /^(\d+)$/ # Ranges
145
+ $1.to_i
146
+ when /^\((\S+)\.\.(\S+)\)$/ # Floats
147
+ (resolve($1).to_i..resolve($2).to_i)
148
+ when /^(\d[\d\.]+)$/
149
+ $1.to_f
150
+ else
151
+ variable(key)
152
+ end
158
153
  end
159
- end
160
154
 
161
- # fetches an object starting at the local scope and then moving up
162
- # the hierachy
163
- def find_variable(key)
164
- scope = @scopes.find { |s| s.has_key?(key) }
165
- if scope.nil?
166
- @environments.each do |e|
167
- if variable = lookup_and_evaluate(e, key)
168
- scope = e
169
- break
155
+ # Fetches an object starting at the local scope and then moving up the hierachy
156
+ def find_variable(key)
157
+ scope = @scopes.find { |s| s.has_key?(key) }
158
+
159
+ if scope.nil?
160
+ @environments.each do |e|
161
+ if variable = lookup_and_evaluate(e, key)
162
+ scope = e
163
+ break
164
+ end
170
165
  end
171
166
  end
172
- end
173
- scope ||= @environments.last || @scopes.last
174
- variable ||= lookup_and_evaluate(scope, key)
175
-
176
- variable = variable.to_liquid
177
- variable.context = self if variable.respond_to?(:context=)
178
- return variable
179
- end
180
167
 
181
- # resolves namespaced queries gracefully.
182
- #
183
- # Example
184
- #
185
- # @context['hash'] = {"name" => 'tobi'}
186
- # assert_equal 'tobi', @context['hash.name']
187
- # assert_equal 'tobi', @context['hash["name"]']
188
- #
189
- def variable(markup)
190
- parts = markup.scan(VariableParser)
191
- square_bracketed = /^\[(.*)\]$/
168
+ scope ||= @environments.last || @scopes.last
169
+ variable ||= lookup_and_evaluate(scope, key)
170
+
171
+ variable = variable.to_liquid
172
+ variable.context = self if variable.respond_to?(:context=)
192
173
 
193
- first_part = parts.shift
194
- if first_part =~ square_bracketed
195
- first_part = resolve($1)
174
+ return variable
196
175
  end
197
176
 
198
- if object = find_variable(first_part)
177
+ # Resolves namespaced queries gracefully.
178
+ #
179
+ # Example
180
+ # @context['hash'] = {"name" => 'tobi'}
181
+ # assert_equal 'tobi', @context['hash.name']
182
+ # assert_equal 'tobi', @context['hash["name"]']
183
+ def variable(markup)
184
+ parts = markup.scan(VariableParser)
185
+ square_bracketed = /^\[(.*)\]$/
199
186
 
200
- parts.each do |part|
201
- part = resolve($1) if part_resolved = (part =~ square_bracketed)
187
+ first_part = parts.shift
202
188
 
203
- # If object is a hash- or array-like object we look for the
204
- # presence of the key and if its available we return it
205
- if object.respond_to?(:[]) and
206
- ((object.respond_to?(:has_key?) and object.has_key?(part)) or
207
- (object.respond_to?(:fetch) and part.is_a?(Integer)))
189
+ if first_part =~ square_bracketed
190
+ first_part = resolve($1)
191
+ end
208
192
 
209
- # if its a proc we will replace the entry with the proc
210
- res = lookup_and_evaluate(object, part)
211
- object = res.to_liquid
193
+ if object = find_variable(first_part)
212
194
 
213
- # Some special cases. If the part wasn't in square brackets and
214
- # no key with the same name was found we interpret following calls
215
- # as commands and call them on the current object
216
- elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
195
+ parts.each do |part|
196
+ part = resolve($1) if part_resolved = (part =~ square_bracketed)
217
197
 
218
- object = object.send(part.intern).to_liquid
198
+ # If object is a hash- or array-like object we look for the
199
+ # presence of the key and if its available we return it
200
+ if object.respond_to?(:[]) and
201
+ ((object.respond_to?(:has_key?) and object.has_key?(part)) or
202
+ (object.respond_to?(:fetch) and part.is_a?(Integer)))
219
203
 
220
- # No key was present with the desired value and it wasn't one of the directly supported
221
- # keywords either. The only thing we got left is to return nil
222
- else
223
- return nil
224
- end
204
+ # if its a proc we will replace the entry with the proc
205
+ res = lookup_and_evaluate(object, part)
206
+ object = res.to_liquid
225
207
 
226
- # If we are dealing with a drop here we have to
227
- object.context = self if object.respond_to?(:context=)
208
+ # Some special cases. If the part wasn't in square brackets and
209
+ # no key with the same name was found we interpret following calls
210
+ # as commands and call them on the current object
211
+ elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
212
+
213
+ object = object.send(part.intern).to_liquid
214
+
215
+ # No key was present with the desired value and it wasn't one of the directly supported
216
+ # keywords either. The only thing we got left is to return nil
217
+ else
218
+ return nil
219
+ end
220
+
221
+ # If we are dealing with a drop here we have to
222
+ object.context = self if object.respond_to?(:context=)
223
+ end
228
224
  end
229
- end
230
225
 
231
- object
232
- end
233
-
234
- def lookup_and_evaluate(obj, key)
235
- if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
236
- obj[key] = value.call(self)
237
- else
238
- value
239
- end
240
- end
241
-
242
- def squash_instance_assigns_with_environments
243
- @scopes.last.each_key do |k|
244
- @environments.each do |env|
245
- if env.has_key?(k)
246
- scopes.last[k] = lookup_and_evaluate(env, k)
247
- break
226
+ object
227
+ end # variable
228
+
229
+ def lookup_and_evaluate(obj, key)
230
+ if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
231
+ obj[key] = (value.arity == 0) ? value.call : value.call(self)
232
+ else
233
+ value
234
+ end
235
+ end # lookup_and_evaluate
236
+
237
+ def squash_instance_assigns_with_environments
238
+ @scopes.last.each_key do |k|
239
+ @environments.each do |env|
240
+ if env.has_key?(k)
241
+ scopes.last[k] = lookup_and_evaluate(env, k)
242
+ break
243
+ end
248
244
  end
249
245
  end
250
- end
251
- end
252
-
253
- end
254
- end
246
+ end # squash_instance_assigns_with_environments
247
+ end # Context
248
+
249
+ end # Liquid
@@ -1,36 +1,40 @@
1
1
  require 'cgi'
2
2
 
3
3
  module Liquid
4
-
4
+
5
5
  module StandardFilters
6
-
6
+
7
7
  # Return the size of an array or of an string
8
8
  def size(input)
9
-
9
+
10
10
  input.respond_to?(:size) ? input.size : 0
11
- end
12
-
11
+ end
12
+
13
13
  # convert a input string to DOWNCASE
14
14
  def downcase(input)
15
15
  input.to_s.downcase
16
- end
16
+ end
17
17
 
18
18
  # convert a input string to UPCASE
19
19
  def upcase(input)
20
20
  input.to_s.upcase
21
21
  end
22
-
22
+
23
23
  # capitalize words in the input centence
24
24
  def capitalize(input)
25
25
  input.to_s.capitalize
26
26
  end
27
-
27
+
28
28
  def escape(input)
29
29
  CGI.escapeHTML(input) rescue input
30
30
  end
31
-
31
+
32
+ def escape_once(input)
33
+ ActionView::Helpers::TagHelper.escape_once(input) rescue input
34
+ end
35
+
32
36
  alias_method :h, :escape
33
-
37
+
34
38
  # Truncate a string down to x characters
35
39
  def truncate(input, length = 50, truncate_string = "...")
36
40
  if input.nil? then return end
@@ -44,19 +48,19 @@ module Liquid
44
48
  wordlist = input.to_s.split
45
49
  l = words.to_i - 1
46
50
  l = 0 if l < 0
47
- wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
51
+ wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
48
52
  end
49
-
53
+
50
54
  def strip_html(input)
51
55
  input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<.*?>/, '')
52
- end
53
-
56
+ end
57
+
54
58
  # Remove all newlines from the string
55
- def strip_newlines(input)
56
- input.to_s.gsub(/\n/, '')
59
+ def strip_newlines(input)
60
+ input.to_s.gsub(/\n/, '')
57
61
  end
58
-
59
-
62
+
63
+
60
64
  # Join elements of the array with certain character between them
61
65
  def join(input, glue = ' ')
62
66
  [input].flatten.join(glue)
@@ -73,8 +77,8 @@ module Liquid
73
77
  elsif ary.first.respond_to?(property)
74
78
  ary.sort {|a,b| a.send(property) <=> b.send(property) }
75
79
  end
76
- end
77
-
80
+ end
81
+
78
82
  # map/collect on a given property
79
83
  def map(input, property)
80
84
  ary = [input].flatten
@@ -84,42 +88,42 @@ module Liquid
84
88
  ary.map {|e| e.send(property) }
85
89
  end
86
90
  end
87
-
91
+
88
92
  # Replace occurrences of a string with another
89
93
  def replace(input, string, replacement = '')
90
94
  input.to_s.gsub(string, replacement)
91
95
  end
92
-
96
+
93
97
  # Replace the first occurrences of a string with another
94
98
  def replace_first(input, string, replacement = '')
95
99
  input.to_s.sub(string, replacement)
96
- end
97
-
100
+ end
101
+
98
102
  # remove a substring
99
103
  def remove(input, string)
100
- input.to_s.gsub(string, '')
104
+ input.to_s.gsub(string, '')
101
105
  end
102
-
106
+
103
107
  # remove the first occurrences of a substring
104
108
  def remove_first(input, string)
105
- input.to_s.sub(string, '')
106
- end
107
-
109
+ input.to_s.sub(string, '')
110
+ end
111
+
108
112
  # add one string to another
109
113
  def append(input, string)
110
114
  input.to_s + string.to_s
111
115
  end
112
-
116
+
113
117
  # prepend a string to another
114
118
  def prepend(input, string)
115
119
  string.to_s + input.to_s
116
120
  end
117
-
121
+
118
122
  # Add <br /> tags in front of all newlines in input string
119
- def newline_to_br(input)
120
- input.to_s.gsub(/\n/, "<br />\n")
123
+ def newline_to_br(input)
124
+ input.to_s.gsub(/\n/, "<br />\n")
121
125
  end
122
-
126
+
123
127
  # Reformat a date
124
128
  #
125
129
  # %a - The abbreviated weekday name (``Sun'')
@@ -149,74 +153,74 @@ module Liquid
149
153
  # %Z - Time zone name
150
154
  # %% - Literal ``%'' character
151
155
  def date(input, format)
152
-
156
+
153
157
  if format.to_s.empty?
154
158
  return input.to_s
155
159
  end
156
-
160
+
157
161
  date = input.is_a?(String) ? Time.parse(input) : input
158
-
162
+
159
163
  if date.respond_to?(:strftime)
160
164
  date.strftime(format.to_s)
161
165
  else
162
166
  input
163
167
  end
164
- rescue => e
168
+ rescue => e
165
169
  input
166
170
  end
167
-
168
- # Get the first element of the passed in array
169
- #
171
+
172
+ # Get the first element of the passed in array
173
+ #
170
174
  # Example:
171
175
  # {{ product.images | first | to_img }}
172
- #
176
+ #
173
177
  def first(array)
174
178
  array.first if array.respond_to?(:first)
175
179
  end
176
180
 
177
- # Get the last element of the passed in array
178
- #
181
+ # Get the last element of the passed in array
182
+ #
179
183
  # Example:
180
184
  # {{ product.images | last | to_img }}
181
- #
185
+ #
182
186
  def last(array)
183
187
  array.last if array.respond_to?(:last)
184
188
  end
185
-
189
+
186
190
  # addition
187
191
  def plus(input, operand)
188
192
  to_number(input) + to_number(operand)
189
193
  end
190
-
194
+
191
195
  # subtraction
192
196
  def minus(input, operand)
193
197
  to_number(input) - to_number(operand)
194
198
  end
195
-
199
+
196
200
  # multiplication
197
201
  def times(input, operand)
198
202
  to_number(input) * to_number(operand)
199
203
  end
200
-
204
+
201
205
  # division
202
206
  def divided_by(input, operand)
203
207
  to_number(input) / to_number(operand)
204
208
  end
205
-
209
+
206
210
  private
207
-
208
- def to_number(obj)
209
- case obj
210
- when Numeric
211
- obj
212
- when String
213
- (obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
214
- else
215
- 0
211
+
212
+ def to_number(obj)
213
+ case obj
214
+ when Numeric
215
+ obj
216
+ when String
217
+ (obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
218
+ else
219
+ 0
220
+ end
216
221
  end
217
- end
218
-
222
+
219
223
  end
220
-
224
+
221
225
  Template.register_filter(StandardFilters)
222
226
  end
@@ -16,6 +16,9 @@ module Liquid
16
16
  INTERNAL_METHOD = /^__/
17
17
  @@required_methods = Set.new([:__id__, :__send__, :respond_to?, :extend, :methods, :class, :object_id])
18
18
 
19
+ # Ruby 1.9.2 introduces Object#respond_to_missing?, which is invoked by Object#respond_to?
20
+ @@required_methods << :respond_to_missing? if Object.respond_to? :respond_to_missing?
21
+
19
22
  @@filters = {}
20
23
 
21
24
  def initialize(context)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquid
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 7
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
- - 1
9
- - 3
10
- version: 2.1.3
8
+ - 2
9
+ - 0
10
+ version: 2.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tobias Luetke
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-05 00:00:00 -04:00
18
+ date: 2010-08-22 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies: []
21
21