Replace Grails Console With the Editor of Your Choice

| Comments

The grails console has a number of disadvantages that make it a little clunky to use:

  • You can’t attach it to a running grails instance, you need to run grails console from scratch
  • You can’t pass it parameters, such as the name of a script you’ve saved
  • It’s a swing app, which is always a little wonky in it’s keybindings and it’s behavior
  • It’s not <insert your chosen editor>

I got tired of these limitations so I decided to do something about it. I put together a groovy script that uses HTTPBuilder to POST groovy code to a running Grails app that has the grails console plugin installed.

If you’re running this in any non-development environment, you’ll want to ensure that it’s behind some form of authentication. The most popular grails security plugin is Burt Beckwith’s spring security core, so this script comes with built-in support for that.

This script makes it easy to call from any editor that allows you to execute scripts (pretty much every programmer’s editor on the market). At the bottom of the post, I show how to integrate it with Vim (my editor of choice), but it’d be just as easy to script it from TextMate, JEdit, IntelliJ, Eclipse, etc. Any editor that can take the file you’re currently editing and pass it to a shell script.

Here’s my Vim session with a grails script at the bottom and the output from that grails script in another window at the top.

Using Vim as a Grails Console

This is the script that makes the magic happen (linked from github so any future tweaks will be reflected below), just grab a copy and save it somewhere in your path.


If you execute the script without any parameters, it’ll show you the usage syntax:

% postCode.groovy 
usage: postCode.groovy [-u username] [-p password] -b baseUrl file
 -b,--baseurl      The required base url to auth/post to, ex:
                            http://localhost:8080/myapp/
 -p,--password    The password to authenticate with
 -u,--user            The username to authenticate with, null
                            username/password skips authentication

It uses the nifty CliBuilder that’s comes with recent versions of Groovy to parse command line arguments. If your project doesn’t have any authentication, you can simply call it with the -b argument to let it know where your app is running and pass it a script to execute on that running instance:

postCode.groovy -b http://localhost:8080/myapp/ scriptToRun.groovy 
... results of scriptToRun.groovy ...

If your project IS using the spring security plugin for authentication, you can add a username and a password to use for authentication:

postCode.groovy -u admin -p sekrit -b http://localhost:8080/myapp/ scriptToRun.groovy
... results of scriptToRun.groovy ...

The first time that you run it, it might take a little while to run because the Grape Grab annotation is downloading the HTTPBuilder jars and it’s dependencies.

UPDATE: 8/7/2011 The below is no longer necessary with the new @GrabExclude to ignore the groovy dependency. One sort of screwy thing that I’ve found with the default Grape settings is that it really isn’t that smart about what dependencies it should download again when they’re already satisfied. This can cause a ~30 second pause every time the script is run, which is unacceptable. I posted a message on the Groovy mailing list and the tl;dr fix for it is to increase the ivy.cache.ttl.default timeout in your ~/.groovy/grapeConfig.xml file (use this as a template if you don’t have the file).

 
   
... 

Example Use with Spring Security Core Plugin

Here’s a quick demo project where the console plugin is protected by the Spring Security Core plugin.

grails create-app demo-post-code
cd demo-post-code
grails install-plugin console

Install the spring-security-core plugin and create the default User and Role domain classes.

grails install-plugin spring-security-core
grails s2-quickstart com.example User Role

Edit grails-app/conf/Config.groovy and add a rule next to the other spring security configuration stating only ROLE_ADMIN users can get to the console plugin:

grails.plugins.springsecurity.controllerAnnotations.staticRules = [
   '/console/**': ['ROLE_ADMIN']
]

Now we’ll need an admin user in grails-app/conf/BootStrap.groovy that has permission to execute code in the console. Edit the bootstrap file to look like this:

import com.example.*

class BootStrap {

    def springSecurityService
    
    def init = { servletContext ->
        def adminRole = Role.findByAuthority('ROLE_ADMIN') ?: new Role(authority: 'ROLE_ADMIN').save(failOnError: true)
        def adminUser = User.findByUsername('admin') ?: new User(
            username: 'admin', password: springSecurityService.encodePassword('sekrit'), enabled: true
        ).save(failOnError: true)
 
        if (!adminUser.authorities.contains(adminRole)) UserRole.create(adminUser, adminRole)
    }
    def destroy = {
    }
}

As you can see, we’ve created an admin user with the password sekrit.

Now, create a sample script to run in the grails context, something like this:

import com.example.* 

println "User count = ${User.count()}"

Now run the app with grails run-app and in another terminal window execute the postCode.groovy script and give it the credentials and the base url to post it to. If you’ve got everything set up, you should see that there’s one User in the database (the admin user that we bootstrapped in :).

% postCode.groovy -u admin -p sekrit -b http://localhost:8080/demo-post-code test.groovy

User count = 1

Integrating With Vim

