Created
February 24, 2019 16:13
-
-
Save zeitnot/959d5501bb2e15858cca87a3d9db4cb6 to your computer and use it in GitHub Desktop.
Ruby html builder with DSL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'test/unit' | |
class HtmlBuilder < BasicObject | |
attr_accessor :html_data | |
SELF_CLOSING_TAGS = %i(area base br col embed iframe hr img input link meta param source track wbr command keygen menuitem) | |
class << self | |
attr_accessor :output | |
def build(&block) | |
object = new | |
object.instance_eval(&block) | |
self.output = object.html_data | |
end | |
def clear_output | |
self.output = nil | |
end | |
end | |
private | |
def build_closing_tag(tag) | |
return if SELF_CLOSING_TAGS.member?(tag) | |
"</#{tag}>" | |
end | |
def build_opening_tag(tag, *attributes, **hash_attributes) | |
tag_str = "<#{tag}" | |
tag_str << ' ' << hash_attributes.map{ |key,val| "#{key}='#{val}'" }.join(' ') if hash_attributes.any? | |
if SELF_CLOSING_TAGS.member?(tag) | |
tag_str << '/>' | |
else | |
tag_str << '>' | |
end | |
end | |
def method_missing(name, *args, &block) | |
self.html_data ||= '' | |
self.html_data += build_opening_tag(name, *args) | |
if str = args.first | |
self.html_data += str if str.is_a?(::String) | |
end | |
if ::Kernel.block_given? | |
call_block = block.call | |
if call_block.is_a?(::Proc) | |
call_block.call | |
else | |
self.html_data += call_block.to_s | |
end | |
end | |
self.html_data += build_closing_tag(name).to_s | |
'' | |
end | |
end | |
class TestHtmlBuilder < Test::Unit::TestCase | |
def setup | |
HtmlBuilder.clear_output | |
@builder = HtmlBuilder | |
end | |
def test_self_closing_tags | |
@builder.build do | |
html do | |
body do | |
area | |
br | |
end | |
end | |
end | |
str = '<html><body><area/><br/></body></html>' | |
assert_equal(@builder.output, str) | |
end | |
def test_html_attributes | |
@builder.build do | |
html class: :html, id: :html1 do | |
body class: :body do | |
div 'div content', class: :content, id: '1', onclick: 'return someFunctionn()' | |
end | |
end | |
end | |
str = <<~STR.tr("\n",'').gsub(/(?<=\>)\s+(?=\<)/, '') | |
<html class='html' id='html1'> | |
<body class='body'> | |
<div class='content' id='1' onclick='return someFunctionn()'>div content</div> | |
</body> | |
</html> | |
STR | |
assert_equal(@builder.output, str) | |
end | |
def test_nested_tags | |
@builder.build do | |
html class: :html, id: :html1 do | |
body class: :body do | |
div 'div content', class: :content, id: '1', onclick: 'return someFunctionn()' do | |
article 'this is article 1' | |
article 'this is article 2' | |
div do | |
p 'this is paragraph' | |
div 'this is inner div' | |
end | |
end | |
end | |
end | |
end | |
str = <<~STR.tr("\n",'').gsub(/(?<=\>)\s+(?=\<)/, '') | |
<html class='html' id='html1'> | |
<body class='body'> | |
<div class='content' id='1' onclick='return someFunctionn()'>div content<article>this is article 1</article> | |
<article>this is article 2</article> | |
<div> | |
<p>this is paragraph</p> | |
<div>this is inner div</div> | |
</div> | |
</div> | |
</body> | |
</html> | |
STR | |
assert_equal(@builder.output, str) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
HtmlBuilder
class extendsBasicObject
due to fact thatBasicObject
has few methods. This class makes it easy to implement greatDSL
s.HtmlBuilder
usesmethod_missing
function to apply to meta programming techniques while creating aDSL
. Due to few methods ofBasicObject
, the rest of other method calls will be forwarded tomethod_missing
and this function will do the rest of the magic.