haml 2.2.24 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (168) hide show
  1. data/.yardopts +0 -1
  2. data/README.md +91 -151
  3. data/REMEMBER +11 -1
  4. data/Rakefile +73 -55
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/css2sass +7 -1
  8. data/bin/sass-convert +7 -0
  9. data/extra/haml-mode.el +2 -1
  10. data/lib/haml/buffer.rb +22 -4
  11. data/lib/haml/engine.rb +5 -1
  12. data/lib/haml/exec.rb +231 -46
  13. data/lib/haml/filters.rb +19 -8
  14. data/lib/haml/helpers.rb +47 -20
  15. data/lib/haml/helpers/action_view_extensions.rb +2 -4
  16. data/lib/haml/helpers/action_view_mods.rb +11 -8
  17. data/lib/haml/helpers/xss_mods.rb +13 -2
  18. data/lib/haml/html.rb +179 -48
  19. data/lib/haml/html/erb.rb +141 -0
  20. data/lib/haml/precompiler.rb +40 -15
  21. data/lib/haml/railtie.rb +1 -5
  22. data/lib/haml/root.rb +3 -0
  23. data/lib/haml/template.rb +4 -14
  24. data/lib/haml/util.rb +120 -30
  25. data/lib/haml/version.rb +25 -2
  26. data/lib/sass.rb +5 -1
  27. data/lib/sass/callbacks.rb +50 -0
  28. data/lib/sass/css.rb +40 -191
  29. data/lib/sass/engine.rb +170 -74
  30. data/lib/sass/environment.rb +8 -2
  31. data/lib/sass/error.rb +163 -25
  32. data/lib/sass/files.rb +31 -28
  33. data/lib/sass/plugin.rb +268 -87
  34. data/lib/sass/plugin/rails.rb +9 -4
  35. data/lib/sass/repl.rb +1 -1
  36. data/lib/sass/script.rb +31 -29
  37. data/lib/sass/script/bool.rb +1 -0
  38. data/lib/sass/script/color.rb +290 -23
  39. data/lib/sass/script/css_lexer.rb +22 -0
  40. data/lib/sass/script/css_parser.rb +28 -0
  41. data/lib/sass/script/funcall.rb +22 -3
  42. data/lib/sass/script/functions.rb +523 -33
  43. data/lib/sass/script/interpolation.rb +42 -0
  44. data/lib/sass/script/lexer.rb +169 -52
  45. data/lib/sass/script/literal.rb +58 -9
  46. data/lib/sass/script/node.rb +79 -1
  47. data/lib/sass/script/number.rb +20 -5
  48. data/lib/sass/script/operation.rb +49 -3
  49. data/lib/sass/script/parser.rb +162 -28
  50. data/lib/sass/script/string.rb +50 -2
  51. data/lib/sass/script/unary_operation.rb +25 -2
  52. data/lib/sass/script/variable.rb +21 -4
  53. data/lib/sass/scss.rb +14 -0
  54. data/lib/sass/scss/css_parser.rb +39 -0
  55. data/lib/sass/scss/parser.rb +683 -0
  56. data/lib/sass/scss/rx.rb +112 -0
  57. data/lib/sass/scss/script_lexer.rb +13 -0
  58. data/lib/sass/scss/script_parser.rb +25 -0
  59. data/lib/sass/tree/comment_node.rb +69 -27
  60. data/lib/sass/tree/debug_node.rb +7 -2
  61. data/lib/sass/tree/directive_node.rb +41 -35
  62. data/lib/sass/tree/for_node.rb +6 -0
  63. data/lib/sass/tree/if_node.rb +13 -1
  64. data/lib/sass/tree/import_node.rb +52 -27
  65. data/lib/sass/tree/mixin_def_node.rb +18 -0
  66. data/lib/sass/tree/mixin_node.rb +41 -6
  67. data/lib/sass/tree/node.rb +197 -70
  68. data/lib/sass/tree/prop_node.rb +152 -57
  69. data/lib/sass/tree/root_node.rb +118 -0
  70. data/lib/sass/tree/rule_node.rb +193 -96
  71. data/lib/sass/tree/variable_node.rb +9 -5
  72. data/lib/sass/tree/while_node.rb +4 -0
  73. data/test/benchmark.rb +5 -5
  74. data/test/haml/engine_test.rb +147 -10
  75. data/test/haml/{rhtml/_av_partial_1.rhtml → erb/_av_partial_1.erb} +1 -1
  76. data/test/haml/{rhtml/_av_partial_2.rhtml → erb/_av_partial_2.erb} +1 -1
  77. data/test/haml/{rhtml/action_view.rhtml → erb/action_view.erb} +1 -1
  78. data/test/haml/{rhtml/standard.rhtml → erb/standard.erb} +0 -0
  79. data/test/haml/helper_test.rb +91 -24
  80. data/test/haml/html2haml/erb_tests.rb +410 -0
  81. data/test/haml/html2haml_test.rb +210 -66
  82. data/test/haml/results/filters.xhtml +1 -1
  83. data/test/haml/results/just_stuff.xhtml +2 -0
  84. data/test/haml/spec_test.rb +44 -0
  85. data/test/haml/template_test.rb +22 -2
  86. data/test/haml/templates/helpers.haml +0 -13
  87. data/test/haml/templates/just_stuff.haml +2 -0
  88. data/test/haml/util_test.rb +48 -0
  89. data/test/sass/callbacks_test.rb +61 -0
  90. data/test/sass/conversion_test.rb +884 -0
  91. data/test/sass/css2sass_test.rb +99 -18
  92. data/test/sass/data/hsl-rgb.txt +319 -0
  93. data/test/sass/engine_test.rb +1049 -131
  94. data/test/sass/functions_test.rb +398 -47
  95. data/test/sass/more_results/more_import.css +1 -1
  96. data/test/sass/more_templates/more_import.sass +3 -3
  97. data/test/sass/plugin_test.rb +184 -10
  98. data/test/sass/results/compact.css +1 -1
  99. data/test/sass/results/complex.css +5 -5
  100. data/test/sass/results/compressed.css +1 -1
  101. data/test/sass/results/expanded.css +1 -1
  102. data/test/sass/results/import.css +3 -1
  103. data/test/sass/results/mixins.css +12 -12
  104. data/test/sass/results/nested.css +1 -1
  105. data/test/sass/results/options.css +1 -0
  106. data/test/sass/results/parent_ref.css +4 -4
  107. data/test/sass/results/script.css +3 -3
  108. data/test/sass/results/scss_import.css +15 -0
  109. data/test/sass/results/scss_importee.css +2 -0
  110. data/test/sass/script_conversion_test.rb +153 -0
  111. data/test/sass/script_test.rb +137 -70
  112. data/test/sass/scss/css_test.rb +811 -0
  113. data/test/sass/scss/rx_test.rb +156 -0
  114. data/test/sass/scss/scss_test.rb +871 -0
  115. data/test/sass/scss/test_helper.rb +37 -0
  116. data/test/sass/templates/alt.sass +2 -2
  117. data/test/sass/templates/bork1.sass +2 -0
  118. data/test/sass/templates/bork3.sass +2 -0
  119. data/test/sass/templates/bork4.sass +2 -0
  120. data/test/sass/templates/import.sass +4 -4
  121. data/test/sass/templates/importee.sass +3 -3
  122. data/test/sass/templates/line_numbers.sass +1 -1
  123. data/test/sass/templates/mixin_bork.sass +5 -0
  124. data/test/sass/templates/mixins.sass +2 -2
  125. data/test/sass/templates/nested_bork1.sass +2 -0
  126. data/test/sass/templates/nested_bork2.sass +2 -0
  127. data/test/sass/templates/nested_bork3.sass +2 -0
  128. data/test/sass/templates/nested_bork4.sass +2 -0
  129. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  130. data/test/sass/templates/options.sass +2 -0
  131. data/test/sass/templates/parent_ref.sass +2 -2
  132. data/test/sass/templates/script.sass +69 -69
  133. data/test/sass/templates/scss_import.scss +10 -0
  134. data/test/sass/templates/scss_importee.scss +1 -0
  135. data/test/sass/templates/units.sass +10 -10
  136. data/test/test_helper.rb +20 -8
  137. data/vendor/fssm/LICENSE +20 -0
  138. data/vendor/fssm/README.markdown +55 -0
  139. data/vendor/fssm/Rakefile +59 -0
  140. data/vendor/fssm/VERSION.yml +5 -0
  141. data/vendor/fssm/example.rb +9 -0
  142. data/vendor/fssm/fssm.gemspec +77 -0
  143. data/vendor/fssm/lib/fssm.rb +33 -0
  144. data/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
  145. data/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
  146. data/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
  147. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
  148. data/vendor/fssm/lib/fssm/monitor.rb +26 -0
  149. data/vendor/fssm/lib/fssm/path.rb +91 -0
  150. data/vendor/fssm/lib/fssm/pathname.rb +502 -0
  151. data/vendor/fssm/lib/fssm/state/directory.rb +57 -0
  152. data/vendor/fssm/lib/fssm/state/file.rb +24 -0
  153. data/vendor/fssm/lib/fssm/support.rb +63 -0
  154. data/vendor/fssm/lib/fssm/tree.rb +176 -0
  155. data/vendor/fssm/profile/prof-cache.rb +40 -0
  156. data/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
  157. data/vendor/fssm/profile/prof-pathname.rb +68 -0
  158. data/vendor/fssm/profile/prof-plain-pathname.html +988 -0
  159. data/vendor/fssm/profile/prof.html +2379 -0
  160. data/vendor/fssm/spec/path_spec.rb +75 -0
  161. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  162. data/vendor/fssm/spec/root/file.css +0 -0
  163. data/vendor/fssm/spec/root/file.rb +0 -0
  164. data/vendor/fssm/spec/root/file.yml +0 -0
  165. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  166. data/vendor/fssm/spec/spec_helper.rb +14 -0
  167. metadata +94 -14
  168. data/test/sass/templates/bork.sass +0 -2
