Java tutorial
/* * (c) 2005 David B. Bracewell * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.davidbracewell.cli; import com.davidbracewell.config.Config; import com.davidbracewell.conversion.Val; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.HashMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import java.util.*; import java.util.Map.Entry; /** * <p> A simple command line parser. </p> <p> Options (flags) are added via the <code>addOption</code> method and have * the following specification: <p/> * <pre> * --longOption=ARG * </pre> * <p/> * <pre> * -s ARG * </pre> * <p/> where ARG means the option requires an argument. An option can have an unlimited number of aliases. </p> * * @author David B. Bracewell */ public class CommandLineParser { private static final String KEY_VALUE_SEPARATOR = "="; private static final String LONG = "--"; private static final String SHORT = "-"; private final Map<String, String> aliases; private final Map<String, String> helpDescriptions; private final Map<String, CommandLineOption> knownArguments; private final Map<String, String> setOptionMap; /** * Default Constructor */ public CommandLineParser() { this.knownArguments = Maps.newTreeMap(); this.helpDescriptions = Maps.newHashMap(); this.aliases = Maps.newHashMap(); this.setOptionMap = Maps.newConcurrentMap(); addOption(CommandLineOption.HELP); addOption(CommandLineOption.CONFIG); addOption(CommandLineOption.CONFIG_EXPLAIN); } /** * Adds a command line option * * @param clo The command line option to add * @return The command line option that was added */ public CommandLineOption addOption(CommandLineOption clo) { Preconditions.checkNotNull(clo); knownArguments.put(clo.getName(), clo); helpDescriptions.put(clo.getName(), clo.getDescription()); if (clo.getAliases() != null) { for (String a : clo.getAliases()) { this.aliases.put(a.trim(), clo.getName()); } } return clo; } /** * Adds a non-required option * * @param spec The specification * @param description The description * @param aliases Any aliases. * @return The <code>CommandLineParser</code> instance */ public CommandLineOption addOption(String spec, String description, String... aliases) { CommandLineOption o = CommandLineOption.parse(Preconditions.checkNotNull(spec), description, aliases); knownArguments.put(o.getName(), o); helpDescriptions.put(o.getName(), description); if (aliases != null) { for (String a : aliases) { this.aliases.put(a.trim(), o.getName()); } } return o; } /** * Gets the value of an option * * @param optionName The name of the option * @return The option value as a {@link Val} */ public Val get(String optionName) { if (Strings.isNullOrEmpty(optionName)) { return Val.of(null); } CommandLineOption option = getOption(optionName); if (option == null) { return Val.of(setOptionMap.get(optionName)); } return get(option); } /** * Gets the value of an option * * @param option The command line option * @return The option value as a {@link Val} */ public Val get(CommandLineOption option) { return Val.of(setOptionMap.get(option.getName())); } /** * Gets the command line option associated with the given name * * @param name The name of the command line option * @return The command line option for the name or null if there is not one */ public CommandLineOption getOption(String name) { if (knownArguments.containsKey(name)) { return knownArguments.get(name); } else if (aliases.containsKey(name)) { return knownArguments.get(aliases.get(name)); } return null; } private void setConfigOption(String name, String value) { value = value == null ? "true" : value; Config.setProperty(name.replaceAll("^--?", ""), value); } private String setValue(String key, String value) { CommandLineOption option = getOption(key); if (option == null) { value = (value == null ? "true" : value); setConfigOption(key, value); setOptionMap.put(key.replaceFirst("^\\-\\-?", ""), value); } else if (option.isArgumentRequired() && value == null) { throw new CommandLineParserException(key, null); } else if (option.isArgumentRequired()) { setConfigOption(key, value); setOptionMap.put(option.getName(), value); } else { setConfigOption(key, "true"); setOptionMap.put(option.getName(), "true"); if (value != null) { return value; } } return null; } /** * Parses an array of arguments * * @param args The command line arguments. * @return the non-config/option parameters */ public String[] parse(String[] args) { Preconditions.checkNotNull(args); List<String> filtered = new ArrayList<>(); for (ListIterator<String> iterator = Arrays.asList(args).listIterator(); iterator.hasNext();) { String current = iterator.next(); if (LONG.equals(current) || SHORT.equals(current)) { throw new CommandLineParserException(current, null); } if (current.startsWith(LONG) || current.startsWith(SHORT)) { String key; String value; if (current.endsWith(KEY_VALUE_SEPARATOR)) { key = current.substring(0, current.length() - 1); value = iterator.hasNext() ? iterator.next() : null; } else if (current.contains(KEY_VALUE_SEPARATOR)) { int index = current.indexOf(KEY_VALUE_SEPARATOR); key = current.substring(0, index); value = current.substring(index + 1); } else { key = current; value = iterator.hasNext() ? iterator.next() : null; if (KEY_VALUE_SEPARATOR.equals(value)) { value = iterator.hasNext() ? iterator.next() : null; } } value = setValue(key, value); if (value != null) { iterator.previous(); } } else { filtered.add(current); } } for (CommandLineOption option : knownArguments.values()) { if (option.isRequired() && !option.wasSeen(this)) { showHelp(option.getName() + " is required."); System.exit(-1); } } return filtered.toArray(new String[filtered.size()]); } /** * Determines if this option was seen * * @param name The name of the option * @return True if the option was seen */ public boolean wasOptionSeen(String name) { return setOptionMap.containsKey(name); } /** * @return The options that were set */ public Set<String> setOptions() { return setOptionMap.keySet(); } /** * Prints the help StdErr * * @param description Description text to show ahead of the usage. */ public void showHelp(String description) { int maxArgName = 0; Multimap<String, String> allAliases = HashMultimap.create(); System.err.println(description); for (String arg : knownArguments.keySet()) { allAliases.put(arg, arg); maxArgName = Math.max(maxArgName, arg.length() + 4); for (Entry<String, String> entry : aliases.entrySet()) { if (entry.getValue().equals(arg)) { allAliases.put(arg, entry.getKey()); maxArgName = Math.max(maxArgName, entry.getKey().length() + 4); } } } Set<String> args = Sets.newTreeSet(allAliases.keySet()); for (String arg : args) { boolean insertSpace = !arg.startsWith("--"); System.err.print(String.format((insertSpace ? " " : "") + "%1$-" + maxArgName + "s", arg)); System.err.print("\t" + helpDescriptions.get(arg)); if (knownArguments.get(arg).isRequired()) { System.err.print("\t[REQUIRED]"); } System.err.println(); for (String alias : allAliases.get(arg)) { if (!alias.equals(arg)) { insertSpace = !alias.startsWith("--"); System.err.println( String.format((insertSpace ? " " : "") + " %1$-" + maxArgName + "s ", alias)); } } } } }// END OF CLASS CommandLineParser