Coverage Report - joptsimple.OptionParser
 
Classes in this File Line Coverage Branch Coverage Complexity
OptionParser
100%
80/80
96%
27/28
1.556
 
 1  
 /*
 2  
  Copyright 2004-2008 Paul R. Holser, Jr.  All rights reserved.
 3  
  Licensed under the Academic Free License version 3.0
 4  
  */
 5  
 
 6  
 package joptsimple;
 7  
 
 8  
 import java.io.IOException;
 9  
 import java.io.OutputStream;
 10  
 import java.io.OutputStreamWriter;
 11  
 import java.io.Writer;
 12  
 import java.util.Collections;
 13  
 import java.util.List;
 14  
 
 15  
 import joptsimple.util.KeyValuePair;
 16  
 
 17  
 /**
 18  
  * <p>Parses command line arguments according to GNU's conventions.</p>
 19  
  *
 20  
  * <p>This parser supports short options and long options.</p>
 21  
  *
 22  
  * <ul>
 23  
  *   <li><dfn>Short options</dfn> begin with a single hyphen ("<kbd>-</kbd>") followed
 24  
  *   by a single letter or digit, or question mark ("<kbd>?</kbd>"), or dot
 25  
  *   ("<kbd>.</kbd>").</li>
 26  
  *
 27  
  *   <li>Short options can accept single arguments.  The argument can be made required or
 28  
  *   optional.  The option's argument can occur:
 29  
  *     <ul>
 30  
  *       <li>in the slot after the option, as in <kbd>-d /tmp</kbd></li>
 31  
  *       <li>right up against the option, as in <kbd>-d/tmp</kbd></li>
 32  
  *       <li>right up against the option separated by an equals sign (<kbd>"="</kbd>),
 33  
  *       as in <kbd>-d=/tmp</kbd></li>
 34  
  *     </ul>
 35  
  *   To specify <var>n</var> arguments for an option, specify the option <var>n</var>
 36  
  *   times, once for each argument, as in <kbd>-d /tmp -d /var -d /opt</kbd>; or, when
 37  
  *   using the {@linkplain ArgumentAcceptingOptionSpec#withValuesSeparatedBy(char)
 38  
  *   "separated values"} clause of the "fluent interface" (see below), give multiple
 39  
  *   values separated by a given character as a single argument to the option.
 40  
  *   </li>
 41  
  *
 42  
  *   <li>Short options can be clustered -- so that <kbd>-abc</kbd> is treated as
 43  
  *   <kbd>-a -b -c</kbd> -- if none of those options can accept arguments.</li>
 44  
  *
 45  
  *   <li>An argument consisting only of two hyphens (<kbd>"--"</kbd>) signals that the
 46  
  *   remaining arguments are to be treated as non-options.</li>
 47  
  *
 48  
  *   <li>An argument consisting only of a single hyphen is considered a non-option
 49  
  *   argument (though it can be an argument of an option).  Many Unix programs treat
 50  
  *   single hyphens as stand-ins for the standard input or standard output streams.</li>
 51  
  *
 52  
  *   <li><dfn>Long options</dfn> begin with two hyphens (<kbd>"--"</kbd>), followed
 53  
  *   by multiple letters, digits, hyphens, question marks, or dots.  A hyphen cannot be
 54  
  *   the first character of a long option specification when configuring the parser.</li>
 55  
  *
 56  
  *   <li>You can abbreviate long options, so long as the abbreviation is unique.</li>
 57  
  *
 58  
  *   <li>Long options can accept single arguments.  The argument can be made required or
 59  
  *   optional.  The option's argument can occur:
 60  
  *     <ul>
 61  
  *       <li>in the slot after the option, as in <kbd>--directory /tmp</kbd></li>
 62  
  *       <li>right up against the option separated by an equals sign (<kbd>"="</kbd>),
 63  
  *       as in <kbd>--directory=/tmp</kbd>
 64  
  *     </ul>
 65  
  *   Specify multiple arguments for a long option in the same manner as for short options
 66  
  *   (see above).
 67  
  *   </li>
 68  
  *
 69  
  *   <li>You can use a single hyphen (<kbd>"-"</kbd>) instead of a double hyphen
 70  
  *   (<kbd>"--"</kbd>) for a long option.</li>
 71  
  *
 72  
  *   <li>The option <kbd>-W</kbd> is reserved.  If you tell the parser to {@linkplain
 73  
  *   #recognizeAlternativeLongOptions(boolean) recognize alternative long options}, then
 74  
  *   it will treat, for example, <kbd>-W foo=bar</kbd> as the long option
 75  
  *   <kbd>foo</kbd> with argument <kbd>bar</kbd>, as though you had written
 76  
  *   <kbd>--foo=bar</kbd>.</li>
 77  
  *
 78  
  *   <li>You can specify <kbd>-W</kbd> as a valid short option, or use it as an
 79  
  *   abbreviation for a long option, but {@linkplain
 80  
  *   #recognizeAlternativeLongOptions(boolean) recognizing alternative long options} will
 81  
  *   always supersede this behavior.</li>
 82  
  *
 83  
  *   <li>You can specify a given short or long option multiple times on a single command
 84  
  *   line.  The parser collects any arguments specified for those options as a list.</li>
 85  
  *
 86  
  *   <li>If the parser detects an option whose argument is optional, and the next argument
 87  
  *   "looks like" an option, that argument is not treated as the argument to the option,
 88  
  *   but as a potentially valid option.  If, on the other hand, the optional argument is
 89  
  *   typed as a derivative of {@link Number}, then that argument is treated as the
 90  
  *   negative number argument of the option, even if the parser recognizes the
 91  
  *   corresponding numeric option.  For example:
 92  
  *   <pre>
 93  
  *     <code>
 94  
  *     parser.accepts( "a" ).withOptionalArg().ofType( Integer.class );
 95  
  *     parser.accepts( "2" );
 96  
  *     OptionSet options = parser.parse( new String[] { "-a", "-2" } );
 97  
  *     </code>
 98  
  *   </pre>
 99  
  *   In this case, the option set contains <kbd>"a"</kbd> with argument <kbd>-2</kbd>,
 100  
  *   not both <kbd>"a"</kbd> and <kbd>"2"</kbd>.  Swapping the elements in the
 101  
  *   <var>args</var> array gives the latter.</li>
 102  
  * </ul>
 103  
  *
 104  
  * <p>There are two ways to tell the parser what options to recognize:</p>
 105  
  *
 106  
  * <ol>
 107  
  *   <li>A "fluent interface"-style API for specifying options, available since
 108  
  *   version 2.  Sentences in this internal domain-specific language begin with a call to
 109  
  *   {@link #accepts(String) accepts} or {@link #acceptsAll(List) acceptsAll} methods on
 110  
  *   the ensuing chain of objects describe whether the options can take an argument,
 111  
  *   whether the argument is required or optional, and to what type arguments of the
 112  
  *   options should be converted.</li>
 113  
  *
 114  
  *   <li>Since version 1, a more concise way of specifying short options has been to use
 115  
  *   the special {@linkplain #OptionParser(String) constructor}.  Arguments of options
 116  
  *   specified in this manner will be of type {@link String}.  Here are the rules for the
 117  
  *   format of the specification strings this constructor accepts:
 118  
  *
 119  
  *     <ul>
 120  
  *         <li>Any letter or digit is treated as an option character.</li>
 121  
  *
 122  
  *         <li>If an option character is followed by a single colon (<kbd>":"</kbd>),
 123  
  *         then the option requires an argument.</li>
 124  
  *
 125  
  *         <li>If an option character is followed by two colons (<kbd>"::"</kbd>), then
 126  
  *         the option accepts an optional argument.</li>
 127  
  *
 128  
  *         <li>Otherwise, the option character accepts no argument.</li>
 129  
  *
 130  
  *         <li>If the option specification string begins with a plus sign
 131  
  *         (<kbd>"+"</kbd>), the parser will behave "POSIX-ly correct".</li>
 132  
  *
 133  
  *         <li>If the option specification string contains the sequence <kbd>"W;"</kbd>
 134  
  *         (capital W followed by a semicolon), the parser will recognize the alternative
 135  
  *         form of long options.</li>
 136  
  *     </ul>
 137  
  *   </li>
 138  
  * </ol>
 139  
  *
 140  
  * <p>Each of the options in a list of options given to {@link #acceptsAll(List)
 141  
  * acceptsAll} is treated as a synonym of the others.  For example:
 142  
  *   <pre>
 143  
  *     <code>
 144  
  *     parser.acceptsAll(
 145  
  *         Arrays.asList( new String[] { "w", "interactive", "confirmation" } ) );
 146  
  *     OptionSet options = parser.parse( new String[] { "-w" } );
 147  
  *     </code>
 148  
  *   </pre>
 149  
  * In this case, <code>options.{@link OptionSet#has(String) has}</code> would answer
 150  
  * <code>true</code> when given arguments <kbd>"w"</kbd>, <kbd>"interactive"</kbd>, and
 151  
  * <kbd>"confirmation"</kbd>. The {@link OptionSet} would give the same responses to
 152  
  * these arguments for its other methods as well.</p>
 153  
  *
 154  
  * <p>By default, as with GNU <code>getopt()</code>, the parser allows intermixing of
 155  
  * options and non-options.  If, however, the parser has been created to be "POSIX-ly
 156  
  * correct", then the first argument that does not look lexically like an option, and
 157  
  * is not a required argument of a preceding option, signals the end of options.
 158  
  * You can still bind optional arguments to their options using the abutting (for short
 159  
  * options) or <kbd>=</kbd> syntax.</p>
 160  
  *
 161  
  * <p>Unlike GNU <code>getopt()</code>, this parser does not honor the environment
 162  
  * variable <code>POSIXLY_CORRECT</code>.  "POSIX-ly correct" parsers are configured by
 163  
  * either:</p>
 164  
  *
 165  
  * <ol>
 166  
  *   <li>using the method {@link #posixlyCorrect(boolean)}, or</li>
 167  
  *
 168  
  *   <li>using the {@linkplain #OptionParser(String) constructor} with an argument whose
 169  
  *   first character is a plus sign (<kbd>"+"</kbd>)</li>
 170  
  * </ol>
 171  
  *
 172  
  * @since 1.0
 173  
  * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a>
 174  
  * @version $Id: OptionParser.java,v 1.10 2008/05/23 15:02:43 pholser Exp $
 175  
  * @see <a href="http://www.gnu.org/software/libc/manual">The GNU C Library </a>
 176  
  */
 177  
 public class OptionParser {
 178  336
     private final AbbreviationMap recognizedOptions = new AbbreviationMap();
 179  336
     private OptionParserState state = OptionParserState.moreOptions( false );
 180  
     private boolean posixlyCorrect;
 181  
 
 182  
     /**
 183  
      * <p>Creates an option parser that initially recognizes no options, and does not
 184  
      * exhibit "POSIX-ly correct" behavior.</p>
 185  
      *
 186  
      * @since 1.0
 187  
      */
 188  304
     public OptionParser() {
 189  304
     }
 190  
 
 191  
     /**
 192  
      * <p>Creates an option parser and configures it to recognize the short options
 193  
      * specified in the given string.</p>
 194  
      *
 195  
      * <p>Arguments of options specified this way will be of type {@link String}.</p>
 196  
      *
 197  
      * @since 1.0
 198  
      * @param optionSpecification an option specification
 199  
      * @throws NullPointerException if <var>optionSpecification</var> is
 200  
      * <code>null</code>
 201  
      * @throws OptionException if the option specification contains illegal characters
 202  
      * or otherwise cannot be recognized
 203  
      */
 204  32
     public OptionParser( String optionSpecification ) {
 205  32
         new OptionSpecTokenizer( optionSpecification ).configure( this );
 206  28
     }
 207  
 
 208  
     /**
 209  
      * <p>Tells the parser to recognize the given option.</p>
 210  
      *
 211  
      * <p>This method returns an instance of {@link OptionSpecBuilder} to allow the
 212  
      * formation of parser directives as sentences in an internal domain-specific
 213  
      * language.  For example:</p>
 214  
      *
 215  
      * <pre><code>
 216  
      *   OptionParser parser = new OptionParser();
 217  
      *   parser.<strong>accepts( "c" )</strong>.withRequiredArg().ofType( Integer.class );
 218  
      * </code></pre>
 219  
      *
 220  
      * <p>If no methods are invoked on the returned {@link OptionSpecBuilder}, then the
 221  
      * parser treats the option as accepting no argument.</p>
 222  
      *
 223  
      * @since 2.0
 224  
      * @param option the option to recognize
 225  
      * @return an object that can be used to flesh out more detail about the option
 226  
      * @throws OptionException if the option contains illegal characters
 227  
      * @throws NullPointerException if the option is <code>null</code>
 228  
      */
 229  
     public OptionSpecBuilder accepts( String option ) {
 230  440
         return acceptsAll( Collections.singletonList( option ) );
 231  
     }
 232  
 
 233  
     /**
 234  
      * <p>Tells the parser to recognize the given option.</p>
 235  
      *
 236  
      * @since 2.1
 237  
      * @see #accepts(String)
 238  
      * @param option the option to recognize
 239  
      * @param description a string that describes the purpose of the option.  This is
 240  
      * used when generating help information about the parser.
 241  
      * @return an object that can be used to flesh out more detail about the option
 242  
      * @throws OptionException if the option contains illegal characters
 243  
      * @throws NullPointerException if the option is <code>null</code>
 244  
      */
 245  
     public OptionSpecBuilder accepts( String option, String description ) {
 246  16
         return acceptsAll( Collections.singletonList( option ), description );
 247  
     }
 248  
 
 249  
     /**
 250  
      * <p>Tells the parser to recognize the given options, and treat them as
 251  
      * synonymous.</p>
 252  
      *
 253  
      * @since 2.4
 254  
      * @see #accepts(String)
 255  
      * @param options the options to recognize and treat as synonymous
 256  
      * @return an object that can be used to flesh out more detail about the options
 257  
      * @throws OptionException if any of the options contain illegal characters
 258  
      * @throws NullPointerException if the option list or any of its elements are
 259  
      * <code>null</code>
 260  
      * @throws ClassCastException if any of the items in the option list is not a
 261  
      * {@link String}
 262  
      */
 263  
     public OptionSpecBuilder acceptsAll( List options ) {
 264  474
         return acceptsAll( options, "" );
 265  
     }
 266  
 
 267  
     /**
 268  
      * <p>Tells the parser to recognize the given options, and treat them as
 269  
      * synonymous.</p>
 270  
      *
 271  
      * @since 2.4
 272  
      * @see #acceptsAll(List)
 273  
      * @param options the options to recognize and treat as synonymous
 274  
      * @param description a string that describes the purpose of the option.  This is
 275  
      * used when generating help information about the parser.
 276  
      * @return an object that can be used to flesh out more detail about the options
 277  
      * @throws OptionException if any of the options contain illegal characters
 278  
      * @throws NullPointerException if the option list or any of its elements are
 279  
      * <code>null</code>
 280  
      * @throws IllegalArgumentException if the option list is empty
 281  
      * @throws ClassCastException if any of the items in the option list is not a
 282  
      * {@link String}
 283  
      */
 284  
     public OptionSpecBuilder acceptsAll( List options, String description ) {
 285  566
         if ( options.isEmpty() )
 286  2
             throw new IllegalArgumentException( "acceptsAll needs at least one option" );
 287  
 
 288  560
         ParserRules.checkLegalOptions( options );
 289  
 
 290  540
         return new OptionSpecBuilder( this, options, description );
 291  
     }
 292  
 
 293  
     /**
 294  
      * <p>Tells the parser whether or not to behave "POSIX-ly correct"-ly.</p>
 295  
      *
 296  
      * @since 1.0
 297  
      * @param setting <code>true</code> if the parser should behave "POSIX-ly
 298  
      * correct"-ly
 299  
      * @deprecated Use {@link #posixlyCorrect(boolean)} instead.
 300  
      */
 301  
     public void setPosixlyCorrect( boolean setting ) {
 302  18
         this.posixlyCorrect = setting;
 303  18
         state = OptionParserState.moreOptions( setting );
 304  18
     }
 305  
 
 306  
     /**
 307  
      * <p>Tells the parser whether or not to behave "POSIX-ly correct"-ly.</p>
 308  
      *
 309  
      * @since 2.4
 310  
      * @param setting <code>true</code> if the parser should behave "POSIX-ly
 311  
      * correct"-ly
 312  
      */
 313  
     public void posixlyCorrect( boolean setting ) {
 314  18
         setPosixlyCorrect( setting );
 315  18
     }
 316  
 
 317  
     boolean posixlyCorrect() {
 318  52
         return posixlyCorrect;
 319  
     }
 320  
 
 321  
     /**
 322  
      * <p>Tells the parser either to recognize or ignore <kbd>"-W"</kbd>-style long
 323  
      * options.</p>
 324  
      *
 325  
      * @since 1.0
 326  
      * @param recognize <code>true</code> if the parser is to recognize the special style
 327  
      * of long options
 328  
      */
 329  
     public void recognizeAlternativeLongOptions( boolean recognize ) {
 330  22
         if ( recognize )
 331  20
             recognize( new AlternativeLongOptionSpec() );
 332  
         else
 333  2
             recognizedOptions.remove(
 334  
                 String.valueOf( ParserRules.RESERVED_FOR_EXTENSIONS ) );
 335  22
     }
 336  
 
 337  
     void recognize( OptionSpec spec ) {
 338  886
         recognizedOptions.putAll( spec.options(), spec );
 339  886
     }
 340  
 
 341  
     /**
 342  
      * <p>Writes information about the options this parser recognizes to the given output
 343  
      * sink.</p>
 344  
      *
 345  
      * <p>The output sink is flushed, but not closed.</p>
 346  
      *
 347  
      * @since 2.1
 348  
      * @param sink the sink to write information to
 349  
      * @throws IOException if there is a problem writing to the sink
 350  
      * @throws NullPointerException if <var>sink</var> is <code>null</code>
 351  
      * @see #printHelpOn(java.io.Writer)
 352  
      */
 353  
     public void printHelpOn( OutputStream sink ) throws IOException {
 354  4
         printHelpOn( new OutputStreamWriter( sink ) );
 355  4
     }
 356  
 
 357  
     /**
 358  
      * <p>Writes information about the options this parser recognizes to the given output
 359  
      * sink.</p>
 360  
      *
 361  
      * <p>The output sink is flushed, but not closed.</p>
 362  
      *
 363  
      * @since 2.1
 364  
      * @param sink the sink to write information to
 365  
      * @throws IOException if there is a problem writing to the sink
 366  
      * @throws NullPointerException if <var>sink</var> is <code>null</code>
 367  
      * @see #printHelpOn(java.io.OutputStream)
 368  
      */
 369  
     public void printHelpOn( Writer sink ) throws IOException {
 370  42
         sink.write(
 371  
             new OptionParserHelpFormatter().format( recognizedOptions.toJavaUtilMap() ) );
 372  42
         sink.flush();
 373  42
     }
 374  
 
 375  
     /**
 376  
      * <p>Parses the given command line arguments according to the option specifications
 377  
      * given to the parser.</p>
 378  
      *
 379  
      * @since 1.0
 380  
      * @param arguments arguments to parse
 381  
      * @return an {@link OptionSet} describing the parsed options, their arguments, and
 382  
      * any non-option arguments found
 383  
      * @throws OptionException if problems are detected while parsing
 384  
      * @throws NullPointerException if the argument list is <code>null</code>
 385  
      */
 386  
     public OptionSet parse( String[] arguments ) {
 387  262
         ArgumentList argumentList = new ArgumentList( arguments );
 388  260
         OptionSet detected = new OptionSet();
 389  
 
 390  822
         while ( argumentList.hasMore() )
 391  594
             state.handleArgument( this, argumentList, detected );
 392  
 
 393  228
         reset();
 394  228
         return detected;
 395  
     }
 396  
 
 397  
     void handleLongOptionToken( String candidate, ArgumentList arguments,
 398  
         OptionSet detected ) {
 399  
 
 400  92
         KeyValuePair optionAndArgument = parseLongOptionWithArgument( candidate );
 401  
 
 402  92
         if ( !isRecognized( optionAndArgument.key ) )
 403  12
             throw OptionException.unrecognizedOption( optionAndArgument.key );
 404  
 
 405  80
         OptionSpec optionSpec = specFor( optionAndArgument.key );
 406  80
         optionSpec.handleOption( this, arguments, detected, optionAndArgument.value );
 407  78
     }
 408  
 
 409  
     void handleShortOptionToken( String candidate, ArgumentList arguments,
 410  
         OptionSet detected ) {
 411  
 
 412  314
         KeyValuePair optionAndArgument = parseShortOptionWithArgument( candidate );
 413  
 
 414  314
         if ( isRecognized( optionAndArgument.key ) ) {
 415  254
             specFor( optionAndArgument.key ).handleOption(
 416  
                 this, arguments, detected, optionAndArgument.value );
 417  
         }
 418  
         else
 419  60
             handleShortOptionCluster( candidate, arguments, detected );
 420  296
     }
 421  
 
 422  
     private void handleShortOptionCluster( String candidate, ArgumentList arguments,
 423  
         OptionSet detected ) {
 424  
 
 425  60
         char[] options = extractShortOptionsFrom( candidate );
 426  60
         validateOptionCharacters( options );
 427  
 
 428  50
         OptionSpec optionSpec = specFor( options[ 0 ] );
 429  
 
 430  50
         if ( optionSpec.acceptsArguments() && options.length > 1 ) {
 431  46
             String detectedArgument = String.valueOf( options, 1, options.length - 1 );
 432  46
             optionSpec.handleOption( this, arguments, detected, detectedArgument );
 433  46
         }
 434  
         else {
 435  14
             for ( int i = 0; i < options.length; ++i )
 436  10
                 specFor( options[ i ] ).handleOption( this, arguments, detected, null );
 437  
         }
 438  50
     }
 439  
 
 440  
     void noMoreOptions() {
 441  26
         state = OptionParserState.noMoreOptions();
 442  26
     }
 443  
 
 444  
     boolean looksLikeAnOption( String argument ) {
 445  76
         return ParserRules.isShortOptionToken( argument )
 446  
             || ParserRules.isLongOptionToken( argument );
 447  
     }
 448  
 
 449  
     private boolean isRecognized( String option ) {
 450  478
         return recognizedOptions.contains( option );
 451  
     }
 452  
 
 453  
     private OptionSpec specFor( char option ) {
 454  60
         return specFor( String.valueOf( option ) );
 455  
     }
 456  
 
 457  
     private OptionSpec specFor( String option ) {
 458  458
         return (OptionSpec) recognizedOptions.get( option );
 459  
     }
 460  
 
 461  
     private void reset() {
 462  228
         state = OptionParserState.moreOptions( posixlyCorrect );
 463  228
     }
 464  
 
 465  
     private static char[] extractShortOptionsFrom( String argument ) {
 466  60
         char[] options = new char[ argument.length() - 1 ];
 467  60
         argument.getChars( 1, argument.length(), options, 0 );
 468  
 
 469  60
         return options;
 470  
     }
 471  
 
 472  
     private void validateOptionCharacters( char[] options ) {
 473  76
         for ( int i = 0; i < options.length; ++i ) {
 474  72
             String option = String.valueOf( options[ i ] );
 475  
 
 476  72
             if ( !isRecognized( option ) )
 477  8
                 throw OptionException.unrecognizedOption( option );
 478  
 
 479  64
             if ( specFor( option ).acceptsArguments() ) {
 480  48
                 if ( i > 0 )
 481  2
                     throw OptionException.illegalOptionCluster( option );
 482  
 
 483  
                 // remainder of chars are the argument to the option at char 0
 484  46
                 return;
 485  
             }
 486  
         }
 487  4
     }
 488  
 
 489  
     private static KeyValuePair parseLongOptionWithArgument( String argument ) {
 490  92
         return KeyValuePair.valueOf( argument.substring( 2 ) );
 491  
     }
 492  
 
 493  
     private static KeyValuePair parseShortOptionWithArgument( String argument ) {
 494  314
         return KeyValuePair.valueOf( argument.substring( 1 ) );
 495  
     }
 496  
 }