com.linkedin.harisekhon.CLI.java Source code

Java tutorial

Introduction

Here is the source code for com.linkedin.harisekhon.CLI.java

Source

//  vim:ts=4:sts=4:sw=4:et
//
//  Author: Hari Sekhon
//  Date: 2014-09-15 20:49:22 +0100 (Mon, 15 Sep 2014)
//
//  https://github.com/harisekhon/lib-java
//
//  Port of Python version from https://github.com/harisekhon/pylib repo
//
//  License: see accompanying Hari Sekhon LICENSE file
//
//  If you're using my code you're welcome to connect with me on LinkedIn and optionally send me feedback
//  to help improve or steer this or other code I publish
//
//  https://www.linkedin.com/in/harisekhon
//

package com.linkedin.harisekhon;

import static com.linkedin.harisekhon.Utils.*;
import org.apache.commons.cli.*;

public class CLI {

    private boolean debug = false;
    private int verbose = 0;
    private int verbose_default = 0;
    private int timeout = 0;
    private int timeout_default = 10;
    private int timeout_max = 86400;
    protected String usage_header = "Hari Sekhon - https://github.com/harisekhon\n\n";
    protected String usage_msg = "<prog> <options>";
    protected CommandLine cmd;
    protected Options options = new Options();

    public CLI() {
        options.addOption("t", "timeout", true, String.format("Timeout in secs (default: %s)", timeout_default));
        options.addOption("v", "verbose", false, "Verbose mode (-v, -vv, -vvv)");
        options.addOption("D", "debug", false, "Debug mode");
        //        options.addOption("V", "version", false, "Print version and exit");
        options.addOption("h", "help", false, "Print usage help and exit");
    }

    // TODO: add logic for default host + port support
    public final void addHostOption(String name, String default_host, String default_port) {
        String name2 = "";
        if (name != null) {
            name2 = name + " ";
        }
        //        if default_port is not None:
        //            // assert isPort(default_port)
        //            if not isPort(default_port):
        //                raise CodingError("invalid default port supplied to add_hostoption()")
        //        (host_envs, default_host) = getenvs2("HOST", default_host, name)
        //        (port_envs, default_port) = getenvs2("PORT", default_port, name)
        //        self.add_opt("-H", "--host", dest="host", help="%sHost (%s)" % (name2, host_envs), default=default_host)
        //        self.add_opt("-P", "--port", dest="port", help="%sPort (%s)" % (name2, port_envs), default=default_port)

        options.addOption("H", "host", true, "Host ($HOST)");
        options.addOption("P", "port", true, "Port ($PORT)");
    }

    public final void addHostOption() {
        addHostOption(null, null, null);
    }

    public final void addUserOption(String name, String default_user, String default_password) {
        String name2;
        if (name != null) {
            name2 = name + " ";
        } else {
            name2 = "";
        }
        //        (user_envs, default_user) = getenvs2(["USERNAME", "USER"], default_user, name)
        //        (pw_envs, default_password) = getenvs2("PASSWORD", default_password, name)
        //        self.add_opt("-u", "--user", dest="user", help="%sUsername (%s)" % (name2, user_envs), default=default_user)
        //        self.add_opt("-p", "--password", dest="password", help="%sPassword (%s)" % (name2, pw_envs),
        //                     default=default_password)
        options.addOption("u", "user", true, "Username");
        options.addOption("p", "password", true, "Password");
    }

    public final void addUserOption() {
        addUserOption(null, null, null);
    }

    public void setup() {
        // hook to be overridden by client
    }

