You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2018/05/18 02:58:10 UTC

[2/2] groovy git commit: GROOVY-8569 Migrate groovy.ui.GroovyMain to picocli

GROOVY-8569 Migrate groovy.ui.GroovyMain to picocli


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/14c82530
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/14c82530
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/14c82530

Branch: refs/heads/GROOVY_2_6_X
Commit: 14c825301f2afca7354d3559cb0cad019a2ddc46
Parents: d182c97
Author: Remko Popma <re...@yahoo.com>
Authored: Sun May 6 20:20:03 2018 +0900
Committer: Paul King <pa...@asert.com.au>
Committed: Fri May 18 12:58:00 2018 +1000

----------------------------------------------------------------------
 src/main/groovy/groovy/ui/GroovyMain.java | 321 ++++++++++++-------------
 src/test/groovy/ui/GroovyMainTest.groovy  |   8 +-
 2 files changed, 164 insertions(+), 165 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/14c82530/src/main/groovy/groovy/ui/GroovyMain.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/ui/GroovyMain.java b/src/main/groovy/groovy/ui/GroovyMain.java
index 24c1a9f..61bb946 100644
--- a/src/main/groovy/groovy/ui/GroovyMain.java
+++ b/src/main/groovy/groovy/ui/GroovyMain.java
@@ -25,12 +25,6 @@ import groovy.lang.GroovyShell;
 import groovy.lang.GroovySystem;
 import groovy.lang.MissingMethodException;
 import groovy.lang.Script;
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
 import org.codehaus.groovy.control.CompilationFailedException;
 import org.codehaus.groovy.control.CompilerConfiguration;
 import org.codehaus.groovy.control.customizers.ImportCustomizer;
@@ -39,6 +33,8 @@ import org.codehaus.groovy.runtime.InvokerInvocationException;
 import org.codehaus.groovy.runtime.ResourceGroovyMethods;
 import org.codehaus.groovy.runtime.StackTraceUtils;
 import org.codehaus.groovy.runtime.StringGroovyMethods;
+import picocli.CommandLine;
+import picocli.CommandLine.*;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -59,11 +55,12 @@ import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Properties;
+import java.util.concurrent.Callable;
 import java.util.regex.Pattern;
 
-import static org.apache.commons.cli.Option.builder;
-
 /**
  * A Command line to execute groovy.
  */
