haml 4.0.7 → 5.0.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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +42 -4
  4. data/FAQ.md +4 -14
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +85 -42
  7. data/REFERENCE.md +108 -57
  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 +22 -132
  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 -43
  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/haml-spec/LICENSE +0 -14
  81. data/test/haml-spec/README.md +0 -106
  82. data/test/haml-spec/lua_haml_spec.lua +0 -38
  83. data/test/haml-spec/perl_haml_test.pl +0 -81
  84. data/test/haml-spec/ruby_haml_test.rb +0 -23
  85. data/test/haml-spec/tests.json +0 -660
  86. data/test/templates/_av_partial_1_ugly.haml +0 -9
  87. data/test/templates/_av_partial_2_ugly.haml +0 -5
  88. data/test/templates/action_view_ugly.haml +0 -47
  89. 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,6 +159,8 @@ RUBY
270
159
  # @since Haml 4.0.1
271
160
  # @private
272
161
  def fix_textareas!(input)
162
+ return input unless toplevel? && input.include?('<textarea'.freeze)
163
+
273
164
  pattern = /<(textarea)([^>]*)>(\n|&#x000A;)(.*?)<\/textarea>/im
274
165
  input.gsub!(pattern) do |s|
275
166
  match = pattern.match(s)
@@ -281,16 +172,13 @@ RUBY
281
172
  end
282
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