    public final void main2(String[] args) {
        log.trace("running CLI.main2()");
        setup();
        try {
            addOptions();
        } catch (IllegalArgumentException e) {
            usage(e);
        }
        try {
            parseArgs2(args);
            //            autoflush();
            // TODO: this will reduce TRACE level, check to only increase log level and never reduce it
            //            if(verbose > 2) {
            //                log.setLevel(Level.DEBUG);
            //            } else if(verbose > 1){
            //                log.setLevel(Level.INFO);
            //            }
            //            if(debug){
            //                log.setLevel(Level.DEBUG);
            //            }
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                e.printStackTrace();
            }
            usage(e.getMessage());
        }
        log.info(String.format("verbose level: %s", verbose));
        validateInt(timeout, "timeout", 0, timeout_max);
        log.info(String.format("setting timeout to %s secs", timeout));
        Thread t = new Thread(new Timeout(timeout));
        t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                if (e instanceof QuitException) {
                    println(((QuitException) e).status + ": " + ((QuitException) e).message);
                    System.exit(getStatusCode("UNKNOWN"));
                } else {
                    // normal Thread.stop() at end of program raises exception with null
                    if (e.getMessage() != null) {
                        println(e.getMessage());
                        System.exit(getStatusCode("UNKNOWN"));
                    }
                }
            }
        });
        t.start();
        try {
            log.trace("running CLI.processArgs()");
            processArgs();
            log.trace("running CLI.run()");
            run();
            log.trace("running CLI.end()");
            end();
            log.trace("stopping timeout thread");
            t.stop();
        } catch (IllegalArgumentException e) {
            log.trace("caught exception in CLI.main2()");
            if (log.isDebugEnabled()) {
                e.printStackTrace();
                // not as nicely formatted - not printing anything right now??
                //                println(e.getStackTrace().toString());
            }
            usage(e.getMessage());
            // not thrown by try block, how is Control-C thrown?
            //        } catch (InterruptedException e){
            //            System.out.println("Caught control-c...");
            //            System.exit(getStatusCode("UNKNOWN"));
        }
    }

    // can't make this abstract as conflicts with static
    public void run() {
        // client defines CLI tool behaviour here
    }

    public void end() {
        // client hook
    }

    public final void usage(String msg, String status) {
        String msg2 = "";
        if (msg != null) {
            msg2 = msg + "\n";
        }
        if (status == null) {
            status = "UNKNOWN";
        }
        int terminalWidth = jline.TerminalFactory.get().getWidth();
        if (terminalWidth < 80) {
            terminalWidth = 80;
        }
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp(terminalWidth, msg2 + "\n\n" + usage_header + "\n", usage_msg + "\n\n", options, "");
        System.exit(getStatusCode(status));
    }

    public final void usage(String msg) {
        usage(msg, null);
    }

    public final void usage(Exception e) {
        usage(e.getMessage(), null);
    }

    public final void usage() {
        usage(null, null);
    }

    // not used in this base class, convenience method for client parseArgs() method
    public void noArgs() {
        if (cmd.getArgList().size() > 0) {
            usage("invalid non-switch arguments supplied on command line");
        }
    }

    public void addOptions() {
        // client hook
        // leave this as optional not abstract as some cli tools may not need to add additional options
    }

    public int getVerbose() {
        return verbose;
    }

    public void setVerbose(int v) {
        //        log.debug("setting verbose to %s", verbose);
        verbose = v;
    }

    public int getVerboseDefault() {
        return verbose_default;
    }

    public void setVerboseDefault(int v) {
        //        log.debug("setting default verbose to %s", verbose);
        verbose_default = v;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int secs) {
        validateInt(secs, "timeout", 0, timeout_max);
        //        log.debug("setting timeout to %s secs", secs);
        timeout = secs;
    }

    public int getTimeoutDefault() {
        return timeout_default;
    }

    // TODO: null to prevent --timeout switch becoming exposed, while 0 will still add timeout switch
    public void setTimeoutDefault(int secs) {
        //        validateInt(secs, "timeout default", 0, timeout_max);
        if (secs > timeout_max) {
            throw new IllegalArgumentException("set default timeout > timeout max");
        }
        //        log.debug("setting default timeout to %s secs", secs);
        timeout_default = secs;
    }

    public int getTimeoutMax() {
        return timeout_max;
    }

    public void setTimeoutMax(int secs) {
        timeout_max = secs;
    }

    // XXX: Not sure this can be ported without **kwargs pass through that Python has...
    //    def add_opt(self, *args, **kwargs):
    //        self.__parser.add_option(*args, **kwargs)

    public String getOpt(String opt) {
        if (cmd.hasOption(opt)) {
            return cmd.getOptionValue(opt);
        }
        // TODO: switch this to Optional later
        return null;
    }

    public void timeoutHandler(Exception e) {
        // consider letting exception propagate
        quit("UNKNOWN", String.format("self timeout out after %s second%s", timeout, plural(timeout)));
    }

    //    def add_default_opts(self):
    //        // This was a hack because main() was called more than once resulting in this being called more than once
    //        // use separate objects in future
    //        // for _ in ("--help", "--version", "--timeout", "--verbose", "--debug"):
    //        //     try:
    //        //         self.__parser.remove_option(_)
    //        //     except ValueError:
    //        //         pass
    //
    //        if self.__timeout_default is not None:
    //            self.add_opt("-t", "--timeout", help="Timeout in secs (default: %d)" % self.__timeout_default,
    //                         metavar="secs", default=self.__timeout_default)
    //        self.add_opt("-v", "--verbose", help="Verbose mode (-v, -vv, -vvv)", action="count",
    //                     default=self.__verbose_default)
    //        self.add_opt("-V", "--version", action="store_true", help="Show version and exit")
    //        // this would intercept and return exit code 0
    //        // self.__parser.add_option("-h", "--help", action="help")
    //        self.add_opt("-h", "--help", action="store_true", help="Show full help and exit")
    //        self.add_opt("-D", "--debug", action="store_true", help=SUPPRESS_HELP, default=bool(os.getenv("DEBUG")))

    private void parseArgs2(String[] args) {
        log.trace("parseArgs2()");
        // 1.3+ API problem with Spark, go back to older API for commons-cli
        //CommandLineParser parser = new DefaultParser();
        CommandLineParser parser = new GnuParser();
        try {
            cmd = parser.parse(options, args);
            // TODO: swtich to getOpt after Optional implemented
            if (cmd.hasOption("h")) {
                usage();
            }
            if (cmd.hasOption("D")) {
                debug = true;
            }
            if (cmd.hasOption("v")) {
                verbose += 1;
            }
            // TODO: get version and top level class name
            //            if(cmd.hasOption("%s version %s".format())){
            //                println(version_string);
            //                System.exit(exit_codes.get("UNKNOWN"));
            //            }
            timeout = timeout_default;
            if (cmd.hasOption("t")) {
                timeout = Integer.valueOf(cmd.getOptionValue("t", String.valueOf(timeout)));
            }
        } catch (ParseException e) {
            if (debug) {
                e.printStackTrace();
            }
            log.error(e + "\n");
            usage();
        }
        String env_verbose = System.getenv("VERBOSE");
        if (env_verbose != null && !env_verbose.trim().isEmpty()) {
            try {
                int v = Integer.valueOf(env_verbose.trim());
                if (v > verbose) {
                    log.trace(
                            String.format("environment variable $VERBOSE = %d, increasing verbosity to %d", v, v));
                    verbose = v;
                }
            } catch (NumberFormatException e) {
                log.warn(String.format("$VERBOSE environment variable is not an integer ('%s')", env_verbose));
            }
        }
        parseArgs();
    }

    public void parseArgs() {
        // client hook
    }

    public void processArgs() {
        // client hook
    }
}

