[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 

It's probably also a really dumb idea.  Like I say, first time Ruby 
programmer.  Anyway, here's a motivating example of its use

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 }

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

  - 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) }

   def text(*stuff)
     texts stuff

   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 + ">"
       texts body.call
       text "</#{name}>"
       super # not on our list, let it raise UndefinedMethodError

   def output
     print @content
---cut here---

More information about the Chat mailing list