All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.athaydes.osgiaas.cli.groovy.command.GroovyCommand.groovy Maven / Gradle / Ivy

The newest version!
package com.athaydes.osgiaas.cli.groovy.command

import com.athaydes.osgiaas.cli.CommandHelper
import com.athaydes.osgiaas.cli.StreamingCommand
import com.athaydes.osgiaas.cli.args.ArgsSpec
import org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack
import org.osgi.framework.BundleContext

import java.util.concurrent.atomic.AtomicReference
import java.util.function.Consumer
import java.util.regex.Pattern

//@CompileStatic
class GroovyCommand implements StreamingCommand {

    static final String RESET_CODE_ARG = "-r"
    static final String RESET_CODE_LONG_ARG = "--reset"
    static final String SHOW_ARG = "-s"
    static final String SHOW_LONG_ARG = "--show"

    static int charOf( String c ) { ( c as char ) as int }

    private final AtomicReference contextRef = new AtomicReference<>()
    private final CommandHelper.CommandBreakupOptions breakupOptions =
            CommandHelper.CommandBreakupOptions.create()
                    .quoteCodes( charOf( '"' ), charOf( "'" ) )
                    .includeQuotes( true )

    private final Pattern closureWithArgPattern = Pattern.compile( ".*\\s+->\\s+.*" )

    final GroovyShell shell = new GroovyShell()

    final List codeBuffer = [ ]
    final ArgsSpec argsSpec = ArgsSpec.builder()
            .accepts( RESET_CODE_ARG, RESET_CODE_LONG_ARG )
            .withDescription( 'reset the current code statements buffer' ).end()
            .accepts( SHOW_ARG, SHOW_LONG_ARG )
            .withDescription( 'show the current statements buffer' ).end()
            .build()

    GroovyCommand() {
        // add pre-built variables to the context so auto-completion works from the start
        shell.context.with {
            setVariable( 'out', System.out )
            setVariable( 'err', System.err )
            setVariable( 'ctx', contextRef.get() )
            setVariable( 'binding', variables )
        }
        Thread.start {
            shell.evaluate( '"warm up"' )
        }
    }

    @Override
    String getName() { 'groovy' }

    @Override
    String getUsage() {
        "groovy ${argsSpec.usage} "
    }

    @Override
    String getShortDescription() {
        """
           Executes a Groovy script.

           If the script returns a non-null value, the value is printed.

           The following options are supported:

        """.stripIndent() + argsSpec.getDocumentation( '  ' ) + """

           The code statements buffer contains all entered import statements (so imports do not need to be
           re-typed on every command).

           Simple Example:

           >> groovy 2 + 2
           < 4

           Multi-line example to define a separate class:

           >> :{
           groovy
           @groovy.transform.Canonical
           class Person {
             String name
             int age
           }
           :}
           < class Person
           >> groovy new Person("Mary", 24)
           < Person(Mary, 24)

           When run through pipes, the Groovy code should return a Function that takes
           each input line as an argument, returning something to be printed (or null).

           For example, to only print the lines containing the word 'text' from the output of some_command:

           >> some_command | groovy { line -> if (line.contains("text")) line }

           The curly braces can be omitted:

           >> some_command | groovy line -> if (line.contains("text")) line
           
           Even the closure argument can be omitted (use 'it' to access it), this is Groovy after all:
           
           >> some_command | groovy if (it.contains("text")) it

           State is maintained between invocations (unless 'def' is used to define a variable):

           >> groovy x = 10
           < 10
           >> groovy x + 1
           < 11
           """.stripIndent()
    }

    @Override
    Consumer pipe( String command, PrintStream out, PrintStream err ) {
        command = ( command.trim() - 'groovy' ).trim()

        if ( !command.startsWith( "{" ) && !command.endsWith( "}" ) ) {
            if ( command.matches( closureWithArgPattern ) ) {
                command = "{ $command }"
            } else {
                command = "{ it -> $command }"
            }
        }

        def callback = run( command, out, err )
        if ( callback instanceof Closure ) {
            { String line ->
                def result = ( callback as Closure ).call( line )
                if ( result != null ) {
                    out.println result
                }
            } as Consumer
        } else {
            throw new RuntimeException( 'When used in a pipeline, the groovy script must return a ' +
                    'Closure callback that takes one text line of the input at a time.\n' +
                    'Example: ... | groovy def count = 0; { line -> "Line ${count++}: $line" }' )
        }
    }

    @Override
    void execute( String line, PrintStream out, PrintStream err ) {
        def command = argsSpec.parse( line, breakupOptions )
        if ( command.hasOption( RESET_CODE_ARG ) ) {
            codeBuffer.clear()
        } else if ( command.hasOption( SHOW_ARG ) ) {
            out.println codeBuffer.join( '\n' )
        } else {
            def code = command.unprocessedInput

            if ( command.unprocessedInput.startsWith( 'import ' ) ) {
                def importedClass = command.unprocessedInput.substring( 'import '.size() )
                        .takeWhile { c -> c != ';' && c != '\n' }

                // make sure the import statement compiles
                try {
                    def importStatement = 'import ' + importedClass
                    shell.evaluate( importStatement )
                    codeBuffer << importStatement
                    code = code.substring( importStatement.size() )
                } catch ( e ) {
                    err.println "Error: $e"
                }
            }

            if ( code.trim() ) {
                def result = run( code, out, err )
                if ( result != null ) out.println( result )
            }
        }
    }

    void activate( BundleContext context ) throws Exception {
        contextRef.set context
    }

    void deactivate( BundleContext context ) throws Exception {
        contextRef.set null
    }

    def run( String script, PrintStream out, PrintStream err ) {
        def fullScript = ( codeBuffer + [ script ] ).join( '\n' )

        try {
            shell.context.with {
                setVariable( 'out', out )
                setVariable( 'err', err )
                setVariable( 'ctx', contextRef.get() )
                setVariable( 'binding', variables )
            }

            def result = shell.evaluate( fullScript )

            return result
        } catch ( MissingMethodExceptionNoStack e ) {
            // happens when a class is defined without a main method
            return e.type
        } catch ( Exception e ) {
            err.println( e )
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy