merb_comatose 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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