haml 4.0.6 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +49 -4
  4. data/FAQ.md +4 -14
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +85 -42
  7. data/REFERENCE.md +109 -58
  8. data/Rakefile +46 -54
  9. data/lib/haml/attribute_builder.rb +163 -0
  10. data/lib/haml/attribute_compiler.rb +215 -0
  11. data/lib/haml/attribute_parser.rb +144 -0
  12. data/lib/haml/buffer.rb +26 -136
  13. data/lib/haml/compiler.rb +87 -295
  14. data/lib/haml/engine.rb +25 -41
  15. data/lib/haml/error.rb +3 -0
  16. data/lib/haml/escapable.rb +49 -0
  17. data/lib/haml/exec.rb +33 -19
  18. data/lib/haml/filters.rb +18 -24
  19. data/lib/haml/generator.rb +41 -0
  20. data/lib/haml/helpers/action_view_extensions.rb +3 -2
  21. data/lib/haml/helpers/action_view_mods.rb +36 -58
  22. data/lib/haml/helpers/action_view_xss_mods.rb +1 -0
  23. data/lib/haml/helpers/safe_erubi_template.rb +27 -0
  24. data/lib/haml/helpers/safe_erubis_template.rb +4 -1
  25. data/lib/haml/helpers/xss_mods.rb +18 -12
  26. data/lib/haml/helpers.rb +133 -90
  27. data/lib/haml/options.rb +38 -47
  28. data/lib/haml/parser.rb +278 -216
  29. data/lib/haml/{template/plugin.rb → plugin.rb} +8 -15
  30. data/lib/haml/railtie.rb +21 -12
  31. data/lib/haml/sass_rails_filter.rb +17 -4
  32. data/lib/haml/template/options.rb +12 -2
  33. data/lib/haml/template.rb +12 -6
  34. data/lib/haml/temple_engine.rb +120 -0
  35. data/lib/haml/temple_line_counter.rb +29 -0
  36. data/lib/haml/util.rb +80 -199
  37. data/lib/haml/version.rb +2 -1
  38. data/lib/haml.rb +1 -0
  39. data/test/attribute_parser_test.rb +101 -0
  40. data/test/engine_test.rb +287 -176
  41. data/test/filters_test.rb +32 -19
  42. data/test/gemfiles/Gemfile.rails-4.0.x +9 -3
  43. data/test/gemfiles/Gemfile.rails-4.0.x.lock +87 -0
  44. data/test/gemfiles/Gemfile.rails-4.1.x +5 -0
  45. data/test/gemfiles/Gemfile.rails-4.2.x +5 -0
  46. data/test/gemfiles/Gemfile.rails-5.0.x +4 -0
  47. data/test/helper_test.rb +224 -112
  48. data/test/options_test.rb +22 -0
  49. data/test/parser_test.rb +71 -4
  50. data/test/results/bemit.xhtml +4 -0
  51. data/test/results/eval_suppressed.xhtml +4 -4
  52. data/test/results/helpers.xhtml +43 -41
  53. data/test/results/helpful.xhtml +6 -3
  54. data/test/results/just_stuff.xhtml +21 -20
  55. data/test/results/list.xhtml +9 -9
  56. data/test/results/nuke_inner_whitespace.xhtml +22 -22
  57. data/test/results/nuke_outer_whitespace.xhtml +84 -92
  58. data/test/results/original_engine.xhtml +17 -17
  59. data/test/results/partial_layout.xhtml +4 -3
  60. data/test/results/partial_layout_erb.xhtml +4 -3
  61. data/test/results/partials.xhtml +11 -10
  62. data/test/results/silent_script.xhtml +63 -63
  63. data/test/results/standard.xhtml +156 -159
  64. data/test/results/tag_parsing.xhtml +19 -19
  65. data/test/results/very_basic.xhtml +2 -2
  66. data/test/results/whitespace_handling.xhtml +77 -76
  67. data/test/template_test.rb +24 -56
  68. data/test/template_test_helper.rb +38 -0
  69. data/test/templates/bemit.haml +3 -0
  70. data/test/templates/just_stuff.haml +1 -0
  71. data/test/templates/standard_ugly.haml +1 -0
  72. data/test/templates/with_bom.haml +1 -0
  73. data/test/temple_line_counter_test.rb +40 -0
  74. data/test/test_helper.rb +26 -8
  75. data/test/util_test.rb +6 -47
  76. metadata +53 -36
  77. data/test/gemfiles/Gemfile.rails-3.0.x +0 -5
  78. data/test/gemfiles/Gemfile.rails-3.1.x +0 -6
  79. data/test/gemfiles/Gemfile.rails-3.2.x +0 -5
  80. data/test/templates/_av_partial_1_ugly.haml +0 -9
  81. data/test/templates/_av_partial_2_ugly.haml +0 -5
  82. data/test/templates/action_view_ugly.haml +0 -47
  83. data/test/templates/standard_ugly.haml +0 -43
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+ require 'haml/attribute_parser'
3
+
4
+ module Haml
5
+ class AttributeCompiler
6
+ # @param type [Symbol] :static or :dynamic
7
+ # @param key [String]
8
+ # @param value [String] Actual string value for :static type, value's Ruby literal for :dynamic type.
9
+ class AttributeValue < Struct.new(:type, :key, :value)
10
+ # @return [String] A Ruby literal of value.
11
+ def to_literal
12
+ case type
13
+ when :static
14
+ Haml::Util.inspect_obj(value)
15
+ when :dynamic
16
+ value
17
+ end
18
+ end
19
+
20
+ # Key's substring before a hyphen. This is necessary because values with the same
21
+ # base_key can conflict by Haml::AttributeBuidler#build_data_keys.
22
+ def base_key
23
+ key.split('-', 2).first
24
+ end
25
+ end
26
+
27
+ # Returns a script to render attributes on runtime.
28
+ #
29
+ # @param attributes [Hash]
30
+ # @param object_ref [String,:nil]
31
+ # @param dynamic_attributes [DynamicAttributes]
32
+ # @return [String] Attributes rendering code
33
+ def self.runtime_build(attributes, object_ref, dynamic_attributes)
34
+ "_hamlout.attributes(#{Haml::Util.inspect_obj(attributes)}, #{object_ref},#{dynamic_attributes.to_literal})"
35
+ end
36
+
37
+ # @param options [Haml::Options]
38
+ def initialize(options)
39
+ @is_html = [:html4, :html5].include?(options[:format])
40
+ @attr_wrapper = options[:attr_wrapper]
41
+ @escape_attrs = options[:escape_attrs]
42
+ @hyphenate_data_attrs = options[:hyphenate_data_attrs]
43
+ end
44
+
45
+ # Returns Temple expression to render attributes.
46
+ #
47
+ # @param attributes [Hash]
48
+ # @param object_ref [String,:nil]
49
+ # @param dynamic_attributes [DynamicAttributes]
50
+ # @return [Array] Temple expression
51
+ def compile(attributes, object_ref, dynamic_attributes)
52
+ if object_ref != :nil || !AttributeParser.available?
53
+ return [:dynamic, AttributeCompiler.runtime_build(attributes, object_ref, dynamic_attributes)]
54
+ end
55
+
56
+ parsed_hashes = [dynamic_attributes.new, dynamic_attributes.old].compact.map do |attribute_hash|
57
+ unless (hash = AttributeParser.parse(attribute_hash))
58
+ return [:dynamic, AttributeCompiler.runtime_build(attributes, object_ref, dynamic_attributes)]
59
+ end
60
+ hash
61
+ end
62
+ attribute_values = build_attribute_values(attributes, parsed_hashes)
63
+ AttributeBuilder.verify_attribute_names!(attribute_values.map(&:key))
64
+
65
+ values_by_base_key = attribute_values.group_by(&:base_key)
66
+ [:multi, *values_by_base_key.keys.sort.map { |base_key|
67
+ compile_attribute_values(values_by_base_key[base_key])
68
+ }]
69
+ end
70
+
71
+ private
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
88
+ end
89
+ end
90
+ end
91
+
92
+ # Compiles attribute values with the same base_key to Temple expression.
93
+ #
94
+ # @param values [Array<AttributeValue>] `base_key`'s results are the same. `key`'s result may differ.
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)
99
+ else
100
+ runtime_build(values)
101
+ end
102
+ end
103
+
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
+ [:dynamic, "_hamlout.attributes({ #{hash_content} }, nil)"]
111
+ end
112
+
113
+ # Renders attribute values statically.
114
+ #
115
+ # @param values [Array<AttributeValue>]
116
+ # @return [Array] Temple expression
117
+ def static_build(values)
118
+ hash_content = values.group_by(&:key).map do |key, values_for_key|
119
+ "#{frozen_string(key)} => #{merged_value(key, values_for_key)}"
120
+ end.join(', ')
121
+
122
+ arguments = [@is_html, @attr_wrapper, @escape_attrs, @hyphenate_data_attrs]
123
+ code = "::Haml::AttributeBuilder.build_attributes"\
124
+ "(#{arguments.map { |a| Haml::Util.inspect_obj(a) }.join(', ')}, { #{hash_content} })"
125
+ [:static, eval(code).to_s]
126
+ end
127
+
128
+ # @param key [String]
129
+ # @param values [Array<AttributeValue>]
130
+ # @return [String]
131
+ def merged_value(key, values)
132
+ if values.size == 1
133
+ values.first.to_literal
134
+ else
135
+ "::Haml::AttributeBuilder.merge_values(#{frozen_string(key)}, #{values.map(&:to_literal).join(', ')})"
136
+ end
137
+ end
138
+
139
+ # @param str [String]
140
+ # @return [String]
141
+ def frozen_string(str)
142
+ "#{Haml::Util.inspect_obj(str)}.freeze"
143
+ end
144
+
145
+ # Compiles attribute values for one key to Temple expression that generates ` key='value'`.
146
+ #
147
+ # @param key [String]
148
+ # @param values [Array<AttributeValue>]
149
+ # @return [Array] Temple expression
150
+ def compile_attribute(key, values)
151
+ if values.all? { |v| Temple::StaticAnalyzer.static?(v.to_literal) }
152
+ return static_build(values)
153
+ end
154
+
155
+ case key
156
+ when 'id', 'class'
157
+ compile_id_or_class_attribute(key, values)
158
+ else
159
+ compile_common_attribute(key, values)
160
+ end
161
+ end
162
+
163
+ # @param id_or_class [String] "id" or "class"
164
+ # @param values [Array<AttributeValue>]
165
+ # @return [Array] Temple expression
166
+ def compile_id_or_class_attribute(id_or_class, values)
167
+ var = unique_name
168
+ [:multi,
169
+ [:code, "#{var} = (#{merged_value(id_or_class, values)})"],
170
+ [:case, var,
171
+ ['Hash, Array', runtime_build([AttributeValue.new(:dynamic, id_or_class, var)])],
172
+ ['false, nil', [:multi]],
173
+ [:else, [:multi,
174
+ [:static, " #{id_or_class}=#{@attr_wrapper}"],
175
+ [:escape, @escape_attrs, [:dynamic, var]],
176
+ [:static, @attr_wrapper]],
177
+ ]
178
+ ],
179
+ ]
180
+ end
181
+
182
+ # @param key [String] Not "id" or "class"
183
+ # @param values [Array<AttributeValue>]
184
+ # @return [Array] Temple expression
185
+ def compile_common_attribute(key, values)
186
+ var = unique_name
187
+ [:multi,
188
+ [:code, "#{var} = (#{merged_value(key, values)})"],
189
+ [:case, var,
190
+ ['Hash', runtime_build([AttributeValue.new(:dynamic, key, var)])],
191
+ ['true', true_value(key)],
192
+ ['false, nil', [:multi]],
193
+ [:else, [:multi,
194
+ [:static, " #{key}=#{@attr_wrapper}"],
195
+ [:escape, @escape_attrs, [:dynamic, var]],
196
+ [:static, @attr_wrapper]],
197
+ ]
198
+ ],
199
+ ]
200
+ end
201
+
202
+ def true_value(key)
203
+ if @is_html
204
+ [:static, " #{key}"]
205
+ else
206
+ [:static, " #{key}=#{@attr_wrapper}#{key}#{@attr_wrapper}"]
207
+ end
208
+ end
209
+
210
+ def unique_name
211
+ @unique_name ||= 0
212
+ "_haml_attribute_compiler#{@unique_name += 1}"
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+ begin
3
+ require 'ripper'
4
+ rescue LoadError
5
+ end
6
+
7
+ module Haml
8
+ # Haml::AttriubuteParser parses Hash literal to { String (key name) => String (value literal) }.
9
+ module AttributeParser
10
+ class UnexpectedTokenError < StandardError; end
11
+ class UnexpectedKeyError < StandardError; end
12
+
13
+ # Indices in Ripper tokens
14
+ TYPE = 1
15
+ TEXT = 2
16
+
17
+ IGNORED_TYPES = %i[on_sp on_ignored_nl]
18
+
19
+ class << self
20
+ # @return [Boolean] - return true if AttributeParser.parse can be used.
21
+ def available?
22
+ defined?(Ripper) && Temple::StaticAnalyzer.available?
23
+ end
24
+
25
+ # @param [String] exp - Old attributes literal or Hash literal generated from new attributes.
26
+ # @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.
27
+ def parse(exp)
28
+ return nil unless hash_literal?(exp)
29
+
30
+ hash = {}
31
+ each_attribute(exp) do |key, value|
32
+ hash[key] = value
33
+ end
34
+ hash
35
+ rescue UnexpectedTokenError, UnexpectedKeyError
36
+ nil
37
+ end
38
+
39
+ private
40
+
41
+ # @param [String] exp - Ruby expression
42
+ # @return [Boolean] - Return true if exp is a single Hash literal
43
+ def hash_literal?(exp)
44
+ return false if Temple::StaticAnalyzer.syntax_error?(exp)
45
+ sym, body = Ripper.sexp(exp)
46
+ sym == :program && body.is_a?(Array) && body.size == 1 && body[0] && body[0][0] == :hash
47
+ end
48
+
49
+ # @param [Array] tokens - Ripper tokens. Scanned tokens will be destructively removed from this argument.
50
+ # @return [String] - attribute name in String
51
+ def shift_key!(tokens)
52
+ while !tokens.empty? && IGNORED_TYPES.include?(tokens.first[TYPE])
53
+ tokens.shift # ignore spaces
54
+ end
55
+
56
+ _, type, first_text = tokens.shift
57
+ case type
58
+ when :on_label # `key:`
59
+ first_text.tr(':', '')
60
+ when :on_symbeg # `:key =>`, `:'key' =>` or `:"key" =>`
61
+ key = tokens.shift[TEXT]
62
+ if first_text != ':' # `:'key'` or `:"key"`
63
+ expect_string_end!(tokens.shift)
64
+ end
65
+ shift_hash_rocket!(tokens)
66
+ key
67
+ when :on_tstring_beg # `"key":`, `'key':` or `"key" =>`
68
+ key = tokens.shift[TEXT]
69
+ next_token = tokens.shift
70
+ if next_token[TYPE] != :on_label_end # on_label_end is `":` or `':`, so `"key" =>`
71
+ expect_string_end!(next_token)
72
+ shift_hash_rocket!(tokens)
73
+ end
74
+ key
75
+ else
76
+ raise UnexpectedKeyError.new("unexpected token is given!: #{first_text} (#{type})")
77
+ end
78
+ end
79
+
80
+ # @param [Array] token - Ripper token
81
+ def expect_string_end!(token)
82
+ if token[TYPE] != :on_tstring_end
83
+ raise UnexpectedTokenError
84
+ end
85
+ end
86
+
87
+ # @param [Array] tokens - Ripper tokens
88
+ def shift_hash_rocket!(tokens)
89
+ until tokens.empty?
90
+ _, type, str = tokens.shift
91
+ break if type == :on_op && str == '=>'
92
+ end
93
+ end
94
+
95
+ # @param [String] hash_literal
96
+ # @param [Proc] block - that takes [String, String] as arguments
97
+ def each_attribute(hash_literal, &block)
98
+ all_tokens = Ripper.lex(hash_literal.strip)
99
+ all_tokens = all_tokens[1...-1] || [] # strip tokens for brackets
100
+
101
+ each_balaned_tokens(all_tokens) do |tokens|
102
+ key = shift_key!(tokens)
103
+ value = tokens.map(&:last).join.strip
104
+ block.call(key, value)
105
+ end
106
+ end
107
+
108
+ # @param [Array] tokens - Ripper tokens
109
+ # @param [Proc] block - that takes balanced Ripper tokens as arguments
110
+ def each_balaned_tokens(tokens, &block)
111
+ attr_tokens = []
112
+ open_tokens = Hash.new { |h, k| h[k] = 0 }
113
+
114
+ tokens.each do |token|
115
+ case token[TYPE]
116
+ when :on_comma
117
+ if open_tokens.values.all?(&:zero?)
118
+ block.call(attr_tokens)
119
+ attr_tokens = []
120
+ next
121
+ end
122
+ when :on_lbracket
123
+ open_tokens[:array] += 1
124
+ when :on_rbracket
125
+ open_tokens[:array] -= 1
126
+ when :on_lbrace
127
+ open_tokens[:block] += 1
128
+ when :on_rbrace
129
+ open_tokens[:block] -= 1
130
+ when :on_lparen
131
+ open_tokens[:paren] += 1
132
+ when :on_rparen
133
+ open_tokens[:paren] -= 1
134
+ when *IGNORED_TYPES
135
+ next if attr_tokens.empty?
136
+ end
137
+
138
+ attr_tokens << token
139
+ end
140
+ block.call(attr_tokens) unless attr_tokens.empty?
141
+ end
142
+ end
143
+ end
144
+ end
data/lib/haml/buffer.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Haml
2
3
  # This class is used only internally. It holds the buffer of HTML that
