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 */
018package org.apache.oozie.cli;
019
020import org.apache.commons.cli.Options;
021import org.apache.commons.cli.GnuParser;
022import org.apache.commons.cli.ParseException;
023import org.apache.commons.cli.CommandLine;
024import org.apache.commons.cli.HelpFormatter;
025
026import java.util.Map;
027import java.util.LinkedHashMap;
028import java.text.MessageFormat;
029import java.io.PrintWriter;
030import java.util.HashSet;
031import java.util.Set;
032
033/**
034 * Command line parser based on Apache common-cli 1.x that supports subcommands.
035 */
036public 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