////////////////

//        self.topfile = get_topfile()
//        //print("topfile = %s" % self.topfile)
//        self._docstring = get_file_docstring(self.topfile)
//        if self._docstring:
//            self._docstring = "\n" + self._docstring.strip() + "\n"
//        if self._docstring is None:
//            self._docstring = ""
//        self._topfile_version = get_file_version(self.topfile)
//        // this doesn"t work in unit tests
//        // if self._topfile_version:
//        //     raise CodingError("failed to get topfile version - did you set a __version__ in top cli program?") // pylint: disable=line-too-long
//        self._cli_version = self.__version__
//        self._utils_version = harisekhon.utils.__version__
//        // returns "python -m unittest" :-/
//        // prog = os.path.basename(sys.argv[0])
//        self._prog = os.path.basename(self.topfile)
//        self._github_repo = get_file_github_repo(self.topfile)
//        // if not self.github_repo:
//        //     self.github_repo = "https://github.com/harisekhon/pytools"
//        if self._github_repo:
//            self._github_repo = " - " + self._github_repo
//        // _hidden attributes are shown in __dict__
//        self.version = "%(_prog)s version %(_topfile_version)s " % self.__dict__ + \
//                       "=>  CLI version %(_cli_version)s  =>  Utils version %(_utils_version)s" % self.__dict__
//        self.usagemsg = "Hari Sekhon%(_github_repo)s\n\n%(_prog)s version %(_topfile_version)s\n%(_docstring)s\n" \
//                        % self.__dict__
//        self.usagemsg_short = "Hari Sekhon%(_github_repo)s\n\n" % self.__dict__
//        // set this in simpler client programs when you don"t want to exclude
//        // self.__parser = OptionParser(usage=self.usagemsg_short, version=self.version)
//        // self.__parser = OptionParser(version=self.version)
//        // will be added by default_opts later so that it"s not annoyingly at the top of the option help
//        // also this allows us to print full docstring for a complete description and not just the cli switches
//        // description=self._docstring // don"t want description printed for option errors
//        width = os.getenv("COLUMNS", None)
//        if not isInt(width) or not width:
//            width = Terminal().width
//        width = min(width, 200)
//        self.__parser = OptionParser(add_help_option=False, formatter=IndentedHelpFormatter(width=width))
//        // duplicate key error or duplicate options, sucks
//        // self.__parser.add_option("-V", dest="version", help="Show version and exit", action="store_true")
//        self.setup()
//