I'm banging my head against this wall for more than a week now, let me explain briefly what I'm trying to achieve.
I have a DSL defined like this (brief example)
TestingSpec
.newInstance()
.sayHello() --> print "Hello "+something
.sayGoddbye() --> print "Goddbye"+something
Note the something in there that is not passed anywhere intentionally, because I want to limit the scope of use of my DSL by the will be users, so what I actually want is to somehow "inject" the something in that DSL instance programmatically. This is being done by using a Closure.
Myscript.execute( {
TestingSpec
.newInstance()
.sayHello() --> print "Hello "+something
.sayGoddbye() --> print "Goddbye"+something
})
and MyScript will invoke it by passing the something value to it, either by passing a parameter, or by adding a property or by adding a Binding (don't know what's the best, really)
close.call("Toni")
close.metaClass.something = "Toni"
def binding = new Binding()
binding.setVariable("something", "Toni")
close.setBinding(binding)
But now I'm stuck in what I thought it was going to be easy, how from the TestingSpec code can I access the Closure something? For instance
public newInstance() {
def instance = new TestingSpec()
instance.something = *MY CLOSURE SOMETHING*
return instance
}
I've tried several options, like messing with the this, owner and delegate of the closure, but couldn't do it.
Any hint will be very welcome...
Cheers.
I don't think that's doable. TestingSpec is too high on the stack to be aware he is being invoked from a closure. I'd like to suggest two solutions
1. Pass something to TestingSpec programmatically.
Myscript.execute( {
TestingSpec
.newInstance(something)
.sayHello() --> print "Hello "+something
.sayGoddbye() --> print "Goddbye"+something
})
2. Make the TestingSpec instance be created by MyScript
I like this solution more. It is about MyScript.execute({}) being responsible for creating and handling the lifecycle of TestingSpec:
class TestingSpec {
def something
def sayHello() {
println "Hello " + something
this
}
def sayGoddbye() {
println "Goddbye " + something
this
}
}
class Myscript {
static TestingSpec testingSpec
static execute(closure) {
def binding = new Binding(something: 'test for echo')
testingSpec = new TestingSpec(something: binding.something)
binding.testingSpec = testingSpec
closure.binding = binding
closure()
}
}
Myscript.execute( {
testingSpec // this is an instance, and not a static call
.sayHello() // print "Hello "+something
.sayGoddbye() // print "Goddbye"+something
})
Output:
$ groovy Spec.groovy
Hello test for echo
Goddbye test for echo
3. Delegate the closure to testingSpec
If you set the closure delegate to testingSpec, you can invoke its methods without referencing testingSpec directly, providing a very clean code. Note i also updated the example to remove MyScript:
class TestingSpec {
static TestingSpec testingSpec
static execute(closure) {
def binding = new Binding(something: 'test for echo')
testingSpec = new TestingSpec(something: binding.something)
binding.testingSpec = testingSpec
closure.delegate = testingSpec
closure.binding = binding
closure()
}
def something
def sayHello() {
println "Hello " + something
this
}
def sayGoddbye() {
println "Goddbye " + something
this
}
}
TestingSpec.execute( {
sayHello() // print "Hello "+something
sayGoddbye() // print "Goddbye"+something
})
4. Inject testingSpec as a parameter into your closure
As per your answer, a simple suggestion: consider giving a testingSpec var that you've built yourself to the user, since it allows more control and customization to you. Think a simple inversion of control (this allows testingSpec to see dynamic variables in the closure's binding):
.withDistinctNames({ testingSpec ->
testingSpec
.from(name)
.withAddress(email)
.sendEmail(template)
})
With a full test:
class TestingSpec {
def commands = []
static newInstance(listOfNames) {
new TestingSpec()
}
def sayHello() { commands << "sayHello"; this }
def waveGoddbye() { commands << "waveGoddbye"; this }
def withAddress(address) { commands << "withAddress $address"; this }
def sendEmail(template) { commands << "sendEmail $template"; this }
def withDistinctNames(closure) {
commands << "withDistinctNames"
closure.binding = new Binding(address: "sunset boulevard", template: 'email is $email')
closure(this)
this
}
}
test = TestingSpec
.newInstance([:])
.sayHello()
.withDistinctNames({ me ->
me
.withAddress(address)
.sendEmail(template)
})
.waveGoddbye()
assert test.commands == [
'sayHello',
'withDistinctNames',
'withAddress sunset boulevard',
'sendEmail email is $email',
'waveGoddbye'
]
I finally reach a solution to my problem that while not ideal it's "elegant" enough and "easy" to understand by would-be users of my DSL. So, what I want to do is allow the DSL users to write things like this:
TestingSpec
.newInstance()
.from(listOfNames)
.sayHello()
.withDistinctNames({
TestingSpec
.newInstance()
.from(*****) <---- problem
.withAddress()
.sendEmail(template)
})
.waveGoddbye()
There could be a number of with methods that basically serve as a filter+loop, so in this example it will filter the names to be unique and then apply the Closure to each name in turn. Of course this would be extremely easy to do by just doing
.withDistinctNames({
TestingSpec
.newInstance()
.from(it)
.withAddress()
.sendEmail(template)
})
or for more complicated cases
.withDistinctNames({ name, email, template ->
TestingSpec
.newInstance()
.from(name)
.withAddress(email)
.sendEmail(template)
})
The prob is I don't know who is going to use this DSL, it may not be people familiar with closures, parameters, functions, whatnots, it may be people even from outside my organization, so my goal was to keep it simple by passing the needed variables from the outer Spec to the inner Spec. In my TestingSpec implementation I would have:
public TestingSpec withAll(Closure k, Closure f) { // withAll is used by all with* methods
def result = k.call(root) // root is the entire listOfNames
def results = result
.collect {
// HERE'S THE PROBLEM, HOW TO INJECT ALL THE PARAMETERS INTO THE INNER SPEC?
f.call(it) // this line passes vars to the Closure, not the inner Spec, and I found no way for the Closure itself to inject them in the Spec
}
return this;
}
After I saw it was impossible to inject params in the inner Spec itself (that is yet to be instantiated) I tried to pass them to the Closure and from there to the Spec, like this:
.withDistinctNames({
TestingSpec
.newInstance()
.from(this) <-------------------
.withAddress()
.sendEmail(template)
})
I supposed that this would be the outer Spec that contains all the info I need, but it was not. I tried with this, this.thisObject, this.owner, this.delegate.
But then, with the help of the 3rd above suggestion I ended up doing this:
public TestingSpec withAll(Closure k, Closure f) {
f = f.rehydrate(f.getDelegate(), f.getOwner(), this)
def result = k.call(root)
def results = result
.collect {
this.parameters = [whatever I need]
f.call()
}
return this;
}
This way the this in the DSL is actually the outer Spec, so the users can now use the more intuitive "this" keyword. To avoid confusion I got rid of the .from() method, like this:
TestingSpec
.newInstance(listOfNames)
.sayHello()
.withDistinctNames({
TestingSpec
.newInstance(this) // my newInstance implementation can parse all the parameters as it wishes
.withAddress()
.sendEmail(template)
})
.waveGoddbye()
While it's not ideal is close enough, from the perspective of the potential users, I think. If somebody has suggestions please let me know.
Thanks Will P and all.
After working on a small similar Groovy DSL tool recently and digging a bit into griffon and swing and other material I currently ended with the following, that I submit as another suggestion to the answer.
Any comment/suggestion will be greatly appreciated.
IMHO a clear separation between the domain model (a Model class) , and the support classes (building, aggregation , context sharing, and filtering part of the model, at least a Builder class) is one of the key elements
to have a simple and efficient model. This is the pattern used in groovy swing and griffon and showed to be very flexible.
It allows constructs with each {}, with {} and collection operations which are clean and understandable enough,
and mostly without chaining operations (ie adding an annoying return this at the end of each operation).
Like here for example (source code at the end, MSys is a Facade to the underlying system):
MSys.with {
model.each {
it.sayHello
it.sayGoodBye
it.setAddress randomAddress(it.name);
it.sendEmail
}
println " Hello counts"
model.each {
def key = it.name
def value = MSys.context[key]
println "Counter for ${key} = ${value}"
}
}
or
MSys.model.each {
it.with {
sayHello
sayGoodBye
setAddress randomAddress(name) ;
sendEmail
}
}
or
MSys.eachModelDo {
sayHello
sayGoodBye
setAddress randomAddress(it.name);
sendEmail
}
or ...
(lot of possibilities)
It also allows to have some shared context very easily (in the example , context is a Map shared between all the Model elements where almost anything could be put, connection information, user preferences, cache etc.),
and to hide all the boiler plate to the user by putting it in another class/script .
The responsibilities would be :
SpecModel : Domain model : say hello, goodbye, properties etc
SpecBuilder : creates models (from a list in the example), holds shared context (a map) and eventually take care of the closure delegate context for some operations
This separation is important to deal with set operations on one side and entity (model) operations on the other.
Apart from beeing a builder, from a user POV it is a facade
and from a developer POV BTW it should be a Facade to several classes including the builder -but better start simple.
The next step would be to integrate FactoryBuilderSupport in this builder in order to benefit from Groovy DSL builder facilities, but this one big step beyond and I'm not comfortable with that yet ... (WIP right now)
Few Groovy recaps that you certainly already know but part of the problem if you want a light syntax :
In Groovy , , ; and () are optional except when there is a conflict.
Even where there is no direct conflict, the delegation strategy is not always direct and
run time errors are not always clear (see link on closures doc below)
Having a public getter method for properties, allows the use of property like access methods without the (),
the get and set prefixes are inferred
ie with a method like
public getSendEmail() {
println "email for ${name}"
}
you can use the syntax :
myObject sendEmail
Groovy assumes a property getter call and do the rest.
This helps removing the () in the dsl syntax.
The context is the delegate context of the closure it not the this
When you use closures, you use it to reference the object you are working on in the closure
(ie the receiver of the message).
Example:
[1, 2, 3].each { println it }
is ok
If you haved used
[1, 2, 3].each { println this }
you would have print a reference to the outer object, the console in groovy console
(Which IIRC was one of the problem you had in your first post)
This is well explained in the Groovy Closures doc :
(excerpt:)
this : the enclosing class where the closure is defined
owner : the enclosing object where the closure is defined, a class or a closure
delegate : third party object where methods calls or properties are resolved whenever the receiver of the message is not defined
The message delegation strategy (explained in the same document) is not always direct and this was your real problem I think.
That said, another key point in a DSL is the coherence from a user POV.
This means consistence in data access patterns and here the standard Groovy collections (Lists, maps each{} etc) can help a lot.
Plus it can be a huge plus for power users.
Syntactic sugar methods like eachModelDo can easily be done on a builder/facade class:
MSys.eachModelDo {
sayHello
sayGoodBye
setAddress randomAddress(it.name);
sendEmail
}
NB: eachModelDo is very simple but a bit tricky to debug
At one point it "worked" well without accessing correct variables :(
I have the feeling that something is wrong here (?) or at least it should be improved (comments welcome)
/**
* syntactic sugar
* direct access without 'it' (optional)
*/
public SpecBuilder eachModelDo(closure) {
model.each {
closure.delegate = it;
closure(it)
}
}
Bellow is the source code of a small test I did that you can cut and paste in a groovy console
The only part visible to the user should be the method Demo.run()
All the other stuff should be hidden
Any comments are welcome
/**
* The builder build Specs and defines utility methods, filters
* It shares its context with all elements in the domain
*/
class SpecBuilder {
/** the context will be shared with all domain model objects */
private context
/** model = all the domain model objects */
private model
/** getters / setters */
public getModel() { return model }
public getContext() {
return context
}
/** constructors and helpers */
public SpecBuilder(aContext) {
context = aContext
}
/** Default constructor forbidden */
private SpecBuilder() {}
public from(aList, closure) {
from(aList);
model.each { closure(it) }
return this
}
public from(aList) {
model = aList.collect { new SpecModel(it, context) }
return this
}
/* TODO filters etc */
/** stats: print counters */
public stats() {
println " Hello counts"
model.each {
def key = it.name
def value = this.context[key]
println "Counter for ${key} = ${value}"
}
}
/**
* syntactic sugar
* direct access without 'it' (optional)
*/
public SpecBuilder eachModelDo(closure) {
model.each {
closure.delegate = it;
closure(it)
}
}
}
/**
* The Spec Domain Model
*/
class SpecModel {
/** the shared context */
private context;
/** other properties */
private name;
public address;
/** getters and setters */
public getName() { return name }
public void setAddress(a) { address = a }
public getAddress() { return address }
public sayHello() { return getSayHello }
public sayGoodBye() { return getSayGoodBye }
public sendEmail() { return getSendEmail }
/** constructors */
public SpecModel(aName, aContext) {
name = aName
context = aContext
}
/** Default constructor forbidden */
private SpecModel() {}
/** method used like properties, without 'get' and ()*/
public getSayHello() {
println "(!) hello ${name}"
context[name] = context.get(name,0) +1;
}
public getSayGoodBye() {
println "goodBye ${name} !"
}
public getSendEmail() {
println "email for ${name}"
if (address)
println "Address ${address}"
}
public getPrintContext() {
println context
}
/**
* Returns info to caller
*/
public gatherInfo() {
"info for ${name} : ${new java.util.Random().nextInt(50000)}"
}
}
class Demo {
// several Groots here to test uniques ...
def customers = ['Groot', 'Gamora', 'Groot', 'Groot', 'Groot', 'Star-Lord']
/**
* Utility function who generates a random address
* #param name will prefix the address
* #return the address
*/
public randomAddress(def name) {
// good places ... :)
def places = [
"Grande Rue",
"Cours Emile Zola",
"Place Antonin Poncet",
"Rue de la République",
"Boulevard de la Croix Rousse",
"Place Bellecour"
]
def random = new java.util.Random();
return new StringBuilder().append(name).append(" ... ")
// why not 42?
.append( random.nextInt(155)).append(" ")
.append( places[random.nextInt(places.size())] )
.toString();
}
/**
* ======================== Main user program =========================
*/
def run() {
/** the shared context */
def context = [:].asSynchronized() // In case of multi threading access
/** The whole system From a user POV : a big façade */
def MSys = new SpecBuilder(context).from(customers) ;
println "*** 1 ==================== "
/** First form */
MSys.model.each {
it.with {
sayHello
sayGoodBye
setAddress randomAddress(name) ;
sendEmail
}
}
/** other forms
* MSys.with{ is handy
* one could write MSys... on each line
*/
MSys.with {
println "*** 2 ==================== "
model.each { it.sayHello };
println "*** 3 ==================== "
model.with { println " a Model entry = ${it.name} + ${it.address}" }
println "*** 4 ==================== "
/** false not to mutate the model !!! */
model.unique(false, { a, b -> a.name <=> b.name }).each { it.sayHello }
println "*** 5 ==================== "
context['aKey'] = 42
// verify that each entity has the same context
model.with { println " a shared context for ${it.name} : " + it.context }
println "*** Stats ================ "
/** stats on the shared context */
stats()
}
println "*** 6 Info to process ======== "
/** Gather info to process (addresses)*/
def data = MSys.model.inject([:]) { result, entity ->
result[entity.name] = entity.address
result
}
println data
MSys.with {
println "*** 7 ==================== "
model.each {
it.sayHello
it.sayGoodBye
it.setAddress randomAddress(it.name);
it.sendEmail
}
println "*** 8 ==================== "
println " Hello counts"
model.each {
def key = it.name
def value = MSys.context[key]
println "Counter for ${key} = ${value}"
}
}
println "*** 9 ==================== "
MSys.eachModelDo {
sayHello
sayGoodBye
setAddress randomAddress(it.name);
sendEmail
}
}
}
new Demo().run()
/* end of script */
I am trying to write my own gradle plugin and it needs to be able to configure a set of objects - how many of these objects and what they're called is up to the user.
The doco for creating custom gradle plugins with advanced customisability is quite poor. It mentions project.container() method to do this kind of thing, but I couldn't figure out how to make it work in my usecase.
This is an example of my plugin's configuration DSL as it stands:
teregrin {
terraformVersion = '0.6.6'
root("dev"){
accessKey = "flobble"
}
root("prd"){
}
}
And this is my plugin extension object that allows me to configure it:
class TeregrinPluginExtension {
boolean debug = false
boolean forceUnzip = false
String terraformVersion = null
Set<TeregrinRoot> roots = []
def root(String name, Closure c){
def newRoot = new TeregrinRoot(name)
c.setDelegate(newRoot)
c()
roots << newRoot
}
}
The extensions wired up in my plugin in the standard way:
project.extensions.create("teregrin", TeregrinPluginExtension)
This works ok, but it's a pretty ugly configuration style, not really in the style of the typical gradle DSL.
How can I change my plugin configuration DSL to be something like this:
teregrin {
terraformVersion = '0.6.6'
roots {
dev {
accessKey = "flobble"
}
prd {
}
}
}
The gradle way of implementing such DSL is by using extensions and containers:
apply plugin: SamplePlugin
whatever {
whateverVersion = '0.6.6'
conf {
dev {}
qa {}
prod {
accessKey = 'prod'
}
}
}
task printWhatever << {
println whatever.whateverVersion
whatever.conf.each { c ->
println "$c.name -> $c.accessKey"
}
}
class SamplePlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create('whatever', SampleWhatever)
project.whatever.extensions.conf = project.container(SampleConf)
project.whatever.conf.all {
accessKey = 'dev'
}
}
}
class SampleWhatever {
String whateverVersion
}
class SampleConf {
final String name
String accessKey
SampleConf(String name) {
this.name = name
}
}
while groovy way of doing implementing such DSL is meta programming - you need to implement methodMissing in this particular case. Below is a very simple example that demonstrates how it works:
class SomeExtension {
def devConf = new SomeExtensionConf()
void methodMissing(String name, args) {
if ('dev'.equals(name)) {
def c = args[0]
c.resolveStrategy = Closure.DELEGATE_FIRST
c.delegate = devConf
c()
} else {
throw new MissingMethodException("Could not find $name method")
}
}
def getDev() {
devConf
}
}
class SomeExtensionConf {
def accessKey
}
project.extensions.create('some', SomeExtension)
some {
dev {
accessKey = 'lol'
}
}
assert 'lol'.equals(some.dev.accessKey)
Of course it has no error checking - so the args size and type of each argument need to be validated - it's omitted for the sake of brevity.
Of course there's no need to create a separate class for each configuration (I mean dev, prod, etc.). Create a single class that holds configuration and store them all in a Map where key is configuration name.
You can find a demo here.
I'm trying to write a Jenkins Job DSL script and would like to write it as declaratively / DRY-ly as possible. The Jenkins task is calling some other tasks via a MultiJob. I have Groovy that originally looks like this (everything's contained within a class because it's referenced elsewhere):
static void buildDownstream(def parentJob, String commit_a="master",
String commit_b="master") {
parentJob.with {
steps {
phase('Phase') {
job("name_1") {
prop('COMMIT_A', commit_a)
nodeLabel('NODE_LABEL', NODE_LABEL_MAP["name_1"])
killPhaseCondition('NEVER')
}
job("name_2") {
prop('COMMIT_A', commit_a)
prop('COMMIT_B', commit_b)
nodeLabel('NODE_LABEL', NODE_LABEL_MAP["name_2"])
killPhaseCondition('NEVER')
}
job("name_3") {
prop('COMMIT_A', commit_a)
prop('COMMIT_B', commit_b)
nodeLabel('NODE_LABEL', NODE_LABEL_MAP["name_3"])
killPhaseCondition('NEVER')
}
}
}
}
}
I'd like to abstract out the job creation, which contains lots of duplication. I've ended up with something strange like this:
static void buildDownstream(def parentJob, String commit_a="master",
String commit_b="master") {
parentJob.with {
steps {
phase('Phase') {
def phase = ({ owner })();
{ ->
add_node_label=true;
{ ->
commit_a = null;
def self = ({ owner })();
addJob("name_1", self).call(phase);
}
def self = ({ owner })();
addJob("name_2", self).call(phase);
addJob("name_3", self).call(phase);
}
}
}
}
}
private static Closure addJob(String job_name, Closure callingClosure) {
return { phase ->
def job_config = {
if(commit_a != null) {
prop('COMMIT_A', commit_a)
}
if(commit_b != null) {
prop('COMMIT_B', commit_b)
}
if(add_node_label == true) {
nodeLabel('NODE_LABEL', NODE_LABEL_MAP[job_name])
}
killPhaseCondition('NEVER')
}
job_config.delegate = callingClosure
job_config.resolveStrategy = Closure.DELEGATE_ONLY
phase.job(job_name, job_config)
}
}
which, probably being totally non-idiomatic Groovy (all this def self = ({ owner })() stuff doesn't sit right with me), doesn't work at all.
Basically, I want to pass all the variables in callingClosure's scope to the job_config closure without explicitly passing all of them in as arguments. (Explicitly passing a map of arguments works, but it gets unwieldy when there are lots of arguments.) How can I do this?
(P.S: Currently, Groovy is trying to resolve the commit_a variable inside job_config as coming from javaposse.jobdsl.dsl.helpers.step.PhaseContext, which I find strange; didn't I explicitly set the delegate to a closure inside that PhaseContext?)
EDIT: From another SO question, it appears that I can set phase = delegate (which defaults to owner?) instead of ({ owner })() and be fine; I don't really get this either, since job is a property of the PhaseContext, and not its parent (?)
Well, I ended up not trying to ask Groovy to implicitly resolve variables from the delegate context, and instead just passed in the parameters in a map.
static void buildDownstream(def parentJob,
String commit_a="master", String commit_b="master") {
parentJob.with {
steps {
phase('Tests') {
def params = [COMMIT_A:commit_a]
this.getTestJob(delegate, "name_1", params)
params.COMMIT_B = commit_b
this.getTestJob(delegate, "name_2", params)
this.getTestJob(delegate, "name_3", params)
continuationCondition('ALWAYS')
}
}
}
}
private static void getTestJob(def phase, String job_name,
Map properties) {
phase.job(job_name) {
properties.each { k, v -> prop(k, v) }
killPhaseCondition('NEVER')
}
}
One problem with my original method was that I was trying to access local variables in the closures, but that requires the closure to be evaluated; that turns out to be really weird, and I guess I should just not try to do that.
I'm going to internationalize groovy API abit.
For final class (e.g. String)
String.metaClass.вСтроку = {-> this.toString() }
However, this will create additional closure. Isn't there any way to just alias method with another method?
Something like this:
String.metaClass.вСтроку = String.metaClass.&toString
You could use #Category transform like this
#Category(String) class StringInternationalization {
String вСтроку() {
this.toString()
}
int длина() {
this.length()
}
}
class ApplyMixin {
static {
String.mixin(StringInternationalization)
final helloString = "Привет мир!"
println helloString.вСтроку()
assert helloString.длина() == helloString.length()
}
}
new Main()
This will create 1 Category class for each localised class and one class to apply all mixin transformations(to register all methods.) Also should be faster, then individual closures.
More reading here: http://groovy.codehaus.org/Category+and+Mixin+transformations
I have simple Groovy category class which adds method to String instances:
final class SampleCategory {
static String withBraces(String self) {
"($self)"
}
}
I want to use this category in my unit tests (for example). It looks like this:
class MyTest {
#Test
void shouldDoThis() {
use (SampleCategory) {
assert 'this'.withBraces() == '(this)'
}
}
#Test
void shouldDoThat() {
use (SampleCategory) {
assert 'that'.withBraces() == '(that)'
}
}
}
What I'd like to achieve, however, is ability to specify that category SampleCategory is used in scope of each and every instance method of MyTest so I don't have to specify use(SampleCategory) { ... } in every method.
Is it possible?
You can use mixin to apply the category directly to String's metaClass. Assign null to the metaClass to reset it to groovy defaults. For example:
#Before void setUp() {
String.mixin(SampleCategory)
}
#After void tearDown() {
String.metaClass = null
}
#Test
void shouldDoThat() {
assert 'that'.withBraces() == '(that)'
}
Now you have the option to use extension modules instead of categories:
http://mrhaki.blogspot.se/2013/01/groovy-goodness-adding-extra-methods.html
On the plus side Intellij will recognize the extensions. I've just noticed that it doesn't even need to be a separate module as suggested by the link, just add META-INF/services/org.codehaus.groovy.runtime.ExtensionModule to the project:
# File: src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = module
moduleVersion = 1.0
extensionClasses = SampleExtension
The extension class is pretty much defined like a normal category:
class SampleExtension {
static String withBraces(String self) {
"($self)"
}
}
Can be used like:
def "Sample extension"() {
expect: 'this'.withBraces() == '(this)'
}
If you are using Spock there is a #Use annotation that can be used on the specifications. The drawback with that is that Intellij will not recognize it.