Convert camelCase string to dash-separated in Gradle (i.e. Groovy) - string

I have a bunch of string properties in a gradle build script, which is in camelCase (for example "jmxConsoleWeb"), and I'd like to split each into a dash-separated (e.g. jmx-console-web) string instead.
Is there a "non-hackish" way to do this?

I've solved it like this:
public static String dashSeparated(String s) {
return s.replaceAll(/\B[A-Z]/) { '-' + it }.toLowerCase()
}
Also take a look here

You can use Guava if you don't mind the extra dependancy. Here's a Groovy script that demonstrates its use:
#Grab( 'com.google.guava:guava:13.0.1' )
import static com.google.common.base.CaseFormat.*
String.metaClass.caseFormat = { from, to ->
from.to( to, delegate )
}
assert 'varName'.caseFormat( LOWER_CAMEL, UPPER_UNDERSCORE ) == 'VAR_NAME'
assert 'var-name'.caseFormat( LOWER_HYPHEN, UPPER_CAMEL ) == 'VarName'
assert 'var_name'.caseFormat( LOWER_UNDERSCORE, LOWER_CAMEL ) == 'varName'
assert 'VAR_NAME'.caseFormat( UPPER_UNDERSCORE, LOWER_UNDERSCORE ) == 'var_name'
assert 'VarName'.caseFormat( UPPER_CAMEL, LOWER_HYPHEN ) == 'var-name'
Of course, in a Gradle script, you'd need to import guava into the buildScript dependencies if you want its methods available to the build itself

Related

Is there a shorter replacement for Kotlin's deprecated String.capitalize() function?

Kotlin deprecated the capitalize function on String class, and their suggested replacement is obnoxiously long. This is an example of a situation where they made the right call on deprecating it, but the wrong call on the user experience.
For example, this code:
val x = listOf("foo", "bar", "baz").map { it.capitalize() }
is "cleaned up" by the IDE to become:
val x = listOf("foo", "bar", "baz").map { it.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(
Locale.getDefault()
) else it.toString()
} }
This is preeeeetty ugly. What can we do about it?
The suggested replacement is ugly because it needs to be equivalent to what capitalize() used to do:
dependent on the default locale
NOT converting an uppercase first char into titlecase (e.g.
capitalize does NOT transform a leading 'DŽ' into 'Dž' - both are single characters here, try to select them)
If you didn't care too much about this behaviour, you can use a simpler expression using an invariant locale and unconditionally titlecasing the first character even if uppercase:
val x = listOf("foo", "bar", "baz").map { it.replaceFirstChar(Char::titlecase) }
This means that if the first character is uppercase like 'DŽ', it will be transformed into the titlecase variant 'Dž' anyway, while the original code wouldn't touch it. This might actually be desirable.
One of the reasons capitalize() has been deprecated is because the behaviour of the method was unclear. For instance:
behaviour #2 is pretty weird
not capitalizing words in a sentence might be unexpected (C# would titlecase every space-separated word)
not lowercasing other characters of the words might be unexpected as well
If you want to keep the exact current behaviour on purpose, but make it more convenient to use, you can always roll your own extension function with a name that suits you ("capitalize(d)" might not give enough info to the unaware reader):
fun String.titlecaseFirstCharIfItIsLowercase() = replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
}
Or for the version with invariant locale that titlecases the uppercase chars:
fun String.titlecaseFirstChar() = replaceFirstChar(Char::titlecase)
A neat solution is to define a new extension function on String, which hides the gory details with a cleaner name:
/**
* Replacement for Kotlin's deprecated `capitalize()` function.
*/
fun String.capitalized(): String {
return this.replaceFirstChar {
if (it.isLowerCase())
it.titlecase(Locale.getDefault())
else it.toString()
}
}
Now your old code can look like this:
val x = listOf("foo", "bar", "baz").map { it.capitalized() }
You'll need to define the extension function at the top level in some package that you can import easily. For example, if you have a kotlin file called my.package.KotlinUtils (KotlinUtils.kt), and you put the definition inside it like so:
package my.package
fun String.capitalized(): String {...}
Then you can import it in your other packages with:
import my.package.capitalized
val fruits = listOf("baNana", "avocAdo", "apPle", "kiwifRuit")
fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.lowercase().replaceFirstChar(Char::uppercase) }
.forEach { println(it) }
Output:
Apple
Avocado
You can call the replaceFirstChar function on the original string and pass the transform function as input. The transform function takes the first character and converts it to an uppercase character using the uppercase() function.
val list = listOf("foo", "bar", "baz") .map {
it.replaceFirstChar { firstChar ->
firstChar.uppercase()
}
}
println("List - > $list")
Output
List - > [Foo, Bar, Baz]
How about this?
fun main() {
val x = listOf("foo", "bar", "baz").map { it[0].uppercase() + it.drop(1) }
println(x)
}
Output:
[Foo, Bar, Baz]
If you are not sure (maybe you receive Strings from an API) if the first letter is upper or lower case , you can use the below method;
var title = "myTitle"
title.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else
it.toString()
}
New title will be "MyTitle"
You can use this extension function to capitalize first characture of String
fun String.capitalize(): String {
return this.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault())
else it.toString()
}
}
And call this method like
"abcd".capitalize()
I found a method trying to capitalize a string that came from the API and it apparently worked, found it in the Kotlin docs:
println("kotlin".replaceFirstChar { it.uppercase() }) // Kotlin
and use it like this in my code:
binding.textDescriptions.text = "${it.Year} - ${it.Type.replaceFirstChar {it.uppercase()}}"

