[Jython-checkins] jython (merge default -> default): Merge console work to trunk
jeff.allen
jython-checkins at python.org
Sat Sep 7 19:12:49 CEST 2013
http://hg.python.org/jython/rev/dbf21ca9391a
changeset: 7117:dbf21ca9391a
parent: 7113:be402c429680
parent: 7116:c71cbf98acf1
user: Jeff Allen <ja.py at farowl.co.uk>
date: Sat Sep 07 16:27:44 2013 +0100
summary:
Merge console work to trunk
files:
Lib/readline.py | 31 +-
build.xml | 1 +
registry | 15 +-
src/org/python/core/Console.java | 60 +
src/org/python/core/PlainConsole.java | 106 ++
src/org/python/core/Py.java | 77 +-
src/org/python/core/PySystemState.java | 524 +++++----
src/org/python/core/__builtin__.java | 39 +-
src/org/python/util/ConsoleStream.java | 242 ++++
src/org/python/util/InteractiveConsole.java | 70 +-
src/org/python/util/JLineConsole.java | 279 +++-
src/org/python/util/ReadlineConsole.java | 222 +++-
src/org/python/util/jython.java | 238 ++--
tests/java/javatests/Issue1972.java | 155 +-
tests/java/org/python/util/jythonTest.java | 108 +-
15 files changed, 1499 insertions(+), 668 deletions(-)
diff --git a/Lib/readline.py b/Lib/readline.py
--- a/Lib/readline.py
+++ b/Lib/readline.py
@@ -14,10 +14,11 @@
'set_history_length', 'set_pre_input_hook', 'set_startup_hook',
'write_history_file']
-try:
- _reader = sys._jy_interpreter.reader
+try:
+ _console = sys._jy_console
+ _reader = _console.reader
except AttributeError:
- raise ImportError("Cannot access JLineConsole")
+ raise ImportError("Cannot access JLineConsole reader")
_history_list = None
@@ -38,7 +39,7 @@
# modify the history (ipython uses the function
# remove_history_item to mutate the history relatively frequently)
global _history_list
-
+
history = _reader.history
try:
history_list_field = history.class.getDeclaredField("history")
@@ -68,7 +69,7 @@
def insert_text(string):
_reader.putString(string)
-
+
def read_init_file(filename=None):
warn("read_init_file: %s" % (filename,), NotImplementedWarning, "module", 2)
@@ -94,7 +95,7 @@
_reader.history.clear()
def add_history(line):
- _reader.addToHistory(line)
+ _reader.history.addToHistory(line)
def get_history_length():
return _reader.history.maxSize
@@ -106,7 +107,11 @@
return len(_reader.history.historyList)
def get_history_item(index):
- return _reader.history.historyList[index]
+ # JLine indexes from 0 while readline indexes from 1 (at least in test_readline)
+ if index>0:
+ return _reader.history.historyList[index-1]
+ else:
+ return None
def remove_history_item(pos):
if _history_list:
@@ -114,12 +119,18 @@
else:
warn("Cannot remove history item at position: %s" % (pos,), SecurityWarning, stacklevel=2)
+def replace_history_item(pos, line):
+ if _history_list:
+ _history_list.set(pos, line)
+ else:
+ warn("Cannot replace history item at position: %s" % (pos,), SecurityWarning, stacklevel=2)
+
def redisplay():
_reader.redrawLine()
def set_startup_hook(function=None):
- sys._jy_interpreter.startupHook = function
-
+ _console.startupHook = function
+
def set_pre_input_hook(function=None):
warn("set_pre_input_hook %s" % (function,), NotImplementedWarning, stacklevel=2)
@@ -151,7 +162,7 @@
return start
_reader.addCompletor(complete_handler)
-
+
def get_completer():
return _completer_function
diff --git a/build.xml b/build.xml
--- a/build.xml
+++ b/build.xml
@@ -936,6 +936,7 @@
<fileset dir="${test.source.dir}" includes="**/*Test*.java">
<exclude name="javatests/**/*" />
<exclude name="**/InterpTestCase.java" />
+ <exclude name="**/jythonTest*" /> <!-- Must run interactively -->
<exclude name="org/python/antlr/**" />
<exclude name=".classpath" />
<exclude name=".project" />
diff --git a/registry b/registry
--- a/registry
+++ b/registry
@@ -31,15 +31,16 @@
#python.verbose = message
# Jython ships with a JLine console (http://jline.sourceforge.net/)
-# out of the box. Setting this to the name of a different console class,
-# new console features can be enabled. Readline support is such an
-# example:
+# out of the box.
+python.console=org.python.util.JLineConsole
+# To activate explicitly the featureless Jython console, choose:
+#python.console=org.python.core.PlainConsole
+# By setting this to the name of a different console class,
+# new console features can be enabled. For example:
#python.console=org.python.util.ReadlineConsole
-#python.console.readlinelib=JavaReadline
-# To activate the legacy Jython console:
-#python.console=org.python.util.InteractiveConsole
+#python.console.readlinelib=GnuReadline
-# Setting this to a valid codec name will cause the console to use a
+# Setting this to a valid (Java) codec name will cause the console to use a
# different encoding when reading commands from the console.
#python.console.encoding = cp850
diff --git a/src/org/python/core/Console.java b/src/org/python/core/Console.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/core/Console.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2013 Jython Developers
+package org.python.core;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * A class named in configuration as the value of <code>python.console</code> must implement this
+ * interface, and provide a constructor with a single <code>String</code> argument, to be acceptable
+ * during initialization of the interpreter. The argument to the constructor names the encoding in
+ * use on the console. Such a class may provide line editing and history recall to an interactive
+ * console. A default implementation (that does not provide any such facilities) is available as
+ * {@link PlainConsole}.
+ */
+public interface Console {
+
+ /**
+ * Complete initialization and (optionally) install a stream object with line-editing as the
+ * replacement for <code>System.in</code>.
+ *
+ * @throws IOException in case of failure related to i/o
+ */
+ public void install() throws IOException;
+
+ /**
+ * Uninstall the Console (if possible). A Console that installs a replacement for
+ * <code>System.in</code> should put back the original value.
+ *
+ * @throws UnsupportedOperationException if the Console cannot be uninstalled
+ */
+ public void uninstall() throws UnsupportedOperationException;
+
+ /**
+ * 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, an EOFException should be
+ * raised. The built-in function <code>raw_input</code> calls this method on the installed
+ * console.
+ *
+ * @param prompt to output before reading a line
+ * @return the line read in (encoded as bytes)
+ * @throws IOException in case of failure related to i/o
+ * @throws EOFException when the user enters the EOF key sequence
+ */
+ public ByteBuffer raw_input(CharSequence prompt) throws IOException, EOFException;
+
+ /**
+ * 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, an EOFException should be
+ * raised. The Py3k built-in function <code>input</code> calls this method on the installed
+ * console.
+ *
+ * @param prompt to output before reading a line
+ * @return the line read in
+ * @throws IOException in case of failure related to i/o
+ * @throws EOFException when the user enters the EOF key sequence
+ */
+ public CharSequence input(CharSequence prompt) throws IOException, EOFException;
+
+}
diff --git a/src/org/python/core/PlainConsole.java b/src/org/python/core/PlainConsole.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/core/PlainConsole.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2013 Jython Developers
+package org.python.core;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+
+/**
+ * A base class for classes that can install a console wrapper for a specific console-handling
+ * library. The Jython command-line application, when it detects that the console is an interactive
+ * session, chooses and installs a class named in registry item <code>python.console</code>, and
+ * implementing interface {@link Console}. <code>PlainConsole</code> may be selected by the user
+ * through that registry, and is the default console when the selected one fails to load. It will
+ * also be installed by the Jython command-line application when in non-interactive mode.
+ * <p>
+ * Unlike some consoles, <code>PlainConsole</code> does not install a replacement for
+ * <code>System.in</code> or use a native library. It prompts on <code>System.out</code> and reads
+ * from <code>System.in</code> (wrapped with the console encoding).
+ */
+public class PlainConsole implements Console {
+
+ /** Encoding to use for line input. */
+ public final String encoding;
+
+ /** Encoding to use for line input as a <code>Charset</code>. */
+ public final Charset encodingCharset;
+
+ /** BufferedReader used by {@link #input(CharSequence)} */
+ private BufferedReader reader;
+
+ /**
+ * Construct an instance of the console class specifying the character encoding. This encoding
+ * must be one supported by the JVM. The PlainConsole does not replace <code>System.in</code>,
+ * and does not add any line-editing capability to what is standard for your OS console.
+ *
+ * @param encoding name of a supported encoding or <code>null</code> for
+ * <code>Charset.defaultCharset()</code>
+ */
+ public PlainConsole(String encoding) throws IllegalCharsetNameException,
+ UnsupportedCharsetException {
+ if (encoding == null) {
+ encoding = Charset.defaultCharset().name();
+ }
+ this.encoding = encoding;
+ encodingCharset = Charset.forName(encoding);
+ }
+
+ @Override
+ public void install() {
+ // Create a Reader with the right character encoding
+ reader = new BufferedReader(new InputStreamReader(System.in, encodingCharset));
+ }
+
+ /**
+ * A <code>PlainConsole</code> may be uninstalled. This method assumes any sub-class may not be
+ * uninstalled. Sub-classes that permit themselves to be uninstalled <b>must</b> override (and
+ * not call) this method.
+ *
+ * @throws UnsupportedOperationException unless this class is exactly <code>PlainConsole</code>
+ */
+ @Override
+ public void uninstall() throws UnsupportedOperationException {
+ Class<? extends Console> myClass = this.getClass();
+ if (myClass != PlainConsole.class) {
+ throw new UnsupportedOperationException(myClass.getSimpleName()
+ + " console may not be uninstalled.");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The base implementation calls {@link #input(CharSequence)} and applies the console encoding
+ * to obtain the bytes. This may be a surprise. Line-editing consoles necessarily operate in
+ * terms of characters rather than bytes, and therefore support a direct implementation of
+ * <code>input</code>.
+ */
+ @Override
+ public ByteBuffer raw_input(CharSequence prompt) throws IOException, EOFException {
+ CharSequence line = input(prompt);
+ return encodingCharset.encode(CharBuffer.wrap(line));
+ }
+
+ // The base implementation simply uses <code>System.out</code> and <code>System.in</code>.
+ @Override
+ public CharSequence input(CharSequence prompt) throws IOException, EOFException {
+
+ // Issue the prompt with no newline
+ System.out.print(prompt);
+
+ // Get the line from the console via java.io
+ String line = reader.readLine();
+ if (line == null) {
+ throw new EOFException();
+ } else {
+ return line;
+ }
+ }
+
+}
diff --git a/src/org/python/core/Py.java b/src/org/python/core/Py.java
--- a/src/org/python/core/Py.java
+++ b/src/org/python/core/Py.java
@@ -3,6 +3,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -17,14 +18,17 @@
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
+import java.util.ArrayList;
import java.util.Calendar;
+import java.util.List;
import java.util.Set;
-import org.python.antlr.base.mod;
import jnr.constants.Constant;
import jnr.constants.platform.Errno;
-import java.util.ArrayList;
-import java.util.List;
+import jnr.posix.POSIX;
+import jnr.posix.POSIXFactory;
+
+import org.python.antlr.base.mod;
import org.python.core.adapter.ClassicPyObjectAdapter;
import org.python.core.adapter.ExtensiblePyObjectAdapter;
import org.python.modules.posix.PosixModule;
@@ -1386,6 +1390,73 @@
getThreadState().frame = f;
}
+ /**
+ * The handler for interactive consoles, set by {@link #installConsole(Console)} and accessed by
+ * {@link #getConsole()}.
+ */
+ private static Console console;
+
+ /**
+ * Get the Jython Console (used for <code>input()</code>, <code>raw_input()</code>, etc.) as
+ * constructed and set by {@link PySystemState} initialization.
+ *
+ * @return the Jython Console
+ */
+ public static Console getConsole() {
+ if (console == null) {
+ // We really shouldn't ask for a console before PySystemState initialization but ...
+ try {
+ // ... something foolproof that we can supersede.
+ installConsole(new PlainConsole("ascii"));
+ } catch (Exception e) {
+ // This really, really shouldn't happen
+ throw Py.RuntimeError("Could not create fall-back PlainConsole: " + e);
+ }
+ }
+ return console;
+ }
+
+ /**
+ * Install the provided Console, first uninstalling any current one. The Jython Console is used
+ * for <code>raw_input()</code> etc., and may provide line-editing and history recall at the
+ * prompt. A Console may replace <code>System.in</code> with its line-editing input method.
+ *
+ * @param console The new Console object
+ * @throws UnsupportedOperationException if some prior Console refuses to uninstall
+ * @throws IOException if {@link Console#install()} raises it
+ */
+ public static void installConsole(Console console) throws UnsupportedOperationException,
+ IOException {
+ if (Py.console != null) {
+ // Some Console class already installed: may be able to uninstall
+ Py.console.uninstall();
+ Py.console = null;
+ }
+
+ // Install the specified Console
+ console.install();
+ Py.console = console;
+
+ // Cause sys (if it exists) to export the console handler that was installed
+ if (Py.defaultSystemState != null) {
+ Py.defaultSystemState.__setattr__("_jy_console", Py.java2py(console));
+ }
+ }
+
+ /**
+ * Check (using the {@link POSIX} library) whether we are in an interactive environment. Amongst
+ * other things, this affects the type of console that may be legitimately installed during
+ * system initialisation.
+ *
+ * @return
+ */
+ public static boolean isInteractive() {
+ // Decide if System.in is interactive
+ POSIX posix = POSIXFactory.getPOSIX();
+ FileDescriptor in = FileDescriptor.in;
+ return posix.isatty(in);
+ }
+
/* A collection of functions for implementing the print statement */
public static StdoutWrapper stderr;
static StdoutWrapper stdout;
diff --git a/src/org/python/core/PySystemState.java b/src/org/python/core/PySystemState.java
--- a/src/org/python/core/PySystemState.java
+++ b/src/org/python/core/PySystemState.java
@@ -2,7 +2,6 @@
package org.python.core;
import java.io.BufferedReader;
-import java.io.Console;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -11,6 +10,8 @@
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
@@ -21,26 +22,26 @@
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
-import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import jnr.posix.util.Platform;
+
import org.python.Version;
import org.python.core.adapter.ClassicPyObjectAdapter;
import org.python.core.adapter.ExtensiblePyObjectAdapter;
import org.python.core.packagecache.PackageManager;
import org.python.core.packagecache.SysPackageManager;
-import org.python.modules.Setup;
-import org.python.modules.zipimport.zipimporter;
-import org.python.util.Generic;
import org.python.expose.ExposedGet;
import org.python.expose.ExposedType;
+import org.python.modules.Setup;
+import org.python.util.Generic;
/**
* The "sys" module.
@@ -48,6 +49,7 @@
// xxx Many have lamented, this should really be a module!
// but it will require some refactoring to see this wish come true.
public class PySystemState extends PyObject implements ClassDictInit {
+
public static final String PYTHON_CACHEDIR = "python.cachedir";
public static final String PYTHON_CACHEDIR_SKIP = "python.cachedir.skip";
public static final String PYTHON_CONSOLE_ENCODING = "python.console.encoding";
@@ -63,22 +65,19 @@
public static final PyString version = new PyString(Version.getVersion());
- public static final PyTuple subversion = new PyTuple(new PyString("Jython"),
- Py.newString(""),
- Py.newString(""));
+ public static final PyTuple subversion = new PyTuple(new PyString("Jython"), Py.newString(""),
+ Py.newString(""));
- public static final int hexversion = ((Version.PY_MAJOR_VERSION << 24) |
- (Version.PY_MINOR_VERSION << 16) |
- (Version.PY_MICRO_VERSION << 8) |
- (Version.PY_RELEASE_LEVEL << 4) |
- (Version.PY_RELEASE_SERIAL << 0));
+ public static final int hexversion = ((Version.PY_MAJOR_VERSION << 24)
+ | (Version.PY_MINOR_VERSION << 16) | (Version.PY_MICRO_VERSION << 8)
+ | (Version.PY_RELEASE_LEVEL << 4) | (Version.PY_RELEASE_SERIAL << 0));
public static PyTuple version_info;
public final static int maxunicode = 1114111;
- //XXX: we should someday make this Long.MAX_VALUE, but see test_index.py
- // for tests that would need to pass but today would not.
+ // XXX: we should someday make this Long.MAX_VALUE, but see test_index.py
+ // for tests that would need to pass but today would not.
public final static int maxsize = Integer.MAX_VALUE;
public final static PyString float_repr_style = Py.newString("short");
@@ -86,31 +85,26 @@
public static boolean py3kwarning = false;
public final static Class flags = Options.class;
-
+
public static PyTuple _mercurial;
/**
* The copyright notice for this release.
*/
- public static final PyObject copyright = Py.newString(
- "Copyright (c) 2000-2013 Jython Developers.\n" +
- "All rights reserved.\n\n" +
+ public static final PyObject copyright = Py
+ .newString("Copyright (c) 2000-2013 Jython Developers.\n" + "All rights reserved.\n\n" +
- "Copyright (c) 2000 BeOpen.com.\n" +
- "All Rights Reserved.\n\n"+
+ "Copyright (c) 2000 BeOpen.com.\n" + "All Rights Reserved.\n\n" +
- "Copyright (c) 2000 The Apache Software Foundation.\n" +
- "All rights reserved.\n\n" +
+ "Copyright (c) 2000 The Apache Software Foundation.\n" + "All rights reserved.\n\n" +
- "Copyright (c) 1995-2000 Corporation for National Research "+
- "Initiatives.\n" +
- "All Rights Reserved.\n\n" +
+ "Copyright (c) 1995-2000 Corporation for National Research " + "Initiatives.\n"
+ + "All Rights Reserved.\n\n" +
- "Copyright (c) 1991-1995 Stichting Mathematisch Centrum, " +
- "Amsterdam.\n" +
- "All Rights Reserved.");
+ "Copyright (c) 1991-1995 Stichting Mathematisch Centrum, " + "Amsterdam.\n"
+ + "All Rights Reserved.");
- private static Map<String,String> builtinNames;
+ private static Map<String, String> builtinNames;
public static PyTuple builtin_module_names = null;
public static PackageManager packageManager;
@@ -182,8 +176,8 @@
private final PySystemStateCloser closer;
private static final ReferenceQueue<PySystemState> systemStateQueue =
new ReferenceQueue<PySystemState>();
- private static final ConcurrentMap<WeakReference<PySystemState>,
- PySystemStateCloser> sysClosers = Generic.concurrentMap();
+ private static final ConcurrentMap<WeakReference<PySystemState>, PySystemStateCloser> sysClosers =
+ Generic.concurrentMap();
// float_info
public static PyObject float_info;
@@ -205,7 +199,7 @@
meta_path = new PyList();
path_hooks = new PyList();
path_hooks.append(new JavaImporter());
- path_hooks.append(zipimporter.TYPE);
+ path_hooks.append(org.python.modules.zipimport.zipimporter.TYPE);
path_hooks.append(ClasspathPyImporter.TYPE);
path_importer_cache = new PyDictionary();
@@ -246,24 +240,16 @@
}
private static void checkReadOnly(String name) {
- if (name == "__dict__" ||
- name == "__class__" ||
- name == "registry" ||
- name == "exec_prefix" ||
- name == "packageManager") {
+ if (name == "__dict__" || name == "__class__" || name == "registry"
+ || name == "exec_prefix" || name == "packageManager") {
throw Py.TypeError("readonly attribute");
}
}
private static void checkMustExist(String name) {
- if (name == "__dict__" ||
- name == "__class__" ||
- name == "registry" ||
- name == "exec_prefix" ||
- name == "platform" ||
- name == "packageManager" ||
- name == "builtins" ||
- name == "warnoptions") {
+ if (name == "__dict__" || name == "__class__" || name == "registry"
+ || name == "exec_prefix" || name == "platform" || name == "packageManager"
+ || name == "builtins" || name == "warnoptions") {
throw Py.TypeError("readonly attribute");
}
}
@@ -275,7 +261,7 @@
}
for (PyFile stdStream : new PyFile[] {(PyFile)this.stdin, (PyFile)this.stdout,
- (PyFile)this.stderr}) {
+ (PyFile)this.stderr}) {
if (stdStream.isatty()) {
stdStream.encoding = encoding;
}
@@ -285,6 +271,7 @@
// might be nice to have something general here, but for now these
// seem to be the only values that need to be explicitly shadowed
private Shadow shadowing;
+
public synchronized void shadow() {
if (shadowing == null) {
shadowing = new Shadow();
@@ -292,6 +279,7 @@
}
private static class DefaultBuiltinsHolder {
+
static final PyObject builtins = fillin();
static PyObject fillin() {
@@ -355,6 +343,7 @@
}
// xxx fix this accessors
+ @Override
public PyObject __findattr_ex__(String name) {
if (name == "exc_value") {
PyException exc = Py.getThreadState().exception;
@@ -398,6 +387,7 @@
}
}
+ @Override
public void __setattr__(String name, PyObject value) {
checkReadOnly(name);
if (name == "builtins") {
@@ -418,6 +408,7 @@
}
}
+ @Override
public void __delattr__(String name) {
checkMustExist(name);
PyObject ret = getType().lookup(name); // xxx fix fix fix
@@ -434,10 +425,12 @@
}
// xxx
+ @Override
public void __rawdir__(PyDictionary accum) {
accum.update(__dict__);
}
+ @Override
public String toString() {
return "<module '" + __name__ + "' (built-in)>";
}
@@ -447,7 +440,7 @@
}
public void setrecursionlimit(int recursionlimit) {
- if(recursionlimit <= 0) {
+ if (recursionlimit <= 0) {
throw Py.ValueError("Recursion limit must be positive");
}
this.recursionlimit = recursionlimit;
@@ -486,8 +479,7 @@
/**
* Change the current working directory to the specified path.
*
- * path is assumed to be absolute and canonical (via
- * os.path.realpath).
+ * path is assumed to be absolute and canonical (via os.path.realpath).
*
* @param path a path String
*/
@@ -505,8 +497,7 @@
}
/**
- * Resolve a path. Returns the full path taking the current
- * working directory into account.
+ * Resolve a path. Returns the full path taking the current working directory into account.
*
* @param path a path String
* @return a resolved path String
@@ -516,12 +507,10 @@
}
/**
- * Resolve a path. Returns the full path taking the current
- * working directory into account.
+ * Resolve a path. Returns the full path taking the current working directory into account.
*
- * Like getPath but called statically. The current PySystemState
- * is only consulted for the current working directory when it's
- * necessary (when the path is relative).
+ * Like getPath but called statically. The current PySystemState is only consulted for the
+ * current working directory when it's necessary (when the path is relative).
*
* @param path a path String
* @return a resolved path String
@@ -539,8 +528,8 @@
File file = new File(path);
// Python considers r'\Jython25' and '/Jython25' abspaths on Windows, unlike
// java.io.File
- if (!file.isAbsolute() && (!Platform.IS_WINDOWS
- || !(path.startsWith("\\") || path.startsWith("/")))) {
+ if (!file.isAbsolute()
+ && (!Platform.IS_WINDOWS || !(path.startsWith("\\") || path.startsWith("/")))) {
if (sys == null) {
sys = Py.getSystemState();
}
@@ -557,8 +546,7 @@
exitfunc.__call__();
} catch (PyException exc) {
if (!exc.match(Py.SystemExit)) {
- Py.println(stderr,
- Py.newString("Error in sys.exitfunc:"));
+ Py.println(stderr, Py.newString("Error in sys.exitfunc:"));
}
Py.printException(exc);
}
@@ -574,18 +562,19 @@
this.classLoader = classLoader;
}
- private static String findRoot(Properties preProperties,
- Properties postProperties,
- String jarFileName)
- {
+ private static String findRoot(Properties preProperties, Properties postProperties,
+ String jarFileName) {
String root = null;
try {
- if (postProperties != null)
+ if (postProperties != null) {
root = postProperties.getProperty("python.home");
- if (root == null)
+ }
+ if (root == null) {
root = preProperties.getProperty("python.home");
- if (root == null)
+ }
+ if (root == null) {
root = preProperties.getProperty("install.root");
+ }
determinePlatform(preProperties);
} catch (Exception exc) {
@@ -640,8 +629,7 @@
}
private static void initRegistry(Properties preProperties, Properties postProperties,
- boolean standalone, String jarFileName)
- {
+ boolean standalone, String jarFileName) {
if (registry != null) {
Py.writeError("systemState", "trying to reinitialize registry");
return;
@@ -662,6 +650,7 @@
addRegistryFile(homeFile);
addRegistryFile(new File(prefix, "registry"));
} catch (Exception exc) {
+ // Continue
}
}
if (prefix != null) {
@@ -676,6 +665,7 @@
registry.setProperty("python.path", jythonpath);
}
} catch (SecurityException e) {
+ // Continue
}
registry.putAll(postProperties);
if (standalone) {
@@ -693,7 +683,7 @@
// Set up options from registry
Options.setFromRegistry();
}
-
+
/**
* @return the encoding of the underlying platform; can be <code>null</code>
*/
@@ -724,7 +714,7 @@
}
return encoding;
}
-
+
private static void addRegistryFile(File file) {
if (file.exists()) {
if (!file.isDirectory()) {
@@ -769,24 +759,18 @@
initialize(preProperties, postProperties, new String[] {""});
}
- public static synchronized void initialize(Properties preProperties,
- Properties postProperties,
- String[] argv) {
+ public static synchronized void initialize(Properties preProperties, Properties postProperties,
+ String[] argv) {
initialize(preProperties, postProperties, argv, null);
}
- public static synchronized void initialize(Properties preProperties,
- Properties postProperties,
- String[] argv,
- ClassLoader classLoader) {
+ public static synchronized void initialize(Properties preProperties, Properties postProperties,
+ String[] argv, ClassLoader classLoader) {
initialize(preProperties, postProperties, argv, classLoader, new ClassicPyObjectAdapter());
}
- public static synchronized void initialize(Properties preProperties,
- Properties postProperties,
- String[] argv,
- ClassLoader classLoader,
- ExtensiblePyObjectAdapter adapter) {
+ public static synchronized void initialize(Properties preProperties, Properties postProperties,
+ String[] argv, ClassLoader classLoader, ExtensiblePyObjectAdapter adapter) {
if (initialized) {
return;
}
@@ -807,51 +791,45 @@
}
ClassLoader sysStateLoader = PySystemState.class.getClassLoader();
if (sysStateLoader != null) {
- if (initialize(preProperties,
- postProperties,
- argv,
- classLoader,
- adapter,
- sysStateLoader)) {
+ if (initialize(preProperties, postProperties, argv, classLoader, adapter,
+ sysStateLoader)) {
return;
}
} else {
Py.writeDebug("initializer", "PySystemState.class class loader null, skipping");
}
} catch (UnsupportedCharsetException e) {
- Py.writeWarning("initializer", "Unable to load the UTF-8 charset to read an initializer definition");
+ Py.writeWarning("initializer",
+ "Unable to load the UTF-8 charset to read an initializer definition");
e.printStackTrace(System.err);
} catch (SecurityException e) {
// Must be running in a security environment that doesn't allow access to the class
// loader
} catch (Exception e) {
Py.writeWarning("initializer",
- "Unexpected exception thrown while trying to use initializer service");
+ "Unexpected exception thrown while trying to use initializer service");
e.printStackTrace(System.err);
}
doInitialize(preProperties, postProperties, argv, classLoader, adapter);
}
private static final String INITIALIZER_SERVICE =
- "META-INF/services/org.python.core.JythonInitializer";
+ "META-INF/services/org.python.core.JythonInitializer";
/**
- * Attempts to read a SystemStateInitializer service from the given classloader, instantiate it,
- * and initialize with it.
+ * Attempts to read a SystemStateInitializer service from the given class loader, instantiate
+ * it, and initialize with it.
*
- * @throws UnsupportedCharsetException
- * if unable to load UTF-8 to read a service definition
+ * @throws UnsupportedCharsetException if unable to load UTF-8 to read a service definition
* @return true if a service is found and successfully initializes.
*/
- private static boolean initialize(Properties pre,
- Properties post,
- String[] argv,
- ClassLoader sysClassLoader,
- ExtensiblePyObjectAdapter adapter,
- ClassLoader initializerClassLoader) {
+ private static boolean initialize(Properties pre, Properties post, String[] argv,
+ ClassLoader sysClassLoader, ExtensiblePyObjectAdapter adapter,
+ ClassLoader initializerClassLoader) {
InputStream in = initializerClassLoader.getResourceAsStream(INITIALIZER_SERVICE);
if (in == null) {
- Py.writeDebug("initializer", "'" + INITIALIZER_SERVICE + "' not found on " + initializerClassLoader);
+ Py.writeDebug("initializer", "'" + INITIALIZER_SERVICE + "' not found on "
+ + initializerClassLoader);
return false;
}
BufferedReader r = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8")));
@@ -873,11 +851,8 @@
return false;
}
try {
- ((JythonInitializer)initializer.newInstance()).initialize(pre,
- post,
- argv,
- sysClassLoader,
- adapter);
+ ((JythonInitializer)initializer.newInstance()).initialize(pre, post, argv,
+ sysClassLoader, adapter);
} catch (Exception e) {
Py.writeWarning("initializer", "Failed initializing with class '" + className
+ "', continuing");
@@ -891,12 +866,9 @@
return initialized;
}
-
public static synchronized PySystemState doInitialize(Properties preProperties,
- Properties postProperties,
- String[] argv,
- ClassLoader classLoader,
- ExtensiblePyObjectAdapter adapter) {
+ Properties postProperties, String[] argv, ClassLoader classLoader,
+ ExtensiblePyObjectAdapter adapter) {
if (initialized) {
return Py.defaultSystemState;
}
@@ -907,17 +879,25 @@
if (jarFileName != null) {
standalone = isStandalone(jarFileName);
}
+
// initialize the Jython registry
initRegistry(preProperties, postProperties, standalone, jarFileName);
+
// other initializations
initBuiltins(registry);
initStaticFields();
+
// Initialize the path (and add system defaults)
defaultPath = initPath(registry, standalone, jarFileName);
defaultArgv = initArgv(argv);
defaultExecutable = initExecutable(registry);
+
// Set up the known Java packages
initPackages(registry);
+
+ // Condition the console
+ initConsole(registry);
+
// Finish up standard Python initialization...
Py.defaultSystemState = new PySystemState();
Py.setSystemState(Py.defaultSystemState);
@@ -925,10 +905,16 @@
Py.defaultSystemState.setClassLoader(classLoader);
}
Py.initClassExceptions(getDefaultBuiltins());
+
// defaultSystemState can't init its own encoding, see its constructor
Py.defaultSystemState.initEncoding();
+
// Make sure that Exception classes have been loaded
new PySyntaxError("", 1, 1, "", "");
+
+ // Cause sys to export the console handler that was installed
+ Py.defaultSystemState.__setattr__("_jy_console", Py.java2py(Py.getConsole()));
+
return Py.defaultSystemState;
}
@@ -961,32 +947,33 @@
Py.stdout = new StdoutWrapper();
String s;
- if(Version.PY_RELEASE_LEVEL == 0x0A)
+ if (Version.PY_RELEASE_LEVEL == 0x0A) {
s = "alpha";
- else if(Version.PY_RELEASE_LEVEL == 0x0B)
+ } else if (Version.PY_RELEASE_LEVEL == 0x0B) {
s = "beta";
- else if(Version.PY_RELEASE_LEVEL == 0x0C)
+ } else if (Version.PY_RELEASE_LEVEL == 0x0C) {
s = "candidate";
- else if(Version.PY_RELEASE_LEVEL == 0x0F)
+ } else if (Version.PY_RELEASE_LEVEL == 0x0F) {
s = "final";
- else if(Version.PY_RELEASE_LEVEL == 0xAA)
+ } else if (Version.PY_RELEASE_LEVEL == 0xAA) {
s = "snapshot";
- else
- throw new RuntimeException("Illegal value for PY_RELEASE_LEVEL: " +
- Version.PY_RELEASE_LEVEL);
- version_info = new PyTuple(Py.newInteger(Version.PY_MAJOR_VERSION),
- Py.newInteger(Version.PY_MINOR_VERSION),
- Py.newInteger(Version.PY_MICRO_VERSION),
- Py.newString(s),
- Py.newInteger(Version.PY_RELEASE_SERIAL));
- _mercurial = new PyTuple(Py.newString("Jython"), Py.newString(Version.getHGIdentifier()),
- Py.newString(Version.getHGVersion()));
+ } else {
+ throw new RuntimeException("Illegal value for PY_RELEASE_LEVEL: "
+ + Version.PY_RELEASE_LEVEL);
+ }
+ version_info =
+ new PyTuple(Py.newInteger(Version.PY_MAJOR_VERSION),
+ Py.newInteger(Version.PY_MINOR_VERSION),
+ Py.newInteger(Version.PY_MICRO_VERSION), Py.newString(s),
+ Py.newInteger(Version.PY_RELEASE_SERIAL));
+ _mercurial =
+ new PyTuple(Py.newString("Jython"), Py.newString(Version.getHGIdentifier()),
+ Py.newString(Version.getHGVersion()));
float_info = FloatInfo.getInfo();
long_info = LongInfo.getInfo();
}
-
public static boolean isPackageCacheEnabled() {
return cachedir != null;
}
@@ -1025,8 +1012,8 @@
}
/**
- * Determine the default sys.executable value from the
- * registry. Returns Py.None is no executable can be found.
+ * Determine the default sys.executable value from the registry. Returns Py.None is no
+ * executable can be found.
*
* @param props a Properties registry
* @return a PyObject path string or Py.None
@@ -1049,6 +1036,86 @@
return new PyString(executableFile.getPath());
}
+ /**
+ * Wrap standard input with a customised console handler specified in the property
+ * <code>python.console</code> in the supplied property set, which in practice is the
+ * fully-initialised Jython {@link #registry}. The value of <code>python.console</code> is the
+ * name of a class that implements {@link org.python.core.Console}. An instance is constructed
+ * with the value of <code>python.console.encoding</code>, and the console
+ * <code>System.in</code> returns characters in that encoding. After the call, the console
+ * object may be accessed via {@link Py#getConsole()}.
+ *
+ * @param props containing (or not) <code>python.console</code>
+ */
+ private static void initConsole(Properties props) {
+ // At this stage python.console.encoding is always defined (but null=default)
+ String encoding = props.getProperty(PYTHON_CONSOLE_ENCODING);
+ // The console type is chosen by this registry entry:
+ String consoleName = props.getProperty("python.console", "").trim();
+ // And must be of type ...
+ final Class<Console> consoleType = Console.class;
+
+ if (consoleName.length() > 0 && Py.isInteractive()) {
+ try {
+ // Load the class specified as the console
+ Class<?> consoleClass = Class.forName(consoleName);
+
+ // Ensure it can be cast to the interface type of all consoles
+ if (! consoleType.isAssignableFrom(consoleClass)) {
+ throw new ClassCastException();
+ }
+
+ // Construct an instance
+ Constructor<?> consoleConstructor = consoleClass.getConstructor(String.class);
+ Object consoleObject = consoleConstructor.newInstance(encoding);
+ Console console = consoleType.cast(consoleObject);
+
+ // Replace System.in with stream this console manufactures
+ Py.installConsole(console);
+ return;
+
+ } catch (NoClassDefFoundError e) {
+ writeConsoleWarning(consoleName, "not found");
+ } catch (ClassCastException e) {
+ writeConsoleWarning(consoleName, "does not implement " + consoleType);
+ } catch (NoSuchMethodException e) {
+ writeConsoleWarning(consoleName, "has no constructor from String");
+ } catch (InvocationTargetException e) {
+ writeConsoleWarning(consoleName, e.getCause().toString());
+ } catch (Exception e) {
+ writeConsoleWarning(consoleName, e.toString());
+ }
+ }
+
+ // No special console required, or requested installation failed somehow
+ try {
+ // Default is a plain console
+ Py.installConsole(new PlainConsole(encoding));
+ return;
+ } catch (Exception e) {
+ /*
+ * May end up here if prior console won't uninstall: but then at least we have a
+ * console. Or it may be an unsupported encoding, in which case Py.getConsole() will try
+ * "ascii"
+ */
+ writeConsoleWarning(consoleName, e.toString());
+ }
+ }
+
+ /**
+ * Convenience method wrapping {@link Py#writeWarning(String, String)} to issue a warning
+ * message something like:
+ * "console: Failed to load 'org.python.util.ReadlineConsole': <b>msg</b>.". It's only a warning
+ * because the interpreter will fall back to a plain console, but it is useful to know exactly
+ * why it didn't work.
+ *
+ * @param consoleName console class name we're trying to initialise
+ * @param msg specific cause of the failure
+ */
+ private static void writeConsoleWarning(String consoleName, String msg) {
+ Py.writeWarning("console", "Failed to install '" + consoleName + "': " + msg + ".");
+ }
+
private static void addBuiltin(String name) {
String classname;
String modname;
@@ -1057,19 +1124,20 @@
if (colon != -1) {
// name:fqclassname
modname = name.substring(0, colon).trim();
- classname = name.substring(colon+1, name.length()).trim();
- if (classname.equals("null"))
+ classname = name.substring(colon + 1, name.length()).trim();
+ if (classname.equals("null")) {
// name:null, i.e. remove it
classname = null;
- }
- else {
+ }
+ } else {
modname = name.trim();
classname = "org.python.modules." + modname;
}
- if (classname != null)
+ if (classname != null) {
builtinNames.put(modname, classname);
- else
+ } else {
builtinNames.remove(modname);
+ }
}
private static void initBuiltins(Properties props) {
@@ -1080,17 +1148,19 @@
builtinNames.put("sys", "");
// add builtins specified in the Setup.java file
- for (String builtinModule : Setup.builtinModules)
+ for (String builtinModule : Setup.builtinModules) {
addBuiltin(builtinModule);
+ }
// add builtins specified in the registry file
String builtinprop = props.getProperty("python.modules.builtin", "");
StringTokenizer tok = new StringTokenizer(builtinprop, ",");
- while (tok.hasMoreTokens())
+ while (tok.hasMoreTokens()) {
addBuiltin(tok.nextToken());
+ }
int n = builtinNames.size();
- PyObject [] built_mod = new PyObject[n];
+ PyObject[] built_mod = new PyObject[n];
int i = 0;
for (String key : builtinNames.keySet()) {
built_mod[i++] = Py.newString(key);
@@ -1133,11 +1203,13 @@
JarEntry jarEntry = jarFile.getJarEntry("Lib/os.py");
standalone = jarEntry != null;
} catch (IOException ioe) {
+ // Continue
} finally {
if (jarFile != null) {
try {
jarFile.close();
} catch (IOException e) {
+ // Continue
}
}
}
@@ -1146,7 +1218,8 @@
}
/**
- * @return the full name of the jar file containing this class, <code>null</code> if not available.
+ * @return the full name of the jar file containing this class, <code>null</code> if not
+ * available.
*/
private static String getJarFileName() {
Class<PySystemState> thisClass = PySystemState.class;
@@ -1204,10 +1277,10 @@
}
private static void addPaths(PyList path, String pypath) {
- StringTokenizer tok = new StringTokenizer(pypath,
- java.io.File.pathSeparator);
- while (tok.hasMoreTokens())
+ StringTokenizer tok = new StringTokenizer(pypath, java.io.File.pathSeparator);
+ while (tok.hasMoreTokens()) {
path.append(new PyString(tok.nextToken().trim()));
+ }
}
public static PyJavaPackage add_package(String n) {
@@ -1219,31 +1292,25 @@
}
/**
- * Add a classpath directory to the list of places that are searched
- * for java packages.
+ * Add a classpath directory to the list of places that are searched for java packages.
* <p>
- * <b>Note</b>. Classes found in directory and subdirectory are not
- * made available to jython by this call. It only makes the java
- * package found in the directory available. This call is mostly
- * usefull if jython is embedded in an application that deals with
- * its own classloaders. A servlet container is a very good example.
- * Calling add_classdir("<context>/WEB-INF/classes") makes the java
- * packages in WEB-INF classes available to jython import. However the
- * actual classloading is completely handled by the servlet container's
- * context classloader.
+ * <b>Note</b>. Classes found in directory and sub-directory are not made available to jython by
+ * this call. It only makes the java package found in the directory available. This call is
+ * mostly useful if jython is embedded in an application that deals with its own class loaders.
+ * A servlet container is a very good example. Calling add_classdir("<context>/WEB-INF/classes")
+ * makes the java packages in WEB-INF classes available to jython import. However the actual
+ * classloading is completely handled by the servlet container's context classloader.
*/
public static void add_classdir(String directoryPath) {
packageManager.addDirectory(new File(directoryPath));
}
/**
- * Add a .jar & .zip directory to the list of places that are searched
- * for java .jar and .zip files. The .jar and .zip files found will not
- * be cached.
+ * Add a .jar & .zip directory to the list of places that are searched for java .jar and .zip
+ * files. The .jar and .zip files found will not be cached.
* <p>
- * <b>Note</b>. Classes in .jar and .zip files found in the directory
- * are not made available to jython by this call. See the note for
- * add_classdir(dir) for more details.
+ * <b>Note</b>. Classes in .jar and .zip files found in the directory are not made available to
+ * jython by this call. See the note for add_classdir(dir) for more details.
*
* @param directoryPath The name of a directory.
*
@@ -1254,16 +1321,14 @@
}
/**
- * Add a .jar & .zip directory to the list of places that are searched
- * for java .jar and .zip files.
+ * Add a .jar & .zip directory to the list of places that are searched for java .jar and .zip
+ * files.
* <p>
- * <b>Note</b>. Classes in .jar and .zip files found in the directory
- * are not made available to jython by this call. See the note for
- * add_classdir(dir) for more details.
+ * <b>Note</b>. Classes in .jar and .zip files found in the directory are not made available to
+ * jython by this call. See the note for add_classdir(dir) for more details.
*
* @param directoryPath The name of a directory.
- * @param cache Controls if the packages in the zip and jar
- * file should be cached.
+ * @param cache Controls if the packages in the zip and jar file should be cached.
*
* @see #add_classdir
*/
@@ -1278,8 +1343,9 @@
/* Print value except if None */
/* After printing, also assign to '_' */
/* Before, set '_' to None to avoid recursion */
- if (o == Py.None)
- return;
+ if (o == Py.None) {
+ return;
+ }
PyObject currentBuiltins = Py.getSystemState().getBuiltins();
currentBuiltins.__setitem__("_", Py.None);
@@ -1295,8 +1361,8 @@
* Exit a Python program with the given status.
*
* @param status the value to exit with
- * @exception Py.SystemExit always throws this exception.
- * When caught at top level the program will exit.
+ * @exception Py.SystemExit always throws this exception. When caught at top level the program
+ * will exit.
*/
public static void exit(PyObject status) {
throw new PyException(Py.SystemExit, status);
@@ -1311,13 +1377,12 @@
public static PyTuple exc_info() {
PyException exc = Py.getThreadState().exception;
- if(exc == null)
+ if (exc == null) {
return new PyTuple(Py.None, Py.None, Py.None);
+ }
PyObject tb = exc.traceback;
PyObject value = exc.value;
- return new PyTuple(exc.type,
- value == null ? Py.None : value,
- tb == null ? Py.None : tb);
+ return new PyTuple(exc.type, value == null ? Py.None : value, tb == null ? Py.None : tb);
}
public static void exc_clear() {
@@ -1335,8 +1400,9 @@
f = f.f_back;
--depth;
}
- if (f == null)
- throw Py.ValueError("call stack is not deep enough");
+ if (f == null) {
+ throw Py.ValueError("call stack is not deep enough");
+ }
return f;
}
@@ -1441,7 +1507,7 @@
* {@link PySystemStateCloser#cleanup()} to close resources (such as still-open files). The
* closing sequence is from last-created resource to first-created, so that dependencies between
* them are respected. (There are some amongst layers in the _io module.)
- *
+ *
* @param resourceClosers to be called in turn
*/
private static void runClosers(Set<Callable<Void>> resourceClosers) {
@@ -1467,52 +1533,68 @@
}
-class PySystemStateFunctions extends PyBuiltinFunctionSet
-{
+class PySystemStateFunctions extends PyBuiltinFunctionSet {
+
PySystemStateFunctions(String name, int index, int minargs, int maxargs) {
super(name, index, minargs, maxargs);
}
+ @Override
public PyObject __call__(PyObject arg) {
switch (index) {
- case 10:
- PySystemState.displayhook(arg);
- return Py.None;
- default:
- throw info.unexpectedCall(1, false);
+ case 10:
+ PySystemState.displayhook(arg);
+ return Py.None;
+ default:
+ throw info.unexpectedCall(1, false);
}
}
+
+ @Override
public PyObject __call__(PyObject arg1, PyObject arg2, PyObject arg3) {
switch (index) {
- case 30:
- PySystemState.excepthook(arg1, arg2, arg3);
- return Py.None;
- default:
- throw info.unexpectedCall(3, false);
+ case 30:
+ PySystemState.excepthook(arg1, arg2, arg3);
+ return Py.None;
+ default:
+ throw info.unexpectedCall(3, false);
}
}
}
+
/**
- * Value of a class or instance variable when the corresponding
- * attribute is deleted. Used only in PySystemState for now.
+ * Value of a class or instance variable when the corresponding attribute is deleted. Used only in
+ * PySystemState for now.
*/
class PyAttributeDeleted extends PyObject {
+
final static PyAttributeDeleted INSTANCE = new PyAttributeDeleted();
+
private PyAttributeDeleted() {}
- public String toString() { return ""; }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+
+ @Override
public Object __tojava__(Class c) {
- if (c == PyObject.class)
+ if (c == PyObject.class) {
return this;
+ }
// we can't quite "delete" non-PyObject attributes; settle for
// null or nothing
- if (c.isPrimitive())
+ if (c.isPrimitive()) {
return Py.NoConversion;
+ }
return null;
}
}
+
class Shadow {
+
PyObject builtins;
PyList warnoptions;
PyObject platform;
@@ -1527,13 +1609,14 @@
@ExposedType(name = "sys.float_info", isBaseType = false)
class FloatInfo extends PyTuple {
+
@ExposedGet
- public PyObject max, max_exp, max_10_exp, min, min_exp, min_10_exp, dig,
- mant_dig, epsilon, radix, rounds;
+ public PyObject max, max_exp, max_10_exp, min, min_exp, min_10_exp, dig, mant_dig, epsilon,
+ radix, rounds;
public static final PyType TYPE = PyType.fromClass(FloatInfo.class);
-
- private FloatInfo(PyObject ...vals) {
+
+ private FloatInfo(PyObject... vals) {
super(TYPE, vals);
max = vals[0];
@@ -1552,42 +1635,41 @@
static public FloatInfo getInfo() {
// max_10_exp, dig and epsilon taken from ssj library Num class
// min_10_exp, mant_dig, radix and rounds by ɲeuroburɳ (bit.ly/Iwo2LT)
- return new FloatInfo(
- Py.newFloat(Double.MAX_VALUE), // DBL_MAX
- Py.newLong(Double.MAX_EXPONENT), // DBL_MAX_EXP
- Py.newLong(308), // DBL_MIN_10_EXP
- Py.newFloat(Double.MIN_VALUE), // DBL_MIN
- Py.newLong(Double.MIN_EXPONENT), // DBL_MIN_EXP
- Py.newLong(-307), // DBL_MIN_10_EXP
- Py.newLong(10), // DBL_DIG
- Py.newLong(53), // DBL_MANT_DIG
- Py.newFloat(2.2204460492503131e-16), // DBL_EPSILON
- Py.newLong(2), // FLT_RADIX
- Py.newLong(1) // FLT_ROUNDS
+ return new FloatInfo( //
+ Py.newFloat(Double.MAX_VALUE), // DBL_MAX
+ Py.newLong(Double.MAX_EXPONENT), // DBL_MAX_EXP
+ Py.newLong(308), // DBL_MIN_10_EXP
+ Py.newFloat(Double.MIN_VALUE), // DBL_MIN
+ Py.newLong(Double.MIN_EXPONENT), // DBL_MIN_EXP
+ Py.newLong(-307), // DBL_MIN_10_EXP
+ Py.newLong(10), // DBL_DIG
+ Py.newLong(53), // DBL_MANT_DIG
+ Py.newFloat(2.2204460492503131e-16), // DBL_EPSILON
+ Py.newLong(2), // FLT_RADIX
+ Py.newLong(1) // FLT_ROUNDS
);
}
}
+
@ExposedType(name = "sys.long_info", isBaseType = false)
class LongInfo extends PyTuple {
+
@ExposedGet
public PyObject bits_per_digit, sizeof_digit;
public static final PyType TYPE = PyType.fromClass(LongInfo.class);
-
- private LongInfo(PyObject ...vals) {
+
+ private LongInfo(PyObject... vals) {
super(TYPE, vals);
bits_per_digit = vals[0];
sizeof_digit = vals[1];
}
- //XXX: I've cheated and just used the values that CPython gives me for my
- // local Ubuntu system. I'm not sure that they are correct.
+ // XXX: I've cheated and just used the values that CPython gives me for my
+ // local Ubuntu system. I'm not sure that they are correct.
static public LongInfo getInfo() {
- return new LongInfo(
- Py.newLong(30),
- Py.newLong(4)
- );
+ return new LongInfo(Py.newLong(30), Py.newLong(4));
}
}
diff --git a/src/org/python/core/__builtin__.java b/src/org/python/core/__builtin__.java
--- a/src/org/python/core/__builtin__.java
+++ b/src/org/python/core/__builtin__.java
@@ -4,18 +4,19 @@
*/
package org.python.core;
+import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.HashMap;
+import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Map;
import org.python.antlr.base.mod;
import org.python.core.util.RelativeFile;
-
+import org.python.core.util.StringUtil;
import org.python.modules._functools._functools;
class BuiltinFunctions extends PyBuiltinFunctionSet {
@@ -1010,6 +1011,14 @@
}
}
+ /**
+ * Companion to <code>raw_input</code> built-in function used when the interactive interpreter
+ * is directed to a file.
+ *
+ * @param prompt to issue at console before read
+ * @param file a file-like object to read from
+ * @return line of text from the file (encoded as bytes values compatible with PyString)
+ */
public static String raw_input(PyObject prompt, PyObject file) {
PyObject stdout = Py.getSystemState().stdout;
if (stdout instanceof PyAttributeDeleted) {
@@ -1027,16 +1036,32 @@
return data;
}
+ /**
+ * Implementation of <code>raw_input(prompt)</code> built-in function using the console
+ * directly.
+ *
+ * @param prompt to issue at console before read
+ * @return line of text from console (encoded as bytes values compatible with PyString)
+ */
public static String raw_input(PyObject prompt) {
- PyObject stdin = Py.getSystemState().stdin;
- if (stdin instanceof PyAttributeDeleted) {
- throw Py.RuntimeError("[raw_]input: lost sys.stdin");
+ try {
+ Console console = Py.getConsole();
+ ByteBuffer buf = console.raw_input(prompt.toString());
+ return StringUtil.fromBytes(buf);
+ } catch (EOFException eof) {
+ throw Py.EOFError("raw_input()");
+ } catch (IOException ioe) {
+ throw Py.IOError(ioe);
}
- return raw_input(prompt, stdin);
}
+ /**
+ * Implementation of <code>raw_input()</code> built-in function using the console directly.
+ *
+ * @return line of text from console (encoded as bytes values compatible with PyString)
+ */
public static String raw_input() {
- return raw_input(new PyString(""));
+ return raw_input(Py.EmptyString);
}
public static PyObject reduce(PyObject f, PyObject l, PyObject z) {
diff --git a/src/org/python/util/ConsoleStream.java b/src/org/python/util/ConsoleStream.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/util/ConsoleStream.java
@@ -0,0 +1,242 @@
+// Copyright (c) 2013 Jython Developers
+package org.python.util;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+/**
+ * This class is intended to replace <code>System.in</code> for use with console libraries that
+ * provide a line-oriented input mechanism. The console libraries provide a method to get the next
+ * line from the console as a String. Particular sub-classes should wrap this character-oriented
+ * method in a definition of {@link #getLine()}.
+ * <p>
+ * The libraries JLine and Java Readline have both been used to give Jython line-recall, editing and
+ * a line history preserved between sessions. Both deal with the console encoding internally, and
+ * interact with the user in terms of a buffer of characters. Our need in Jython is to access a
+ * byte-stream encoding the characters, with line-endings, since it is the text layer of the Python
+ * io stack, whether we are using the <code>io</code> module or <code>file</code> built-in, that
+ * should deal with encoding.
+ */
+public abstract class ConsoleStream extends FilterInputStream {
+
+ /**
+ * Enumeration used to specify whether an end-of-line should be added or replaced at the end of
+ * each line read. LEAVE means process the line exactly as the library returns it; ADD means
+ * always add an end-of-line; and REPLACE means strip any final '\n', '\r', or '\r\n' and add an
+ * end-of-line. The end-of-line to add is specified as a String in the constructor.
+ */
+ public enum EOLPolicy {
+ LEAVE, ADD, REPLACE
+ };
+
+ /** The {@link EOLPolicy} specified in the constructor. */
+ protected final EOLPolicy eolPolicy;
+ /** The end-of-line String specified in the constructor. */
+ protected final String eol;
+ /** The end-of-line String specified in the constructor. */
+ protected final Charset encoding;
+ /** Bytes decoded from the last line read. */
+ private ByteBuffer buf;
+ /** Empty buffer */
+ protected static final ByteBuffer EMPTY_BUF = ByteBuffer.allocate(0);
+ /** Platform-defined end-of-line for convenience */
+ protected static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+ /**
+ * Create a wrapper configured with end-of-line handling that matches the specific console
+ * library being wrapped, and a character encoding matching the expectations of the client.
+ * Since this is an abstract class, this constructor will be called as the first action of the
+ * library-specific concrete class. The end-of-line policy can be chosen from <code>LEAVE</code>
+ * (do not modify the line), <code>ADD</code> (always append <code>eol</code>, and
+ * <code>REPLACE</code> (remove a trailing '\n', '\r', or '\r\n' provided by the library, then
+ * add <code>eol</code>).
+ *
+ * @param encoding to use to encode the buffered characters
+ * @param eolPolicy choice of how to treat an end-of-line marker
+ * @param eol the end-of-line to use when <code>eolPolicy</code> is not <code>LEAVE</code>
+ */
+ ConsoleStream(Charset encoding, EOLPolicy eolPolicy, String eol) {
+
+ // Wrap original System.in so <code>StreamIO.isatty()</code> will find it reflectively
+ super(System.in);
+
+ // But our real input comes from (re-)encoding the console line
+ this.encoding = encoding;
+ this.eolPolicy = eolPolicy;
+ this.eol = eol != null ? eol : LINE_SEPARATOR;
+
+ // The logic is simpler if we always supply a buffer
+ buf = EMPTY_BUF;
+ }
+
+ /**
+ * Get one line of input from the console. Override this method with the actions specific to the
+ * library in use.
+ *
+ * @return Line entered by user
+ * @throws IOException in case of an error
+ * @throws EOFException if the library recognises an end-of-file condition
+ */
+ protected abstract CharSequence getLine() throws IOException, EOFException;
+
+ /**
+ * Get a line of text from the console and re-encode it using the console encoding to bytes that
+ * will be returned from this InputStream in subsequent read operations.
+ *
+ * @throws IOException
+ * @throws EOFException
+ */
+ private void fillBuffer() throws IOException, EOFException {
+
+ // In case we exit on an exception ...
+ buf = EMPTY_BUF;
+
+ // Bring in another line
+ CharSequence line = getLine();
+ CharBuffer cb = CharBuffer.allocate(line.length() + eol.length());
+ cb.append(line);
+
+ // Apply the EOL policy
+ switch (eolPolicy) {
+
+ case LEAVE:
+ // Do nothing
+ break;
+
+ case ADD:
+ // Always add eol
+ cb.append(eol);
+ break;
+
+ case REPLACE:
+ // Strip '\n', '\r', or '\r\n' and add eol
+ int n = cb.position() - 1;
+ if (n >= 0 && cb.charAt(n) == '\n') {
+ n -= 1;
+ }
+ if (n >= 0 && cb.charAt(n) == '\r') {
+ n -= 1;
+ }
+ cb.position(n + 1);
+ cb.append(eol);
+ break;
+ }
+
+ // Prepare to read
+ cb.flip();
+
+ // Make this line into a new buffer of encoded bytes
+ if (cb.hasRemaining()) {
+ buf = encoding.encode(cb); // includes a flip()
+ }
+ }
+
+ /**
+ * Reads the next byte of data from the buffered input line.
+ *
+ * The byte is returned as an int in the range 0 to 255. If no byte is available because the end
+ * of the stream has been recognised, the value -1 is returned. This method blocks until input
+ * data is available, the end of the stream is detected, or an exception is thrown. Normally, an
+ * empty line results in an encoded end-of-line being returned.
+ */
+ @Override
+ public int read() throws IOException {
+
+ try {
+ // Do we need to refill?
+ while (!buf.hasRemaining()) {
+ fillBuffer();
+ }
+ return buf.get() & 0xff;
+ } catch (EOFException e) {
+ // End of file condition recognised (e.g. ctrl-D, ctrl-Z)
+ return -1;
+ }
+ }
+
+ /**
+ * Reads up to len bytes of data from this input stream into an array of bytes. If len is not
+ * zero, the method blocks until some input is available; otherwise, no bytes are read and 0 is
+ * returned. This implementation calls {@link #fillBuffer()} at most once to get a line of
+ * characters from the console using {@link #getLine()}, and encodes them as bytes to be read
+ * back from the stream.
+ */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException, EOFException {
+
+ if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException();
+
+ } else {
+ try {
+ if (len > 0) {
+ // Do we need to refill? (Not if zero bytes demanded.)
+ int n = buf.remaining();
+ if (n <= 0) {
+ fillBuffer();
+ n = buf.remaining();
+ }
+
+ // Deliver all there is, or all that's wanted, whichever is less.
+ len = n < len ? n : len;
+ buf.get(b, off, len);
+ }
+ return len;
+
+ } catch (EOFException e) {
+ // Thrown from getLine
+ return -1;
+ }
+ }
+ }
+
+ /**
+ * Skip forward n bytes within the current encoded line. A call to <code>skip</code> will not
+ * result in reading a new line with {@link #getLine()}.
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ long r = buf.remaining();
+ if (n > r) {
+ n = r;
+ }
+ buf.position(buf.position() + (int)n);
+ return n;
+ }
+
+ /** The number of bytes left unread in the current encoded line. */
+ @Override
+ public int available() throws IOException {
+ return buf.remaining();
+ }
+
+ /**
+ * If possible, restore the standard <code>System.in</code>. Override this if necessary to
+ * perform close down actions on the console library, then call <code>super.close()</code>.
+ */
+ @Override
+ public void close() throws IOException {
+ // Restore original System.in
+ System.setIn(in);
+ }
+
+ /** Mark is not supported. */
+ @Override
+ public synchronized void mark(int readlimit) {}
+
+ /** Mark is not supported. */
+ @Override
+ public synchronized void reset() throws IOException {}
+
+ /** Mark is not supported. */
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+}
diff --git a/src/org/python/util/InteractiveConsole.java b/src/org/python/util/InteractiveConsole.java
--- a/src/org/python/util/InteractiveConsole.java
+++ b/src/org/python/util/InteractiveConsole.java
@@ -28,20 +28,21 @@
}
/**
- * @param replaceRawInput -
- * if true, we hook this Class's raw_input into the builtins
- * table so that clients like cmd.Cmd use it.
+ * @param replaceRawInput if true, we hook this Class's raw_input into the built-ins table so
+ * that clients like cmd.Cmd use it.
*/
public InteractiveConsole(PyObject locals, String filename, boolean replaceRawInput) {
super(locals);
this.filename = filename;
- if(replaceRawInput) {
+ 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));
}
@@ -52,9 +53,9 @@
/**
* Closely emulate the interactive Python console.
- *
- * The optional banner argument specifies the banner to print before the
- * first interaction; by default it prints "Jython <version> on <platform>".
+ *
+ * The optional banner argument specifies the banner to print before the first interaction; by
+ * default it prints "Jython <version> on <platform>".
*/
public void interact() {
interact(getDefaultBanner(), null);
@@ -65,7 +66,7 @@
}
public void interact(String banner, PyObject file) {
- if(banner != null) {
+ if (banner != null) {
write(banner);
write("\n");
}
@@ -73,17 +74,19 @@
exec("2");
// System.err.println("interp2");
boolean more = false;
- while(true) {
+ 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))
+ if (file == null) {
+ line = raw_input(prompt);
+ } else {
+ line = raw_input(prompt, file);
+ }
+ } catch (PyException exc) {
+ if (!exc.match(Py.EOFError)) {
throw exc;
+ }
write("\n");
break;
}
@@ -93,43 +96,40 @@
/**
* 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()).
+ *
+ * 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)
+ if (buffer.length() > 0) {
buffer.append("\n");
+ }
buffer.append(line);
boolean more = runsource(buffer.toString(), filename);
- if(!more)
+ 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.
+ * 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);
+ return __builtin__.raw_input(prompt, file);
}
}
diff --git a/src/org/python/util/JLineConsole.java b/src/org/python/util/JLineConsole.java
--- a/src/org/python/util/JLineConsole.java
+++ b/src/org/python/util/JLineConsole.java
@@ -1,40 +1,41 @@
-/* Copyright (c) Jython Developers */
+// Copyright (c) 2013 Jython Developers
package org.python.util;
+import java.io.EOFException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
+import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
-import jnr.constants.platform.Errno;
-
import jline.ConsoleReader;
import jline.Terminal;
import jline.WindowsTerminal;
+import jnr.constants.platform.Errno;
-import org.python.core.Py;
+import org.python.core.PlainConsole;
import org.python.core.PyObject;
/**
- * This class uses <a href="http://jline.sourceforge.net/">JLine</a> to provide
- * readline like functionality to its console without requiring native readline
- * support.
+ * This class uses <a href="http://jline.sourceforge.net/">JLine</a> to provide readline like
+ * functionality to its console without requiring native readline support.
*/
-public class JLineConsole extends InteractiveConsole {
+public class JLineConsole extends PlainConsole {
/** Main interface to JLine. */
- protected ConsoleReader reader;
+ public ConsoleReader reader;
- /** Set by readline.set_startup_hook */
+ /** Callable object set by <code>readline.set_startup_hook</code>. */
protected PyObject startup_hook;
+ /** <b>Not</b> currently set by <code>readline.set_pre_input_hook</code>. Why not? */
protected PyObject pre_input_hook;
/** Whether reader is a WindowsTerminal. */
@@ -44,62 +45,203 @@
protected static final String CTRL_Z = "\u001a";
/**
- * Errno strerrors possibly caused by a SIGSTP (ctrl-z). They may propagate up to
- * IOException messages.
+ * Errno strerrors possibly caused by a SIGSTP (ctrl-z). They may propagate up to IOException
+ * messages.
*/
- private static final List<String> SUSPENDED_STRERRORS =
- Arrays.asList(Errno.EINTR.description(), Errno.EIO.description());
+ private static final List<String> SUSPENDED_STRERRORS = Arrays.asList(
+ Errno.EINTR.description(), Errno.EIO.description());
- public JLineConsole() {
- this(null);
+ /**
+ * Construct an instance of the console class specifying the character encoding. This encoding
+ * must be one supported by the JVM.
+ * <p>
+ * Most of the initialisation is deferred to the {@link #install()} method so that any prior
+ * console can uninstall itself before we change system console settings and
+ * <code>System.in</code>.
+ *
+ * @param encoding name of a supported encoding or <code>null</code> for
+ * <code>Charset.defaultCharset()</code>
+ */
+ public JLineConsole(String encoding) {
+ /*
+ * Super-class needs the encoding in order to re-encode the characters that
+ * jline.ConsoleReader.readLine() has decoded.
+ */
+ super(encoding);
+ /*
+ * Communicate the specified encoding to JLine. jline.ConsoleReader.readLine() edits a line
+ * of characters, decoded from stdin.
+ */
+ System.setProperty("jline.WindowsTerminal.input.encoding", this.encoding);
+ System.setProperty("input.encoding", this.encoding);
+ // ... not "jline.UnixTerminal.input.encoding" as you might think, not even in JLine2
}
- public JLineConsole(PyObject locals) {
- this(locals, CONSOLE_FILENAME);
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation overrides that by setting <code>System.in</code> to a
+ * <code>FilterInputStream</code> object that wraps JLine.
+ */
+ @Override
+ public void install() {
+ Terminal.setupTerminal();
+
+ String userHomeSpec = System.getProperty("user.home", ".");
+
+ // Configure a ConsoleReader (the object that does most of the line editing).
try {
- File historyFile = new File(System.getProperty("user.home"), ".jline-jython.history");
+ /*
+ * Wrap System.out in the specified encoding. jline.ConsoleReader.readLine() echoes the
+ * line through this Writer.
+ */
+ Writer out = new PrintWriter(new OutputStreamWriter(System.out, encoding));
+
+ // Get the key bindings (built in ones treat TAB Pythonically).
+ InputStream bindings = getBindings(userHomeSpec, getClass().getClassLoader());
+
+ // Create the reader as unbuffered as possible
+ InputStream in = new FileInputStream(FileDescriptor.in);
+ reader = new ConsoleReader(in, out, bindings);
+
+ // We find the bell too noisy
+ reader.setBellEnabled(false);
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ // Access and load (if possible) the line history.
+ try {
+ File historyFile = new File(userHomeSpec, ".jline-jython.history");
reader.getHistory().setHistoryFile(historyFile);
} catch (IOException e) {
// oh well, no history from file
}
+
+ // Check for OS type
+ windows = reader.getTerminal() instanceof WindowsTerminal;
+
+ // Replace System.in
+ FilterInputStream wrapper = new Stream();
+ System.setIn(wrapper);
}
- public JLineConsole(PyObject locals, String filename) {
- super(locals, filename, true);
+ // Inherited raw_input() is adequate: calls input()
- // Disable JLine's unicode handling so it yields raw bytes
- System.setProperty("jline.UnixTerminal.input.encoding", "ISO-8859-1");
- System.setProperty("jline.WindowsTerminal.input.encoding", "ISO-8859-1");
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This console implements <code>input</code> using JLine to handle the prompt and data entry,
+ * so that the cursor may be correctly handled in relation to the prompt string.
+ */
+ @Override
+ public CharSequence input(CharSequence prompt) throws IOException, EOFException {
+ // Get the line from the console via the library
+ String line = readerReadLine(prompt.toString());
+ if (line == null) {
+ throw new EOFException();
+ } else {
+ return line;
+ }
+ }
- Terminal.setupTerminal();
- try {
- InputStream input = new FileInputStream(FileDescriptor.in);
- // Raw bytes in, so raw bytes out
- Writer output = new OutputStreamWriter(new FileOutputStream(FileDescriptor.out),
- "ISO-8859-1");
- reader = new ConsoleReader(input, output, getBindings());
- reader.setBellEnabled(false);
- } catch (IOException e) {
- throw new RuntimeException(e);
+ /**
+ * Class to wrap the line-oriented interface to JLine with an InputStream that can replace
+ * System.in.
+ */
+ private class Stream extends ConsoleStream {
+
+ /** Create a System.in replacement with JLine that adds system-specific line endings */
+ Stream() {
+ super(encodingCharset, EOLPolicy.ADD, LINE_SEPARATOR);
}
- windows = reader.getTerminal() instanceof WindowsTerminal;
+ @Override
+ protected CharSequence getLine() throws IOException, EOFException {
+
+ // Get a line and hope to be done. Suppress any remembered prompt.
+ String line = readerReadLine("");
+
+ if (!isEOF(line)) {
+ return line;
+ } else {
+ // null or ctrl-z on Windows indicates EOF
+ throw new EOFException();
+ }
+ }
+ }
+
+ /**
+ * Wrapper on reader.readLine(prompt) that deals with retries (on Unix) when the user enters
+ * cvtrl-Z to background Jython, the brings it back to the foreground. The inherited
+ * implementation says this is necessary and effective on BSD Unix.
+ *
+ * @param prompt to display
+ * @return line of text read in
+ * @throws IOException if an error occurs (other than an end of suspension)
+ * @throws EOFException if an EOF is detected
+ */
+ private String readerReadLine(String prompt) throws IOException, EOFException {
+
+ // We must be prepared to try repeatedly since the read may be interrupted.
+
+ while (true) {
+
+ try {
+ // If there's a hook, call it
+ if (startup_hook != null) {
+ startup_hook.__call__();
+ }
+ // Get a line and hope to be done.
+ String line = reader.readLine(prompt);
+ return line;
+
+ } catch (IOException ioe) {
+ // Something went wrong, or we were interrupted (seems only BSD throws this)
+ if (!fromSuspend(ioe)) {
+ // The interruption is not the result of (the end of) a ctrl-Z suspension
+ throw ioe;
+
+ } else {
+ // The interruption seems to be (return from) a ctrl-Z suspension:
+ try {
+ // Must reset JLine and continue (not repeating the prompt)
+ reader.getTerminal().initializeTerminal();
+ prompt = "";
+ } catch (Exception e) {
+ // Do our best to say what went wrong
+ throw new IOException("Failed to re-initialize JLine: " + e.getMessage());
+ }
+ }
+ }
+ }
+
}
/**
* Return the JLine bindings file.
- *
- * This handles loading the user's custom keybindings (normally JLine does) so it can
- * fallback to Jython's (which disable tab completition) when the user's are not
- * available.
- *
+ *
+ * This handles loading the user's custom key bindings (normally JLine does) so it can fall back
+ * to Jython's (which disable tab completion) when the user's are not available.
+ *
* @return an InputStream of the JLine bindings file.
*/
- protected InputStream getBindings() {
- String userBindings = new File(System.getProperty("user.home"),
- ".jlinebindings.properties").getAbsolutePath();
- File bindingsFile = new File(System.getProperty("jline.keybindings", userBindings));
+ protected static InputStream getBindings(String userHomeSpec, ClassLoader loader) {
+ // The key bindings file may be specified explicitly
+ String bindingsFileSpec = System.getProperty("jline.keybindings");
+ File bindingsFile;
+
+ if (bindingsFileSpec != null) {
+ // Bindings file explicitly specified
+ bindingsFile = new File(bindingsFileSpec);
+ } else {
+ // Otherwise try ~/.jlinebindings.properties
+ bindingsFile = new File(userHomeSpec, ".jlinebindings.properties");
+ }
+
+ // See if that file really exists (and can be read)
try {
if (bindingsFile.isFile()) {
try {
@@ -112,52 +254,14 @@
} catch (SecurityException se) {
// continue
}
- return getClass().getResourceAsStream("jline-keybindings.properties");
- }
- @Override
- public String raw_input(PyObject prompt) {
- String line = null;
- String promptString = prompt.toString();
-
- while (true) {
- try {
- if (startup_hook != null) {
- try {
- startup_hook.__call__();
- } catch (Exception ex) {
- System.err.println(ex);
- }
- }
- line = reader.readLine(promptString);
- break;
- } catch (IOException ioe) {
- if (!fromSuspend(ioe)) {
- throw Py.IOError(ioe);
- }
-
- // Hopefully an IOException caused by ctrl-z (seems only BSD throws this).
- // Must reset jline to continue
- try {
- reader.getTerminal().initializeTerminal();
- } catch (Exception e) {
- throw Py.IOError(e.getMessage());
- }
- // Don't redisplay the prompt
- promptString = "";
- }
- }
-
- if (isEOF(line)) {
- throw Py.EOFError("");
- }
-
- return line;
+ // User/specific key bindings could not be read: use the ones from the class path or jar.
+ return loader.getResourceAsStream("org/python/util/jline-keybindings.properties");
}
/**
- * Determine if the IOException was likely caused by a SIGSTP (ctrl-z). Seems only
- * applicable to BSD platforms.
+ * Determine if the IOException was likely caused by a SIGSTP (ctrl-z). Seems only applicable to
+ * BSD platforms.
*/
private boolean fromSuspend(IOException ioe) {
return !windows && SUSPENDED_STRERRORS.contains(ioe.getMessage());
@@ -177,7 +281,6 @@
return reader;
}
-
/**
* @return the startup hook (called prior to each readline)
*/
diff --git a/src/org/python/util/ReadlineConsole.java b/src/org/python/util/ReadlineConsole.java
--- a/src/org/python/util/ReadlineConsole.java
+++ b/src/org/python/util/ReadlineConsole.java
@@ -1,72 +1,208 @@
-// Copyright (c) Corporation for National Research Initiatives
+// Copyright (c) 2013 Jython Developers
package org.python.util;
import java.io.EOFException;
+import java.io.FilterInputStream;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
import org.gnu.readline.Readline;
import org.gnu.readline.ReadlineLibrary;
-
-import org.python.core.Py;
-import org.python.core.PyException;
-import org.python.core.PyObject;
-import org.python.core.PySystemState;
+import org.python.core.PlainConsole;
/**
- * Uses: <a href="http://java-readline.sourceforge.net/">Java Readline</a> <p/>
- *
- * Based on CPython-1.5.2's code module
- *
+ * Uses: <a href="http://java-readline.sourceforge.net/">Java Readline</a> to provide readline like
+ * functionality to its console through native readline support (either GNU Readline or Editline).
*/
-public class ReadlineConsole extends InteractiveConsole {
+public class ReadlineConsole extends PlainConsole {
- public String filename;
+ /**
+ * Construct an instance of the console class specifying the character encoding. This encoding
+ * must be one supported by the JVM. The particular backing library loaded will be as specified
+ * by registry item <code>python.console.readlinelib</code>, or "Editline" by default.
+ * <p>
+ * Most of the initialisation is deferred to the {@link #install()} method so that any prior
+ * console can uninstall itself before we change system console settings and
+ * <code>System.in</code>.
+ *
+ * @param encoding name of a supported encoding or <code>null</code> for
+ * <code>Charset.defaultCharset()</code>
+ */
+ public ReadlineConsole(String encoding) {
+ super(encoding);
+ /*
+ * Load the chosen native library. If it's not there, raise UnsatisfiedLinkError. We cannot
+ * fall back to Readline's Java mode since it reads from System.in, which would be pointless
+ * ... and fatal once we have replaced System.in with a wrapper on Readline.
+ */
+ String backingLib = System.getProperty("python.console.readlinelib", "Editline");
+ Readline.load(ReadlineLibrary.byName(backingLib));
- public ReadlineConsole() {
- this(null, CONSOLE_FILENAME);
+ /*
+ * The following is necessary to compensate for (a possible thinking error in) Readline's
+ * handling of the bytes returned from the library, and of the prompt.
+ */
+ String name = encodingCharset.name();
+ if (name.equals("ISO-8859-1") || name.equals("US-ASCII")) {
+ // Indicate that Readline's Latin fixation will work for this encoding
+ latin1 = null;
+ } else {
+ // We'll need the bytes-to-pointcode mapping
+ latin1 = Charset.forName("ISO-8859-1");
+ }
}
- public ReadlineConsole(PyObject locals) {
- this(locals, CONSOLE_FILENAME);
- }
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation overrides that by setting <code>System.in</code> to a
+ * <code>FilterInputStream</code> object that wraps the configured console library.
+ */
+ @Override
+ public void install() {
- public ReadlineConsole(PyObject locals, String filename) {
- super(locals, filename, true);
- String backingLib = PySystemState.registry.getProperty("python.console.readlinelib",
- "Editline");
- try {
- Readline.load(ReadlineLibrary.byName(backingLib));
- } catch(RuntimeException e) {
- // Silently ignore errors during load of the native library.
- // Will use a pure java fallback.
- }
+ // Complete the initialisation
Readline.initReadline("jython");
try {
// Force rebind of tab to insert a tab instead of complete
Readline.parseAndBind("tab: tab-insert");
+ } catch (UnsupportedOperationException uoe) {
+ // parseAndBind not supported by this readline
}
- catch (UnsupportedOperationException uoe) {
- // parseAndBind not supported by this readline
+
+ // Replace System.in
+ FilterInputStream wrapper = new Stream();
+ System.setIn(wrapper);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This console implements <code>input</code> using the configured library to handle the prompt
+ * and data entry, so that the cursor may be correctly handled in relation to the prompt string.
+ */
+ @Override
+ public ByteBuffer raw_input(CharSequence prompt) throws IOException {
+ // If Readline.readline really returned the line as typed, we could simply use:
+ // return line==null ? "" : line;
+ // Compensate for Readline.readline prompt handling
+ prompt = preEncode(prompt);
+ // Get the line from the console via the library
+ String line = Readline.readline(prompt.toString());
+ return postDecodeToBuffer(line);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This console implements <code>input</code> using the configured library to handle the prompt
+ * and data entry, so that the cursor may be correctly handled in relation to the prompt string.
+ */
+ @Override
+ public CharSequence input(CharSequence prompt) throws IOException, EOFException {
+ // Compensate for Readline.readline prompt handling
+ prompt = preEncode(prompt);
+ // Get the line from the console via the library
+ String line = Readline.readline(prompt.toString());
+ // If Readline.readline really returned the line as typed, next would have been:
+ // return line==null ? "" : line;
+ return postDecode(line);
+ }
+
+ /**
+ * Class to wrap the line-oriented interface to Readline with an InputStream that can replace
+ * System.in.
+ */
+ protected class Stream extends ConsoleStream {
+
+ /** Create a System.in replacement with Readline that adds Unix-like line endings */
+ Stream() {
+ super(encodingCharset, EOLPolicy.ADD, LINE_SEPARATOR);
+ }
+
+ @Override
+ protected CharSequence getLine() throws IOException, EOFException {
+ // The Py3k input method does exactly what we want
+ return input("");
}
}
/**
- * Write a prompt and read a line.
- *
- * The returned line does not include the trailing newline. When the user
- * enters the EOF key sequence, EOFError is raised.
- *
- * This subclass implements the functionality using JavaReadline.
+ * Encode a prompt to bytes in the console encoding and represent these bytes as the point codes
+ * of a Java String. The actual GNU readline function expects a prompt string that is C char
+ * array in the console encoding, but the wrapper <code>Readline.readline</code> acts as if this
+ * encoding is always Latin-1. This transformation compensates by encoding correctly then
+ * representing those bytes as point codes.
+ *
+ * @param prompt to display via <code>Readline.readline</code>
+ * @return encoded form of prompt
*/
- public String raw_input(PyObject prompt) {
- try {
- String line = Readline.readline(prompt == null ? "" : prompt.toString());
- return (line == null ? "" : line);
- } catch(EOFException eofe) {
- throw new PyException(Py.EOFError);
- } catch(IOException ioe) {
- throw new PyException(Py.IOError);
+ private CharSequence preEncode(CharSequence prompt) {
+ if (prompt == null || prompt.length() == 0) {
+ return "";
+ } else if (latin1 == null) {
+ // Encoding is such that readline does the right thing
+ return prompt;
+ } else {
+ // Compensate for readline prompt handling
+ CharBuffer cb = CharBuffer.wrap(prompt);
+ ByteBuffer bb = encodingCharset.encode(cb);
+ return latin1.decode(bb).toString();
}
}
+
+ /**
+ * Decode the bytes argument (a return from code>Readline.readline</code>) to the String
+ * actually entered at the console. The actual GNU readline function returns a C char array in
+ * the console encoding, but the wrapper <code>Readline.readline</code> acts as if this encoding
+ * is always Latin-1, and on this basis it gives us a Java String whose point codes are the
+ * encoded bytes. This method gets the bytes back, then decodes them correctly to a String.
+ *
+ * @param bytes encoded line (or <code>null</code> for an empty line)
+ * @return bytes recovered from the argument
+ */
+ private CharSequence postDecode(String line) {
+ if (line == null) {
+ // Library returns null for an empty line
+ return "";
+ } else if (latin1 == null) {
+ // Readline's assumed Latin-1 encoding will have produced the correct result
+ return line;
+ } else {
+ // We have to transcode the line
+ CharBuffer cb = CharBuffer.wrap(line);
+ ByteBuffer bb = latin1.encode(cb);
+ return encodingCharset.decode(bb).toString();
+ }
+ }
+
+ /**
+ * Decode the line (a return from code>Readline.readline</code>) to bytes in the console
+ * encoding. The actual GNU readline function returns a C char array in the console encoding,
+ * but the wrapper <code>Readline.readline</code> acts as if this encoding is always Latin-1,
+ * and on this basis it gives us a Java String whose point codes are the encoded bytes. This
+ * method gets the bytes back.
+ *
+ * @param bytes encoded line (or <code>null</code> for an empty line)
+ * @return bytes recovered from the argument
+ */
+ private ByteBuffer postDecodeToBuffer(String line) {
+ if (line == null) {
+ // Library returns null for an empty line
+ return ConsoleStream.EMPTY_BUF;
+ } else if (latin1 == null) {
+ // Readline's assumed Latin-1 encoding will have produced the correct result
+ return encodingCharset.encode(line);
+ } else {
+ // We have to transcode the line
+ CharBuffer cb = CharBuffer.wrap(line);
+ return latin1.encode(cb);
+ }
+ }
+
+ private final Charset latin1;
+
}
diff --git a/src/org/python/util/jython.java b/src/org/python/util/jython.java
--- a/src/org/python/util/jython.java
+++ b/src/org/python/util/jython.java
@@ -2,6 +2,7 @@
package org.python.util;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -14,6 +15,9 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import jnr.posix.POSIX;
+import jnr.posix.POSIXFactory;
+
import org.python.Version;
import org.python.core.CodeFlag;
import org.python.core.CompileMode;
@@ -33,67 +37,68 @@
import org.python.modules.thread.thread;
public class jython {
+
private static final String COPYRIGHT =
- "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.";
+ "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.";
static final String usageHeader =
- "usage: jython [option] ... [-c cmd | -m mod | file | -] [arg] ...\n";
+ "usage: jython [option] ... [-c cmd | -m mod | file | -] [arg] ...\n";
- private static final String usage = usageHeader +
- "Options and arguments:\n" + //(and corresponding environment variables):\n" +
- "-B : don't write .py[co] 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 PYTHONPATH)\n" +
- "-C codec : Use a different codec when reading from the console.\n"+
- "-h : print this help message and exit (also --help)\n" +
- "-i : inspect interactively after running script\n" + //, (also PYTHONINSPECT=x)\n" +
- " and force prompts, even if stdin does not appear to be a terminal\n" +
- "-jar jar : program read from __run__.py in jar file\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 (trace import statements)\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" +
- "JYTHONPATH: '" + File.pathSeparator +
- "'-separated list of directories prefixed to the default module\n" +
- " search path. The result is sys.path.";
+ private static final String usage = usageHeader
+ + "Options and arguments:\n"
+ // + "(and corresponding environment variables):\n"
+ + "-B : don't write .py[co] 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 PYTHONPATH)\n"
+ + "-C codec : Use a different codec when reading from the console.\n"
+ + "-h : print this help message and exit (also --help)\n"
+ + "-i : inspect interactively after running script\n"
+ // + ", (also PYTHONINSPECT=x)\n"
+ + " and force prompts, even if stdin does not appear to be a terminal\n"
+ + "-jar jar : program read from __run__.py in jar file\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 (trace import statements)\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" + "JYTHONPATH: '" + File.pathSeparator
+ + "'-separated list of directories prefixed to the default module\n"
+ + " search path. The result is sys.path.";
public static boolean shouldRestart;
/**
- * Runs a JAR file, by executing the code found in the file __run__.py,
- * which should be in the root of the JAR archive.
+ * 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 the __name__ is set to the base name of the JAR file and
+ * not to "__main__" (for historic reasons). This method do NOT handle exceptions. the caller
+ * SHOULD handle any (Py)Exceptions thrown by the code.
*
- * Note that the __name__ is set to the base name of the JAR file and not
- * to "__main__" (for historic reasons).
- *
- * This method do NOT handle exceptions. the caller SHOULD handle any
- * (Py)Exceptions thrown by the code.
- *
- * @param filename The path to the filename to run.
+ * @param filename The path to the filename to run.
*/
public static void 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 (preferrably, IMO), __run__.py should be imported and a main()
- // function extracted. This function should be called passing zipfile in as an
+ // 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 (?)
@@ -146,7 +151,6 @@
return opts;
}
-
for (String opt : envVar.split(",")) {
opt = opt.trim();
if (opt.length() == 0) {
@@ -155,13 +159,14 @@
opts.add(opt);
}
} catch (SecurityException e) {
+ // Continue
}
return opts;
}
- private static List<String> validWarnActions = Arrays.asList(
- "error", "ignore", "always", "default", "module", "once");
+ private static List<String> validWarnActions = Arrays.asList("error", "ignore", "always",
+ "default", "module", "once");
private static void addWarnings(List<String> from, PyList to) {
outerLoop : for (String opt : from) {
@@ -170,14 +175,14 @@
if (validWarnAction.startsWith(action)) {
if (opt.contains(":")) {
to.append(Py.newString(validWarnAction + opt.substring(opt.indexOf(":"))));
- }
- else {
+ } else {
to.append(Py.newString(validWarnAction));
}
continue outerLoop;
}
}
- System.err.println(String.format("Invalid -W option ignored: invalid action: '%s'", action));
+ System.err.println(String.format("Invalid -W option ignored: invalid action: '%s'",
+ action));
}
}
@@ -200,8 +205,17 @@
System.exit(exitcode);
}
+ // Get system properties (or empty set if we're prevented from accessing them)
+ Properties preProperties = PySystemState.getBaseProperties();
+
+ // Decide if System.in is interactive
+ if (!opts.fixInteractive || opts.interactive) {
+ // The options suggest System.in is interactive: but only if isatty() agrees
+ opts.interactive = Py.isInteractive();
+ }
+
// Setup the basic python system state from these options
- PySystemState.initialize(PySystemState.getBaseProperties(), opts.properties, opts.argv);
+ PySystemState.initialize(preProperties, opts.properties, opts.argv);
PySystemState systemState = Py.getSystemState();
PyList warnoptions = new PyList();
@@ -215,18 +229,12 @@
imp.load("warnings");
}
-
- // Decide if stdin is interactive
- if (!opts.fixInteractive || opts.interactive) {
- opts.interactive = ((PyFile)Py.defaultSystemState.stdin).isatty();
- if (!opts.interactive) {
- systemState.ps1 = systemState.ps2 = Py.EmptyString;
- }
+ // Now create an interpreter
+ if (!opts.interactive) {
+ // Not (really) interactive, so do not use console prompts
+ systemState.ps1 = systemState.ps2 = Py.EmptyString;
}
-
- // Now create an interpreter
- InteractiveConsole interp = newInterpreter(opts.interactive);
- systemState.__setattr__("_jy_interpreter", Py.java2py(interp));
+ InteractiveConsole interp = new InteractiveConsole();
// Print banner and copyright information (or not)
if (opts.interactive && opts.notice && !opts.runModule) {
@@ -265,21 +273,21 @@
if (opts.filename != null) {
String path;
try {
- path = new File(opts.filename).getCanonicalFile().getParent();
+ path = new File(opts.filename).getCanonicalFile().getParent();
} catch (IOException ioe) {
- path = new File(opts.filename).getAbsoluteFile().getParent();
+ path = new File(opts.filename).getAbsoluteFile().getParent();
}
if (path == null) {
path = "";
}
Py.getSystemState().path.insert(0, new PyString(path));
if (opts.jar) {
- try {
- runJar(opts.filename);
- } catch (Throwable t) {
+ try {
+ runJar(opts.filename);
+ } catch (Throwable t) {
Py.printException(t);
- System.exit(-1);
- }
+ System.exit(-1);
+ }
} else if (opts.filename.equals("-")) {
try {
interp.globals.__setitem__(new PyString("__file__"), new PyString("<stdin>"));
@@ -289,29 +297,29 @@
}
} else {
try {
- interp.globals.__setitem__(new PyString("__file__"),
- new PyString(opts.filename));
+ interp.globals.__setitem__(new PyString("__file__"),
+ new PyString(opts.filename));
- FileInputStream file;
- try {
- file = new FileInputStream(new RelativeFile(opts.filename));
- } catch (FileNotFoundException e) {
- throw Py.IOError(e);
- }
- try {
- if (PosixModule.getPOSIX().isatty(file.getFD())) {
- opts.interactive = true;
- interp.interact(null, new PyFile(file));
- return;
- } else {
- interp.execfile(file, opts.filename);
- }
- } finally {
- file.close();
- }
+ FileInputStream file;
+ try {
+ file = new FileInputStream(new RelativeFile(opts.filename));
+ } catch (FileNotFoundException e) {
+ throw Py.IOError(e);
+ }
+ try {
+ if (PosixModule.getPOSIX().isatty(file.getFD())) {
+ opts.interactive = true;
+ interp.interact(null, new PyFile(file));
+ return;
+ } else {
+ interp.execfile(file, opts.filename);
+ }
+ } finally {
+ file.close();
+ }
} catch (Throwable t) {
if (t instanceof PyException
- && ((PyException)t).match(_systemrestart.SystemRestart)) {
+ && ((PyException)t).match(_systemrestart.SystemRestart)) {
// Shutdown this instance...
shouldRestart = true;
shutdownInterpreter();
@@ -327,10 +335,9 @@
}
}
}
- }
- else {
+ } else {
// if there was no file name on the command line, then "" is the first element
- // on sys.path. This is here because if there /was/ a filename on the c.l.,
+ // on sys.path. This is here because if there /was/ a filename on the c.l.,
// and say the -i option was given, sys.path[0] will have gotten filled in
// with the dir of the argument filename.
Py.getSystemState().path.insert(0, Py.EmptyString);
@@ -367,8 +374,8 @@
if (opts.encoding != null) {
if (!Charset.isSupported(opts.encoding)) {
System.err.println(opts.encoding
- + " is not a supported encoding on this JVM, so it can't "
- + "be used in python.console.encoding.");
+ + " is not a supported encoding on this JVM, so it can't "
+ + "be used in python.console.encoding.");
System.exit(1);
}
interp.cflags.encoding = opts.encoding;
@@ -383,31 +390,6 @@
}
/**
- * Returns a new python interpreter using the InteractiveConsole subclass from the
- * <tt>python.console</tt> registry key.
- * <p>
-
- * When stdin is interactive the default is {@link JLineConsole}. Otherwise the
- * featureless {@link InteractiveConsole} is always used as alternative consoles cause
- * unexpected behavior with the std file streams.
- */
- private static InteractiveConsole newInterpreter(boolean interactiveStdin) {
- if (!interactiveStdin) {
- return new InteractiveConsole();
- }
-
- String interpClass = PySystemState.registry.getProperty("python.console", "");
- if (interpClass.length() > 0) {
- try {
- return (InteractiveConsole)Class.forName(interpClass).newInstance();
- } catch (Throwable t) {
- // fall through
- }
- }
- return new JLineConsole();
- }
-
- /**
* Run any finalizations on the current interpreter in preparation for a SytemRestart.
*/
public static void shutdownInterpreter() {
@@ -424,7 +406,9 @@
}
}
+
class CommandLineOptions {
+
public String filename;
public boolean jar, interactive, notice;
public boolean runCommand, runModule;
@@ -455,7 +439,7 @@
// continue
}
}
-
+
private boolean argumentExpected(String arg) {
System.err.println("Argument expected for the " + arg + " option");
return false;
@@ -492,7 +476,7 @@
} else if (arg.equals("-vv")) {
Options.verbose += 2;
} else if (arg.equals("-vvv")) {
- Options.verbose +=3 ;
+ Options.verbose += 3;
} else if (arg.equals("-s")) {
Options.no_user_site = true;
} else if (arg.equals("-S")) {
@@ -525,7 +509,7 @@
encoding = args[++index];
setProperty("python.console.encoding", encoding);
} else if (arg.equals("-E")) {
- // XXX: accept -E (ignore environment variables) to be compatiable with
+ // XXX: accept -E (ignore environment variables) to be compatible with
// CPython. do nothing for now (we could ignore the registry)
Options.ignore_environment = true;
} else if (arg.startsWith("-D")) {
diff --git a/tests/java/javatests/Issue1972.java b/tests/java/javatests/Issue1972.java
--- a/tests/java/javatests/Issue1972.java
+++ b/tests/java/javatests/Issue1972.java
@@ -14,6 +14,7 @@
import java.util.List;
import java.util.Properties;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.regex.Pattern;
import org.junit.After;
import org.junit.Test;
@@ -26,9 +27,7 @@
* debugging of the subprocess.
* <p>
* This test passes in Jython 2.5.2 and 2.5.4rc1. The test {@link #jythonReadline()} fails with
- * Jython 2.5.3. The test will fail the first time it is run on a clean build, or after switching
- * Jython versions (JAR files). This is because it monitors stderr from the subprocess and does not
- * expect the messages the cache manager produces on a first run.
+ * Jython 2.5.3.
* <p>
* The bulk of this program is designed to be run as JUnit tests, but it also has a
* {@link #main(String[])} method that echoes <code>System.in</code> onto <code>System.out</code>
@@ -46,7 +45,13 @@
static int DEBUG_PORT = 0; // 8000 or 0
/** Control the amount of output to the console: 0, 1 or 2. */
- static int VERBOSE = 2;
+ static int VERBOSE = 0;
+
+ /** Lines in stdout (as regular expressions) to ignore when checking subprocess output. */
+ static String[] STDOUT_IGNORE = {"^Listening for transport dt_socket"};
+
+ /** Lines in stderr (as regular expressions) to ignore when checking subprocess output. */
+ static String[] STDERR_IGNORE = {"^Jython 2", "^\\*sys-package-mgr"};
/**
* Extra JVM options used when debugging is enabled. <code>DEBUG_PORT</code> will be substituted
@@ -81,7 +86,7 @@
/**
* Check that on this system we know how to launch and read the error output from a subprocess.
- *
+ *
* @throws IOException
*/
@Test
@@ -103,7 +108,7 @@
/**
* Check that on this system we know how to launch and read standard output from a subprocess.
- *
+ *
* @throws IOException
*/
@Test
@@ -122,7 +127,7 @@
/**
* Check that on this system we know how to launch, write to and read from a subprocess.
- *
+ *
* @throws IOException
*/
@Test
@@ -152,7 +157,7 @@
* <code>System.in</code> in the subprocess, which of course writes hex to
* <code>System.out</code> but that data is not received back in the parent process until
* <code>System.out.println()</code> is called in the subprocess.
- *
+ *
* @throws IOException
*/
@Test
@@ -176,7 +181,7 @@
/**
* Test reading back from Jython subprocess with program on command-line.
- *
+ *
* @throws Exception
*/
@Test
@@ -195,43 +200,42 @@
/**
* Discover what is handling the "console" when the program is on the command line only.
- *
+ *
* @throws Exception
*/
@Test
- public void jythonNonInteractiveConsole() throws Exception {
+ public void jythonNonInteractive() throws Exception {
announceTest(VERBOSE, "jythonNonInteractiveConsole");
// Run Jython enquiry about console as -c program
setProcJava("org.python.util.jython", "-c",
- "import sys; print type(sys._jy_interpreter).__name__; print sys.stdin.isatty()");
+ "import sys; print type(sys._jy_console).__name__; print sys.stdin.isatty()");
proc.waitFor();
outputAsStrings(VERBOSE, inFromProc, errFromProc);
checkErrFromProc();
- checkInFromProc("InteractiveConsole", "False");
+ checkInFromProc("PlainConsole", "False");
}
/**
* Discover what is handling the "console" when the program is entered interactively at the
* Jython prompt.
- *
+ *
* @throws Exception
*/
@Test
- public void jythonInteractiveConsole() throws Exception {
+ public void jythonInteractive() throws Exception {
announceTest(VERBOSE, "jythonInteractiveConsole");
// Run Jython with simple actions at the command prompt
setProcJava( //
- "-Dpython.console=org.python.util.InteractiveConsole", //
"-Dpython.home=" + pythonHome, //
"org.python.util.jython");
writeToProc("12+3\n");
writeToProc("import sys\n");
- writeToProc("print type(sys._jy_interpreter).__name__\n");
+ writeToProc("print type(sys._jy_console).__name__\n");
writeToProc("print sys.stdin.isatty()\n");
toProc.close();
proc.waitFor();
@@ -239,13 +243,13 @@
outputAsStrings(VERBOSE, inFromProc, errFromProc);
checkErrFromProc(""); // stderr produces one empty line. Why?
- checkInFromProc("15", "InteractiveConsole", "False");
+ checkInFromProc("15", "PlainConsole", "False");
}
/**
* Discover what is handling the "console" when the program is entered interactively at the
* Jython prompt, and we try to force use of JLine (which fails).
- *
+ *
* @throws Exception
*/
@Test
@@ -260,7 +264,7 @@
writeToProc("12+3\n");
writeToProc("import sys\n");
- writeToProc("print type(sys._jy_interpreter).__name__\n");
+ writeToProc("print type(sys._jy_console).__name__\n");
writeToProc("print sys.stdin.isatty()\n");
toProc.close();
proc.waitFor();
@@ -269,13 +273,13 @@
checkErrFromProc(""); // stderr produces one empty line. Why?
- // Although we asked for it, a subprocess doesn't get JLine, and isatty() is false
- checkInFromProc("15", "InteractiveConsole", "False");
+ // We can specify JLineConsole, but isatty() is not fooled.
+ checkInFromProc("15", "PlainConsole", "False");
}
/**
* Test writing to and reading back from Jython subprocess with echo program on command-line.
- *
+ *
* @throws Exception
*/
@Test
@@ -284,6 +288,9 @@
// Run Jython simple readline programme
setProcJava( //
+ "-Dpython.console=org.python.util.JLineConsole", //
+ // "-Dpython.console.interactive=True", //
+ "-Dpython.home=" + pythonHome, //
"org.python.util.jython", //
"-c", //
"import sys; sys.stdout.write(sys.stdin.readline()); sys.stdout.flush();" //
@@ -338,7 +345,7 @@
* <td>echo the characters as hexadecimal</td>
* </tr>
* </table>
- *
+ *
* @param args
* @throws IOException
*/
@@ -373,7 +380,7 @@
/**
* Invoke the java command with the given arguments. The class path will be the same as this
* programme's class path (as in the property <code>java.class.path</code>).
- *
+ *
* @param args further arguments to the program run
* @return the running process
* @throws IOException
@@ -413,7 +420,7 @@
* programme's class path (as in the property <code>java.class.path</code>). After the call,
* {@link #proc} references the running process and {@link #inFromProc} and {@link #errFromProc}
* are handling the <code>stdout</code> and <code>stderr</code> of the subprocess.
- *
+ *
* @param args further arguments to the program run
* @throws IOException
*/
@@ -427,7 +434,7 @@
/**
* Write this string into the <code>stdin</code> of the subprocess. The platform default
* encoding will be used.
- *
+ *
* @param s to write
* @throws IOException
*/
@@ -441,14 +448,15 @@
* {@link #escape(byte[])} transormation has been applied, are expected to be equal to the
* strings supplied, optionally after {@link #escapedSeparator} has been added to the expected
* strings.
- *
+ *
* @param message identifies the queue in error message
* @param addSeparator if true, system-defined line separator expected
* @param queue to be compared
+ * @param toIgnore patterns defining lines to ignore while processing
* @param expected lines of text (given without line separators)
*/
private void checkFromProc(String message, boolean addSeparator, LineQueue queue,
- String... expected) {
+ List<Pattern> toIgnore, String... expected) {
if (addSeparator) {
// Each expected string must be treated as if the lineSeparator were appended
@@ -470,29 +478,73 @@
// Count through the results, stopping when either results or expected strings run out
int count = 0;
for (String r : results) {
- if (count < expected.length) {
+ if (count >= expected.length) {
+ break;
+ } else if (!matchesAnyOf(r, toIgnore)) {
assertEquals(message, expected[count++], r);
- } else {
- break;
}
}
assertEquals(message, expected.length, results.size());
}
+ /** Compiled regular expressions for the lines to ignore (on stdout). */
+ private static List<Pattern> stdoutIgnore;
+
+ /** Compiled regular expressions for the lines to ignore (on stderr). */
+ private static List<Pattern> stderrIgnore;
+
+ /** If not already done, compile the regular expressions we need. */
+ private static void compileToIgnore() {
+ if (stdoutIgnore == null || stderrIgnore == null) {
+ // Compile the lines to ignore to Pattern objects
+ stdoutIgnore = compileAll(STDOUT_IGNORE);
+ stderrIgnore = compileAll(STDERR_IGNORE);
+ }
+ }
+
+ /** If not already done, compile one set of regular expressions to patterns. */
+ private static List<Pattern> compileAll(String[] regex) {
+ List<Pattern> result = new LinkedList<Pattern>();
+ if (regex != null) {
+ for (String s : regex) {
+ Pattern p = Pattern.compile(s);
+ result.add(p);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Compute whether a string matches any of a set of strings.
+ *
+ * @param s the string in question
+ * @param patterns to check against
+ * @return
+ */
+ private static boolean matchesAnyOf(String s, List<Pattern> patterns) {
+ for (Pattern p : patterns) {
+ if (p.matcher(s).matches()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Check lines of {@link #inFromProc} against expected text.
- *
+ *
* @param addSeparator if true, system-defined line separator expected
* @param expected lines of text (given without line separators)
*/
private void checkInFromProc(boolean addSeparator, String... expected) {
- checkFromProc("subprocess stdout", addSeparator, inFromProc, expected);
+ compileToIgnore(); // Make sure we have the matcher patterns
+ checkFromProc("subprocess stdout", addSeparator, inFromProc, stdoutIgnore, expected);
}
/**
* Check lines of {@link #inFromProc} against expected text. Lines from the subprocess are
* expected to be equal to those supplied after {@link #escapedSeparator} has been added.
- *
+ *
* @param expected lines of text (given without line separators)
*/
private void checkInFromProc(String... expected) {
@@ -501,18 +553,19 @@
/**
* Check lines of {@link #errFromProc} against expected text.
- *
+ *
* @param addSeparator if true, system-defined line separator expected
* @param expected lines of text (given without line separators)
*/
private void checkErrFromProc(boolean addSeparator, String... expected) {
- checkFromProc("subprocess stderr", addSeparator, errFromProc, expected);
+ compileToIgnore(); // Make sure we have the matcher patterns
+ checkFromProc("subprocess stderr", addSeparator, errFromProc, stderrIgnore, expected);
}
/**
* Check lines of {@link #errFromProc} against expected text. Lines from the subprocess are
* expected to be equal to those supplied after {@link #escapedSeparator} has been added.
- *
+ *
* @param expected lines of text (given without line separators)
*/
private void checkErrFromProc(String... expected) {
@@ -521,7 +574,7 @@
/**
* Brevity for announcing tests on the console when that is used to dump values.
- *
+ *
* @param verbose if <1 suppress output
* @param name of test
*/
@@ -533,7 +586,7 @@
/**
* Output is System.out the formatted strings representing lines from a subprocess stdout.
- *
+ *
* @param verbose if <2 suppress output
* @param inFromProc lines received from the stdout of a subprocess
*/
@@ -546,7 +599,7 @@
/**
* Output is System.out the formatted strings representing lines from a subprocess stdout, and
* if there are any, from stderr.
- *
+ *
* @param verbose if <2 suppress output
* @param inFromProc lines received from the stdout of a subprocess
* @param errFromProc lines received from the stderr of a subprocess
@@ -559,7 +612,7 @@
/**
* Output is System.out a hex dump of lines from a subprocess stdout.
- *
+ *
* @param verbose if <2 suppress output
* @param inFromProc lines received from the stdout of a subprocess
*/
@@ -572,7 +625,7 @@
/**
* Output is System.out a hex dump of lines from a subprocess stdout, and if there are any, from
* stderr.
- *
+ *
* @param verbose if <2 suppress output
* @param inFromProc lines received from the stdout of a subprocess
* @param errFromProc lines received from the stderr of a subprocess
@@ -586,7 +639,7 @@
/**
* Output is System.out the formatted strings representing lines from a subprocess stdout, and
* if there are any, from stderr.
- *
+ *
* @param stdout to output labelled "Output stream:"
* @param stderr to output labelled "Error stream:" unless an empty list or null
*/
@@ -612,7 +665,7 @@
/**
* Helper to format one line of string output hex-escaping non-ASCII characters.
- *
+ *
* @param sb to overwrite with the line of dump output
* @param bb from which to take the bytes
*/
@@ -646,7 +699,7 @@
/**
* Convert bytes (interpreted as ASCII) to String where the non-ascii characters are escaped.
- *
+ *
* @param b
* @return
*/
@@ -676,7 +729,7 @@
/**
* Wrap a stream in the reader and immediately begin reading it.
- *
+ *
* @param in
*/
LineQueue(InputStream in) {
@@ -701,7 +754,7 @@
/**
* Scan every byte read from the input and squirrel them away in buffers, one per line,
* where lines are delimited by \r, \n or \r\n..
- *
+ *
* @throws IOException
*/
private void runScribe() throws IOException {
@@ -754,7 +807,7 @@
/**
* Return the contents of the queue as a list of escaped strings, interpreting the bytes as
* ASCII.
- *
+ *
* @return contents as strings
*/
public List<String> asStrings() {
@@ -774,7 +827,7 @@
/**
* Return a hex dump the contents of the object as a list of strings
- *
+ *
* @return dump as strings
*/
public List<String> asHexDump() {
@@ -799,7 +852,7 @@
/**
* Helper to format one line of hex dump output up to a maximum number of bytes.
- *
+ *
* @param sb to overwrite with the line of dump output
* @param bb from which to take the bytes
* @param n number of bytes to take (up to <code>len</code>)
diff --git a/tests/java/org/python/util/jythonTest.java b/tests/java/org/python/util/jythonTest.java
--- a/tests/java/org/python/util/jythonTest.java
+++ b/tests/java/org/python/util/jythonTest.java
@@ -1,88 +1,44 @@
package org.python.util;
-import java.lang.reflect.Method;
-import java.util.Properties;
+import static org.junit.Assert.*;
-import org.python.core.PySystemState;
-
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.python.core.Console;
+import org.python.core.PlainConsole;
+import org.python.core.Py;
/**
- * Tests for creating the right interactive console.
+ * Tests of creating and getting the right interactive console.
+ * <p>
+ * System initialisation is a one-time thing normally, and the embedding of a console handler
+ * similarly, so it is difficult to test more than one console choice in a single executable. For
+ * this reason, there are two programs like this one: one that follows the native preference for a
+ * {@link PlainConsole} and this one that induces selection of a {@link JLineConsole}. Other
+ * features of the JLine console (such as access history) could be tested here. But the test
+ * Lib/test/test_readline.py does this fairly well, although it has to be run manually.
+ * <p>
+ * Automated testing of the console seems impossible since, in a scripted context (e.g. as a
+ * subprocess or under Ant) Jython is no longer interactive. To run it at the prompt, suggested
+ * idiom is (all one line):
+ *
+ * <pre>
+ * java -cp build/exposed;build/classes;extlibs/* -Dpython.home=dist
+ * org.junit.runner.JUnitCore org.python.util.jythonTest
+ * </pre>
*/
-public class jythonTest extends TestCase {
+public class jythonTest {
- private static final String PYTHON_CONSOLE = "python.console";
-
- private Properties _originalRegistry;
-
- @Override
- protected void setUp() throws Exception {
- _originalRegistry = PySystemState.registry;
- Properties registry;
- if (_originalRegistry != null) {
- registry = new Properties(_originalRegistry);
- } else {
- registry = new Properties();
- }
- PySystemState.registry = registry;
- }
-
- @Override
- protected void tearDown() throws Exception {
- PySystemState.registry = _originalRegistry;
- }
+ private static String[] commands = {"-c", "import sys; print type(sys._jy_console)"};
/**
- * test the default behavior
- *
- * @throws Exception
+ * Test that the default behaviour is to provide a JLineConsole. If CALL_RUN is true, it fails
+ * under Ant (or Eclipse) as the console is not then recognised to be interactive.
*/
- public void testNewInterpreter() throws Exception {
- assertEquals(JLineConsole.class, invokeNewInterpreter(true).getClass());
- }
-
- /**
- * test registry override
- *
- * @throws Exception
- */
- public void testNewInterpreter_registry() throws Exception {
- PySystemState.registry.setProperty(PYTHON_CONSOLE, "org.python.util.InteractiveConsole");
- assertEquals(InteractiveConsole.class, invokeNewInterpreter(true).getClass());
- }
-
- /**
- * test fallback in case of an invalid registry value
- *
- * @throws Exception
- */
- public void testNewInterpreter_unknown() throws Exception {
- PySystemState.registry.setProperty(PYTHON_CONSOLE, "foo.bar.NoConsole");
- assertEquals(JLineConsole.class, invokeNewInterpreter(true).getClass());
- }
-
- /**
- * test non-interactive fallback to legacy console
- *
- * @throws Exception
- */
- public void testNewInterpreter_NonInteractive() throws Exception {
- assertEquals(InteractiveConsole.class, invokeNewInterpreter(false).getClass());
- }
-
- /**
- * Invoke the private static method 'newInterpreter(boolean)' on jython.class
- *
- * @throws Exception
- */
- private InteractiveConsole invokeNewInterpreter(boolean interactiveStdin) throws Exception {
- Method method = jython.class.getDeclaredMethod("newInterpreter", Boolean.TYPE);
- assertNotNull(method);
- method.setAccessible(true);
- Object result = method.invoke(null, interactiveStdin);
- assertNotNull(result);
- assertTrue(result instanceof InteractiveConsole);
- return (InteractiveConsole)result;
+ @Test
+ public void testDefaultConsole() {
+ // This path only if you changed it to run manually
+ jython.run(commands);
+ Console console = Py.getConsole();
+ assertEquals(JLineConsole.class, console.getClass());
}
}
--
Repository URL: http://hg.python.org/jython
More information about the Jython-checkins
mailing list