haml 5.2.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/workflows/test.yml +15 -15
  4. data/.gitignore +16 -16
  5. data/.yardopts +0 -3
  6. data/CHANGELOG.md +168 -4
  7. data/FAQ.md +1 -1
  8. data/Gemfile +21 -10
  9. data/MIT-LICENSE +1 -1
  10. data/README.md +22 -34
  11. data/REFERENCE.md +95 -159
  12. data/Rakefile +15 -82
  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 -36
  21. data/lib/haml/ambles.rb +20 -0
  22. data/lib/haml/attribute_builder.rb +127 -184
  23. data/lib/haml/attribute_compiler.rb +90 -194
  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 -697
  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 +158 -23
  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 -46
  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 +19 -15
  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 +188 -50
  73. data/.gitmodules +0 -3
  74. data/TODO +0 -24
  75. data/benchmark.rb +0 -70
  76. data/bin/haml +0 -9
  77. data/lib/haml/.gitattributes +0 -1
  78. data/lib/haml/buffer.rb +0 -182
  79. data/lib/haml/escapable.rb +0 -77
  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,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
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]
14
+
6
15
  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]
16
+ def initialize(identity, options)
17
+ @identity = identity
18
+ @quote = options[:attr_quote]
19
+ @format = options[:format]
16
20
  @escape_attrs = options[:escape_attrs]
17
- @hyphenate_data_attrs = options[:hyphenate_data_attrs]
18
21
  end
19
22
 
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)]
23
+ def compile(node)
24
+ hashes = []
25
+ if node.value[:object_ref] != :nil || !AttributeParser.available?
26
+ return runtime_compile(node)
29
27
  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
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
36
32
  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
- }]
33
+ static_compile(node.value[:attributes], hashes)
43
34
  end
44
35
 
45
36
  private
46
37
 
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
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)
69
64
  end
70
65
  end
66
+ temple
71
67
  end
72
68
 
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
88
- end
89
- end
90
- end
91
-
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)
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]]
99
73
  else
100
- runtime_build(values)
74
+ temple << [:html, :attr, key, [:dynamic, build_code]]
101
75
  end
102
76
  end
103
77
 
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)
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]]
135
82
  else
136
- "::Haml::AttributeBuilder.merge_values(#{frozen_string(key)}, #{values.map(&method(:attr_literal)).join(', ')})"
83
+ temple << [:html, :attr, key, [:dynamic, build_code]]
137
84
  end
138
85
  end
139
86
 
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
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(', ')})"
155
90
 
156
- case key
157
- when 'id', 'class'
158
- compile_id_or_class_attribute(key, values)
91
+ if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
92
+ temple << [:static, eval(build_code).to_s]
159
93
  else
160
- compile_common_attribute(key, values)
94
+ temple << [:dynamic, build_code]
161
95
  end
162
96
  end
163
97
 
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
98
+ def compile_boolean!(temple, key, values)
99
+ exp = literal_for(values.last)
202
100
 
203
- def true_value(key)
204
- if @is_html
205
- [:static, " #{key}"]
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
206
108
  else
207
- [:static, " #{key}=#{@attr_wrapper}#{key}#{@attr_wrapper}"]
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
+ ]
208
116
  end
209
117
  end
210
118
 
211
- def unique_name
212
- @unique_name ||= 0
213
- "_haml_attribute_compiler#{@unique_name += 1}"
119
+ def compile_common!(temple, key, values)
120
+ temple << [:html, :attr, key, [:fescape, @escape_attrs, values.last]]
214
121
  end
215
122
 
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
123
+ def attribute_builder(type, values)
124
+ args = [@escape_attrs.inspect, *values.map { |v| literal_for(v) }]
125
+ "::Haml::AttributeBuilder.build_#{type}(#{args.join(', ')})"
224
126
  end
225
127
 
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
128
+ def literal_for(value)
129
+ type, exp = value
130
+ type == :static ? "#{exp.inspect}.freeze" : exp
235
131
  end
236
132
  end
237
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