Match Operator prettier syntax in Groovy - groovy

I'm very new to groovy (here working on Jenkinsfile)
One of my coworkers uses a Match Operator to check a condition. But I find it not readable and hard to maintain.
Original Match Operator:
PROJECT_NAME = 'projectA' // User Input from Jenkins params normaly
if ( "${PROJECT_NAME}" ==~ /projectA|projectB|projectC|projectD/) { // The real line is 300 Char long
// Do stuff
}
There is 15 projects in total, i've shorten up the line because it was too long. So every time he needs to add a project name, he appends at the start or end of his regex.
Also, those project name are in a list before.
projects = ['projectA',
'projectB',
'projectC',
'projectD']
Could there be a way to use this list to build the regex?
Here is what I tried:
string_regex = "/"
for (project in projects) {
string_regex = string_regex + project + "|"
}
string_regex = string_regex.substring(0, string_regex.length() - 1)
string_regex = string_regex + "/"
print "${string_regex}\n"
if ("${PROJECT_NAME}" ==~ string_regex) {
print "Well Done you did it\n"
// Do stuff
}
But saddly it doesn't seems to work, since I'm using a string?
EDIT: I found out that I could use the contains method from a list in Groovy. In my case, it fixes my original problem. But I'm still curious on how to build such regex with strings.
if (projects.contains(PROJECT_NAME)) {
// Do stuff
}

You can join your projects and then turn the string into a regexp via Pattern.compile(). For good measure use Pattern.quote() to safe-guard against chars in your project names with "meaning" in regexp.
import java.util.regex.Pattern
def projects = ['projectA',
'projectB',
'projectC',
'projectD']
def re = Pattern.compile(projects.collect{ Pattern.quote it }.join("|"))
['projectA', 'projectX'].each{
println it ==~ re
}
// -> true
// -> false

For what it's worth, I came to like the Groovy matching operators for their compact syntax. If you learn about them and practice for a short bit, you will probably get to like them, too.
Regardless, for a simple check, on whether a String is part of a list, there is a much simpler way in Groovy than using full blown regexp : the in operator, e.g.:
def projects = ['projectA',
'projectB',
'projectC',
'projectD']
['projectA', 'projectX'].each {
println "${it} is ${it in projects ? 'IN' : 'NOT IN'} the project list"
}
which yields e.g.:
projectA is IN the project list
projectX is NOT IN the project list
More info on that operator and many other aspects of the Groovy language from the always excellent MrHaki here
Of course, if you need to account for case differences, etc... you have to massage the code a bit, but at some point, a regexp might be warranted.

