public
Rubygem
Fork of tobi/liquid
Description: Liquid markup language. Save, customer facing template language for flexible web apps.
Homepage: http://www.liquidmarkup.org
Clone URL: git://github.com/mhw/liquid.git
Search Repo:
Strip trailing whitespace.
mhw (author)
Wed Jul 09 00:34:01 -0700 2008
commit  eb609ee4aa76a4e510beb701bde9e9416e2d67eb
tree    9ad1eb6b3aa779c22ea19e1f9d188d92e79d8d4f
parent  6dd4f716f824b66680976f564cfc85615283fe9e
...
1
2
 
3
4
5
6
7
8
9
10
 
 
11
12
13
...
15
16
17
18
 
19
20
21
22
23
24
25
 
 
26
27
28
29
30
31
 
 
 
 
32
33
34
35
36
 
 
37
38
39
 
40
41
 
42
43
44
45
 
46
47
48
49
 
 
 
50
51
52
53
54
 
 
55
56
57
58
59
60
 
61
62
63
...
65
66
67
68
 
69
70
71
72
73
 
74
75
76
 
77
78
79
 
80
81
82
83
84
 
85
86
87
...
91
92
93
94
 
95
96
97
 
98
99
 
100
101
102
103
104
 
105
106
107
108
 
109
110
111
112
 
113
114
115
116
 
 
 
117
118
119
120
 
121
122
123
...
128
129
130
131
 
132
133
 
134
135
136
...
138
139
140
141
 
142
143
 
144
145
146
 
147
148
149
 
150
151
152
153
 
154
155
156
157
 
 
 
158
159
 
160
161
 
162
163
164
...
169
170
171
172
 
173
174
 
175
176
177
...
179
180
181
182
 
183
184
185
186
187
 
188
189
190
191
192
 
 
 
193
194
 
195
196
197
 
198
199
200
 
201
202
203
204
205
 
206
207
208
209
210
211
212
 
213
214
215
216
217
 
218
219
220
221
 
222
223
 
224
225
226
227
228
229
230
231
 
 
232
233
234
235
 
236
237
238
 
 
239
240
 
241
242
243
...
1
 
2
3
4
5
6
7
8
 
 
9
10
11
12
13
...
15
16
17
 
18
19
20
21
22
23
 
 
24
25
26
27
 
 
 
 
28
29
30
31
32
33
34
 
 
35
36
37
38
 
39
40
 
41
42
43
44
 
45
46
 
 
 
47
48
49
50
51
52
 
 
53
54
55
56
57
58
59
 
60
61
62
63
...
65
66
67
 
68
69
70
71
72
 
73
74
75
 
76
77
78
 
79
80
81
82
83
 
84
85
86
87
...
91
92
93
 
94
95
96
 
97
98
 
99
100
101
102
103
 
104
105
106
107
 
108
109
110
111
 
112
113
 
 
 
114
115
116
117
118
119
 
120
121
122
123
...
128
129
130
 
131
132
 
133
134
135
136
...
138
139
140
 
141
142
 
143
144
145
 
146
147
148
 
149
150
151
152
 
153
154
 
 
 
155
156
157
158
 
159
160
 
161
162
163
164
...
169
170
171
 
172
173
 
174
175
176
177
...
179
180
181
 
182
183
184
185
186
 
187
188
 
 
189
 
190
191
192
193
 
194
195
196
 
197
198
199
 
200
201
202
203
204
 
205
206
207
208
209
210
211
 
212
213
214
215
216
 
217
218
219
220
 
221
222
 
223
224
225
226
227
228
229
 
 
230
231
232
233
234
 
235
236
 
 
237
238
239
 
240
241
242
243
0
@@ -1,13 +1,13 @@
0
 module Liquid
0
-
0
+
0
   # Context keeps the variable stack and resolves variables, as well as keywords
0
   #
0
   # context['variable'] = 'testing'
0
   # context['variable'] #=> 'testing'
0
   # context['true'] #=> true
0
   # context['10.2232'] #=> 10.2232
