haml 5.2.2 → 6.0.0.beta.1

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +13 -9
  4. data/.gitignore +16 -16
  5. data/CHANGELOG.md +13 -3
  6. data/Gemfile +18 -11
  7. data/MIT-LICENSE +1 -1
  8. data/README.md +13 -19
  9. data/Rakefile +95 -93
  10. data/bin/bench +66 -0
  11. data/bin/console +11 -0
  12. data/bin/ruby +3 -0
  13. data/bin/setup +7 -0
  14. data/bin/stackprof +27 -0
  15. data/bin/test +24 -0
  16. data/exe/haml +6 -0
  17. data/ext/haml/extconf.rb +10 -0
  18. data/ext/haml/haml.c +537 -0
  19. data/ext/haml/hescape.c +108 -0
  20. data/ext/haml/hescape.h +20 -0
  21. data/haml.gemspec +39 -37
  22. data/lib/haml/ambles.rb +20 -0
  23. data/lib/haml/attribute_builder.rb +135 -179
  24. data/lib/haml/attribute_compiler.rb +85 -194
  25. data/lib/haml/attribute_parser.rb +86 -126
  26. data/lib/haml/cli.rb +154 -0
  27. data/lib/haml/compiler/children_compiler.rb +126 -0
  28. data/lib/haml/compiler/comment_compiler.rb +39 -0
  29. data/lib/haml/compiler/doctype_compiler.rb +46 -0
  30. data/lib/haml/compiler/script_compiler.rb +116 -0
  31. data/lib/haml/compiler/silent_script_compiler.rb +24 -0
  32. data/lib/haml/compiler/tag_compiler.rb +76 -0
  33. data/lib/haml/compiler.rb +63 -296
  34. data/lib/haml/dynamic_merger.rb +67 -0
  35. data/lib/haml/engine.rb +42 -227
  36. data/lib/haml/error.rb +3 -52
  37. data/lib/haml/escapable.rb +6 -70
  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 +49 -0
  54. data/lib/haml/filters.rb +54 -378
  55. data/lib/haml/force_escapable.rb +29 -0
  56. data/lib/haml/haml_error.rb +66 -0
  57. data/lib/haml/helpers.rb +3 -697
  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 +179 -49
  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 -45
  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 -34
  68. data/lib/haml/temple_line_counter.rb +2 -1
  69. data/lib/haml/util.rb +17 -15
  70. data/lib/haml/version.rb +1 -2
  71. data/lib/haml.rb +8 -20
  72. metadata +211 -57
  73. data/.gitmodules +0 -3
  74. data/.yardopts +0 -22
  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 -182
  80. data/lib/haml/exec.rb +0 -347
  81. data/lib/haml/generator.rb +0 -42
  82. data/lib/haml/helpers/action_view_extensions.rb +0 -60
  83. data/lib/haml/helpers/action_view_mods.rb +0 -132
  84. data/lib/haml/helpers/action_view_xss_mods.rb +0 -60
  85. data/lib/haml/helpers/safe_erubi_template.rb +0 -20
  86. data/lib/haml/helpers/safe_erubis_template.rb +0 -33
  87. data/lib/haml/helpers/xss_mods.rb +0 -114
  88. data/lib/haml/options.rb +0 -273
  89. data/lib/haml/plugin.rb +0 -54
  90. data/lib/haml/sass_rails_filter.rb +0 -47
  91. data/lib/haml/template/options.rb +0 -27
  92. data/lib/haml/temple_engine.rb +0 -124
  93. data/yard/default/.gitignore +0 -1
  94. data/yard/default/fulldoc/html/css/common.sass +0 -15
  95. 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 || !Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
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) unless hash
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,110 @@
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
5
+ class AttributeParser
6
+ class ParseSkip < StandardError
7
+ end
14
8
 
15
- # Indices in Ripper tokens
16
- TYPE = 1
17
- TEXT = 2
9
+ def self.parse(text)
10
+ self.new.parse(text)
11
+ end
18
12
 
19
- IGNORED_TYPES = %i[on_sp on_ignored_nl].freeze
13
+ def parse(text)
14
+ exp = wrap_bracket(text)
15
+ return if RubyExpression.syntax_error?(exp)
20
16
 
21
- class << self
22
- # @return [Boolean] - return true if AttributeParser.parse can be used.
23
- def available?
24
- 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
25
22
  end
23
+ hash
24
+ rescue ParseSkip
25
+ nil
26
+ end
27
+
28
+ private
26
29
 
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)
30
+ def wrap_bracket(text)
31
+ text = text.strip
32
+ return text if text[0] == '{'
33
+ "{#{text}}"
34
+ end
31
35
 
32
- hash = {}
33
- each_attribute(exp) do |key, value|
34
- hash[key] = value
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)
35
54
  end
36
- hash
37
- rescue UnexpectedTokenError, UnexpectedKeyError
38
- nil
55
+ key
56
+ else
57
+ raise ParseSkip
39
58
  end
59
+ end
40
60
 
41
- private
61
+ def assert_type!(token, type)
62
+ raise ParseSkip if token[1] != type
63
+ end
42
64
 
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
65
+ def skip_until_hash_rocket!(tokens)
66
+ until tokens.empty?
67
+ _, type, str = tokens.shift
68
+ break if type == :on_op && str == '=>'
49
69
  end
70
+ end
50
71
 
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
72
+ def each_attr(tokens)
73
+ attr_tokens = []
74
+ open_tokens = Hash.new { |h, k| h[k] = 0 }
57
75
 
58
- _, type, first_text = tokens.shift
76
+ tokens.each do |token|
77
+ _, type, _ = token
59
78
  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)
79
+ when :on_comma
80
+ if open_tokens.values.all?(&:zero?)
81
+ yield(attr_tokens)
82
+ attr_tokens = []
83
+ next
66
84
  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})")
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?
79
103
  end
80
- end
81
104
 
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
88
-
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 == '=>'
94
- end
95
- end
96
-
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
102
-
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
108
- end
109
-
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 }
115
-
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?
142
- end
143
-
144
- attr_tokens << token
145
- end
146
- block.call(attr_tokens) unless attr_tokens.empty?
105
+ attr_tokens << token
147
106
  end
107
+ yield(attr_tokens) unless attr_tokens.empty?
148
108
  end
149
109
  end
150
110
  end