diff --git a/lib/haml.rb b/lib/haml.rb index 6c1ee998..27bb7688 100644 --- a/lib/haml.rb +++ b/lib/haml.rb @@ -342,6 +342,61 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # # # +# ==== > and < +# +# > and < give you more control over the whitespace near a tag. +# > will remove all whitespace surrounding a tag, +# while < will remove all whitespace immediately within a tag. +# You can think of them as alligators eating the whitespace: +# > faces out of the tag and eats the whitespace on the outside, +# and < faces into the tag and eats the whitespace on the inside. +# They're placed at the end of a tag definition, +# after class, id, and attribute declarations +# but before / or =. +# For example: +# +# %blockquote< +# %div +# Foo! +# +# is compiled to: +# +#
+# Foo! +#
+# +# And: +# +# %img +# %img> +# %img +# +# is compiled to: +# +# +# +# And: +# +# %p<= "Foo\nBar" +# +# is compiled to: +# +#

Foo +# Bar

+# +# And finally: +# +# %img +# %pre>< +# foo +# bar +# %img +# +# is compiled to: +# +#
foo
+#   bar
+# # ==== = # # = is placed at the end of a tag definition, diff --git a/lib/haml/buffer.rb b/lib/haml/buffer.rb index 030c97d7..5d5103e3 100644 --- a/lib/haml/buffer.rb +++ b/lib/haml/buffer.rb @@ -84,19 +84,23 @@ module Haml # Renders +text+ with the proper tabulation. This also deals with # making a possible one-line tag one line or not. - def push_text(text, tab_change = 0) + def push_text(text, dont_tab_up = false, tab_change = 0) if @tabulation > 0 && !@options[:ugly] - # Have to push every line in by the extra user set tabulation - text.gsub!(/^/m, tabs) + # Have to push every line in by the extra user set tabulation. + # Don't push lines with just whitespace, though, + # because that screws up precompiled indentation. + text.gsub!(/^(?!\s+$)/m, tabs) + text.sub!(tabs, '') if dont_tab_up end @buffer << text @real_tabs += tab_change + @dont_tab_up_next_line = false end # Properly formats the output of a script that was run in the # instance_eval. - def push_script(result, preserve_script, close_tag = nil, preserve_tag = false, escape_html = false) + def push_script(result, preserve_script, in_tag = false, preserve_tag = false, escape_html = false) tabulation = @real_tabs if preserve_tag @@ -114,39 +118,41 @@ module Haml result = html_escape(result) if escape_html has_newline = result.include?("\n") - if close_tag && (@options[:ugly] || !has_newline || preserve_tag) - @buffer << "#{result}\n" + if in_tag && (@options[:ugly] || !has_newline || preserve_tag) + @buffer << result @real_tabs -= 1 - else - if close_tag - @buffer << "\n" - end + return + end - # Precompiled tabulation may be wrong - if @tabulation > 0 && !close_tag - result = tabs + result - end + if in_tag + @buffer << "\n" + end - if has_newline && !@options[:ugly] - result = result.gsub "\n", "\n" + tabs(tabulation) + # Precompiled tabulation may be wrong + if @tabulation > 0 && !in_tag + result = tabs + result + end - # Add tabulation if it wasn't precompiled - result = tabs(tabulation) + result if close_tag - end - @buffer << "#{result}\n" + if has_newline && !@options[:ugly] + result = result.gsub "\n", "\n" + tabs(tabulation) - if close_tag - # We never get here if @options[:ugly] is true - @buffer << "#{tabs(tabulation-1)}\n" - @real_tabs -= 1 - end + # Add tabulation if it wasn't precompiled + result = tabs(tabulation) + result if in_tag + end + @buffer << "#{result}\n" + + if in_tag + # We never get here if @options[:ugly] is true + @buffer << tabs(tabulation-1) + @real_tabs -= 1 end nil end # Takes the various information about the opening tag for an # element, formats it, and adds it to the buffer. - def open_tag(name, self_closing, try_one_line, preserve_tag, escape_html, class_id, obj_ref, content, *attributes_hashes) + def open_tag(name, self_closing, try_one_line, preserve_tag, escape_html, class_id, + nuke_outer_whitespace, nuke_inner_whitespace, obj_ref, content, *attributes_hashes) tabulation = @real_tabs attributes = class_id @@ -157,7 +163,7 @@ module Haml self.class.merge_attrs(attributes, parse_object_ref(obj_ref)) if obj_ref if self_closing - str = " />\n" + str = " />" + (nuke_outer_whitespace ? "" : "\n") elsif try_one_line || preserve_tag str = ">" else @@ -165,10 +171,10 @@ module Haml end attributes = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes) - @buffer << "#{@options[:ugly] ? '' : tabs(tabulation)}<#{name}#{attributes}#{str}" + @buffer << "#{nuke_outer_whitespace || @options[:ugly] ? '' : tabs(tabulation)}<#{name}#{attributes}#{str}" if content - @buffer << "#{content}\n" + @buffer << "#{content}" << (nuke_outer_whitespace ? "" : "\n") elsif !self_closing @real_tabs += 1 end diff --git a/lib/haml/precompiler.rb b/lib/haml/precompiler.rb index 6e625614..6d769e74 100644 --- a/lib/haml/precompiler.rb +++ b/lib/haml/precompiler.rb @@ -199,12 +199,12 @@ END when ELEMENT; render_tag(text) when COMMENT; render_comment(text[1..-1].strip) when SANITIZE - return push_script(unescape_interpolation(text[3..-1].strip), false, nil, false, true) if text[1..2] == "==" - return push_script(text[2..-1].strip, false, nil, false, true) if text[1] == SCRIPT + return push_script(unescape_interpolation(text[3..-1].strip), false, false, false, true) if text[1..2] == "==" + return push_script(text[2..-1].strip, false, false, false, true) if text[1] == SCRIPT push_plain text when SCRIPT return push_script(unescape_interpolation(text[2..-1].strip), false) if text[1] == SCRIPT - return push_script(text[1..-1], false, nil, false, true) if options[:escape_html] + return push_script(text[1..-1], false, false, false, true) if options[:escape_html] push_script(text[1..-1], false) when FLAT_SCRIPT; push_flat_script(text[1..-1]) when SILENT_SCRIPT @@ -277,8 +277,9 @@ END # Adds text to @buffer with appropriate tabulation # without parsing it. - def push_merged_text(text, tab_change = 0) - @merged_text << (@options[:ugly] ? text : "#{' ' * @output_tabs}#{text}") + def push_merged_text(text, tab_change = 0, indent = true) + @merged_text << (!indent || @dont_indent_next_line || @options[:ugly] ? text : "#{' ' * @output_tabs}#{text}") + @dont_indent_next_line = false @tab_change += tab_change end @@ -295,9 +296,11 @@ END return if @merged_text.empty? @precompiled << "_hamlout.push_text(#{@merged_text.dump}" + @precompiled << ", #{@dont_tab_up_next_text.inspect}" if @dont_tab_up_next_text || @tab_change != 0 @precompiled << ", #{@tab_change}" if @tab_change != 0 @precompiled << ");" @merged_text = '' + @dont_tab_up_next_text = false @tab_change = 0 end @@ -324,9 +327,9 @@ END # # If preserve_script is true, Haml::Helpers#find_and_flatten is run on # the result before it is added to @buffer - def push_script(text, preserve_script, close_tag = nil, preserve_tag = false, escape_html = false) + def push_script(text, preserve_script, in_tag = false, preserve_tag = false, escape_html = false) # Prerender tabulation unless we're in a tag - push_merged_text '' unless close_tag + push_merged_text '' unless in_tag flush_merged_text return if options[:suppress_eval] @@ -335,7 +338,8 @@ END push_silent "haml_temp = #{text}" newline_now - out = "haml_temp = _hamlout.push_script(haml_temp, #{preserve_script.inspect}, #{close_tag.inspect}, #{preserve_tag.inspect}, #{escape_html.inspect});" + args = [preserve_script, in_tag, preserve_tag, escape_html].map { |a| a.inspect }.join(', ') + out = "haml_temp = _hamlout.push_script(haml_temp, #{args});" if @block_opened push_and_tabulate([:loud, out]) else @@ -374,10 +378,12 @@ END # Puts a line in @precompiled that will add the closing tag of # the most recently opened tag. - def close_tag(tag) + def close_tag(value) + tag, nuke_outer_whitespace, nuke_inner_whitespace = value @output_tabs -= 1 @template_tabs -= 1 - push_text("", -1) + push_merged_text("" + (nuke_outer_whitespace ? "" : "\n"), -1) + @dont_indent_next_line = nuke_outer_whitespace end # Closes a Ruby block. @@ -505,10 +511,14 @@ END if rest object_ref, rest = balance(rest, ?[, ?]) if rest[0] == ?[ attributes_hash, rest = parse_attributes(rest) if rest[0] == ?{ && attributes_hash.nil? - action, value = rest.scan(/([=\/\~&!]?)?(.*)?/)[0] + nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0] + nuke_whitespace ||= '' + nuke_outer_whitespace = nuke_whitespace.include? '>' + nuke_inner_whitespace = nuke_whitespace.include? '<' end value = value.to_s.strip - [tag_name, attributes, attributes_hash, object_ref, action, value] + [tag_name, attributes, attributes_hash, object_ref, nuke_outer_whitespace, + nuke_inner_whitespace, action, value] end def parse_attributes(line) @@ -521,10 +531,21 @@ END # Parses a line that will render as an XHTML tag, and adds the code that will # render that tag to @precompiled. def render_tag(line) - tag_name, attributes, attributes_hash, object_ref, action, value = parse_tag(line) + tag_name, attributes, attributes_hash, object_ref, nuke_outer_whitespace, + nuke_inner_whitespace, action, value = parse_tag(line) raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/ + # Get rid of whitespace outside of the tag if we need to + if nuke_outer_whitespace + unless @merged_text.empty? + @merged_text.rstrip! + else + push_silent("_erbout.rstrip!", false) + @dont_tab_up_next_text = true + end + end + preserve_tag = options[:preserve].include?(tag_name) case action @@ -566,29 +587,42 @@ END tag_closed = !@block_opened && !self_closing && !parse open_tag = prerender_tag(tag_name, self_closing, attributes) - open_tag << "#{value}" if tag_closed - open_tag << "\n" unless parse + if tag_closed + open_tag << "#{value}" + open_tag << "\n" unless nuke_outer_whitespace + else + open_tag << "\n" unless parse || (self_closing && nuke_outer_whitespace) + end + + push_merged_text(open_tag, tag_closed || self_closing ? 0 : 1, + !nuke_outer_whitespace) - push_merged_text(open_tag, tag_closed || self_closing ? 0 : 1) + @dont_indent_next_line = nuke_outer_whitespace && !@block_opened return if tag_closed else flush_merged_text content = value.empty? || parse ? 'nil' : value.dump attributes_hash = ', ' + attributes_hash if attributes_hash - push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{self_closing.inspect}, #{(!@block_opened).inspect}, #{preserve_tag.inspect}, #{escape_html.inspect}, #{attributes.inspect}, #{object_ref}, #{content}#{attributes_hash})" + args = [tag_name, self_closing, !@block_opened, preserve_tag, escape_html, + attributes, nuke_outer_whitespace, nuke_inner_whitespace + ].map { |v| v.inspect }.join(', ') + push_silent "_hamlout.open_tag(#{args}, #{object_ref}, #{content}#{attributes_hash})" + @dont_tab_up_next_text = @dont_indent_next_line = nuke_outer_whitespace && !@block_opened end return if self_closing if value.empty? - push_and_tabulate([:element, tag_name]) + push_and_tabulate([:element, [tag_name, nuke_outer_whitespace, nuke_inner_whitespace]]) @output_tabs += 1 return end if parse flush_merged_text - push_script(value, preserve_script, tag_name, preserve_tag, escape_html) + push_script(value, preserve_script, true, preserve_tag, escape_html) + @dont_tab_up_next_text = true + concat_merged_text("" + (nuke_outer_whitespace ? "" : "\n")) end end diff --git a/test/haml/results/nuke_outer_whitespace.xhtml b/test/haml/results/nuke_outer_whitespace.xhtml new file mode 100644 index 00000000..a31cde3a --- /dev/null +++ b/test/haml/results/nuke_outer_whitespace.xhtml @@ -0,0 +1,148 @@ +