0
- #
0
- # context.stack do
0
+ #
0
+ # context.stack do
0
   # context['bob'] = 'bobsen'
0
   # end
0
   #
0
@@ -15,49 +15,49 @@ module Liquid
0
   class Context
0
     attr_reader :scopes
0
     attr_reader :errors, :registers
0
-
0
+
0
     def initialize(assigns = {}, registers = {}, rethrow_errors = false)
0
       @scopes = [(assigns || {})]
0
       @registers = registers
0
       @errors = []
0
       @rethrow_errors = rethrow_errors
0
- end
0
-
0
+ end
0
+
0
     def strainer
0
       @strainer ||= Strainer.create(self)
0
- end
0
-
0
- # adds filters to this context.
0
- # this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
0
+ end
0
+
0
+ # adds filters to this context.
0
+ # this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
0
     # for that
0
     def add_filters(filters)
0
       filters = [filters].flatten.compact
0
-
0
- filters.each do |f|
0
+
0
+ filters.each do |f|
0
         raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
0
         strainer.extend(f)
0
- end
0
+ end
0
     end
0
-
0
+
0
     def handle_error(e)
0
       errors.push(e)
0
       raise if @rethrow_errors
0
-
0
+
0
       case e
0
- when SyntaxError
0
- "Liquid syntax error: #{e.message}"
0
- else
0
+ when SyntaxError
0
+ "Liquid syntax error: #{e.message}"
0
+ else
0
         "Liquid error: #{e.message}"
0
       end
0
     end
0
-
0
-
0
+
0
+
0
     def invoke(method, *args)
0
       if strainer.respond_to?(method)
0
         strainer.__send__(method, *args)
0
       else
0
         args.first
0
- end
0
+ end
0
     end
0
 
0
     # push new local scope on the stack. use <tt>Context#stack</tt> instead
0
@@ -65,23 +65,23 @@ module Liquid
0
       raise StackLevelError, "Nesting too deep" if @scopes.length > 100
0
       @scopes.unshift({})
0
     end
0
-
0
+
0
     # merge a hash of variables in the current local scope
0
     def merge(new_scopes)
0
       @scopes[0].merge!(new_scopes)
0
     end
0
-
0
+
0
     # pop from the stack. use <tt>Context#stack</tt> instead
0
     def pop
0
- raise ContextError if @scopes.size == 1
0
+ raise ContextError if @scopes.size == 1
0
       @scopes.shift
0
     end
0
-
0
+
0
     # pushes a new local scope on the stack, pops it at the end of the block
0
     #
0
     # Example:
0
     #
0
- # context.stack do
0
+ # context.stack do
0
     # context['var'] = 'hi'
0
     # end
0
     # context['var] #=> nil
0
@@ -91,33 +91,33 @@ module Liquid
0
       push
0
       begin
0
         result = yield
0
- ensure
0
+ ensure
0
         pop
0
       end
0
- result
0
+ result
0
     end
0
-
0
+
0
     # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
0
     def []=(key, value)
0
       @scopes[0][key] = value
0
     end
0
-
0
+
0
     def [](key)
0
       resolve(key)
0
     end
0
-
0
+
0
     def has_key?(key)
0
       resolve(key) != nil
0
     end
0
-
0
+
0
     private
0
-
0
- # Look up variable, either resolve directly after considering the name. We can directly handle
0
- # Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and
0
+
0
+ # Look up variable, either resolve directly after considering the name. We can directly handle
0
+ # Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and
0
     # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
0
     # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
0
     #
0
- # Example:
0
+ # Example:
0
     #
0
     # products == empty #=> products.empty?
0
     #
0
@@ -128,9 +128,9 @@ module Liquid
0
       when 'true'
0
         true
0
       when 'false'
0
- false
0
+ false
0
       when 'blank'
0
- :blank?
0
+ :blank?
0
       when 'empty'
0
         :empty?
0
       # Single quoted strings
0
@@ -138,27 +138,27 @@ module Liquid
0
         $1.to_s
