darthapo-comatose 2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. data/CHANGELOG +171 -0
  2. data/INSTALL +19 -0
  3. data/LICENSE +20 -0
  4. data/MANIFEST +91 -0
  5. data/README.rdoc +148 -0
  6. data/Rakefile +122 -0
  7. data/SPECS +61 -0
  8. data/about.yml +7 -0
  9. data/bin/comatose +20 -0
  10. data/generators/comatose_migration/USAGE +15 -0
  11. data/generators/comatose_migration/comatose_migration_generator.rb +59 -0
  12. data/generators/comatose_migration/templates/migration.rb +35 -0
  13. data/generators/comatose_migration/templates/v4_upgrade.rb +15 -0
  14. data/generators/comatose_migration/templates/v6_upgrade.rb +23 -0
  15. data/generators/comatose_migration/templates/v7_upgrade.rb +22 -0
  16. data/init.rb +10 -0
  17. data/install.rb +16 -0
  18. data/lib/acts_as_versioned.rb +543 -0
  19. data/lib/comatose.rb +23 -0
  20. data/lib/comatose/comatose_drop.rb +79 -0
  21. data/lib/comatose/configuration.rb +68 -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_admin_controller.rb +348 -0
  29. data/lib/comatose_admin_helper.rb +37 -0
  30. data/lib/comatose_controller.rb +141 -0
  31. data/lib/comatose_helper.rb +3 -0
  32. data/lib/comatose_page.rb +141 -0
  33. data/lib/liquid.rb +52 -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/redcloth.rb +1129 -0
  49. data/lib/support/class_options.rb +36 -0
  50. data/lib/support/inline_rendering.rb +48 -0
  51. data/lib/support/route_mapper.rb +50 -0
  52. data/lib/text_filters.rb +138 -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/rails/init.rb +12 -0
  60. data/resources/layouts/comatose_admin_template.html.erb +28 -0
  61. data/resources/public/images/collapsed.gif +0 -0
  62. data/resources/public/images/expanded.gif +0 -0
  63. data/resources/public/images/no-children.gif +0 -0
  64. data/resources/public/images/page.gif +0 -0
  65. data/resources/public/images/spinner.gif +0 -0
  66. data/resources/public/images/title-hover-bg.gif +0 -0
  67. data/resources/public/javascripts/comatose_admin.js +401 -0
  68. data/resources/public/stylesheets/comatose_admin.css +381 -0
  69. data/tasks/comatose.rake +9 -0
  70. data/test/behaviors.rb +106 -0
  71. data/test/fixtures/comatose_pages.yml +96 -0
  72. data/test/functional/comatose_admin_controller_test.rb +112 -0
  73. data/test/functional/comatose_controller_test.rb +44 -0
  74. data/test/javascripts/test.html +26 -0
  75. data/test/javascripts/test_runner.js +307 -0
  76. data/test/test_helper.rb +43 -0
  77. data/test/unit/class_options_test.rb +52 -0
  78. data/test/unit/comatose_page_test.rb +128 -0
  79. data/test/unit/processing_context_test.rb +108 -0
  80. data/test/unit/text_filters_test.rb +52 -0
  81. data/views/comatose_admin/_form.html.erb +96 -0
  82. data/views/comatose_admin/_page_list_item.html.erb +60 -0
  83. data/views/comatose_admin/delete.html.erb +18 -0
  84. data/views/comatose_admin/edit.html.erb +5 -0
  85. data/views/comatose_admin/index.html.erb +18 -0
  86. data/views/comatose_admin/new.html.erb +5 -0
  87. data/views/comatose_admin/reorder.html.erb +30 -0
  88. data/views/comatose_admin/versions.html.erb +40 -0
  89. data/views/layouts/comatose_admin.html.erb +814 -0
  90. data/views/layouts/comatose_admin_customize.html.erb +28 -0
  91. data/views/layouts/comatose_content.html.erb +17 -0
  92. metadata +157 -0
