haml 5.2.2 → 6.1.1

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