haml 5.1.2 → 6.3.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 +3 -0
  3. data/.github/workflows/test.yml +36 -0
  4. data/.gitignore +16 -15
  5. data/.yardopts +0 -3
  6. data/CHANGELOG.md +189 -1
  7. data/FAQ.md +1 -1
  8. data/Gemfile +20 -12
  9. data/MIT-LICENSE +1 -1
  10. data/README.md +10 -17
  11. data/REFERENCE.md +129 -164
  12. data/Rakefile +15 -89
  13. data/bin/bench +66 -0
  14. data/bin/console +11 -0
  15. data/bin/ruby +3 -0
  16. data/bin/setup +7 -0
  17. data/bin/stackprof +27 -0
  18. data/bin/test +24 -0
  19. data/exe/haml +6 -0
  20. data/haml.gemspec +34 -35
  21. data/lib/haml/ambles.rb +20 -0
  22. data/lib/haml/attribute_builder.rb +131 -133
  23. data/lib/haml/attribute_compiler.rb +91 -182
  24. data/lib/haml/attribute_parser.rb +92 -126
  25. data/lib/haml/cli.rb +154 -0
  26. data/lib/haml/compiler/children_compiler.rb +155 -0
  27. data/lib/haml/compiler/comment_compiler.rb +51 -0
  28. data/lib/haml/compiler/doctype_compiler.rb +52 -0
  29. data/lib/haml/compiler/script_compiler.rb +114 -0
  30. data/lib/haml/compiler/silent_script_compiler.rb +24 -0
  31. data/lib/haml/compiler/tag_compiler.rb +76 -0
  32. data/lib/haml/compiler.rb +63 -296
  33. data/lib/haml/dynamic_merger.rb +67 -0
  34. data/lib/haml/engine.rb +48 -227
  35. data/lib/haml/error.rb +5 -4
  36. data/lib/haml/escape.rb +13 -0
  37. data/lib/haml/escape_any.rb +21 -0
  38. data/lib/haml/filters/base.rb +12 -0
  39. data/lib/haml/filters/cdata.rb +20 -0
  40. data/lib/haml/filters/coffee.rb +17 -0
  41. data/lib/haml/filters/css.rb +33 -0
  42. data/lib/haml/filters/erb.rb +10 -0
  43. data/lib/haml/filters/escaped.rb +22 -0
  44. data/lib/haml/filters/javascript.rb +33 -0
  45. data/lib/haml/filters/less.rb +20 -0
  46. data/lib/haml/filters/markdown.rb +11 -0
  47. data/lib/haml/filters/plain.rb +29 -0
  48. data/lib/haml/filters/preserve.rb +22 -0
  49. data/lib/haml/filters/ruby.rb +10 -0
  50. data/lib/haml/filters/sass.rb +15 -0
  51. data/lib/haml/filters/scss.rb +15 -0
  52. data/lib/haml/filters/text_base.rb +25 -0
  53. data/lib/haml/filters/tilt_base.rb +59 -0
  54. data/lib/haml/filters.rb +54 -378
  55. data/lib/haml/force_escape.rb +29 -0
  56. data/lib/haml/helpers.rb +3 -691
  57. data/lib/haml/html.rb +22 -0
  58. data/lib/haml/identity.rb +13 -0
  59. data/lib/haml/object_ref.rb +35 -0
  60. data/lib/haml/parser.rb +190 -27
  61. data/lib/haml/rails_helpers.rb +53 -0
  62. data/lib/haml/rails_template.rb +62 -0
  63. data/lib/haml/railtie.rb +3 -41
  64. data/lib/haml/ruby_expression.rb +32 -0
  65. data/lib/haml/string_splitter.rb +140 -0
  66. data/lib/haml/template.rb +15 -34
  67. data/lib/haml/temple_line_counter.rb +2 -1
  68. data/lib/haml/util.rb +20 -16
  69. data/lib/haml/version.rb +1 -2
  70. data/lib/haml/whitespace.rb +8 -0
  71. data/lib/haml.rb +8 -20
  72. metadata +205 -53
  73. data/.gitmodules +0 -3
  74. data/.travis.yml +0 -97
  75. data/TODO +0 -24
  76. data/benchmark.rb +0 -70
  77. data/bin/haml +0 -9
  78. data/lib/haml/.gitattributes +0 -1
  79. data/lib/haml/buffer.rb +0 -238
  80. data/lib/haml/escapable.rb +0 -50
  81. data/lib/haml/exec.rb +0 -347
  82. data/lib/haml/generator.rb +0 -42
  83. data/lib/haml/helpers/action_view_extensions.rb +0 -60
  84. data/lib/haml/helpers/action_view_mods.rb +0 -132
  85. data/lib/haml/helpers/action_view_xss_mods.rb +0 -60
  86. data/lib/haml/helpers/safe_erubi_template.rb +0 -20
  87. data/lib/haml/helpers/safe_erubis_template.rb +0 -33
  88. data/lib/haml/helpers/xss_mods.rb +0 -111
  89. data/lib/haml/options.rb +0 -273
  90. data/lib/haml/plugin.rb +0 -37
  91. data/lib/haml/sass_rails_filter.rb +0 -47
  92. data/lib/haml/template/options.rb +0 -27
  93. data/lib/haml/temple_engine.rb +0 -123
  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,224 +1,133 @@
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
- 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) do
11
- # @return [String] A Ruby literal of value.
12
- def to_literal
13
- case type
14
- when :static
15
- Haml::Util.inspect_obj(value)
16
- when :dynamic
17
- value
18
- end
19
- end
20
- end
21
-
22
- # Returns a script to render attributes on runtime.
23
- #
24
- # @param attributes [Hash]
25
- # @param object_ref [String,:nil]
26
- # @param dynamic_attributes [DynamicAttributes]
27
- # @return [String] Attributes rendering code
28
- def self.runtime_build(attributes, object_ref, dynamic_attributes)
29
- "_hamlout.attributes(#{Haml::Util.inspect_obj(attributes)}, #{object_ref},#{dynamic_attributes.to_literal})"
30
- end
7
+ # The list of boolean attributes. You may add custom attributes to this constant.
8
+ BOOLEAN_ATTRIBUTES = %w[disabled readonly multiple checked autobuffer
9
+ autoplay controls loop selected hidden scoped async
10
+ defer reversed ismap seamless muted required
11
+ autofocus novalidate formnovalidate open pubdate
12
+ itemscope allowfullscreen default inert sortable
13
+ truespeed typemustmatch download]
31
14
 
