haml 5.0.4 → 6.0.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +40 -0
  4. data/.gitignore +16 -15
  5. data/CHANGELOG.md +62 -1
  6. data/Gemfile +18 -14
  7. data/MIT-LICENSE +2 -2
  8. data/README.md +4 -5
  9. data/REFERENCE.md +46 -12
  10. data/Rakefile +93 -103
  11. data/bin/bench +66 -0
  12. data/bin/console +11 -0
  13. data/bin/ruby +3 -0
  14. data/bin/setup +7 -0
  15. data/bin/stackprof +27 -0
  16. data/bin/test +24 -0
  17. data/exe/haml +6 -0
  18. data/ext/haml/extconf.rb +10 -0
  19. data/ext/haml/haml.c +537 -0
  20. data/ext/haml/hescape.c +108 -0
  21. data/ext/haml/hescape.h +20 -0
  22. data/haml.gemspec +39 -30
  23. data/lib/haml/ambles.rb +20 -0
  24. data/lib/haml/attribute_builder.rb +140 -128
  25. data/lib/haml/attribute_compiler.rb +86 -181
  26. data/lib/haml/attribute_parser.rb +86 -124
  27. data/lib/haml/cli.rb +154 -0
  28. data/lib/haml/compiler/children_compiler.rb +126 -0
  29. data/lib/haml/compiler/comment_compiler.rb +39 -0
  30. data/lib/haml/compiler/doctype_compiler.rb +46 -0
  31. data/lib/haml/compiler/script_compiler.rb +116 -0
  32. data/lib/haml/compiler/silent_script_compiler.rb +24 -0
  33. data/lib/haml/compiler/tag_compiler.rb +76 -0
  34. data/lib/haml/compiler.rb +64 -298
  35. data/lib/haml/dynamic_merger.rb +67 -0
  36. data/lib/haml/engine.rb +43 -219
  37. data/lib/haml/error.rb +29 -27
  38. data/lib/haml/escapable.rb +6 -42
  39. data/lib/haml/filters/base.rb +12 -0
  40. data/lib/haml/filters/cdata.rb +20 -0
  41. data/lib/haml/filters/coffee.rb +17 -0
  42. data/lib/haml/filters/css.rb +33 -0
  43. data/lib/haml/filters/erb.rb +10 -0
  44. data/lib/haml/filters/escaped.rb +22 -0
  45. data/lib/haml/filters/javascript.rb +33 -0
  46. data/lib/haml/filters/less.rb +20 -0
  47. data/lib/haml/filters/markdown.rb +11 -0
  48. data/lib/haml/filters/plain.rb +29 -0
  49. data/lib/haml/filters/preserve.rb +22 -0
  50. data/lib/haml/filters/ruby.rb +10 -0
  51. data/lib/haml/filters/sass.rb +15 -0
  52. data/lib/haml/filters/scss.rb +15 -0
  53. data/lib/haml/filters/text_base.rb +25 -0
  54. data/lib/haml/filters/tilt_base.rb +49 -0
  55. data/lib/haml/filters.rb +55 -378
  56. data/lib/haml/force_escapable.rb +29 -0
  57. data/lib/haml/helpers.rb +4 -696
  58. data/lib/haml/html.rb +22 -0
  59. data/lib/haml/identity.rb +13 -0
  60. data/lib/haml/object_ref.rb +30 -0
  61. data/lib/haml/parser.rb +208 -43
  62. data/lib/haml/rails_helpers.rb +51 -0
  63. data/lib/haml/rails_template.rb +55 -0
  64. data/lib/haml/railtie.rb +7 -40
  65. data/lib/haml/ruby_expression.rb +32 -0
  66. data/lib/haml/string_splitter.rb +20 -0
  67. data/lib/haml/template.rb +15 -33
  68. data/lib/haml/temple_line_counter.rb +2 -0
  69. data/lib/haml/util.rb +23 -21
  70. data/lib/haml/version.rb +1 -1
  71. data/lib/haml.rb +8 -19
  72. metadata +222 -50
  73. data/.gitmodules +0 -3
  74. data/.travis.yml +0 -54
  75. data/.yardopts +0 -23
  76. data/TODO +0 -24
  77. data/benchmark.rb +0 -66
  78. data/bin/haml +0 -9
  79. data/lib/haml/.gitattributes +0 -1
  80. data/lib/haml/buffer.rb +0 -235
  81. data/lib/haml/exec.rb +0 -348
  82. data/lib/haml/generator.rb +0 -41
  83. data/lib/haml/helpers/action_view_extensions.rb +0 -59
  84. data/lib/haml/helpers/action_view_mods.rb +0 -129
  85. data/lib/haml/helpers/action_view_xss_mods.rb +0 -59
  86. data/lib/haml/helpers/safe_erubi_template.rb +0 -19
  87. data/lib/haml/helpers/safe_erubis_template.rb +0 -32
  88. data/lib/haml/helpers/xss_mods.rb +0 -110
  89. data/lib/haml/options.rb +0 -273
  90. data/lib/haml/plugin.rb +0 -34
  91. data/lib/haml/sass_rails_filter.rb +0 -46
  92. data/lib/haml/template/options.rb +0 -26
  93. data/lib/haml/temple_engine.rb +0 -121
  94. data/yard/default/.gitignore +0 -1
  95. data/yard/default/fulldoc/html/css/common.sass +0 -15
  96. data/yard/default/layout/html/footer.erb +0 -12