My vimscript skills are pretty much non-existent, and I’m sure there’s a better way to do this, but here’s the code that I’ve got in my .vimrc to bind F7 to executing the current buffer as plain groovy, and binds F8 to executing it as a grails script, with full binding.

Shift-F7/F8 will close the execution window. If you highlight a part of your code in visual mode, it will just post the highlighted code rather than the whole file.

You can also override the default user/password/base url in a grails session by overriding any of these values, either directly in the vimscript or by setting the appropriate environment variable:

let g:grails_user = $DEFAULT_GRAILS_USER
let g:grails_password = $DEFAULT_GRAILS_PASSWORD
let g:grails_base_url = $DEFAULT_GRAILS_BASE_URL

So to test the above script, I’d use this in my .zshrc/.bashrc/.profile:



and this script to temporarily change the values using the current directory for the BASE_URL and optionally passing in a user and password

# example usage from root of grails directory: grailsBaseUrlReset myUsername myOtherPassword
function grailsBaseUrlReset() {
    export DEFAULT_GRAILS_BASE_URL="http://localhost:8080/${PWD##*/}"
    echo "DEFAULT_GRAILS_BASE_URL set to $DEFAULT_GRAILS_BASE_URL"
    export DEFAULT_GRAILS_USER=$1
    echo "DEFAULT_GRAILS_USER set to $DEFAULT_GRAILS_USER"
    export DEFAULT_GRAILS_PASSWORD=$2
    echo "DEFAULT_GRAILS_PASSWORD set to $DEFAULT_GRAILS_PASSWORD"
}

You can change them temporarily in vim by changing a value for your current grails session, just hit colon and change the value:

:let g:grails_password = "reallyReallySekrit"

(or you could add them to a different file that you source independently)

Here’s the full script, just add it to your ~/.vimrc and make sure the postCode.groovy script is in your path and you should be set to go:

function! s:copy_groovy_buffer_to_temp(first, last)
  " groovy/java scripts can't start with a # and tempname's normally do
  let src = substitute(tempname(), "[^\/]*$", "vim_&.groovy", "") 
  
  " put current buffer's content in a temp file
  silent exe ": " . a:first . "," . a:last . "w " . src

  return src
endfunction

function! s:select_new_temp_buffer()
  let temp_file = tempname()

  " open the preview window to the temp file
  silent exe ":pedit! " . temp_file

  " select the temp buffer as active 
  wincmd P

  " set options for temp buffer
  setlocal buftype=nofile
  setlocal noswapfile
  setlocal syntax=none
  setlocal bufhidden=delete

  return temp_file
endfunction

function! Groovy_eval_vsplit() range
  let temp_source = s:copy_groovy_buffer_to_temp(a:firstline, a:lastline)
  let temp_file = s:select_new_temp_buffer()
  
  " replace current buffer with groovy's output
  silent execute ":%! groovy " . temp_source . " 2>&1 "

  wincmd p " change back to the source buffer
endfunction

au BufNewFile,BufRead *.groovy vmap   :call Groovy_eval_vsplit()
au BufNewFile,BufRead *.groovy nmap   mzggVG`z
au BufNewFile,BufRead *.groovy imap   a
au BufNewFile,BufRead *.groovy map   :wincmd P:q
au BufNewFile,BufRead *.groovy imap   a


" set these up as environment variables on your system, or override
" per session by using ':let g:grails_user = foo'
let g:grails_user = $DEFAULT_GRAILS_USER
let g:grails_password = $DEFAULT_GRAILS_PASSWORD
let g:grails_base_url = $DEFAULT_GRAILS_BASE_URL

function! Grails_eval_vsplit() range
  let temp_source = s:copy_groovy_buffer_to_temp(a:firstline, a:lastline)
  let temp_file = s:select_new_temp_buffer()
  
  if strlen(g:grails_user) > 0
      " replace current buffer with grails' output
      silent execute ":%! postCode.groovy -u " . g:grails_user . " -p " . g:grails_password . " -b " . g:grails_base_url . " " . temp_source . " 2>&1 "
  else 
      silent execute ":%! postCode.groovy -b " . g:grails_base_url . " " . temp_source . " 2>&1 "
  endif

  wincmd p " change back to the source buffer
endfunction

au BufNewFile,BufRead *.groovy vmap   :call Grails_eval_vsplit()
au BufNewFile,BufRead *.groovy nmap   mzggVG`z
au BufNewFile,BufRead *.groovy imap   a
au BufNewFile,BufRead *.groovy map   :wincmd P:q
au BufNewFile,BufRead *.groovy imap   a

If you’re not a Vim user, it should be easy to integrate with any other editor that allows you to pass the current file to a shell command and display the results. I went a little extra with the vimscript to allow it to post the current buffer (or selected lines within that buffer) without having to save to disk first.

Comments