All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.wrapper.cli.CommandLineParser Maven / Gradle / Ivy

Go to download

Maven Wrapper Jar download, installs and launches installed target Maven distribution as part of Maven Wrapper scripts run.

There is a newer version: 3.3.2
Show newest version
/*
 * 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 org.apache.maven.wrapper.cli;

import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Formatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * 

* A command-line parser which supports a command/sub-command style command-line interface. Supports the following * syntax: *

* *
 * <option>* (<sub-command> <sub-command-option>*)*
 * 
*
    *
  • Short options are a '-' followed by a single character. For example: {@code -a}.
  • *
  • Long options are '--' followed by multiple characters. For example: {@code --long-option}.
  • *
  • Options can take arguments. The argument follows the option. For example: {@code -a arg} or * {@code --long arg}.
  • *
  • Arguments can be attached to the option using '='. For example: {@code -a=arg} or {@code --long=arg}.
  • *
  • Arguments can be attached to short options. For example: {@code -aarg}.
  • *
  • Short options can be combined. For example {@code -ab} is equivalent to {@code -a -b}.
  • *
  • Anything else is treated as an extra argument. This includes a single {@code -} character.
  • *
  • '--' indicates the end of the options. Anything following is not parsed and is treated as extra arguments.
  • *
  • The parser is forgiving, and allows '--' to be used with short options and '-' to be used with long options.
  • *
  • The set of options must be known at parse time. Sub-commands and their options do not need to be known at parse * time. Use {@link ParsedCommandLine#getExtraArguments()} to obtain the non-option command-line arguments.
  • *
*/ public class CommandLineParser { private Map optionsByString = new HashMap<>(); private boolean allowMixedOptions; private boolean allowUnknownOptions; private final PrintWriter deprecationPrinter; public CommandLineParser() { this(new OutputStreamWriter(System.out)); } public CommandLineParser(Writer deprecationPrinter) { this.deprecationPrinter = new PrintWriter(deprecationPrinter); } /** * Parses the given command-line. * * @param commandLine The command-line. * @return The parsed command line. * @throws org.apache.maven.wrapper.cli.CommandLineArgumentException On parse failure. */ public ParsedCommandLine parse(String... commandLine) throws CommandLineArgumentException { return parse(Arrays.asList(commandLine)); } /** * Parses the given command-line. * * @param commandLine The command-line. * @return The parsed command line. * @throws org.apache.maven.wrapper.cli.CommandLineArgumentException On parse failure. */ public ParsedCommandLine parse(Iterable commandLine) throws CommandLineArgumentException { ParsedCommandLine parsedCommandLine = new ParsedCommandLine(new HashSet(optionsByString.values())); ParserState parseState = new BeforeFirstSubCommand(parsedCommandLine); for (String arg : commandLine) { if (parseState.maybeStartOption(arg)) { if ("--".equals(arg)) { parseState = new AfterOptions(parsedCommandLine); } else if (arg.matches("--[^=]+")) { OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(2)); parseState = parsedOption.onStartNextArg(); } else if (arg.matches("--[^=]+=.*")) { int endArg = arg.indexOf('='); OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(2, endArg)); parseState = parsedOption.onArgument(arg.substring(endArg + 1)); } else if (arg.matches("-[^=]=.*")) { OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(1, 2)); parseState = parsedOption.onArgument(arg.substring(3)); } else { assert arg.matches("-[^-].*"); String option = arg.substring(1); if (optionsByString.containsKey(option)) { OptionParserState parsedOption = parseState.onStartOption(arg, option); parseState = parsedOption.onStartNextArg(); } else { String option1 = arg.substring(1, 2); OptionParserState parsedOption; if (optionsByString.containsKey(option1)) { parsedOption = parseState.onStartOption("-" + option1, option1); if (parsedOption.getHasArgument()) { parseState = parsedOption.onArgument(arg.substring(2)); } else { parseState = parsedOption.onComplete(); for (int i = 2; i < arg.length(); i++) { String optionStr = arg.substring(i, i + 1); parsedOption = parseState.onStartOption("-" + optionStr, optionStr); parseState = parsedOption.onComplete(); } } } else { if (allowUnknownOptions) { // if we are allowing unknowns, just pass through the whole arg parsedOption = parseState.onStartOption(arg, option); parseState = parsedOption.onComplete(); } else { // We are going to throw a CommandLineArgumentException below, but want the message // to reflect that we didn't recognise the first char (i.e. the option specifier) parsedOption = parseState.onStartOption("-" + option1, option1); parseState = parsedOption.onComplete(); } } } } } else { parseState = parseState.onNonOption(arg); } } parseState.onCommandLineEnd(); return parsedCommandLine; } public CommandLineParser allowMixedSubcommandsAndOptions() { allowMixedOptions = true; return this; } public CommandLineParser allowUnknownOptions() { allowUnknownOptions = true; return this; } /** * Prints a usage message to the given stream. * * @param out The output stream to write to. */ public void printUsage(Appendable out) { Formatter formatter = new Formatter(out); Set orderedOptions = new TreeSet<>(new OptionComparator()); orderedOptions.addAll(optionsByString.values()); Map lines = new LinkedHashMap<>(); for (CommandLineOption option : orderedOptions) { Set orderedOptionStrings = new TreeSet<>(new OptionStringComparator()); orderedOptionStrings.addAll(option.getOptions()); List prefixedStrings = new ArrayList<>(); for (String optionString : orderedOptionStrings) { if (optionString.length() == 1) { prefixedStrings.add("-" + optionString); } else { prefixedStrings.add("--" + optionString); } } String key = join(prefixedStrings, ", "); String value = option.getDescription(); if (value == null || value.length() == 0) { value = ""; } lines.put(key, value); } int max = 0; for (String optionStr : lines.keySet()) { max = Math.max(max, optionStr.length()); } for (Map.Entry entry : lines.entrySet()) { if (entry.getValue().length() == 0) { formatter.format("%s%n", entry.getKey()); } else { formatter.format("%-" + max + "s %s%n", entry.getKey(), entry.getValue()); } } formatter.flush(); } private static String join(Collection things, String separator) { StringBuffer buffer = new StringBuffer(); boolean first = true; if (separator == null) { separator = ""; } for (Object thing : things) { if (!first) { buffer.append(separator); } buffer.append(thing.toString()); first = false; } return buffer.toString(); } /** * Defines a new option. By default, the option takes no arguments and has no description. * * @param options The options values. * @return The option, which can be further configured. */ public CommandLineOption option(String... options) { for (String option : options) { if (optionsByString.containsKey(option)) { throw new IllegalArgumentException(String.format("Option '%s' is already defined.", option)); } if (option.startsWith("-")) { throw new IllegalArgumentException( String.format("Cannot add option '%s' as an option cannot" + " start with '-'.", option)); } } CommandLineOption option = new CommandLineOption(Arrays.asList(options)); for (String optionStr : option.getOptions()) { this.optionsByString.put(optionStr, option); } return option; } private static final class OptionString { private final String arg; private final String option; private OptionString(String arg, String option) { this.arg = arg; this.option = option; } public String getDisplayName() { return arg.startsWith("--") ? "--" + option : "-" + option; } @Override public String toString() { return getDisplayName(); } } private abstract static class ParserState { public abstract boolean maybeStartOption(String arg); boolean isOption(String arg) { return arg.matches("-.+"); } public abstract OptionParserState onStartOption(String arg, String option); public abstract ParserState onNonOption(String arg); public void onCommandLineEnd() {} } private abstract class OptionAwareParserState extends ParserState { protected final ParsedCommandLine commandLine; protected OptionAwareParserState(ParsedCommandLine commandLine) { this.commandLine = commandLine; } @Override public boolean maybeStartOption(String arg) { return isOption(arg); } @Override public ParserState onNonOption(String arg) { commandLine.addExtraValue(arg); return allowMixedOptions ? new AfterFirstSubCommand(commandLine) : new AfterOptions(commandLine); } } private final class BeforeFirstSubCommand extends OptionAwareParserState { private BeforeFirstSubCommand(ParsedCommandLine commandLine) { super(commandLine); } @Override public OptionParserState onStartOption(String arg, String option) { OptionString optionString = new OptionString(arg, option); CommandLineOption commandLineOption = optionsByString.get(option); if (commandLineOption == null) { if (allowUnknownOptions) { return new UnknownOptionParserState(arg, commandLine, this); } else { throw new CommandLineArgumentException( String.format("Unknown command-line option '%s'.", optionString)); } } return new KnownOptionParserState(optionString, commandLineOption, commandLine, this); } } private final class AfterFirstSubCommand extends OptionAwareParserState { private AfterFirstSubCommand(ParsedCommandLine commandLine) { super(commandLine); } @Override public OptionParserState onStartOption(String arg, String option) { CommandLineOption commandLineOption = optionsByString.get(option); if (commandLineOption == null) { return new UnknownOptionParserState(arg, commandLine, this); } return new KnownOptionParserState(new OptionString(arg, option), commandLineOption, commandLine, this); } } private static final class AfterOptions extends ParserState { private final ParsedCommandLine commandLine; private AfterOptions(ParsedCommandLine commandLine) { this.commandLine = commandLine; } @Override public boolean maybeStartOption(String arg) { return false; } @Override public OptionParserState onStartOption(String arg, String option) { return new UnknownOptionParserState(arg, commandLine, this); } @Override public ParserState onNonOption(String arg) { commandLine.addExtraValue(arg); return this; } } private static final class MissingOptionArgState extends ParserState { private final OptionParserState option; private MissingOptionArgState(OptionParserState option) { this.option = option; } @Override public boolean maybeStartOption(String arg) { return isOption(arg); } @Override public OptionParserState onStartOption(String arg, String option) { return this.option.onComplete().onStartOption(arg, option); } @Override public ParserState onNonOption(String arg) { return option.onArgument(arg); } @Override public void onCommandLineEnd() { option.onComplete(); } } private abstract static class OptionParserState { public abstract ParserState onStartNextArg(); public abstract ParserState onArgument(String argument); public abstract boolean getHasArgument(); public abstract ParserState onComplete(); } private final class KnownOptionParserState extends OptionParserState { private final OptionString optionString; private final CommandLineOption option; private final ParsedCommandLine commandLine; private final ParserState state; private final List values = new ArrayList<>(); private KnownOptionParserState( OptionString optionString, CommandLineOption option, ParsedCommandLine commandLine, ParserState state) { this.optionString = optionString; this.option = option; this.commandLine = commandLine; this.state = state; } @Override public ParserState onArgument(String argument) { if (!getHasArgument()) { throw new CommandLineArgumentException( String.format("Command-line option '%s' does not" + " take an argument.", optionString)); } if (argument.length() == 0) { throw new CommandLineArgumentException(String.format( "An empty argument was provided" + " for command-line option '%s'.", optionString)); } values.add(argument); return onComplete(); } @Override public ParserState onStartNextArg() { if (option.getAllowsArguments() && values.isEmpty()) { return new MissingOptionArgState(this); } return onComplete(); } @Override public boolean getHasArgument() { return option.getAllowsArguments(); } @Override public ParserState onComplete() { if (getHasArgument() && values.isEmpty()) { throw new CommandLineArgumentException( String.format("No argument was provided" + " for command-line option '%s'.", optionString)); } ParsedCommandLineOption parsedOption = commandLine.addOption(optionString.option, option); if (values.size() + parsedOption.getValues().size() > 1 && !option.getAllowsMultipleArguments()) { throw new CommandLineArgumentException(String.format( "Multiple arguments were provided" + " for command-line option '%s'.", optionString)); } for (String value : values) { parsedOption.addArgument(value); } if (option.getDeprecationWarning() != null) { deprecationPrinter.println( "The " + optionString + " option is deprecated - " + option.getDeprecationWarning()); } if (option.getSubcommand() != null) { return state.onNonOption(option.getSubcommand()); } return state; } } private static final class UnknownOptionParserState extends OptionParserState { private final ParserState state; private final String arg; private final ParsedCommandLine commandLine; private UnknownOptionParserState(String arg, ParsedCommandLine commandLine, ParserState state) { this.arg = arg; this.commandLine = commandLine; this.state = state; } @Override public boolean getHasArgument() { return true; } @Override public ParserState onStartNextArg() { return onComplete(); } @Override public ParserState onArgument(String argument) { return onComplete(); } @Override public ParserState onComplete() { commandLine.addExtraValue(arg); return state; } } private static final class OptionComparator implements Comparator { public int compare(CommandLineOption option1, CommandLineOption option2) { String min1 = Collections.min(option1.getOptions(), new OptionStringComparator()); String min2 = Collections.min(option2.getOptions(), new OptionStringComparator()); return new CaseInsensitiveStringComparator().compare(min1, min2); } } private static final class CaseInsensitiveStringComparator implements Comparator { public int compare(String option1, String option2) { int diff = option1.compareToIgnoreCase(option2); if (diff != 0) { return diff; } return option1.compareTo(option2); } } private static final class OptionStringComparator implements Comparator { public int compare(String option1, String option2) { boolean short1 = option1.length() == 1; boolean short2 = option2.length() == 1; if (short1 && !short2) { return -1; } if (!short1 && short2) { return 1; } return new CaseInsensitiveStringComparator().compare(option1, option2); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy