Call closure's delegate method from a function in Groovy? - groovy

In a Gradle script I have a Groovy closure with a delegate, and I have created a function calling methods on that delegate as described below:
// Simplified example
ant.compressFiles() {
addFile(file: "A.txt")
addFile(file: "B.txt")
addAllFilesMatching("C*.txt", getDelegate())
}
def addAllFilesMatching(pattern, closureDelegate) {
// ...
foundFiles.each {
closureDelegate.addFile(file: it)
}
}
Is it possible to do this in a prettier way, without having to pass the delegate to a function? Is it for example possible to somehow extend the delegate with new methods?

This can be solved by creating a function that returns a Closure:
ant.compressFiles() addAllFilesMatching("A.txt", "B.txt", "C*.txt")
Closure addAllFilesMatching(String... patterns) {
// Calculate foundFiles from patterns...
return {
foundFiles.each { foundFile ->
addFile(file: foundFile)
}
}
}

You can declare the closure first, set its delegate, resolveStrategy and then pass it to each:
def addAllFilesMatching(pattern, delegate) {
def closure = {
addFile file: it
}
closure.delegate = delegate
closure.resolveStrategy = Closure.DELEGATE_FIRST
foundFiles = ["a.txt", "b.txt", "c.txt", "d.txt"]
foundFiles.each closure
}

How about this?
This is a minute modification to WillP's answer (which is absolutely correct and the way should be done) and should be prettier (as per your request) because it uses a closure instead of a method.
def addAllFilesMatching = {pattern ->
// ... foundFiles based on pattern
foundFiles.each {
delegate.addFile(file: it)
}
}
ant.compressFiles() {
addFile(file: "A.txt")
addFile(file: "B.txt")
addAllFilesMatching.delegate = getDelegate()
addAllFilesMatching("C*.txt")
}

Related

Jenkins Library / Groovy Script - Wrap Code dynamically

I'm developing a Jenkins shared library right now.
I wasn't able to figure how to easily "wrap" a code inside a function without copy-pasting the whole code. For example: If a developer sets a value to true, then I want to wrap the whole code inside a function. Right now I want to use this to allow e.g. the gitlabIntegration to be turned off from the Jenkinsfile.
Example:
// vars/stageWrapper.groovy
def call(Map parameters = [:], body) {
stage(stageName) {
if (pushtoGitlab) {
gitlabCommitStatus(stageName) {
if (!containerName) body()
else {
container(containerName) {
body()
}
}
}
} else {
if (!containerName) body()
else {
container(containerName) {
body()
}
}
}
}
}
let the user select if the stage should be pushed to gitlab via the gitlabCommitStatus wrapper.
switch to a specified container or use default container (if none is specified)
To realize this I currently repeat the code, which I really don't like...
Is there any way of achieving the same, but without repeating the same code over and over?
Thank You!
In Groovy you can reuse a Closure in different DSL-Builders by setting it's delegate to builder's delegate.
Something like this should work:
def containerNameBody = { body ->
if (!containerName)
body()
else
container(containerName) {
body()
}
}
def call(Map parameters = [:], body) {
stage(stageName) {
containerNameBody.delegate = delegate
if (pushtoGitlab)
gitlabCommitStatus(stageName) {
containerNameBody body
}
else
containerNameBody body
}
}
How about following approach to pass down the param into function, then decide how to do inside the function by the param value.
def gitHub(gitHubOn) {
}
def gitLab(gitLabOn) {
}
def call(Map parameters = [:], body){
//some code....
foo=bar
gitLab(parameters.gitLabOn)
gitHub(parameters.gitHubOn)
body()
}

How to pass parameters to groovy with() method

I want to invoke an externally provided closure in the context of some script, but I'd like it to be able to access an object scoped to the method from which the closure is being invoked:
def method1(Map config) {
...
with config.some_closure //here I'd like to pass the config map as a param.
}
//now invoke:
method1 {
some_value: 1,
some_closure: { def param1 ->
//access param here, eg:
param1.some_value = 2
}
}
My current workaround is to assign my object to a script-scoped variable so that the provided closure can access it:
globalConfig = null
def method1(Map config) {
globalConfig = config
...
with config.some_closure //here I'd like to pass the config map as a param.
}
//now invoke:
method1 {
some_value: 1,
some_closure: {
//access global here, eg:
globalConfig.some_value = 2
}
}
Is there a better way?
I think currying is what you are looking for:
def method1(Map config) {
with config.some_closure.curry(config) // this is a new closure taking no arguments, where param1 has been fixed.
}
//now invoke:
method1 ([
some_value: 1,
some_closure: { def param1 ->
//access param here, eg:
param1.some_value = 2
}
])
I think that using delegate is a valid alternative to with() that permits parameter passing:
def method1(Map config) {
...
config.some_closure.delegate = this // retain access to this object's methods
config.some_closure(config) //pass config explicitly
}
//now invoke:
method1 {
some_value: 1,
some_closure: { def param1 ->
//access param here, eg:
param1.some_value = 2
}
}

How to do Groovy method signatures with Python-style kwargs AND default values?

