aslakjo-comatose 2.0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +195 -0
- data/INSTALL +20 -0
- data/LICENSE +20 -0
- data/MANIFEST +91 -0
- data/README.markdown +159 -0
- data/Rakefile +176 -0
- data/SPECS +61 -0
- data/about.yml +7 -0
- data/bin/comatose +112 -0
- data/comatose.gemspec +113 -0
- data/generators/comatose_migration/USAGE +15 -0
- data/generators/comatose_migration/comatose_migration_generator.rb +74 -0
- data/generators/comatose_migration/templates/migration.rb +35 -0
- data/generators/comatose_migration/templates/v4_upgrade.rb +15 -0
- data/generators/comatose_migration/templates/v6_upgrade.rb +23 -0
- data/generators/comatose_migration/templates/v7_upgrade.rb +22 -0
- data/init.rb +2 -0
- data/install.rb +18 -0
- data/lib/acts_as_versioned.rb +543 -0
- data/lib/comatose/comatose_drop.rb +79 -0
- data/lib/comatose/configuration.rb +69 -0
- data/lib/comatose/page_wrapper.rb +119 -0
- data/lib/comatose/processing_context.rb +69 -0
- data/lib/comatose/tasks/admin.rb +60 -0
- data/lib/comatose/tasks/data.rb +82 -0
- data/lib/comatose/tasks/setup.rb +52 -0
- data/lib/comatose/version.rb +4 -0
- data/lib/comatose.rb +33 -0
- data/lib/comatose_admin_controller.rb +395 -0
- data/lib/comatose_admin_helper.rb +37 -0
- data/lib/comatose_controller.rb +138 -0
- data/lib/comatose_helper.rb +3 -0
- data/lib/comatose_page.rb +141 -0
- data/lib/liquid/block.rb +96 -0
- data/lib/liquid/context.rb +190 -0
- data/lib/liquid/document.rb +17 -0
- data/lib/liquid/drop.rb +48 -0
- data/lib/liquid/errors.rb +7 -0
- data/lib/liquid/extensions.rb +53 -0
- data/lib/liquid/file_system.rb +62 -0
- data/lib/liquid/htmltags.rb +64 -0
- data/lib/liquid/standardfilters.rb +111 -0
- data/lib/liquid/standardtags.rb +399 -0
- data/lib/liquid/strainer.rb +42 -0
- data/lib/liquid/tag.rb +25 -0
- data/lib/liquid/template.rb +88 -0
- data/lib/liquid/variable.rb +39 -0
- data/lib/liquid.rb +52 -0
- data/lib/redcloth.rb +1129 -0
- data/lib/support/class_options.rb +36 -0
- data/lib/support/inline_rendering.rb +48 -0
- data/lib/support/route_mapper.rb +50 -0
- data/lib/text_filters/markdown.rb +14 -0
- data/lib/text_filters/markdown_smartypants.rb +15 -0
- data/lib/text_filters/none.rb +8 -0
- data/lib/text_filters/rdoc.rb +13 -0
- data/lib/text_filters/simple.rb +8 -0
- data/lib/text_filters/textile.rb +15 -0
- data/lib/text_filters.rb +140 -0
- data/rails/init.rb +3 -0
- data/resources/layouts/comatose_admin_template.html.erb +28 -0
- data/resources/public/images/collapsed.gif +0 -0
- data/resources/public/images/expanded.gif +0 -0
- data/resources/public/images/no-children.gif +0 -0
- data/resources/public/images/page.gif +0 -0
- data/resources/public/images/spinner.gif +0 -0
- data/resources/public/images/title-hover-bg.gif +0 -0
- data/resources/public/javascripts/comatose_admin.js +401 -0
- data/resources/public/stylesheets/comatose_admin.css +404 -0
- data/tasks/comatose.rake +9 -0
- data/test/behaviors.rb +106 -0
- data/test/fixtures/comatose_pages.yml +96 -0
- data/test/functional/comatose_admin_controller_test.rb +114 -0
- data/test/functional/comatose_controller_test.rb +44 -0
- data/test/javascripts/test.html +26 -0
- data/test/javascripts/test_runner.js +307 -0
- data/test/test_helper.rb +55 -0
- data/test/unit/class_options_test.rb +52 -0
- data/test/unit/comatose_page_test.rb +136 -0
- data/test/unit/processing_context_test.rb +108 -0
- data/test/unit/text_filters_test.rb +52 -0
- data/views/comatose_admin/_form.html.erb +96 -0
- data/views/comatose_admin/_page_list_item.html.erb +60 -0
- data/views/comatose_admin/delete.html.erb +18 -0
- data/views/comatose_admin/edit.html.erb +5 -0
- data/views/comatose_admin/index.html.erb +29 -0
- data/views/comatose_admin/new.html.erb +5 -0
- data/views/comatose_admin/reorder.html.erb +30 -0
- data/views/comatose_admin/versions.html.erb +40 -0
- data/views/layouts/comatose_admin.html.erb +837 -0
- data/views/layouts/comatose_admin_customize.html.erb +28 -0
- data/views/layouts/comatose_content.html.erb +17 -0
- 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
|