@@ -114,188 +111,190 @@ public class GroovyMain {
      * @param args all command line args.
      */
     public static void main(String args[]) {
-        processArgs(args, System.out);
+        processArgs(args, System.out, System.err);
     }
 
     // package-level visibility for testing purposes (just usage/errors at this stage)
-    // TODO: should we have an 'err' printstream too for ParseException?
+    @Deprecated
     static void processArgs(String[] args, final PrintStream out) {
-        Options options = buildOptions();
-
+        processArgs(args, out, out);
+    }
+    // package-level visibility for testing purposes (just usage/errors at this stage)
+    static void processArgs(String[] args, final PrintStream out, final PrintStream err) {
+        GroovyCommand groovyCommand = new GroovyCommand();
+        CommandLine parser = new CommandLine(groovyCommand).setUnmatchedArgumentsAllowed(true).setStopAtUnmatched(true);
+        parser.getCommandSpec().mixinStandardHelpOptions(true);
         try {
-            CommandLine cmd = parseCommandLine(options, args);
-
-            if (cmd.hasOption('h')) {
-                printHelp(out, options);
-            } else if (cmd.hasOption('v')) {
-                String version = GroovySystem.getVersion();
-                out.println("Groovy Version: " + version + " JVM: " + System.getProperty("java.version") +
-                        " Vendor: " + System.getProperty("java.vm.vendor")  + " OS: " + System.getProperty("os.name"));
-            } else {
-                // If we fail, then exit with an error so scripting frameworks can catch it
-                // TODO: pass printstream(s) down through process
-                if (!process(cmd)) {
-                    System.exit(1);
-                }
+            List<CommandLine> result = parser.parse(args);
+            if (CommandLine.printHelpIfRequested(result, out, err, Help.Ansi.AUTO)) {
+                return;
             }
-        } catch (ParseException pe) {
-            out.println("error: " + pe.getMessage());
-            printHelp(out, options);
+            // TODO: pass printstream(s) down through process
+            if (!groovyCommand.process(parser)) {
+                // If we fail, then exit with an error so scripting frameworks can catch it.
+                System.exit(1);
+            }
+
+        } catch (ParameterException ex) { // command line arguments could not be parsed
+            err.println(ex.getMessage());
+            ex.getCommandLine().usage(err);
         } catch (IOException ioe) {
-            out.println("error: " + ioe.getMessage());
+            err.println("error: " + ioe.getMessage());
         }
     }
 
-    private static void printHelp(PrintStream out, Options options) {
-        HelpFormatter formatter = new HelpFormatter();
-        PrintWriter pw = new PrintWriter(out);
-
-        formatter.printHelp(
-                pw,
-                80,
-                "groovy [options] [filename] [args]",
-                "The Groovy command line processor.\nOptions:",
-                options,
-                2,
-                4,
-                null, // footer
-                false);
-
-        pw.flush();
+    static class VersionProvider implements IVersionProvider {
+        @Override
+        public String[] getVersion() {
+            return new String[] {
+                    "Groovy Version: " + GroovySystem.getVersion() + " JVM: " + System.getProperty("java.version") +
+                    " Vendor: " + System.getProperty("java.vm.vendor")  + " OS: " + System.getProperty("os.name")
+            };
+        }
     }
 
-    /**
-     * Parse the command line.
-     *
-     * @param options the options parser.
-     * @param args    the command line args.
-     * @return parsed command line.
-     * @throws ParseException if there was a problem.
-     */
-    private static CommandLine parseCommandLine(Options options, String[] args) throws ParseException {
-        CommandLineParser parser = new DefaultParser();
-        return parser.parse(options, args, true);
-    }
+    @Command(name = "groovy",
+            customSynopsis = "groovy [options] [filename] [args]",
+            description = "The Groovy command line processor.",
+            sortOptions = false,
+            versionProvider = VersionProvider.class)
+    private static class GroovyCommand {
 
-    /**
-     * Build the options parser.
-     *
-     * @return an options parser.
-     */
-    private static Options buildOptions() {
-        return new Options()
-                .addOption(builder("classpath").hasArg().argName("path").desc("Specify where to find the class files - must be first argument").build())
-                .addOption(builder("cp").longOpt("classpath").hasArg().argName("path").desc("Aliases for '-classpath'").build())
-                .addOption(builder("D").longOpt("define").desc("Define a system property").numberOfArgs(2).valueSeparator().argName("name=value").build())
-                .addOption(
-                        builder().longOpt("disableopt")
-                                .desc("Disables one or all optimization elements; " +
-                                        "optlist can be a comma separated list with the elements: " +
-                                        "all (disables all optimizations), " +
-                                        "int (disable any int based optimizations)")
-                                .hasArg().argName("optlist").build())
-                .addOption(builder("h").hasArg(false).desc("Usage information").longOpt("help").build())
-                .addOption(builder("d").hasArg(false).desc("Debug mode will print out full stack traces").longOpt("debug").build())
-                .addOption(builder("v").hasArg(false).desc("Display the Groovy and JVM versions").longOpt("version").build())
-                .addOption(builder("c").argName("charset").hasArg().desc("Specify the encoding of the files").longOpt("encoding").build())
-                .addOption(builder("e").argName("script").hasArg().desc("Specify a command line script").build())
-                .addOption(builder("i").argName("extension").optionalArg(true).desc("Modify files in place; create backup if extension is given (e.g. \'.bak\')").build())
-                .addOption(builder("n").hasArg(false).desc("Process files line by line using implicit 'line' variable").build())
-                .addOption(builder("p").hasArg(false).desc("Process files line by line and print result (see also -n)").build())
-                .addOption(builder("pa").hasArg(false).desc("Generate metadata for reflection on method parameter names (jdk8+ only)").longOpt("parameters").build())
-                .addOption(builder("l").argName("port").optionalArg(true).desc("Listen on a port and process inbound lines (default: 1960)").build())
-                .addOption(builder("a").argName("splitPattern").optionalArg(true).desc("Split lines using splitPattern (default '\\s') using implicit 'split' variable").longOpt("autosplit").build())
-                .addOption(builder().longOpt("indy").desc("Enables compilation using invokedynamic").build())
-                .addOption(builder().longOpt("configscript").hasArg().desc("A script for tweaking the configuration options").build())
-                .addOption(builder("b").longOpt("basescript").hasArg().argName("class").desc("Base class name for scripts (must derive from Script)").build());
-    }
+        // IMPLEMENTATION NOTE:
+        // classpath must be the first argument, so that the `startGroovy(.bat)` script
+        // can extract it and the JVM can be started with the classpath already correctly set.
+        // This saves us from having to fork a new JVM process with the classpath set from the processed arguments.
+        @Option(names = {"-cp", "-classpath", "--classpath"}, paramLabel = "<path>", description = "Specify where to find the class files - must be first argument")
+        private String classpath;
 
-    /**
-     * Process the users request.
-     *
-     * @param line the parsed command line.
-     * @throws ParseException if invalid options are chosen
-     */
-    private static boolean process(CommandLine line) throws ParseException, IOException {
-        List args = line.getArgList();
-
-        if (line.hasOption('D')) {
-            Properties optionProperties = line.getOptionProperties("D");
-            Enumeration<String> propertyNames = (Enumeration<String>) optionProperties.propertyNames();
-            while (propertyNames.hasMoreElements()) {
-                String nextName = propertyNames.nextElement();
-                System.setProperty(nextName, optionProperties.getProperty(nextName));
+        @Option(names = {"-D", "--define"}, paramLabel = "<property=value>", description = "Define a system property")
+        private Map<String, String> systemProperties = new LinkedHashMap<String, String>();
+
+        @Option(names = "--disableopt", paramLabel = "optlist", split = ",",
+                description = {
+                        "Disables one or all optimization elements; optlist can be a comma separated list with the elements: ",
+                                "all (disables all optimizations), ",
+                                "int (disable any int based optimizations)"})
+        private List<String> disableopt = new ArrayList<String>();
+
+        @Option(names = {"-d", "--debug"}, description = "Debug mode will print out full stack traces")
+        private boolean debug;
+
+        @Option(names = {"-c", "--encoding"}, paramLabel = "<charset>", description = "Specify the encoding of the files")
+        private String encoding;
+
+        @Option(names = {"-e"}, paramLabel = "<script>", description = "Specify a command line script")
+        private String script;
+
+        @Option(names = {"-i"}, arity = "0..1", paramLabel = "<extension>", description = "Modify files in place; create backup if extension is given (e.g. \'.bak\')")
+        private String extension;
+
+        @Option(names = {"-n"}, description = "Process files line by line using implicit 'line' variable")
+        private boolean lineByLine;
+
+        @Option(names = {"-p"}, description = "Process files line by line and print result (see also -n)")
+        private boolean lineByLinePrint;
+
+        @Option(names = {"-pa", "--parameters"}, description = "Generate metadata for reflection on method parameter names (jdk8+ only)")
+        private boolean parameterMetadata;
+
+        @Option(names = "-l", arity = "0..1", paramLabel = "<port>", description = "Listen on a port and process inbound lines (default: 1960)")
+        private String port;
+
+        @Option(names = {"-a", "--autosplit"}, arity = "0..1", paramLabel = "<splitPattern>", description = "Split lines using splitPattern (default '\\s') using implicit 'split' variable")
+        private String splitPattern;
+
+        @Option(names = {"--indy"}, description = "Enables compilation using invokedynamic")
+        private boolean indy;
+
+        @Option(names = {"--configscript"}, paramLabel = "<script>", description = "A script for tweaking the configuration options")
+        private String configscript;
+
+        @Option(names = {"-b", "--basescript"}, paramLabel = "<class>", description = "Base class name for scripts (must derive from Script)")
+        private String scriptBaseClass;
+
+        @Unmatched
+        List<String> arguments = new ArrayList<String>();
+
+        /**
+         * Process the users request.
+         *
+         * @param parser the parsed command line. Used when the user input was invalid.
+         * @throws ParameterException if the user input was invalid
+         */
+        boolean process(CommandLine parser) throws ParameterException, IOException {
+            for (String key : systemProperties.keySet()) {
+                System.setProperty(key, systemProperties.get(key));
+            }
+            GroovyMain main = new GroovyMain();
+
+            // add the ability to parse scripts with a specified encoding
+            main.conf.setSourceEncoding(encoding);
+
+            main.debug = debug;
+            main.conf.setDebug(main.debug);
+            main.conf.setParameters(parameterMetadata);
+            main.processFiles = lineByLine || lineByLinePrint;
+            main.autoOutput = lineByLinePrint;
+            main.editFiles = extension != null;
+            if (main.editFiles) {
+                main.backupExtension = extension;
             }
-        }
 
-        GroovyMain main = new GroovyMain();
+            main.autoSplit = splitPattern != null;
+            if (main.autoSplit) {
+                main.splitPattern = splitPattern;
+            }
 
-        // add the ability to parse scripts with a specified encoding
-        main.conf.setSourceEncoding(line.getOptionValue('c',main.conf.getSourceEncoding()));
+            main.isScriptFile = script == null;
+            if (main.isScriptFile) {
+                if (arguments.isEmpty()) {
+                    throw new ParameterException(parser, "error: neither -e or filename provided");
+                }
+                main.script = arguments.remove(0);
+                if (main.script.endsWith(".java")) {
+                    throw new ParameterException(parser, "error: cannot compile file with .java extension: " + main.script);
+                }
+            } else {
+                main.script = script;
+            }
 
-        main.isScriptFile = !line.hasOption('e');
-        main.debug = line.hasOption('d');
-        main.conf.setDebug(main.debug);
-        main.conf.setParameters(line.hasOption("pa"));
-        main.processFiles = line.hasOption('p') || line.hasOption('n');
-        main.autoOutput = line.hasOption('p');
-        main.editFiles = line.hasOption('i');
-        if (main.editFiles) {
-            main.backupExtension = line.getOptionValue('i');
-        }
-        main.autoSplit = line.hasOption('a');
-        String sp = line.getOptionValue('a');
-        if (sp != null)
-            main.splitPattern = sp;
-
-        if (main.isScriptFile) {
-            if (args.isEmpty())
-                throw new ParseException("neither -e or filename provided");
-
-            main.script = (String) args.remove(0);
-            if (main.script.endsWith(".java"))
-                throw new ParseException("error: cannot compile file with .java extension: " + main.script);
-        } else {
-            main.script = line.getOptionValue('e');
-        }
+            main.processSockets = port != null;
+            if (main.processSockets) {
+                String p = port.trim().length() > 0 ? port : "1960"; // default port to listen to
+                main.port = Integer.parseInt(p);
+            }
 
-        main.processSockets = line.hasOption('l');
-        if (main.processSockets) {
-            String p = line.getOptionValue('l', "1960"); // default port to listen to
-            main.port = Integer.parseInt(p);
-        }
+            for (String optimization : disableopt) {
+                main.conf.getOptimizationOptions().put(optimization, false);
+            }
 
-        // we use "," as default, because then split will create
-        // an empty array if no option is set
-        String disabled = line.getOptionValue("disableopt", ",");
-        String[] deopts = disabled.split(",");
-        for (String deopt_i : deopts) {
-            main.conf.getOptimizationOptions().put(deopt_i,false);
-        }
+            if (indy) {
+                CompilerConfiguration.DEFAULT.getOptimizationOptions().put("indy", true);
+                main.conf.getOptimizationOptions().put("indy", true);
+            }
 
-        if (line.hasOption("indy")) {
-            CompilerConfiguration.DEFAULT.getOptimizationOptions().put("indy", true);
-            main.conf.getOptimizationOptions().put("indy", true);
-        }
+            if (scriptBaseClass != null) {
+                main.conf.setScriptBaseClass(scriptBaseClass);
+            }
+
+            processConfigScripts(getConfigScripts(), main.conf);
 
-        if (line.hasOption("basescript")) {
-            main.conf.setScriptBaseClass(line.getOptionValue("basescript"));
+            main.args = arguments;
+            return main.run();
         }
 
-        String configScripts = System.getProperty("groovy.starter.configscripts", null);
-        if (line.hasOption("configscript") || (configScripts != null && !configScripts.isEmpty())) {
+        private List<String> getConfigScripts() {
             List<String> scripts = new ArrayList<String>();
-            if (line.hasOption("configscript")) {
-                scripts.add(line.getOptionValue("configscript"));
+            if (this.configscript != null) {
+                scripts.add(this.configscript);
             }
-            if (configScripts != null) {
+            String configScripts = System.getProperty("groovy.starter.configscripts", null);
+            if (configScripts != null && !configScripts.isEmpty()) {
                 scripts.addAll(StringGroovyMethods.tokenize((CharSequence) configScripts, ','));
             }
-            processConfigScripts(scripts, main.conf);
+            return scripts;
         }
-
-        main.args = args;
-        return main.run();
     }
 
     public static void processConfigScripts(List<String> scripts, CompilerConfiguration conf) throws IOException {

http://git-wip-us.apache.org/repos/asf/groovy/blob/14c82530/src/test/groovy/ui/GroovyMainTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/ui/GroovyMainTest.groovy b/src/test/groovy/ui/GroovyMainTest.groovy
index 6c9396e..ab788ac 100644
--- a/src/test/groovy/ui/GroovyMainTest.groovy
+++ b/src/test/groovy/ui/GroovyMainTest.groovy
@@ -26,14 +26,14 @@ class GroovyMainTest extends GroovyTestCase {
         String[] args = ['-h']
         GroovyMain.processArgs(args, ps)
         def out = baos.toString()
-        assert out.contains('usage: groovy')
-        ['-a', '-c', '-d', '-e', '-h', '-i', '-l', '-n', '-p', '-v'].each{
+        assert out.contains('Usage: groovy')
+        ['-a', '-c', '-d', '-e', '-h', '-i', '-l', '-n', '-p', '-V'].each{
             assert out.contains(it)
         }
     }
 
     void testVersion() {
-        String[] args = ['-v']
+        String[] args = ['-V']
         GroovyMain.processArgs(args, ps)
         def out = baos.toString()
         assert out.contains('Groovy Version:')
@@ -51,7 +51,7 @@ class GroovyMainTest extends GroovyTestCase {
         String[] args = ['abc.java']
         GroovyMain.processArgs(args, ps)
         def out = baos.toString()
-        assert out.contains('error: error: cannot compile file with .java extension: abc.java')
+        assert out.contains('error: cannot compile file with .java extension: abc.java')
     }
 
     /**