haml 5.1.2 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.
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