@@ -0,0 +1,17 @@
1
+ module Liquid
2
+ class Document < Block
3
+ # we don't need markup to open this block
4
+ def initialize(tokens)
5
+ parse(tokens)
6
+ end
7
+
8
+ # There isn't a real delimter
9
+ def block_delimiter
10
+ []
11
+ end
12
+
13
+ # Document blocks don't need to be terminated since they are not actually opened
14
+ def assert_missing_delimitation!
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,48 @@
1
+ module Liquid
2
+
3
+ # A drop in liquid is a class which allows you to to export DOM like things to liquid
4
+ # Methods of drops are callable.
5
+ # The main use for liquid drops is the implement lazy loaded objects.
6
+ # If you would like to make data available to the web designers which you don't want loaded unless needed then
7
+ # a drop is a great way to do that
8
+ #
9
+ # Example:
10
+ #
11
+ # class ProductDrop < Liquid::Drop
12
+ # def top_sales
13
+ # Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
14
+ # end
15
+ # end
16
+ #
17
+ # tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
18
+ # tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
19
+ #
20
+ # Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
21
+ # catch all
22
+ class Drop
23
+ attr_writer :context
24
+
25
+ # Catch all for the method
26
+ def before_method(method)
27
+ nil
28
+ end
29
+
30
+ # called by liquid to invoke a drop
31
+ def invoke_drop(method)
32
+ result = before_method(method)
33
+ result ||= send(method.to_sym) if self.class.public_instance_methods.include?(method.to_s)
34
+ result
35
+ end
36
+
37
+ def has_key?(name)
38
+ true
39
+ end
40
+
41
+ def to_liquid
42
+ self
43
+ end
44
+
45
+ alias :[] :invoke_drop
46
+ end
47
+
48
+ end
@@ -0,0 +1,7 @@
1
+ module Liquid
2
+ class FilterNotFound < StandardError
3
+ end
4
+
5
+ class FileSystemError < StandardError
6
+ end
7
+ end
@@ -0,0 +1,53 @@
1
+ class String
2
+ def to_liquid
3
+ self
4
+ end
5
+ end
6
+
7
+ class Array
8
+ def to_liquid
9
+ self
10
+ end
11
+ end
12
+
13
+ class Hash
14
+ def to_liquid
15
+ self
16
+ end
17
+ end
18
+
19
+ class Numeric
20
+ def to_liquid
21
+ self
22
+ end
23
+ end
24
+
25
+ class Time
26
+ def to_liquid
27
+ self
28
+ end
29
+ end
30
+
31
+ class DateTime
32
+ def to_liquid
33
+ self
34
+ end
35
+ end
36
+
37
+ class Date
38
+ def to_liquid
39
+ self
40
+ end
41
+ end
42
+
43
+ def true.to_liquid
44
+ self
45
+ end
46
+
47
+ def false.to_liquid
48
+ self
49
+ end
50
+
51
+ def nil.to_liquid
52
+ self
53
+ end
@@ -0,0 +1,62 @@
1
+ module Liquid
2
+ # A Liquid file system is way to let your templates retrieve other templates for use with the include tag.
3
+ #
4
+ # You can implement subclasses that retrieve templates from the database, from the file system using a different
5
+ # path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
6
+ #
7
+ # You can add additional instance variables, arguments, or methods as needed.
8
+ #
9
+ # Example:
10
+ #
11
+ # Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
12
+ # liquid = Liquid::Template.parse(template)
13
+ #
14
+ # This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
15
+ class BlankFileSystem
16
+ # Called by Liquid to retrieve a template file
17
+ def read_template_file(template_path)
18
+ raise FileSystemError, "This liquid context does not allow includes."
19
+ end
20
+ end
21
+
22
+ # This implements an abstract file system which retrieves template files named in a manner similar to Rails partials,
23
+ # ie. with the template name prefixed with an underscore. The extension ".liquid" is also added.
24
+ #
25
+ # For security reasons, template paths are only allowed to contain letters, numbers, and underscore.
26
+ #
27
+ # Example:
28
+ #
29
+ # file_system = Liquid::LocalFileSystem.new("/some/path")
30
+ #
31
+ # file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
32
+ # file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
33
+ #
34
+ class LocalFileSystem
35
+ attr_accessor :root
36
+
37
+ def initialize(root)
38
+ @root = root
39
+ end
40
+
41
+ def read_template_file(template_path)
42
+ full_path = full_path(template_path)
43
+ raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
44
+
45
+ File.read(full_path)
46
+ end
47
+
48
+ def full_path(template_path)
49
+ raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
50
+
51
+ full_path = if template_path.include?('/')
52
+ File.join(root, File.dirname(template_path), "_#{File.basename(template_path)}.liquid")
53
+ else
54
+ File.join(root, "_#{template_path}.liquid")
55
+ end
56
+
57
+ raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/
58
+
59
+ full_path
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,64 @@
1
+ module Liquid
2
+ class TableRow < Block
3
+ Syntax = /(\w+)\s+in\s+(#{AllowedVariableCharacters}+)/
4
+
5
+ def initialize(markup, tokens)
6
+ super
7
+
8
+ if markup =~ Syntax
9
+ @variable_name = $1
10
+ @collection_name = $2
11
+ @attributes = {}
12
+ markup.scan(TagAttributes) do |key, value|
13
+ @attributes[key] = value
14
+ end
15
+ else
16
+ raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
17
+ end
18
+ end
19
+
20
+ def render(context)
21
+ collection = context[@collection_name] or return ''
22
+
23
+ if @attributes['limit'] or @attributes['offset']
24
+ limit = context[@attributes['limit']] || -1
25
+ offset = context[@attributes['offset']] || 0
26
+ collection = collection[offset.to_i..(limit.to_i + offset.to_i - 1)]
27
+ end
28
+
29
+ length = collection.length
30
+
31
+ cols = context[@attributes['cols']].to_i
32
+
33
+ row = 1
34
+ col = 0
35
+
36
+ result = ["<tr class=\"row1\">\n"]
37
+ context.stack do
38
+
39
+ collection.each_with_index do |item, index|
40
+ context[@variable_name] = item
41
+ context['tablerowloop'] = {
42
+ 'length' => length,
43
+ 'index' => index + 1,
44
+ 'index0' => index,
45
+ 'rindex' => length - index,
46
+ 'rindex0' => length - index -1,
47
+ 'first' => (index == 0),
48
+ 'last' => (index == length - 1) }
49
+
50
+ result << ["<td class=\"col#{col += 1}\">"] + render_all(@nodelist, context) + ['</td>']
51
+
52
+ if col == cols and not (index == length - 1)
53
+ col = 0
54
+ result << ["</tr>\n<tr class=\"row#{row += 1}\">"]
55
+ end
56
+
57
+ end
58
+ end
59
+ result + ["</tr>\n"]
60
+ end
61
+ end
62
+
63
+ Template.register_tag('tablerow', TableRow)
64
+ end
@@ -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] + '&hellip;'
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(' ') + '&hellip;'
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