src.org.python.util.OptionScanner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jython Show documentation
Show all versions of jython Show documentation
Jython is an implementation of the high-level, dynamic, object-oriented
language Python written in 100% Pure Java, and seamlessly integrated with
the Java platform. It thus allows you to run Python on any Java platform.
package org.python.util;
/**
* A somewhat general-purpose scanner for command options, based on CPython {@code getopt.c}.
*/
class OptionScanner {
/** Valid options. ':' means expect an argument following. */
private final String programOpts; // e.g. in Python "3bBc:dEhiJm:OQ:RsStuUvVW:xX?";
/** Index in argv of the arg currently being processed (or about to be started). */
private int argIndex = 0;
/** Character index within the current element of argv (of the next option to process). */
private int optIndex = 0;
/** Option argument (where present for returned option). */
private String optarg = null;
/** Error message (after returning {@link #ERROR}. */
private String message = "";
/** Original argv passed at reset */
private String[] args;
/** Return to indicate argument processing is over. */
static final char DONE = '\uffff';
/** Return to indicate option was not recognised. */
static final char ERROR = '\ufffe';
/** Return to indicate the next argument is a free-standing argument. */
static final char ARGUMENT = '\ufffd';
/**
* Class representing an argument of the long type, where the whole program argument represents
* one option, e.g. "--help" or "-version". Such options must start with a '-'. The client
* supplies an array of {@code LongSpec} objects to the constructor to define the valid cases.
* Long options are recognised before single-letter options are looked for. Note that "-" itself
* is treated as a long option (even though it is quite short), returning
* {@link OptionScanner#ERROR} if not explicitly defined as a {@code LongSpec}.
*/
static class LongSpec {
final String key;
final char returnValue;
final boolean hasArgument;
/**
* Define that the long argument should return a given char value in calls to
* {@link OptionScanner#getOption()}, and whether or not an option argument should appear
* following it on the command line. This character value need not be the same as any
* single-character option, and may be {@link OptionScanner#DONE} (typically for the key
* {@code "--"}.
*
* @param key to match
* @param returnValue to return when that matches
* @param hasArgument an argument to the option is expected to follow
*/
public LongSpec(String key, char returnValue, boolean hasArgument) {
this.key = key;
this.returnValue = returnValue;
this.hasArgument = hasArgument;
}
/** The same as {@code LongSpec(key, returnValue, false)}. */
public LongSpec(String key, char returnValue) {
this(key, returnValue, false);
}
}
private final LongSpec[] longSpec;
/**
* Create the scanner from command-line arguments, and information about the valid options.
*
* @param args command-line arguments (which must not change during scanning)
* @param programOpts the one-letter options (with : indicating an option argument
* @param longSpec table of long options (like --help)
*/
OptionScanner(String[] args, String programOpts, LongSpec[] longSpec) {
this.args = args;
this.programOpts = programOpts;
this.longSpec = longSpec;
}
/**
* Get the next option (as a character), or return a code designating successful or erroneous
* completion.
*
* @return next option from command line: the actual character or a code.
*/
char getOption() {
message = "";
String arg;
optarg = null;
if (argIndex >= args.length) {
// Option processing is complete
return DONE;
} else {
// We are currently processing:
arg = args[argIndex];
if (optIndex == 0) {
// And we're at the start of it.
if (!arg.startsWith("-") || arg.length() <= 1) {
// Non-option program argument e.g. "-" or file name. Note no ++argIndex.
return ARGUMENT;
} else if (longSpec != null) {
// Test for "whole arg" special cases
for (LongSpec spec : longSpec) {
if (spec.key.equals(arg)) {
if (spec.hasArgument) {
// Argument to option should be in next arg
if (++argIndex < args.length) {
optarg = args[argIndex];
} else {
// There wasn't a next arg.
return error("Argument expected for the %s option", arg);
}
}
// And the next processing will be in the next arg
++argIndex;
return spec.returnValue;
}
}
// No match: fall through.
}
// arg is one or more single character options. Continue after the '-'.
optIndex = 1;
}
}
// We are in arg=argv[argvIndex] at the character to examine is at optIndex.
assert argIndex < args.length;
assert optIndex > 0;
assert optIndex < arg.length();
char option = arg.charAt(optIndex++);
if (optIndex >= arg.length()) {
// The option was at the end of the arg, so the next action uses the next arg.
++argIndex;
optIndex = 0;
}
// Look up the option character in the list of allowable ones
int ptr;
if ((ptr = programOpts.indexOf(option)) < 0 || option == ':') {
if (arg.length() <= 2) {
return error("Unknown option: -%c", option);
} else {
// Might be unrecognised long arg, or a one letter option in a group.
return error("Unknown option: -%c or '%s'", option, arg);
}
}
// Is the option marked as expecting an argument?
if (++ptr < programOpts.length() && programOpts.charAt(ptr) == ':') {
/*
* The option's argument is the rest of the current argv[argvIndex][optIndex:]. If the
* option is the last character of arg, argvIndex has already moved on and optIndex==0,
* so this statement is still true, except that argvIndex may have moved beyond the end
* of the array.
*/
if (argIndex < args.length) {
optarg = args[argIndex].substring(optIndex);
// And the next processing will be in the next arg
++argIndex;
optIndex = 0;
} else {
// We were looking for an argument but there wasn't one.
return error("Argument expected for the -%c option", option);
}
}
return option;
}
/** Get the argument of the previously returned option or {@code null} if none. */
String getOptionArgument() {
return optarg;
}
/**
* Get a whole argument (not the argument of an option), for use after {@code ARGUMENT} was
* returned. This advances the internal state to the next argument.
*/
String getWholeArgument() {
optIndex = 0;
return args[argIndex++];
}
/**
* Peek at a whole argument (not the argument of an option), for use after {@code ARGUMENT} was
* returned. This does not advance the internal state to the next argument.
*/
String peekWholeArgument() {
return args[argIndex];
}
/** Number of arguments that remain unprocessed from the original array. */
int countRemainingArguments() {
return args.length - argIndex;
}
/** Get the error message (when we previously returned {@link #ERROR}. */
String getMessage() {
return message;
}
/** Set the error message as {@code String.format(message, args)} and return {@link #ERROR}. */
char error(String message, Object... args) {
this.message = String.format(message, args);
return ERROR;
}
}