org.python.util.InteractiveConsole Maven / Gradle / Ivy
Show all versions of jython-slim Show documentation
// Copyright (c) Corporation for National Research Initiatives
package org.python.util;
import org.python.core.Py;
import org.python.core.PyBuiltinFunctionSet;
import org.python.core.PyException;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.core.PySystemState;
import org.python.core.__builtin__;
/**
* This class provides the read, execute, print loop needed by a Python console; it is not actually
* a console itself. The primary capability is the {@link #interact()} method, which repeatedly
* calls {@link #raw_input(PyObject)}, and hence {@link __builtin__#raw_input(PyObject)}, in order
* to get lines, and {@link #push(String)} them into the interpreter. The built-in
* raw_input()
method prompts on sys.stdout
and reads from
* sys.stdin
, the standard console. These may be redirected using
* {@link #setOut(java.io.OutputStream)} and {@link #setIn(java.io.InputStream)}, as may also
* sys.stderr
.
*/
// Based on CPython-1.5.2's code module
public class InteractiveConsole extends InteractiveInterpreter {
/**
* Note: This field is actually final; don't modify.
*
* To work around an issue in javadoc with Java 8 we cannot have it final for now, see
* issue 2539 for details.
*/
public static String CONSOLE_FILENAME = "";
public String filename;
/**
* Construct an interactive console, which will "run" when {@link #interact()} is called. The
* name of the console (e.g. in error messages) will be {@link #CONSOLE_FILENAME}.
*/
public InteractiveConsole() {
this(null, CONSOLE_FILENAME);
}
/**
* Construct an interactive console, which will "run" when {@link #interact()} is called. The
* name of the console (e.g. in error messages) will be {@link #CONSOLE_FILENAME}.
*
* @param locals dictionary to use, or if null
, a new empty one will be created
*/
public InteractiveConsole(PyObject locals) {
this(locals, CONSOLE_FILENAME);
}
/**
* Construct an interactive console, which will "run" when {@link #interact()} is called.
*
* @param locals dictionary to use, or if null
, a new empty one will be created
* @param filename name with which to label this console input (e.g. in error messages).
*/
public InteractiveConsole(PyObject locals, String filename) {
this(locals, filename, false);
}
/**
* Full-feature constructor for an interactive console, which will "run" when
* {@link #interact()} is called. This version allows the caller to replace the built-in
* raw_input() methods with {@link #raw_input(PyObject)} and
* {@link #raw_input(PyObject, PyObject)}, which may be overridden in a sub-class.
*
* @param locals dictionary to use, or if null
, a new empty one will be created
* @param filename name with which to label this console input
* @param replaceRawInput if true, hook this class's raw_input
into the built-ins.
*/
public InteractiveConsole(PyObject locals, String filename, boolean replaceRawInput) {
super(locals);
this.filename = filename;
if (replaceRawInput) {
PyObject newRawInput = new PyBuiltinFunctionSet("raw_input", 0, 0, 1) {
@Override
public PyObject __call__() {
return __call__(Py.EmptyString);
}
@Override
public PyObject __call__(PyObject prompt) {
return Py.newString(raw_input(prompt));
}
};
Py.getSystemState().getBuiltins().__setitem__("raw_input", newRawInput);
}
}
/**
* Operate a Python console, as in {@link #interact(String, PyObject)}, on the standard input.
* The standard input may have been redirected by {@link #setIn(java.io.InputStream)} or its
* variants. The banner (printed before first input) is obtained by calling
* {@link #getDefaultBanner()}.
*/
public void interact() {
interact(getDefaultBanner(), null);
}
/**
* Returns the banner to print before the first interaction:
* "{@code Jython on }".
*
* @return the banner.
*/
public static String getDefaultBanner() {
return String.format("Jython %s on %s", PySystemState.version,
Py.getSystemState().platform);
}
/**
* Operate a Python console by repeatedly calling {@link #raw_input(PyObject, PyObject)} and
* interpreting the lines read. An end of file causes the method to return.
*
* @param banner to print before accepting input, or if null
, no banner.
* @param file from which to read commands, or if null
, read the console.
*/
public void interact(String banner, PyObject file) {
PyObject old_ps1 = systemState.ps1;
PyObject old_ps2 = systemState.ps2;
systemState.ps1 = new PyString(">>> ");
systemState.ps2 = new PyString("... ");
try {
_interact(banner, file);
} finally {
systemState.ps1 = old_ps1;
systemState.ps2 = old_ps2;
}
}
public void _interact(String banner, PyObject file) {
if (banner != null) {
write(banner);
write("\n");
}
// Dummy exec in order to speed up response on first command
exec("2");
// System.err.println("interp2");
boolean more = false;
while (true) {
PyObject prompt = more ? systemState.ps2 : systemState.ps1;
String line;
try {
if (file == null) {
line = raw_input(prompt);
} else {
line = raw_input(prompt, file);
}
} catch (PyException exc) {
if (!exc.match(Py.EOFError)) {
throw exc;
}
if (banner != null) {
write("\n");
}
break;
} catch (Throwable t) {
// catch jline.console.UserInterruptException, rethrow as a KeyboardInterrupt
throw Py.JavaError(t);
// One would expect that it would be possible to then catch the KeyboardInterrupt at the
// bottom of this loop, however, for some reason the control-C restores the input text,
// so simply doing
// resetbuffer(); more = false;
// is not sufficient
}
more = push(line);
}
}
/**
* Push a line to the interpreter.
*
* The line should not have a trailing newline; it may have internal newlines. The line is
* appended to a buffer and the interpreter's runsource() method is called with the concatenated
* contents of the buffer as source. If this indicates that the command was executed or invalid,
* the buffer is reset; otherwise, the command is incomplete, and the buffer is left as it was
* after the line was appended. The return value is 1 if more input is required, 0 if the line
* was dealt with in some way (this is the same as runsource()).
*/
public boolean push(String line) {
if (buffer.length() > 0) {
buffer.append("\n");
}
buffer.append(line);
boolean more = runsource(buffer.toString(), filename);
if (!more) {
resetbuffer();
}
return more;
}
/**
* Write a prompt and read a line from standard input. The returned line does not include the
* trailing newline. When the user enters the EOF key sequence, EOFError is raised. The base
* implementation uses the built-in function raw_input(); a subclass may replace this with a
* different implementation.
*/
public String raw_input(PyObject prompt) {
return __builtin__.raw_input(prompt);
}
/**
* Write a prompt and read a line from a file.
*/
public String raw_input(PyObject prompt, PyObject file) {
return __builtin__.raw_input(prompt, file);
}
}