Groovy 1.8 introduces command chain expression to further the support for DSLs. We already could leave out parenthesis when invoking top-level methods. But now we can also leave out punctuation when we chain methods calls, so we don't have to type the dots anymore. This results in a DSL that looks and reads like a natural language.
Let 's see a little sample where we can see how the DSL maps to real methods with arguments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // DSL: take 3 .apples from basket // maps to: take( 3 .apples).from(basket) // DSL (odd number of elements): calculate high risk // maps to: calculate(high).risk // or: calculate(high).getRisk() // DSL: // We must use () for last method call, because method doesn't have arguments. talk to: 'mrhaki' loudly() // maps to: talk(to: 'mrhaki' ).loudly() |
Implementing the methods to support these kind of DSLs can be done using maps and closures. The following sample is a DSL to record the time spent on a task at different clients:
1 2 3 | worked 2 .hours on design at GroovyRoom developed 3 .hours at OfficeSpace developed 1 .hour at GroovyRoom worked 4 .hours on testing at GroovyRoom |
We see how to implement the methods to support this DSL here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | // Constants for tasks and clients. enum Task { design, testing, developing } enum Client { GroovyRoom, OfficeSpace } // Supporting class to save work item info. class WorkItem { Task task Client client Integer hours } // Support syntax 1.hour, 3.hours and so on. Integer.metaClass.getHour = { -> delegate } Integer.metaClass.getHours = { -> delegate } // Import enum values as constants. import static Task.* import static Client.* // List to save hours spent on tasks at // different clients. workList = [] def worked(Integer hours) { [ 'on' : { Task task -> [ 'at' : { Client client -> workList << new WorkItem(task: task, client: client, hours: hours) }] }] } def developed(Integer hours) { [ 'at' : { Client client -> workList << new WorkItem(task: developing, client: client, hours: hours) }] } // ----------------------------------- // DSL // ----------------------------------- worked 2 .hours on design at GroovyRoom developed 3 .hours at OfficeSpace developed 1 .hour at GroovyRoom worked 4 .hours on testing at GroovyRoom // Test if workList is filled // with correct data. def total(condition) { workList. findAll (condition).sum { it.hours } } assert total({ it.client == GroovyRoom }).hours == 7 assert total({ it.client == OfficeSpace }).hours == 3 assert total({ it.task == developing }).hours == 4 assert total({ it.task == design }).hours == 2 assert total({ it.task == testing }).hours == 4 |