haml 5.0.4 → 5.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'optparse'
3
4
  require 'rbconfig'
4
5
  require 'pp'
@@ -120,7 +121,7 @@ module Haml
120
121
  @options[:input], @options[:output] = input, output
121
122
  end
122
123
 
123
- COLORS = { :red => 31, :green => 32, :yellow => 33 }
124
+ COLORS = {red: 31, green: 32, yellow: 33}.freeze
124
125
 
125
126
  # Prints a status message about performing the given action,
126
127
  # colored using the given color (via terminal escapes) if possible.
@@ -337,11 +338,9 @@ END
337
338
  end
338
339
 
339
340
  def validate_ruby(code)
340
- begin
341
- eval("BEGIN {return nil}; #{code}", binding, @options[:filename] || "")
342
- rescue ::SyntaxError # Not to be confused with Haml::SyntaxError
343
- $!
344
- end
341
+ eval("BEGIN {return nil}; #{code}", binding, @options[:filename] || "")
342
+ rescue ::SyntaxError # Not to be confused with Haml::SyntaxError
343
+ $!
345
344
  end
346
345
  end
347
346
  end
@@ -1,4 +1,5 @@
1
- # frozen_string_literal: false
1
+ # frozen_string_literal: true
2
+
2
3
  require "tilt"
3
4
 
4
5
  module Haml
@@ -119,7 +120,7 @@ module Haml
119
120
  # @param text [String] The source text for the filter to process
120
121
  # @return [String] The filtered result
121
122
  # @raise [Haml::Error] if it's not overridden
122
- def render(text)
123
+ def render(_text)
123
124
  raise Error.new("#{self.inspect}#render not defined!")
124
125
  end
125
126
 
@@ -130,7 +131,7 @@ module Haml
130
131
  # @param text [String] The source text for the filter to process
131
132
  # @return [String] The filtered result
132
133
  # @raise [Haml::Error] if it or \{#render} isn't overridden
133
- def render_with_options(text, options)
134
+ def render_with_options(text, _options)
134
135
  render(text)
135
136
  end
136
137
 
@@ -164,7 +165,11 @@ module Haml
164
165
  if contains_interpolation?(text)
165
166
  return if options[:suppress_eval]
166
167
 
167
- text = unescape_interpolation(text, options[:escape_html]).gsub(/(\\+)n/) do |s|
168
+ escape = options[:escape_filter_interpolations]
169
+ # `escape_filter_interpolations` defaults to `escape_html` if unset.
170
+ escape = options[:escape_html] if escape.nil?
171
+
172
+ text = unescape_interpolation(text, escape).gsub(/(\\+)n/) do |s|
168
173
  escapes = $1.size
169
174
  next s if escapes % 2 == 0
170
175
  "#{'\\' * (escapes - 1)}\n"
@@ -182,9 +187,8 @@ RUBY
182
187
  return
183
188
  end
184
189
 
185
- rendered = Haml::Helpers::find_and_preserve(filter.render_with_options(text, compiler.options), compiler.options[:preserve])
186
- rendered.rstrip!
187
- push_text("#{rendered}\n")
190
+ rendered = Haml::Helpers::find_and_preserve(filter.render_with_options(text.to_s, compiler.options), compiler.options[:preserve])
191
+ push_text("#{rendered.rstrip}\n")
188
192
  end
189
193
  end
190
194
  end
@@ -247,10 +251,7 @@ RUBY
247
251
 
248
252
  # @see Base#render
249
253
  def render(text)
250
- text = "\n#{text}"
251
- text.rstrip!
252
- text.gsub!("\n", "\n ")
253
- "<![CDATA[#{text}\n]]>"
254
+ "<![CDATA[#{"\n#{text.rstrip}".gsub("\n", "\n ")}\n]]>"
254
255
  end
255
256
  end
256
257
 
@@ -1,4 +1,5 @@
1
- # frozen_string_literal: false
1
+ # frozen_string_literal: true
2
+
2
3
  module Haml
3
4
  # Ruby code generator, which is a limited version of Temple::Generator.
4
5
  # Limit methods since Haml doesn't need most of them.