@@ -100,11 +100,7 @@ module Haml
100
100
  if contains_interpolation?(text)
101
101
  return if options[:suppress_eval]
102
102
 
103
- text = unescape_interpolation(text).gsub(/(\\+)n/) do |s|
104
- escapes = $1.size
105
- next s if escapes % 2 == 0
106
- ("\\" * (escapes - 1)) + "\n"
107
- end
103
+ text = unescape_interpolation(text).gsub("\\n", "\n")
108
104
  newline if text.gsub!(/\n"\Z/, "\\n\"")
109
105
  push_script <<RUBY.strip, :escape_html => false
110
106
  find_and_preserve(#{filter.inspect}.render_with_options(#{text}, _hamlout.options))
@@ -210,6 +206,23 @@ END
210
206
  end
211
207
  end
212
208
 
209
+ # Surrounds the filtered text with `<style>` and CDATA tags.
210
+ # Useful for including inline CSS.
211
+ module Css
212
+ include Base
213
+
214
+ # @see Base#render_with_options
215
+ def render_with_options(text, options)
216
+ <<END
217
+ <style type=#{options[:attr_wrapper]}text/css#{options[:attr_wrapper]}>
218
+ /*<![CDATA[*/
219
+ #{text.rstrip.gsub("\n", "\n ")}
220
+ /*]]>*/
221
+ </style>
222
+ END
223
+ end
224
+ end
225
+
213
226
  # Surrounds the filtered text with CDATA tags.
214
227
  module Cdata
215
228
  include Base
@@ -281,7 +294,7 @@ END
281
294
  end
282
295
  end
283
296
 
284
- # Parses the filtered text with ERB, like an RHTML template.
297
+ # Parses the filtered text with ERB.
285
298
  # Not available if the {file:HAML_REFERENCE.md#suppress_eval-option `:suppress_eval`} option is set to true.
286
299
  # Embedded Ruby code is evaluated in the same context as the Haml template.
287
300
  module ERB
@@ -308,8 +321,6 @@ END
308
321
  ::RedCloth.new(text).to_html(:textile)
309
322
  end
310
323
  end
311
- # An alias for the Textile filter,
312
- # since the only available Textile parser is RedCloth.
313
324
  RedCloth = Textile
314
325
  Filters.defined['redcloth'] = RedCloth
315
326
 
@@ -1,3 +1,8 @@
1
+ if defined?(ActionView)
2
+ require 'haml/helpers/action_view_mods'
3
+ require 'haml/helpers/action_view_extensions'
4
+ end
5
+
1
6
  module Haml
2
7
  # This module contains various helpful methods to make it easier to do various tasks.
3
8
  # {Haml::Helpers} is automatically included in the context
@@ -47,7 +52,8 @@ MESSAGE
47
52
 
48
53
  self.extend self
49
54
 
50
- @@action_view_defined = false
55
+ @@action_view_defined = defined?(ActionView)
56
+ @@force_no_action_view = false
51
57
 
52
58
  # @return [Boolean] Whether or not ActionView is loaded
53
59
  def self.action_view?
@@ -359,22 +365,16 @@ MESSAGE
359
365
  haml_buffer.capture_position = nil
360
366
  end
361
367
 
362
- # @deprecated This will be removed in version 3.0.
363
- # @see #haml_concat
364
- def puts(*args)
365
- warn <<END
366
- DEPRECATION WARNING:
367
- The Haml #puts helper is deprecated and will be removed in version 3.0.
368
- Use the #haml_concat helper instead.
369
- END
370
- haml_concat(*args)
371
- end
372
-
373
368
  # Outputs text directly to the Haml buffer, with the proper indentation.
374
369
  #
375
370
  # @param text [#to_s] The text to output
376
371
  def haml_concat(text = "")
377
- haml_buffer.buffer << haml_indent << text.to_s << "\n"
372
+ unless haml_buffer.options[:ugly] || haml_indent == 0
373
+ haml_buffer.buffer << haml_indent <<
374
+ text.to_s.gsub("\n", "\n" + haml_indent) << "\n"
375
+ else
376
+ haml_buffer.buffer << text.to_s << "\n"
377
+ end
378
378
  ErrorReturn.new("haml_concat")
379
379
  end
380
380
 
@@ -388,6 +388,11 @@ END
388
388
  # If the block is a Haml block or outputs text using \{#haml\_concat},
389
389
  # the text will be properly indented.
390
390
  #
391
+ # `name` can be a string using the standard Haml class/id shorthand
392
+ # (e.g. "span#foo.bar", "#foo").
393
+ # Just like standard Haml tags, these class and id values
394
+ # will be merged with manually-specified attributes.
395
+ #
391
396
  # `flags` is a list of symbol flags
392
397
  # like those that can be put at the end of a Haml tag
393
398
  # (`:/`, `:<`, and `:>`).
@@ -402,7 +407,7 @@ END
402
407
  #
403
408
  # haml_tag :table do
404
409
  # haml_tag :tr do
405
- # haml_tag :td, {:class => 'cell'} do
410
+ # haml_tag 'td.cell' do
406
411
  # haml_tag :strong, "strong!"
407
412
  # haml_concat "data"
408
413
  # end
@@ -438,13 +443,14 @@ END
438
443
  def haml_tag(name, *rest, &block)
439
444
  ret = ErrorReturn.new("haml_tag")
440
445
 
441
- name = name.to_s
442
446
  text = rest.shift.to_s unless [Symbol, Hash, NilClass].any? {|t| rest.first.is_a? t}
443
447
  flags = []
444
448
  flags << rest.shift while rest.first.is_a? Symbol
449
+ name, attrs = merge_name_and_attributes(name.to_s, rest.shift || {})
450
+
445
451
  attributes = Haml::Precompiler.build_attributes(haml_buffer.html?,
446
452
  haml_buffer.options[:attr_wrapper],
447
- rest.shift || {})
453
+ attrs)
448
454
 
