darthapo-comatose 2.0

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 (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