* 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 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, "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: