[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