TINKERPOP-1653 Allow multiple scripts to be passed to gremlin.sh This is a non-breaking change and simply allows multiple -i and -e options on gremlin.sh. The old method for passing a single script name to gremlin.sh remains intact as well. Had to write a custom parser for the command line arguments to do this because it doesn't appear that CliBuilder is capable of parsing the in the manner necessary to support this feature unfortunately.
Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/1503311f Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/1503311f Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/1503311f Branch: refs/heads/TINKERPOP-1625 Commit: 1503311f030291745276805188e61b525576d604 Parents: 3821792 Author: Stephen Mallette <sp...@genoprime.com> Authored: Fri Mar 17 10:23:34 2017 -0400 Committer: Stephen Mallette <sp...@genoprime.com> Committed: Fri Mar 17 10:23:34 2017 -0400 ---------------------------------------------------------------------- CHANGELOG.asciidoc | 1 + .../src/reference/gremlin-applications.asciidoc | 13 ++ .../upgrade/release-3.2.x-incubating.asciidoc | 15 ++ .../tinkerpop/gremlin/console/Console.groovy | 139 ++++++++++++------- 4 files changed, 119 insertions(+), 49 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/1503311f/CHANGELOG.asciidoc ---------------------------------------------------------------------- diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index a77991e..6e0204b 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -26,6 +26,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima TinkerPop 3.2.5 (Release Date: NOT OFFICIALLY RELEASED YET) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* Allowed for multiple scripts and related arguments to be passed to `gremlin.sh` via `-i` and `-e`. * Added various metrics to the `GremlinGroovyScriptEngine` around script compilation and exposed them in Gremlin Server. * Moved the `caffeine` dependency down to `gremlin-groovy` and out of `gremlin-server`. * Improved script compilation in `GremlinGroovyScriptEngine to use better caching, log long compile times and prevent failed compilations from recompiling on future requests. http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/1503311f/docs/src/reference/gremlin-applications.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/reference/gremlin-applications.asciidoc b/docs/src/reference/gremlin-applications.asciidoc index 3a2c446..bfaa153 100644 --- a/docs/src/reference/gremlin-applications.asciidoc +++ b/docs/src/reference/gremlin-applications.asciidoc @@ -303,6 +303,16 @@ $ bin/gremlin.sh -e gremlin.groovy vadas v[2] ---- +It is also possible to pass multiple scripts by specifying multiple `-e` options. The scripts will execute in the order +that they are specified. Note that only the arguments from the last script executed will be preserved in the console. +Finally, if the arguments conflict with the reserved flags that `gremlin.sh` responds double quotes can be used to +wrap all the arugments to the option: + +[source,bash] +---- +$ bin/gremlin.sh -e "gremlin.groovy -e -i --color" +---- + [[interactive-mode]] Interactive Mode ~~~~~~~~~~~~~~~~ @@ -347,6 +357,9 @@ gremlin> g.V() Note that the user can now reference `g` (and `graph` for that matter) at startup without having to directly type that variable initialization code into the console. +Like, execution mode, it is also possible to pass multiple scripts by specifying multiple `-i` options. See the +<<execution-mode, Execution Mode Section>> for more information on the specfics of that capability. + [[gremlin-server]] Gremlin Server -------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/1503311f/docs/src/upgrade/release-3.2.x-incubating.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/upgrade/release-3.2.x-incubating.asciidoc b/docs/src/upgrade/release-3.2.x-incubating.asciidoc index d01f58f..79b1ac3 100644 --- a/docs/src/upgrade/release-3.2.x-incubating.asciidoc +++ b/docs/src/upgrade/release-3.2.x-incubating.asciidoc @@ -42,6 +42,21 @@ set. Note that metrics are captured for both sessionless requests as well as for See: https://issues.apache.org/jira/browse/TINKERPOP-1644[TINKERPOP-1644] +Gremlin Console Scripting +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The `gremlin.sh` command has two flags, `-i` and `-e`, which are used to pass a script and arguments into the Gremlin +Console for execution. Those flags now allow for passing multiple scripts and related arguments to be supplied which +can yield greater flexibilty in automation tasks. + +[source,bash] +---- +$ bin/gremlin.sh -i y.groovy 1 2 3 -i x.groovy +$ bin/gremlin.sh -e y.groovy 1 2 3 -e x.groovy +---- + +See: link:https://issues.apache.org/jira/browse/TINKERPOP-1653[TINKERPOP-1653] + Gremlin-Python Driver ^^^^^^^^^^^^^^^^^^^^^ Gremlin-Python now offers a more complete driver implementation that uses connection pooling and http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/1503311f/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy ---------------------------------------------------------------------- diff --git a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy index 651d6f3..393f1f2 100644 --- a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy +++ b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy @@ -69,10 +69,10 @@ class Console { */ @Deprecated public Console(final String initScriptFile) { - this(new IO(System.in, System.out, System.err), initScriptFile.size() != null ? [initScriptFile]: null, true) + this(new IO(System.in, System.out, System.err), initScriptFile.size() != null ? [[initScriptFile]]: null, true) } - public Console(final IO io, final List<String> scriptAndArgs, final boolean interactive) { + public Console(final IO io, final List<List<String>> scriptsAndArgs, final boolean interactive) { this.io = io this.interactive = interactive @@ -158,7 +158,7 @@ class Console { try { // if the init script contains :x command it will throw an ExitNotification so init script execution // needs to appear in the try/catch - if (scriptAndArgs != null && scriptAndArgs.size() > 0) executeInShell(scriptAndArgs) + if (scriptsAndArgs != null && !scriptsAndArgs.isEmpty()) executeInShell(scriptsAndArgs) // start iterating results to show as output showShellEvaluationOutput(true) @@ -364,49 +364,51 @@ class Console { return Preferences.resultPrompt } - private void executeInShell(final List<String> scriptAndArgs) { - final String scriptFile = scriptAndArgs[0] - try { - // check if this script comes with arguments. if so then set them up in an "args" bundle - if (scriptAndArgs.size() > 1) { - List<String> args = scriptAndArgs.subList(1, scriptAndArgs.size()) - groovy.execute("args = [\"" + args.join('\",\"') + "\"]") - } else { - groovy.execute("args = []") - } - - File file = new File(scriptFile) - if (!file.exists() && !file.isAbsolute()) { - final String userWorkingDir = System.getProperty("user.working_dir"); - if (userWorkingDir != null) { - file = new File(userWorkingDir, scriptFile); + private void executeInShell(final List<List<String>> scriptsAndArgs) { + scriptsAndArgs.each { scriptAndArgs -> + final String scriptFile = scriptAndArgs[0] + try { + // check if this script comes with arguments. if so then set them up in an "args" bundle + if (scriptAndArgs.size() > 1) { + List<String> args = scriptAndArgs.subList(1, scriptAndArgs.size()) + groovy.execute("args = [\"" + args.join('\",\"') + "\"]") + } else { + groovy.execute("args = []") } - } - int lineNumber = 0 - def lines = file.readLines() - for (String line : lines) { - try { - lineNumber++ - groovy.execute(line) - } catch (Exception ex) { - io.err.println(Colorizer.render(Preferences.errorColor, "Error in $scriptFile at [$lineNumber: $line] - ${ex.message}")) - if (interactive) - break - else { - ex.printStackTrace(io.err) - System.exit(1) + + File file = new File(scriptFile) + if (!file.exists() && !file.isAbsolute()) { + final String userWorkingDir = System.getProperty("user.working_dir"); + if (userWorkingDir != null) { + file = new File(userWorkingDir, scriptFile); } + } + int lineNumber = 0 + def lines = file.readLines() + for (String line : lines) { + try { + lineNumber++ + groovy.execute(line) + } catch (Exception ex) { + io.err.println(Colorizer.render(Preferences.errorColor, "Error in $scriptFile at [$lineNumber: $line] - ${ex.message}")) + if (interactive) + break + else { + ex.printStackTrace(io.err) + System.exit(1) + } + } } - } - if (!interactive) System.exit(0) - } catch (FileNotFoundException ignored) { - io.err.println(Colorizer.render(Preferences.errorColor, "Gremlin file not found at [$scriptFile].")) - if (!interactive) System.exit(1) - } catch (Exception ex) { - io.err.println(Colorizer.render(Preferences.errorColor, "Failure processing Gremlin script [$scriptFile] - ${ex.message}")) - if (!interactive) System.exit(1) + if (!interactive) System.exit(0) + } catch (FileNotFoundException ignored) { + io.err.println(Colorizer.render(Preferences.errorColor, "Gremlin file not found at [$scriptFile].")) + if (!interactive) System.exit(1) + } catch (Exception ex) { + io.err.println(Colorizer.render(Preferences.errorColor, "Failure processing Gremlin script [$scriptFile] - ${ex.message}")) + if (!interactive) System.exit(1) + } } } @@ -414,11 +416,7 @@ class Console { Preferences.expandoMagic() - // need to do some up front processing to try to support "bin/gremlin.sh init.groovy" until this deprecated - // feature can be removed. ultimately this should be removed when a breaking change can go in IO io = new IO(System.in, System.out, System.err) - if (args.length == 1 && !args[0].startsWith("-")) - new Console(io, [args[0]], true) final CliBuilder cli = new CliBuilder(usage: 'gremlin.sh [options] [...]', formatter: new HelpFormatter(), stopAtNonOption: false) @@ -452,6 +450,8 @@ class Console { } if (options.v) { + if (args.length == 1 && !args[0].startsWith("-")) + new Console(io, [args[0]], true) println("gremlin " + Gremlin.version()) System.exit(0) } @@ -469,9 +469,50 @@ class Console { System.exit(0) } - List<String> scriptAndArgs = options.e ? - (options.es != null && options.es ? options.es : null) : - (options.is != null && options.is ? options.is : null) - new Console(io, scriptAndArgs, !options.e) + // need to do some up front processing to try to support "bin/gremlin.sh init.groovy" until this deprecated + // feature can be removed. ultimately this should be removed when a breaking change can go in + if (args.length == 1 && !args[0].startsWith("-")) { + new Console(io, [[args[0]]], true) + } else { + def scriptAndArgs = parseArgs(options.e ? "-e" : "-i", args, cli) + new Console(io, scriptAndArgs, !options.e) + } + } + + /** + * Provides a bit of a hack around the limitations of the {@code CliBuilder}. This method directly parses the + * argument list to allow for multiple {@code -e} and {@code -i} values and parses such parameters into a list + * of lists where the inner list is a script file and its arguments. + */ + private static List<List<String>> parseArgs(final String option, final String[] args, final CliBuilder cli) { + def parsed = [] + for (int ix = 0; ix < args.length; ix++) { + if (args[ix] == option) { + // increment the counter to move past the option that was found. should now be positioned on the + // first argument to that option + ix++ + + def parsedSet = [] + for (ix; ix < args.length; ix++) { + // this is a do nothing as there's no arguments to the option or it's the start of a new option + if (cli.options.options.any { "-" + it.opt == args[ix] || "--" + it.longOpt == args[ix] }) { + // rollback the counter now that we hit the next option + ix-- + break; + } + parsedSet << args[ix] + } + + if (!parsedSet.isEmpty()) { + // check if the params were passed in with double quotes such that they arrive as a single arg + if (parsedSet.size() == 1) + parsed << parsedSet[0].toString().split(" ").toList() + else + parsed << parsedSet + } + } + } + + return parsed } }