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