How to write DSLs with Ruby
Posted by MSch Sat, 01 Apr 2006 20:51:00 GMT
Recently I needed a DSL like the one in Rake for one of my projects. At first I just googled around a bit hoping to find a comprehensive tutorial. I did gain some more or less useful insights, but what I wanted was something as clean and coherent as a Rakefile:
namespace "samples" do
task :build do
# Build the sample programs
end
endAfter reading lots of tutorials and gaining come clues all I managed to code was:
namespace do |n|
n.task do |t|
# Build the sample programs
end
endI felt terrible and started reading the Pickaxe, desperate for a solution. Proc, Binding, Object, Kernel. I was looking everywhere for some clues on how to make my DSL as great as Rake’s. Finally I found what I was looking for: Object#instance_eval!
I’ll spare you any further explainations and present you the source (abridged, download full version) to my DSL implementation:
class PseudoRakefile
def evaluate(&block)
Tasks.new({}, {}, self, &block)
end
protected
class Tasks
attr_reader :parent
def initialize(args, options, parent, &block)
# snip
instance_eval &block
end
def task(task, args = {}, &block)
Tasks.new(@args.merge(args), @options.merge(:task => task), self, &block)
end
def namespace(name, &block)
Tasks.new(@args, @options.merge(:namespace=>name), self, &block)
end
# snip
def puts(msg)
Kernel.puts "#{@options[:namespace]}:#{@options[:task]} #{msg}"
end
end
end
rakefile = PseudoRakefile.new
rakefile.evaluate do
namespace "samples" do
task :build do
develop('deal', :do_it => :great)
sleep 2
develop('solution', :do_it => :sloppy)
end
task :destroy, :probability => 1 do
puts "Tear everything down like a little kid"
rm("Schroedinger's Cat", :probability => 0.5)
rm("some unimportant file")
end
end
end
It’s free to use for anyone, but you’d make this boy very happy by leaving a comment, especially if you have improved the core idea or actually use my code in your projects.

What? I don’t understand anything of this crap. Why did you send me that? By the way… I am drunk :-)