@@ -1,4 +1,5 @@
1
- # frozen_string_literal: false
1
+ # frozen_string_literal: true
2
+
2
3
  require 'erb'
3
4
 
4
5
  module Haml
@@ -109,10 +110,7 @@ MESSAGE
109
110
  # @yield The block within which to escape newlines
110
111
  def find_and_preserve(input = nil, tags = haml_buffer.options[:preserve], &block)
111
112
  return find_and_preserve(capture_haml(&block), input || tags) if block
112
- tags = tags.each_with_object('') do |t, s|
113
- s << '|' unless s.empty?
114
- s << Regexp.escape(t)
115
- end
113
+ tags = tags.map { |tag| Regexp.escape(tag) }.join('|')
116
114
  re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
117
115
  input.to_s.gsub(re) do |s|
118
116
  s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
@@ -200,8 +198,8 @@ MESSAGE
200
198
  # @yield [item] A block which contains Haml code that goes within list items
201
199
  # @yieldparam item An element of `enum`
202
200
  def list_of(enum, opts={}, &block)
203
- opts_attributes = opts.each_with_object('') {|(k, v), s| s << " #{k}='#{v}'"}
204
- enum.each_with_object('') do |i, ret|
201
+ opts_attributes = opts.map { |k, v| " #{k}='#{v}'" }.join
202
+ enum.map do |i|
205
203
  result = capture_haml(i, &block)
206
204
 
207
205
  if result.count("\n") > 1
@@ -211,9 +209,8 @@ MESSAGE
211
209
  result.strip!
212
210
  end
213
211
 
214
- ret << "\n" unless ret.empty?
215
- ret << %Q!<li#{opts_attributes}>#{result}</li>!
216
- end
212
+ %Q!<li#{opts_attributes}>#{result}</li>!
213
+ end.join("\n")
217
214
  end
218
215
 
219
216
  # Returns a hash containing default assignments for the `xmlns`, `lang`, and `xml:lang`
@@ -596,7 +593,7 @@ MESSAGE
596
593
  end
597
594
 
598
595
  # Characters that need to be escaped to HTML entities from user input
599
- HTML_ESCAPE = { '&' => '&amp;', '<' => '&lt;', '>' => '&gt;', '"' => '&quot;', "'" => '&#39;' }
596
+ HTML_ESCAPE = {'&' => '&amp;', '<' => '&lt;', '>' => '&gt;', '"' => '&quot;', "'" => '&#39;'}.freeze
600
597
 