I might be asking too much, but Groovy seems super flexible, so here goes...
I would like a method in a class to be defined like so:
class Foo {
Boolean y = SomeOtherClass.DEFAULT_Y
Boolean z = SomeOtherClass.DEFAULT_Z
void bar(String x = SomeOtherClass.DEFAULT_X,
Integer y = this.y, Boolean z = this.z) {
// ...
}
}
And to be able to provide only certain arguments like so:
def f = new Foo(y: 16)
f.bar(z: true) // <-- This line throws groovy.lang.MissingMethodException!
I am trying to provide an API that is both flexible and type safe, which is the problem. The given code is not flexible in that I would have to pass in (and know as the user of the API) the default value for x in order to call the method. Here are some challenges for the solution I want:
Type safety is a must--no void bar(Map) signatures unless the keys can somehow be made type safe. I realize with this I could do the type checking in the method body, but I'm trying to avoid that level of redundancy as I have many of this "kind" of method to write.
I could use a class for each method signature--something like:
class BarArgs {
String x = SomeOtherClass.DEFAULT_X
String y
String z
}
And define it like:
void bar(BarArgs barArgs) {
// ...
}
And call it using my desired way using the map constructor: f.bar(z: true), but my problem lies in the object's default on y. There's no way to handle that (that I know of) without having to specify it when calling the method as in: f.bar(y: f.y, z: true). This is fine for my little sample, but I'm looking at 20-30 optional parameters on some methods.
Any suggestions (or questions if needed) are welcome! Thank you for taking a look.
Interesting question. I've interpreted your requirements like this
The class should have a set of default properties.
Each method should have a set of default arguments.
The method defaults override the class defaults.
Each method can have additional arguments, not existing on the class.
The method arguments should not modify the class instance.
Provided arguments needs to be checked for type.
I was not sure about number 5 since it is not explicitly specified, but it
looked like that was what you wanted.
As far as I know, there is nothing built-in in groovy to support all this,
but there are several ways to make it work in a "simple-to-use" manner.
One way that comes to mind is to create specialized argument classes, but
only use maps as the arguments in the methods. With a simple super-class
or trait to verify and set the properties, it is a one-liner to get the
actual arguments for each method.
Here is a trait and some examples that can be used as a starting point:
trait DefaultArgs {
void setArgs(Map args, DefaultArgs defaultArgs) {
if (defaultArgs) {
setArgs(defaultArgs.toArgsMap())
}
setArgs(args)
}
void setArgs(Map args) {
MetaClass thisMetaClass = getMetaClass()
args.each { name, value ->
assert name instanceof String
MetaProperty metaProperty = thisMetaClass.getMetaProperty(name)
assert name && metaProperty != null
if (value != null) {
assert metaProperty.type.isAssignableFrom(value.class)
}
thisMetaClass.setProperty(this, name, value)
}
}
Map toArgsMap() {
def properties = getProperties()
properties.remove('class')
return properties
}
}
With this trait is it easy to create specialized argument classes.
#ToString(includePackage = false, includeNames = true)
class FooArgs implements DefaultArgs {
String a = 'a'
Boolean b = true
Integer i = 42
FooArgs(Map args = [:], DefaultArgs defaultArgs = null) {
setArgs(args, defaultArgs)
}
}
#ToString(includePackage = false, includeNames = true, includeSuper = true)
class BarArgs extends FooArgs {
Long l = 10
BarArgs(Map args = [:], FooArgs defaultArgs = null) {
setArgs(args, defaultArgs)
}
}
And a class that uses these arguments:
class Foo {
FooArgs defaultArgs
Foo(Map args = [:]) {
defaultArgs = new FooArgs(args)
}
void foo(Map args = [:]) {
FooArgs fooArgs = new FooArgs(args, defaultArgs)
println fooArgs
}
void bar(Map args = [:]) {
BarArgs barArgs = new BarArgs(args, defaultArgs)
println barArgs
}
}
Finally, a simple test script; output of method invocations in comments
def foo = new Foo()
foo.foo() // FooArgs(a:a, b:true, i:42)
foo.foo(a:'A') // FooArgs(a:A, b:true, i:42)
foo.bar() // BarArgs(l:10, super:FooArgs(a:a, b:true, i:42))
foo.bar(i:1000, a:'H') // BarArgs(l:10, super:FooArgs(a:H, b:true, i:1000))
foo.bar(l:50L) // BarArgs(l:50, super:FooArgs(a:a, b:true, i:42))
def foo2 = new Foo(i:16)
foo2.foo() // FooArgs(a:a, b:true, i:16)
foo2.foo(a:'A') // FooArgs(a:A, b:true, i:16)
foo2.bar() // BarArgs(l:10, super:FooArgs(a:a, b:true, i:16))
foo2.bar(i:1000, a:'H') // BarArgs(l:10, super:FooArgs(a:H, b:true, i:1000))
foo2.bar(l:50L) // BarArgs(l:50, super:FooArgs(a:a, b:true, i:16))
def verifyError(Class thrownClass, Closure closure) {
try {
closure()
assert "Expected thrown: $thrownClass" && false
} catch (Throwable e) {
assert e.class == thrownClass
}
}
// Test exceptions on wrong type
verifyError(PowerAssertionError) { foo.foo(a:5) }
verifyError(PowerAssertionError) { foo.foo(b:'true') }
verifyError(PowerAssertionError) { foo.bar(i:10L) } // long instead of integer
verifyError(PowerAssertionError) { foo.bar(l:10) } // integer instead of long
// Test exceptions on missing properties
verifyError(PowerAssertionError) { foo.foo(nonExisting: 'hello') }
verifyError(PowerAssertionError) { foo.bar(nonExisting: 'hello') }
verifyError(PowerAssertionError) { foo.foo(l: 50L) } // 'l' does not exist on foo

