aslakjo-comatose 2.0.5.2

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 (93) hide show
  1. data/CHANGELOG +195 -0
  2. data/INSTALL +20 -0
  3. data/LICENSE +20 -0
  4. data/MANIFEST +91 -0
  5. data/README.markdown +159 -0
  6. data/Rakefile +176 -0
  7. data/SPECS +61 -0
  8. data/about.yml +7 -0
  9. data/bin/comatose +112 -0
  10. data/comatose.gemspec +113 -0
  11. data/generators/comatose_migration/USAGE +15 -0
  12. data/generators/comatose_migration/comatose_migration_generator.rb +74 -0
  13. data/generators/comatose_migration/templates/migration.rb +35 -0
  14. data/generators/comatose_migration/templates/v4_upgrade.rb +15 -0
  15. data/generators/comatose_migration/templates/v6_upgrade.rb +23 -0
  16. data/generators/comatose_migration/templates/v7_upgrade.rb +22 -0
  17. data/init.rb +2 -0
  18. data/install.rb +18 -0
  19. data/lib/acts_as_versioned.rb +543 -0
  20. data/lib/comatose/comatose_drop.rb +79 -0
  21. data/lib/comatose/configuration.rb +69 -0
  22. data/lib/comatose/page_wrapper.rb +119 -0
  23. data/lib/comatose/processing_context.rb +69 -0
  24. data/lib/comatose/tasks/admin.rb +60 -0
  25. data/lib/comatose/tasks/data.rb +82 -0
  26. data/lib/comatose/tasks/setup.rb +52 -0
  27. data/lib/comatose/version.rb +4 -0
  28. data/lib/comatose.rb +33 -0
  29. data/lib/comatose_admin_controller.rb +395 -0
  30. data/lib/comatose_admin_helper.rb +37 -0
  31. data/lib/comatose_controller.rb +138 -0
  32. data/lib/comatose_helper.rb +3 -0
  33. data/lib/comatose_page.rb +141 -0
  34. data/lib/liquid/block.rb +96 -0
  35. data/lib/liquid/context.rb +190 -0
  36. data/lib/liquid/document.rb +17 -0
  37. data/lib/liquid/drop.rb +48 -0
  38. data/lib/liquid/errors.rb +7 -0
  39. data/lib/liquid/extensions.rb +53 -0
  40. data/lib/liquid/file_system.rb +62 -0
  41. data/lib/liquid/htmltags.rb +64 -0
  42. data/lib/liquid/standardfilters.rb +111 -0
  43. data/lib/liquid/standardtags.rb +399 -0
  44. data/lib/liquid/strainer.rb +42 -0
  45. data/lib/liquid/tag.rb +25 -0
  46. data/lib/liquid/template.rb +88 -0
  47. data/lib/liquid/variable.rb +39 -0
  48. data/lib/liquid.rb +52 -0
  49. data/lib/redcloth.rb +1129 -0
  50. data/lib/support/class_options.rb +36 -0
  51. data/lib/support/inline_rendering.rb +48 -0
  52. data/lib/support/route_mapper.rb +50 -0
  53. data/lib/text_filters/markdown.rb +14 -0
  54. data/lib/text_filters/markdown_smartypants.rb +15 -0
  55. data/lib/text_filters/none.rb +8 -0
  56. data/lib/text_filters/rdoc.rb +13 -0
  57. data/lib/text_filters/simple.rb +8 -0
  58. data/lib/text_filters/textile.rb +15 -0
  59. data/lib/text_filters.rb +140 -0
  60. data/rails/init.rb +3 -0
  61. data/resources/layouts/comatose_admin_template.html.erb +28 -0
  62. data/resources/public/images/collapsed.gif +0 -0
  63. data/resources/public/images/expanded.gif +0 -0
  64. data/resources/public/images/no-children.gif +0 -0
  65. data/resources/public/images/page.gif +0 -0
  66. data/resources/public/images/spinner.gif +0 -0
  67. data/resources/public/images/title-hover-bg.gif +0 -0
  68. data/resources/public/javascripts/comatose_admin.js +401 -0
  69. data/resources/public/stylesheets/comatose_admin.css +404 -0
  70. data/tasks/comatose.rake +9 -0
  71. data/test/behaviors.rb +106 -0
  72. data/test/fixtures/comatose_pages.yml +96 -0
  73. data/test/functional/comatose_admin_controller_test.rb +114 -0
  74. data/test/functional/comatose_controller_test.rb +44 -0
  75. data/test/javascripts/test.html +26 -0
  76. data/test/javascripts/test_runner.js +307 -0
  77. data/test/test_helper.rb +55 -0
  78. data/test/unit/class_options_test.rb +52 -0
  79. data/test/unit/comatose_page_test.rb +136 -0
  80. data/test/unit/processing_context_test.rb +108 -0
  81. data/test/unit/text_filters_test.rb +52 -0
  82. data/views/comatose_admin/_form.html.erb +96 -0
  83. data/views/comatose_admin/_page_list_item.html.erb +60 -0
  84. data/views/comatose_admin/delete.html.erb +18 -0
  85. data/views/comatose_admin/edit.html.erb +5 -0
  86. data/views/comatose_admin/index.html.erb +29 -0
  87. data/views/comatose_admin/new.html.erb +5 -0
  88. data/views/comatose_admin/reorder.html.erb +30 -0
  89. data/views/comatose_admin/versions.html.erb +40 -0
  90. data/views/layouts/comatose_admin.html.erb +837 -0
  91. data/views/layouts/comatose_admin_customize.html.erb +28 -0
  92. data/views/layouts/comatose_content.html.erb +17 -0
  93. metadata +148 -0