Groovy Spock unit tests with closures

How can I do this in Spock/groovy?
package org.jenkinsci.plugins
import hudson.matrix.*
import spock.lang.*
import org.junit.Rule
import org.jvnet.hudson.test.JenkinsRule
class xxxx extends Specification {
#Rule JenkinsRule rule = new JenkinsRule()
def 'matrix'() {
given:
def matrixProject = rule.createMatrixProject()
AxisList axl = new AxisList();
def axis = new TextAxis('TEST', "1", "2", "3")
axl.add(axis)
matrixProject.setAxes(axl)
expect: matrixProject.scheduleBuild2(0).get().logFile.text.contains("Some String!")
matrixProject.scheduleBuild2(0).get().getRuns().each(){
expect: it.logFile.text.contains("Another String")
}
}
}
specifically, how can I run a closure with a nested test? The "Another String" test doesn't work
Does this work?
def 'matrix'() {
given:
def matrixProject = rule.createMatrixProject()
def axis = new TextAxis('TEST', "1", "2", "3")
matrixProject.axes.add(axis)
expect:
with( matrixProject.scheduleBuild2(0).get() ) {
logFile.text.contains("Some String!")
runs.every { it.logFile.text.contains("Another String") }
}
}
}
Either use every instead of each, or use a nested assert.
I'm not sure if I understand your question well. However if by nested test you mean evaluating statement inside of each closure, why not just use assert
expect:
matrixProject.scheduleBuild2(0).get().logFile.text.contains("Some String!")
matrixProject.scheduleBuild2(0).get().getRuns().each() {
assert it.logFile.text.contains("Another String")
}
#tim_yates's approach also seems fine and it's more like Spock's way. I haven't tested it though.
EDIT
If you want be sure that all logFiles contain test string then use 'every' method as Peter suggested.
expect:
...
matrixProject.scheduleBuild2(0).get().getRuns().every {
it.text.contains('Another String')
}
Other approach, if you prefer to know how many logFiles don't contain test string on test fail count them and compare result size to zero:
expect:
...
matrixProject.scheduleBuild2(0).get().getRuns().count {
!it.text.contains('Another String')
} == 0
Yet another, if you like to know which files caused test to fail, get names of those which don't contain test string and compare that to an empty list:
expect:
...
matrixProject.scheduleBuild2(0).get().getRuns().findAll {
!it.text.contains('Another String')
}*.name == []

Load script from groovy script

