Need the Groovy way to do partial file substitutions - regex

I have a file that I need to modify. The part I need to modify (not the entire file), is similar to the properties shown below. The problem is that I only need to replace part of the "value", the "ConfigurablePart" if you will. I receive this file so can not control it's format.
alpha.beta.gamma.1 = constantPart1ConfigurablePart1
alpha.beta.gamma.2 = constantPart2ConfigurablePart2
alpha.beta.gamma.3 = constantPart3ConfigurablePart3
I made this work this way, though I know it is really bad!
def updateFile(String pattern, String updatedValue) {
def myFile = new File(".", "inputs/fileInherited.txt")
StringBuffer updatedFileText = new StringBuffer()
def ls = System.getProperty('line.separator')
myFile.eachLine{ line ->
def regex = Pattern.compile(/$pattern/)
def m = (line =~ regex)
if (m.matches()) {
def buf = new StringBuffer(line)
buf.replace(m.start(1), m.end(1), updatedValue)
line = buf.toString()
}
println line
updatedFileText.append(line).append(ls)
}
myFile.write(updatedFileText.toString())
}
The passed in pattern is required to contain a group that is substituted in the StringBuffer. Does anyone know how this should really be done in Groovy?
EDIT -- to define the expected output
The file that contains the example lines needs to be updated such that the "ConfigurablePart" of each line is replaced with the updated text provided. For my ugly solution, I would need to call the method 3 times, once to replace ConfigurablePart1, once for ConfigurablePart2, and finally for ConfigurablePart3. There is likely a better approach to this too!!!
*UPDATED -- Answer that did what I really needed *
In case others ever hit a similar issue, the groovy code improvements I asked about are best reflected in the accepted answer. However, for my problem that did not quite solve my issues. As I needed to substitute only a portion of the matched lines, I needed to use back-references and groups. The only way I could make this work was to define a three-part regEx like:
(.*)(matchThisPart)(.*)
Once that was done, I was able to use:
it.replaceAdd(~/$pattern/, "\$1$replacement\$3")
Thanks to both replies - each helped me out a lot!

It can be made more verbose with the use of closure as args. Here is how this can be done:
//abc.txt
abc.item.1 = someDummyItem1
abc.item.2 = someDummyItem2
abc.item.3 = someDummyItem3
alpha.beta.gamma.1 = constantPart1ConfigurablePart1
alpha.beta.gamma.2 = constantPart2ConfigurablePart2
alpha.beta.gamma.3 = constantPart3ConfigurablePart3
abc.item.4 = someDummyItem4
abc.item.5 = someDummyItem5
abc.item.6 = someDummyItem6
Groovy Code:-
//Replace the pattern in file and write to file sequentially.
def replacePatternInFile(file, Closure replaceText) {
file.write(replaceText(file.text))
}
def file = new File('abc.txt')
def patternToFind = ~/ConfigurablePart/
def patternToReplace = 'NewItem'
//Call the method
replacePatternInFile(file){
it.replaceAll(patternToFind, patternToReplace)
}
println file.getText()
//Prints:
abc.item.1 = someDummyItem1
abc.item.2 = someDummyItem2
abc.item.3 = someDummyItem3
alpha.beta.gamma.1 = constantPart1NewItem1
alpha.beta.gamma.2 = constantPart2NewItem2
alpha.beta.gamma.3 = constantPart3NewItem3
abc.item.4 = someDummyItem4
abc.item.5 = someDummyItem5
abc.item.6 = someDummyItem6
Confirm file abc.txt. I have not used the method updateFile() as done by you, but you can very well parameterize as below:-
def updateFile(file, patternToFind, patternToReplace){
replacePatternInFile(file){
it.replaceAll(patternToFind, patternToReplace)
}
}

For a quick answer I'd just go this route:
patterns = [pattern1 : constantPart1ConfigurablePart1,
pattern2 : constantPart2ConfigurablePart2,
pattern3 : constantPart3ConfigurablePart3]
def myFile = new File(".", "inputs/fileInherited.txt")
StringBuffer updatedFileText = new StringBuffer()
def ls = System.getProperty('line.separator')
myFile.eachLine{ line ->
patterns.each { pattern, replacement ->
line = line.replaceAll(pattern, replacement)
}
println line
updatedFileText.append(line).append(ls)
}
myFile.write(updatedFileText.toString())

