Search code examples
groovygroovyshell

Groovysh use custom command inside loop


I have a groovysh issue, where I've noticed that you can't use goovysh commands inside a loop context or inside functions. It seems that the commands get evaluated at parse time instead of runtime.

Is there some magic syntax to work around this?

Here is an example of this:

import org.codehaus.groovy.tools.shell.CommandSupport
import org.codehaus.groovy.tools.shell.Groovysh

class Rand extends CommandSupport {
    private Random random = new Random()

    protected Rand(final Groovysh shell) {
        super(shell, 'rand', 'r')
    }

    public Integer execute(List args) {
        random.nextInt()
    }

}

:register Rand

(1..3).each {
    println "number ${it}"
    rand
    foo = _
    println "Random number is ${foo}"
}

When executed you see that the random number doesn't change and you can see that it evaluated when I pasted the code into the console, but before it went through the loop:

Groovy Shell (2.4.11, JVM: 1.8.0_51)
Type ':help' or ':h' for help.
-----------------------------------------------------------------------------------------------------------------------
groovy:000> import org.codehaus.groovy.tools.shell.CommandSupport
===> org.codehaus.groovy.tools.shell.CommandSupport
groovy:000> import org.codehaus.groovy.tools.shell.Groovysh
===> org.codehaus.groovy.tools.shell.CommandSupport, org.codehaus.groovy.tools.shell.Groovysh
groovy:000>
groovy:000> class Rand extends CommandSupport {
groovy:001>     private Random random = new Random()
groovy:002>
groovy:002>     protected Rand(final Groovysh shell) {
groovy:003>         super(shell, 'rand', 'r')
groovy:004>     }
groovy:005>
groovy:005>     public Integer execute(List args) {
groovy:006>         random.nextInt()
groovy:007>     }
groovy:008>
groovy:008> }
===> true
groovy:000>
groovy:000> :register Rand
===> true
groovy:000>
groovy:000> (1..3).each {
groovy:001>     println "number ${it}"
groovy:002>     rand
===> -1321819102
groovy:002>     foo = _
groovy:003>     println "Random number is ${foo}"
groovy:004> }
number 1
Random number is -1321819102
number 2
Random number is -1321819102
number 3
Random number is -1321819102
===> [1, 2, 3]
groovy:000>

I'm hoping that there is some way to refer to the custom command via some other syntax that directly references the shell or something.


Solution

  • Ok, I just came up with a bit of a hacky solution. Getting a hold of the Groovysh instance means I can evaluate when I feel like it:

    import org.codehaus.groovy.tools.shell.CommandSupport
    import org.codehaus.groovy.tools.shell.Groovysh
    
    class Rand extends CommandSupport {
        private Random random = new Random()
    
        protected Rand(final Groovysh shell) {
            super(shell, 'rand', 'r')
        }
    
        public Integer execute(List args) {
            random.nextInt()
        }
    
    }
    
    :register Rand
    
    class Shell extends CommandSupport {
    
        private Groovysh shellint
    
        protected Shell(final Groovysh shell) {
            super(shell, 'shell', 's')
            shellint = shell
        }
    
        public Groovysh execute(List args) {
            shellint
        }
    
    }
    
    :register Shell
    
    shell
    myshell = _
    
    (1..3).each {
        println "number ${it}"
        foo = myshell.execute("rand")
        println "Random number is ${foo}"
    }
    

    With the output of:

    Groovy Shell (2.4.11, JVM: 1.8.0_51)
    Type ':help' or ':h' for help.
    -----------------------------------------------------------------------------------------------------------------------
    groovy:000> import org.codehaus.groovy.tools.shell.CommandSupport
    ===> org.codehaus.groovy.tools.shell.CommandSupport
    groovy:000> import org.codehaus.groovy.tools.shell.Groovysh
    ===> org.codehaus.groovy.tools.shell.CommandSupport, org.codehaus.groovy.tools.shell.Groovysh
    groovy:000>
    groovy:000> class Rand extends CommandSupport {
    groovy:001>     private Random random = new Random()
    groovy:002>
    groovy:002>     protected Rand(final Groovysh shell) {
    groovy:003>         super(shell, 'rand', 'r')
    groovy:004>     }
    groovy:005>
    groovy:005>     public Integer execute(List args) {
    groovy:006>         random.nextInt()
    groovy:007>     }
    groovy:008>
    groovy:008> }
    ===> true
    groovy:000>
    groovy:000> :register Rand
    ===> true
    groovy:000>
    groovy:000> class Shell extends CommandSupport {
    groovy:001>
    groovy:001>     private Groovysh shellint
    groovy:002>
    groovy:002>     protected Shell(final Groovysh shell) {
    groovy:003>         super(shell, 'shell', 's')
    groovy:004>         shellint = shell
    groovy:005>     }
    groovy:006>
    groovy:006>     public Groovysh execute(List args) {
    groovy:007>         shellint
    groovy:008>     }
    groovy:009>
    groovy:009> }
    ===> true
    groovy:000>
    groovy:000> :register Shell
    ===> true
    groovy:000>
    groovy:000> shell
    ===> org.codehaus.groovy.tools.shell.Groovysh@16ec132
    groovy:000> myshell = _
    ===> org.codehaus.groovy.tools.shell.Groovysh@16ec132
    groovy:000>
    groovy:000> (1..3).each {
    groovy:001>     println "number ${it}"
    groovy:002>     foo = myshell.execute("rand")
    groovy:003>     println "Random number is ${foo}"
    groovy:004> }
    number 1
    ===> -666149132
    Random number is -666149132
    number 2
    ===> -1675600826
    Random number is -1675600826
    number 3
    ===> 412144734
    Random number is 412144734
    ===> [1, 2, 3]
    

    Is there another way to do this? In the context that I need this, the groovysh is a customised one with :register removed

    I modifed the groovysh jar file to add the :register command back in, and then I could use the above solution. I did this by looking at https://github.com/groovy/groovy-core/blob/master/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Groovysh.groovy and seeing that org/codehaus/groovy/tools/shell/commands.xml contains the list of commands, and I added <command>org.codehaus.groovy.tools.shell.commands.RegisterCommand</command> to the list.