Notations:
// don't do like this
// better like this
Not supported:
do-while
loops (added in 3.0)
def a = 123
Integer b = 123
def c = "123" as Integer
def
or type-specificdef calc(def x, def y)
def calc(x, y) // def in params can be skipped
Float calc(Float x, Integer y)
int method(String arg) { return 1; }
int method(Object arg) { return 2; }
Object o = "Object";
int result = method(o);
assertEquals(2, result); // in Java
assertEquals(1, result); // in Groovy โ ๏ธ
return
& ;
Integer fib(Integer n) {
if (n < 2) return 1 ;
return fib(n - 1) + fib(n - 2) ;
}
return
& ;
Integer fib(Integer n) {
if (n < 2) return 1
fib(n - 1) + fib(n - 2)
}
()
def sum(a, b) { a + b }
def a = sum(4, 5)
def b = sum 4, 5
println sum(4, 5)
def noParam() { 123 }
def c = noParam()
if (<expr>) { ... }
|
|
Closure is a piece of code that is defined and then executed at a later point. It can take arguments, return a value and be assigned to a variable. It may reference variables declared in its surrounding scope.
def sum = { int a, int b -> a + b }
assert sum(2, 3) == 5
def greet = { name -> "Hello ${name}!" }
def greet = { "Hello ${it}!" } // โ ๏ธ implicit `it`
assert greet('John') == 'Hello John!'
owner
- enclosing object where the closure is defined (either a class or a closure)delegate
- an object where methods calls or properties are resolved if the receiver of the message is not definedowner
== delegate
.delegate
can be changed.class One {
def value() { "A" }
def cl = { "value = ${value()}" }
}
println new One().cl() // value = A
OWNER_FIRST
- default. If a property/method exists on the owner, call it on owner. Otherwise use the delegate.OWNER_ONLY
- call the property/method on the owner, ignore delegate.DELEGATE_FIRST
DELEGATE_ONLY
def calendar = Calendar.instance
calendar .clear()
calendar .set calendar .YEAR, 2019
calendar .set calendar .MONTH, calendar .DECEMBER
calendar .set calendar .DAY_OF_MONTH, 2
println calendar .time // Mon Dec 02 00:00:00 CET 2019
def closure = {
clear()
set YEAR, 2019
set MONTH, DECEMBER
set DAY_OF_MONTH, 2
println time
}
def calendar = Calendar.instance
closure.delegate = calendar
closure.resolveStrategy = Closure.DELEGATE_ONLY
closure() // Mon Dec 02 00:00:00 CET 2019
with
def calendar = Calendar.instance
calendar.with {
clear()
set YEAR, 2019
set MONTH, DECEMBER
set DAY_OF_MONTH, 2
println time // Mon Dec 02 00:00:00 CET 2019
}
def method(Closure cl) { cl 'world' }
def closure = { println "Hello $it!" }
method(closure)
method({ println "Hello $it!" })
method { println "Hello $it!" }
method {
println "Hello $it!"
}
A domain-specific language (DSL) is a computer language specialized to a particular application domain.
pipeline {
stage('preparation') {
sh './clone.sh'
sh './init.sh'
}
stage('build') {
sh './build.sh --env=prod'
echo 'DONE!'
}
}
# stage preparation
/bin/sh -c './clone.sh'
/bin/sh -c './init.sh'
# stage build
/bin/sh -c './build.sh --env=prod'
echo 'DONE!'
class PipelineBuilder {
List<String> cmds = []
def stage(String name, Closure cl) {
cmds << "# stage ${name}"
cl()
}
def echo(String command) {
cmds << "echo '${command}'"
}
def sh(String command) {
cmds << "/bin/sh -c '${command}'"
}
}
def pipeline(Closure cl) {
def builder = new PipelineBuilder()
cl.delegate = builder
cl()
builder.cmds.each { println it }
}
pipeline {
stage('preparation') {
sh './clone.sh'
sh './init.sh'
}
stage('build') {
sh './build.sh --env=prod'
echo 'DONE!'
}
}
def a = [1, 2, 3, 4]
def b = [city: 'Berlin', state: 'DE']
"x = ${b.city} + ${20 + 22}"
โ 'x = Berlin 42'
'x = ${b.city}'
โ 'x = ${b.city}'
List a = [1, 2, 3]
a << 4 // a == [1, 2, 3, 4]
a << [5, 6] // a == [1, 2, 3, 4, [5, 6]]
def a = [1, 2, 3]
a += [4, 5] // a == [1, 2, 3, 4, 5]
List<Integer> a = [1, 2, 3]
a.addAll([4, 5]) // โ ๏ธ returns true, a == [1, 2, 3, 4, 5]
for
โ .each
[1, 2, 3].each {
println "Item: $it"
}
['a', 'b', 'c'].eachWithIndex { it, i ->
println "Index = $i, Item = $it"
}
[1, 2, 3].collect { it * 2 } // [2, 4, 6]
[1, 2, 3].find { it > 1 } // 2
[1, 2, 3].findAll { it > 1 } // [2, 3]
['a', 'b', 'c'].collectEntries { [(it): it.toUpperCase()] }
// ['a': 'A', 'b': 'B', 'c': 'C']
(0..10).findAll { it % 2 == 0 }.collect { it ** 2 }.sum()
// 220 = 0 + 4 + 16 + 36 + 64 + 100
def a = [1, 2, 3, 4, 5]
assert a[0] == 1
assert a[-2] == 4
assert a[2..4] == [3, 4, 5]
assert a[4..<2, 0, 0, 4] == [5, 4, 1, 1, 5]
assert 'hello'[0, 4..<0] == 'holle'
assert 'hello'.split('').first() == 'h' // โ ๏ธ fails if null/[]
assert 'hello'.split('').last() == 'o' // โ ๏ธ fails if null/[]
Map a = [key1: 'val1']
assert a.key1 == 'val1'
assert a['key1'] == 'val1'
a << [key2: 'val20', key3: 'val3']
// a == [key1: 'val1', key2: 'val20', key3: 'val3']
a += [key3: 'val30']
// a == [key1: 'val1', key2: 'val20', key3: 'val30']
for
โ .each
[a: 1, b: 2, c: 3].each {
println "k = ${it.key}, v = ${it.value}"
}
[a: 1, b: 2, c: 3].each { k, v ->
println "k = ${k}, v = ${v}"
}
[a: 1, b: 2, c: 3].collect { it.value * 2 } // [2, 4, 6]
[a: 1, b: 2, c: 3].collect { k, v -> v * 2 } // [2, 4, 6]
[a: 1, b: 2, c: 3].find { it.value > 1 }.value // 2 โ ๏ธ
[a: 1, b: 2, c: 3].findAll { it.value > 1 } // [b: 2, c: 3]
['a': 1, 'b': 2, 'c': 3].collectEntries {
k, v -> [(k): v ** 3]
}
// ['a': 1, 'b': 8, 'c': 27]
in
operator'a' in ['a', 'b', 'c'] // true
'b' in [a: 1, b: 2, c: 3] // true
'a' in null // false
assert (2..5) == [2, 3, 4, 5]
assert (2..<5) == [2, 3, 4]
assert (-5..3).step(2) == [-5, -3, -1, 1, 3]
assert ('d'..<'a') == ['d', 'c', 'b']
3.times { println "i = ${it}" }
// i = 0
// i = 1
// i = 2
def expected = [0, 2, 4, 6]
assert (0..10).findAll { it % 2 == 0 } == expected
| | |
[0, 2, 4, 6, 8, 10] | [0, 2, 4, 6]
false
Properties — fields without access modifier
class Company {
String name
}
def name = company.getName()
company.setName("whatever")
def name = company.name
company.name = "whatever"
class Company {
def getInformation() {
// implementation
}
}
assert company.getInformation() == company.information
class A {
boolean is Something() {
// implementation
}
}
def a = new A()
assert a.isSomething() == a.something
class Person {
String firstName
String lastName
String fullName() {
"${firstName} ${lastName}"
}
}
def p = new Person(firstName: 'John', lastName: 'Doe')
assert p.fullName() == 'John Doe'
call
method ๐
class A {
def call(Integer x) { "got int" }
def call(Float x) { "got float" }
def call(Object x) { "got something else" }
def call() { "got nothing" }
}
def a = new A()
println a() // got nothing
println a(1) // got int
println a(2.5f) // got float
println a('abc') // got something else
def p = new Person(firstName: 'John', lastName: 'Doe')
?:
def displayName
if (user.name) {
displayName = user.name
} else {
displayName = 'Anonymous'
}
def displayName = user.name ? user.name : 'Anonymous'
def displayName = user.name ?: 'Anonymous'
?:
*.
def a = new Company(name: 'ABC')
def b = new Company(name: 'BCD')
def c = new Company(name: 'DEF')
def names = [a, b, c].collect { it.name }
// ['ABC', 'BCD', 'DEF']
def names = [a, b, c]*.name
assert [a, b, c]*.name*.toLowerCase() == ['abc', 'bcd', 'def']
?.
class Company {
Address address
}
if (company != null && company.address != null &&
company.address.street != null) {
println company.address.street.name
}
println company?.address?.street?.name
private
, hidden public
public
is default
public def sum(def a, def b) { a + b }
private
& protected
are accessible by anyoneprivate
methods in tests.
private
โ protected
๐PPP
are
def testSwitch(val) {
def result
switch (val) {
case 60..90:
result = 'Range contains'; break
case [21, 'test', 9.12]:
result = 'List contains'; break
case { it instanceof Integer && it < 50 }:
result = 'Closure boolean'; break
}
result
}
assert testSwitch(21) == 'List contains'
def finder = ('groovy' ==~ /gr.*/)
assert finder == true
def group = ('groovy and grails, ruby and rails' =~
/(\w+) and (\w+)/)
assert group instanceof java.util.regex.Matcher
assert 2 == group.size()
assert ['groovy and grails', 'groovy', 'grails'] == group[0]
assert ['ruby and rails', 'ruby', 'rails'] == group[1]
import groovy.json.JsonSlurper
String url = 'https://jsonplaceholder.typicode.com/todos/1'
String response = url.toURL().text
def contents = new JsonSlurper().parseText(response)
assert contents.title == 'delectus aut autem'
def changeLogSets = currentBuild.changeSets ?: []
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
def files = new ArrayList(entry.affectedFiles)
for (int k = 0; k < files.size(); k++) {
def file = files[k]
body(jenkinsFileNames, entry, file)
}
}
}
def changeLogSets = currentBuild.changeSets ?: []
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
def files = new ArrayList(entry.affectedFiles)
for (int k = 0; k < files.size(); k++) {
def file = files[k]
body(jenkinsFileNames, entry, file)
}
}
}
def changeLogSets = currentBuild.changeSets ?: []
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
def files = new ArrayList(entry.affectedFiles)
for (int k = 0; k < files.size(); k++) {
body(jenkinsFileNames, entry, files[k])
}
}
}
def changeLogSets = currentBuild.changeSets ?: []
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
for (int k = 0; k < entry.affectedFiles.size(); k++) {
body(jenkinsFileNames, entry, entry.affectedFiles[k])
}
}
}
def changeLogSets = currentBuild.changeSets ?: []
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
}
}
def changeLogSets = currentBuild.changeSets ?: []
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
entries.each { entry ->
echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
}
}
def changeLogSets = currentBuild.changeSets ?: []
changeLogSets.each { changeLogSet ->
def entries = changeLogSet.items
entries.each { entry ->
echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
}
}
def changeLogSets = currentBuild.changeSets ?: []
changeLogSets.each { changeLogSet ->
changeLogSet.items.each { entry ->
echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
}
}
def changeLogSets = currentBuild.changeSets ?: []
changeLogSets*.items.each { entries ->
entries.each { entry ->
echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
}
}
currentBuild.changeSets*.items.each { entries ->
entries.each { entry ->
echo "${entry.commitId} by ${entry.author} on ${entry.timestamp}: ${entry.msg}"
entry.affectedFiles.each { body(jenkinsFileNames, entry, it) }
}
}
Traits are a structural construct of the language which allows:
- composition of behaviors
- runtime implementation of interfaces
- behavior overriding
- compatibility with static type checking/compilation
They can be seen as interfaces carrying both default implementations and state.
trait Greetable {
abstract String name()
String greeting() { "Hello, ${name()}!" }
}
class Person implements Greetable {
String name() { 'Bob' }
}
def p = new Person()
assert p.greeting() == 'Hello, Bob!'
trait FlyingAbility { String fly() { 'I am flying!' } } trait SpeakingAbility { String speak() { 'I am speaking!' } }
class Duck implements FlyingAbility, SpeakingAbility {} def d = new Duck() assert d.fly() == 'I am flying!' assert d.speak() == 'I am speaking!'
trait Extra { String extra() { 'Extra method' } } class Something { String doSomething() { 'Something' } }
def s = new Something() as Extra println s.extra() println s.doSomething()
@NonCPS
CodeNarc analyzes Groovy code for defects, bad practices, inconsistencies, style issues and more. A flexible framework for rules, rulesets and custom rules means it's easy to configure CodeNarc to fit into your project.
jenkins-job-dsl-seed-jobs, 11k LOC
Spock is a testing and specification framework for Java and Groovy applications. What makes it stand out from the crowd is its beautiful and highly expressive specification language.Test structure:
given
: setupwhen
: method invocationthen
: result checkexpect
: when
+ then
where
: parametrizationdef "mply: tests multiply function"() {
when:
def result = mply(a, b)
then:
assert result == expected
where:
a | b | expected
3 | 4 | 12
0 | 1000 | 0
-1 | -1 | 1
}
def "mply: tests multiply function"() {
expect:
mply(a, b) == expected
where:
a | b | expected
3 | 4 | 12
0 | 1000 | 0
-1 | -1 | 1
}
assert ['Hubert', 'Alexander', 'Klein', 'Ikkink'].inject('mr') { nickname, name ->
nickname + name[0].toLowerCase()
} == 'mrhaki'
ยซHow can I make it shorter?ยป