32
- # @param options [Haml::Options]
33
- def initialize(options)
34
- @is_html = [:html4, :html5].include?(options[:format])
35
- @attr_wrapper = options[:attr_wrapper]
15
+ class AttributeCompiler
16
+ def initialize(identity, options)
17
+ @identity = identity
18
+ @quote = options[:attr_quote]
19
+ @format = options[:format]
36
20
  @escape_attrs = options[:escape_attrs]
37
- @hyphenate_data_attrs = options[:hyphenate_data_attrs]
38
21
  end
39
22
 
40
- # Returns Temple expression to render attributes.
41
- #
42
- # @param attributes [Hash]
43
- # @param object_ref [String,:nil]
44
- # @param dynamic_attributes [DynamicAttributes]
45
- # @return [Array] Temple expression
46
- def compile(attributes, object_ref, dynamic_attributes)
47
- if object_ref != :nil || !AttributeParser.available?
48
- return [:dynamic, AttributeCompiler.runtime_build(attributes, object_ref, dynamic_attributes)]
23
+ def compile(node)
24
+ hashes = []
25
+ if node.value[:object_ref] != :nil || !AttributeParser.available?
26
+ return runtime_compile(node)
49
27
  end
50
-
51
- parsed_hashes = [dynamic_attributes.new, dynamic_attributes.old].compact.map do |attribute_hash|
52
- unless (hash = AttributeParser.parse(attribute_hash))
53
- return [:dynamic, AttributeCompiler.runtime_build(attributes, object_ref, dynamic_attributes)]
54
- end
55
- hash
28
+ [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
29
+ hash = AttributeParser.parse(attribute_str)
30
+ return runtime_compile(node) if hash.nil? || hash.any? { |_key, value| value.empty? }
31
+ hashes << hash
56
32
  end
57
- attribute_values = build_attribute_values(attributes, parsed_hashes)
58
- AttributeBuilder.verify_attribute_names!(attribute_values.map(&:key))
59
-
60
- [:multi, *group_values_for_sort(attribute_values).map { |value_group|
61
- compile_attribute_values(value_group)
62
- }]
33
+ static_compile(node.value[:attributes], hashes)
63
34
  end
64
35
 
65
36
  private
66
37
 
67
- # Build array of grouped values whose sort order may go back and forth, which is also sorted with key name.
68
- # This method needs to group values with the same start because it can be changed in `Haml::AttributeBuidler#build_data_keys`.
69
- # @param values [Array<Haml::AttributeCompiler::AttributeValue>]
70
- # @return [Array<Array<Haml::AttributeCompiler::AttributeValue>>]
71
- def group_values_for_sort(values)
72
- sorted_values = values.sort_by(&:key)
73
- [].tap do |value_groups|
74
- until sorted_values.empty?
75
- key = sorted_values.first.key
76
- value_group, sorted_values = sorted_values.partition { |v| v.key.start_with?(key) }
77
- value_groups << value_group
38
+ def runtime_compile(node)
39
+ attrs = []
40
+ attrs.unshift(node.value[:attributes].inspect) if node.value[:attributes] != {}
41
+
42
+ args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect, node.value[:object_ref]] + attrs
43
+ [:html, :attrs, [:dynamic, "::Haml::AttributeBuilder.build(#{args.join(', ')}, #{node.value[:dynamic_attributes].to_literal})"]]
44
+ end
45
+
46
+ def static_compile(static_hash, dynamic_hashes)
47
+ temple = [:html, :attrs]
48
+ keys = [*static_hash.keys, *dynamic_hashes.map(&:keys).flatten].uniq.sort
49
+ keys.each do |key|
50
+ values = [[:static, static_hash[key]], *dynamic_hashes.map { |h| [:dynamic, h[key]] }]
51
+ values.select! { |_, exp| exp != nil }
52
+
53
+ case key
54
+ when 'id'
55
+ compile_id!(temple, key, values)
56
+ when 'class'
57
+ compile_class!(temple, key, values)
58
+ when 'data', 'aria'
59
+ compile_data!(temple, key, values)
60
+ when *BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
61
+ compile_boolean!(temple, key, values)
62
+ else
63
+ compile_common!(temple, key, values)
78
64
  end
