locomotivecms-liquid 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +108 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +75 -0
  5. data/lib/extras/liquid_view.rb +51 -0
  6. data/lib/liquid/block.rb +160 -0
  7. data/lib/liquid/condition.rb +120 -0
  8. data/lib/liquid/context.rb +268 -0
  9. data/lib/liquid/document.rb +18 -0
  10. data/lib/liquid/drop.rb +74 -0
  11. data/lib/liquid/errors.rb +21 -0
  12. data/lib/liquid/extensions.rb +62 -0
  13. data/lib/liquid/file_system.rb +62 -0
  14. data/lib/liquid/htmltags.rb +74 -0
  15. data/lib/liquid/i18n.rb +39 -0
  16. data/lib/liquid/interrupts.rb +17 -0
  17. data/lib/liquid/lexer.rb +51 -0
  18. data/lib/liquid/locales/en.yml +25 -0
  19. data/lib/liquid/module_ex.rb +62 -0
  20. data/lib/liquid/parser.rb +89 -0
  21. data/lib/liquid/standardfilters.rb +285 -0
  22. data/lib/liquid/strainer.rb +53 -0
  23. data/lib/liquid/tag.rb +61 -0
  24. data/lib/liquid/tags/assign.rb +36 -0
  25. data/lib/liquid/tags/break.rb +21 -0
  26. data/lib/liquid/tags/capture.rb +40 -0
  27. data/lib/liquid/tags/case.rb +77 -0
  28. data/lib/liquid/tags/comment.rb +16 -0
  29. data/lib/liquid/tags/continue.rb +21 -0
  30. data/lib/liquid/tags/cycle.rb +61 -0
  31. data/lib/liquid/tags/decrement.rb +39 -0
  32. data/lib/liquid/tags/default_content.rb +21 -0
  33. data/lib/liquid/tags/extends.rb +79 -0
  34. data/lib/liquid/tags/for.rb +167 -0
  35. data/lib/liquid/tags/if.rb +100 -0
  36. data/lib/liquid/tags/ifchanged.rb +20 -0
  37. data/lib/liquid/tags/include.rb +97 -0
  38. data/lib/liquid/tags/increment.rb +36 -0
  39. data/lib/liquid/tags/inherited_block.rb +101 -0
  40. data/lib/liquid/tags/raw.rb +22 -0
  41. data/lib/liquid/tags/unless.rb +33 -0
  42. data/lib/liquid/template.rb +213 -0
  43. data/lib/liquid/utils.rb +30 -0
  44. data/lib/liquid/variable.rb +109 -0
  45. data/lib/liquid/version.rb +4 -0
  46. data/lib/liquid.rb +72 -0
  47. data/lib/locomotivecms-liquid.rb +1 -0
  48. metadata +94 -0