601
598
  HTML_ESCAPE_REGEX = /['"><&]/
602
599
 
@@ -610,9 +607,12 @@ MESSAGE
610
607
  # @param text [String] The string to sanitize
611
608
  # @return [String] The sanitized string
612
609
  def html_escape(text)
613
- ERB::Util.html_escape(text)
610
+ CGI.escapeHTML(text.to_s)
614
611
  end
615
612
 
613
+ # Always escape text regardless of html_safe?
614
+ alias_method :html_escape_without_haml_xss, :html_escape
615
+
616
616
  HTML_ESCAPE_ONCE_REGEX = /['"><]|&(?!(?:[a-zA-Z]+|#(?:\d+|[xX][0-9a-fA-F]+));)/
617
617
 
618
618
  # Escapes HTML entities in `text`, but without escaping an ampersand
@@ -625,6 +625,9 @@ MESSAGE
625
625
  text.gsub(HTML_ESCAPE_ONCE_REGEX, HTML_ESCAPE)
626
626
  end
627
627
 
628
+ # Always escape text once regardless of html_safe?
629
+ alias_method :escape_once_without_haml_xss, :escape_once
630
+
628
631
  # Returns whether or not the current template is a Haml template.
629
632
  #
630
633
  # This function, unlike other {Haml::Helpers} functions,
@@ -704,4 +707,3 @@ class Object
704
707
  false
705
708
  end
706
709
  end
707
-
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Haml
3
4
  module Helpers
4
5
  @@action_view_defined = true
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Haml
3
4
  module Helpers
4
5
  module ActionViewMods
@@ -52,10 +53,12 @@ module ActionView
52
53
  end
53
54
 
54
55
  module TagHelper
56
+ DEFAULT_PRESERVE_OPTIONS = %w(textarea pre code).freeze
57
+
55
58
  def content_tag_with_haml(name, *args, &block)
56
59
  return content_tag_without_haml(name, *args, &block) unless is_haml?
57
60
 
58
- preserve = haml_buffer.options.fetch(:preserve, %w[textarea pre code]).include?(name.to_s)
61
+ preserve = haml_buffer.options.fetch(:preserve, DEFAULT_PRESERVE_OPTIONS).include?(name.to_s)
59
62
 
60
63
  if block_given? && block_is_haml?(block) && preserve
61
64
  return content_tag_without_haml(name, *args) do
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ActionView
3
4
  module Helpers
4
5
  module CaptureHelper
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'action_view'
3
4
 
4
5
  module Haml
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'action_view'
3
4
 
4
5
  module Haml
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Haml
3
4
  module Helpers
4
5
  # This module overrides Haml helpers to work properly
@@ -7,12 +8,15 @@ module Haml
7
8
  # to work with Rails' XSS protection methods.
8
9
  module XssMods
9
10
  def self.included(base)
10
- %w[html_escape find_and_preserve preserve list_of surround
11
- precede succeed capture_haml haml_concat haml_internal_concat haml_indent
12
- escape_once].each do |name|
11
+ %w[find_and_preserve preserve list_of surround
12
+ precede succeed capture_haml haml_concat haml_internal_concat haml_indent].each do |name|
13
13
  base.send(:alias_method, "#{name}_without_haml_xss", name)
14
14
  base.send(:alias_method, name, "#{name}_with_haml_xss")
15
15
  end
16
+ # Those two always have _without_haml_xss
17
+ %w[html_escape escape_once].each do |name|
18
+ base.send(:alias_method, name, "#{name}_with_haml_xss")
19
+ end
16
20
  end
17
21
 
18
22
  # Don't escape text that's already safe,
@@ -1,47 +1,44 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Haml
3
4
  # This class encapsulates all of the configuration options that Haml
4
5
  # understands. Please see the {file:REFERENCE.md#options Haml Reference} to
5
6
  # learn how to set the options.
6
7
  class Options
7
-
8
8
  @valid_formats = [:html4, :html5, :xhtml]
9
-
10
9
  @buffer_option_keys = [:autoclose, :preserve, :attr_wrapper, :format,
11
- :encoding, :escape_html, :escape_attrs, :hyphenate_data_attrs, :cdata]
10
+ :encoding, :escape_html, :escape_filter_interpolations, :escape_attrs, :hyphenate_data_attrs, :cdata]
12
11
 
13
- # The default option values.
14
- # @return Hash
15
- def self.defaults
16
- @defaults ||= Haml::TempleEngine.options.to_hash.merge(encoding: 'UTF-8')
17
- end
12
+ class << self
13
+ # The default option values.
14
+ # @return Hash
15
+ def defaults
16
+ @defaults ||= Haml::TempleEngine.options.to_hash.merge(encoding: 'UTF-8')
17
+ end
18
18
 
19
- # An array of valid values for the `:format` option.
20
- # @return Array
21
- def self.valid_formats
22
- @valid_formats
23
- end
19
+ # An array of valid values for the `:format` option.
20
+ # @return Array
21
+ attr_reader :valid_formats
24
22
 
25
- # An array of keys that will be used to provide a hash of options to
26
- # {Haml::Buffer}.
27
- # @return Hash
28
- def self.buffer_option_keys
29
- @buffer_option_keys
30
- end
23
+ # An array of keys that will be used to provide a hash of options to
24
+ # {Haml::Buffer}.
25
+ # @return Hash
26
+ attr_reader :buffer_option_keys
31
27
 
32
- # Returns a subset of defaults: those that {Haml::Buffer} cares about.
33
- # @return [{Symbol => Object}] The options hash
34
- def self.buffer_defaults
35
- @buffer_defaults ||= buffer_option_keys.inject({}) do |hash, key|
36
- hash.merge(key => defaults[key])
28
+ # Returns a subset of defaults: those that {Haml::Buffer} cares about.
29
+ # @return [{Symbol => Object}] The options hash
30
+ def buffer_defaults
31
+ @buffer_defaults ||= buffer_option_keys.inject({}) do |hash, key|
32
+ hash.merge(key => defaults[key])
33
+ end
37
34
  end
38
- end
39
35
 
40
- def self.wrap(options)
41
- if options.is_a?(Options)
42
- options
43
- else
44
- Options.new(options)
36
+ def wrap(options)
37
+ if options.is_a?(Options)
38
+ options
39
+ else
40
+ Options.new(options)
41
+ end
45
42
  end
46
43
  end
47
44
 
@@ -85,6 +82,13 @@ module Haml
85
82
  # Defaults to false.
86
83
  attr_accessor :escape_html
87
84
 
85
+ # Sets whether or not to escape HTML-sensitive characters in interpolated strings.
86
+ # See also {file:REFERENCE.md#escaping_html Escaping HTML} and
87
+ # {file:REFERENCE.md#unescaping_html Unescaping HTML}.
88
+ #
89
+ # Defaults to the current value of `escape_html`.
90
+ attr_accessor :escape_filter_interpolations
91
+
88
92
  # The name of the Haml file being parsed.
89
93
  # This is only used as information when exceptions are raised. This is
90
94
  # automatically assigned when working through ActionView, so it's really
@@ -131,7 +135,7 @@ module Haml
131
135
  # formatting errors.
132
136
  #
133
137
  # Defaults to `false`.
134
- attr_reader :remove_whitespace
138
+ attr_accessor :remove_whitespace
135
139
 
136
140
  # Whether or not attribute hashes and Ruby scripts designated by `=` or `~`
137
141
  # should be evaluated. If this is `true`, said scripts are rendered as empty
@@ -167,7 +171,7 @@ module Haml
167
171
  # Key is filter name in String and value is Class to use. Defaults to {}.
168
172
  attr_accessor :filters
169
173
 
170
- def initialize(values = {}, &block)
174
+ def initialize(values = {})
171
175
  defaults.each {|k, v| instance_variable_set :"@#{k}", v}
172
176
  values.each {|k, v| send("#{k}=", v) if defaults.has_key?(k) && !v.nil?}
173
177
  yield if block_given?
@@ -237,10 +241,6 @@ module Haml
237
241
  xhtml? || @cdata
238
242
  end
239
243
 
240
- def remove_whitespace=(value)
241
- @remove_whitespace = value
242
- end
243
-
244
244
  def encoding=(value)
245
245
  return unless value
246
246
  @encoding = value.is_a?(Encoding) ? value.name : value.to_s
@@ -1,4 +1,6 @@
1
- # frozen_string_literal: false
1
+ # frozen_string_literal: true
2
+
3
+ require 'ripper'
2
4
  require 'strscan'
3
5
 
4
6
  module Haml
@@ -60,7 +62,7 @@ module Haml
60
62
  SILENT_SCRIPT,
61
63
  ESCAPE,
62
64
  FILTER
63
- ]
65
+ ].freeze
64
66
 