0
       # Double quoted strings
0
       when /^"(.*)"$/
0
- $1.to_s
0
+ $1.to_s
0
       # Integer and floats
0
- when /^(\d+)$/
0
+ when /^(\d+)$/
0
         $1.to_i
0
       # Ranges
0
- when /^\((\S+)\.\.(\S+)\)$/
0
+ when /^\((\S+)\.\.(\S+)\)$/
0
         (resolve($1).to_i..resolve($2).to_i)
0
       # Floats
0
- when /^(\d[\d\.]+)$/
0
+ when /^(\d[\d\.]+)$/
0
         $1.to_f
0
       else
0
         variable(key)
0
- end
0
+ end
0
     end
0
-
0
- # fetches an object starting at the local scope and then moving up
0
- # the hierachy
0
+
0
+ # fetches an object starting at the local scope and then moving up
0
+ # the hierachy
0
     def find_variable(key)
0
- @scopes.each do |scope|
0
+ @scopes.each do |scope|
0
         if scope.has_key?(key)
0
- variable = scope[key]
0
+ variable = scope[key]
0
           variable = scope[key] = variable.call(self) if variable.is_a?(Proc)
0
           variable = variable.to_liquid
0
           variable.context = self if variable.respond_to?(:context=)
0
@@ -169,9 +169,9 @@ module Liquid
0
     end
0
 
0
     # resolves namespaced queries gracefully.
0
- #
0
+ #
0
     # Example
0
- #
0
+ #
0
     # @context['hash'] = {"name" => 'tobi'}
0
     # assert_equal 'tobi', @context['hash.name']
0
     # assert_equal 'tobi', @context['hash[name]']
0
@@ -179,65 +179,65 @@ module Liquid
0
     def variable(markup)
0
       parts = markup.scan(VariableParser)
0
       square_bracketed = /^\[(.*)\]$/
0
-
0
+
0
       first_part = parts.shift
0
       if first_part =~ square_bracketed
0
         first_part = resolve($1)
0
       end
0
-
0
+
0
       if object = find_variable(first_part)
0
-
0
- parts.each do |part|
0
 
0
- # If object is a hash we look for the presence of the key and if its available
0
+ parts.each do |part|
0
+
0
+ # If object is a hash we look for the presence of the key and if its available
0
           # we return it
0
-
0
+
0
           if part =~ square_bracketed
0
             part = resolve($1)
0
-
0
+
0
             object[pos] = object[part].call(self) if object[part].is_a?(Proc) and object.respond_to?(:[]=)
0
             object = object[part].to_liquid
0
-
0
+
0
           else
0
 
0
             # Hash
0
             if object.respond_to?(:has_key?) and object.has_key?(part)
0
-
0
+
0
               # if its a proc we will replace the entry in the hash table with the proc
0
               res = object[part]
0
               res = object[part] = res.call(self) if res.is_a?(Proc) and object.respond_to?(:[]=)
0
               object = res.to_liquid
0
 
0
             # Array
0
- elsif object.respond_to?(:fetch) and part =~ /^\d+$/
0
+ elsif object.respond_to?(:fetch) and part =~ /^\d+$/
0
               pos = part.to_i
0
 
0
               object[pos] = object[pos].call(self) if object[pos].is_a?(Proc) and object.respond_to?(:[]=)
0
               object = object[pos].to_liquid
0
-
0
+
0
             # Some special cases. If no key with the same name was found we interpret following calls
0
             # as commands and call them on the current object
0
             elsif object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
0
-
0
+
0
               object = object.send(part.intern).to_liquid
0
-
0
+
0
             # No key was present with the desired value and it wasn't one of the directly supported
0
             # keywords either. The only thing we got left is to return nil
0
             else
0
               return nil
0
             end
0
           end
0
-
0
- # If we are dealing with a drop here we have to
0
+
0
+ # If we are dealing with a drop here we have to
0
           object.context = self if object.respond_to?(:context=)
0
         end
0
       end
0
-
0
+
0
       object
