001: /*
002: Copyright 2004-2007 Paul R. Holser, Jr. All rights reserved.
003: Licensed under the Academic Free License version 3.0
004: */
005:
006: package joptsimple;
007:
008: import java.io.IOException;
009: import java.io.OutputStream;
010: import java.io.OutputStreamWriter;
011: import java.io.Writer;
012:
013: /**
014: * <p>Parses command line arguments according to GNU's conventions.</p>
015: *
016: * <p>This parser supports both <dfn>short options</dfn> and <dfn>long options</dfn>.</p>
017: *
018: * <ul>
019: * <li><dfn>Short options</dfn> begin with a single hyphen ("<code>-</code>") followed
020: * by a single letter or digit.</li>
021: *
022: * <li>Short options can accept single arguments. The argument can be made required or
023: * optional. The option can occur in:
024: * <ul>
025: * <li>the argument after the option, as in <code>-d /tmp</code></li>
026: * <li>right up against it, as in <code>-d/tmp</code></li>
027: * <li>right up against it separated by an equals sign (<code>"="</code>), as in
028: * <code>-d=/tmp</code>
029: * </ul>
030: * </li>
031: *
032: * <li>Short options can be clustered, so that <code>-abc</code> is treated as <code>-a
033: * -b -c</code>, if none of those options can accept arguments.</li>
034: *
035: * <li>An argument consisting only of two hyphens (<code>"--"</code>) signals that the
036: * remaining arguments are to be treated as non-options.</li>
037: *
038: * <li>An argument consisting only of a single hyphen is considered a non-option
039: * argument (though it can be an argument of an option). Many Unix programs treat
040: * single hyphens as stand-ins for the standard input or standard output streams.</li>
041: *
042: * <li><dfn>Long options</dfn> generally begin with two hyphens (<code>"--"</code>),
043: * followed by multiple letters and/or digits.</li>
044: *
045: * <li>You can abbreviate long options, so long as the abbreviation is unique.</li>
046: *
047: * <li>Long options can accept single arguments. The argument can be made required or
048: * optional. The option can occur in:
049: * <ul>
050: * <li>the argument after the option, as in <code>--directory /tmp</code></li>
051: * <li>right up against it separated by an equals sign (<code>"="</code>), as in
052: * <code>--directory=/tmp</code>
053: * </ul>
054: * </li>
055: *
056: * <li>You can use a single hyphen (<code>"-"</code>) instead of a double hyphen
057: * (<code>"--"</code>) for a long option.</li>
058: *
059: * <li>The option <code>-W</code> is reserved. If you tell the parser to {@linkplain
060: * #recognizeAlternativeLongOptions(boolean) recognize alternative long options}, then
061: * it will treat, for example, <code>-W foo=bar</code> as the long option
062: * <code>foo</code> with argument <code>bar</code>, as though you had written
063: * <code>--foo=bar</code>.</li>
064: *
065: * <li>You can specify <code>-W</code> as a valid short option, or use it as an
066: * abbreviation for a long option, but {@linkplain
067: * #recognizeAlternativeLongOptions(boolean) recognizing alternative long options} will
068: * always supersede this behavior.</li>
069: *
070: * <li>You can specify a given short or long option multiple times on a single command
071: * line. The parser collects any arguments specified for those options as a list.</li>
072: *
073: * <li>If the parser detects an option whose argument is optional, and the next argument
074: * "looks like" an option, that argument is not treated as the argument to the option,
075: * but as a potentially valid option. If, on the other hand, the optional argument is
076: * typed as a derivative of {@link Number}, then that argument is treated as the
077: * negative number argument of the option, even if the parser recognizes the
078: * corresponding numeric option. For example:
079: * <pre>
080: * parser.accepts( "a" ).withOptionalArg().ofType( Integer.class );
081: * parser.accepts( "1" );
082: * OptionSet options = parser.parse( new String[] { "-a", "-1" } );
083: * </pre>
084: * In this case, the option set contains <code>"a"</code> with argument <code>-1</code>,
085: * not both <code>"a"</code> and <code>"1"</code>. Swapping the elements in the
086: * <var>args</var> array gives the latter.</li>
087: * </ul>
088: *
089: * <p>There are two ways to tell the parser what options to recognize:</p>
090: *
091: * <ol>
092: * <li>A "domain-specific language"-style API for specifying options, available since
093: * version 2. Sentences in this domain-specific language begin with a call to
094: * {@link #accepts(String) accepts}; methods on the ensuing chain of objects describe
095: * whether the option passed to {@link #accepts(String) accepts} can take an argument,
096: * whether the argument is required or optional, and to what type arguments of the
097: * option should be converted.</li>
098: *
099: * <li>Since version 1, a more concise way of specifying short options to recognize has
100: * been to use the special {@linkplain #OptionParser(String) constructor}. Arguments
101: * of options specified in this manner will be of type {@link String}. Here are the
102: * rules for the format of the specification strings this constructor accepts:
103: *
104: * <ul>
105: * <li>Any letter or digit is treated as an option character.</li>
106: *
107: * <li>If an option character is followed by a single colon (<code>":"</code>),
108: * then the option requires an argument.</li>
109: *
110: * <li>If an option character is followed by two colons (<code>"::"</code>), then
111: * the option accepts an optional argument.</li>
112: *
113: * <li>Otherwise, the option character accepts no argument.</li>
114: *
115: * <li>If the option specification string begins with a plus sign
116: * (<code>"+"</code>), the parser will behave "POSIX-ly correct".</li>
117: *
118: * <li>If the option specification string contains the sequence <code>"W;"</code>
119: * (capital W followed by a semicolon), the parser will recognize the alternative
120: * form of long options.</li>
121: * </ul>
122: * </li>
123: * </ol>
124: *
125: * <p>By default, as with GNU <code>getopt()</code>, the parser allows intermixing of
126: * options and non-options. If, however, the parser has been created to be "POSIX-ly
127: * correct", then the first argument that does not look lexically like an option, and
128: * is not a required argument of a preceding option, signals the end of options.
129: * You can still bind optional arguments to their options using the abutting (for short
130: * options) or <code>=</code> syntax.</p>
131: *
132: * <p>
133: * Unlike GNU <code>getopt()</code>, this parser does not honor the
134: * environment variable <code>POSIXLY_CORRECT</code>. "POSIX-ly correct" parsers are
135: * configured by either:</p>
136: *
137: * <ol>
138: * <li>using the method {@link #setPosixlyCorrect(boolean)}, or</li>
139: *
140: * <li>using the {@linkplain #OptionParser(String) constructor} with an argument whose
141: * first character is a plus sign (<code>"+"</code>)</li>
142: * </ol>
143: *
144: * @since 1.0
145: * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a>
146: * @version $Id: OptionParser.java,v 1.42 2007/04/21 22:24:58 pholser Exp $
147: * @see <a href="http://www.gnu.org/software/libc/manual">The GNU C Library </a>
148: */
149: public class OptionParser {
150: private final AbbreviationMap recognizedOptions;
151: private OptionParserState state;
152: private boolean posixlyCorrect;
153:
154: /**
155: * Creates an option parser that initially recognizes no options, and does not exhibit
156: * "POSIX-ly correct" behavior.
157: *
158: * @since 1.0
159: */
160: public OptionParser() {
161: recognizedOptions = new AbbreviationMap();
162: state = new MoreOptionsParserState(false);
163: }
164:
165: /**
166: * Creates an option parser and configures it to recognize the short options specified
167: * in the given string.
168: * <p/>
169: * Arguments of options specified this way will be of type {@link String}.
170: *
171: * @since 1.0
172: * @param optionSpecification an option specification
173: * @throws NullPointerException if <code>optionSpecification</code> is
174: * <code>null</code>
175: */
176: public OptionParser(String optionSpecification) {
177: this ();
178:
179: new OptionSpecTokenizer(optionSpecification).configure(this );
180: }
181:
182: /**
183: * Tells the parser to recognize the given option.
184: * <p/>
185: * This method returns an instance of {@link OptionSpecBuilder} to allow the
186: * formation of parser directives as sentences in a domain-specific language.
187: * For example:
188: * <pre>
189: * OptionParser parser = new OptionParser();
190: * parser.<strong>accepts( "c" )</strong>.withRequiredArg().ofType( Integer.class );
191: * </pre>
192: * If no methods are invoked on the returned {@link OptionSpecBuilder}, then the
193: * parser treats the option as accepting no argument.
194: *
195: * @since 2.0
196: * @param option the option to recognize
197: * @return an object that can be used to flesh out more detail about the option
198: * @throws OptionException if the option contains illegal characters
199: * @throws NullPointerException if the option is <code>null</code>
200: */
201: public OptionSpecBuilder accepts(String option) {
202: return accepts(option, "");
203: }
204:
205: /**
206: * Tells the parser to recognize the given option.
207: *
208: * @since 2.1
209: * @see #accepts(String)
210: * @param option the option to recognize
211: * @param description a string that describes the purpose of the option. This is
212: * used when generating help information about the parser.
213: * @return an object that can be used to flesh out more detail about the option
214: * @throws OptionException if the option contains illegal characters
215: * @throws NullPointerException if the option is <code>null</code>
216: */
217: public OptionSpecBuilder accepts(String option, String description) {
218: ParserRules.checkLegalOption(option);
219:
220: return new OptionSpecBuilder(this , option, description);
221: }
222:
223: /**
224: * Tells the parser whether or not to behave "POSIX-ly correct"-ly.
225: *
226: * @since 1.0
227: * @param posixlyCorrect <code>true</code> if the parser should behave "POSIX-ly
228: * correct"-ly
229: */
230: public void setPosixlyCorrect(boolean posixlyCorrect) {
231: this .posixlyCorrect = posixlyCorrect;
232: state = new MoreOptionsParserState(posixlyCorrect);
233: }
234:
235: boolean isPosixlyCorrect() {
236: return posixlyCorrect;
237: }
238:
239: /**
240: * Tells the parser either to recognize or ignore <code>"-W"</code>-style long
241: * options.
242: *
243: * @since 1.0
244: * @param recognize <code>true</code> if the parser is to recognize the special style
245: * of long options
246: */
247: public void recognizeAlternativeLongOptions(boolean recognize) {
248: if (recognize)
249: recognize(new AlternativeLongOptionSpec());
250: else
251: recognizedOptions.remove(String
252: .valueOf(ParserRules.RESERVED_FOR_EXTENSIONS));
253: }
254:
255: void recognize(OptionSpec spec) {
256: recognizedOptions.put(spec.option(), spec);
257: }
258:
259: /**
260: * Writes information about the options this parser recognizes to the given output
261: * sink.
262: *
263: * @since 2.1
264: * @param sink the sink to write information to
265: * @throws IOException if there is a problem writing to the sink
266: * @throws NullPointerException if <code>sink</code> is <code>null</code>
267: */
268: public void printHelpOn(OutputStream sink) throws IOException {
269: OutputStreamWriter writer = null;
270:
271: try {
272: writer = new OutputStreamWriter(sink);
273: printHelpOn(writer);
274: } finally {
275: if (writer != null)
276: writer.close();
277: }
278: }
279:
280: /**
281: * Writes information about the options this parser recognizes to the given output
282: * sink.
283: *
284: * @since 2.1
285: * @param sink the sink to write information to
286: * @throws IOException if there is a problem writing to the sink
287: * @throws NullPointerException if <code>sink</code> is <code>null</code>
288: */
289: public void printHelpOn(Writer sink) throws IOException {
290: sink.write(new OptionParserHelpFormatter()
291: .format(recognizedOptions.toJavaUtilMap()));
292: }
293:
294: /**
295: * Parses the given command line arguments according to the option specifications
296: * given to the parser.
297: *
298: * @since 1.0
299: * @param arguments arguments to parse
300: * @return an {@link OptionSet} describing the parsed options, their arguments, and
301: * any non-option arguments found
302: * @throws OptionException if problems are detected while parsing
303: * @throws NullPointerException if the argument list is <code>null</code>
304: */
305: public OptionSet parse(String[] arguments) {
306: ArgumentList argumentList = new ArgumentList(arguments);
307: OptionSet detected = new OptionSet();
308:
309: while (argumentList.hasMore())
310: state.handleArgument(this , argumentList, detected);
311:
312: reset();
313: return detected;
314: }
315:
316: void handleLongOptionToken(String candidate,
317: ArgumentList arguments, OptionSet detected) {
318:
319: KeyValuePair optionAndArgument = parseLongOptionWithArgument(candidate);
320:
321: if (!isRecognized(optionAndArgument.key))
322: throw new UnrecognizedOptionException(optionAndArgument.key);
323:
324: OptionSpec optionSpec = specFor(optionAndArgument.key);
325: optionSpec.handleOption(this , arguments, detected,
326: optionAndArgument.value);
327: }
328:
329: void handleShortOptionToken(String candidate,
330: ArgumentList arguments, OptionSet detected) {
331:
332: KeyValuePair optionAndArgument = parseShortOptionWithArgument(candidate);
333:
334: if (isRecognized(optionAndArgument.key)) {
335: specFor(optionAndArgument.key).handleOption(this ,
336: arguments, detected, optionAndArgument.value);
337: } else
338: handleShortOptionCluster(candidate, arguments, detected);
339: }
340:
341: private void handleShortOptionCluster(String candidate,
342: ArgumentList arguments, OptionSet detected) {
343:
344: char[] options = extractShortOptionsFrom(candidate);
345: validateOptionCharacters(options);
346:
347: OptionSpec optionSpec = specFor(options[0]);
348:
349: if (optionSpec.acceptsArguments() && options.length > 1) {
350: String detectedArgument = String.valueOf(options, 1,
351: options.length - 1);
352: optionSpec.handleOption(this , arguments, detected,
353: detectedArgument);
354: } else {
355: for (int i = 0; i < options.length; ++i)
356: specFor(options[i]).handleOption(this , arguments,
357: detected, null);
358: }
359: }
360:
361: void noMoreOptions() {
362: state = new NoMoreOptionsParserState();
363: }
364:
365: boolean looksLikeAnOption(String argument) {
366: return ParserRules.isShortOptionToken(argument)
367: || ParserRules.isLongOptionToken(argument);
368: }
369:
370: private boolean isRecognized(String option) {
371: return recognizedOptions.contains(option);
372: }
373:
374: private OptionSpec specFor(char option) {
375: return specFor(String.valueOf(option));
376: }
377:
378: private OptionSpec specFor(String option) {
379: return (OptionSpec) recognizedOptions.get(option);
380: }
381:
382: private void reset() {
383: state = new MoreOptionsParserState(posixlyCorrect);
384: }
385:
386: private static char[] extractShortOptionsFrom(String argument) {
387: char[] options = new char[argument.length() - 1];
388: argument.getChars(1, argument.length(), options, 0);
389:
390: return options;
391: }
392:
393: private void validateOptionCharacters(char[] options) {
394: for (int i = 0; i < options.length; ++i) {
395: String option = String.valueOf(options[i]);
396:
397: if (!isRecognized(option))
398: throw new UnrecognizedOptionException(option);
399:
400: if (specFor(option).acceptsArguments()) {
401: if (i > 0)
402: throw new IllegalOptionClusterException(option);
403:
404: // remainder of chars are the argument to the option at char 0
405: return;
406: }
407: }
408: }
409:
410: private static KeyValuePair parseLongOptionWithArgument(
411: String argument) {
412: return KeyValuePair.parse(argument.substring(2));
413: }
414:
415: private static KeyValuePair parseShortOptionWithArgument(
416: String argument) {
417: return KeyValuePair.parse(argument.substring(1));
418: }
419: }
|