merb_comatose 0.0.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.
@@ -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
data/lib/liquid.rb ADDED
@@ -0,0 +1,52 @@
1
+ # Copyright (c) 2005 Tobias Luetke
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
17
+ # NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
23
+
24
+ module Liquid
25
+ FilterSperator = /\|/
26
+ ArgumentSeparator = ','
27
+ FilterArgumentSeparator = ':'
28
+ VariableAttributeSeparator = '.'
29
+ TagStart = /\{\%/
30
+ TagEnd = /\%\}/
31
+ VariableStart = /\{\{/
32
+ VariableEnd = /\}\}/
33
+ AllowedVariableCharacters = /[a-zA-Z_.-]/
34
+ QuotedFragment = /"[^"]+"|'[^']+'|[^\s,|]+/
35
+ TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
36
+ TokenizationRegexp = /(#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableEnd})/
37
+ end
38
+
39
+ require 'liquid/drop'
40
+ require 'liquid/extensions'
41
+ require 'liquid/errors'
42
+ require 'liquid/strainer'
43
+ require 'liquid/context'
44
+ require 'liquid/tag'
45
+ require 'liquid/block'
46
+ require 'liquid/document'
47
+ require 'liquid/variable'
48
+ require 'liquid/file_system'
49
+ require 'liquid/template'
50
+ require 'liquid/standardtags'
51
+ require 'liquid/htmltags'
52
+ require 'liquid/standardfilters'
@@ -0,0 +1,6 @@
1
+ namespace :merb_comatose do
2
+ desc "Do something for merb_comatose"
3
+ task :default do
4
+ puts "merb_comatose doesn't do anything"
5
+ end
6
+ end
@@ -0,0 +1,30 @@
1
+ require 'redcloth' unless defined?(RedCloth)
2
+ require 'liquid' unless defined?(Liquid)
3
+
4
+ require 'support/class_options'
5
+ require 'text_filters'
6
+
7
+ require 'comatose/configuration'
8
+ require 'comatose/comatose_drop'
9
+ require 'comatose/processing_context'
10
+ require 'comatose/page_wrapper'
11
+ require 'comatose/version'
12
+
13
+ # make sure we're running inside Merb
14
+ if defined?(Merb::Plugins)
15
+
16
+ # Merb gives you a Merb::Plugins.config hash...feel free to put your stuff in your piece of it
17
+ Merb::Plugins.config[:merb_comatose] = {
18
+ :chickens => false
19
+ }
20
+
21
+ Merb::BootLoader.before_app_loads do
22
+ # require code that must be loaded before the application
23
+ end
24
+
25
+ Merb::BootLoader.after_app_loads do
26
+ # code that can be required after the application loads
27
+ end
28
+
29
+ Merb::Plugins.add_rakefiles "merb_comatose/merbtasks"
30
+ end