3
4
  # is eventually output as the resulting document.
@@ -90,7 +91,8 @@ module Haml
90
91
  def initialize(upper = nil, options = {})
91
92
  @active = true
92
93
  @upper = upper
93
- @options = options
94
+ @options = Options.buffer_defaults
95
+ @options = @options.merge(options) unless options.empty?
94
96
  @buffer = new_encoded_string
95
97
  @tabulation = 0
96
98
 
@@ -115,8 +117,8 @@ module Haml
115
117
  text.sub!(tabs, '') if dont_tab_up
116
118
  end
117
119
 
118
- @buffer << text
119
120
  @real_tabs += tab_change
121
+ @buffer << text
120
122
  end
121
123
 
122
124
  # Modifies the indentation of the document.
@@ -127,82 +129,13 @@ module Haml
127
129
  @real_tabs += tab_change
128
130
  end
129
131
 
130
- Haml::Util.def_static_method(self, :format_script, [:result],
131
- :preserve_script, :in_tag, :preserve_tag, :escape_html,
132
- :nuke_inner_whitespace, :interpolated, :ugly, <<RUBY)
133
- <% # Escape HTML here so that the safety of the string is preserved in Rails
134
- result_name = escape_html ? "html_escape(result.to_s)" : "result.to_s" %>
135
- <% unless ugly %>
136
- # If we're interpolated,
137
- # then the custom tabulation is handled in #push_text.
138
- # The easiest way to avoid it here is to reset @tabulation.
139
- <% if interpolated %>
140
- old_tabulation = @tabulation
141
- @tabulation = 0
142
- <% end %>
143
-
144
- <% if !(in_tag && preserve_tag && !nuke_inner_whitespace) %>
145
- tabulation = @real_tabs
146
- <% end %>
147
- result = <%= result_name %>.<% if nuke_inner_whitespace %>strip<% else %>rstrip<% end %>
148
- <% else %>
149
- result = <%= result_name %><% if nuke_inner_whitespace %>.strip<% end %>
150
- <% end %>
151
-
152
- <% if preserve_tag %>
153
- result = Haml::Helpers.preserve(result)
154
- <% elsif preserve_script %>
155
- result = Haml::Helpers.find_and_preserve(result, options[:preserve])
156
- <% end %>
157
-
158
- <% if ugly %>
159
- fix_textareas!(result) if toplevel? && result.include?('<textarea')
160
- return result
161
- <% else %>
162
- <% if !(in_tag && preserve_tag && !nuke_inner_whitespace) %>
163
- has_newline = result.include?("\\n")
164
- <% end %>
165
-
166
- <% if in_tag && !nuke_inner_whitespace %>
167
- <% unless preserve_tag %> if !has_newline <% end %>
168
- @real_tabs -= 1
169
- <% if interpolated %> @tabulation = old_tabulation <% end %>
170
- return result
171
- <% unless preserve_tag %> end <% end %>
172
- <% end %>
173
-
174
- <% if !(in_tag && preserve_tag && !nuke_inner_whitespace) %>
175
- # Precompiled tabulation may be wrong
176
- <% if !interpolated && !in_tag %>
177
- result = tabs + result if @tabulation > 0
178
- <% end %>
179
-
180
- if has_newline
181
- result = result.gsub "\\n", "\\n" + tabs(tabulation)
182
-
183
- # Add tabulation if it wasn't precompiled
184
- <% if in_tag && !nuke_inner_whitespace %> result = tabs(tabulation) + result <% end %>
185
- end
186
-
187
- fix_textareas!(result) if toplevel? && result.include?('<textarea')
188
-
189
- <% if in_tag && !nuke_inner_whitespace %>
190
- result = "\\n\#{result}\\n\#{tabs(tabulation-1)}"
191
- @real_tabs -= 1
192
- <% end %>
193
- <% if interpolated %> @tabulation = old_tabulation <% end %>
194
- result
195
- <% end %>
196
- <% end %>
197
- RUBY
198
-
199
132
  def attributes(class_id, obj_ref, *attributes_hashes)
