Skip to content

Commit

Permalink
Support ERB interpolation in HTML attributes (#67)
Browse files Browse the repository at this point in the history
This pull request adds support for ERB interpolation in HTML attributes.
This allows use-cases like:

**ERB Input:**
```erb
<div class="<%= classes_helper %>">Text</div>
```

**Output:**
```ruby
div(class: classes_helper) { "Text" }
```



Fixes #48
  • Loading branch information
marcoroth committed Jan 23, 2023
1 parent 235c0d1 commit 1069460
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 5 deletions.
17 changes: 17 additions & 0 deletions gem/lib/phlexing/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ def unescape(source)
CGI.unescapeHTML(source)
end

def unwrap_erb(source)
source
.delete_prefix("<%==")
.delete_prefix("<%=")
.delete_prefix("<%-")
.delete_prefix("<%#")
.delete_prefix("<% #")
.delete_prefix("<%")
.delete_suffix("-%>")
.delete_suffix("%>")
.strip
end

def tag_name(node)
return "template_tag" if node.name == "template-tag"

Expand Down Expand Up @@ -87,6 +100,10 @@ def string_output?(node)
!(blocklist_matched || filter_matched)
end

def children?(node)
node.children.length >= 1
end

def multiple_children?(node)
node.children.length > 1
end
Expand Down
16 changes: 15 additions & 1 deletion gem/lib/phlexing/minifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@
module Phlexing
class Minifier
def self.minify(source)
HtmlPress.press(source.to_s)
minified = HtmlPress.press(source.to_s)
minified = minify_html_entities(minified)

minified
rescue StandardError
source
end

def self.minify_html_entities(source)
source
.gsub("& lt;", "&lt;")
.gsub("& quot;", "&quot;")
.gsub("& gt;", "&gt;")
.gsub("& #amp;", "&#amp;")
.gsub("& #38;", "&#38;")
.gsub("& #60;", "&#60;")
.gsub("& #62;", "&#62;")
end
end
end
17 changes: 17 additions & 0 deletions gem/lib/phlexing/patches/html_press.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,25 @@

module HtmlPress
class Html
# We want to preserve HTML comments in the minification step
# so we can output them again in the phlex template
def process_html_comments(out)
out
end
end

class Entities
# The minification step turned this input
# <div data-erb-class="&lt;%= something? ? &quot;class-1&quot; : &quot;class-2&quot; %&gt;">Text</div>
#
# into this output:
# <div data-erb-class="&lt;%= something? ? " class-1" :" class-2" %& gt;">Text</div>
#
# which in our wasn't ideal, because nokogiri parsed it as:
# <div data-erb-class="<%= something? ? " class-1=" :" class-2="%>">Text</div>
#
def minify(out)
out
end
end
end
38 changes: 34 additions & 4 deletions gem/lib/phlexing/template_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,45 @@ def handle_attributes(node)
attributes = []

node.attributes.each_value do |attribute|
attributes << String.new.tap { |s|
s << arg(attribute.name.underscore)
s << quote(attribute.value)
}
attributes << handle_attribute(attribute)
end

parens(attributes.join(", "))
end

def handle_attribute(attribute)
if attribute.name.start_with?(/data-erb-(\d+)+/)
handle_erb_interpolation_in_tag(attribute)
elsif attribute.name.start_with?("data-erb-")
handle_erb_attribute_output(attribute)
else
handle_html_attribute_output(attribute)
end
end

def handle_html_attribute_output(attribute)
String.new.tap { |s|
s << arg(attribute.name.underscore)
s << quote(attribute.value)
}
end

def handle_erb_attribute_output(attribute)
String.new.tap { |s|
s << arg(attribute.name.delete_prefix("data-erb-").underscore)

s << if attribute.value.start_with?("<%=")
parens(unwrap_erb(attribute.value))
else
quote("FIXME: #{unwrap_erb(attribute.value)}")
end
}
end

def handle_erb_interpolation_in_tag(attribute)
"**#{parens("#{unwrap_erb(unescape(attribute.value))}: true")}"
end

def handle_erb_safe_node(node)
if siblings?(node) && string_output?(node)
handle_text_output(node.text.strip)
Expand Down
96 changes: 96 additions & 0 deletions gem/test/phlexing/converter/attributes_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# frozen_string_literal: true

require_relative "../../test_helper"

class Phlexing::Converter::AttributesTest < Minitest::Spec
it "should interpolate ERB in attributes using <%=" do
html = <<~HTML.strip
<div class="<%= classes_helper %>">Text</div>
HTML

expected = <<~PHLEX.strip
div(class: (classes_helper)) { "Text" }
PHLEX

assert_phlex_template expected, html
end

it "should interpolate ERB in multiple attributes using <%=" do
html = <<~HTML.strip
<div class="<%= classes_helper %>" style="<%= true? ? "background: red" : "background: blue" %>">Text</div>
HTML

expected = <<~PHLEX.strip
div(
class: (classes_helper),
style: (true? ? "background: red" : "background: blue")
) { "Text" }
PHLEX

assert_phlex_template expected, html
end

it "should not interpolate ERB in attributes using <%" do
html = <<~HTML.strip
<div class="<% classes_helper %>">Text</div>
HTML

expected = <<~PHLEX.strip
div(class: "FIXME: classes_helper") { "Text" }
PHLEX

assert_phlex_template expected, html
end

it "should interpolate ERB in attributes using <%= and if" do
html = <<~HTML.strip
<div class="<%= classes_helper if true %>">Text</div>
HTML

expected = <<~PHLEX.strip
div(class: (classes_helper if true)) { "Text" }
PHLEX

assert_phlex_template expected, html
end

it "should interpolate ERB conditional in attribute" do
html = <<~HTML.strip
<div class="<%= something? ? "class-1" : "class-2" %>">Text</div>
HTML

expected = <<~PHLEX.strip
div(class: (something? ? "class-1" : "class-2")) { "Text" }
PHLEX

assert_phlex_template expected, html
end

it "should interpolate ERB in tag" do
html = <<~HTML.strip
<input type="checkbox" <%= "selected" %> />
HTML

expected = <<~PHLEX.strip
input(type: %(checkbox), **(" selected": true))
PHLEX

assert_phlex_template expected, html
end

xit "should interpolate ERB in tag with interpoltion" do
# rubocop:disable Lint/InterpolationCheck
html = '<input type="checkbox" <%= "data-#{Time.now.to_i}"%> />'
expected = 'input(type: %(checkbox), **(" data-#{Time.now.to_i}": true))'
# rubocop:enable Lint/InterpolationCheck

assert_phlex_template expected, html
end

xit "should interpolate ERB in tag with conditional" do
html = %(<input type="checkbox" <%= "selected" if true %> />)
expected = %(input(type: %(checkbox), **(" selected": true)))

assert_phlex_template expected, html
end
end

0 comments on commit 1069460

Please sign in to comment.