+

+ Foo +

+

+

+

+ Foo +

+

+

+

Foo

+

+

+

Foo

+

+

+

+ Foo +

+

+

+

+ Foo +

+

+

+

Foo

+

+

+

Foo

+

+

+

+ Foo + Bar +

+

+

+

+ Foo + Bar +

+

+

+

+ Foo + Bar +

+

+

+

+ Foo + Bar +

+

+

+

+ foo + Foo + bar +

+

+

+

+ foo + Foo + bar +

+

+

+

+ fooFoobar +

+

+

+

+ fooFoobar +

+

+

+

+ foo + Foo + bar +

+

+

+

+ foo + Foo + bar +

+

+

+

+ fooFoobar +

+

+

+

+ fooFoobar +

+

+

+

+ foo + Foo + Bar + bar +

+

+

+

+ foo + Foo + Bar + bar +

+

+

+

+ foo + Foo + Bar + bar +

+

+

+

+ foo + Foo + Bar + bar +

+

+

+

+

+

+

+

+

+

+

+

+

+

diff --git a/test/haml/results/tag_parsing.xhtml b/test/haml/results/tag_parsing.xhtml index 5e95f34b..e8178aa1 100644 --- a/test/haml/results/tag_parsing.xhtml +++ b/test/haml/results/tag_parsing.xhtml @@ -21,7 +21,3 @@
f
g
-
- <{ :a => :b } -
>{ :c => :d }
-
diff --git a/test/haml/template_test.rb b/test/haml/template_test.rb index 1d82b7bd..ecb8ba3e 100644 --- a/test/haml/template_test.rb +++ b/test/haml/template_test.rb @@ -15,7 +15,7 @@ class TemplateTest < Test::Unit::TestCase @@templates = %w{ very_basic standard helpers whitespace_handling original_engine list helpful silent_script tag_parsing just_stuff partials - filters } + filters nuke_outer_whitespace } def setup Haml::Template.options = { :filters => { 'test'=>TestFilter } } diff --git a/test/haml/templates/nuke_outer_whitespace.haml b/test/haml/templates/nuke_outer_whitespace.haml new file mode 100644 index 00000000..1e2a7f5f --- /dev/null +++ b/test/haml/templates/nuke_outer_whitespace.haml @@ -0,0 +1,144 @@ +%p + %p + %q> + Foo +%p + %p + %q{:a => 1 + 1}> + Foo +%p + %p + %q> Foo +%p + %p + %q{:a => 1 + 1}> Foo +%p + %p + %q> + = "Foo" +%p + %p + %q{:a => 1 + 1}> + = "Foo" +%p + %p + %q>= "Foo" +%p + %p + %q{:a => 1 + 1}>= "Foo" +%p + %p + %q> + = "Foo\nBar" +%p + %p + %q{:a => 1 + 1}> + = "Foo\nBar" +%p + %p + %q>= "Foo\nBar" +%p + %p + %q{:a => 1 + 1}>= "Foo\nBar" +%p + %p + - tab_up + foo + %q> + Foo + bar + - tab_down +%p + %p + - tab_up + foo + %q{:a => 1 + 1}> + Foo + bar + - tab_down +%p + %p + - tab_up + foo + %q> Foo + bar + - tab_down +%p + %p + - tab_up + foo + %q{:a => 1 + 1}> Foo + bar + - tab_down +%p + %p + - tab_up + foo + %q> + = "Foo" + bar + - tab_down +%p + %p + - tab_up + foo + %q{:a => 1 + 1}> + = "Foo" + bar + - tab_down +%p + %p + - tab_up + foo + %q>= "Foo" + bar + - tab_down +%p + %p + - tab_up + foo + %q{:a => 1 + 1}>= "Foo" + bar + - tab_down +%p + %p + - tab_up + foo + %q> + = "Foo\nBar" + bar + - tab_down +%p + %p + - tab_up + foo + %q{:a => 1 + 1}> + = "Foo\nBar" + bar + - tab_down +%p + %p + - tab_up + foo + %q>= "Foo\nBar" + bar + - tab_down +%p + %p + - tab_up + foo + %q{:a => 1 + 1}>= "Foo\nBar" + bar + - tab_down +%p + %p + %q> +%p + %p + %q>/ +%p + %p + %q{:a => 1 + 1}> +%p + %p + %q{:a => 1 + 1}>/ diff --git a/test/haml/templates/tag_parsing.haml b/test/haml/templates/tag_parsing.haml index 728a7380..f142ebbd 100644 --- a/test/haml/templates/tag_parsing.haml +++ b/test/haml/templates/tag_parsing.haml @@ -19,6 +19,3 @@ .foo16 e .123 f .foo2u g -%div.broken - %foo<{ :a => :b } - .foo>{ :c => :d }