com.darwinsys.lang.GetOpt Maven / Gradle / Ivy
Show all versions of darwinsys-api Show documentation
package com.darwinsys.lang;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/** GetOpt implements UNIX-style (single-character) command line argument
* parsing. Originally patterned after (but not using code from) the UNIX
* getopt(3) program, this has been redesigned to be more Java-friendly.
* As a result, there are two ways of using it, referred to as
* "the Unix way" and "the Java way".
* - Original (UNIX) model:
*
* GetOpt go = new GetOpt("hno:");
* boolean numeric_option = false;
* String outFileName = "(standard output)";
* char c;
* while ((c = go.getopt(args)) != GetOpt.DONE) {
* switch(c) {
* case 'h':
* doHelp(0);
* break;
* case 'n':
* numeric_option = true;
* break;
* case 'o':
* outFileName = go.optarg();
* break;
* default:
* System.err.println("Unknown option character " + c);
* doHelp(1);
* }
* }
* System.out.print("Options: ");
* System.out.print("Numeric: " + numeric_option + ' ');
* System.out.print("Output: " + outFileName + "; ");
* System.out.print("Inputs: ");
* if (go.getOptInd() == args.length) {
* doFile("(standard input)");
* } else for (int i = go.getOptInd(); i < args.length; i++) {
* doFile(args[i]);
* }
*
* - Newer (Java) model, which allows long-named options (preceded with
* a single dash e.g., "-output-file /tmp/j":
*
* boolean numeric_option = false;
* boolean errs = false;
* String outputFileName = null;
*
* GetOptDesc options[] = {
* new GetOptDesc('n', "numeric", false),
* new GetOptDesc('o', "output-file", true),
* };
* GetOpt parser = new GetOpt(options);
* Map<String,String> optionsFound = parser.parseArguments(argv);
* Iterator<String> it = optionsFound.keySet().iterator();
* while (it.hasNext()) {
* String key = (String)it.next();
* switch (key.charAt(0)) {
* case 'n':
* numeric_option = true;
* break;
* case 'o':
* outputFileName = optionsFound.get(key);
* break;
* case '?':
* errs = true;
* break;
* default:
* throw new IllegalStateException(
* "Unexpected option character: " + key);
* }
* }
* if (errs) {
* System.err.println("Usage: GetOptDemo [-n][-o file][file...]");
* }
* System.out.print("Options: ");
* System.out.print("Numeric: " + numeric_option + ' ');
* System.out.print("Output: " + outputFileName + "; ");
* System.out.print("Input files: ");
* List<Files> files = parser.getFilenameList();
* for (String file : files) {
* System.out.print(file);
* System.out.print(' ');
* }
* System.out.println();
* }
*
*
*
* This class is not threadsafe; it is expected to be used only from main().
*
* For another way of dealing with command lines, see the
* Jakarta Commons
* Command Line Interface.
* @author Ian F. Darwin, http://www.darwinsys.com/
*/
// tag::main[]
// package com.darwinsys.lang;
public class GetOpt {
/** The List of File Names found after args */
protected List fileNameArguments;
/** The set of characters to look for */
protected final GetOptDesc[] options;
/** Where we are in the options */
protected int optind = 0;
/** Public constant for "no more options" */
public static final int DONE = 0;
/** Internal flag - whether we are done all the options */
protected boolean done = false;
/** The current option argument. */
protected String optarg;
/** A logger for debugging */
Logger logger = Logger.getLogger(getClass().getName());
/** Retrieve the current option argument; UNIX variant spelling.
* @return The current option's argument.
*/
public String optarg() {
return optarg;
}
/** Retrieve the current option argument; Java variant spelling.
* @return The current option's argument.
*/
public String optArg() {
return optarg;
}
/** Construct a GetOpt parser, given the option specifications
* in an array of GetOptDesc objects. This is the preferred constructor.
* @param opt The option spec
*/
public GetOpt(final GetOptDesc[] opt) {
this.options = opt.clone();
}
/** Construct a GetOpt parser, storing the set of option characters.
* This is a legacy constructor for backward compatibility.
* That said, it is easier to use if you don't need long-name options,
* so it has not been and will not be marked "deprecated".
* @param opt The option spec
*/
public GetOpt(final String opt) {
if (opt == null) {
throw new IllegalArgumentException("Options may not be null");
}
if (opt.charAt(0) == ':') {
throw new IllegalArgumentException(
"Options pattern incorrect, may not begin with ':'");
}
// Pass One: just count the option letters in the pattern
int n = 0;
for (char ch : opt.toCharArray()) {
if (ch != ':')
++n;
}
if (n == 0) {
throw new IllegalArgumentException(
"No option letters found in " + opt);
}
// Pass Two: construct an array of GetOptDesc objects.
options = new GetOptDesc[n];
for (int i = 0, ix = 0; i
* This parses the options, returns a Map whose keys are the found options.
* Normally followed by a call to getFilenameList().
*
Side effect: sets "fileNameArguments" to a new List
* @param argv The input arguments
* @return a Map whose keys are Strings of length 1 (containing the char
* from the option that was matched) and whose value is a String
* containing the value, or null for a non-option argument.
*/
public Map parseArguments(String[] argv) {
Map optionsValueMap = new HashMap();
fileNameArguments = new ArrayList();
for (int i = 0; i < argv.length; i++) { // Cannot use foreach, need i
logger.info("parseArg: i=" + i + ": arg " + argv[i]);
char c = getopt(argv); // sets global "optarg"
if (c == DONE) {
fileNameArguments.add(argv[i]);
} else {
optionsValueMap.put(Character.toString(c), optarg);
// If this arg takes an option, must arrange here to skip it.
if (optarg != null) {
i++;
}
}
}
return optionsValueMap;
}
/** Get the list of filename-like arguments after options;
* only for use if you called parseArguments.
* @return The list of filename-like arguments
*/
public List getFilenameList() {
if (fileNameArguments == null) {
throw new IllegalArgumentException(
"Illegal call to getFilenameList() before parseOptions()");
}
return fileNameArguments;
}
/** The true heart of getopt, whether used old way or new way:
* returns one argument; call repeatedly until it returns DONE.
* Side-effect: sets globals optarg, optind
* @param argv The input arguments
* @return One option character each time called, or DONE after last.
*/
public char getopt(String argv[]) {
logger.info("optind=" + optind + ", argv.length="+argv.length);
if (optind >= (argv.length) || !argv[optind].startsWith("-")) {
done = true;
}
// If we are finished (either now OR from before), bail.
// Do not collapse this into the "if" above
if (done) {
return DONE;
}
optarg = null;
// XXX TODO - two-pass, 1st check long args, 2nd check for
// char, to allow advanced usage like "-no outfile" == "-n -o outfile".
// Pick off next command line argument, if it starts "-",
// then look it up in the list of valid args.
String thisArg = argv[optind];
if (thisArg.startsWith("-")) {
for (GetOptDesc option : options) {
if ((thisArg.length() == 2 &&
option.getArgLetter() == thisArg.charAt(1)) ||
(option.getArgName() != null &&
option.getArgName().equals(thisArg.substring(1)))) { // found it
// If it needs an option argument, get it.
if (option.takesArgument()) {
if (optind < argv.length-1) {
optarg = argv[++optind];
} else {
throw new IllegalArgumentException(
"Option " + option.getArgLetter() +
" needs value but found end of arg list");
}
}
++optind;
return option.getArgLetter();
}
}
// Began with "-" but not matched, so must be error.
++optind;
return '?';
} else {
// Found non-argument non-option word in argv: end of options.
++optind;
done = true;
return DONE;
}
}
/**
* Get the optind.
* @return optind, the index into args of the last option we looked at.
*/
public int getOptInd() {
return optind;
}
}
// end::main[]