0
- end
0
-
0
+ end
0
+
0
     private
0
-
0
+
0
     def execute_proc(proc)
0
       proc.call(self)
0
     end
...
9
10
11
12
 
13
14
15
...
52
53
54
55
 
56
57
58
 
59
60
61
 
62
63
64
 
65
66
67
68
 
 
69
70
71
 
 
72
73
74
 
75
76
77
 
78
79
 
80
81
82
83
 
84
85
86
87
88
89
 
90
91
92
...
97
98
99
100
 
101
102
 
103
104
 
105
106
107
108
109
110
 
111
112
113
114
115
116
 
 
 
117
118
119
120
121
 
122
123
 
124
125
126
 
 
127
128
129
130
131
 
132
133
134
135
 
136
137
138
139
140
141
 
142
143
 
144
145
 
146
147
148
149
150
151
 
 
152
153
154
155
156
157
 
 
158
159
160
161
 
162
163
164
 
165
166
167
...
172
173
174
175
 
176
177
178
179
180
181
 
 
182
183
184
185
186
187
188
189
 
 
190
191
 
192
193
194
195
196
 
197
198
199
...
203
204
205
206
 
207
208
209
210
211
 
 
212
213
214
215
216
217
 
218
219
 
220
221
222
...
224
225
226
227
 
228
229
 
230
231
232
233
234
 
235
236
237
 
238
239
 
240
241
242
243
244
245
246
 
247
248
249
250
251
 
252
253
254
255
256
257
 
258
259
 
260
261
262
 
 
263
264
265
 
266
267
268
269
 
270
271
272
...
279
280
281
282
 
283
284
285
286
 
287
288
289
290
 
291
292
 
293
294
295
...
297
298
299
300
 
301
302
303
...
307
308
309
310
 
311
312
 
313
314
315
...
317
318
319
320
 
321
322
323
 
324
325
 
326
327
328
 
329
330
 
331
332
333
 
334
335
 
336
337
338
 
339
340
341
342
343
 
344
345
346
347
348
 
349
350
 
351
352
353
...
357
358
359
360
 
361
362
363
 
364
365
 
366
367
368
 
369
370
371
372
373
374
 
375
376
377
378
379
380
 
381
382
383
 
384
385
386
 
 
387
388
389
390
 
391
392
393
394
395
396
 
 
397
398
399
400
401
402
403
 
404
405
406
 
407
408
409
 
 
410
411
 
412
413
414
415
416
417
418
419
 
...
9
10
11
 
12
13
14
15
...
52
53
54
 
55
56
57
 
58
59
60
 
61
62
63
 
64
65
66
 
 
67
68
69
 
 
70
71
72
73
 
74
75
76
 
77
78
 
79
80
81
82
 
83
84
85
86
87
88
 
89
90
91
92
...
97
98
99
 
100
101
 
102
103
 
104
105
106
107
108
109
 
110
111
112
113
 
 
 
114
115
116
117
118
119
120
 
121
122
 
123
124
 
 
125
126
127
128
129
130
 
131
132
133
134
 
135
136
137
138
139
140
 
141
142
 
143
144
 
145
146
147
148
149
 
 
150
151
152
153
154
155
 
 
156
157
158
159
160
 
161
162
163
 
164
165
166
167
...
172
173
174
 
175
176
177
178
179
 
 
180
181
182
183
184
185
186
187
 
 
188
189
190
 
191
192
193
194
195
 
196
197
198
199
...
203
204
205
 
206
207
208
209
 
 
210
211
212
213
214
215
216
 
217
218
 
219
220
221
222
...
224
225
226
 
227
228
 
229
230
231
232
233
 
234
235
236
 
237
238
 
239
240
241
242
243
244
245
 
246
247
248
249
250
 
251
252
253
254
255
256
 
257
258
 
259
260
 
 
261
262
263
264
 
265
266
267
268
 
269
270
271
272
...
279
280
281
 
282
283
284
285
 
286
287
288
289
 
290
291
 
292
293
294
295
...
297
298
299
 
