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    
031    /**
032     * Command line parser based on Apache common-cli 1.x that supports subcommands.
033     */
034    public class CLIParser {
035        private static final String LEFT_PADDING = "      ";
036    
037        private String cliName;
038        private String[] cliHelp;
039        private Map<String, Options> commands = new LinkedHashMap<String, Options>();
040        private Map<String, Boolean> commandWithArgs = new LinkedHashMap<String, Boolean>();
041        private Map<String, String> commandsHelp = new LinkedHashMap<String, String>();
042    
043        /**
044         * Create a parser.
045         *
046         * @param cliName name of the parser, for help purposes.
047         * @param cliHelp help for the CLI.
048         */
049        public CLIParser(String cliName, String[] cliHelp) {
050            this.cliName = cliName;
051            this.cliHelp = cliHelp;
052        }
053    
054        /**
055         * Add a command to the parser.
056         *
057         * @param command comand name.
058         * @param argsHelp command arguments help.
059         * @param commandHelp command description.
060         * @param commandOptions command options.
061         * @param hasArguments
062         */
063        public void addCommand(String command, String argsHelp, String commandHelp, Options commandOptions,
064                               boolean hasArguments) {
065            String helpMsg = argsHelp + ((hasArguments) ? "<ARGS> " : "") + ": " + commandHelp;
066            commandsHelp.put(command, helpMsg);
067            commands.put(command, commandOptions);
068            commandWithArgs.put(command, hasArguments);
069        }
070    
071        /**
072         * Bean that represents a parsed command.
073         */
074        public class Command {
075            private String name;
076            private CommandLine commandLine;
077    
078            private Command(String name, CommandLine commandLine) {
079                this.name = name;
080                this.commandLine = commandLine;
081            }
082    
083            /**
084             * Return the command name.
085             *
086             * @return the command name.
087             */
088            public String getName() {
089                return name;
090            }
091    
092            /**
093             * Return the command line.
094             *
095             * @return the command line.
096             */
097            public CommandLine getCommandLine() {
098                return commandLine;
099            }
100        }
101    
102        /**
103         * Parse a array of arguments into a command.
104         *
105         * @param args array of arguments.
106         * @return the parsed Command.
107         * @throws ParseException thrown if the arguments could not be parsed.
108         */
109        public Command parse(String[] args) throws ParseException {
110            if (args.length == 0) {
111                throw new ParseException("missing sub-command");
112            }
113            else {
114                if (commands.containsKey(args[0])) {
115                    GnuParser parser = new GnuParser();
116                    String[] minusCommand = new String[args.length - 1];
117                    System.arraycopy(args, 1, minusCommand, 0, minusCommand.length);
118                    return new Command(args[0], parser.parse(commands.get(args[0]), minusCommand,
119                                                             commandWithArgs.get(args[0])));
120                }
121                else {
122                    throw new ParseException(MessageFormat.format("invalid sub-command [{0}]", args[0]));
123                }
124            }
125        }
126    
127        public String shortHelp() {
128            return "use 'help' sub-command for help details";
129        }
130    
131        /**
132         * Print the help for the parser to standard output.
133         */
134        public void showHelp() {
135            PrintWriter pw = new PrintWriter(System.out);
136            pw.println("usage: ");
137            for (String s : cliHelp) {
138                pw.println(LEFT_PADDING + s);
139            }
140            pw.println();
141            HelpFormatter formatter = new HelpFormatter();
142            for (Map.Entry<String, Options> entry : commands.entrySet()) {
143                String s = LEFT_PADDING + cliName + " " + entry.getKey() + " ";
144                if (entry.getValue().getOptions().size() > 0) {
145                    pw.println(s + "<OPTIONS> " + commandsHelp.get(entry.getKey()));
146                    formatter.printOptions(pw, 100, entry.getValue(), s.length(), 3);
147                }
148                else {
149                    pw.println(s + commandsHelp.get(entry.getKey()));
150                }
151                pw.println();
152            }
153            pw.flush();
154        }
155    
156    }
157