I created a (Ruby) DSL …
A DSL is a Domain Specific Language – that is, small language that should help frame solutions to particular problems.
In this case, we had some config for a report writer program that was parsed using custom code, things like so:
[code]
<report name>
{
FORMAT = CSV
RECORD = A.PATH.TO.RECORD
MODE = streaming
KEYFIELD = TradeId
FIELDS {
Id
TradeId
RecordType
Date
Time
}
WHERE Date = “20120924”
}
[/code]
The parser was getting hairier and we need to add more features – more complex WHERE clauses etc. So, it seemed an opportunity to throw in a DSL. This is the new DSL format for the above:
[code]
report “<report name>” do
format “CSV”
record “A.PATH.TO.RECORD”
mode “streaming”
keyfield “TradeId”
fields do
column “Id”
column “TradeId”
column “RecordType”
column “Date”
column “Time”
end
where do
Date == “20120924”
end
end
[/code]
When I first saw things like this, it seemed like magic – Ruby must be doing really complex stuff to handle it – but it isnt :)
Its all down to defining methods with the above names and handling the blocks passed to them.
For example there is a “report” method, like so:
[code]
def report(name, &block)
# save report name and &block of code for later use
end
[/code]
The “&block” bit is little funky – its a way of capturing the code passed between the “do … end” block above.
To handle the next level down – the code within the report block, I defined a class with those methods and the block is “call”‘d within the context of that block.
[code]
class WriterDefInRuby < Java::com.WriterDefBase # to make things fun, the class needs to implement a Java interface :)
def where(&block)
@where_clause = block
end
def setup(&block)
self.instance_eval &block
end
...
end
[/code]
So, the above class is used like this:
[code]
def report(name, &block)
writer_def = WriterDefInRuby.new
writer_def.name = name
writer_def.setup(&block)
end
[/code]
And thats it. We had a third level down for the fields stuff - but its just more of the same, another class, eval the block in the context of that class.
PS Thanks to several sites explaining DSL’s in much better detail and several example gems, like Rose and Docile