65
67
  # The value of the character that designates that a line is part
66
68
  # of a multiline string.
@@ -74,8 +76,8 @@ module Haml
74
76
  #
75
77
  BLOCK_WITH_SPACES = /do\s*\|\s*[^\|]*\s+\|\z/
76
78
 
77
- MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when]
78
- START_BLOCK_KEYWORDS = %w[if begin case unless]
79
+ MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when].freeze
80
+ START_BLOCK_KEYWORDS = %w[if begin case unless].freeze
79
81
  # Try to parse assignments to block starters as best as possible
80
82
  START_BLOCK_KEYWORD_REGEX = /(?:\w+(?:,\s*\w+)*\s*=\s*)?(#{START_BLOCK_KEYWORDS.join('|')})/
81
83
  BLOCK_KEYWORD_REGEX = /^-?\s*(?:(#{MID_BLOCK_KEYWORDS.join('|')})|#{START_BLOCK_KEYWORD_REGEX.source})\b/
@@ -89,6 +91,9 @@ module Haml
89
91
  ID_KEY = 'id'.freeze
90
92
  CLASS_KEY = 'class'.freeze
91
93
 
94
+ # Used for scanning old attributes, substituting the first '{'
95
+ METHOD_CALL_PREFIX = 'a('
96
+
92
97
  def initialize(options)
93
98
  @options = Options.wrap(options)
94
99
  # Record the indent levels of "if" statements to validate the subsequent
@@ -178,7 +183,7 @@ module Haml
178
183
  private
179
184
 
180
185
  # @private
181
- class Line < Struct.new(:whitespace, :text, :full, :index, :parser, :eod)
186
+ Line = Struct.new(:whitespace, :text, :full, :index, :parser, :eod) do
182
187
  alias_method :eod?, :eod
183
188
 
184
189
  # @private
@@ -194,25 +199,26 @@ module Haml
194
199
  end
195
200
 
196
201
  # @private
197
- class ParseNode < Struct.new(:type, :line, :value, :parent, :children)
202
+ ParseNode = Struct.new(:type, :line, :value, :parent, :children) do
198
203
  def initialize(*args)
199
204
  super
200
205
  self.children ||= []
201
206
  end
202
207
 
203
208
  def inspect
204
- %Q[(#{type} #{value.inspect}#{children.each_with_object('') {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})]
209
+ %Q[(#{type} #{value.inspect}#{children.each_with_object(''.dup) {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})].dup
205
210
  end
206
211
  end
207
212
 
208
213
  # @param [String] new - Hash literal including dynamic values.
209
214
  # @param [String] old - Hash literal including dynamic values or Ruby literal of multiple Hashes which MUST be interpreted as method's last arguments.
210
- class DynamicAttributes < Struct.new(:new, :old)
215
+ DynamicAttributes = Struct.new(:new, :old) do
216
+ undef :old=
211
217
  def old=(value)
212
218
  unless value =~ /\A{.*}\z/m
213
219
  raise ArgumentError.new('Old attributes must start with "{" and end with "}"')
214
220
  end
215
- super
221
+ self[:old] = value
216
222
  end
217
223
 
218
224
  # This will be a literal for Haml::Buffer#attributes's last argument, `attributes_hashes`.
@@ -287,7 +293,7 @@ module Haml
287
293
  end
288
294
 
289
295
  def block_keyword(text)
290
- return unless keyword = text.scan(BLOCK_KEYWORD_REGEX)[0]
296
+ return unless (keyword = text.scan(BLOCK_KEYWORD_REGEX)[0])
291
297
  keyword[0] || keyword[1]
292
298
  end
293
299
 
@@ -305,7 +311,7 @@ module Haml
305
311
  return ParseNode.new(:plain, line.index + 1, :text => line.text)
306
312
  end
307
313
 
308
- escape_html = @options.escape_html if escape_html.nil?
314
+ escape_html = @options.escape_html && @options.mime_type != 'text/plain' if escape_html.nil?
309
315
  line.text = unescape_interpolation(line.text, escape_html)
310
316
  script(line, false)
311
317
  end
@@ -534,7 +540,7 @@ module Haml
534
540
 
535
541
  # Post-process case statements to normalize the nesting of "when" clauses
536
542
  return unless node.value[:keyword] == "case"
537
- return unless first = node.children.first
543
+ return unless (first = node.children.first)
538
544
  return unless first.type == :silent_script && first.value[:keyword] == "when"
539
545
  return if first.children.empty?
540
546
  # If the case node has a "when" child with children, it's the
@@ -583,9 +589,9 @@ module Haml
583
589
  scanner = StringScanner.new(text)
584
590
  scanner.scan(/\s+/)
585
591
  until scanner.eos?
586
- return unless key = scanner.scan(LITERAL_VALUE_REGEX)
592
+ return unless (key = scanner.scan(LITERAL_VALUE_REGEX))
587
593
  return unless scanner.scan(/\s*=>\s*/)
588
- return unless value = scanner.scan(LITERAL_VALUE_REGEX)
594
+ return unless (value = scanner.scan(LITERAL_VALUE_REGEX))
589
595
  return unless scanner.scan(/\s*(?:,|$)\s*/)
590
596
  attributes[eval(key).to_s] = eval(value).to_s
591
597
  end
@@ -649,13 +655,18 @@ module Haml
649
655
  # @return [String] rest
650
656
  # @return [Integer] last_line
651
657
  def parse_old_attributes(text)
652
- text = text.dup
653
658
  last_line = @line.index + 1
654
659
 
655
660
  begin
656
- attributes_hash, rest = balance(text, ?{, ?})
661
+ # Old attributes often look like a valid Hash literal, but it sometimes allow code like
662
+ # `{ hash, foo: bar }`, which is compiled to `_hamlout.attributes({}, nil, hash, foo: bar)`.
663
+ #
664
+ # To scan such code correctly, this scans `a( hash, foo: bar }` instead, stops when there is
665
+ # 1 more :on_embexpr_end (the last '}') than :on_embexpr_beg, and resurrects '{' afterwards.
666
+ balanced, rest = balance_tokens(text.sub(?{, METHOD_CALL_PREFIX), :on_embexpr_beg, :on_embexpr_end, count: 1)
667
+ attributes_hash = balanced.sub(METHOD_CALL_PREFIX, ?{)
657
668
  rescue SyntaxError => e
658
- if text.strip[-1] == ?, && e.message == Error.message(:unbalanced_brackets)
669
+ if e.message == Error.message(:unbalanced_brackets) && !@template.empty?
659
670
  text << "\n#{@next_line.text}"
660
671
  last_line += 1
661
672
  next_line
@@ -698,7 +709,7 @@ module Haml
698
709
  end
699
710
 
700
711
  static_attributes = {}
701
- dynamic_attributes = "{"
712
+ dynamic_attributes = "{".dup
702
713
  attributes.each do |name, (type, val)|
703
714
  if type == :static
704
715
  static_attributes[name] = val
@@ -713,7 +724,7 @@ module Haml
713
724
  end
714
725
 
715
726
  def parse_new_attribute(scanner)
716
- unless name = scanner.scan(/[-:\w]+/)
727
+ unless (name = scanner.scan(/[-:\w]+/))
717
728
  return if scanner.scan(/\)/)
718
729
  return false
719
730
  end
@@ -722,8 +733,8 @@ module Haml
722
733
  return name, [:static, true] unless scanner.scan(/=/) #/end
723
734
 
724
735
  scanner.scan(/\s*/)
725
- unless quote = scanner.scan(/["']/)
726
- return false unless var = scanner.scan(/(@@?|\$)?\w+/)
736
+ unless (quote = scanner.scan(/["']/))
737
+ return false unless (var = scanner.scan(/(@@?|\$)?\w+/))
727
738
  return name, [:dynamic, var]
728
739
  end
729
740
 
@@ -738,7 +749,7 @@ module Haml
738
749
 
739
750
  return name, [:static, content.first[1]] if content.size == 1
740
751
  return name, [:dynamic,
741
- %!"#{content.each_with_object('') {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
752
+ %!"#{content.each_with_object(''.dup) {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
742
753
  end
743
754
 
744
755
  def next_line
@@ -809,6 +820,25 @@ module Haml
809
820
  Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets)))
810
821
  end
811
822
 
823
+ # Unlike #balance, this balances Ripper tokens to balance something like `{ a: "}" }` correctly.
824
+ def balance_tokens(buf, start, finish, count: 0)
825
+ text = ''.dup
826
+ Ripper.lex(buf).each do |_, token, str|
827
+ text << str
828
+ case token
829
+ when start
830
+ count += 1
831
+ when finish
832
+ count -= 1
833
+ end
834
+
835
+ if count == 0
836
+ return text, buf.sub(text, '')
837
+ end
838
+ end
839
+ raise SyntaxError.new(Error.message(:unbalanced_brackets))
840
+ end
841
+
812
842
  def block_opened?
813
843
  @next_line.tabs > @line.tabs
814
844
  end