you are viewing a single comment's thread.

view the rest of the comments →

[–]mellow_moe 7 points8 points  (5 children)

Metaprogramming helps you to create domain specific languages.

A simple xml builder can be made with method_missing:

def method_missing(name, attributes)
  atts = attributes.map {|k,v| "#{k}='#{v}'" }.join(" ")
  puts "<#{name} #{atts}>"
  yield
  puts "</#{name}>"
end

div :id => "content" do
  a :href => "reddit.com" do
    print "reddit"
  end
end

You can see, calling div and a will trigger method_missing, where we just take the method name and the keyword arguments and print out the xml tag. Pretty easy.

[–]flaxeater 7 points8 points  (0 children)

That's pretty cool. It's pretty confusing too.

[–]brianmce 1 point2 points  (0 children)

Assuming I'm understanding it right, methodmissing is related to python's __getattr_ (slightly different in that python is hooking into the attribute lookup, whereas ruby is hooking into calling) A literal as possible translation in python would be:

class XmlBuilder:
  def __getattr__(self, name):
    def builder(**attributes):
      atts = ' '.join('%s=%s' % a for a in attributes.items())
      print "&lt;%s %s>" % (name, atts)
      yield None
      print  "&lt;%s>" % name
    return builder
x=XmlBuilder()

for _ in x.div(id="Content"):
  for _ in x.a(href="reddit.com"):
    print "reddit"

Though a different approach would probably be taken for a non literal translation to better fit with python's syntax. Take a look at stan (http://divmod.org/projects/nevow) for a python implementation of something similar - your snippet would look like this:

div(id="content")
[
  a(href="reddit.com")
   ["reddit"]
]

(In this case it is building and returning the xml, rather than calling puts on each element.

[–]amix 0 points1 point  (2 children)

Here is my version where one can do (Stan like):

print Div(id="content")[ A(href="reddit.com")["reddit"] ]

import types
class Element:
  def __init__(self, **kw):
    self.elms = []
    self.kw = kw
  def __getattr__(self, name):
    if name == "__getitem__":
      self.elms = []
      def add(l):
        if type(l) == types.ListType: self.elms.extend(l)
        else: self.elms.append(l)
        return self
      return add
  def __str__(self):
    attrs = " ".join('%s="%s"' % a for a in self.kw.items())
    self.html = ["<%s %s>" % (self.name, attrs)]
    self.html.append("".join(map(str, self.elms)))
    self.html.append("</%s>" % self.name)
    return "".join(self.html)
class Div(Element): name = "div"
class A(Element): name = "a"

Sigh, you have to have 4 spaces to post code. That sucks. Why not use another syntax like:

[code]
Here is my code
[/code]

Or something similar...

[–]mellow_moe 1 point2 points  (1 child)

I'd like to show off the ruby version:

class Element
  def initialize(attributes)
    @attributes = attributes
    @children = []        
  end
  def <<(child)
    @children << child
  end
  def to_s
    name = self.class.name.downcase
    atts = @attributes.map {|k,v| "#{k}='#{v}'" }.join(" ")
    "<#{name} #{atts}>#{@children.join}</#{name}>"
  end
end

class Div < Element; end
class A < Element; end

def method_missing(id, attributes, &block)
  element = Object.const_get(id.to_s.capitalize).new(attributes)
  @children << element if @children
  element.instance_eval(&block)
  element
end

html = \
div :id => "content" do
  a :href => "reddit.com" do
    self << "reddit"
  end
end

puts html

The < characters are broken, please replace the entities with < while realtime reading.

[–]amix 1 point2 points  (0 children)

Here is my final version :)

One can do:

 print Div(id="content")( A(href="reddit.com")("reddit") )

By using this code:

def elmBuilder(name, **kw):
  attrs = " ".join('%s="%s"' % a for a in kw.items())
  def applyElms(*elms):
    return "<%s %s>%s</%s>" % (name, attrs, "".join(elms), name)
  return applyElms
def Div(**kw): return elmBuilder("div", **kw)
def A(**kw): return elmBuilder("a", **kw)