@@ -0,0 +1,111 @@
1
+ module Liquid
2
+
3
+ module StandardFilters
4
+
5
+ # Return the size of an array or of an string
6
+ def size(input)
7
+
8
+ input.respond_to?(:size) ? input.size : 0
9
+ end
10
+
11
+ # convert a input string to DOWNCASE
12
+ def downcase(input)
13
+ input.downcase rescue input
14
+ end
15
+
16
+ # convert a input string to UPCASE
17
+ def upcase(input)
18
+ input.upcase rescue input
19
+ end
20
+
21
+ # Truncate a string down to x characters
22
+ def truncate(input, characters = 100)
23
+ if input.to_s.size > characters.to_i
24
+ input.to_s[0..characters.to_i] + '…'
25
+ else
26
+ input
27
+ end
28
+ end
29
+
30
+ # Truncate string down to x words
31
+ def truncatewords(input, words = 15)
32
+ wordlist = [input.to_s.split].flatten
33
+ if wordlist.size > words.to_i
34
+ wordlist[0..words.to_i].join(' ') + '…'
35
+ else
36
+ input
37
+ end
38
+ end
39
+
40
+ def strip_html(input)
41
+ input.to_s.gsub(/<.*?>/, '')
42
+ end
43
+
44
+ # Join elements of the array with certain character between them
45
+ def join(input, glue = ' ')
46
+ [input].flatten.join(glue)
47
+ end
48
+
49
+ # Sort elements of the array
50
+ def sort(input)
51
+ [input].flatten.sort
52
+ end
53
+
54
+ # Reformat a date
55
+ #
56
+ # %a - The abbreviated weekday name (``Sun'')
57
+ # %A - The full weekday name (``Sunday'')
58
+ # %b - The abbreviated month name (``Jan'')
59
+ # %B - The full month name (``January'')
60
+ # %c - The preferred local date and time representation
61
+ # %d - Day of the month (01..31)
62
+ # %H - Hour of the day, 24-hour clock (00..23)
63
+ # %I - Hour of the day, 12-hour clock (01..12)
64
+ # %j - Day of the year (001..366)
65
+ # %m - Month of the year (01..12)
66
+ # %M - Minute of the hour (00..59)
67
+ # %p - Meridian indicator (``AM'' or ``PM'')
68
+ # %S - Second of the minute (00..60)
69
+ # %U - Week number of the current year,
70
+ # starting with the first Sunday as the first
71
+ # day of the first week (00..53)
72
+ # %W - Week number of the current year,
73
+ # starting with the first Monday as the first
74
+ # day of the first week (00..53)
75
+ # %w - Day of the week (Sunday is 0, 0..6)
76
+ # %x - Preferred representation for the date alone, no time
77
+ # %X - Preferred representation for the time alone, no date
78
+ # %y - Year without a century (00..99)
79
+ # %Y - Year with century
80
+ # %Z - Time zone name
81
+ # %% - Literal ``%'' character
82
+ def date(input, format)
83
+ date = input
84
+ date = Time.parse(input) if input.is_a?(String)
85
+ date.strftime(format)
86
+ rescue => e
87
+ input
88
+ end
89
+
90
+ # Get the first element of the passed in array
91
+ #
92
+ # Example:
93
+ # {{ product.images | first | to_img }}
94
+ #
95
+ def first(array)
96
+ array.first if array.respond_to?(:first)
97
+ end
98
+
99
+ # Get the last element of the passed in array
100
+ #
101
+ # Example:
102
+ # {{ product.images | last | to_img }}
103
+ #
104
+ def last(array)
105
+ array.last if array.respond_to?(:last)
106
+ end
107
+
108
+ end
109
+
110
+ Template.register_filter(StandardFilters)
111
+ end
@@ -0,0 +1,399 @@
1
+ module Liquid
2
+
3
+ class Assign < Tag
4
+ Syntax = /(\w+)\s*=\s*(#{AllowedVariableCharacters}+)/
5
+
6
+ def initialize(markup, tokens)
7
+ if markup =~ Syntax
8
+ @to = $1
9
+ @from = $2
10
+ else
11
+ raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
12
+ end
13
+ end
14
+
15
+ def render(context)
16
+ context[@to] = context[@from]
17
+ ''
18
+ end
19
+
20
+ end
21
+
22
+ class Capture < Block
23
+ Syntax = /(\w+)/
24
+
25
+ def initialize(markup, tokens)
26
+ if markup =~ Syntax
27
+ @to = $1
28
+ super
29
+ else
30
+ raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
31
+ end
32
+ end
33
+
34
+ def render(context)
35
+ output = super
36
+ context[@to] = output.to_s
37
+ ''
38
+ end
39
+ end
40
+
41
+ class Cycle < Tag
42
+ SimpleSyntax = /#{QuotedFragment}/
43
+ NamedSyntax = /(#{QuotedFragment})\s*\:\s*(.*)/
44
+
45
+ def initialize(markup, tokens)
46
+ case markup
47
+ when NamedSyntax
48
+ @variables = variables_from_string($2)
49
+ @name = $1
50
+ when SimpleSyntax
51
+ @variables = variables_from_string(markup)
52
+ @name = "'#{@variables.to_s}'"
53
+ else
54
+ raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
55
+ end
56
+
57
+ end
58
+
59
+ def render(context)
60
+ context.registers[:cycle] ||= Hash.new(0)
61
+
62
+ context.stack do
63
+ key = context[@name]
64
+ iteration = context.registers[:cycle][key]
65
+ result = context[@variables[iteration]]
66
+ iteration += 1
67
+ iteration = 0 if iteration >= @variables.size
68
+ context.registers[:cycle][key] = iteration
69
+ result
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def variables_from_string(markup)
76
+ markup.split(',').collect do |var|
77
+ var =~ /\s*(#{QuotedFragment})\s*/
78
+ $1 ? $1 : nil
79
+ end.compact
80
+ end
81
+
82
+ end
83
+
84
+ class Comment < Block
85
+ def render(context)
86
+ ''
87
+ end
88
+ end
89
+
90
+ class For < Block
91
+ Syntax = /(\w+)\s+in\s+(#{AllowedVariableCharacters}+)/
92
+
93
+ def initialize(markup, tokens)
94
+ super
95
+
96
+ if markup =~ Syntax
97
+ @variable_name = $1
98
+ @collection_name = $2
99
+ @name = "#{$1}-#{$2}"
100
+ @attributes = {}
101
+ markup.scan(TagAttributes) do |key, value|
102
+ @attributes[key] = value
103
+ end
104
+ else
105
+ raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
106
+ end
107
+ end
108
+
109
+ def render(context)
110
+ context.registers[:for] ||= Hash.new(0)
111
+
112
+ collection = context[@collection_name]
113
+
114
+ return '' if collection.nil? or collection.empty?
115
+
116
+ range = (0..collection.length)
117
+
118
+ if @attributes['limit'] or @attributes['offset']
119
+
120
+
121
+ offset = 0
122
+ if @attributes['offset'] == 'continue'
123
+ offset = context.registers[:for][@name]
124
+ else
125
+ offset = context[@attributes['offset']] || 0
126
+ end
127
+
128
+ limit = context[@attributes['limit']]
129
+
130
+ range_end = limit ? offset + limit : collection.length
131
+
132
+ range = (offset..range_end-1)
133
+
134
+ # Save the range end in the registers so that future calls to
135
+ # offset:continue have something to pick up
136
+ context.registers[:for][@name] = range_end
137
+ end
138
+
139
+ result = []
140
+ segment = collection[range]
141
+ return '' if segment.nil?
142
+
143
+ context.stack do
144
+ length = segment.length
145
+
146
+ segment.each_with_index do |item, index|
147
+ context[@variable_name] = item
148
+ context['forloop'] = {
149
+ 'name' => @name,
150
+ 'length' => length,
151
+ 'index' => index + 1,
152
+ 'index0' => index,
153
+ 'rindex' => length - index,
154
+ 'rindex0' => length - index -1,
155
+ 'first' => (index == 0),
156
+ 'last' => (index == length - 1) }
157
+
158
+ result << render_all(@nodelist, context)
159
+ end
160
+ end
161
+
162
+ # Store position of last element we rendered. This allows us to do
163
+
164
+ result
165
+ end
166
+ end
167
+
168
+
169
+ class DecisionBlock < Block
170
+ def equal_variables(right, left)
171
+ if left.is_a?(Symbol)
172
+ if right.respond_to?(left.to_s)
173
+ return right.send(left.to_s)
174
+ else
175
+ raise ArgumentError.new("Error in tag '#{name}' - Cannot call method #{left} on type #{right.class}}")
176
+ end
177
+ end
178
+
179
+ if right.is_a?(Symbol)
180
+ if left.respond_to?(right.to_s)
181
+ return left.send(right.to_s)
182
+ else
183
+ raise ArgumentError.new("Error in tag '#{name}' - Cannot call method #{right} on type #{left.class}}")
184
+ end
185
+ end
186
+
187
+ left == right
188
+ end
189
+
190
+ def interpret_condition(left, right, op, context)
191
+
192
+ # If the operator is empty this means that the decision statement is just
193
+ # a single variable. We can just poll this variable from the context and
194
+ # return this as the result.
195
+ return context[left] if op == nil
196
+
197
+ left, right = context[left], context[right]
198
+
199
+ operation = case op
200
+ when '==' then return equal_variables(left, right)
201
+ when '!=' then return !equal_variables(left, right)
202
+ when '>' then :>
203
+ when '<' then :<
204
+ when '>=' then :>=
205
+ when '<=' then :<=
206
+ else
207
+ raise ArgumentError.new("Error in tag '#{name}' - Unknown operator #{op}")
208
+ end
209
+
210
+ if left.respond_to?(operation) and right.respond_to?(operation)
211
+ left.send(operation, right)
212
+ else
213
+ nil
214
+ end
215
+ end
216
+ end
217
+
218
+
219
+ class Case < DecisionBlock
220
+ Syntax = /(#{QuotedFragment})/
221
+ WhenSyntax = /(#{QuotedFragment})/
222
+
223
+ def initialize(markup, tokens)
224
+ @nodelists = []
225
+ @else_nodelist = []
226
+
227
+ super
228
+
229
+ if markup =~ Syntax
230
+ @left = $1
231
+ else
232
+ raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
233
+ end
234
+ end
235
+
236
+ def end_tag
237
+ push_nodelist
238
+ end
239
+
240
+ def unknown_tag(tag, params, tokens)
241
+ case tag
242
+ when 'when'
243
+ if params =~ WhenSyntax
244
+ push_nodelist
245
+ @right = $1
246
+ @nodelist = []
247
+ else
248
+ raise SyntaxError.new("Syntax Error in tag 'case' - Valid when condition: when [condition] ")
249
+ end
250
+ when 'else'
251
+ push_nodelist
252
+ @right = nil
253
+ @else_nodelist = @nodelist = []
254
+ else
255
+ super
256
+ end
257
+ end
258
+
259
+ def push_nodelist
260
+ if @right
261
+ # only push the nodelist if there was actually a when condition stated before.
262
+ # we discard all tokens between the case and the first when condition this way...
263
+ @nodelists << [@right, @nodelist]
264
+ end
265
+ end
266
+
267
+ def render(context)
268
+ output = []
269
+ run_else_block = true
270
+
271
+ @nodelists.each do |right, nodelist|
272
+ if equal_variables(context[@left], context[right])
273
+ run_else_block = false
274
+ context.stack do
275
+ output += render_all(nodelist, context)
276
+ end
277
+ end
278
+ end
279
+
280
+ if run_else_block
281
+ context.stack do
282
+ output += render_all(@else_nodelist, context)
283
+ end
284
+ end
285
+
286
+ output.to_s
287
+ end
288
+ end
289
+
290
+ class If < DecisionBlock
291
+ Syntax = /(#{QuotedFragment})\s*([=!<>]+)?\s*(#{QuotedFragment})?/
292
+
293
+ def initialize(markup, tokens)
294
+ @nodelist_true = @nodelist = []
295
+ @nodelist_false = []
296
+
297
+ super
298
+
299
+ if markup =~ Syntax
300
+ @left = $1
301
+ @operator = $2
302
+ @right = $3
303
+ else
304
+ raise SyntaxError.new("Syntax Error in tag 'if' - Valid syntax: if [condition]")
305
+ end
306
+
307
+ end
308
+
309
+ def unknown_tag(tag, params, tokens)
310
+ if tag == 'else'
311
+ @nodelist = @nodelist_false = []
312
+ else
313
+ super
314
+ end
315
+ end
316
+
317
+ def render(context)
318
+ context.stack do
319
+ if interpret_condition(@left, @right, @operator, context)
320
+ render_all(@nodelist_true, context)
321
+ else
322
+ render_all(@nodelist_false, context)
323
+ end
324
+ end
325
+ end
326
+ end
327
+
328
+ class Unless < If
329
+ def render(context)
330
+ context.stack do
331
+ if interpret_condition(@left, @right, @operator, context)
332
+ render_all(@nodelist_false, context)
333
+ else
334
+ render_all(@nodelist_true, context)
335
+ end
336
+ end
337
+ end
338
+ end
339
+
340
+ class Include < Tag
341
+ Syntax = /("[^"]+"|'[^']+')(\s+(with|for)\s+(#{QuotedFragment}+))?/
342
+
343
+ def initialize(markup, tokens)
344
+ if markup =~ Syntax
345
+ @template_name = $1[1...-1]
346
+ if $2
347
+ @collection = ($3 == "for")
348
+ @variable = $4
349
+ end
350
+ @attributes = {}
351
+ markup.scan(TagAttributes) do |key, value|
352
+ @attributes[key] = value
353
+ end
354
+ else
355
+ raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
356
+ end
357
+
358
+ super
359
+ end
360
+
361
+ def parse(tokens)
362
+ source = Liquid::Template.file_system.read_template_file(@template_name)
363
+ tokens = Liquid::Template.tokenize(source)
364
+ @document = Document.new(tokens)
365
+ end
366
+
367
+ def render(context)
368
+ result = ''
369
+ variable = context[@variable]
370
+ context.stack do
371
+ @attributes.each do |key, value|
372
+ context[key] = context[value]
373
+ end
374
+ if @collection
375
+ variable.each do |item|
376
+ context[@template_name] = item
377
+ result << @document.render(context).to_s
378
+ end
379
+ else
380
+ if @variable
381
+ context[@template_name] = variable
382
+ end
383
+ result << @document.render(context).to_s
384
+ end
385
+ end
386
+ result
387
+ end
388
+ end
389
+
390
+ Template.register_tag('assign', Assign)
391
+ Template.register_tag('capture', Capture)
392
+ Template.register_tag('comment', Comment)
393
+ Template.register_tag('for', For)
394
+ Template.register_tag('if', If)
395
+ Template.register_tag('unless', Unless)
396
+ Template.register_tag('case', Case)
397
+ Template.register_tag('cycle', Cycle)
398
+ Template.register_tag('include', Include)
399
+ end
@@ -0,0 +1,42 @@
1
+ module Liquid
2
+
3
+ # Strainer is the parent class for the filters system.
4
+ # New filters are mixed into the strainer class which is then instanciated for each liquid template render run.
5
+ #
6
+ # One of the strainer's responsibilities is to keep malicious method calls out
7
+ class Strainer
8
+ @@required_methods = ["__send__", "__id__", "respond_to?", "extend", "methods"]
9
+
10
+ @@filters = []
11
+
12
+ def initialize(context)
13
+ @context = context
14
+ end
15
+
16
+ def self.global_filter(filter)
17
+ raise StandardError, "Passed filter is not a module" unless filter.is_a?(Module)
18
+ @@filters << filter
19
+ end
20
+
21
+ def self.create(context)
22
+ strainer = Strainer.new(context)
23
+ @@filters.each { |m| strainer.extend(m) }
24
+ strainer
25
+ end
26
+
27
+ def respond_to?(method)
28
+ method_name = method.to_s
29
+ return false if method_name =~ /^__/
30
+ return false if @@required_methods.include?(method_name)
31
+ super
32
+ end
33
+
34
+ # remove all standard methods from the bucket so circumvent security
35
+ # problems
36
+ instance_methods.each do |m|
37
+ unless @@required_methods.include?(m)
38
+ undef_method m
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/liquid/tag.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Liquid
2
+
3
+ class Tag
4
+ attr_accessor :nodelist
5
+
6
+ def initialize(markup, tokens)
7
+ @markup = markup
8
+ parse(tokens)
9
+ end
10
+
11
+ def parse(tokens)
12
+ end
13
+
14
+ def name
15
+ self.class.name.downcase
16
+ end
17
+
18
+ def render(context)
19
+ ''
20
+ end
21
+ end
22
+
23
+
24
+ end
25
+
@@ -0,0 +1,88 @@
1
+ module Liquid
2
+
3
+ # Templates are central to liquid.
4
+ # Interpretating templates is a two step process. First you compile the
5
+ # source code you got. During compile time some extensive error checking is performed.
6
+ # your code should expect to get some SyntaxErrors.
7
+ #
8
+ # After you have a compiled template you can then <tt>render</tt> it.
9
+ # You can use a compiled template over and over again and keep it cached.
10
+ #
11
+ # Example:
12
+ #
13
+ # template = Liquid::Template.parse(source)
14
+ # template.render('user_name' => 'bob')
15
+ #
16
+ class Template
17
+ attr_accessor :root
18
+ @@file_system = BlankFileSystem.new
19
+
20
+ def self.file_system
21
+ @@file_system
22
+ end
23
+
24
+ def self.file_system=(obj)
25
+ @@file_system = obj
26
+ end
27
+
28
+ def self.register_tag(name, klass)
29
+ tags[name.to_s] = klass
30
+ end
31
+
32
+ def self.tags
33
+ @tags ||= {}
34
+ end
35
+
36
+ # Pass a module with filter methods which should be available
37
+ # to all liquid views. Good for registering the standard library
38
+ def self.register_filter(mod)
39
+ Strainer.global_filter(mod)
40
+ end
41
+
42
+ # creates a new <tt>Template</tt> object from liquid source code
43
+ def self.parse(source)
44
+ self.new(tokenize(source))
45
+ end
46
+
47
+ # Uses the <tt>Liquid::TokenizationRegexp</tt> regexp to tokenize the passed source
48
+ def self.tokenize(source)
49
+ return [] if source.to_s.empty?
50
+ tokens = source.split(TokenizationRegexp)
51
+
52
+ # removes the rogue empty element at the beginning of the array
53
+ tokens.shift if tokens[0] and tokens[0].empty?
54
+
55
+ tokens
56
+ end
57
+
58
+ # creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
59
+ def initialize(tokens = [])
60
+ @root = Document.new(tokens)
61
+ end
62
+
63
+ # Render takes a hash with local variables.
64
+ #
65
+ # if you use the same filters over and over again consider registering them globally
66
+ # with <tt>Template.register_filter</tt>
67
+ #
68
+ # Following options can be passed:
69
+ #
70
+ # * <tt>filters</tt> : array with local filters
71
+ # * <tt>registers</tt> : hash with register variables. Those can be accessed from
72
+ # filters and tags and might be useful to integrate liquid more with its host application
73
+ #
74
+ def render(assigns = {}, options = nil)
75
+ options = { :filters => options } unless options.is_a?(Hash)
76
+ context = Context.new(assigns, options[:registers])
77
+
78
+ # Apply all filters
79
+ [options[:filters]].flatten.each do |filter|
80
+ context.add_filters(filter)
81
+ end
82
+
83
+ # render the nodelist.
84
+ # for performance reasons we get a array back here. to_s will make a string out of it
85
+ @root.render(context).to_s
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,39 @@
1
+ module Liquid
2
+
3
+ # Hols variables. Variables are only loaded "just in time"
4
+ # they are not evaluated as part of the render stage
5
+ class Variable
6
+ attr_accessor :filters, :name
7
+
8
+ def initialize(markup)
9
+ @markup = markup
10
+ @name = markup.match(/\s*(#{QuotedFragment})/)[1]
11
+ if markup.match(/#{FilterSperator}\s*(.*)/)
12
+ filters = Regexp.last_match(1).split(/#{FilterSperator}/)
13
+
14
+ @filters = filters.collect do |f|
15
+ filtername = f.match(/\s*(\w+)/)[1]
16
+ filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/).flatten
17
+ [filtername.to_sym, filterargs]
18
+ end
19
+ else
20
+ @filters = []
21
+ end
22
+ end
23
+
24
+ def render(context)
25
+ output = context[@name]
26
+ @filters.inject(output) do |output, filter|
27
+ filterargs = filter[1].to_a.collect do |a|
28
+ context[a]
29
+ end
30
+ begin
31
+ output = context.invoke(filter[0], output, *filterargs)
32
+ rescue FilterNotFound
33
+ raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
34
+ end
35
+ end
36
+ output
37
+ end
38
+ end
39
+ end