79
65
  end
66
+ temple
80
67
  end
81
68
 
82
- # Returns array of AttributeValue instances from static attributes and dynamic_attributes. For each key,
83
- # the values' order in returned value is preserved in the same order as Haml::Buffer#attributes's merge order.
84
- #
85
- # @param attributes [{ String => String }]
86
- # @param parsed_hashes [{ String => String }]
87
- # @return [Array<AttributeValue>]
88
- def build_attribute_values(attributes, parsed_hashes)
89
- [].tap do |attribute_values|
90
- attributes.each do |key, static_value|
91
- attribute_values << AttributeValue.new(:static, key, static_value)
92
- end
93
- parsed_hashes.each do |parsed_hash|
94
- parsed_hash.each do |key, dynamic_value|
95
- attribute_values << AttributeValue.new(:dynamic, key, dynamic_value)
96
- end
97
- end
69
+ def compile_id!(temple, key, values)
70
+ build_code = attribute_builder(:id, values)
71
+ if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
72
+ temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
73
+ else
74
+ temple << [:html, :attr, key, [:dynamic, build_code]]
98
75
  end
99
76
  end
100
77
 
101
- # Compiles attribute values with the similar key to Temple expression.
102
- #
103
- # @param values [Array<AttributeValue>] whose `key`s are partially or fully the same from left.
104
- # @return [Array] Temple expression
105
- def compile_attribute_values(values)
106
- if values.map(&:key).uniq.size == 1
107
- compile_attribute(values.first.key, values)
78
+ def compile_class!(temple, key, values)
79
+ build_code = attribute_builder(:class, values)
80
+ if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
81
+ temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
108
82
  else
109
- runtime_build(values)
83
+ temple << [:html, :attr, key, [:dynamic, build_code]]
110
84
  end
111
85
  end
112
86
 
113
- # @param values [Array<AttributeValue>]
114
- # @return [Array] Temple expression
115
- def runtime_build(values)
116
- hash_content = values.group_by(&:key).map do |key, values_for_key|
117
- "#{frozen_string(key)} => #{merged_value(key, values_for_key)}"
118
- end.join(', ')
119
- [:dynamic, "_hamlout.attributes({ #{hash_content} }, nil)"]
120
- end
121
-
122
- # Renders attribute values statically.
123
- #
124
- # @param values [Array<AttributeValue>]
125
- # @return [Array] Temple expression
126
- def static_build(values)
127
- hash_content = values.group_by(&:key).map do |key, values_for_key|
128
- "#{frozen_string(key)} => #{merged_value(key, values_for_key)}"
129
- end.join(', ')
130
-
131
- arguments = [@is_html, @attr_wrapper, @escape_attrs, @hyphenate_data_attrs]
132
- code = "::Haml::AttributeBuilder.build_attributes"\
133
- "(#{arguments.map { |a| Haml::Util.inspect_obj(a) }.join(', ')}, { #{hash_content} })"
134
- [:static, eval(code).to_s]
135
- end
87
+ def compile_data!(temple, key, values)
88
+ args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", values.map { |v| literal_for(v) }]
89
+ build_code = "::Haml::AttributeBuilder.build_#{key}(#{args.join(', ')})"
136
90
 
