rtomayko / tilt

Generic interface to multiple Ruby template engines
http://github.com/rtomayko/tilt
Other
1.95k stars 225 forks source link

Nesting a rendering method in Tilt results in out-of-order output #325

Closed bradgessler closed 2 years ago

bradgessler commented 6 years ago

Given the following chunk of Ruby code:

require "tilt"

def render(content, &block)
  Tilt::HamlTemplate.new('haml'){ content }.render(self, &block)
end

haml_block_chunk =%[%h1 I am the title
=render "%center=yield" do
  %h2 This stuff is all center aligned
  %p=yield]

template_nested_output = render haml_block_chunk do
  "I should appear inside of the center element"
end

puts template_nested_output

I expect template_nested_output to return the following result:

<h1>I am the title</h1>
<center>
<h2>This stuff is all center aligned</h2>
<p>I should appear inside of the center element</p>
</center>

but the actual result for template_nested_output is:

<h1>I am the title</h1>
<h2>This stuff is all center aligned</h2>
<p>I should appear inside of the center element</p>
<center><h1>I am the title</h1>
<h2>This stuff is all center aligned</h2>
<p>I should appear inside of the center element</p>
</center>

The code above my appear to be convoluted, but its the same pattern that I'm trying to use in a static site generator to get a render method working from within a template.

I'm not sure if this is a bug or if I'm making too big of an assumption for this to work. Maybe Tilt wasn't designed for this use case? Assuming that's true I'd propose either documentation that states this or documentation that shows potential work arounds.

judofyr commented 6 years ago

Hm. That does seem a bit odd indeed. What version of Haml are you using?

bradgessler commented 6 years ago

The latest versions of tilt and haml. Here's the Gemfile.lock

GEM
  remote: https://rubygems.org/
  specs:
    haml (5.0.4)
      temple (>= 0.8.0)
      tilt
    temple (0.8.0)
    tilt (2.0.8)

PLATFORMS
  ruby

DEPENDENCIES
  haml
  tilt

BUNDLED WITH
   1.16.0
judofyr commented 6 years ago

Hm. Maybe @k0kubun can answer this. The latest Haml ships with its own Tilt-template-class so there's not much I can do in the Tilt repo either way.

k0kubun commented 6 years ago

The latest Haml ships with its own Tilt-template-class

Which class are you mentioning? Temple has Temple::Templates::Tilt.create API but Haml is not using it as far as I know. The Tilt::HamlTemplate written in this issue just depends on ::Haml::TempleEngine, and it does nothing related to Tilt.


I took a look for a short time, but I couldn't understand why it's working so for now. My first assumption was unintentionally overwriting @haml_buffer instance variable but it seems not the case because compiled code seems to have the guard for it. Let me check this when I have enough time...

If you can reproduce the same behavior using Haml::Engine#def_method, you can file it for Haml repository.

jeremyevans commented 2 years ago

I can reproduce this issue. Here's the template code generated:

        TOPOBJECT.class_eval do
          def __tilt_18440(locals)

begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, {});_erbout = _hamlout.buffer;            __in_erb_template = true;            _haml_locals = locals;;_hamlout.buffer << ("<h1>I am the title</h1>\n".freeze);;
haml_temp = render "%center=yield" do
; _hamlout.buffer << ("<h2>This stuff is all center aligned</h2>\n<p>".freeze);;
; _hamlout.buffer << (((yield
).to_s).to_s);; _hamlout.buffer << ("</p>\n".freeze);; end;; _hamlout.buffer << ((haml_temp.to_s;).to_s);;_erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;

end;end;

This uses the equivalent of a stack, with @haml_buffer = Haml::Buffer.new(haml_buffer, {}) pushing onto the stack, and @haml_buffer = @haml_buffer.upper if @haml_buffer popping off the stack.

I'm guessing the problem is that inside the render "%center=yield" block, the template code appends to _hamlout.buffer (the cached @haml_buffer from the outer render), instead of @haml_buffer.buffer (the @haml_buffer from the inner render)

Sure enough, using @haml_buffer.buffer fixes it:

def render(content, &block)
  Tilt::HamlTemplate.new('haml'){ content }.render(self, &block)
end

haml_block_chunk =%[%h1 I am the title
=render "%center=yield" do
  %h2 This stuff is all center aligned
  %p=yield]

t = Tilt::HamlTemplate.new('haml'){ haml_block_chunk }

puts(t.render{"I should appear inside of the center element"})
# incorrect output

          def __tilt_18440(locals)
begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, {});_erbout = _hamlout.buffer;            __in_erb_template = true;            _haml_locals = locals;;_hamlout.buffer << ("<h1>I am the title</h1>\n".freeze);;
haml_temp = render "%center=yield" do
# Use current buffer instead of cached buffer
; @haml_buffer.buffer << ("<h2>This stuff is all center aligned</h2>\n<p>".freeze);;
; @haml_buffer.buffer << (((yield
).to_s).to_s);; _hamlout.buffer << ("</p>\n".freeze);; end;; _hamlout.buffer << ((haml_temp.to_s;).to_s);;_erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;

end;
METHOD = Object.instance_method(:__tilt_18440)
def t.compiled_method(*) METHOD end

puts(t.render{"I should appear inside of the center element"})
# expected output

In any case, this is a bug in haml, since all of the related code is in Haml::TempleEngine, not in Tilt itself (Tilt has something similar, but for haml <5). @k0kubun does that give you enough information to fix the issue?

k0kubun commented 2 years ago

Thanks for digging into it. It's kind of hard to remember all the contexts immediately, so I'll work on this hopefully this weekend.

k0kubun commented 2 years ago

I fixed the issue in not-released-yet Haml 6 https://github.com/haml/haml/pull/1081. Note that you'd also need to pass escape_html: false because Haml 6 made escape_html: true the default behavior, just like Slim.

As I'm not an owner of haml.gem, I cannot release the fixed version myself. Please consult gem owners about it. I'm also not sure if the current Haml 5 is maintained either.

Regardless, I agree with Jeremy that this should be fixed on the Haml side for Haml 5+. You may close this issue in Tilt.

k0kubun commented 2 years ago

I ended up getting permission to gem-push it, so I did. Please try Haml 6.0.0.beta.1.