Grails run-script with parameters

Grails, programming

Scripts vs. parameters

The “grails run-script” task allows running a bunch of scripts, and therein lies the problem: the parameters passed to the command are interpreted by the command to be names of scripts to run:

grails run-script ~/script1.groovy ~/script2.groovy ~/script3.groovy

This works fine if the scripts are self-contained and did not expect any parameters on their own.  What if ~/script1.groovy expects some parameters?

The problem is that the RunScript.groovy script assumes each parameter to be script files to run, so there is no support for parameters to any of the script files run.

So I would like to trade:

  • the ability to run multiple scripts with one call to run-script

with

  • multiple calls to something like run-script-with-params

I figured that, if I were to be calling scripts in a batch, making multiple calls shouldn’t be a big deal. Not being able to pass parameters to individual scripts, on the other hand, IS a big deal.

A tweak of the script RunScript.groovy (below) can help with that:

UPDATE: I just found that a similar script (even the name is remarkably similar) has already been suggested here.  (Scroll down the comment section to see the suggestion.)  Oh well.  Wish it was added to Grails so I didn’t have to spend so much time on this.

import org.springframework.orm.hibernate3.SessionFactoryUtils
import org.springframework.orm.hibernate3.SessionHolder
import org.springframework.transaction.support.TransactionSynchronizationManager

/**
* GANT script to run an arbitrary Groovy script with GORM and Spring context and also with parameters
*
* A sample invocation of a script:
* grails run-script-with-params ~/script1.groovy -enableLog -name=van
*
* Arguments are processed as follows:
* --xx=yy or -xx=yy are parsed into key/value pairs added into argsMap and passed to the script
* --xx or -xx are interpreted as xx:true and treated as a key/value pair and added into argsMap
* xxxxx are treated as a parameter and stored into the array argsMap.params
*
* The FIRST parameter is assumed to be the path to the script file to run.
* The rest of the parameters will be parsed into a map passed onto the script as argsMap
*
* This script is based on and inspired by
* {@link RunScript}
*/
includeTargets << grailsScript("_GrailsBootstrap")

target(main: "GANT script to run an arbitrary Groovy script with GORM and Spring context") {
depends checkVersion, configureProxy, bootstrap, runScript

}

target(runScript: 'Runs a Groovy script file with the parameters propagated to it') {
configureHibernateSession()

if (!argsMap.params) {
event('StatusError', ['ERROR: Required script name parameter is missing'])
System.exit 1
}
executeScriptWithArgsMap argsMap.params[0], classLoader, argsMap
}

def executeScriptWithArgsMap(scriptFile, classLoader, argsMap) {
File script = new File(scriptFile)
if (!script.exists()) {
event('StatusError', ["ERROR: Script $scriptFile does not exist"])
System.exit 1
}
event('StatusUpdate', ["Running script $scriptFile ..."])
def persistenceInterceptor = appCtx.containsBean('persistenceInterceptor') ? appCtx.persistenceInterceptor : null
persistenceInterceptor?.init()
try {
def binding = new Binding(ctx:appCtx, grailsApplication:grailsApp, argsMap: argsMap)
def shell = new GroovyShell(classLoader, binding)
shell.evaluate script.text
event('StatusUpdate', ["Script $scriptFile complete!"])
} finally {
persistenceInterceptor?.flush()
persistenceInterceptor?.destroy()
}
}

def configureHibernateSession() {
// bind a Hibernate Session to avoid lazy initialization exceptions
TransactionSynchronizationManager.bindResource(appCtx.sessionFactory,
new SessionHolder(SessionFactoryUtils.getSession(appCtx.sessionFactory, true)))
}

setDefaultTarget(main)

The trick is how we start a Groovy shell, passing it a Binding object:

def binding = new Binding(ctx:appCtx, grailsApplication:grailsApp, argsMap: argsMap)
def shell = new GroovyShell(classLoader, binding)

We added argsMap to the Binding object passed to the shell. And this allows the target script to now have access to argsMap, in addition to the already available ctx (the Spring application context) and grailsApplication (the GrailsApplication object).

Put the script above into the Grails project’s script subdir as /scripts/RunScriptWithParams.groovy and we can now run this from the Grails subdir:

grails run-script-with-params ~/script1.groovy --enableThis --param1=val1

The target script (in this case ~/script1.groovy) will be able to get the parameters via the argsMap global variable injected into the shell:

println "enableThis = ${argsMap.enableThis}, param1 = ${argsMap.param1}"

Windows caveat

The only caveat I found is that, when running in the Windows command prompt, parameters with a format --key=value need to be in quotes:

grails run-script-with-params ~/script1.groovy --enableThis "--param1=val1"

Running under Cygwin’s terminal works fine without the quotes, however.  So it’s something special about the Windows command.exe or cmd.exe.

Leave a Reply