[LRUG] DSLs for newbies: HTML generation (discuss)
Daniel Barlow
dan at telent.net
Thu Dec 10 05:49:25 PST 2009
I'm playing with Ruby for the first time (pretty much) and having seen
haml I thought it would be fun to play with alternate syntaxes. This
one has much less in the way of funny characters (% and #) and isn't
whitespace-sensitive
It's probably also a really dumb idea. Like I say, first time Ruby
programmer. Anyway, here's a motivating example of its use
h=HTML.new
def h.content
html do
head { title(:id=>123) {"My page title" }}
body do
div do
h1(:class => "fancy_formatted") {"hello world"}
text "some stuff","more stuff"
ul {
%w(red orange yellow green blue indigo violent).map {|name|
li { text name }
}
}
end
end
end
end
h.output
It's all valid Ruby code. There is a method (implemented with
method_missing) for each HTML element: when called it expects HTML
arguments as attributes and a block of element content: it outputs the
markup for the start-tag/end-tag and calls the block.
Inside the block you can call more element-making methods, and/or you
can call #text (as shown) to output plain text, and/or you can return
some (preferably string) value which will also be output as if by #text
So,
- a neat hack?
- an offence against (your choice of) god?
- dull and unoriginal and every other newbie did exactly the same
thing when learning?
- really ugly ruby style?
All criticism welcome. I'm a Lisp programmer in my day job, so I've
almost certainly heard worse.
Oh, the implementation? The HTML it generates is not entirely valid
(attribute quoting and empty elements are two obvious omissions:
introducing all that whitespace, I hazily remember from reading SGML
specs back in the day, is probably also wrong) and indenting is hacky,
but you get the gist. It's more about proof-of-concept and playing with
the DSL syntax at this stage than production-quality output
Is Hash.map supposed to work like that, or is it accidental? It's
dashed useful, that I will say
---cut here---
class HTML
# this is a partial list for testing, and obviously needs to
# be extending to all tags in whatever version of HTML you want
# to produce
@@allowed_tags=%w(html head title body h1 h2 h3 h4 h5 h6
p div span ul li).map {|n| n.to_sym}
def texts(stuff)
stuff and
stuff.each {|x| x and @content << ("\n"+(" " * @indent)+x) }
nil
end
def text(*stuff)
texts stuff
end
def method_missing(name,*args,&body)
if @@allowed_tags.member?(name)
attributes = args[0] || [];
text "<#{name}"+attributes.map {|k,v| " "+k.to_s+"="+v.to_s
}.to_s + ">"
@indent=@indent+4;
texts body.call
@indent=@indent-4;
text "</#{name}>"
else
super # not on our list, let it raise UndefinedMethodError
end
end
def output
@content=[]
@indent=0
content
print @content
puts
end
end
---cut here---
More information about the Chat
mailing list