Closure with conditional logging

I have a function like this:
private downloadAllFiles() {
sftpRetriever.listFiles().findAll {
filter.isResponse(it) || filter.isResponseTurned(it)
}.each { String fileName ->
log.info 'Downloading file: {}', fileName
sftpRetriever.downloadFile(fileName)
log.info 'File downloaded'
removeRemoteFile(fileName)
}
}
I am looking for a simple way of modyfing this closure inside of that function so if the size() of findAll is 0 it will simply log 'No more files to download' and .each won't be executed. Is there any simple way to make it in single closure? It is really simply task if I divide it in several parts, but trying to learn closures here and improve my expressiveness :) Thank you in advance for your help.
Take a look at creature below :) It works due the fact that each returns the collection on which it's invoked (+ elvis operator and quite nice Groovy's truth evaluation):
def printContents(Collection collection) {
collection.each {
println it
} ?: println('Collection is empty')
}
printContents([1,2,'hello'])
printContents([])
I don't like this syntax but it's the shorter version which came to my mind.
You can also use metaprogramming to add the method provided by Steinar. It must be added to metaClass before first use but you'll avoid an effort to make extension module:
Collection.metaClass.doIfEmpty { Closure ifEmptyClosure ->
if (delegate.empty) {
ifEmptyClosure()
}
return delegate
}
def printContents(Collection collection) {
collection.doIfEmpty {
println "Collection is empty"
}.each {
println it
}
}
printContents([1,2,'hello'])
printContents([])
One rather generic and reusable option is to extend Collection using an extension module. This is surprisingly easy to do and is even recognized in IDE's (at least in IntelliJ) so you get code completion, etc.
For example, write an the extension class for collections which will perform the closure if the collection is empty. In addtion, it should always return the collection to allow further chaining:
package stackoverflow
class CollectionExtension {
static <T extends Collection> T doIfEmpty(T self, Closure closure) {
if (self.empty) {
closure()
}
return self
}
}
You will also need to tell groovy that this file is an extension module. Add a property file as a resource on the classpath: META-INF/services/org.codehaus.groovy.runtime.ExtensionModule (note: this name and location is mandatory for extension modules, i.e. you cannot change it).
moduleName=stackoverflow-module
moduleVersion=1.0
extensionClasses=stackoverflow.CollectionExtension
Finally a simple test script to show how this can be used:
def printContents(Collection collection) {
collection.doIfEmpty {
println "Collection is empty"
}.each {
println it
}
}
printContents([1,2,'hello'])
printContents([])
Output:
1
2
hello
Collection is empty
You may try the following piece of code:
def l1 = [1,2,3,4]
def l2 = [5,6,7,8]
def m(list) {
list.findAll { it < 5}.with { l ->
size > 0 ?
l.each { e ->
println e
}
:
println('Zero elements found')
}
}
m(l1)
m(l2)
No better idea at the moment.

Groovy: Implicit call not working on instance variables inside closure

A class implements call method so that it's objects can be called as a method. This works for most of the case but not when the call is being made inside a closure on a object which is instance variable of a class.
To demonstrate the problem, in the code below I've commented the interesting lines with numbers. While most variants result in same output, only the line with comment 5 doesn't work. It throws groovy.lang.MissingMethodException: No signature of method: Client2.instanceVar() is applicable for argument types: () values: [])
Can someone help me understand the reason? Is it a bug?
class CallableObject {
def call() { println "hello" }
}
class Client {
def instanceVar = new CallableObject()
def method() {
def localVar = new CallableObject()
def closure1 = { localVar() }
def closure2 = { instanceVar.call() }
def closure3 = { instanceVar() } // doesn't work
localVar() // 1
instanceVar() // 2
closure1() // 3
closure2() // 4
closure3() // 5
}
}
new Client().method()
I guess this will make it clear.
class CallableObject {
def call() { println "hello" }
}
class Client {
def instanceVar = new CallableObject()
def getInstanceVar() {
println "Getter Called"
instanceVar
}
def method() {
def localVar = new CallableObject()
def closure1 = { localVar() }
def closure2 = { instanceVar.call() }
def closure3 = { this.#instanceVar() } //should work now
localVar() // 1
instanceVar() // 2
closure1() // 3
closure2() // 4
closure3() // 5
}
}
new Client().method()
You will see "Getter Called" printed when closure2() invoked. For a global property to be accessed in the closure inside a method, the getter in called instead. To surmount the error you get, the field instanceVar needs to be accessed directly in order to implicitly use call().

Resources