001    /**
002     * Copyright (c) 2010 Yahoo! Inc. All rights reserved.
003     * Licensed under the Apache License, Version 2.0 (the "License");
004     * you may not use this file except in compliance with the License.
005     * You may obtain a copy of the License at
006     *
007     *   http://www.apache.org/licenses/LICENSE-2.0
008     *
009     *  Unless required by applicable law or agreed to in writing, software
010     *  distributed under the License is distributed on an "AS IS" BASIS,
011     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012     *  See the License for the specific language governing permissions and
013     *  limitations under the License. See accompanying LICENSE file.
014     */
015    package org.apache.oozie.cli;
016    
017    import org.apache.commons.cli.Options;
018    import org.apache.commons.cli.GnuParser;
019    import org.apache.commons.cli.ParseException;
020    import org.apache.commons.cli.CommandLine;
021    import org.apache.commons.cli.HelpFormatter;
022    
023    import java.util.Map;
024    import java.util.LinkedHashMap;
025    import java.text.MessageFormat;
026    import java.io.PrintWriter;
027    
028    /**
029     * Command line parser based on Apache common-cli 1.x that supports subcommands.
030     */
031    public class CLIParser {
032        private static final String LEFT_PADDING = "      ";
033    
034        private String cliName;
035        private String[] cliHelp;
036        private Map<String, Options> commands = new LinkedHashMap<String, Options>();
037        private Map<String, Boolean> commandWithArgs = new LinkedHashMap<String, Boolean>();
038        private Map<String, String> commandsHelp = new LinkedHashMap<String, String>();
039    
040        /**
041         * Create a parser.
042         *
043         * @param cliName name of the parser, for help purposes.
044         * @param cliHelp help for the CLI.
045         */
046        public CLIParser(String cliName, String[] cliHelp) {
047            this.cliName = cliName;
048            this.cliHelp = cliHelp;
049        }
050    
051        /**
052         * Add a command to the parser.
053         *
054         * @param command comand name.
055         * @param argsHelp command arguments help.
056         * @param commandHelp command description.
057         * @param commandOptions command options.
058         * @param hasArguments
059         */
060        public void addCommand(String command, String argsHelp, String commandHelp, Options commandOptions,
061                               boolean hasArguments) {
062            String helpMsg = argsHelp + ((hasArguments) ? "<ARGS> " : "") + ": " + commandHelp;
063            commandsHelp.put(command, helpMsg);
064            commands.put(command, commandOptions);
065            commandWithArgs.put(command, hasArguments);
066        }
067    
068        /**
069         * Bean that represents a parsed command.
070         */
071        public class Command {
072            private String name;
073            private CommandLine commandLine;
074    
075            private Command(String name, CommandLine commandLine) {
076                this.name = name;
077                this.commandLine = commandLine;
078            }
079    
080            /**
081             * Return the command name.
082             *
083             * @return the command name.
084             */
085            public String getName() {
086                return name;
087            }
088    
089            /**
090             * Return the command line.
091             *
092             * @return the command line.
093             */
094            public CommandLine getCommandLine() {
095                return commandLine;
096            }
097        }
098    
099        /**
100         * Parse a array of arguments into a command.
101         *
102         * @param args array of arguments.
103         * @return the parsed Command.
104         * @throws ParseException thrown if the arguments could not be parsed.
105         */
106        public Command parse(String[] args) throws ParseException {
107            if (args.length == 0) {
108                throw new ParseException("missing sub-command");
109            }
110            else {
111                if (commands.containsKey(args[0])) {
112                    GnuParser parser = new GnuParser();
113                    String[] minusCommand = new String[args.length - 1];
114                    System.arraycopy(args, 1, minusCommand, 0, minusCommand.length);
115                    return new Command(args[0], parser.parse(commands.get(args[0]), minusCommand,
116                                                             commandWithArgs.get(args[0])));
117                }
118                else {
119                    throw new ParseException(MessageFormat.format("invalid sub-command [{0}]", args[0]));
120                }
121            }
122        }
123    
124        public String shortHelp() {
125            return "use 'help' sub-command for help details";
126        }
127    
128        /**
129         * Print the help for the parser to standard output.
130         */
131        public void showHelp() {
132            PrintWriter pw = new PrintWriter(System.out);
133            pw.println("usage: ");
134            for (String s : cliHelp) {
135                pw.println(LEFT_PADDING + s);
136            }
137            pw.println();
138            HelpFormatter formatter = new HelpFormatter();
139            for (Map.Entry<String, Options> entry : commands.entrySet()) {
140                String s = LEFT_PADDING + cliName + " " + entry.getKey() + " ";
141                if (entry.getValue().getOptions().size() > 0) {
142                    pw.println(s + "<OPTIONS> " + commandsHelp.get(entry.getKey()));
143                    formatter.printOptions(pw, 100, entry.getValue(), s.length(), 3);
144                }
145                else {
146                    pw.println(s + commandsHelp.get(entry.getKey()));
147                }
148                pw.println();
149            }
150            pw.flush();
151        }
152    
153    }
154