liquid 2.1.3 → 2.2.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.
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