300
301
302
303
...
307
308
309
 
310
311
 
312
313
314
315
...
317
318
319
 
320
321
322
 
323
324
 
325
326
327
 
328
329
 
330
331
332
 
333
334
 
335
336
337
 
338
339
340
341
342
 
343
344
345
346
347
 
348
349
 
350
351
352
353
...
357
358
359
 
360
361
362
 
363
364
 
365
366
367
 
368
369
370
371
372
373
 
374
375
376
377
378
379
 
380
381
382
 
383
384
385
 
386
387
388
389
 
 
390
391
392
393
394
395
 
396
397
398
399
 
400
401
402
 
403
404
405
 
406
407
 
 
408
409
410
 
411
412
413
414
415
416
417
 
418
419
0
@@ -9,7 +9,7 @@ class CentsDrop < Liquid::Drop
0
   def amount
0
     HundredCentes.new
0
   end
0
-
0
+
0
   def non_zero?
0
     true
0
   end
0
@@ -52,41 +52,41 @@ class ContextTest < Test::Unit::TestCase
0
   def test_variables
0
     @context['string'] = 'string'
0
     assert_equal 'string', @context['string']
0
-
0
+
0
     @context['num'] = 5
0
     assert_equal 5, @context['num']
0
-
0
+
0
     @context['time'] = Time.parse('2006-06-06 12:00:00')
0
     assert_equal Time.parse('2006-06-06 12:00:00'), @context['time']
0
-
0
+
0
     @context['date'] = Date.today
0
     assert_equal Date.today, @context['date']
0
-
0
+
0
     now = DateTime.now
0
     @context['datetime'] = now
0
- assert_equal now, @context['datetime']
0
-
0
+ assert_equal now, @context['datetime']
0
+
0
     @context['bool'] = true
0
- assert_equal true, @context['bool']
0
-
0
+ assert_equal true, @context['bool']
0
+
0
     @context['bool'] = false
0
     assert_equal false, @context['bool']
0
-
0
+
0
     @context['nil'] = nil
0
     assert_equal nil, @context['nil']
0
- assert_equal nil, @context['nil']
0
+ assert_equal nil, @context['nil']
0
   end
0
-
0
+
0
   def test_variables_not_existing
0
     assert_equal nil, @context['does_not_exist']
0
   end
0
-
0
+
0
   def test_scoping
0
     assert_nothing_raised do
0
       @context.push
0
       @context.pop
0
     end
0
-
0
+
0
     assert_raise(Liquid::ContextError) do
0
       @context.pop
0
     end
0
@@ -97,71 +97,71 @@ class ContextTest < Test::Unit::TestCase
0
       @context.pop
0
     end
0
   end
0
-
0
+
0
   def test_length_query
0
-
0
+
0
     @context['numbers'] = [1,2,3,4]
0
-
0
+
0
     assert_equal 4, @context['numbers.size']
0
 
0
     @context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4}
0
 
0
     assert_equal 4, @context['numbers.size']
0
-
0
+
0
     @context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4, 'size' => 1000}
0
 
0
     assert_equal 1000, @context['numbers.size']
0
-
0
- end
0
-
0
+
0
+ end
0
+
0
   def test_hyphenated_variable
0
 
0
     @context['oh-my'] = 'godz'
0
     assert_equal 'godz', @context['oh-my']
0
-
0
+
0
   end
0
-
0
+
0
   def test_add_filter
0
-
0
- filter = Module.new do
0
+
0
+ filter = Module.new do
0
       def hi(output)
0
         output + ' hi!'
0
       end
0
     end
0
-
0
+
0
     context = Context.new(@template)
0
     context.add_filters(filter)
0
     assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
0
-
0
+
0
     context = Context.new(@template)
0
     assert_equal 'hi?', context.invoke(:hi, 'hi?')
0
 
0
     context.add_filters(filter)
0
     assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
0
-
0
+
0
   end
0
-
0
+
0
   def test_override_global_filter
0
- global = Module.new do
0
+ global = Module.new do
0
       def notice(output)
