Using Gant to Execute a Groovy Script Within the Grails Context

| Comments

(3/30/08 I’ve posted an update to this script that fixes the problems noted in the comments)

I’ve been working with Gant recently and wanted the ability to be able to execute a script within the grails context, but not add it as part of a controller or needing to paste it every time in a grails console/shell.

After a little playing around, I came up with this:

import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU

grailsHome = Ant.project.properties."environment.GRAILS_HOME"

includeTargets << new File ( "${grailsHome}/scripts/Package.groovy" )
includeTargets << new File ( "${grailsHome}/scripts/Bootstrap.groovy" )

target('default': "Execute the specified script after starting up the application environment") {
    depends(checkVersion, configureProxy, packageApp, classpath)
    runScript()
}

target(runScript: "Main implementation that executes the specified script after starting up the application environment") {
    parseArguments()
    if (argsMap["params"].size() == 0) {
        event("StatusError", ["Required script name parameter is missing"])
        System.exit 1
    }
    compile()
    classLoader = new URLClassLoader([classesDir.toURL()] as URL[], rootLoader)
    Thread.currentThread().setContextClassLoader(classLoader)
    loadApp()
    configureApp()
    argsMap["params"].each { scriptFile ->
        executeScript(scriptFile, classLoader)
    }
}

def executeScript(scriptFile, classLoader) {
    File script = new File(scriptFile)
    if (script.exists()) {
        def shell = new GroovyShell(classLoader, new Binding(ctx: appCtx, grailsApplication: grailsApp))
        shell.evaluate(script.text)
    } else {
        event("StatusError", ["Designated script doesn't exist: $scriptFile"])
    }
}

// this argument parsing target has actually been submitted as a patch to Init.groovy after some feedback
// on the grails user mailing list and will hopefully be in the next release of grails.
// Vote it up if you like it: http://jira.codehaus.org/browse/GRAILS-2663

argsMap = [params: []]

target(parseArguments: "Parse the arguments passed on the command line") {
    args?.tokenize().each {  token ->
        def nameValueSwitch = token =~ "--?(.*)=(.*)"
        if (nameValueSwitch.matches()) { // this token is a name/value pair (ex: --foo=bar or -z=qux)
            argsMap[nameValueSwitch[0][1]] = nameValueSwitch[0][2]
        } else {
            def nameOnlySwitch = token =~ "--?(.*)"
            if (nameOnlySwitch.matches()) {  // this token is just a switch (ex: -force or --help)
                argsMap[nameOnlySwitch[0][1]] = true
            } else { // single item tokens, append in order to an array of params
                argsMap["params"] << token
            }
        }
    }
    event("StatusUpdate", ["Done parsing arguments: $argsMap"])
}

If you save that in your application’s “scripts” directory as “RunScript.groovy” you can execute it like this:

grails run-script [path-to-script-1] [path-to-script-2]...[path-to-script-n]

The paths are relative to the root of the grails application directory. To see it’s potential use, if you had a directory in your app called “userScripts” that contained a script “createBook.groovy”:

def a = new Author(name: "Arthur C. Clarke")
a.save()

def b = new Book(title: "The Nine Billion Names of God", author: a)
b.save()

if (!b.hasErrors()) {
    println "${a.name}'s ${b.title} saved successfully!"
}

println "Current book list: ${b.list()}"

and if you had an Author domain class:

class Author {
    String name

    public String toString() {
        return "Author[name: $name]"
    }
}

and a Book domain class:

class Book {
    String title
    Author author

    public String toString() {
        return "Book[author: ${author}, title: $title]"
    }
}

You could then run the script:

grails run-script userScripts/createBook.groovy

And you’d see the output:

Arthur C. Clarke's The Nine Billion Names of God saved successfully!
Current book list: [Book[author: Author[name: Arthur C. Clarke], title: The Nine Billion Names of God]]

Since you’re running in the grails context, you also have access to all of your service and controller classes. Anything that you can do inside grails can also be scripted using this target.

Comments