File1.groovy
def method() {
println "test"
}
File2.groovy
method()
I want to load/include the functions/methods from File1.groovy during runtime, equals to rubys/rake's load. They are in two different directories.
If you don't mind the code in file2 being in a with block, you can do:
new GroovyShell().parse( new File( 'file1.groovy' ) ).with {
method()
}
Another possible method would be to change file1.groovy to:
class File1 {
def method() {
println "test"
}
}
And then in file2.groovy you can use mixin to add the methods from file1
def script = new GroovyScriptEngine( '.' ).with {
loadScriptByName( 'file1.groovy' )
}
this.metaClass.mixin script
method()
You can evaluate any expression or script in Groovy using the GroovyShell.
File2.groovy
GroovyShell shell = new GroovyShell()
def script = shell.parse(new File('/path/file1.groovy'))
script.method()
It will be easiest if file1.groovy is an actual class class File1 {...}.
Given that, another way to do it is to load the file into the GroovyClassLoader:
this.class.classLoader.parseClass("src/File1.groovy")
File1.method()
File1.newInstance().anotherMethod()
I am late on this but. This is how we have been achieving what you were asking. So, i have a file1.gsh like so:
File1:
println("this is a test script")
def Sometask(param1, param2, param3)
{
retry(3){
try{
///some code that uses the param
}
catch (error){
println("Exception throw, will retry...")
sleep 30
errorHandler.call(error)
}
}
}
return this;
And in the other file, these functions can be accessed by instantiating first. So in file2.
File2:
def somename
somename = load 'path/to/file1.groovy'
//the you can call the function in file1 as
somename.Sometask(param1, param2, param3)
Here is what I'm using.
1: Write any_path_to_the_script.groovy as a class
2: In the calling script, use:
def myClass = this.class.classLoader.parseClass(new File("any_path_to_the_script.groovy"))
myClass.staticMethod()
It's working in the Jenkins Groovy script console. I have not tried non-static methods.
The answer by #tim_yates that uses metaClass.mixin should have worked without needing any changes to file1.groovy (i.e., mixin with the script object), but unfortunately there is a bug in metaClass.mixin that causes a SO error in this scenario (see GROOVY-4214 on this specific issue). However, I worked around the bug using the below selective mixin:
def loadScript(def scriptFile) {
def script = new GroovyShell().parse(new File(scriptFile))
script.metaClass.methods.each {
if (it.declaringClass.getTheClass() == script.class && ! it.name.contains('$') && it.name != 'main' && it.name != 'run') {
this.metaClass."$it.name" = script.&"$it.name"
}
}
}
loadScript('File1.groovy')
method()
The above solution works with no changes being needed to File1.groovy or the callers in File2.groovy (except for the need to introduce a call to loadScript function).

What's wrong with this Groovy construct?

This is a short Groovy script:
import org.apache.commons.io.FileUtils;
def dir = new File("/mydir")
def files = FileUtils.listFiles(dir, new String[] { "java" }, false)
It says:
No expression for the array constructor call at line: 2
What's wrong?
The call should be:
def files = FileUtils.listFiles(dir, [ "java" ] as String[], false)
Groovy uses Lists by default, and the as operator can be used to coerce these lists into arrays of a specified type (often for interacting with the java api as in this example)
[edit]
As an aside, you can do this with pure Groovy like so:
def files = dir.listFiles().findAll { it.name ==~ /.*\.java/ }
Then, you don't need Commons FileUtils

how to create xml document with special node names with groovy markupbuilder

I am building an ant script with groovy markupbuilder. Unfortunately markupbuilder doesn't allow me to create nodes with name 'target' (no problem with targetee),
becauase it throws me
Caught: groovy.lang.MissingMethodException: No signature of method: java.lang.String.call() is applicable for argument types: (java.util.LinkedHashMap, BuildGen$_main_closure1_closure5) values: [[name:clean], BuildGen$_main_closure1_closure5#18efaea]
Possible solutions: wait(), any(), trim(), split(), dump(), next()
so inside my markupbuilder this snippet works:
targete(name: 'clean') {
delete(dir: rootProj.compilerOutput)
}
but I would like to achieve the same with a 'target' node..
I managed to create an empty 'target' node this way:
builder.invokeMethod('target', [name: 'clean'])
but how can I go on and put more nodes inside this 'target' node?
Example of working code:
def writer = new StringWriter()
def builder = new groovy.xml.MarkupBuilder(writer)
builder.project(name: projectName, basedir:'.') {
// works with 'target2' but not with 'target'
'target2'(name: 'build-subprojects') {
rootProj.getAllDependentProjects().each { p->
echo(message: "Compiling project: ${p.projectName}")
// some real stuff
}
}
If I guess right, your problem is you want to create nodes with names that are Groovy keywords?
If so, then you can simply put the name in quotes, like so:
def writer = new StringWriter()
def builder = new groovy.xml.MarkupBuilder( writer )
builder.project {
'for'(name: 'clean') {
delete(dir: '.')
}
}
println writer
That snippet will print out:
<project>
<for name='clean'>
<delete dir='.' />
</for>
</project>
For me, this works:
def projects = [ 'pro_one', 'pro_two' ]
def writer = new StringWriter()
def builder = new groovy.xml.MarkupBuilder(writer)
builder.project( name: 'test', basedir:'.' ) {
'target'( name: 'build-subprojects' ) {
projects.each { p ->
echo( message: "Compiling project: ${p}" )
}
}
}
println writer.toString()
Have you got target set to anything in your code before calling this?
You could try:
builder.target( name: 'build-subprojects' ) {
That might work better?
I've tried Groovy 1.7.5, and 1.8 beta 2 and can't get it to fail :-/

Resources