137
- # @param key [String]
138
- # @param values [Array<AttributeValue>]
139
- # @return [String]
140
- def merged_value(key, values)
141
- if values.size == 1
142
- values.first.to_literal
91
+ if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
92
+ temple << [:static, eval(build_code).to_s]
143
93
  else
144
- "::Haml::AttributeBuilder.merge_values(#{frozen_string(key)}, #{values.map(&:to_literal).join(', ')})"
94
+ temple << [:dynamic, build_code]
145
95
  end
146
96
  end
147
97
 
148
- # @param str [String]
149
- # @return [String]
150
- def frozen_string(str)
151
- "#{Haml::Util.inspect_obj(str)}.freeze"
152
- end
98
+ def compile_boolean!(temple, key, values)
99
+ exp = literal_for(values.last)
153
100
 
154
- # Compiles attribute values for one key to Temple expression that generates ` key='value'`.
155
- #
156
- # @param key [String]
157
- # @param values [Array<AttributeValue>]
158
- # @return [Array] Temple expression
159
- def compile_attribute(key, values)
160
- if values.all? { |v| Temple::StaticAnalyzer.static?(v.to_literal) }
161
- return static_build(values)
162
- end
163
-
164
- case key
165
- when 'id', 'class'
166
- compile_id_or_class_attribute(key, values)
101
+ if Temple::StaticAnalyzer.static?(exp)
102
+ value = eval(exp)
103
+ case value
104
+ when true then temple << [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]
105
+ when false, nil
106
+ else temple << [:html, :attr, key, [:fescape, @escape_attrs, [:static, value.to_s]]]
107
+ end
167
108
  else
168
- compile_common_attribute(key, values)
109
+ var = @identity.generate
110
+ temple << [
111
+ :case, "(#{var} = (#{exp}))",
112
+ ['true', [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]],
113
+ ['false, nil', [:multi]],
114
+ [:else, [:multi, [:static, " #{key}=#{@quote}"], [:fescape, @escape_attrs, [:dynamic, var]], [:static, @quote]]],
115
+ ]
169
116
  end
170
117
  end
171
118
 
172
- # @param id_or_class [String] "id" or "class"
173
- # @param values [Array<AttributeValue>]
174
- # @return [Array] Temple expression
175
- def compile_id_or_class_attribute(id_or_class, values)
176
- var = unique_name
177
- [:multi,
178
- [:code, "#{var} = (#{merged_value(id_or_class, values)})"],
179
- [:case, var,
180
- ['Hash, Array', runtime_build([AttributeValue.new(:dynamic, id_or_class, var)])],
181
- ['false, nil', [:multi]],
182
- [:else, [:multi,
183
- [:static, " #{id_or_class}=#{@attr_wrapper}"],
184
- [:escape, @escape_attrs, [:dynamic, var]],
185
- [:static, @attr_wrapper]],
186
- ]
187
- ],
188
- ]
119
+ def compile_common!(temple, key, values)
120
+ temple << [:html, :attr, key, [:fescape, @escape_attrs, values.last]]
189
121
  end
190
122
 
191
- # @param key [String] Not "id" or "class"
192
- # @param values [Array<AttributeValue>]
193
- # @return [Array] Temple expression
194
- def compile_common_attribute(key, values)
195
- var = unique_name
196
- [:multi,
197
- [:code, "#{var} = (#{merged_value(key, values)})"],
198
- [:case, var,
199
- ['Hash', runtime_build([AttributeValue.new(:dynamic, key, var)])],
200
- ['true', true_value(key)],
201
- ['false, nil', [:multi]],
202
- [:else, [:multi,
203
- [:static, " #{key}=#{@attr_wrapper}"],
204
- [:escape, @escape_attrs, [:dynamic, var]],
205
- [:static, @attr_wrapper]],
206
- ]
207
- ],
208
- ]
209
- end
210
-
211
- def true_value(key)
212
- if @is_html
213
- [:static, " #{key}"]
214
- else
215
- [:static, " #{key}=#{@attr_wrapper}#{key}#{@attr_wrapper}"]
216
- end
123
+ def attribute_builder(type, values)
124
+ args = [@escape_attrs.inspect, *values.map { |v| literal_for(v) }]
125
+ "::Haml::AttributeBuilder.build_#{type}(#{args.join(', ')})"
217
126
  end
218
127
 
219
- def unique_name
220
- @unique_name ||= 0
221
- "_haml_attribute_compiler#{@unique_name += 1}"
128
+ def literal_for(value)
129
+ type, exp = value
130
+ type == :static ? "#{exp.inspect}.freeze" : exp
222
131
  end
223
132
  end
224
133
  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