How to make snakeyaml and GStrings work together - groovy

When I'm trying to use snakeyaml to dump out Yaml out of Groovy interpolated strings, it ends up printing a class name instead.
For example:
#Grab(group='org.yaml', module='snakeyaml', version='1.16')
import org.yaml.snakeyaml.Yaml
Yaml yaml = new Yaml();
def a = "a"
def list = ["$a"]
def s = yaml.dump(list)
Prints:
- !!org.codehaus.groovy.runtime.GStringImpl
metaClass: !!groovy.lang.MetaClassImpl {}
I'm guessing it has something to do with the fact that GStrings get transformed to Strings when they used and I suspect snakeyaml uses some sort of introspection to determine the class of the object.
Is there a better solution than calling toString() on all GStrings?

Try to create a new Representer :
public class GroovyRepresenter extends Representer {
public GroovyRepresenter() {
this.representers.put(GString.class, new StringRepresenter());
}
}
Yaml yaml = new Yaml(new GroovyRepresenter())
...

You could add type info to your variables
Yaml yaml = new Yaml();
def a = "a"
String aStr = "$a"
def list = [aStr]
def s = yaml.dump(list)

Related

What is the meaning of this script field in Groovy?

What parameters.script means in the following Groovy code:
example.groovy
class Example {
def call(Map parameters) {
def script = parameters.script
...
}
}
It retrieves a value from a Map with the key 'script'. The following are all equivalent
def script = parameters.script
script = parameters['script']
script = parameters.get('script')
from https://www.timroes.de/2015/06/28/groovy-tutorial-for-java-developers-part3-collections/:
Beside using brackets you can also use dot notation to access entries

Camunda: Use in an external Groovy script a provided Class

I want to use for scripting external Groovy Scripts.
To not copy a lot of code, I want to share classes.
I have:
- external_test.groovy
- Input.groovy
Running the external_test.groovy in Intellij works.
Input is a simple class:
package helpers
class Input {
String serviceConfig
String httpMethod
String path
LinkedHashMap headers = [:]
String payload
Boolean hasResponseJson
}
When the script is executed by Camunda, it cannot find the class:
import helpers.Input
...
And throws an Exception:
unable to resolve class helpers.Input # line 16, column 9. new helpers.Input(serviceConfig: "camundaService", ^ 1 error
It is listed in the Deployment:
Do I miss something or is this not supported?
I found a post in the Camunda forum, that helped me to solve this:
https://forum.camunda.org/t/groovy-files-cant-invoke-methods-in-other-groovy-files-which-are-part-of-same-deployment/7750/5
Here is the solution (that is not really satisfying - as it needs a lot of boilerplate code):
static def getScript(fileName, execution) {
def processDefinitionId = execution.getProcessDefinitionId()
def deploymentId = execution.getProcessEngineServices().getRepositoryService().getProcessDefinition(processDefinitionId).getDeploymentId()
def resource = execution.getProcessEngineServices().getRepositoryService().getResourceAsStream(deploymentId, fileName)
def scannerResource = new Scanner(resource, 'UTF-8')
def resourceAsString = scannerResource.useDelimiter('\\Z').next()
scannerResource.close()
GroovyShell shell = new GroovyShell()
return shell.parse(resourceAsString)
}
def helper = getScript("helpers/helper_classes.groovy", execution)
helper.myFunction("hello")

Binding Groovy TemplateEngine with any variable names

I am trying to write a method that:
Loads a template file (*.tpl) from the local file system
Parameterizes that template file with a list of supplied variables
Best attempt thus far:
String loadParameterizedTemplateByName(String templateName,
String... variables) {
InputStream inputStream =
this.class.classLoader.getResourceAsStream(templateName)
StringWriter writer = new StringWriter()
IOUtils.copy(inputStream, writer, 'UTF-8')
String templateBody = writer.toString()
def binding = variablesAsBinding(variables) // ?!?!
engine.createTemplate(templateBody).make(binding).toString()
}
So for instance say I have the following HelloEmail.tpl template file:
HelloEmail.tpl
==============
Hello ${firstName},
You are awesome! ${someGuy} even says so!
Sincerely,
${me}
Then my desired invocation of this would be:
String firstName = 'John'
String someGuy = 'Mark'
String me = '#smeeb'
String parameterizedTemplate =
loadParameterizedTemplateByName('HelloEmail.tpl', firstName, someGuy, me)
So that the final result is that parameterizedTemplate string has a value of:
println parameterizedTemplate
// Prints:
Hello John,
You are awesome! Mark even says so!
Sincerely,
#smeeb
The trick here is that the method needs to be able to use any list of supplied variables against any supplied template file!
Is this possible to accomplish via reflection? Meaning the TemplateEngine just looks as the supplied list of String variables, and substitutes them for variables of the same name (as found in the template)?
You can pass a Map like so:
import groovy.text.markup.MarkupTemplateEngine
import groovy.text.markup.TemplateConfiguration
String loadParameterizedTemplateByName(Map variables, String templateName) {
def engine = new groovy.text.SimpleTemplateEngine()
this.class.getResource(templateName).withReader { reader ->
engine.createTemplate(reader).make(variables)
}
}
def result = loadParameterizedTemplateByName('/mail.tpl', firstName:'Tim', someGuy:'StackOverflow', me:'smeeb')
assert result == 'Hello Tim,\n\nYou are awesome! StackOverflow even says so!\n\nSincerely,\nsmeeb'

How to convert a #ToString generated string back to object with Groovy

For example, instance of the following following class produces string A(x:7, values:[hello, world])
#ToString( includeNames=true )
class A {
def x
def values = []
}
How can I transform this String back to an instance of the class?
Based on the comments by #cfrick, this is not possible.
(Answering my own question for the sake of closing this topic.)

Passing variable to be evaluated in groovy gstring

I am wondering if I can pass variable to be evaluated as String inside gstring evaluation.
simplest example will be some thing like
def var ='person.lName'
def value = "${var}"
println(value)
I am looking to get output the value of lastName in the person instance. As a last resort I can use reflection, but wondering there should be some thing simpler in groovy, that I am not aware of.
Can you try:
def var = Eval.me( 'new Date()' )
In place of the first line in your example.
The Eval class is documented here
edit
I am guessing (from your updated question) that you have a person variable, and then people are passing in a String like person.lName , and you want to return the lName property of that class?
Can you try something like this using GroovyShell?
// Assuming we have a Person class
class Person {
String fName
String lName
}
// And a variable 'person' stored in the binding of the script
person = new Person( fName:'tim', lName:'yates' )
// And given a command string to execute
def commandString = 'person.lName'
GroovyShell shell = new GroovyShell( binding )
def result = shell.evaluate( commandString )
Or this, using direct string parsing and property access
// Assuming we have a Person class
class Person {
String fName
String lName
}
// And a variable 'person' stored in the binding of the script
person = new Person( fName:'tim', lName:'yates' )
// And given a command string to execute
def commandString = 'person.lName'
// Split the command string into a list based on '.', and inject starting with null
def result = commandString.split( /\./ ).inject( null ) { curr, prop ->
// if curr is null, then return the property from the binding
// Otherwise try to get the given property from the curr object
curr?."$prop" ?: binding[ prop ]
}

Resources