200
133
  attributes = class_id
201
134
  attributes_hashes.each do |old|
202
- self.class.merge_attrs(attributes, Hash[old.map {|k, v| [k.to_s, v]}])
135
+ AttributeBuilder.merge_attributes!(attributes, Hash[old.map {|k, v| [k.to_s, v]}])
203
136
  end
204
- self.class.merge_attrs(attributes, parse_object_ref(obj_ref)) if obj_ref
205
- Compiler.build_attributes(
137
+ AttributeBuilder.merge_attributes!(attributes, parse_object_ref(obj_ref)) if obj_ref
138
+ AttributeBuilder.build_attributes(
206
139
  html?, @options[:attr_wrapper], @options[:escape_attrs], @options[:hyphenate_data_attrs], attributes)
207
140
  end
208
141
 
@@ -217,50 +150,6 @@ RUBY
217
150
  buffer << buffer.slice!(capture_position..-1).rstrip
218
151
  end
219
152
 
220
- # Merges two attribute hashes.
221
- # This is the same as `to.merge!(from)`,
222
- # except that it merges id, class, and data attributes.
223
- #
224
- # ids are concatenated with `"_"`,
225
- # and classes are concatenated with `" "`.
226
- # data hashes are simply merged.
227
- #
228
- # Destructively modifies both `to` and `from`.
229
- #
230
- # @param to [{String => String}] The attribute hash to merge into
231
- # @param from [{String => #to_s}] The attribute hash to merge from
232
- # @return [{String => String}] `to`, after being merged
233
- def self.merge_attrs(to, from)
234
- from['id'] = Compiler.filter_and_join(from['id'], '_') if from['id']
235
- if to['id'] && from['id']
236
- to['id'] << '_' << from.delete('id').to_s
237
- elsif to['id'] || from['id']
238
- from['id'] ||= to['id']
239
- end
240
-
241
- from['class'] = Compiler.filter_and_join(from['class'], ' ') if from['class']
242
- if to['class'] && from['class']
243
- # Make sure we don't duplicate class names
244
- from['class'] = (from['class'].to_s.split(' ') | to['class'].split(' ')).sort.join(' ')
245
- elsif to['class'] || from['class']
246
- from['class'] ||= to['class']
247
- end
248
-
249
- from_data = from.delete('data') || {}
250
- to_data = to.delete('data') || {}
251
-
252
- # forces to_data & from_data into a hash
253
- from_data = { nil => from_data } unless from_data.is_a?(Hash)
254
- to_data = { nil => to_data } unless to_data.is_a?(Hash)
255
-
256
- merged_data = to_data.merge(from_data)
257
-
258
- to['data'] = merged_data unless merged_data.empty?
259
- to.merge!(from)
260
- end
261
-
262
- private
263
-
264
153
  # Works like #{find_and_preserve}, but allows the first newline after a
265
154
  # preserved opening tag to remain unencoded, and then outdents the content.
266
155
  # This change was motivated primarily by the change in Rails 3.2.3 to emit
@@ -270,27 +159,26 @@ RUBY
270
159
  # @since Haml 4.0.1
271
160
  # @private
272
161
  def fix_textareas!(input)
273
- pattern = /([ ]*)<(textarea)([^>]*)>(\n|&#x000A;)(.*?)(<\/\2>)/im
162
+ return input unless toplevel? && input.include?('<textarea'.freeze)
163
+
164
+ pattern = /<(textarea)([^>]*)>(\n|&#x000A;)(.*?)<\/textarea>/im
274
165
  input.gsub!(pattern) do |s|
275
166
  match = pattern.match(s)
276
- content = match[5]
277
- if match[4] == '&#x000A;'
167
+ content = match[4]
168
+ if match[3] == '&#x000A;'
278
169
  content.sub!(/\A /, '&#x0020;')
279
170
  else
280
171
  content.sub!(/\A[ ]*/, '')
281
172
  end
282
- "#{match[1]}<#{match[2]}#{match[3]}>\n#{content}</#{match[2]}>"
173
+ "<#{match[1]}#{match[2]}>\n#{content}</#{match[1]}>"
283
174
  end
175
+ input
284
176
  end
285
177
 
286
- if RUBY_VERSION < "1.9"
287
- def new_encoded_string
288
- ""
289
- end
290
- else
291
- def new_encoded_string
292
- "".encode(Encoding.find(options[:encoding]))
293
- end
178
+ private
179
+
180
+ def new_encoded_string
181
+ "".encode(options[:encoding])
294
182
  end
295
183
 
296
184
  @@tab_cache = {}
@@ -328,18 +216,20 @@ RUBY
328
216
  id = "#{ prefix }_#{ id }"
329
217
  end
330
218
 
331
- {'id' => id, 'class' => class_name}
219
+ { 'id'.freeze => id, 'class'.freeze => class_name }
332
220
  end
333
221
 
334
222
  # Changes a word from camel case to underscores.
335
223
  # Based on the method of the same name in Rails' Inflector,
336
224
  # but copied here so it'll run properly without Rails.
337
225
  def underscore(camel_cased_word)
338
- camel_cased_word.to_s.gsub(/::/, '_').
339
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
340
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
341
- tr("-", "_").
342
- downcase
226
+ word = camel_cased_word.to_s.dup
227
+ word.gsub!(/::/, '_')
228
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
229
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
230
+ word.tr!('-', '_')
231
+ word.downcase!
232
+ word
343
233
  end
344
234
  end
345
235
  end