@@ -0,0 +1,25 @@
1
+ ---
2
+ errors:
3
+ syntax:
4
+ assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
5
+ capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
6
+ case: "Syntax Error in 'case' - Valid syntax: case [condition]"
7
+ case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}"
8
+ case_invalid_else: "Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) "
9
+ cycle: "Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]"
10
+ for: "Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]"
11
+ for_invalid_in: "For loops require an 'in' clause"
12
+ for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
13
+ if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
14
+ include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
15
+ unknown_tag: "Unknown tag '%{tag}'"
16
+ invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
17
+ unexpected_else: "%{block_name} tag does not expect else tag"
18
+ tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
19
+ variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
20
+ tag_never_closed: "'%{block_name}' tag was never closed"
21
+ meta_syntax_error: "Liquid syntax error: #{e.message}"
22
+ table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
23
+
24
+ extends: "Syntax Error in 'extends' - Valid syntax: extends [template]"
25
+ block: "Syntax Error in 'block' - Valid syntax: block [name]"
@@ -0,0 +1,62 @@
1
+ # Copyright 2007 by Domizio Demichelis
2
+ # This library is free software. It may be used, redistributed and/or modified
3
+ # under the same terms as Ruby itself
4
+ #
5
+ # This extension is used in order to expose the object of the implementing class
6
+ # to liquid as it were a Drop. It also limits the liquid-callable methods of the instance
7
+ # to the allowed method passed with the liquid_methods call
8
+ # Example:
9
+ #
10
+ # class SomeClass
11
+ # liquid_methods :an_allowed_method
12
+ #
13
+ # def an_allowed_method
14
+ # 'this comes from an allowed method'
15
+ # end
16
+ # def unallowed_method
17
+ # 'this will never be an output'
18
+ # end
19
+ # end
20
+ #
21
+ # if you want to extend the drop to other methods you can defines more methods
22
+ # in the class <YourClass>::LiquidDropClass
23
+ #
24
+ # class SomeClass::LiquidDropClass
25
+ # def another_allowed_method
26
+ # 'and this from another allowed method'
27
+ # end
28
+ # end
29
+ # end
30
+ #
31
+ # usage:
32
+ # @something = SomeClass.new
33
+ #
34
+ # template:
35
+ # {{something.an_allowed_method}}{{something.unallowed_method}} {{something.another_allowed_method}}
36
+ #
37
+ # output:
38
+ # 'this comes from an allowed method and this from another allowed method'
39
+ #
40
+ # You can also chain associations, by adding the liquid_method call in the
41
+ # association models.
42
+ #
43
+ class Module
44
+
45
+ def liquid_methods(*allowed_methods)
46
+ drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
47
+ define_method :to_liquid do
48
+ drop_class.new(self)
49
+ end
50
+ drop_class.class_eval do
51
+ def initialize(object)
52
+ @object = object
53
+ end
54
+ allowed_methods.each do |sym|
55
+ define_method sym do
56
+ @object.send sym
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ end
@@ -0,0 +1,89 @@
1
+ module Liquid
2
+ class Parser
3
+ def initialize(input)
4
+ l = Lexer.new(input)
5
+ @tokens = l.tokenize
6
+ @p = 0 # pointer to current location
7
+ end
8
+
9
+ def jump(point)
10
+ @p = point
11
+ end
12
+
13
+ def consume(type = nil)
14
+ token = @tokens[@p]
15
+ if type && token[0] != type
16
+ raise SyntaxError, "Expected #{type} but found #{@tokens[@p].first}"
17
+ end
18
+ @p += 1
19
+ token[1]
20
+ end
21
+
22
+ # Only consumes the token if it matches the type
23
+ # Returns the token's contents if it was consumed
24
+ # or false otherwise.
25
+ def consume?(type)
26
+ token = @tokens[@p]
27
+ return false unless token && token[0] == type
28
+ @p += 1
29
+ token[1]
30
+ end
31
+
32
+ # Like consume? Except for an :id token of a certain name
33
+ def id?(str)
34
+ token = @tokens[@p]
35
+ return false unless token && token[0] == :id
36
+ return false unless token[1] == str
37
+ @p += 1
38
+ token[1]
39
+ end
40
+
41
+ def look(type, ahead = 0)
42
+ tok = @tokens[@p + ahead]
43
+ return false unless tok
44
+ tok[0] == type
45
+ end
46
+
47
+ def expression
48
+ token = @tokens[@p]
49
+ if token[0] == :id
50
+ variable_signature
51
+ elsif [:string, :number].include? token[0]
52
+ consume
53
+ elsif token.first == :open_round
54
+ consume
55
+ first = expression
56
+ consume(:dotdot)
57
+ last = expression
58
+ consume(:close_round)
59
+ "(#{first}..#{last})"
60
+ else
61
+ raise SyntaxError, "#{token} is not a valid expression"
62
+ end
63
+ end
64
+
65
+ def argument
66
+ str = ""
67
+ # might be a keyword argument (identifier: expression)
68
+ if look(:id) && look(:colon, 1)
69
+ str << consume << consume << ' '
70
+ end
71
+
72
+ str << expression
73
+ end
74
+
75
+ def variable_signature
76
+ str = consume(:id)
77
+ if look(:open_square)
78
+ str << consume
79
+ str << expression
80
+ str << consume(:close_square)
81
+ end
82
+ if look(:dot)
83
+ str << consume
84
+ str << variable_signature
85
+ end
86
+ str
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,285 @@
1
+ require 'cgi'
2
+ require 'bigdecimal'
3
+
4
+ module Liquid
5
+
6
+ module StandardFilters
7
+
8
+ # Return the size of an array or of an string
9
+ def size(input)
10
+ input.respond_to?(:size) ? input.size : 0
11
+ end
12
+
13
+ # convert an input string to DOWNCASE
14
+ def downcase(input)
15
+ input.to_s.downcase
16
+ end
17
+
18
+ # convert an input string to UPCASE
19
+ def upcase(input)
20
+ input.to_s.upcase
21
+ end
22
+
23
+ # capitalize words in the input centence
24
+ def capitalize(input)
25
+ input.to_s.capitalize
26
+ end
27
+
28
+ def escape(input)
29
+ CGI.escapeHTML(input) rescue input
30
+ end
31
+
32
+ def escape_once(input)
33
+ ActionView::Helpers::TagHelper.escape_once(input)
34
+ rescue NameError
35
+ input
36
+ end
37
+
38
+ alias_method :h, :escape
39
+
40
+ # Truncate a string down to x characters
41
+ def truncate(input, length = 50, truncate_string = "...")
42
+ if input.nil? then return end
43
+ l = length.to_i - truncate_string.length
44
+ l = 0 if l < 0
45
+ truncated = RUBY_VERSION[0,3] == "1.8" ? input.scan(/./mu)[0...l].to_s : input[0...l]
46
+ input.length > length.to_i ? truncated + truncate_string : input
47
+ end
48
+
49
+ def truncatewords(input, words = 15, truncate_string = "...")
50
+ if input.nil? then return end
51
+ wordlist = input.to_s.split
52
+ l = words.to_i - 1
53
+ l = 0 if l < 0
54
+ wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
55
+ end
56
+
57
+ # Split input string into an array of substrings separated by given pattern.
58
+ #
59
+ # Example:
60
+ # <div class="summary">{{ post | split '//' | first }}</div>
61
+ #
62
+ def split(input, pattern)
63
+ input.split(pattern)
64
+ end
65
+
66
+ def strip_html(input)
67
+ input.to_s.gsub(/<script.*?<\/script>/m, '').gsub(/<!--.*?-->/m, '').gsub(/<style.*?<\/style>/m, '').gsub(/<.*?>/m, '')
68
+ end
69
+
70
+ # Remove all newlines from the string
71
+ def strip_newlines(input)
72
+ input.to_s.gsub(/\r?\n/, '')
73
+ end
74
+
75
+ # Join elements of the array with certain character between them
76
+ def join(input, array_glue = ' ', hash_glue = nil)
77
+ hash_glue ||= array_glue
78
+
79
+ # translate from hash to array if needed
80
+ input = input.map{|k,v| "#{k}#{hash_glue}#{v}" } if input.is_a?(Hash)
81
+
82
+ [input].flatten.join(array_glue)
83
+ end
84
+
85
+ # Sort elements of the array
86
+ # provide optional property with which to sort an array of hashes or drops
87
+ def sort(input, property = nil)
88
+ ary = flatten_if_necessary(input)
89
+ if property.nil?
90
+ ary.sort
91
+ elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
92
+ ary.sort {|a,b| a[property] <=> b[property] }
93
+ elsif ary.first.respond_to?(property)
94
+ ary.sort {|a,b| a.send(property) <=> b.send(property) }
95
+ end
96
+ end
97
+
98
+ # Reverse the elements of an array
99
+ def reverse(input)
100
+ ary = [input].flatten
101
+ ary.reverse
102
+ end
103
+
104
+ # map/collect on a given property
105
+ def map(input, property)
106
+ flatten_if_necessary(input).map do |e|
107
+ e = e.call if e.is_a?(Proc)
108
+
109
+ if property == "to_liquid"
110
+ e
111
+ elsif e.respond_to?(:[])
112
+ e[property]
113
+ end
114
+ end
115
+ end
116
+
117
+ # Replace occurrences of a string with another
118
+ def replace(input, string, replacement = '')
119
+ input.to_s.gsub(string, replacement.to_s)
120
+ end
121
+
122
+ # Replace the first occurrences of a string with another
123
+ def replace_first(input, string, replacement = '')
124
+ input.to_s.sub(string, replacement.to_s)
125
+ end
126
+
127
+ # remove a substring
128
+ def remove(input, string)
129
+ input.to_s.gsub(string, '')
130
+ end
131
+
132
+ # remove the first occurrences of a substring
133
+ def remove_first(input, string)
134
+ input.to_s.sub(string, '')
135
+ end
136
+
137
+ # add one string to another
138
+ def append(input, string)
139
+ input.to_s + string.to_s
140
+ end
141
+
142
+ # prepend a string to another
143
+ def prepend(input, string)
144
+ string.to_s + input.to_s
145
+ end
146
+
147
+ # Add <br /> tags in front of all newlines in input string
148
+ def newline_to_br(input)
149
+ input.to_s.gsub(/\n/, "<br />\n")
150
+ end
151
+
152
+ # Reformat a date
153
+ #
154
+ # %a - The abbreviated weekday name (``Sun'')
155
+ # %A - The full weekday name (``Sunday'')
156
+ # %b - The abbreviated month name (``Jan'')
157
+ # %B - The full month name (``January'')
158
+ # %c - The preferred local date and time representation
159
+ # %d - Day of the month (01..31)
160
+ # %H - Hour of the day, 24-hour clock (00..23)
161
+ # %I - Hour of the day, 12-hour clock (01..12)
162
+ # %j - Day of the year (001..366)
163
+ # %m - Month of the year (01..12)
164
+ # %M - Minute of the hour (00..59)
165
+ # %p - Meridian indicator (``AM'' or ``PM'')
166
+ # %S - Second of the minute (00..60)
167
+ # %U - Week number of the current year,
168
+ # starting with the first Sunday as the first
169
+ # day of the first week (00..53)
170
+ # %W - Week number of the current year,
171
+ # starting with the first Monday as the first
172
+ # day of the first week (00..53)
173
+ # %w - Day of the week (Sunday is 0, 0..6)
174
+ # %x - Preferred representation for the date alone, no time
175
+ # %X - Preferred representation for the time alone, no date
176
+ # %y - Year without a century (00..99)
177
+ # %Y - Year with century
178
+ # %Z - Time zone name
179
+ # %% - Literal ``%'' character
180
+ def date(input, format)
181
+
182
+ if format.to_s.empty?
183
+ return input.to_s
184
+ end
185
+
186
+ if ((input.is_a?(String) && !/^\d+$/.match(input.to_s).nil?) || input.is_a?(Integer)) && input.to_i > 0
187
+ input = Time.at(input.to_i)
188
+ end
189
+
190
+ date = if input.is_a?(String)
191
+ case input.downcase
192
+ when 'now', 'today'
193
+ Time.now
194
+ else
195
+ Time.parse(input)
196
+ end
197
+ else
198
+ input
199
+ end
200
+
201
+ if date.respond_to?(:strftime)
202
+ date.strftime(format.to_s)
203
+ else
204
+ input
205
+ end
206
+ rescue
207
+ input
208
+ end
209
+
210
+ # Get the first element of the passed in array
211
+ #
212
+ # Example:
213
+ # {{ product.images | first | to_img }}
214
+ #
215
+ def first(array)
216
+ array.first if array.respond_to?(:first)
217
+ end
218
+
219
+ # Get the last element of the passed in array
220
+ #
221
+ # Example:
222
+ # {{ product.images | last | to_img }}
223
+ #
224
+ def last(array)
225
+ array.last if array.respond_to?(:last)
226
+ end
227
+
228
+ # addition
229
+ def plus(input, operand)
230
+ apply_operation(input, operand, :+)
231
+ end
232
+
233
+ # subtraction
234
+ def minus(input, operand)
235
+ apply_operation(input, operand, :-)
236
+ end
237
+
238
+ # multiplication
239
+ def times(input, operand)
240
+ apply_operation(input, operand, :*)
241
+ end
242
+
243
+ # division
244
+ def divided_by(input, operand)
245
+ apply_operation(input, operand, :/)
246
+ end
247
+
248
+ def modulo(input, operand)
249
+ apply_operation(input, operand, :%)
250
+ end
251
+
252
+ private
253
+
254
+ def flatten_if_necessary(input)
255
+ ary = if input.is_a?(Array)
256
+ input.flatten
257
+ elsif input.kind_of?(Enumerable)
258
+ input
259
+ else
260
+ [input].flatten
261
+ end
262
+ ary.map{ |e| e.respond_to?(:to_liquid) ? e.to_liquid : e }
263
+ end
264
+
265
+ def to_number(obj)
266
+ case obj
267
+ when Float
268
+ BigDecimal.new(obj.to_s)
269
+ when Numeric
270
+ obj
271
+ when String
272
+ (obj.strip =~ /^\d+\.\d+$/) ? BigDecimal.new(obj) : obj.to_i
273
+ else
274
+ 0
275
+ end
276
+ end
277
+
278
+ def apply_operation(input, operand, operation)
279
+ result = to_number(input).send(operation, to_number(operand))
280
+ result.is_a?(BigDecimal) ? result.to_f : result
281
+ end
282
+ end
283
+
284
+ Template.register_filter(StandardFilters)
285
+ end
@@ -0,0 +1,53 @@
1
+ require 'set'
2
+
3
+ module Liquid
4
+
5
+ # Strainer is the parent class for the filters system.
6
+ # New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
7
+ #
8
+ # The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
9
+ # Context#add_filters or Template.register_filter
10
+ class Strainer #:nodoc:
11
+ @@filters = []
12
+ @@known_filters = Set.new
13
+ @@known_methods = Set.new
14
+
15
+ def initialize(context)
16
+ @context = context
17
+ end
18
+
19
+ def self.global_filter(filter)
20
+ raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
21
+ add_known_filter(filter)
22
+ @@filters << filter unless @@filters.include?(filter)
23
+ end
24
+
25
+ def self.add_known_filter(filter)
26
+ unless @@known_filters.include?(filter)
27
+ @@method_blacklist ||= Set.new(Strainer.instance_methods.map(&:to_s))
28
+ new_methods = filter.instance_methods.map(&:to_s)
29
+ new_methods.reject!{ |m| @@method_blacklist.include?(m) }
30
+ @@known_methods.merge(new_methods)
31
+ @@known_filters.add(filter)
32
+ end
33
+ end
34
+
35
+ def self.create(context)
36
+ strainer = Strainer.new(context)
37
+ @@filters.each { |m| strainer.extend(m) }
38
+ strainer
39
+ end
40
+
41
+ def invoke(method, *args)
42
+ if invokable?(method)
43
+ send(method, *args)
44
+ else
45
+ args.first
46
+ end
47
+ end
48
+
49
+ def invokable?(method)
50
+ @@known_methods.include?(method.to_s) && respond_to?(method)
51
+ end
52
+ end
53
+ end
data/lib/liquid/tag.rb ADDED
@@ -0,0 +1,61 @@
1
+ module Liquid
2
+ class Tag
3
+ attr_accessor :nodelist, :options, :line
4
+ attr_reader :warnings
5
+
6
+ def self.new_with_options(tag_name, markup, tokens, options)
7
+ # Forgive me Matz for I have sinned. I know this code is weird
8
+ # but it was necessary to maintain API compatibility.
9
+ new_tag = self.allocate
10
+ new_tag.options = options
11
+ new_tag.send(:initialize, tag_name, markup, tokens, options)
12
+ new_tag
13
+ end
14
+
15
+ def initialize(tag_name, markup, tokens, options)
16
+ @tag_name = tag_name
17
+ @markup = markup
18
+ @options = options || {}
19
+ @line = @options[:line] || 1
20
+ parse(tokens)
21
+ end
22
+
23
+ def parse(tokens)
24
+ end
25
+
26
+ def name
27
+ self.class.name.downcase
28
+ end
29
+
30
+ def render(context)
31
+ ''
32
+ end
33
+
34
+ def blank?
35
+ @blank || true
36
+ end
37
+
38
+ def parse_with_selected_parser(markup)
39
+ case @options[:error_mode] || Template.error_mode
40
+ when :strict then strict_parse_with_error_context(markup)
41
+ when :lax then lax_parse(markup)
42
+ when :warn
43
+ begin
44
+ return strict_parse_with_error_context(markup)
45
+ rescue SyntaxError => e
46
+ @warnings ||= []
47
+ @warnings << e
48
+ return lax_parse(markup)
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+ def strict_parse_with_error_context(markup)
55
+ strict_parse(markup)
56
+ rescue SyntaxError => e
57
+ e.message << " in \"#{markup.strip}\""
58
+ raise e
59
+ end
60
+ end # Tag
61
+ end # Liquid
@@ -0,0 +1,36 @@
1
+ module Liquid
2
+
3
+ # Assign sets a variable in your template.
4
+ #
5
+ # {% assign foo = 'monkey' %}
6
+ #
7
+ # You can then use the variable later in the page.
8
+ #
9
+ # {{ foo }}
10
+ #
11
+ class Assign < Tag
12
+ Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
13
+
14
+
15
+ def initialize(tag_name, markup, tokens, options)
16
+ if markup =~ Syntax
17
+ @to = $1
18
+ @from = Variable.new($2)
19
+ else
20
+ raise SyntaxError.new options[:locale].t("errors.syntax.assign")
21
+ end
22
+
23
+ super
24
+ end
25
+
26
+ def render(context)
27
+ val = @from.render(context)
28
+ context.scopes.last[@to] = val
29
+ context.resource_limits[:assign_score_current] += (val.respond_to?(:length) ? val.length : 1)
30
+ ''
31
+ end
32
+
33
+ end
34
+
35
+ Template.register_tag('assign', Assign)
36
+ end
@@ -0,0 +1,21 @@
1
+ module Liquid
2
+
3
+ # Break tag to be used to break out of a for loop.
4
+ #
5
+ # == Basic Usage:
6
+ # {% for item in collection %}
7
+ # {% if item.condition %}
8
+ # {% break %}
9
+ # {% endif %}
10
+ # {% endfor %}
11
+ #
12
+ class Break < Tag
13
+
14
+ def interrupt
15
+ BreakInterrupt.new
16
+ end
17
+
18
+ end
19
+
20
+ Template.register_tag('break', Break)
21
+ end
@@ -0,0 +1,40 @@
1
+ module Liquid
2
+
3
+ # Capture stores the result of a block into a variable without rendering it inplace.
4
+ #
5
+ # {% capture heading %}
6
+ # Monkeys!
7
+ # {% endcapture %}
8
+ # ...
9
+ # <h1>{{ heading }}</h1>
10
+ #
11
+ # Capture is useful for saving content for use later in your template, such as
12
+ # in a sidebar or footer.
13
+ #
14
+ class Capture < Block
15
+ Syntax = /(\w+)/
16
+
17
+ def initialize(tag_name, markup, tokens, options)
18
+ if markup =~ Syntax
19
+ @to = $1
20
+ else
21
+ raise SyntaxError.new(options[:locale].t("errors.syntax.capture"), options[:line])
22
+ end
23
+
24
+ super
25
+ end
26
+
27
+ def render(context)
28
+ output = super
29
+ context.scopes.last[@to] = output
30
+ context.resource_limits[:assign_score_current] += (output.respond_to?(:length) ? output.length : 1)
31
+ ''
32
+ end
33
+
34
+ def blank?
35
+ true
36
+ end
37
+ end
38
+
39
+ Template.register_tag('capture', Capture)
40
+ end