@@ -1,223 +1,128 @@
1
1
  # frozen_string_literal: true
2
+ require 'haml/attribute_builder'
2
3
  require 'haml/attribute_parser'
4
+ require 'haml/ruby_expression'
3
5
 
4
6
  module Haml
5
7
  class AttributeCompiler
6
- # @param type [Symbol] :static or :dynamic
7
- # @param key [String]
8
- # @param value [String] Actual string value for :static type, value's Ruby literal for :dynamic type.
9
- class AttributeValue < Struct.new(:type, :key, :value)
10
- # @return [String] A Ruby literal of value.
11
- def to_literal
12
- case type
13
- when :static
14
- Haml::Util.inspect_obj(value)
15
- when :dynamic
16
- value
17
- end
18
- end
19
- end
20
-
21
- # Returns a script to render attributes on runtime.
22
- #
23
- # @param attributes [Hash]
24
- # @param object_ref [String,:nil]
25
- # @param dynamic_attributes [DynamicAttributes]
26
- # @return [String] Attributes rendering code
27
- def self.runtime_build(attributes, object_ref, dynamic_attributes)
28
- "_hamlout.attributes(#{Haml::Util.inspect_obj(attributes)}, #{object_ref},#{dynamic_attributes.to_literal})"
29
- end
30
-
31
- # @param options [Haml::Options]
32
- def initialize(options)
33
- @is_html = [:html4, :html5].include?(options[:format])
34
- @attr_wrapper = options[:attr_wrapper]
8
+ def initialize(identity, options)
9
+ @identity = identity
10
+ @quote = options[:attr_quote]
11
+ @format = options[:format]
35
12
  @escape_attrs = options[:escape_attrs]
36
- @hyphenate_data_attrs = options[:hyphenate_data_attrs]
37
13
  end
38
14
 
39
- # Returns Temple expression to render attributes.
40
- #
41
- # @param attributes [Hash]
42
- # @param object_ref [String,:nil]
43
- # @param dynamic_attributes [DynamicAttributes]
44
- # @return [Array] Temple expression
45
- def compile(attributes, object_ref, dynamic_attributes)
46
- if object_ref != :nil || !AttributeParser.available?
47
- return [:dynamic, AttributeCompiler.runtime_build(attributes, object_ref, dynamic_attributes)]
15
+ def compile(node)
16
+ hashes = []
17
+ if node.value[:object_ref] != :nil || !Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
18
+ return runtime_compile(node)
48
19
  end
