Context
In my armature
shard, I include a templating engine which is mostly a copy/paste of ECR except it sanitizes the output for HTML by default:
<!-- This will be HTML-escaped -->
<%= content %>
<!-- This will be output raw -->
<%== content %>
I’m working on block-capture support like what the erubi
gem uses (or used to use, apparently, until last month) for when you need to wrap the block’s content inside of something else. For example, a Form
component could be used like this:
<%|== Form.new method: "POST", action: action do |f| %>
<%== f.input "name" %>
<%| end %>
And the code could look something like this (note the to_s(io)
method):
struct Form
def initialize(@method : String, @action : String, &@block : self -> Nil)
end
def input(name)
Input.new name
end
def to_s(io) : Nil
io << "<form>"
@block.call self
io << "</form>"
end
record Input, name : String do
def to_s(io) : Nil
io << %{<input name="} << name << %{">}
end
end
end
Problem
Now, for this particular component I want to output raw HTML, but when testing with HTML sanitization enabled, the block output appears in the result ahead of the header/trailer content:
<%|= Form.new method: "POST", action: "/" do |f| %>
<%== f.input "name" %>
<%| end %>
<!-- result — notice that the `input` tag comes before the `form` -->
<input name="name">
<form method="POST" action="/"></form>
This doesn’t happen when I output the raw content, so I’ve narrowed it down to the fact that when I call .to_s
on the object that sanitizes the object’s output for HTML, it’s passed a String::Builder
but the block is piping its output directly to the original IO
object.
So I’m trying to figure out how to make Armature::HTML::SanitizableValue
pipe directly to the same IO
while also HTML-escaping the object’s output, but I can’t figure out how to do that. Any ideas?