449
455
  if text.nil? && block.nil? && (haml_buffer.options[:autoclose].include?(name) || flags.include?(:/))
450
456
  haml_concat "<#{name}#{attributes} />"
@@ -458,8 +464,17 @@ END
458
464
 
459
465
  tag = "<#{name}#{attributes}>"
460
466
  if block.nil?
461
- tag << text.to_s << "</#{name}>"
462
- haml_concat tag
467
+ text = text.to_s
468
+ if text.include?("\n")
469
+ haml_concat tag
470
+ tab_up
471
+ haml_concat text
472
+ tab_down
473
+ haml_concat "</#{name}>"
474
+ else
475
+ tag << text << "</#{name}>"
476
+ haml_concat tag
477
+ end
463
478
  return ret
464
479
  end
465
480
 
@@ -496,7 +511,7 @@ END
496
511
  # @param text [String] The string to sanitize
497
512
  # @return [String] The sanitized string
498
513
  def html_escape(text)
499
- Haml::Util.silence_warnings {text.to_s.gsub(/[\"><&]/n) {|s| HTML_ESCAPE[s]}}
514
+ text.to_s.gsub(/[\"><&]/n) {|s| HTML_ESCAPE[s]}
500
515
  end
501
516
 
502
517
  # Escapes HTML entities in `text`, but without escaping an ampersand
@@ -534,6 +549,17 @@ END
534
549
 
535
550
  private
536
551
 
552
+ # Parses the tag name used for \{#haml\_tag}
553
+ # and merges it with the Ruby attributes hash.
554
+ def merge_name_and_attributes(name, attributes_hash = {})
555
+ # skip merging if no ids or classes found in name
556
+ return name, attributes_hash unless name =~ /^(.+?)?([\.#].*)$/
557
+
558
+ return $1 || "div", Buffer.merge_attrs(
559
+ Precompiler.parse_class_and_id($2),
560
+ Haml::Util.map_keys(attributes_hash) {|key| key.to_s})
561
+ end
562
+
537
563
  # Runs a block of code with the given buffer as the currently active buffer.
538
564
  #
539
565
  # @param buffer [Haml::Buffer] The Haml buffer to use temporarily
@@ -566,10 +592,11 @@ END
566
592
  _erbout = _hamlout.buffer
567
593
  proc { |*args| proc.call(*args) }
568
594
  end
595
+
596
+ include ActionViewExtensions if self.const_defined? "ActionViewExtensions"
569
597
  end
570
598
  end
571
599
 
572
- # @private
573
600
  class Object
574
601
  # Haml overrides various `ActionView` helpers,
575
602
  # which call an \{#is\_haml?} method
@@ -1,7 +1,7 @@
1
+ require 'haml/helpers/action_view_mods'
2
+
1
3
  module Haml
2
4
  module Helpers
3
- @@action_view_defined = true
4
-
5
5
  # This module contains various useful helper methods
6
6
  # that either tie into ActionView or the rest of the ActionPack stack,
7
7
  # or are only useful in that context.
@@ -51,7 +51,5 @@ module Haml
51
51
  @_haml_concat_raw = old
52
52
  end
53
53
  end
54
-
55
- include ActionViewExtensions
56
54
  end
57
55
  end
@@ -125,12 +125,9 @@ module ActionView
125
125
  @template_object.send :is_haml?
126
126
  end
127
127
 
128
- def content_tag(*args)
129
- html_tag = content_tag_with_haml(*args)
130
- return html_tag unless respond_to?(:error_wrapping)
131
- return error_wrapping(html_tag) if method(:error_wrapping).arity == 1
132
- return html_tag unless object.respond_to?(:errors) && object.errors.respond_to?(:on)
133
- return error_wrapping(html_tag, object.errors.on(@method_name))
128
+ unless defined?(ActionView::Helpers) && defined?(ActionView::Helpers::ActiveRecordInstanceTag)
129
+ alias_method :content_tag_without_haml, :content_tag
130
+ alias_method :content_tag, :content_tag_with_haml
134
131
  end
135
132
  end
136
133
 
@@ -160,7 +157,9 @@ module ActionView
160
157
  def form_for_with_haml(object_name, *args, &proc)
161
158
  if block_given? && is_haml?
162
159
  oldproc = proc
163
- proc = proc {|*args| with_tabs(1) {oldproc.call(*args)}}
160
+ proc = haml_bind_proc do |*args|
161
+ with_tabs(1) {oldproc.call(*args)}
162
+ end
164
163
  end
165
164
  res = form_for_without_haml(object_name, *args, &proc)
166
165
  res << "\n" if block_given? && is_haml?
@@ -185,7 +184,10 @@ module ActionView
185
184
  concat haml_indent
186
185
  end
187
186
  res = form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc) + "\n"
188
- concat "\n" if block_given?
187
+ if block_given?
188
+ concat "\n"
189
+ return Haml::Helpers::ErrorReturn.new("form_tag")
190
+ end
189
191
  res
190
192
  else
191
193
  form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc)
@@ -209,6 +211,7 @@ module ActionView
209
211
  end
210
212
  form_for_without_haml(object_name, *args, &proc)
211
213
  concat "\n" if block_given? && is_haml?
214
+ Haml::Helpers::ErrorReturn.new("form_for") if is_haml?
212
215
  end
213
216
  alias_method :form_for_without_haml, :form_for
214
217
  alias_method :form_for, :form_for_with_haml
@@ -92,6 +92,13 @@ module Haml
92
92
  html_escape(text)
93
93
  end
94
94
  end
95
+
96
+ class ErrorReturn
97
+ # Any attempt to treat ErrorReturn as a string should cause it to blow up.
98
+ alias_method :html_safe, :to_s
99
+ alias_method :html_safe?, :to_s
100
+ alias_method :html_safe!, :to_s
101
+ end
95
102
  end
96
103
  end
97
104
 
@@ -112,7 +119,9 @@ module ActionView
112
119
 
113
120
  module FormTagHelper
114
121
  def form_tag_with_haml_xss(*args, &block)
115
- Haml::Util.html_safe(form_tag_without_haml_xss(*args, &block))
122
+ res = form_tag_without_haml_xss(*args, &block)
123
+ res = Haml::Util.html_safe(res) unless block_given?
124
+ res
116
125
  end
117
126
  alias_method :form_tag_without_haml_xss, :form_tag
118
127
  alias_method :form_tag, :form_tag_with_haml_xss
@@ -120,7 +129,9 @@ module ActionView
120
129
 
121
130
  module FormHelper
122
131
  def form_for_with_haml_xss(*args, &block)
123
- Haml::Util.html_safe(form_for_without_haml_xss(*args, &block))
132
+ res = form_for_without_haml_xss(*args, &block)
133
+ return Haml::Util.html_safe(res) if res.is_a?(String)
134
+ return res
124
135
  end
125
136
  alias_method :form_for_without_haml_xss, :form_for
126
137
  alias_method :form_for, :form_for_with_haml_xss
@@ -9,37 +9,75 @@ module Haml
9
9
  # A module containing utility methods that every Hpricot node
10
10
  # should have.
11
11
  module Node
12
+ # Whether this node has already been converted to Haml.
13
+ # Only used for text nodes and elements.
14
+ #
15
+ # @return [Boolean]
16
+ attr_accessor :converted_to_haml
17
+
12
18
  # Returns the Haml representation of the given node.
13
19
  #
14
20
  # @param tabs [Fixnum] The indentation level of the resulting Haml.
15
21
  # @option options (see Haml::HTML#initialize)
16
22
  def to_haml(tabs, options)
17
- parse_text(self.to_s, tabs)
23
+ return "" if converted_to_haml || to_s.strip.empty?
24
+ text = uninterp(self.to_s)
25
+ node = next_node
26
+ while node.is_a?(::Hpricot::Elem) && node.name == "haml:loud"
27
+ node.converted_to_haml = true
28
+ text << '#{' <<
29
+ CGI.unescapeHTML(node.inner_text).gsub(/\n\s*/, ' ').strip << '}'
30
+
31
+ if node.next_node.is_a?(::Hpricot::Text)
32
+ node = node.next_node
33
+ text << uninterp(node.to_s)
34
+ node.converted_to_haml = true
35
+ end
36
+
37
+ node = node.next_node
38
+ end
39
+ return parse_text_with_interpolation(text, tabs)
18
40
  end
19
41
 
20
42
  private
21
43
 
44
+ def erb_to_interpolation(text, options)
45
+ return text unless options[:erb]
46
+ text = CGI.escapeHTML(uninterp(text))
47
+ %w[<haml:loud> </haml:loud>].each {|str| text.gsub!(CGI.escapeHTML(str), str)}
48
+ ::Hpricot::XML(text).children.inject("") do |str, elem|
49
+ if elem.is_a?(::Hpricot::Text)
50
+ str + CGI.unescapeHTML(elem.to_s)
51
+ else # <haml:loud> element
52
+ str + '#{' + CGI.unescapeHTML(elem.innerText.strip) + '}'
53
+ end
54
+ end
55
+ end
56
+
22
57
  def tabulate(tabs)
23
58
  ' ' * tabs
24
59
  end
25
60
 
61
+ def uninterp(text)
62
+ text.gsub('#{', '\#{') #'
63
+ end
64
+
26
65
  def attr_hash
27
66
  attributes.to_hash
28
67
  end
29
68
 
30
69
  def parse_text(text, tabs)
70
+ parse_text_with_interpolation(uninterp(text), tabs)
71
+ end
72
+
73
+ def parse_text_with_interpolation(text, tabs)
31
74
  text.strip!
32
- text.gsub!('#{', '\#{') #'
33
- if text.empty?
34
- String.new
35
- else
36
- lines = text.split("\n")
75
+ return "" if text.empty?
37
76
 
38
- lines.map do |line|
39
- line.strip!
40
- "#{tabulate(tabs)}#{'\\' if Haml::Engine::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n"
41
- end.join
42
- end
77
+ text.split("\n").map do |line|
78
+ line.strip!
79
+ "#{tabulate(tabs)}#{'\\' if Haml::Engine::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n"
80
+ end.join
43
81
  end
44
82
  end
45
83
  end
@@ -65,6 +103,9 @@ require 'hpricot'
65
103
  module Haml
66
104
  # Converts HTML documents into Haml templates.
67
105
  # Depends on [Hpricot](http://github.com/whymirror/hpricot) for HTML parsing.
106
+ # If ERB conversion is being used, also depends on
107
+ # [Erubis](http://www.kuwata-lab.com/erubis) to parse the ERB
108
+ # and [ruby_parser](http://parsetree.rubyforge.org/) to parse the Ruby code.
68
109
  #
69
110
  # Example usage:
70
111
  #
@@ -72,7 +113,7 @@ module Haml
72
113
  # #=> "%a{:href => 'http://google.com'} Blat"
73
114
  class HTML
74
115
  # @param template [String, Hpricot::Node] The HTML template to convert
75
- # @option options :rhtml [Boolean] (false) Whether or not to parse
116
+ # @option options :erb [Boolean] (false) Whether or not to parse
76
117
  # ERB's `<%= %>` and `<% %>` into Haml's `=` and `-`
77
118
  # @option options :xhtml [Boolean] (false) Whether or not to parse
78
119
  # the HTML strictly as XHTML
@@ -86,9 +127,11 @@ module Haml
86
127
  template = template.read
87
128
  end
88
129
 
89
- if @options[:rhtml]
90
- match_to_html(template, /<%=(.*?)-?%>/m, 'loud')
91
- match_to_html(template, /<%-?(.*?)-?%>/m, 'silent')
130
+ template = Haml::Util.check_encoding(template) {|msg, line| raise Haml::Error.new(msg, line)}
131
+
132
+ if @options[:erb]
133
+ require 'haml/html/erb'
134
+ template = ERB.compile(template)
92
135
  end
93
136
 
94
137
  method = @options[:xhtml] ? Hpricot.method(:XML) : method(:Hpricot)
@@ -129,7 +172,9 @@ module Haml
129
172
  class ::Hpricot::CData
130
173
  # @see Haml::HTML::Node#to_haml
131
174
  def to_haml(tabs, options)
132
- "#{tabulate(tabs)}:cdata\n#{parse_text(self.content, tabs + 1)}"
175
+ content = parse_text_with_interpolation(
176
+ erb_to_interpolation(self.content, options), tabs + 1)
177
+ "#{tabulate(tabs)}:cdata\n#{content}"
133
178
  end
134
179
  end
135
180
 
@@ -140,9 +185,7 @@ module Haml
140
185
  def to_haml(tabs, options)
141
186
  attrs = public_id.nil? ? ["", "", ""] :
142
187
  public_id.scan(/DTD\s+([^\s]+)\s*([^\s]*)\s*([^\s]*)\s*\/\//)[0]
143
- if attrs == nil
144
- raise Exception.new("Invalid doctype")
145
- end
188
+ raise Haml::SyntaxError.new("Invalid doctype") if attrs == nil
146
189
 
147
190
  type, version, strictness = attrs.map { |a| a.downcase }
148
191
  if type == "html"
@@ -170,7 +213,17 @@ module Haml
170
213
  class ::Hpricot::Comment
171
214
  # @see Haml::HTML::Node#to_haml
172
215
  def to_haml(tabs, options)
173
- "#{tabulate(tabs)}/\n#{parse_text(self.content, tabs + 1)}"
216
+ content = self.content
217
+ if content =~ /\A(\[[^\]]+\])>(.*)<!\[endif\]\z/m
218
+ condition = $1
219
+ content = $2
220
+ end
221
+
222
+ if content.include?("\n")
223
+ "#{tabulate(tabs)}/#{condition}\n#{parse_text(content, tabs + 1)}"
224
+ else
225
+ "#{tabulate(tabs)}/#{condition} #{content.strip}"
226
+ end
174
227
  end
175
228
  end
176
229
 
@@ -179,32 +232,104 @@ module Haml
179
232
  class ::Hpricot::Elem
180
233
  # @see Haml::HTML::Node#to_haml
181
234
  def to_haml(tabs, options)
182
- output = "#{tabulate(tabs)}"
183
- if options[:rhtml] && name[0...5] == 'haml:'
184
- return output + send("haml_tag_#{name[5..-1]}", CGI.unescapeHTML(self.inner_text))
235
+ return "" if converted_to_haml
236
+ if name == "script" &&
237
+ (attr_hash['type'].nil? || attr_hash['type'] == "text/javascript") &&
238
+ (attr_hash.keys - ['type']).empty?
239
+ return to_haml_filter(:javascript, tabs, options)
240
+ elsif name == "style" &&
241
+ (attr_hash['type'].nil? || attr_hash['type'] == "text/css") &&
242
+ (attr_hash.keys - ['type']).empty?
243
+ return to_haml_filter(:css, tabs, options)
185
244
  end
186
245
 
187
- output += "%#{name}" unless name == 'div' &&
188
- (static_id?(options) || static_classname?(options))
246
+ output = tabulate(tabs)
247
+ if options[:erb] && name[0...5] == 'haml:'
248
+ case name[5..-1]
249
+ when "loud"
250
+ lines = CGI.unescapeHTML(inner_text).split("\n").
251
+ map {|s| s.rstrip}.reject {|s| s.strip.empty?}
252
+ lines.first.gsub!(/^[ \t]*/, "= ")
253
+
254
+ if lines.size > 1 # Multiline script block
255
+ # Normalize the indentation so that the last line is the base
256
+ indent_str = lines.last[/^[ \t]*/]
257
+ indent_re = /^[ \t]{0,#{indent_str.count(" ") + 8 * indent_str.count("\t")}}/
258
+ lines.map! {|s| s.gsub!(indent_re, '')}
259
+
260
+ # Add an extra " " to make it indented relative to "= "
261
+ lines[1..-1].each {|s| s.gsub!(/^/, " ")}
262
+
263
+ # Add | at the end, properly aligned
264
+ length = lines.map {|s| s.size}.max + 1
265
+ lines.map! {|s| "%#{-length}s|" % s}
266
+
267
+ if next_sibling && next_sibling.is_a?(Hpricot::Elem) && next_sibling.name == "haml:loud" &&
268
+ next_sibling.inner_text.split("\n").reject {|s| s.strip.empty?}.size > 1
269
+ lines << "-#"
270
+ end
271
+ end
272
+ return lines.map {|s| output + s + "\n"}.join
273
+ when "silent"
274
+ return CGI.unescapeHTML(inner_text).split("\n").map do |line|
275
+ next "" if line.strip.empty?
276
+ "#{output}- #{line.strip}\n"
277
+ end.join
278
+ when "block"
279
+ return render_children("", tabs, options)
280
+ end
281
+ end
282
+
283
+ output << "%#{name}" unless name == 'div' &&
284
+ (static_id?(options) ||
285
+ static_classname?(options) &&
286
+ attr_hash['class'].split(' ').any?(&method(:haml_css_attr?)))
189
287
 
190
288
  if attr_hash
191
289
  if static_id?(options)
192
- output += "##{attr_hash['id']}"
290
+ output << "##{attr_hash['id']}"
193
291
  remove_attribute('id')
194
292
  end
195
293
  if static_classname?(options)
196
- attr_hash['class'].split(' ').each { |c| output += ".#{c}" }
294
+ leftover = attr_hash['class'].split(' ').reject do |c|
295
+ next unless haml_css_attr?(c)
296
+ output << ".#{c}"
297
+ end
197
298
  remove_attribute('class')
299
+ set_attribute('class', leftover.join(' ')) unless leftover.empty?
198
300
  end
199
- output += haml_attributes(options) if attr_hash.length > 0
301
+ output << haml_attributes(options) if attr_hash.length > 0
200
302
  end
201
303
 
202
- (self.children || []).inject(output + "\n") do |output, child|
203
- output + child.to_haml(tabs + 1, options)
304
+ output << "/" if empty? && !etag
305
+
306
+ if children && children.size == 1
307
+ child = children.first
308
+ if child.is_a?(::Hpricot::Text)
309
+ if !child.to_s.include?("\n")
310
+ text = child.to_haml(tabs + 1, options)
311
+ return output + " " + text.lstrip.gsub(/^\\/, '') unless text.chomp.include?("\n")
312
+ return output + "\n" + text
313
+ elsif ["pre", "textarea"].include?(name) ||
314
+ (name == "code" && parent.is_a?(::Hpricot::Elem) && parent.name == "pre")
315
+ return output + "\n#{tabulate(tabs + 1)}:preserve\n" +
316
+ innerText.gsub(/^/, tabulate(tabs + 2))
317
+ end
318
+ elsif child.is_a?(::Hpricot::Elem) && child.name == "haml:loud"
319
+ return output + child.to_haml(tabs + 1, options).lstrip
320
+ end
204
321
  end
322
+
323
+ render_children(output + "\n", tabs, options)
205
324
  end
206
325
 
207
326
  private
327
+
328
+ def render_children(so_far, tabs, options)
329
+ (self.children || []).inject(so_far) do |output, child|
330
+ output + child.to_haml(tabs + 1, options)
331
+ end
332
+ end
208
333
 
209
334
  def dynamic_attributes
210
335
  @dynamic_attributes ||= begin
@@ -221,47 +346,53 @@ module Haml
221
346
  end
222
347
  end
223
348
 
224
- def haml_tag_loud(text)
225
- "= #{text.gsub(/\n\s*/, ' ').strip}\n"
226
- end
349
+ def to_haml_filter(filter, tabs, options)
350
+ content =
351
+ if children.first.is_a?(::Hpricot::CData)
352
+ children.first.content
353
+ else
354
+ CGI.unescapeHTML(self.innerText)
355
+ end
356
+
357
+ content = erb_to_interpolation(content, options)
358
+ content.gsub!(/\A\s*\n(\s*)/, '\1')
359
+ original_indent = content[/\A(\s*)/, 1]
360
+ if content.split("\n").all? {|l| l.strip.empty? || l =~ /^#{original_indent}/}
361
+ content.gsub!(/^#{original_indent}/, tabulate(tabs + 1))
362
+ end
227
363
 
228
- def haml_tag_silent(text)
229
- text.split("\n").map { |line| "- #{line.strip}\n" }.join
364
+ "#{tabulate(tabs)}:#{filter}\n#{content}"
230
365
  end
231
366
 
232
367
  def static_attribute?(name, options)
233
- attr_hash[name] and !dynamic_attribute?(name, options)
368
+ attr_hash[name] && !dynamic_attribute?(name, options)
234
369
  end
235
370
 
236
371
  def dynamic_attribute?(name, options)
237
- options[:rhtml] and dynamic_attributes.key?(name)
372
+ options[:erb] and dynamic_attributes.key?(name)
238
373
  end
239
374
 
240
375
  def static_id?(options)
241
- static_attribute?('id', options)
376
+ static_attribute?('id', options) && haml_css_attr?(attr_hash['id'])
242
377
  end
243
378
 
244
379
  def static_classname?(options)
245
380
  static_attribute?('class', options)
246
381
  end
247
382
 
383
+ def haml_css_attr?(attr)
384
+ attr =~ /^[-:\w]+$/
385
+ end
386
+
248
387
  # Returns a string representation of an attributes hash
249
388
  # that's prettier than that produced by Hash#inspect
250
389
  def haml_attributes(options)
251
- attrs = attr_hash.map do |name, value|
390
+ attrs = attr_hash.sort.map do |name, value|
252
391
  value = dynamic_attribute?(name, options) ? dynamic_attributes[name] : value.inspect
253
392
  name = name.index(/\W/) ? name.inspect : ":#{name}"
254
393
  "#{name} => #{value}"
255
394
  end
256
- "{ #{attrs.join(', ')} }"
257
- end
258
- end
259
-
260
- private
261
-
262
- def match_to_html(string, regex, tag)
263
- string.gsub!(regex) do
264
- "<haml:#{tag}>#{CGI.escapeHTML($1)}</haml:#{tag}>"
395
+ "{#{attrs.join(', ')}}"
265
396
  end
266
397
  end
267
398
  end