0
         "Global #{output}"
0
       end
0
     end
0
-
0
- local = Module.new do
0
+
0
+ local = Module.new do
0
       def notice(output)
0
         "Local #{output}"
0
       end
0
     end
0
-
0
- Template.register_filter(global)
0
+
0
+ Template.register_filter(global)
0
     assert_equal 'Global test', Template.parse("{{'test' | notice }}").render
0
     assert_equal 'Local test', Template.parse("{{'test' | notice }}").render({}, :filters => [local])
0
   end
0
-
0
+
0
   def test_only_intended_filters_make_it_there
0
 
0
- filter = Module.new do
0
+ filter = Module.new do
0
       def hi(output)
0
         output + ' hi!'
0
       end
0
@@ -172,28 +172,28 @@ class ContextTest < Test::Unit::TestCase
0
     context.add_filters(filter)
0
     assert_equal (methods + ['hi']).sort, context.strainer.methods.sort
0
   end
0
-
0
+
0
   def test_add_item_in_outer_scope
0
     @context['test'] = 'test'
0
     @context.push
0
     assert_equal 'test', @context['test']
0
- @context.pop
0
- assert_equal 'test', @context['test']
0
+ @context.pop
0
+ assert_equal 'test', @context['test']
0
   end
0
 
0
   def test_add_item_in_inner_scope
0
     @context.push
0
     @context['test'] = 'test'
0
     assert_equal 'test', @context['test']
0
- @context.pop
0
- assert_equal nil, @context['test']
0
+ @context.pop
0
+ assert_equal nil, @context['test']
0
   end
0
-
0
+
0
   def test_hierachical_data
0
     @context['hash'] = {"name" => 'tobi'}
0
     assert_equal 'tobi', @context['hash.name']
0
   end
0
-
0
+
0
   def test_keywords
0
     assert_equal true, @context['true']
0
     assert_equal false, @context['false']
0
@@ -203,20 +203,20 @@ class ContextTest < Test::Unit::TestCase
0
     assert_equal 100, @context['100']
0
     assert_equal 100.00, @context['100.00']
0
   end
0
-
0
+
0
   def test_strings
0
     assert_equal "hello!", @context['"hello!"']
0
     assert_equal "hello!", @context["'hello!'"]
0
- end
0
-
0
+ end
0
+
0
   def test_merge
0
     @context.merge({ "test" => "test" })
0
     assert_equal 'test', @context['test']
0
     @context.merge({ "test" => "newvalue", "foo" => "bar" })
0
     assert_equal 'newvalue', @context['test']
0
- assert_equal 'bar', @context['foo']
0
+ assert_equal 'bar', @context['foo']
0
   end
0
-
0
+
0
   def test_array_notation
0
     @context['test'] = [1,2,3,4,5]
0
 
0
@@ -224,49 +224,49 @@ class ContextTest < Test::Unit::TestCase
0
     assert_equal 2, @context['test[1]']
0
     assert_equal 3, @context['test[2]']
0
     assert_equal 4, @context['test[3]']
0
- assert_equal 5, @context['test[4]']
0
+ assert_equal 5, @context['test[4]']
0
   end
0
-
0
+
0
   def test_recoursive_array_notation
0
     @context['test'] = {'test' => [1,2,3,4,5]}
0
 
0
     assert_equal 1, @context['test.test[0]']
0
-
0
+
0
     @context['test'] = [{'test' => 'worked'}]
0
 
0
- assert_equal 'worked', @context['test[0].test']
0
+ assert_equal 'worked', @context['test[0].test']
0
   end
0
-
0
+
0
   def test_hash_to_array_transition
0
     @context['colors'] = {
0
      'Blue' => ['003366','336699', '6699CC', '99CCFF'],
0
      'Green' => ['003300','336633', '669966', '99CC99'],
0
      'Yellow' => ['CC9900','FFCC00', 'FFFF99', 'FFFFCC'],
0
      'Red' => ['660000','993333', 'CC6666', 'FF9999']