org.python.util.jython Maven / Gradle / Ivy
Show all versions of jython-slim Show documentation
// Copyright (c) Corporation for National Research Initiatives
package org.python.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.python.Version;
import org.python.core.BytecodeLoader;
import org.python.core.CompileMode;
import org.python.core.Options;
import org.python.core.PrePy;
import org.python.core.Py;
import org.python.core.PyCode;
import org.python.core.PyException;
import org.python.core.PyFile;
import org.python.core.PyList;
import org.python.core.PyNullImporter;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.core.PyStringMap;
import org.python.core.PySystemState;
import org.python.core.RegistryKey;
import org.python.core.imp;
public class jython {
/** Exit status: must have {@code OK.ordinal()==0} */
private enum Status {
OK, ERROR, NOT_RUN, NO_FILE
}
/** The root of the Jython Logger hierarchy, named "org.python". */
private static final Logger logger = Logger.getLogger("org.python");
/**
* The default format for console log messages in the command-line Jython. See
* {@code java.util.logging.SimpleFormatter} for an explanation of the syntax.
*
* This format is used in the absence of other logging preferences, and only if property
* {@code python.logging.default} is not defined. Jython tests for definitions in the system
* properties of {@code java.util.logging.config.class}, {@code java.util.logging.config.file},
* and {@code java.util.logging.SimpleFormatter.format} and if none of these is defined, it sets
* the last of them to this value.
*
* You can choose something else, for example to log with millisecond time stamps, launch Jython
* as:
* jython -vv -J-Djava.util.logging.SimpleFormatter.format="[%1$tT.%1$tL] %3$s: (%4$s) %5$s%n"
*
Depending on your shell, the argument may need quoting or escaping.
*/
public static final String CONSOLE_LOG_FORMAT = "%3$s %4$s %5$s%n";
/** The console handler we normally attach to "org.python". See {@link #getConsoleHandler()} */
private static ConsoleHandler consoleHandler;
// An instance of this class will provide the console (python.console) by default.
private static final String PYTHON_CONSOLE_CLASS = "org.python.util.JLineConsole";
private static final String COPYRIGHT =
"Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.";
/** The message output when reporting command-line errors and when asked for help. */
static final String usageHeader =
"usage: jython [option] ... [-c cmd | -m mod | file | -] [arg] ...\n";
/** The message additional to {@link #usageHeader} output when asked for help. */
// @formatter:off
static final String usageBody =
"Options and arguments:\n"
// + "(and corresponding environment variables):\n"
+ "-B : don't write bytecode files on import;\n"
+ " also PYTHONDONTWRITEBYTECODE=x\n"
+ "-c cmd : program passed in as string (terminates option list)\n"
// + "-d : debug output from parser (also PYTHONDEBUG=x)\n"
+ "-Dprop=v : Set the property `prop' to value `v'\n"
+ "-E : ignore environment variables (such as JYTHONPATH)\n"
+ "-h : print this help message and exit (also --help)\n"
+ "-i : inspect interactively after running script; forces a prompt even\n"
+ " if stdin does not appear to be a terminal; also PYTHONINSPECT=x\n"
+ "-jar jar : program read from __run__.py in jar file. Deprecated: instead,\n"
+ " name the archive as the file argument (runs __main__.py).\n"
+ "-m mod : run library module as a script (terminates option list)\n"
// + "-O : optimize generated bytecode (a tad; also PYTHONOPTIMIZE=x)\n"
// + "-OO : remove doc-strings in addition to the -O optimizations\n"
+ "-Q arg : division options: -Qold (default), -Qwarn, -Qwarnall, -Qnew\n"
+ "-s : don't add user site directory to sys.path;\n"
// + "also PYTHONNOUSERSITE\n"
+ "-S : don't imply 'import site' on initialization\n"
// + "-t : issue warnings about inconsistent tab usage (-tt: issue errors)\n"
+ "-u : unbuffered binary stdout and stderr\n"
// + "(also PYTHONUNBUFFERED=x)\n"
// + " see man page for details on internal buffering relating to '-u'\n"
+ "-v : verbose (emit more \"org.python\" log messages)\n"
// + "(also PYTHONVERBOSE=x)\n"
+ " can be supplied multiple times to increase verbosity\n"
+ "-V : print the Python version number and exit (also --version)\n"
+ "-W arg : warning control (arg is action:message:category:module:lineno)\n"
// + "-x : skip first line of source, allowing use of non-Unix forms of #!cmd\n"
+ "-3 : warn about Python 3.x incompatibilities that 2to3 cannot trivially fix\n"
+ "file : program read from script file\n"
+ "- : program read from stdin (default; interactive mode if a tty)\n"
+ "arg ... : arguments passed to program in sys.argv[1:]\n" + "\n"
+ "Other environment variables:\n" //
+ "JYTHONSTARTUP: file executed on interactive startup (no default)\n"
+ "JYTHONPATH : '" + File.pathSeparator
+ "'-separated list of directories prefixed to the default module\n"
+ " search path. The result is sys.path.\n"
+ "PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.";
// @formatter:on
/**
* Print a full usage message onto {@code System.out} or a brief usage message onto
* {@code System.err}.
*
* @param status if {@code == 0} full help version on {@code System.out}.
* @return the status given as the argument.
*/
static Status usage(Status status) {
boolean fullHelp = (status == Status.OK);
PrintStream f = fullHelp ? System.out : System.err;
f.printf(usageHeader);
if (fullHelp) {
f.printf(usageBody);
} else {
f.println("Try 'jython -h' for more information.");
}
return status;
}
/**
* Try to set the format for {@code SimpleFormatter} if no other mechanism has been provided,
* and security allows it. Note that the absolute fall-back format is:
* {@code "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n"}, defined
* ultimately in {@code sun.util.logging.LoggingSupport}.
*
* @param format to set for {@code java.util.logging.SimpleFormatter}
* @throws SecurityException if not allowed to read or set necessary properties.
*/
private static void configureSimpleFormatter(String format) throws SecurityException {
final String CLASS_KEY = "java.util.logging.config.class";
String className = System.getProperty(CLASS_KEY);
if (className == null) {
final String FILE_KEY = "java.util.logging.config.file";
String fileName = System.getProperty(FILE_KEY);
if (fileName == null) {
final String FORMAT_KEY = "java.util.logging.SimpleFormatter.format";
String currentFormat = System.getProperty(FORMAT_KEY);
if (currentFormat == null) {
// Note that this sets the format for _all_ console logging
System.setProperty(FORMAT_KEY, format);
}
}
}
}
/**
* Get or lazily create the console handler we normally attach to "org.python", which uses
* {@code SimpleFormatter} and accepts all levels of message. The format will be that currently
* assigned in the system property {@code java.util.logging.SimpleFormatter.format}, which in
* turn is a succinct format {@link #CONSOLE_LOG_FORMAT}, if no other configuration mechanism
* has been provided by the application.
*
* @return Configured {@link #consoleHandler}
* @throws SecurityException if no permission to adjust logging
*/
private static synchronized ConsoleHandler getConsoleHandler() throws SecurityException {
ConsoleHandler h = consoleHandler;
if (h == null) {
consoleHandler = h = new ConsoleHandler();
h.setFormatter(new SimpleFormatter());
h.setLevel(Level.ALL);
}
return h;
}
/**
* Events from within the Jython implementation are surfaced through the
* {@code java.util.logging.Logger} {@code org.python} or a child of it. When Jython is used
* interactively, we normally want these to emerge on the console, in a succinct form, not via
* the java.util.logging root logger. This method is called by {@link #main(String[])} to
* achieve that.
*
* The root logger format is hard to read for humans. The logging level of the handler defaults
* to {@code INFO}, and does not respond to the {@code -v} option. (It could be made to, but
* then all logging would be be turned up together.) This method makes these adjustments to
* logging:
*
* - The format {@code SimpleFormatter} is replaced with a simpler format (but only if no
* other logging customisation is present). (Affects all logging.)
* - A console handler is installed for logger {@code org.python} using that a
* {@code SimpleFormatter} and accepts all levels.
* - Logger {@code org.python} is told not to propagate records to parent handlers (so that
* messages are not emitted twice.
*
* The level of logger {@code org.python} and its child loggers by default, which determines
* admission of logging, reflects the "verbosity" set by the `-v` option.
*/
public static void loggingToConsole() {
SecurityException exception = null;
try {
// Jython console messages (-v option) are emitted using SimpleFormatter
configureSimpleFormatter(CONSOLE_LOG_FORMAT);
} catch (SecurityException se) {
// Unable to access the necessary system properties. Give up on custom format.
exception = se;
}
// Whether we can choose a format or not, we can still _use_ logging.
if (exception == null) {
try {
// Make our "org.python" logger do its own output and not propagate to root.
/*
* Customise the logger so that it does not propagate to its parent and has its own
* {@code Handler} accepting all messages. The level set on the logger alone
* therefore controls whether messages are emitted to the console.
*/
logger.addHandler(getConsoleHandler());
logger.setUseParentHandlers(false);
} catch (SecurityException se) {
// This probably means no logging finer than INFO (so none enabled by -v)
exception = se;
}
}
if (exception != null) {
logger.log(Level.WARNING, "Unable to format console messages: {0}",
exception.getMessage());
}
}
/**
* Mostly reverse the effects of {@link #loggingToConsole()}, by removing from the "org.python"
* logger the handler that prints to the console, and setting the "org.python" logger to
* propagate records to its parent handlers. The method does not try to reset the property
* {@code java.util.logging.SimpleFormatter.format}, which is in any case only read during
* static initialisation by {@code SimpleFormatter}.
*/
public static void loggingToDefault() {
try {
logger.config("Revert logging to default hierarchy");
logger.setUseParentHandlers(true);
logger.removeHandler(consoleHandler);
} catch (SecurityException se) {
logger.log(Level.WARNING, "Unable to revert logging to default: {0}", se.getMessage());
}
}
/**
* Runs a JAR file, by executing the code found in the file __run__.py, which should be in the
* root of the JAR archive. Note that {@code __name__} is set to the base name of the JAR file
* and not to "__main__" (for historical reasons). This method does not handle
* exceptions. the caller should handle any {@code (Py)Exceptions} thrown by the code.
*
* @param filename The path to the filename to run.
* @return {@code 0} on normal termination (otherwise throws {@code PyException}).
*/
public static int runJar(String filename) {
// TBD: this is kind of gross because a local called `zipfile' just magically
// shows up in the module's globals. Either `zipfile' should be called
// `__zipfile__' or (preferably, IMO), __run__.py should be imported and a main()
// function extracted. This function should be called passing zipfile in as an
// argument.
//
// Probably have to keep this code around for backwards compatibility (?)
try (ZipFile zip = new ZipFile(filename)) {
ZipEntry runit = zip.getEntry("__run__.py");
if (runit == null) {
throw Py.ValueError("can't find '__run__.py' in '" + filename + "'");
}
/*
* Stripping the stuff before the last File.separator fixes Bug #931129 by keeping
* illegal characters out of the generated proxy class name. Mostly.
*/
int beginIndex = filename.lastIndexOf(File.separator);
if (beginIndex >= 0) {
filename = filename.substring(beginIndex + 1);
}
PyStringMap locals = Py.newStringMap();
locals.__setitem__("__name__", Py.fileSystemEncode(filename));
locals.__setitem__("zipfile", Py.java2py(zip));
InputStream file = zip.getInputStream(runit); // closed when zip is closed
PyCode code = Py.compile(file, "__run__", CompileMode.exec);
Py.runCode(code, locals, locals);
} catch (IOException e) {
throw Py.IOError(e);
}
return Status.OK.ordinal();
}
/** Now equivalent to {@link #main(String[])}, which is to be preferred. */
@Deprecated
public static void run(String[] args) {
main(args);
}
/** Exit Jython with status (converted to an integer). */
private static void exit(Status status) {
System.exit(status.ordinal());
}
/**
* Append options from the environment variable {@code PYTHONWARNINGS}, respecting the -E
* option.
*
* @param opts the list to which the options are appended.
*/
private static void addWarnOptionsFromEnv(PyList opts) {
String envVar = getenv("PYTHONWARNINGS", "");
for (String opt : envVar.split(",")) {
opt = opt.trim();
if (opt.length() > 0) {
opts.add(Py.fileSystemEncode(opt));
}
}
}
/**
* Attempt to run a module as the {@code __main__} module, via a call to
* {@code runpy._run_module_as_main}. Exceptions raised by the imported module, including
* {@code SystemExit}, if not handled by {@code runpy} itself, will propagate out of this
* method. Note that if {@code runpy} cannot import the module it calls {@code sys.exit} with a
* message, which will raise {@code SystemExit} from this method.
*
* @param moduleName to run
* @param set_argv0 replace {@code sys.argv[0]} with the file name of the module source
* {@code runpy._run_module_as_main} option.
* @return {@code Status.OK} on normal termination.
*/
private static Status runModule(String moduleName, boolean set_argv0) {
// PEP 338 - Execute module as a script
PyObject runpy = imp.importName("runpy", true);
PyObject runmodule = runpy.__findattr__("_run_module_as_main");
// May raise SystemExit (with message)
runmodule.__call__(Py.fileSystemEncode(moduleName), Py.newBoolean(set_argv0));
return Status.OK;
}
/**
* Attempt to treat a file as a source of imports, import a module {@code __main__}, and run it.
* If the file is suitable (e.g. it's a directory or a ZIP archive) the method places the file
* first on {@code sys.path}, so that {@code __main__} and its packaged dependencies may be
* found in it. This permits a zip file containing Python source to be run when given as a first
* argument on the command line. It may be that the file is not of a type that can be imported,
* in which case the return indicates this.
*
* @param archive to run (FS encoded name)
* @return {@code Status.OK} or {@code Status.NOT_RUN} (if the file was not an archive).
*/
private static Status runMainFromImporter(PyString archive) {
PyObject importer = imp.getImporter(archive);
if (!(importer instanceof PyNullImporter)) {
// filename is usable as an import source, so put it in sys.path[0] and import __main__
Py.getSystemState().path.insert(0, archive);
return runModule("__main__", false);
}
return Status.NOT_RUN;
}
/**
* Execute the stream {@code fp} as a file, in the given interpreter, as {@code __main__}. The
* file name provided must correspond to the stream. In particular, the file name extension
* "$py.class" will cause the stream to be interpreted as compiled code. For streams that are
* not really files, this name may be a conventional one like {@code ""}, however this
* method will not treat a console stream as interactive.
*
* @param fp Python source code
* @param filename appears FS-encoded in variable {@code __file__} and in error messages
* @param interp to do the work
* @return {@code Status.OK} on normal termination.
*/
// This is roughly equivalent to CPython PyRun_SimpleFileExFlags
private static Status runSimpleFile(InputStream fp, String filename, PythonInterpreter interp) {
// Reflect the current name in the module's __file__, compare PyRun_SimpleFileExFlags.
final String __file__ = "__file__";
PyObject globals = interp.globals;
PyObject previousFilename = globals.__finditem__(__file__);
if (previousFilename == null) {
globals.__setitem__(__file__,
// Note that __file__ is widely expected to be encoded bytes
Py.fileSystemEncode(filename));
}
// Allow for already-compiled target, but for us it's a $py.class not a .pyc.
if (filename.endsWith("$py.class")) {
// Jython compiled file.
String name = filename.substring(0, filename.length() - 6); // = - ".class"
try {
byte[] codeBytes = imp.readCode(filename, fp, false, imp.NO_MTIME);
File file = new File(filename);
PyCode code = BytecodeLoader.makeCode(name, codeBytes, file.getParent());
interp.exec(code);
} catch (IOException e) {
throw Py.IOError(e);
}
} else {
// Assume Python source file: run in the interpreter
interp.execfile(fp, filename);
}
// Delete __file__ variable, previously non-existent. Compare PyRun_SimpleFileExFlags.
if (previousFilename == null) {
globals.__delitem__(__file__);
}
return Status.OK;
}
/**
* Execute the stream {@code fp} in the given interpreter. If {@code fp} refers to a stream
* associated with an interactive device (console or terminal input), execute Python source
* statements from the stream in the interpreter as {@code __main__}. Otherwise, the file name
* provided must correspond to the stream, as in
* {@link #runSimpleFile(InputStream, String, PythonInterpreter)}.
*
* @param fp Python source code
* @param filename the name of the file or {@code null} meaning "???"
* @param interp to do the work
* @return {@code Status.OK} on normal termination.
*/
// This is roughly equivalent to CPython PyRun_AnyFileExFlags
private static Status runStream(InputStream fp, String filename, InteractiveConsole interp) {
// Following CPython PyRun_AnyFileExFlags here, blindly, concerning null name.
filename = filename != null ? filename : "???";
// Run the contents in the interpreter
if (PrePy.isInteractive(fp, filename)) {
// __file__ not defined
interp.interact(null, new PyFile(fp));
} else {
// __file__ will be defined
runSimpleFile(fp, filename, interp);
}
return Status.OK;
}
/**
* Attempt to open the named file and execute it in the interpreter as {@code __main__}, as in
* {@link #runStream(InputStream, String, InteractiveConsole)}. This may raise a Python
* exception, including {@code SystemExit}. If the file cannot be opened, or using it throws a
* Java {@code IOException} that is not converted to a {@code PyException} (i.e. not within the
* executing code), it is reported via {@link #printError(String, Object...)}, and reflected in
* the return status. If the file can be opened, its parent directory will be inserted at
* {@code sys.argv[0]}.
*
* @param filename the name of the file or {@code null} meaning "???"
* @param interp to do the work
* @return {@code Status.OK} on normal termination, {@code Status.NO_FILE} if the file cannot be
* read, or {@code Status.ERROR} on other {@code IOException}s.
*/
private static Status runFile(String filename, InteractiveConsole interp) {
File file = new File(filename);
try (InputStream is = new FileInputStream(file)) {
String parent = file.getAbsoluteFile().getParent();
interp.getSystemState().path.insert(0, Py.fileSystemEncode(parent));
// May raise exceptions, (including SystemExit)
return runStream(is, filename, interp);
} catch (FileNotFoundException fnfe) {
// Couldn't open it. No point in going interactive, even if -i given.
printError("can't open file '%s': %s", filename, fnfe);
return Status.NO_FILE;
} catch (IOException ioe) {
// This may happen on the automatically-generated close()
printError("error closing '%s': %s", filename, ioe);
return Status.ERROR;
}
}
/**
* Attempt to execute the file named in the registry entry {@code python.startup}, which may
* also have been set via the environment variable {@code JYTHONSTARTUP}. This may raise a
* Python exception, including {@code SystemExit} that propagates to the caller. If the file
* cannot be opened, or using it throws a Java {@code IOException} that is not converted to a
* {@code PyException} (i.e. not within the executing code), it is reported via
* {@link #printError(String, Object...)}.
*
* @param interp to do the work
*/
private static void runStartupFile(InteractiveConsole interp) {
String filename = PySystemState.registry.getProperty(RegistryKey.PYTHON_STARTUP, null);
if (filename != null) {
try (InputStream fp = new FileInputStream(filename)) {
// May raise exceptions, (including SystemExit)
// RunStreamOrThrow(fp, filename, interp);
runSimpleFile(fp, filename, interp);
} catch (FileNotFoundException fnfe) {
// Couldn't open it. No point in going interactive, even if -i given.
printError("Could not open startup file '%s'", filename);
} catch (IOException ioe) {
// This may happen on the automatically-generated close()
printError("error closing '%s': %s", filename, ioe);
}
}
}
/**
* Main Jython program, following the structure and logic of CPython {@code main.c} to produce
* the same behaviour. The argument to the method is the argument list supplied after the class
* name in the {@code java} command. Arguments up to the executable script name are options for
* Jython; arguments after the executable script are supplied in {@code sys.argv}. "Executable
* script" here means a Python source file name, a module name (following the {@code -m}
* option), a literal command (following the {@code -c} option), or a JAR file name (following
* the {@code -jar} option). As a special case of the file name, "-" is allowed, meaning take
* the script from standard input.
*
* The main difference for the caller stems from a difference between C and Java: in C, the
* argument list {@code (argv)} begins with the program name, while in Java all elements of
* {@code (args)} are arguments to the program.
*
* @param args arguments to the program.
*/
public static void main(String[] args) {
// Parse the command line options
CommandLineOptions opts = CommandLineOptions.parse(args);
// Choose the basic action
switch (opts.action) {
case VERSION:
System.err.printf("Jython %s\n", Version.PY_VERSION);
exit(Status.OK);
case HELP:
exit(usage(Status.OK));
case ERROR:
System.err.println(opts.message);
exit(usage(Status.ERROR));
case RUN:
// Let's run some Python! ...
}
// Adjust relative to the level set by java.util.logging.
PrePy.increaseLoggingLevel(opts.verbosity);
// Get system properties (or empty set if we're prevented from accessing them)
Properties preProperties = PrePy.getSystemProperties();
addDefaultsFromEnvironment(preProperties);
/*
* Normally we divert org.python.* logging to the console (but can suppress with property).
* Note we have not read the registry at this point, so setting it there has no effect.
*/
if (!preProperties.containsKey("python.logging.default")) {
loggingToConsole();
}
// Treat the apparent filename "-" as no filename
boolean haveDash = "-".equals(opts.filename);
if (haveDash) {
opts.filename = null;
}
// Sense whether the console is interactive, or we have been told to consider it so.
boolean stdinIsInteractive = PrePy.isInteractive(System.in, null);
// Shorthand
boolean haveScript = opts.command != null || opts.filename != null || opts.module != null;
if (Options.inspect || !haveScript) {
// We'll be going interactive eventually. condition an interactive console.
if (PrePy.haveConsole()) {
// Set the default console type if nothing else has
addDefault(preProperties, RegistryKey.PYTHON_CONSOLE, PYTHON_CONSOLE_CLASS);
}
}
/*
* Set up the executable-wide state from the options, environment and registry, and create
* the first instance of a sys module. We try to leave to this initialisation the things
* necessary to an embedded interpreter, and to do in the present method only that which
* belongs only to command line application.
*
* (Jython partitions system and interpreter state differently from modern CPython, which is
* able explicitly to create a PyInterpreterState first, after which sys and the thread
* state are created to hang from it.)
*/
// The Jython type system will spring into existence here. This may take a while.
PySystemState.initialize(preProperties, opts.properties);
// Now we can use PyObjects safely.
PySystemState sys = Py.getSystemState();
/*
* Jython initialisation does not load the "warnings" module. Rather we defer it to here,
* where we may safely prepare sys.warnoptions from the -W arguments and the contents of
* PYTHONWARNINGS (compare PEP 432).
*/
addFSEncoded(opts.warnoptions, sys.warnoptions);
addWarnOptionsFromEnv(sys.warnoptions);
if (!sys.warnoptions.isEmpty()) {
// The warnings module validates (and may complain about) the warning options.
imp.load("warnings");
}
/*
* Create the interpreter that we will use as a name space in which to execute the script or
* interactive session. We run site.py as part of interpreter initialisation (as CPython).
*/
InteractiveConsole interp = new InteractiveConsole();
if (opts.verbosity > 0 || (!haveScript && stdinIsInteractive)) {
// Verbose or going interactive immediately: produce sign on messages.
System.err.println(InteractiveConsole.getDefaultBanner());
if (Options.importSite) {
System.err.println(COPYRIGHT);
}
}
/*
* We currently have sys.argv = PySystemState.defaultArgv = ['']. Python has a special use
* for sys.argv[0] according to the source of the script (-m, -c, etc.), but the rest of it
* comes from the unparsed part of the command line.
*/
addFSEncoded(opts.argv, sys.argv);
/*
* At last, we are ready to execute something. This has two parts: execute the script or
* console and (if we didn't execute the console) optionally start an interactive console
* session. The sys.path needs to be prepared in a slightly different way for each case.
*/
Status sts = Status.NOT_RUN;
try {
if (opts.command != null) {
// The script is an immediate command -c "..."
sys.argv.set(0, Py.newString("-c"));
sys.path.insert(0, Py.EmptyString);
interp.exec(opts.command);
sts = Status.OK;
} else if (opts.module != null) {
// The script is a module (and yet CPython has -c here)
sys.argv.set(0, Py.newString("-c")); // "-m" in CPython3
sys.path.insert(0, Py.EmptyString);
sts = runModule(opts.module, true);
} else if (opts.filename != null) {
// The script is designated by file (or directory) name.
PyString pyFileName = Py.fileSystemEncode(opts.filename);
sys.argv.set(0, pyFileName);
if (opts.jar) {
// The filename was given with the -jar option.
sys.path.insert(0, pyFileName);
runJar(opts.filename);
sts = Status.OK;
} else {
/*
* The filename was given as the leading argument after the options. Our first
* approach is to treat it as an archive (or directory) in which to find a
* __main__.py (as per PEP 338). The handler for this inserts the module at
* sys.path[0] if it runs. It may raise exceptions, but only SystemExit as runpy
* deals with the others.
*/
sts = runMainFromImporter(pyFileName);
if (sts == Status.NOT_RUN) {
/*
* The filename was not a suitable source for import, so treat it as a file
* to execute. The handler for this inserts the parent of the file at
* sys.path[0].
*/
sts = runFile(opts.filename, interp);
// If we really had no script, do not go interactive at the end.
haveScript = sts != Status.NO_FILE;
}
}
} else { // filename == null
// There is no script. (No argument or it was "-".)
if (haveDash) {
sys.argv.set(0, Py.newString('-'));
}
sys.path.insert(0, Py.EmptyString);
// Genuinely interactive, or just interpreting piped instructions?
if (stdinIsInteractive) {
// If genuinely interactive, SystemExit should mean exit the application.
Options.inspect = false;
// If genuinely interactive, run a start-up file if one is specified.
runStartupFile(interp);
}
// Run from console: exceptions other than SystemExit are handled in the REPL.
sts = runStream(System.in, "", interp);
}
} catch (PyException pye) {
// Whatever the mode of execution an uncaught PyException lands here.
// If pye was SystemExit *and* Options.inspect==false, this will exit the JVM:
Py.printException(pye);
// It was an exception other than SystemExit or Options.inspect==true.
sts = Status.ERROR;
}
/*
* Check this environment variable at the end, to give programs the opportunity to set it
* from Python.
*/
if (!Options.inspect) {
// If set from Python, the value will be in os.environ, not Java System.getenv.
Options.inspect = Py.getenv("PYTHONINSPECT", "").length() > 0;
}
if (Options.inspect && stdinIsInteractive && haveScript) {
/*
* The inspect flag is set (-i option) so we've been asked to end with an interactive
* session: the console is interactive, and we have just executed some kind of script
* (i.e. it wasn't already an interactive session).
*/
try {
// Ensure that this time SystemExit means exit.
Options.inspect = false;
// Run from console: exceptions other than SystemExit are handled in the REPL.
sts = runStream(System.in, "", interp);
} catch (PyException pye) {
// Exception from the execution of Python code.
Py.printException(pye); // SystemExit will exit the JVM here.
sts = Status.ERROR;
}
}
/*
* If we arrive here then we ran some Python code. It is possible that threads we started
* are still running, so if the status is currently good, just return into the JVM. (This
* exits with good status if nothing goes wrong subsequently.)
*/
if (sts != Status.OK) {
// Something went wrong running Python code: shut down in a tidy way.
interp.cleanup();
exit(sts);
}
}
/**
* If the key is not currently present and the passed value is not null
, sets the
* key
to the value
in the given Properties
object. Thus,
* it provides a default value for a subsequent getProperty()
.
*
* @param registry to be (possibly) updated
* @param key at which to set value
* @param value to set (or null
for no setting)
* @return true iff a value was set
*/
private static boolean addDefault(Properties registry, String key, String value) {
// Set value at key if nothing else has set it
if (value == null || registry.containsKey(key)) {
return false;
} else {
registry.setProperty(key, value);
return true;
}
}
/**
* Provides default registry entries from particular supported environment variables, obtained
* by calls to {@link #getenv(String)}. If a corresponding entry already exists in the
* properties passed, it takes precedence.
*
* @param registry to be (possibly) updated
*/
private static void addDefaultsFromEnvironment(Properties registry) {
// Pick up the path from the environment
addDefault(registry, "python.path", getenv("JYTHONPATH"));
// Runs at the start of each (wholly) interactive session.
addDefault(registry, "python.startup", getenv("JYTHONSTARTUP"));
// Go interactive after script. (PYTHONINSPECT because Python scripts may set it.)
addDefault(registry, "python.inspect", getenv("PYTHONINSPECT"));
// PYTHONDONTWRITEBYTECODE
if (getenv("PYTHONDONTWRITEBYTECODE") != null) {
Options.dont_write_bytecode = true;
}
// Read environment variable PYTHONIOENCODING into properties (registry)
String pythonIoEncoding = getenv("PYTHONIOENCODING");
if (pythonIoEncoding != null) {
String[] spec = pythonIoEncoding.split(":", 2);
// Note that if encoding or errors is blank (=null), the registry value wins.
addDefault(registry, RegistryKey.PYTHON_IO_ENCODING, spec[0]);
if (spec.length > 1) {
addDefault(registry, RegistryKey.PYTHON_IO_ERRORS, spec[1]);
}
}
}
/**
* The same as {@link} {@link #getenv(String, String) getenv} with a null default value.
*
* @param name to access in the environment (if allowed by
* {@link Options#ignore_environment}=={@code false}).
* @return the corresponding value or defaultValue
.
*/
private static String getenv(String name) {
return getenv(name, null);
}
/**
* Get the value of an environment variable, respecting {@link Options#ignore_environment} (the
* -E option), or return the given default if the variable is undefined or the security
* environment prevents access. An empty string value from the environment is treated as
* undefined.
*
* This accesses the read-only Java copy of the system environment directly, not
* {@code os.environ} so that it is safe to use before Python types are available.
*
* @param name to access in the environment (if allowed by
* {@link Options#ignore_environment}=={@code false}).
* @param defaultValue to return if {@code name} is not defined, is "" or access is forbidden.
* @return the corresponding value or defaultValue
.
*/
private static String getenv(String name, String defaultValue) {
if (!Options.ignore_environment) {
try {
String value = System.getenv(name);
return (value != null && value.length() > 0) ? value : defaultValue;
} catch (SecurityException e) {
// We're not allowed to access them after all
Options.ignore_environment = true;
}
}
return defaultValue;
}
/** Non-fatal error message when ignoring unsupported option (usually one valid for CPython). */
private static void optionNotSupported(char option) {
printError("Option -%c is not supported", option);
}
/**
* Print {@code "jython: "} on {@code System.err} as one line.
*
* @param format suitable to use in {@code String.format(format, args)}
* @param args zero or more args
*/
private static void printError(String format, Object... args) {
System.err.println(String.format("jython: " + format, args));
}
/**
* Append strings to a PyList as {@code bytes/str} objects. These might come from the command
* line, or any source with the possibility of non-ascii values.
*
* @param source of {@code String}s
* @param destination list
*/
private static void addFSEncoded(Iterable source, PyList destination) {
for (String s : source) {
destination.add(Py.fileSystemEncode(s));
}
}
/**
* Class providing a parser for Jython command line options. Many of the allowable options set
* values directly in the static {@link Options} as the parser runs, while others set values in
* (an instance) of this class.
*/
static class CommandLineOptions {
/** Possible actions to take after processing the options. */
enum Action {
RUN, ERROR, HELP, VERSION
};
/** The action to take after processing the options. */
Action action = Action.RUN;
/** Set informatively when {@link #action}{@code ==ERROR}. */
String message = "";
/** Argument to the -c option. */
String command;
/** First argument that is not an option (therefore the executable file). */
String filename;
/** Argument to the -m option. */
String module;
/** -h or --help option. */
boolean help = false;
/** -V or --version option. */
boolean version = false;
/** -jar option. */
boolean jar = false;
/** Count of -v options. */
int verbosity = 0;
/** Collects definitions made with the -D option directly to Jython (not java -D). */
Properties properties = new Properties();
/** Arguments after the first non-option, therefore arguments to the executable program. */
List argv = new LinkedList();
/** Arguments collected from succesive -W options. */
List warnoptions = new LinkedList();
/** Valid single character options. ':' means expect an argument following. */
// XJD are extra to CPython. X and J are sanctioned while D may one day clash.
static final String PROGRAM_OPTS = "3bBc:dEhim:OQ:RsStuUvVW:x?" + "XJD:";
/** Valid long-name options. */
static final char JAR_OPTION = '\u2615';
static final OptionScanner.LongSpec[] PROGRAM_LONG_OPTS =
{new OptionScanner.LongSpec("--", OptionScanner.DONE),
new OptionScanner.LongSpec("--help", 'h'),
new OptionScanner.LongSpec("--version", 'V'),
new OptionScanner.LongSpec("-jar", JAR_OPTION, true), // Yes, just one dash.
};
/**
* Parse the arguments into the static {@link Options} and a returned instance of this
* class.
*
* @param args from program invocation.
* @return
*/
static CommandLineOptions parse(String args[]) {
CommandLineOptions opts = new CommandLineOptions();
opts._parse(args);
return opts;
}
/** Parser implementation. Do not call this twice on the same instance. */
private void _parse(String args[]) {
// Create a scanner with the right tables for Python/Jython
OptionScanner scanner = new OptionScanner(args, PROGRAM_OPTS, PROGRAM_LONG_OPTS);
_parse(scanner, args);
if (action == Action.RUN) {
// Squirrel away the unprocessed arguments
while (scanner.countRemainingArguments() > 0) {
argv.add(scanner.getWholeArgument());
}
}
}
/**
* Parse options into object state, until we encounter the first argument. This is a helper
* to {@link #_parse(String[])}.
*/
private void _parse(OptionScanner scanner, String args[]) {
char c;
/*
* The default action is RUN, taken when we all the options have been processed, and
* either we have run out of arguments (we'll start an interactive session) or
* encountered a non-option argument, which ought to name the file to execute.
* Executable options (like -m and -c) cause a return with action==RUN from their case.
* Any errors, and some special options like --help and -V, return set some other action
* than RUN, ending the loop.
*/
while (action == Action.RUN && (c = scanner.getOption()) != OptionScanner.DONE) {
switch (c) {
/*
* The first 4 cases all terminate the options in with a RUN action, meaning
* that this option defines the executable script and the arguments following
* will be passed to the script.
*/
case 'c':
/*
* -c is the last option; following arguments that look like options are
* left for the command to interpret.
*/
command = scanner.getOptionArgument() + "\n";
return;
case 'm':
/*
* -m is the last option; following arguments that look like options are
* left for the module to interpret.
*/
module = scanner.getOptionArgument();
return;
case JAR_OPTION:
/*
* -jar is the last option; following arguments that look like options are
* left for __run__.py to interpret.
*/
jar = true;
filename = scanner.getOptionArgument();
return;
case OptionScanner.ARGUMENT:
/*
* This should be a file name (or "-", meaning stdin); following arguments
* that look like options are left for the code it contains to interpret.
*/
filename = scanner.getWholeArgument();
return;
// Options that don't terminate option processing (mostly).
case 'b':
case 'd':
optionNotSupported(c);
break;
case '3':
Options.py3k_warning = true;
if (Options.division_warning == 0) {
Options.division_warning = 1;
}
break;
case 'Q':
switch (scanner.getOptionArgument()) {
case "old":
Options.division_warning = 0;
break;
case "warn":
Options.division_warning = 1;
break;
case "warnall":
Options.division_warning = 2;
break;
case "new":
Options.Qnew = true;
break;
default:
error("-Q option should be `-Qold', "
+ "`-Qwarn', `-Qwarnall', or `-Qnew' only");
}
break;
case 'i':
Options.inspect = Options.interactive = true;
break;
case 'O':
Options.optimize++;
break;
case 'B':
Options.dont_write_bytecode = true;
break;
case 's':
Options.no_user_site = true;
break;
case 'S':
Options.no_site = true;
Options.importSite = false;
break;
case 'E':
Options.ignore_environment = true;
break;
case 't':
optionNotSupported(c);
// Py_TabcheckFlag++;
break;
case 'u':
Options.unbuffered = true;
break;
case 'v':
verbosity++;
break;
case 'x':
optionNotSupported(c);
// skipfirstline = true;
break;
// case 'X': reserved for implementation-specific arguments
case 'U':
optionNotSupported(c);
// Py_UnicodeFlag++;
break;
case 'W':
warnoptions.add(scanner.getOptionArgument());
break;
case 'R':
optionNotSupported(c);
break;
case 'D':
// Definitions made on the command line: -Dprop=v
try {
optionD(scanner);
} catch (SecurityException e) {
// Prevented by security policy.
}
break;
// Options that terminate option processing with something other than RUN.
case 'h':
case '?':
action = Action.HELP;
break;
case 'V':
action = Action.VERSION;
break;
case 'J':
/*
* This shouldn't happen because the launcher should have recognised this
* and converted it to an option or argument to the java command. If it
* shows up here, maybe it was supplied outside the loader or the context
* has confused the launcher.
*/
error("-J is only valid when using the Jython launcher. "
+ "In a complex command, put the -J options early.");
break;
case OptionScanner.ERROR:
error(scanner.getMessage());
break;
default:
// Acceptable to the scanner, but missing from the case statement?
error("parser did not recognise option -%c \\u%04x", c, c);
break;
}
}
}
/**
* Helper for option {@code -Dprop=v}, adding to the "post-properties". (This is a
* clash-in-waiting with Python.) The effect is slightly different from {@code -J-Dprop=v},
* which contributes to the "pre-properties".
*/
private void optionD(OptionScanner scanner) throws SecurityException {
String[] kv = scanner.getOptionArgument().split("=", 2);
String prop = kv[0].trim();
if (kv.length > 1) {
properties.put(prop, kv[1]);
} else {
properties.put(prop, "");
}
}
/**
* Set the error message as {@code String.format(message, args)} and set the action to
* {@link Action#ERROR}.
*/
private void error(String message, Object... args) {
this.message = args.length == 0 ? message : String.format(message, args);
action = Action.ERROR;
}
}
}