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-slim Show documentation
Show all versions of jython-slim 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;
}
}