Related

Groovy script for scriptrunner

I am currently working on a Scriptrunner script that has the objectives of rejecting push if the user pushes a not freeze dependancies. The script itself is not really hard but I don't know how to use Scriptrunner well enough to make it work, any advices ?
I tried several ways to make my script work, here is the script as it is right now :
def regex = ".*requires\\(\"[^\\d|_|\\-](\\S)+/\\[(\\d)(\\.\\d)*\\]#(eca|exail)/stable\""
def fileName = "conanfile.py"
def file = new File(fileName)
if (file.exists()) {
file.readLines().each { line ->
if (line.startsWith("self.requires")) {
if (!(line =~ regex)) {
log.error("Error: Freeze your dependancies")
return false
}
}
}
// log.info("All 'self.requires' lines match the regex.")
return true
} else {
//log.error("Error: $fileName does not exist.")
return true
}
as you can see it is pretty simple, we check if there is a file named "conanfile.py", and read it to find a line starting with "self.requires" and compare the line with our Regex ( the double \ is because of groovy). I am quite lost on how to make it works on Scriptrunner.

Using cfmail() (ACF2018), how does one go about adding in parameters?

I have a function that sends e-mail which works fine in it's basic form. However I need to add in params for images and attachments and can't find any documentation other than the basic cfmail() functionality.
cfmail(
to = arguments.emailTo,
from = arguments.emailFrom,
subject = arguments.emailSubject,
type = 'html'
) {
writeOutput(arguments.mailbody);
}
I feel they should be up above the mailbody but can't find any docs on how to add them.
The linked documents above are great for new mail(), but - at least what I saw - were not updated for the newer cfmail() function. Here's the solution that also includes a boolean if one image is optional. I also found that in the included e-mail body, CID: does not show the image in Outlook (but is fine in Gmail), but cid: works in both.
private void function sendEmail (
required string mailbody,
required string emailTo,
string emailFrom = 'this is a subject line',
required string emailSubject,
boolean includeBoth = false
) output = false {
cfmail(
to = arguments.emailTo,
from = arguments.emailFrom,
subject = arguments.emailSubject,
type = 'html')
{
cfmailparam(file='C:\path\to\my\image\image.jpg', contentid='header', disposition='inline');
if (arguments.includeBoth) {
cfmailparam(file='C:\path\to\a\different\image\icon.png', contentid='icon', disposition='inline');
}
writeOutput(arguments.mailbody);
}

Compressing series of if-else statement in Groovy

I have a series of if-else statements in Groovy:
String invoke = params?.target
AuxService aux = new AuxService()
def output
if(invoke?.equalsIgnoreCase("major")) {
output = aux.major()
}
else if(invoke?.equalsIgnoreCase("minor")) {
output = aux.minor()
}
else if(invoke?.equalsIgnoreCase("repository")) {
output = aux.repository()
}
...
else if(invoke?.equalsIgnoreCase("temp")) {
output = aux.temp()
}
else {
output = aux.propagate()
}
The ellipsis contains yet another 14 sets of if-else statements, a total of 19. You see, depending on the value of invoke the method that will be called from AuxService. Now I'm thinking the following to reduce the lines:
String invoke = params?.target()
AuxService aux = new AuxService()
def output = aux?."$invoke"() ?: aux.propagate()
But I think the third line might not work, it looks very unconventional. And just a hunch, I think that line is prone to error. Is this a valid code or are there any more optimal approach to compress these lines?
Just test String invoke before using it. Note that aux won't be null, so no need to use safe navigation (?.).
class AuxService {
def major() { 'major invoked' }
def minor() { 'minor invoked' }
def propagate() { 'propagate invoked' }
}
def callService(invoke) {
def aux = new AuxService()
return invoke != null ? aux.invokeMethod(invoke, null) : aux.propagate()
}
assert callService('major') == 'major invoked'
assert callService(null) == 'propagate invoked'
Note this is will fail if the input doesn't contain a valid method in class AuxService.
Firstly, your code is avlid in Groovy. Though, if equalsIgnoreCase is required, your reduced code will not work. The same for if params is null, since then invoke would be null. But I think your basic idea is right. So what I would do is making a map (final static somewhere) with the methods in uppercase as String key and the real method in correct casing as String value. Then you can use that to ensure correctness for different cases. Null handling I would solve separate:
def methodsMap = ["MAJOR":"major",/* more mappings here */]
String invoke = params?.target()
AuxService aux = new AuxService()
def methodName = methodsMap[invoke?.toUpperCase()]
def output = methodName ? aux."$methodName"() : aux.propagate()
An slightly different approach would be to use Closure values in the map. I personally find that a bit overkill, but it allows you to do more than just the plain invocation
def methodsMap = ["MAJOR":{it.major()},/* more mappings here */]
String invoke = params?.target()
AuxService aux = new AuxService()
def stub = methodsMap[invoke?.toUpperCase()]
def output = stub==null ? stub(aux) : aux.propagate()
I thought about using the Map#withDefault, but since that will create a new entry I decided not to. It could potentially cause memory problems. In Java8 you can use Map#getOrDefault:
String invoke = params?.target()
AuxService aux = new AuxService()
def methodName = methodsMap.getOrDefault(invoke?.toUpperCase(), "propagate")
def output = aux."$methodName"()
The Elvis operator is used to shorten the equivalent Java ternary operator expression.
For example,
def nationality = (user.nationality!=null) ? user.nationality : "undefined"
can be shortened using the Elvis operator to
def nationality = user.nationality ?: "undefined"
Note that the Elvis operator evaluates the expression to the left of the "?" symbol. If the result is non-null, it returns the result immediately, else it evaluates the expression on the right of the ":" symbol and returns the result.
What this means is that you cannot use the Elvis operator to perform some extra logic on the right side of the "?" symbol if the condition evaluates to true. So, the ternary expression
user.nationality ? reportCitizen(user) : reportAlien(user)
cannot be (directly)expressed using the Elvis operator.
Coming back to the original question, the Elvis operator cannot be (directly) applied to checking if a method exists on an object and invoking it if present. So,
def output = aux?."$invoke"() ?: aux.propagate()
will not work as expected, because the Elvis operator will try to evaluate "aux?."$invoke"()" first. If "invoke" refers to a method that does not exist, you will get a MissingMethodException.
One way I can think of to work around this is -
class AuxService {
def major() { println 'major invoked' }
def minor() { println 'minor invoked' }
def propagate() { println 'propagate invoked' }
}
def auxService = new AuxService()
def allowedMethods = ["major", "minor", "propagate"]
def method = null
allowedMethods.contains(method?.toLowerCase()) ? auxService."${method?.toLowerCase()}"() : auxService.propagate() // Prints "propagate invoked"
method = "MaJoR"
allowedMethods.contains(method?.toLowerCase()) ? auxService."${method?.toLowerCase()}"() : auxService.propagate() // Prints "major invoked"
method = "undefined"
allowedMethods.contains(method?.toLowerCase()) ? auxService."${method?.toLowerCase()}"() : auxService.propagate() // Prints "propagate invoked"
In a nutshell, store the list of invoke-able methods in a list and check to see if we're trying to invoke a method from this list. If not, invoke the default method.

Struggling with simple Groovy List operations

I've abstracted a very simple situation here, in which I want to pass a list of strings into my cleanLines function and get a list of strings back out. Unfortunately, I'm new to Groovy and I've spent about a day trying to get this to work with no avail. Here's a stand-alone test that exhibits the problem I'm having:
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
class ConfigFileTest {
private def tab = '\t'
private def returnCarriage = '\r'
private def equals = '='
List <String> cleanLines(List <String> lines) {
lines = lines.collect(){it.findAll(){c -> c != tab && c != returnCarriage}}
lines = lines.findAll(){it.contains(equals)}
lines = lines.collect{it.trim()}
}
#Test
public void test() {
List <String> dirtyLines = [" Colour=Red",
"Shape=Square "]
List <String> cleanedLines = ["Colour=Red",
"Shape=Square"]
assert cleanLines(dirtyLines) == cleanedLine
}
}
I believe that I've followed the correct usage for collect(), findAll() and trim(). But when I run the test, it crashes on the trim() line stating
groovy.lang.MissingMethodException: No signature of method:
java.util.ArrayList.trim() is applicable for argument types: ()
values: []
. Something's suspicious.
I've been staring at this for too long and noticed that my IDE thinks the type of my first lines within the cleanLines function is List<String>, but that by the second line it has type Collection and by the third it expects type List<Object<E>>. I think that String is an Object and so this might be okay, but it certainly hints at a misunderstanding on my part. What am I doing wrong? How can I get my test to pass here?
Here's a corrected script:
import groovy.transform.Field
#Field
def tab = '\t'
#Field
def returnCarriage = '\r'
#Field
def equals = '='
List <String> cleanLines(List <String> lines) {
lines = lines.findAll { it.contains(equals) }
lines = lines.collect { it.replaceAll('\\s+', '') }
lines = lines.collect { it.trim() }
}
def dirtyLines = [" Colour=Red",
"Shape=Square "]
def cleanedLines = ["Colour=Red", "Shape=Square"]
assert cleanLines(dirtyLines) == cleanedLines
In general findAll and collect are maybe not mutually exclusive but have different purposes. Use findAll to find elements that matches certain criteria, whereas collect when you need to process/transform the whole list.
this line
lines = lines.collect(){it.findAll(){c -> c != tab && c != returnCarriage}}`
replaces the original list of Strings with the list of lists. Hence NSME for ArrayList.trim(). You might want to replace findAll{} with find{}
You can clean the lines like this:
def dirtyLines = [" Colour=Red", "Shape=Square "]
def cleanedLines = ["Colour=Red", "Shape=Square"]
assert dirtyLines.collect { it.trim() } == cleanedLines
If you separate the first line of cleanLines() into two separate lines and print lines after each, you will see the problem.
it.findAll { c -> c != tab && c != returnCarriage }
will return a list of strings that match the criteria. The collect method is called on every string in the list of lines. So you end up with a list of lists of strings. I think what you are looking for is something like this:
def cleanLines(lines) {
return lines.findAll { it.contains(equals) }
.collect { it.replaceAll(/\s+/, '') }
}

Grails spock Testing with File class

I have a method that create a file based in a content copied from an another file.
Like below
private cloneBaseFile(fileName, ddi, ddd){
def config = grailsApplication.config.kicksim.fileConstants
String baseFileContents = new File(config.baseFile).getText('UTF-8')
def help = handleString("${ddd}.${ddi}")
baseFileContents = baseFileContents.replaceAll("DDDDDI", help);
def f1= new File(fileName)
f1 << baseFileContents
return fileName
}
I'd like to know how to unit test it.
I think you should look to separate out the responsibilities of this method (c.f. Single Responsibility Principle). If you haven't got a copy of Robert Martin's book called Clean Code then I'd highly recommend it: his code is a work of art and taught me a lot.
cloneBaseFile() is actually doing many things: it's opening a file, getting the contents of the file, doing something (I don't know what) in handleString(), replacing the contents of the file with a modified version of its contents and then finally saving the file.
How about (as a starter for 10):
private cloneBaseFile(fileName, ddi, ddd){
def config = grailsApplication.config.kicksim.fileConstants
String baseFileContents = getFileContents(config.baseFile)
baseFileContents = handleFileContents(baseFileContents)
return createNewFileWithContents(fileName, baseFileContents)
}
String getFileContents(String fileName) {
String contents = new File(fileName).getText('UTF-8')
return contents
}
String handleFileContents(String oldContents) {
def help = handleString("${ddd}.${ddi}")
return oldContents.replaceAll("DDDDDI", help);
}
String createNewFileWithContents(String newFileName, String newContents) {
def f1= new File(newFileName)
f1 << newContents
return newFileName
}
Now you have multiple small methods, each of which can be tested far more easily. You'll also want to test the handleString() method.