If you have already an collection, you should nearly always use an collection operator; E.g. replace
if ( "${PROJECT_NAME}" ==~ /projectA|projectB|projectC|projectD/) {
with:
if (PROJECT_NAME in projects) {
Much easier to read and understand, no? 😉

Related

How to avoid google translate to translate :parameters

I'm using a library nikaia/translation-sheet who basically pulls all the translations from the Laravel site into a google spreadsheet making it "easily" translatable with =GOOGLETRANSLATE(A1)
The problem comes with the parameters:
:price
:amount
:etc
So I've got the idea to substitute ":" with #nonmakingsenseworblablaprice so Google couldn't translate example:
=SUBSTITUTE(GOOGLETRANSLATE(SUBSTITUTE(B2;":";"#nonmakingsenseworblabla");"ES";"EU");"#nonmakingsenseworblabla";":")
Well, not sure why Google eats some letters and puts new ones:
:amount de saldo -> #nonmakingseseworblatamount of saldo
So I decided to do something like detect the parameter and change :amount to :a_m_o_u_n_t and that is apparently working and not being weirdly parsed converted or translated.
I was looking for a solution and found a similar idea but having problems migrating it to spreadsheets script plus is not detecting the parameter
Any one knows how to detect all :parameters in a sentence and put a symbol, slash, dash etc between the characters or letters? Example:
The amount :amount for this order number :order_id is :price
I've also tried regex but not been lucky so far
=REGEXREPLACE(GOOGLETRANSLATE(REGEXREPLACE(B22; ":(\w)([\w]+)"; "{%$1_$2%}"); "ES"; $C$1); "{%(\w)_([^_]+)%}"; ":$1$2")
There's a regex to select the spaces between letters, but good luck making that in excel or spreadsheets. Demo
Finally I've created a script to avoid parameters translation:
function translate(cell, lang) {
const content = cell.toString();
const keys = [];
const enc = content.replace(/:([\w_]+)/ig, function(m, param) {
const n = `[§${keys.length}]`;
keys.push(param);
return n;
});
return LanguageApp.translate(enc, "es", lang).replace(/\[§(\d+)\]/ig, function(m, param) {
return `:${keys[param]}`;
});

Kotlin and String.contains() not working as I thought

I thought I knew how string.contains() worked in Kotlin and Java, but apparently I don't.
I'vet got a small piece of code that takes a list of file names, and puts them in another list if the do not contain certain words.
for (i in 0..filliste.size-1) {
if (!filliste[i].contains("utenfastbopel") || !filliste.contains("sperret") ||
!filliste.contains("reservert")){
var a = filliste[i]
tempFnrliste += filliste[i].split("_")[0]
}
}
However, this does not exclude a file which contains the phrase "sperretstrengtreservert", even though both "reservert" and "sperret" is in the "not contains".
How come? I thought .contains found every occurence of a substring?
But if you look at the debug run, a file containing two of the phrases that are to be ignored, is indeed not ignored:
UPDATE:
To be clear, I'm looking for any of the file names to contain one OR more of the strings. So the logical OR/|| is correct.
However, I missed some indices. But adding them changed nothing. See the updated code below:
As far as I can see, the code now clearly says IF THE STRING DOES NOT CONTAIN THIS, THIS OR THIS SUBSTRING... But still, a string containing two of the substrings gets a match.
Strangely, if I only use ONE substring in the "not-contains" - for instance "reservert", the code does indeed skip all strings not containing that. But when I use the || operator for several substrings, things gets messed up.
"sperretstrengtreservert" does not contain utenfastbopel.
You are using || aka OR. Your first condition is true.
If any of these is true, it will go to the body of the condition.
!filliste[i].contains("utenfastbopel") ||
!filliste.contains("sperret") ||
!filliste.contains("reservert")
Also as said you are not accessing the same object in the follow-up conditions although it wouldn't change the result as is.
You need to change it from "at least one of these conditions must be true" to "all of these conditions must be true" && aka AND.
for (i in 0..filliste.size-1) {
val f = filliste[i]
if (!f.contains("utenfastbopel") && !f.contains("sperret") && !f.contains("reservert")) {
tempFnrliste += f.split("_")[0]
}
}

Replacing a certain part of string with a pre-specified Value

I am fairly new to Puppet and Ruby. Most likely this question has been asked before but I am not able to find any relevant information.
In my puppet code I will have a string variable retrieved from the fact hostname.
$n="$facts['hostname'].ex-ample.com"
I am expecting to get the values like these
DEV-123456-02B.ex-ample.com,
SCC-123456-02A.ex-ample.com,
DEV-123456-03B.ex-ample.com,
SCC-999999-04A.ex-ample.com
I want to perform the following action. Change the string to lowercase and then replace the
-02, -03 or -04 to -01.
So my output would be like
dev-123456-01b.ex-ample.com,
scc-123456-01a.ex-ample.com,
dev-123456-01b.ex-ample.com,
scc-999999-01a.ex-ample.com
I figured I would need to use .downcase on $n to make everything lowercase. But I am not sure how to replace the digits. I was thinking of .gsub or split but not sure how. I would prefer to make this happen in a oneline code.
If you really want a one-liner, you could run this against each string:
str
.downcase
.split('-')
.map
.with_index { |substr, i| i == 2 ? substr.gsub(/0[0-9]/, '01') : substr }
.join('-')
Without knowing what format your input list is taking, I'm not sure how to advise on how to iterate through it, but maybe you have that covered already. Hope it helps.
Note that Puppet and Ruby are entirely different languages and the other answers are for Ruby and won't work in Puppet.
What you need is:
$h = downcase(regsubst($facts['hostname'], '..(.)$', '01\1'))
$n = "${h}.ex-ample.com"
notice($n)
Note:
The downcase and regsubst functions come from stdlib.
I do a regex search and replace using the regsubst function and replace ..(.)$ - 2 characters followed by another one that I capture at the end of the string and replace that with 01 and the captured string.
All of that is then downcased.
If the -01--04 part is always on the same string index you could use that to replace the content.
original = 'DEV-123456-02B.ex-ample.com'
# 11 -^
string = original.downcase # creates a new downcased string
string[11, 2] = '01' # replace from index 11, 2 characters
string #=> "dev-123456-01b.ex-ample.com"

How to check if string contains a substring in terraform interpolation?

How do you check if a terraform string contains another string?
For example, I want to treat terraform workspaces with "tmp" in the name specially (e.g. allowing rds instances to be deleted without a snapshot), so something like this:
locals
{
is_tmp = "${"tmp" in terraform.workspace}"
}
As far as I can tell, the substr interpolation function doesn't accomplish this.
For terraform 0.12.xx apparently you are suppose to use regexall to do this.
From the manual for terraform 0.12.XX:
regexall() documentation
regexall can also be used to test whether a particular string matches a given pattern, by testing whether the length of the resulting list of matches is greater than zero.
Example from the manual:
> length(regexall("[a-z]+", "1234abcd5678efgh9"))
2
> length(regexall("[a-z]+", "123456789")) > 0
false
Example applied to your case in terraform 0.12.xx syntax should be something like:
locals
{
is_tmp = length(regexall(".*tmp.*", terraform.workspace)) > 0
}
It also specifically says in the manual not to use "regex" but instead use regexall.
If the given pattern does not match at all, the regex raises an error. To test whether a given pattern matches a string, use regexall and test that the result has length greater than zero.
As stated above this is because you will actually get an exception error when you try to use it in the later versions of 0.12.xx that are out now when you run plan. This is how I found this out and why I posted the new answer back here.
You can indirectly check for substrings using replace, e.g.
locals
{
is_tmp = "${replace(terraform.workspace, "tmp", "") != terraform.workspace}"
}
Like #MechaStorm, with Terrafor 0.12.7+ you can use regex to return a Boolean value if your string contains a particular substring
locals {
is_tmp = contains(regex("^(?:.*(tmp))?.*$",terraform.workspace),"tmp")
}
The regex query returns a list of capture groups for any characters before tmp, tmp if found, any characters after tmp. Then contains looks for "tmp" in the list and returns true or false. I am using this type of logic in my own terraform.
Length of the list produced by split function is greater than one when separtor is a substring.
locals {
is_tmp = length(split("tmp", terraform.workspace)) > 1
}
Use replace( string, search, replace ) as in the snippet:
// string contains ABBA = result is ABBA
output "match" {
value = "${ replace("xxxABBAyyy", "/(?:.*)(ABBA)(?:.*)/", "$1") }"
}
// string doesn't contain ABBA = result is original string
output "no_match" {
value = "${ replace("xxxBABAyyy", "/(?:.*)(ABBA)(?:.*)/", "$1")}"
}
// string contains ABBA (ingorecase) = result is AbBA
output "equals_ignorecase" {
value = "${ replace("xxxAbBAyyy", "/(?:.*)((?i)ABBA)(?:.*)/", "$1")}"
}
An output of terraform apply is:
Outputs:
equals_ignorecase = AbBA
match = ABBA
no_match = xxxBABAyyy
In terraform 0.12.7, we now have regex . This may help simplify some code and make it readable to some (perhaps?)
> regex("[a-z]+", "53453453.345345aaabbbccc23454")
aaabbbccc
I use this way to check if bucket name start with test-tmp
eg. test-tmp, test-tmp-app1, test-tmp-db1 etc..
is_test_bucket = can(regex("^(test-tmp){1}($|-{1}.*$)", var.bucket_name))
Something that makes sense reading, IMHO:
locals {
is_tmp = can(regex("tmp", terraform.workspace))
}
This works because the regex function will raise an error if no matches are found.
Bonus: since Terraform 1.3.x, there are the new startswith and endswith functions that can be handy in a good amount of cases.

Switching on String Value Yields Unexpected Results in Groovy

I am working in a groovy/grails set up and am having some trouble trying to execute a switch statement on a String value.
Basically, I am looping through the attribute names in a webservice response to see if they match pre-defined mappings that are configured on a per user basis. If they have established a mapping on that field, I pull the value out of the response and use it elsewhere.
The code looks something like this:
switch(attributeName)
{
case {attributeName} :
log.info("Currently switching on value... ${attributeName}")
case user.getFirstNameMapping():
model.user.userInfo.firstName = attributeValue
break
case user.getLastNameMapping():
model.user.userInfo.lastName = attributeValue
break
case user.getAuthenticationKeyMapping():
model.authenticationValue = attributeValue
break
case user.getEmailMapping():
model.email = attributeValue.toLowerCase()
break
}
The value being switched on (attributeName) is of type String, and the getter methods for the user instance also return type String.
Based on my research and understanding of the Groovy language, switching on an Object such as a String should end up using String.equals() to make the comparison. The result, however, is that it is matching on the user.getFirstNameMapping() case every time, and repeatedly overwriting the value in the model; therefore, the last value that comes back in the response is what ends up saved, and none of the other values are saved.
What's interesting is that if I use an if/else structure and do something like this:
if(attributeName.equals(user.getFirstNameMapping())
{
...
}
It works fine every time. I've verified through logging that it's not something silly like extra whitespace or a capitalization issue. I've also tried changing things around to run the switch by default and explicitly compare the attributeName in the case like this:
switch(true)
{
case {attributeName} :
log.info("Currently switching on value... ${attributeName}")
case {user.getFirstNameMapping().equals(attributeName)}:
model.user.userInfo.firstName = attributeValue
break
case {user.getLastNameMapping().equals(attributeName)}:
model.user.userInfo.lastName = attributeValue
break
case {user.getAuthenticationKeyMapping().equals(attributeName)}:
model.authenticationValue = attributeValue
break
case {user.getEmailMapping().equals(attributeName)}:
model.email = attributeValue.toLowerCase()
break
}
And it still fails to meet my expectations in the exact same way. So, I'm wondering why this is the behavior when the switch statement should simply be using .equals() to compare the strings, and when I explicitly compare them in an if/else using .equals(), it works as expected.
The issue is in your switch case.
Have a look here :-
case {attributeName} :
log.info("Currently switching on value... ${attributeName}")
case user.getFirstNameMapping():
model.user.userInfo.firstName = attributeValue
break
As you can see your these two cases will run every time because the switch condition is :-
switch(attributeName)
So the first one will get match and will run until it encounters break; which is at after case 2 i.e. case user.getFirstNameMapping(): so i would suggest you to print the value of {attributeName} before the swtich starts.
Hope that will help you.
Thanks
I don't know exactly what's your issue, but the case statement works just fine, even with methods. See my example
String something = "Foo"
class User {
String firstName
String lastName
}
User u = new User(firstName: 'Something', lastName:'Foo')
switch(something) {
case u.getFirstName():
println "firstName: ${u.firstName}"
break;
case u.getLastName():
println "lastName: ${u.lastName}"
break;
default:
println "nothing..."
}
This code will print lastName as expected.
​

Resources