/* The MIT License Copyright (c) 2009 Paul R. Holser, Jr. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package joptsimple; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import static java.util.Collections.*; import joptsimple.internal.AbbreviationMap; import joptsimple.util.KeyValuePair; import static joptsimple.OptionException.*; import static joptsimple.OptionParserState.*; import static joptsimple.ParserRules.*; /** *

Parses command line arguments, using a syntax that attempts to take from the best * of POSIX {@code getopt()} and GNU {@code getopt_long()}.

* *

This parser supports short options and long options.

* * * *

There are two ways to tell the parser what options to recognize:

* *
    *
  1. A "fluent interface"-style API for specifying options, available since * version 2. Sentences in this fluent interface language begin with a call to * {@link #accepts(String) accepts} or {@link #acceptsAll(Collection) acceptsAll} * methods; calls on the ensuing chain of objects describe whether the options can take * an argument, whether the argument is required or optional, to what type arguments of * the options should be converted if any, etc. Since version 3, these calls return * an instance of {@link OptionSpec}, which can subsequently be used to retrieve the * arguments of the associated option in a type-safe manner.
  2. * *
  3. Since version 1, a more concise way of specifying short options has been to use * the special {@linkplain #OptionParser(String) constructor}. Arguments of options * specified in this manner will be of type {@link String}. Here are the rules for the * format of the specification strings this constructor accepts: * * *
  4. *
* *

Each of the options in a list of options given to {@link #acceptsAll(Collection) * acceptsAll} is treated as a synonym of the others. For example: *

 *     
 *     OptionParser parser = new OptionParser();
 *     parser.acceptsAll( asList( "w", "interactive", "confirmation" ) );
 *     OptionSet options = parser.parse( "-w" );
 *     
 *   
* In this case, options.{@link OptionSet#has(String) has} would answer * {@code true} when given arguments "w", "interactive", and * "confirmation". The {@link OptionSet} would give the same responses to * these arguments for its other methods as well.

* *

By default, as with GNU {@code getopt()}, the parser allows intermixing of options * and non-options. If, however, the parser has been created to be "POSIX-ly correct", * then the first argument that does not look lexically like an option, and is not a * required argument of a preceding option, signals the end of options. You can still * bind optional arguments to their options using the abutting (for short options) or * = syntax.

* *

Unlike GNU {@code getopt()}, this parser does not honor the environment variable * {@code POSIXLY_CORRECT}. "POSIX-ly correct" parsers are configured by either:

* *
    *
  1. using the method {@link #posixlyCorrect(boolean)}, or
  2. * *
  3. using the {@linkplain #OptionParser(String) constructor} with an argument whose * first character is a plus sign ("+")
  4. *
* * @author Paul Holser * @version $Id: OptionParser.java,v 1.38 2009/10/25 18:37:06 pholser Exp $ * @see The GNU C Library */ public class OptionParser { private final AbbreviationMap> recognizedOptions; private OptionParserState state; private boolean posixlyCorrect; /** *

Creates an option parser that initially recognizes no options, and does not * exhibit "POSIX-ly correct" behavior.

*/ public OptionParser() { recognizedOptions = new AbbreviationMap>(); state = moreOptions( false ); } /** *

Creates an option parser and configures it to recognize the short options * specified in the given string.

* *

Arguments of options specified this way will be of type {@link String}.

* * @param optionSpecification an option specification * @throws NullPointerException if optionSpecification is * {@code null} * @throws OptionException if the option specification contains illegal characters * or otherwise cannot be recognized */ public OptionParser( String optionSpecification ) { this(); new OptionSpecTokenizer( optionSpecification ).configure( this ); } /** *

Tells the parser to recognize the given option.

* *

This method returns an instance of {@link OptionSpecBuilder} to allow the * formation of parser directives as sentences in a fluent interface language. * For example:

* *

     *   OptionParser parser = new OptionParser();
     *   parser.accepts( "c" ).withRequiredArg().ofType( Integer.class );
     * 
* *

If no methods are invoked on the returned {@link OptionSpecBuilder}, then the * parser treats the option as accepting no argument.

* * @param option the option to recognize * @return an object that can be used to flesh out more detail about the option * @throws OptionException if the option contains illegal characters * @throws NullPointerException if the option is {@code null} */ public OptionSpecBuilder accepts( String option ) { return acceptsAll( singletonList( option ) ); } /** *

Tells the parser to recognize the given option.

* * @see #accepts(String) * @param option the option to recognize * @param description a string that describes the purpose of the option. This is * used when generating help information about the parser. * @return an object that can be used to flesh out more detail about the option * @throws OptionException if the option contains illegal characters * @throws NullPointerException if the option is {@code null} */ public OptionSpecBuilder accepts( String option, String description ) { return acceptsAll( singletonList( option ), description ); } /** *

Tells the parser to recognize the given options, and treat them as * synonymous.

* * @see #accepts(String) * @param options the options to recognize and treat as synonymous * @return an object that can be used to flesh out more detail about the options * @throws OptionException if any of the options contain illegal characters * @throws NullPointerException if the option list or any of its elements are * {@code null} */ public OptionSpecBuilder acceptsAll( Collection options ) { return acceptsAll( options, "" ); } /** *

Tells the parser to recognize the given options, and treat them as * synonymous.

* * @see #acceptsAll(Collection) * @param options the options to recognize and treat as synonymous * @param description a string that describes the purpose of the option. This is * used when generating help information about the parser. * @return an object that can be used to flesh out more detail about the options * @throws OptionException if any of the options contain illegal characters * @throws NullPointerException if the option list or any of its elements are * {@code null} * @throws IllegalArgumentException if the option list is empty */ public OptionSpecBuilder acceptsAll( Collection options, String description ) { if ( options.isEmpty() ) throw new IllegalArgumentException( "need at least one option" ); ensureLegalOptions( options ); return new OptionSpecBuilder( this, options, description ); } /** *

Tells the parser whether or not to behave "POSIX-ly correct"-ly.

* * @param setting {@code true} if the parser should behave "POSIX-ly correct"-ly */ public void posixlyCorrect( boolean setting ) { posixlyCorrect = setting; state = moreOptions( setting ); } boolean posixlyCorrect() { return posixlyCorrect; } /** *

Tells the parser either to recognize or ignore "-W"-style long * options.

* * @param recognize {@code true} if the parser is to recognize the special style * of long options */ public void recognizeAlternativeLongOptions( boolean recognize ) { if ( recognize ) recognize( new AlternativeLongOptionSpec() ); else recognizedOptions.remove( String.valueOf( RESERVED_FOR_EXTENSIONS ) ); } void recognize( AbstractOptionSpec spec ) { recognizedOptions.putAll( spec.options(), spec ); } /** *

Writes information about the options this parser recognizes to the given output * sink.

* *

The output sink is flushed, but not closed.

* * @param sink the sink to write information to * @throws IOException if there is a problem writing to the sink * @throws NullPointerException if sink is {@code null} * @see #printHelpOn(Writer) */ public void printHelpOn( OutputStream sink ) throws IOException { printHelpOn( new OutputStreamWriter( sink ) ); } /** *

Writes information about the options this parser recognizes to the given output * sink.

* *

The output sink is flushed, but not closed.

* * @param sink the sink to write information to * @throws IOException if there is a problem writing to the sink * @throws NullPointerException if sink is {@code null} * @see #printHelpOn(OutputStream) */ public void printHelpOn( Writer sink ) throws IOException { sink.write( new HelpFormatter().format( recognizedOptions.toJavaUtilMap() ) ); sink.flush(); } /** *

Parses the given command line arguments according to the option specifications * given to the parser.

* * @param arguments arguments to parse * @return an {@link OptionSet} describing the parsed options, their arguments, and * any non-option arguments found * @throws OptionException if problems are detected while parsing * @throws NullPointerException if the argument list is {@code null} */ public OptionSet parse( String... arguments ) { ArgumentList argumentList = new ArgumentList( arguments ); OptionSet detected = new OptionSet( defaultValues() ); while ( argumentList.hasMore() ) state.handleArgument( this, argumentList, detected ); reset(); return detected; } void handleLongOptionToken( String candidate, ArgumentList arguments, OptionSet detected ) { KeyValuePair optionAndArgument = parseLongOptionWithArgument( candidate ); if ( !isRecognized( optionAndArgument.key ) ) throw unrecognizedOption( optionAndArgument.key ); AbstractOptionSpec optionSpec = specFor( optionAndArgument.key ); optionSpec.handleOption( this, arguments, detected, optionAndArgument.value ); } void handleShortOptionToken( String candidate, ArgumentList arguments, OptionSet detected ) { KeyValuePair optionAndArgument = parseShortOptionWithArgument( candidate ); if ( isRecognized( optionAndArgument.key ) ) { specFor( optionAndArgument.key ).handleOption( this, arguments, detected, optionAndArgument.value ); } else handleShortOptionCluster( candidate, arguments, detected ); } private void handleShortOptionCluster( String candidate, ArgumentList arguments, OptionSet detected ) { char[] options = extractShortOptionsFrom( candidate ); validateOptionCharacters( options ); AbstractOptionSpec optionSpec = specFor( options[ 0 ] ); if ( optionSpec.acceptsArguments() && options.length > 1 ) { String detectedArgument = String.valueOf( options, 1, options.length - 1 ); optionSpec.handleOption( this, arguments, detected, detectedArgument ); } else { for ( char each : options ) specFor( each ).handleOption( this, arguments, detected, null ); } } void noMoreOptions() { state = OptionParserState.noMoreOptions(); } boolean looksLikeAnOption( String argument ) { return isShortOptionToken( argument ) || isLongOptionToken( argument ); } private boolean isRecognized( String option ) { return recognizedOptions.contains( option ); } private AbstractOptionSpec specFor( char option ) { return specFor( String.valueOf( option ) ); } private AbstractOptionSpec specFor( String option ) { return recognizedOptions.get( option ); } private void reset() { state = moreOptions( posixlyCorrect ); } private static char[] extractShortOptionsFrom( String argument ) { char[] options = new char[ argument.length() - 1 ]; argument.getChars( 1, argument.length(), options, 0 ); return options; } private void validateOptionCharacters( char[] options ) { for ( int i = 0; i < options.length; ++i ) { String option = String.valueOf( options[ i ] ); if ( !isRecognized( option ) ) throw unrecognizedOption( option ); if ( specFor( option ).acceptsArguments() ) { if ( i > 0 ) throw illegalOptionCluster( option ); // remainder of chars are the argument to the option at char 0 return; } } } private static KeyValuePair parseLongOptionWithArgument( String argument ) { return KeyValuePair.valueOf( argument.substring( 2 ) ); } private static KeyValuePair parseShortOptionWithArgument( String argument ) { return KeyValuePair.valueOf( argument.substring( 1 ) ); } private Map> defaultValues() { Map> defaults = new HashMap>(); for ( Map.Entry> each : recognizedOptions.toJavaUtilMap().entrySet() ) defaults.put( each.getKey(), each.getValue().defaultValues() ); return defaults; } }