49
-
50
- parsed_hashes = [dynamic_attributes.new, dynamic_attributes.old].compact.map do |attribute_hash|
51
- unless (hash = AttributeParser.parse(attribute_hash))
52
- return [:dynamic, AttributeCompiler.runtime_build(attributes, object_ref, dynamic_attributes)]
53
- end
54
- hash
20
+ [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
21
+ hash = AttributeParser.parse(attribute_str)
22
+ return runtime_compile(node) unless hash
23
+ hashes << hash
55
24
  end
56
- attribute_values = build_attribute_values(attributes, parsed_hashes)
57
- AttributeBuilder.verify_attribute_names!(attribute_values.map(&:key))
58
-
59
- [:multi, *group_values_for_sort(attribute_values).map { |value_group|
60
- compile_attribute_values(value_group)
61
- }]
25
+ static_compile(node.value[:attributes], hashes)
62
26
  end
63
27
 
64
28
  private
65
29
 
66
- # Build array of grouped values whose sort order may go back and forth, which is also sorted with key name.
67
- # This method needs to group values with the same start because it can be changed in `Haml::AttributeBuidler#build_data_keys`.
68
- # @param values [Array<Haml::AttributeCompiler::AttributeValue>]
69
- # @return [Array<Array<Haml::AttributeCompiler::AttributeValue>>]
70
- def group_values_for_sort(values)
71
- sorted_values = values.sort_by(&:key)
72
- [].tap do |value_groups|
73
- until sorted_values.empty?
74
- key = sorted_values.first.key
75
- value_group, sorted_values = sorted_values.partition { |v| v.key.start_with?(key) }
76
- value_groups << value_group
30
+ def runtime_compile(node)
31
+ attrs = []
32
+ attrs.unshift(node.value[:attributes].inspect) if node.value[:attributes] != {}
33
+
34
+ args = [
35
+ @escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect,
36
+ '::Haml::AttributeBuilder::BOOLEAN_ATTRIBUTES', node.value[:object_ref],
37
+ ] + attrs
38
+ [:html, :attrs, [:dynamic, "::Haml::AttributeBuilder.build(#{args.join(', ')}, #{node.value[:dynamic_attributes].to_literal})"]]
39
+ end
40
+
41
+ def static_compile(static_hash, dynamic_hashes)
42
+ temple = [:html, :attrs]
43
+ keys = [*static_hash.keys, *dynamic_hashes.map(&:keys).flatten].uniq.sort
44
+ keys.each do |key|
45
+ values = [[:static, static_hash[key]], *dynamic_hashes.map { |h| [:dynamic, h[key]] }]
46
+ values.select! { |_, exp| exp != nil }
47
+
48
+ case key
49
+ when 'id'
50
+ compile_id!(temple, key, values)
51
+ when 'class'
52
+ compile_class!(temple, key, values)
53
+ when 'data', 'aria'
54
+ compile_data!(temple, key, values)
55
+ when *AttributeBuilder::BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
56
+ compile_boolean!(temple, key, values)
57
+ else
58
+ compile_common!(temple, key, values)
77
59
  end
78
60
  end
61
+ temple
79
62
  end
80
63
 
81
- # Returns array of AttributeValue instances from static attributes and dynamic_attributes. For each key,
82
- # the values' order in returned value is preserved in the same order as Haml::Buffer#attributes's merge order.
83
- #
84
- # @param attributes [{ String => String }]
85
- # @param parsed_hashes [{ String => String }]
86
- # @return [Array<AttributeValue>]
87
- def build_attribute_values(attributes, parsed_hashes)
88
- [].tap do |attribute_values|
89
- attributes.each do |key, static_value|
90
- attribute_values << AttributeValue.new(:static, key, static_value)
91
- end
92
- parsed_hashes.each do |parsed_hash|
93
- parsed_hash.each do |key, dynamic_value|
94
- attribute_values << AttributeValue.new(:dynamic, key, dynamic_value)
95
- end
96
- end
64
+ def compile_id!(temple, key, values)
65
+ build_code = attribute_builder(:id, values)
66
+ if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
67
+ temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
68
+ else
69
+ temple << [:html, :attr, key, [:dynamic, build_code]]
97
70
  end
98
71
  end
99
72
 
100
- # Compiles attribute values with the similar key to Temple expression.
101
- #
102
- # @param values [Array<AttributeValue>] whose `key`s are partially or fully the same from left.
103
- # @return [Array] Temple expression
104
- def compile_attribute_values(values)
105
- if values.map(&:key).uniq.size == 1
106
- compile_attribute(values.first.key, values)
73
+ def compile_class!(temple, key, values)
74
+ build_code = attribute_builder(:class, values)
75
+ if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
76
+ temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
107
77
  else
108
- runtime_build(values)
78
+ temple << [:html, :attr, key, [:dynamic, build_code]]
109
79
  end
110
80
  end
111
81
 
112
- # @param values [Array<AttributeValue>]
113
- # @return [Array] Temple expression
114
- def runtime_build(values)
115
- hash_content = values.group_by(&:key).map do |key, values_for_key|
116
- "#{frozen_string(key)} => #{merged_value(key, values_for_key)}"
117
- end.join(', ')
118
- [:dynamic, "_hamlout.attributes({ #{hash_content} }, nil)"]
119
- end
120
-
121
- # Renders attribute values statically.
122
- #
123
- # @param values [Array<AttributeValue>]
124
- # @return [Array] Temple expression
125
- def static_build(values)
126
- hash_content = values.group_by(&:key).map do |key, values_for_key|
127
- "#{frozen_string(key)} => #{merged_value(key, values_for_key)}"
128
- end.join(', ')
129
-
130
- arguments = [@is_html, @attr_wrapper, @escape_attrs, @hyphenate_data_attrs]
131
- code = "::Haml::AttributeBuilder.build_attributes"\
132
- "(#{arguments.map { |a| Haml::Util.inspect_obj(a) }.join(', ')}, { #{hash_content} })"
133
- [:static, eval(code).to_s]
134
- end
82
+ def compile_data!(temple, key, values)
83
+ args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", values.map { |v| literal_for(v) }]
84
+ build_code = "::Haml::AttributeBuilder.build_#{key}(#{args.join(', ')})"
135
85
 
136
- # @param key [String]
137
- # @param values [Array<AttributeValue>]
138
- # @return [String]
139
- def merged_value(key, values)
140
- if values.size == 1
141
- values.first.to_literal
86
+ if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
87
+ temple << [:static, eval(build_code).to_s]
142
88
  else
143
- "::Haml::AttributeBuilder.merge_values(#{frozen_string(key)}, #{values.map(&:to_literal).join(', ')})"
89
+ temple << [:dynamic, build_code]
144
90
  end
145
91
  end
146
92
 
147
- # @param str [String]
148
- # @return [String]
149
- def frozen_string(str)
150
- "#{Haml::Util.inspect_obj(str)}.freeze"
151
- end
93
+ def compile_boolean!(temple, key, values)
94
+ exp = literal_for(values.last)
152
95
 
153
- # Compiles attribute values for one key to Temple expression that generates ` key='value'`.
154
- #
155
- # @param key [String]
156
- # @param values [Array<AttributeValue>]
157
- # @return [Array] Temple expression
158
- def compile_attribute(key, values)
159
- if values.all? { |v| Temple::StaticAnalyzer.static?(v.to_literal) }
160
- return static_build(values)
161
- end
162
-
163
- case key
164
- when 'id', 'class'
165
- compile_id_or_class_attribute(key, values)
96
+ if Temple::StaticAnalyzer.static?(exp)
97
+ value = eval(exp)
98
+ case value
99
+ when true then temple << [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]
100
+ when false, nil
101
+ else temple << [:html, :attr, key, [:fescape, @escape_attrs, [:static, value.to_s]]]
102
+ end
166
103
  else
167
- compile_common_attribute(key, values)
168
- end
169
- end
170
-
171
- # @param id_or_class [String] "id" or "class"
172
- # @param values [Array<AttributeValue>]
173
- # @return [Array] Temple expression
174
- def compile_id_or_class_attribute(id_or_class, values)
175
- var = unique_name
176
- [:multi,
177
- [:code, "#{var} = (#{merged_value(id_or_class, values)})"],
178
- [:case, var,
179
- ['Hash, Array', runtime_build([AttributeValue.new(:dynamic, id_or_class, var)])],
180
- ['false, nil', [:multi]],
181
- [:else, [:multi,
182
- [:static, " #{id_or_class}=#{@attr_wrapper}"],
183
- [:escape, @escape_attrs, [:dynamic, var]],
184
- [:static, @attr_wrapper]],
104
+ var = @identity.generate
105
+ temple << [
106
+ :case, "(#{var} = (#{exp}))",
107
+ ['true', [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]],
108
+ ['false, nil', [:multi]],
109
+ [:else, [:multi, [:static, " #{key}=#{@quote}"], [:fescape, @escape_attrs, [:dynamic, var]], [:static, @quote]]],
185
110
  ]
186
- ],
187
- ]
111
+ end
188
112
  end
189
113
 
190
- # @param key [String] Not "id" or "class"
191
- # @param values [Array<AttributeValue>]
192
- # @return [Array] Temple expression
193
- def compile_common_attribute(key, values)
194
- var = unique_name
195
- [:multi,
196
- [:code, "#{var} = (#{merged_value(key, values)})"],
197
- [:case, var,
198
- ['Hash', runtime_build([AttributeValue.new(:dynamic, key, var)])],
199
- ['true', true_value(key)],
200
- ['false, nil', [:multi]],
201
- [:else, [:multi,
202
- [:static, " #{key}=#{@attr_wrapper}"],
203
- [:escape, @escape_attrs, [:dynamic, var]],
204
- [:static, @attr_wrapper]],
205
- ]
206
- ],
207
- ]
114
+ def compile_common!(temple, key, values)
115
+ temple << [:html, :attr, key, [:fescape, @escape_attrs, values.last]]
208
116
  end
209
117
 
210
- def true_value(key)
211
- if @is_html
212
- [:static, " #{key}"]
213
- else
214
- [:static, " #{key}=#{@attr_wrapper}#{key}#{@attr_wrapper}"]
215
- end
118
+ def attribute_builder(type, values)
119
+ args = [@escape_attrs.inspect, *values.map { |v| literal_for(v) }]
120
+ "::Haml::AttributeBuilder.build_#{type}(#{args.join(', ')})"
216
121
  end
217
122
 
218
- def unique_name
219
- @unique_name ||= 0
220
- "_haml_attribute_compiler#{@unique_name += 1}"
123
+ def literal_for(value)
124
+ type, exp = value
125
+ type == :static ? "#{exp.inspect}.freeze" : exp
221
126
  end
222
127
  end
223
128
  end
@@ -1,148 +1,110 @@
1
1
  # frozen_string_literal: true
2
- begin
3
- require 'ripper'
4
- rescue LoadError
5
- end
2
+ require 'haml/ruby_expression'
6
3
 
7
4
  module Haml
8
- # Haml::AttriubuteParser parses Hash literal to { String (key name) => String (value literal) }.
9
- module AttributeParser
10
- class UnexpectedTokenError < StandardError; end
11
- class UnexpectedKeyError < StandardError; end
5
+ class AttributeParser
6
+ class ParseSkip < StandardError
7
+ end
12
8
 
13
- # Indices in Ripper tokens
14
- TYPE = 1
15
- TEXT = 2
9
+ def self.parse(text)
10
+ self.new.parse(text)
11
+ end
16
12
 
17
- IGNORED_TYPES = %i[on_sp on_ignored_nl]
13
+ def parse(text)
14
+ exp = wrap_bracket(text)
15
+ return if RubyExpression.syntax_error?(exp)
18
16
 
19
- class << self
20
- # @return [Boolean] - return true if AttributeParser.parse can be used.
21
- def available?
22
- defined?(Ripper) && Temple::StaticAnalyzer.available?
17
+ hash = {}
18
+ tokens = Ripper.lex(exp)[1..-2] || []
19
+ each_attr(tokens) do |attr_tokens|
20
+ key = parse_key!(attr_tokens)
21
+ hash[key] = attr_tokens.map { |t| t[2] }.join.strip
23
22
  end
23
+ hash
24
+ rescue ParseSkip
25
+ nil
26
+ end
24
27
 
25
- # @param [String] exp - Old attributes literal or Hash literal generated from new attributes.
26
- # @return [Hash<String, String>,nil] - Return parsed attribute Hash whose values are Ruby literals, or return nil if argument is not a single Hash literal.
27
- def parse(exp)
28
- return nil unless hash_literal?(exp)
28
+ private
29
29
 
30
- hash = {}
31
- each_attribute(exp) do |key, value|
32
- hash[key] = value
30
+ def wrap_bracket(text)
31
+ text = text.strip
32
+ return text if text[0] == '{'
33
+ "{#{text}}"
34
+ end
35
+
36
+ def parse_key!(tokens)
37
+ _, type, str = tokens.shift
38
+ case type
39
+ when :on_sp
40
+ parse_key!(tokens)
41
+ when :on_label
42
+ str.tr(':', '')
43
+ when :on_symbeg
44
+ _, _, key = tokens.shift
45
+ assert_type!(tokens.shift, :on_tstring_end) if str != ':'
46
+ skip_until_hash_rocket!(tokens)
47
+ key
48
+ when :on_tstring_beg
49
+ _, _, key = tokens.shift
50
+ next_token = tokens.shift
51
+ unless next_token[1] == :on_label_end
52
+ assert_type!(next_token, :on_tstring_end)
53
+ skip_until_hash_rocket!(tokens)
33
54
  end
34
- hash
35
- rescue UnexpectedTokenError, UnexpectedKeyError
36
- nil
55
+ key
56
+ else
57
+ raise ParseSkip
37
58
  end
59
+ end
38
60
 
39
- private
61
+ def assert_type!(token, type)
62
+ raise ParseSkip if token[1] != type
63
+ end
40
64
 
41
- # @param [String] exp - Ruby expression
42
- # @return [Boolean] - Return true if exp is a single Hash literal
43
- def hash_literal?(exp)
44
- return false if Temple::StaticAnalyzer.syntax_error?(exp)
45
- sym, body = Ripper.sexp(exp)
46
- sym == :program && body.is_a?(Array) && body.size == 1 && body[0] && body[0][0] == :hash
65
+ def skip_until_hash_rocket!(tokens)
66
+ until tokens.empty?
67
+ _, type, str = tokens.shift
68
+ break if type == :on_op && str == '=>'
47
69
  end
70
+ end
48
71
 
49
- # @param [Array] tokens - Ripper tokens. Scanned tokens will be destructively removed from this argument.
50
- # @return [String] - attribute name in String
51
- def shift_key!(tokens)
52
- while !tokens.empty? && IGNORED_TYPES.include?(tokens.first[TYPE])
53
- tokens.shift # ignore spaces
54
- end
72
+ def each_attr(tokens)
73
+ attr_tokens = []
74
+ open_tokens = Hash.new { |h, k| h[k] = 0 }
55
75
 
56
- _, type, first_text = tokens.shift
76
+ tokens.each do |token|
77
+ _, type, _ = token
57
78
  case type
58
- when :on_label # `key:`
59
- first_text.tr(':', '')
60
- when :on_symbeg # `:key =>`, `:'key' =>` or `:"key" =>`
61
- key = tokens.shift[TEXT]
62
- if first_text != ':' # `:'key'` or `:"key"`
63
- expect_string_end!(tokens.shift)
79
+ when :on_comma
80
+ if open_tokens.values.all?(&:zero?)
81
+ yield(attr_tokens)
82
+ attr_tokens = []
83
+ next
64
84
  end
65
- shift_hash_rocket!(tokens)
66
- key
67
- when :on_tstring_beg # `"key":`, `'key':` or `"key" =>`
68
- key = tokens.shift[TEXT]
69
- next_token = tokens.shift
70
- if next_token[TYPE] != :on_label_end # on_label_end is `":` or `':`, so `"key" =>`
71
- expect_string_end!(next_token)
72
- shift_hash_rocket!(tokens)
73
- end
74
- key
75
- else
76
- raise UnexpectedKeyError.new("unexpected token is given!: #{first_text} (#{type})")
77
- end
78
- end
79
-
80
- # @param [Array] token - Ripper token
81
- def expect_string_end!(token)
82
- if token[TYPE] != :on_tstring_end
83
- raise UnexpectedTokenError
85
+ when :on_lbracket
86
+ open_tokens[:array] += 1
87
+ when :on_rbracket
88
+ open_tokens[:array] -= 1
89
+ when :on_lbrace
90
+ open_tokens[:block] += 1
91
+ when :on_rbrace
92
+ open_tokens[:block] -= 1
93
+ when :on_lparen
94
+ open_tokens[:paren] += 1
95
+ when :on_rparen
96
+ open_tokens[:paren] -= 1
97
+ when :on_embexpr_beg
98
+ open_tokens[:embexpr] += 1
99
+ when :on_embexpr_end
100
+ open_tokens[:embexpr] -= 1
101
+ when :on_sp
102
+ next if attr_tokens.empty?
84
103
  end
85
- end
86
104
 
87
- # @param [Array] tokens - Ripper tokens
88
- def shift_hash_rocket!(tokens)
89
- until tokens.empty?
90
- _, type, str = tokens.shift
91
- break if type == :on_op && str == '=>'
92
- end
93
- end
94
-
95
- # @param [String] hash_literal
96
- # @param [Proc] block - that takes [String, String] as arguments
97
- def each_attribute(hash_literal, &block)
98
- all_tokens = Ripper.lex(hash_literal.strip)
99
- all_tokens = all_tokens[1...-1] || [] # strip tokens for brackets
100
-
101
- each_balanced_tokens(all_tokens) do |tokens|
102
- key = shift_key!(tokens)
103
- value = tokens.map {|t| t[2] }.join.strip
104
- block.call(key, value)
105
- end
106
- end
107
-
108
- # @param [Array] tokens - Ripper tokens
109
- # @param [Proc] block - that takes balanced Ripper tokens as arguments
110
- def each_balanced_tokens(tokens, &block)
111
- attr_tokens = []
112
- open_tokens = Hash.new { |h, k| h[k] = 0 }
113
-
114
- tokens.each do |token|
115
- case token[TYPE]
116
- when :on_comma
117
- if open_tokens.values.all?(&:zero?)
118
- block.call(attr_tokens)
119
- attr_tokens = []
120
- next
121
- end
122
- when :on_lbracket
123
- open_tokens[:array] += 1
124
- when :on_rbracket
125
- open_tokens[:array] -= 1
126
- when :on_lbrace
127
- open_tokens[:block] += 1
128
- when :on_rbrace
129
- open_tokens[:block] -= 1
130
- when :on_lparen
131
- open_tokens[:paren] += 1
132
- when :on_rparen
133
- open_tokens[:paren] -= 1
134
- when :on_embexpr_beg
135
- open_tokens[:embexpr] += 1
136
- when :on_embexpr_end
137
- open_tokens[:embexpr] -= 1
138
- when *IGNORED_TYPES
139
- next if attr_tokens.empty?
140
- end
141
-
142
- attr_tokens << token
143
- end
144
- block.call(attr_tokens) unless attr_tokens.empty?
105
+ attr_tokens << token
145
106
  end
107
+ yield(attr_tokens) unless attr_tokens.empty?
146
108
  end
147
109
  end
148
110
  end