From jython-checkins at python.org Thu Sep 5 01:45:32 2013 From: jython-checkins at python.org (jim.baker) Date: Thu, 5 Sep 2013 01:45:32 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Proxymakers_that_subclass_C?= =?utf-8?q?ustomMaker_can_now_completely_control_the?= Message-ID: <3cVhVN6HcKz7LjT@mail.python.org> http://hg.python.org/jython/rev/be402c429680 changeset: 7113:be402c429680 user: Jim Baker date: Wed Sep 04 17:45:12 2013 -0600 summary: Proxymakers that subclass CustomMaker can now completely control the proxymaking process, including saving bytecode. files: Lib/test/bark.py | 30 ++ Lib/test/clamp.py | 60 ++++ Lib/test/test_java_integration.py | 140 +++++++++- src/org/python/compiler/CustomMaker.java | 54 ++++ src/org/python/core/MakeProxies.java | 16 +- 5 files changed, 276 insertions(+), 24 deletions(-) diff --git a/Lib/test/bark.py b/Lib/test/bark.py new file mode 100644 --- /dev/null +++ b/Lib/test/bark.py @@ -0,0 +1,30 @@ +from __future__ import print_function + +from java.io import Serializable +from java.util.concurrent import Callable + +from clamp import PackageProxy + + +class Dog(Callable, Serializable): + # FIXME in a future branch, support the ability to call __init__ + # via __initProxy__ To support this functionality, the test + # version of the clamp module in this directory needs to add a + # Java constructor with an Object... arg list; possibly introspect + # the arg list of __init__; or get some additional metadata from + # the initialization of PackageProxy. + __proxymaker__ = PackageProxy("org.python.test") + + def __init__(self): + self.name = "Rover" + self.number = 42 + + def whoami(self): + return self.name + + def call(self): + # Using print forces use of PySystemState and shows it's initialized + print("%s barks %s times" % (self.name, self.number)) + + def __eq__(self, other): + return self.name == other.name and self.number == other.number diff --git a/Lib/test/clamp.py b/Lib/test/clamp.py new file mode 100644 --- /dev/null +++ b/Lib/test/clamp.py @@ -0,0 +1,60 @@ +import java +import os +import os.path + +from java.lang.reflect import Modifier +from org.python.util import CodegenUtils +from org.python.compiler import CustomMaker, ProxyCodeHelpers + + +__all__ = ["PackageProxy", "SerializableProxies"] + + +class SerializableProxies(CustomMaker): + + # NOTE: SerializableProxies is itself a java proxy, but it's not a custom one! + + serialized_path = None + + def doConstants(self): + self.classfile.addField("serialVersionUID", + CodegenUtils.ci(java.lang.Long.TYPE), Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL) + code = self.classfile.addMethod("", ProxyCodeHelpers.makeSig("V"), Modifier.STATIC) + code.visitLdcInsn(java.lang.Long(1)) + code.putstatic(self.classfile.name, "serialVersionUID", CodegenUtils.ci(java.lang.Long.TYPE)) + code.return_() + + def saveBytes(self, bytes): + if self.serialized_path: + path = os.path.join(self.serialized_path, os.path.join(*self.myClass.split(".")) + ".class") + parent = os.path.dirname(path) + try: + os.makedirs(parent) + except OSError: + pass # Directory exists + with open(path, "wb") as f: + f.write(bytes.toByteArray()) + + def makeClass(self): + try: + # If already defined on CLASSPATH, simply return this class + cls = java.lang.Class.forName(self.myClass) + print "Class defined on CLASSPATH", cls + except: + # Otherwise build it + cls = CustomMaker.makeClass(self) + return cls + + +class PackageProxy(object): + + def __init__(self, package): + self.package = package + + def __call__(self, superclass, interfaces, className, pythonModuleName, fullProxyName, mapping): + """Constructs a usable proxy name that does not depend on ordering""" + if "." in pythonModuleName: + # get around that will be called differently from regrtest, as test.module instead of module + pythonModuleName = pythonModuleName.split(".")[-1] + return SerializableProxies(superclass, interfaces, className, pythonModuleName, self.package + "." + pythonModuleName + "." + className, mapping) + diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py --- a/Lib/test/test_java_integration.py +++ b/Lib/test/test_java_integration.py @@ -1,5 +1,6 @@ import copy import glob +import importlib import operator import os import os.path @@ -16,6 +17,7 @@ from java.lang import (ClassCastException, ExceptionInInitializerError, String, Runnable, System, Runtime, Math, Byte) from java.math import BigDecimal, BigInteger +from java.net import URI from java.io import (ByteArrayInputStream, ByteArrayOutputStream, File, FileInputStream, FileNotFoundException, FileOutputStream, FileWriter, ObjectInputStream, ObjectOutputStream, OutputStreamWriter, UnsupportedEncodingException) @@ -24,17 +26,24 @@ from java.awt import Dimension, Color, Component, Container from java.awt.event import ComponentEvent from javax.swing.tree import TreePath +from javax.tools import SimpleJavaFileObject, JavaFileObject, ToolProvider from org.python.core.util import FileUtil +from org.python.compiler import CustomMaker from org.python.tests import (BeanImplementation, Child, Child2, CustomizableMapHolder, Listenable, ToUnicode) from org.python.tests.mro import (ConfusedOnGetitemAdd, FirstPredefinedGetitem, GetitemAdder) from org.python.util import PythonInterpreter + +import java import org.python.core.Options from javatests import Issue1833 from javatests.ProxyTests import NullToString, Person +from clamp import SerializableProxies + + class InstantiationTest(unittest.TestCase): def test_cant_instantiate_abstract(self): @@ -561,6 +570,39 @@ return jars + + +class JavaSource(SimpleJavaFileObject): + + def __init__(self, name, source): + self._name = name + self._source = source + SimpleJavaFileObject.__init__( + self, + URI.create("string:///" + name.replace(".", "/") + JavaFileObject.Kind.SOURCE.extension), + JavaFileObject.Kind.SOURCE) + + def getName(self): + return self._name + + def getCharContent(self, ignore): + return self._source + + +def compile_java_source(options, class_name, source): + """Compiles a single java source "file" contained in the string source + + Use options, specifically -d DESTDIR, to control where the class + file is emitted. Note that we could use forwarding managers to + avoid tempdirs, but this is overkill here given that we still need + to run the emitted Java class. + """ + f = JavaSource(class_name, source) + compiler = ToolProvider.getSystemJavaCompiler() + task = compiler.getTask(None, None, None, options, None, [f]) + task.call() + + class SerializationTest(unittest.TestCase): def test_java_serialization(self): @@ -611,13 +653,72 @@ jars = find_jython_jars() jars.append(proxies_jar_path) classpath = ":".join(jars) + env = dict(os.environ) + env.update(JYTHONPATH=os.path.normpath(os.path.join(__file__, ".."))) cmd = [os.path.join(System.getProperty("java.home"), "bin/java"), "-classpath", classpath, "ProxyDeserialization", cat_path] - self.assertEqual(subprocess.check_output(cmd), "meow\n") + self.assertEqual(subprocess.check_output(cmd, env=env), "meow\n") finally: org.python.core.Options.proxyDebugDirectory = old_proxy_debug_dir shutil.rmtree(tempdir) + def test_custom_proxymaker(self): + """Verify custom proxymaker supports direct usage of Python code in Java""" + tempdir = tempfile.mkdtemp() + try: + SerializableProxies.serialized_path = tempdir + import bark #importlib.import_module("bark") + dog = bark.Dog() + self.assertEqual(dog.whoami(), "Rover") + self.assertEqual(dog.serialVersionUID, 1) + self.assertEqual(dog, roundtrip_serialization(dog)) + + # Create a jar file containing the org.python.test.Dog proxy + proxies_jar_path = os.path.join(tempdir, "proxies.jar") + subprocess.check_call(["jar", "cf", proxies_jar_path, "-C", tempdir, "org/"]) + + # Build a Java class importing Dog + source = """ +import org.python.test.bark.Dog; // yes, it's that simple + +public class BarkTheDog { + public static void main(String[] args) { + Dog dog = new Dog(); + try { + dog.call(); + } + catch(Exception e) { + System.err.println(e); + } + } +} +""" + jars = find_jython_jars() + jars.append(proxies_jar_path) + classpath = ":".join(jars) + compile_java_source( + ["-classpath", classpath, "-d", tempdir], + "BarkTheDog", source) + + # Then in a completely different JVM running our + # BarkTheDog code, verify we get an appropriate bark + # message printed to stdout, which in turn ensures that + # PySystemState (and Jython runtime) is initialized for + # the proxy + classpath += ":" + tempdir + cmd = [os.path.join(System.getProperty("java.home"), "bin/java"), + "-classpath", classpath, "BarkTheDog"] + env = dict(os.environ) + env.update(JYTHONPATH=os.path.normpath(os.path.join(__file__, ".."))) + self.assertEqual( + subprocess.check_output(cmd, env=env), + "Class defined on CLASSPATH \n" + "Rover barks 42 times\n") + finally: + pass + # print "Will not remove", tempdir + #shutil.rmtree(tempdir) + class CopyTest(unittest.TestCase): @@ -691,24 +792,25 @@ def test_main(): - test_support.run_unittest(InstantiationTest, - BeanTest, - SysIntegrationTest, - IOTest, - JavaReservedNamesTest, - PyReservedNamesTest, - ImportTest, - ColorTest, - TreePathTest, - BigNumberTest, - JavaStringTest, - JavaDelegationTest, - SecurityManagerTest, - JavaWrapperCustomizationTest, - SerializationTest, - CopyTest, - UnicodeTest, - BeanPropertyTest) + test_support.run_unittest( + BeanPropertyTest, + BeanTest, + BigNumberTest, + ColorTest, + CopyTest, + IOTest, + ImportTest, + InstantiationTest, + JavaDelegationTest, + JavaReservedNamesTest, + JavaStringTest, + JavaWrapperCustomizationTest, + PyReservedNamesTest, + SecurityManagerTest, + SerializationTest, + SysIntegrationTest, + TreePathTest, + UnicodeTest) if __name__ == "__main__": test_main() diff --git a/src/org/python/compiler/CustomMaker.java b/src/org/python/compiler/CustomMaker.java new file mode 100644 --- /dev/null +++ b/src/org/python/compiler/CustomMaker.java @@ -0,0 +1,54 @@ +package org.python.compiler; + +import org.python.core.BytecodeLoader; +import org.python.core.Py; +import org.python.core.PyObject; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Arrays; + + +public class CustomMaker extends JavaMaker { + + public CustomMaker(Class superclass, + Class[] interfaces, + String pythonClass, + String pythonModule, + String myClass, + PyObject methods) { + super(superclass, interfaces, pythonClass, pythonModule, myClass, methods); + } + + // Override to save bytes + public void saveBytes(ByteArrayOutputStream bytes) { + } + + // By default makeClass will have the same behavior as MakeProxies calling JavaMaker, + // other than the debug behavior of saving the classfile (as controlled by + // Options.ProxyDebugDirectory; users of CustomMaker simply need to save it themselves). + // + // Override this method to get custom classes built from any desired source. + public Class makeClass() { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + build(bytes); // Side effect of writing to bytes + saveBytes(bytes); + List> secondary = new LinkedList(Arrays.asList(interfaces)); + List> referents = null; + if (secondary != null) { + if (superclass != null) { + secondary.add(0, superclass); + } + referents = secondary; + } else if (superclass != null) { + referents = new ArrayList>(1); + referents.add(superclass); + } + return BytecodeLoader.makeClass(myClass, referents, bytes.toByteArray()); + } catch (Exception exc) { + throw Py.JavaError(exc); + } + } +} diff --git a/src/org/python/core/MakeProxies.java b/src/org/python/core/MakeProxies.java --- a/src/org/python/core/MakeProxies.java +++ b/src/org/python/core/MakeProxies.java @@ -8,6 +8,7 @@ import org.python.compiler.APIVersion; import org.python.compiler.AdapterMaker; +import org.python.compiler.CustomMaker; import org.python.compiler.JavaMaker; class MakeProxies { @@ -65,16 +66,21 @@ // Grab the proxy maker from the class if it exists, and if it does, use the proxy class // name from the maker - PyObject customProxyMaker = dict.__finditem__("__proxymaker__"); - if (customProxyMaker != null) { + PyObject userDefinedProxyMaker = dict.__finditem__("__proxymaker__"); + if (userDefinedProxyMaker != null) { if (module == null) { throw Py.TypeError("Classes using __proxymaker__ must define __module__"); } PyObject[] args = Py.javas2pys(superclass, interfaces, className, pythonModuleName, fullProxyName, dict); - javaMaker = Py.tojava(customProxyMaker.__call__(args), JavaMaker.class); - // TODO Full proxy name + javaMaker = Py.tojava(userDefinedProxyMaker.__call__(args), JavaMaker.class); + if (javaMaker instanceof CustomMaker) { + // This hook hack is necessary because of the divergent behavior of how classes + // are made - we want to allow CustomMaker complete freedom in constructing the + // class, including saving any bytes. + CustomMaker customMaker = (CustomMaker) javaMaker; + return customMaker.makeClass(); + } } - if (javaMaker == null) { javaMaker = new JavaMaker(superclass, interfaces, -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Sep 7 19:12:43 2013 From: jython-checkins at python.org (jeff.allen) Date: Sat, 7 Sep 2013 19:12:43 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Formatting_only_=28preparat?= =?utf-8?q?ory_to_JLine_changes=29?= Message-ID: <3cXMdl44Vhz7Ljs@mail.python.org> http://hg.python.org/jython/rev/bb71f88cbe80 changeset: 7114:bb71f88cbe80 parent: 7109:aa079dc20555 user: Jeff Allen date: Sun May 12 17:02:08 2013 +0100 summary: Formatting only (preparatory to JLine changes) Tabs to whitespace, curly brackets, @Override, import list order, line-wraps, spellings, etc. in files I expect to touch. files: src/org/python/core/PySystemState.java | 421 ++++----- src/org/python/util/InteractiveConsole.java | 70 +- src/org/python/util/JLineConsole.java | 35 +- src/org/python/util/jython.java | 192 ++-- 4 files changed, 350 insertions(+), 368 deletions(-) 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 @@ -21,26 +21,27 @@ 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.expose.ExposedGet; +import org.python.expose.ExposedType; 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; /** * 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 builtinNames; + private static Map builtinNames; public static PyTuple builtin_module_names = null; public static PackageManager packageManager; @@ -182,8 +176,8 @@ private final PySystemStateCloser closer; private static final ReferenceQueue systemStateQueue = new ReferenceQueue(); - private static final ConcurrentMap, - PySystemStateCloser> sysClosers = Generic.concurrentMap(); + private static final ConcurrentMap, PySystemStateCloser> sysClosers = + Generic.concurrentMap(); // float_info public static PyObject float_info; @@ -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 ""; } @@ -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 null */ @@ -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; } @@ -961,32 +933,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 +998,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 @@ -1057,19 +1030,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 +1054,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 +1109,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 +1124,8 @@ } /** - * @return the full name of the jar file containing this class, null if not available. + * @return the full name of the jar file containing this class, null if not + * available. */ private static String getJarFileName() { Class thisClass = PySystemState.class; @@ -1204,10 +1183,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 +1198,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. *

- * Note. 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("/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. + * Note. 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("/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. *

- * Note. 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. + * Note. 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 +1227,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. *

- * Note. 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. + * Note. 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 +1249,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 +1267,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 +1283,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 +1306,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 +1413,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> resourceClosers) { @@ -1467,52 +1439,65 @@ } -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); } } + 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 ""; } + + public String toString() { + return ""; + } + 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 +1512,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 +1538,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/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 on ". + * + * The optional banner argument specifies the banner to print before the first interaction; by + * default it prints "Jython on ". */ 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 @@ -13,19 +13,17 @@ 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.PyObject; /** - * This class uses JLine to provide - * readline like functionality to its console without requiring native readline - * support. + * This class uses JLine to provide readline like + * functionality to its console without requiring native readline support. */ public class JLineConsole extends InteractiveConsole { @@ -44,11 +42,11 @@ 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 SUSPENDED_STRERRORS = - Arrays.asList(Errno.EINTR.description(), Errno.EIO.description()); + private static final List SUSPENDED_STRERRORS = Arrays.asList( + Errno.EINTR.description(), Errno.EIO.description()); public JLineConsole() { this(null); @@ -75,8 +73,8 @@ 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"); + Writer output = + new OutputStreamWriter(new FileOutputStream(FileDescriptor.out), "ISO-8859-1"); reader = new ConsoleReader(input, output, getBindings()); reader.setBellEnabled(false); } catch (IOException e) { @@ -89,15 +87,15 @@ /** * 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(); + String userBindings = + new File(System.getProperty("user.home"), ".jlinebindings.properties") + .getAbsolutePath(); File bindingsFile = new File(System.getProperty("jline.keybindings", userBindings)); try { @@ -156,8 +154,8 @@ } /** - * 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 +175,6 @@ return reader; } - /** * @return the startup hook (called prior to each readline) */ 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 @@ -33,67 +33,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. - * - * 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. + * 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. + * + * @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 +147,6 @@ return opts; } - for (String opt : envVar.split(",")) { opt = opt.trim(); if (opt.length() == 0) { @@ -155,13 +155,14 @@ opts.add(opt); } } catch (SecurityException e) { + // Continue } return opts; } - private static List validWarnActions = Arrays.asList( - "error", "ignore", "always", "default", "module", "once"); + private static List validWarnActions = Arrays.asList("error", "ignore", "always", + "default", "module", "once"); private static void addWarnings(List from, PyList to) { outerLoop : for (String opt : from) { @@ -170,14 +171,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)); } } @@ -215,7 +216,6 @@ imp.load("warnings"); } - // Decide if stdin is interactive if (!opts.fixInteractive || opts.interactive) { opts.interactive = ((PyFile)Py.defaultSystemState.stdin).isatty(); @@ -265,21 +265,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("")); @@ -289,29 +289,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 +327,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 +366,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; @@ -386,10 +385,9 @@ * Returns a new python interpreter using the InteractiveConsole subclass from the * python.console registry key. *

- - * 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. + * 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) { @@ -424,7 +422,9 @@ } } + class CommandLineOptions { + public String filename; public boolean jar, interactive, notice; public boolean runCommand, runModule; @@ -455,7 +455,7 @@ // continue } } - + private boolean argumentExpected(String arg) { System.err.println("Argument expected for the " + arg + " option"); return false; @@ -492,7 +492,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 +525,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")) { -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Sep 7 19:12:44 2013 From: jython-checkins at python.org (jeff.allen) Date: Sat, 7 Sep 2013 19:12:44 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Changes_to_readline=2Epy_to?= =?utf-8?q?_pass_test=5Freadline=2Epy?= Message-ID: <3cXMdm5dvRz7Ljv@mail.python.org> http://hg.python.org/jython/rev/6231c3bbcd90 changeset: 7115:6231c3bbcd90 user: Jeff Allen date: Tue Jun 18 22:30:52 2013 +0100 summary: Changes to readline.py to pass test_readline.py test_readline is skipped when regression tests run, as stdin is not then a terminal, but when run manually it would fail. This was discovered during work on the revised JLineConsole but is independent of it. files: Lib/readline.py | 14 ++++++++++++-- 1 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Lib/readline.py b/Lib/readline.py --- a/Lib/readline.py +++ b/Lib/readline.py @@ -94,7 +94,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 +106,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,6 +118,12 @@ 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() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Sep 7 19:12:46 2013 From: jython-checkins at python.org (jeff.allen) Date: Sat, 7 Sep 2013 19:12:46 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_A_substantial_re-organisati?= =?utf-8?q?on_of_the_interactive_console=2E?= Message-ID: <3cXMdp6BH4z7Lk3@mail.python.org> http://hg.python.org/jython/rev/c71cbf98acf1 changeset: 7116:c71cbf98acf1 user: Jeff Allen date: Sat Sep 07 15:59:59 2013 +0100 summary: A substantial re-organisation of the interactive console. Re-works (JLine, Readline or Plain) consoles, separating the console from the notion of an interpreter, and fixing (almost) a series of niggles with non-ascii encoding. files: Lib/readline.py | 17 +- 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 | 103 +++- src/org/python/core/__builtin__.java | 39 +- src/org/python/util/ConsoleStream.java | 242 +++++++++ src/org/python/util/JLineConsole.java | 254 +++++++-- src/org/python/util/ReadlineConsole.java | 222 +++++++- src/org/python/util/jython.java | 56 +- tests/java/javatests/Issue1972.java | 155 ++++-- tests/java/org/python/util/jythonTest.java | 108 +-- 14 files changed, 1147 insertions(+), 308 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) @@ -128,8 +129,8 @@ _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) @@ -161,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 @@ + 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 python.console must implement this + * interface, and provide a constructor with a single String 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 System.in. + * + * @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 + * System.in 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 raw_input 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 input 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 python.console, and + * implementing interface {@link Console}. PlainConsole 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. + *

+ * Unlike some consoles, PlainConsole does not install a replacement for + * System.in or use a native library. It prompts on System.out and reads + * from System.in (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 Charset. */ + 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 System.in, + * and does not add any line-editing capability to what is standard for your OS console. + * + * @param encoding name of a supported encoding or null for + * Charset.defaultCharset() + */ + 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 PlainConsole may be uninstalled. This method assumes any sub-class may not be + * uninstalled. Sub-classes that permit themselves to be uninstalled must override (and + * not call) this method. + * + * @throws UnsupportedOperationException unless this class is exactly PlainConsole + */ + @Override + public void uninstall() throws UnsupportedOperationException { + Class myClass = this.getClass(); + if (myClass != PlainConsole.class) { + throw new UnsupportedOperationException(myClass.getSimpleName() + + " console may not be uninstalled."); + } + } + + /** + * {@inheritDoc} + *

+ * 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 + * input. + */ + @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 System.out and System.in. + @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 input(), raw_input(), 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 raw_input() etc., and may provide line-editing and history recall at the + * prompt. A Console may replace System.in 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; @@ -40,7 +41,6 @@ import org.python.expose.ExposedGet; import org.python.expose.ExposedType; import org.python.modules.Setup; -import org.python.modules.zipimport.zipimporter; import org.python.util.Generic; /** @@ -199,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(); @@ -879,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); @@ -897,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; } @@ -1022,6 +1036,86 @@ return new PyString(executableFile.getPath()); } + /** + * Wrap standard input with a customised console handler specified in the property + * python.console in the supplied property set, which in practice is the + * fully-initialised Jython {@link #registry}. The value of python.console is the + * name of a class that implements {@link org.python.core.Console}. An instance is constructed + * with the value of python.console.encoding, and the console + * System.in returns characters in that encoding. After the call, the console + * object may be accessed via {@link Py#getConsole()}. + * + * @param props containing (or not) python.console + */ + 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 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': msg.". 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; @@ -1456,6 +1550,7 @@ } } + @Override public PyObject __call__(PyObject arg1, PyObject arg2, PyObject arg3) { switch (index) { case 30: @@ -1478,10 +1573,12 @@ private PyAttributeDeleted() {} + @Override public String toString() { return ""; } + @Override public Object __tojava__(Class c) { if (c == PyObject.class) { return this; 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 raw_input 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 raw_input(prompt) 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 raw_input() 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 System.in 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()}. + *

+ * 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 io module or file 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 LEAVE + * (do not modify the line), ADD (always append eol, and + * REPLACE (remove a trailing '\n', '\r', or '\r\n' provided by the library, then + * add eol). + * + * @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 eolPolicy is not LEAVE + */ + ConsoleStream(Charset encoding, EOLPolicy eolPolicy, String eol) { + + // Wrap original System.in so StreamIO.isatty() 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 skip 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 System.in. Override this if necessary to + * perform close down actions on the console library, then call super.close(). + */ + @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/JLineConsole.java b/src/org/python/util/JLineConsole.java --- a/src/org/python/util/JLineConsole.java +++ b/src/org/python/util/JLineConsole.java @@ -1,14 +1,16 @@ -/* 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; @@ -18,21 +20,22 @@ 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 JLine 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 readline.set_startup_hook. */ protected PyObject startup_hook; + /** Not currently set by readline.set_pre_input_hook. Why not? */ protected PyObject pre_input_hook; /** Whether reader is a WindowsTerminal. */ @@ -48,56 +51,197 @@ private static final List 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. + *

+ * 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 + * System.in. + * + * @param encoding name of a supported encoding or null for + * Charset.defaultCharset() + */ + 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} + *

+ * This implementation overrides that by setting System.in to a + * FilterInputStream 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} + *

+ * This console implements input 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 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 { @@ -110,47 +254,9 @@ } 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"); } /** 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: Java Readline

- * - * Based on CPython-1.5.2's code module - * + * Uses: Java Readline 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 python.console.readlinelib, or "Editline" by default. + *

+ * 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 + * System.in. + * + * @param encoding name of a supported encoding or null for + * Charset.defaultCharset() + */ + 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} + *

+ * This implementation overrides that by setting System.in to a + * FilterInputStream 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} + *

+ * This console implements input 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} + *

+ * This console implements input 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 Readline.readline 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 Readline.readline + * @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) 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 Readline.readline 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 null 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) to bytes in the console + * encoding. The actual GNU readline function returns a C char array in the console encoding, + * but the wrapper Readline.readline 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 null 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; @@ -87,7 +91,7 @@ * 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. - * + * * @param filename The path to the filename to run. */ public static void runJar(String filename) { @@ -201,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(); @@ -216,17 +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) { @@ -382,30 +390,6 @@ } /** - * Returns a new python interpreter using the InteractiveConsole subclass from the - * python.console registry key. - *

- * 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() { 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. *

* 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. *

* The bulk of this program is designed to be run as JUnit tests, but it also has a * {@link #main(String[])} method that echoes System.in onto System.out @@ -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. DEBUG_PORT 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 @@ * System.in in the subprocess, which of course writes hex to * System.out but that data is not received back in the parent process until * System.out.println() 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 @@ * echo the characters as hexadecimal * * - * + * * @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 java.class.path). - * + * * @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 java.class.path). After the call, * {@link #proc} references the running process and {@link #inFromProc} and {@link #errFromProc} * are handling the stdout and stderr of the subprocess. - * + * * @param args further arguments to the program run * @throws IOException */ @@ -427,7 +434,7 @@ /** * Write this string into the stdin 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 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 stdoutIgnore; + + /** Compiled regular expressions for the lines to ignore (on stderr). */ + private static List 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 compileAll(String[] regex) { + List result = new LinkedList(); + 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 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 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 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 len) 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. + *

+ * 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. + *

+ * 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): + * + *

+ * java -cp build/exposed;build/classes;extlibs/* -Dpython.home=dist
+ *              org.junit.runner.JUnitCore org.python.util.jythonTest
+ * 
*/ -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 From jython-checkins at python.org Sat Sep 7 19:12:49 2013 From: jython-checkins at python.org (jeff.allen) Date: Sat, 7 Sep 2013 19:12:49 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge_console_work_to_trunk?= Message-ID: <3cXMds15p0z7Lkp@mail.python.org> http://hg.python.org/jython/rev/dbf21ca9391a changeset: 7117:dbf21ca9391a parent: 7113:be402c429680 parent: 7116:c71cbf98acf1 user: Jeff Allen 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 @@ + 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 python.console must implement this + * interface, and provide a constructor with a single String 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 System.in. + * + * @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 + * System.in 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 raw_input 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 input 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 python.console, and + * implementing interface {@link Console}. PlainConsole 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. + *

+ * Unlike some consoles, PlainConsole does not install a replacement for + * System.in or use a native library. It prompts on System.out and reads + * from System.in (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 Charset. */ + 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 System.in, + * and does not add any line-editing capability to what is standard for your OS console. + * + * @param encoding name of a supported encoding or null for + * Charset.defaultCharset() + */ + 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 PlainConsole may be uninstalled. This method assumes any sub-class may not be + * uninstalled. Sub-classes that permit themselves to be uninstalled must override (and + * not call) this method. + * + * @throws UnsupportedOperationException unless this class is exactly PlainConsole + */ + @Override + public void uninstall() throws UnsupportedOperationException { + Class myClass = this.getClass(); + if (myClass != PlainConsole.class) { + throw new UnsupportedOperationException(myClass.getSimpleName() + + " console may not be uninstalled."); + } + } + + /** + * {@inheritDoc} + *

+ * 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 + * input. + */ + @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 System.out and System.in. + @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 input(), raw_input(), 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 raw_input() etc., and may provide line-editing and history recall at the + * prompt. A Console may replace System.in 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 builtinNames; + private static Map builtinNames; public static PyTuple builtin_module_names = null; public static PackageManager packageManager; @@ -182,8 +176,8 @@ private final PySystemStateCloser closer; private static final ReferenceQueue systemStateQueue = new ReferenceQueue(); - private static final ConcurrentMap, - PySystemStateCloser> sysClosers = Generic.concurrentMap(); + private static final ConcurrentMap, 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 ""; } @@ -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 null */ @@ -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 + * python.console in the supplied property set, which in practice is the + * fully-initialised Jython {@link #registry}. The value of python.console is the + * name of a class that implements {@link org.python.core.Console}. An instance is constructed + * with the value of python.console.encoding, and the console + * System.in returns characters in that encoding. After the call, the console + * object may be accessed via {@link Py#getConsole()}. + * + * @param props containing (or not) python.console + */ + 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 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': msg.". 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, null if not available. + * @return the full name of the jar file containing this class, null if not + * available. */ private static String getJarFileName() { Class 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. *

- * Note. 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("/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. + * Note. 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("/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. *

- * Note. 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. + * Note. 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. *

- * Note. 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. + * Note. 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> 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 raw_input 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 raw_input(prompt) 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 raw_input() 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 System.in 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()}. + *

+ * 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 io module or file 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 LEAVE + * (do not modify the line), ADD (always append eol, and + * REPLACE (remove a trailing '\n', '\r', or '\r\n' provided by the library, then + * add eol). + * + * @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 eolPolicy is not LEAVE + */ + ConsoleStream(Charset encoding, EOLPolicy eolPolicy, String eol) { + + // Wrap original System.in so StreamIO.isatty() 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 skip 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 System.in. Override this if necessary to + * perform close down actions on the console library, then call super.close(). + */ + @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 on ". + * + * The optional banner argument specifies the banner to print before the first interaction; by + * default it prints "Jython on ". */ 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 JLine to provide - * readline like functionality to its console without requiring native readline - * support. + * This class uses JLine 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 readline.set_startup_hook. */ protected PyObject startup_hook; + /** Not currently set by readline.set_pre_input_hook. 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 SUSPENDED_STRERRORS = - Arrays.asList(Errno.EINTR.description(), Errno.EIO.description()); + private static final List 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. + *

+ * 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 + * System.in. + * + * @param encoding name of a supported encoding or null for + * Charset.defaultCharset() + */ + 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} + *

+ * This implementation overrides that by setting System.in to a + * FilterInputStream 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} + *

+ * This console implements input 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: Java Readline

- * - * Based on CPython-1.5.2's code module - * + * Uses: Java Readline 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 python.console.readlinelib, or "Editline" by default. + *

+ * 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 + * System.in. + * + * @param encoding name of a supported encoding or null for + * Charset.defaultCharset() + */ + 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} + *

+ * This implementation overrides that by setting System.in to a + * FilterInputStream 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} + *

+ * This console implements input 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} + *

+ * This console implements input 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 Readline.readline 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 Readline.readline + * @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) 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 Readline.readline 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 null 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) to bytes in the console + * encoding. The actual GNU readline function returns a C char array in the console encoding, + * but the wrapper Readline.readline 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 null 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 validWarnActions = Arrays.asList( - "error", "ignore", "always", "default", "module", "once"); + private static List validWarnActions = Arrays.asList("error", "ignore", "always", + "default", "module", "once"); private static void addWarnings(List 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("")); @@ -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 - * python.console registry key. - *

- - * 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. *

* 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. *

* The bulk of this program is designed to be run as JUnit tests, but it also has a * {@link #main(String[])} method that echoes System.in onto System.out @@ -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. DEBUG_PORT 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 @@ * System.in in the subprocess, which of course writes hex to * System.out but that data is not received back in the parent process until * System.out.println() 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 @@ * echo the characters as hexadecimal * * - * + * * @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 java.class.path). - * + * * @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 java.class.path). After the call, * {@link #proc} references the running process and {@link #inFromProc} and {@link #errFromProc} * are handling the stdout and stderr of the subprocess. - * + * * @param args further arguments to the program run * @throws IOException */ @@ -427,7 +434,7 @@ /** * Write this string into the stdin 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 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 stdoutIgnore; + + /** Compiled regular expressions for the lines to ignore (on stderr). */ + private static List 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 compileAll(String[] regex) { + List result = new LinkedList(); + 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 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 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 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 len) 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. + *

+ * 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. + *

+ * 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): + * + *

+ * java -cp build/exposed;build/classes;extlibs/* -Dpython.home=dist
+ *              org.junit.runner.JUnitCore org.python.util.jythonTest
+ * 
*/ -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 From jython-checkins at python.org Sat Sep 7 19:12:50 2013 From: jython-checkins at python.org (jeff.allen) Date: Sat, 7 Sep 2013 19:12:50 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Test_we_get_a_PlainConsole_?= =?utf-8?q?when_we_should=2E?= Message-ID: <3cXMdt39CNz7LkG@mail.python.org> http://hg.python.org/jython/rev/0c4829021a3b changeset: 7118:0c4829021a3b parent: 7116:c71cbf98acf1 user: Jeff Allen date: Sat Sep 07 16:34:15 2013 +0100 summary: Test we get a PlainConsole when we should. files: tests/java/org/python/util/jythonTestPlain.java | 64 ++++++++++ 1 files changed, 64 insertions(+), 0 deletions(-) diff --git a/tests/java/org/python/util/jythonTestPlain.java b/tests/java/org/python/util/jythonTestPlain.java new file mode 100644 --- /dev/null +++ b/tests/java/org/python/util/jythonTestPlain.java @@ -0,0 +1,64 @@ +package org.python.util; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.python.core.Console; +import org.python.core.PlainConsole; +import org.python.core.Py; + +/** + * Tests of creating and getting the right interactive console. + *

+ * 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 JLineConsole} and this one that induces selection of a {@link PlainConsole} + *

+ *

+ * 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): + * + *

+ * java -cp build/exposed;build/classes;extlibs/* -Dpython.home=dist
+ *              org.junit.runner.JUnitCore org.python.util.jythonTestPlain
+ * 
+ */ +public class jythonTestPlain { + + private static final String PYTHON_CONSOLE = "python.console"; + private static String[] commands = {"-c", "import sys; print type(sys._jy_console)"}; + + /** + * Test that specifying an invalid console class falls back to a plain console. No, really, + * "org.python.util.InteractiveConsole" is not a {@link Console}! A warning to that effect will + * be issued when the test runs. + */ + @Test + public void testFallbackConsole() { + System.out.println("testFallbackConsole"); + System.getProperties().setProperty(PYTHON_CONSOLE, "org.python.util.InteractiveConsole"); + jython.run(commands); + Console console = Py.getConsole(); + assertEquals(PlainConsole.class, console.getClass()); + } + + /** + * Show that a {@link PlainConsole} may be replaced with a {@link JLineConsole}. + */ + @Test + public void testChangeConsole() throws Exception { + System.out.println("testChangeConsole"); + // In the case where this test is run in isolation, cause initialisation with PlainConsole + System.getProperties().setProperty(PYTHON_CONSOLE, "org.python.core.PlainConsole"); + PythonInterpreter interp = new PythonInterpreter(); + // Now replace it + Py.installConsole(new JLineConsole(null)); + jython.run(commands); + Console console = Py.getConsole(); + assertEquals(JLineConsole.class, console.getClass()); + interp.cleanup(); + } + +} -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Sep 7 19:12:51 2013 From: jython-checkins at python.org (jeff.allen) Date: Sat, 7 Sep 2013 19:12:51 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_And_merge_a_missing_test?= Message-ID: <3cXMdv4sxQz7LlT@mail.python.org> http://hg.python.org/jython/rev/ea6aee922a6e changeset: 7119:ea6aee922a6e parent: 7117:dbf21ca9391a parent: 7118:0c4829021a3b user: Jeff Allen date: Sat Sep 07 16:37:24 2013 +0100 summary: And merge a missing test files: tests/java/org/python/util/jythonTestPlain.java | 64 ++++++++++ 1 files changed, 64 insertions(+), 0 deletions(-) diff --git a/tests/java/org/python/util/jythonTestPlain.java b/tests/java/org/python/util/jythonTestPlain.java new file mode 100644 --- /dev/null +++ b/tests/java/org/python/util/jythonTestPlain.java @@ -0,0 +1,64 @@ +package org.python.util; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.python.core.Console; +import org.python.core.PlainConsole; +import org.python.core.Py; + +/** + * Tests of creating and getting the right interactive console. + *

+ * 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 JLineConsole} and this one that induces selection of a {@link PlainConsole} + *

+ *

+ * 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): + * + *

+ * java -cp build/exposed;build/classes;extlibs/* -Dpython.home=dist
+ *              org.junit.runner.JUnitCore org.python.util.jythonTestPlain
+ * 
+ */ +public class jythonTestPlain { + + private static final String PYTHON_CONSOLE = "python.console"; + private static String[] commands = {"-c", "import sys; print type(sys._jy_console)"}; + + /** + * Test that specifying an invalid console class falls back to a plain console. No, really, + * "org.python.util.InteractiveConsole" is not a {@link Console}! A warning to that effect will + * be issued when the test runs. + */ + @Test + public void testFallbackConsole() { + System.out.println("testFallbackConsole"); + System.getProperties().setProperty(PYTHON_CONSOLE, "org.python.util.InteractiveConsole"); + jython.run(commands); + Console console = Py.getConsole(); + assertEquals(PlainConsole.class, console.getClass()); + } + + /** + * Show that a {@link PlainConsole} may be replaced with a {@link JLineConsole}. + */ + @Test + public void testChangeConsole() throws Exception { + System.out.println("testChangeConsole"); + // In the case where this test is run in isolation, cause initialisation with PlainConsole + System.getProperties().setProperty(PYTHON_CONSOLE, "org.python.core.PlainConsole"); + PythonInterpreter interp = new PythonInterpreter(); + // Now replace it + Py.installConsole(new JLineConsole(null)); + jython.run(commands); + Console console = Py.getConsole(); + assertEquals(JLineConsole.class, console.getClass()); + interp.cleanup(); + } + +} -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Sep 8 23:25:50 2013 From: jython-checkins at python.org (jeff.allen) Date: Sun, 8 Sep 2013 23:25:50 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Minor_improvements/correcti?= =?utf-8?q?ons_to_console-related_Java_tests=2E?= Message-ID: <3cY5CL3Tgzz7Ljt@mail.python.org> http://hg.python.org/jython/rev/0b8df72bce68 changeset: 7120:0b8df72bce68 user: Jeff Allen date: Sun Sep 08 17:58:06 2013 +0100 summary: Minor improvements/corrections to console-related Java tests. Re-works the Issue1972 test so it correctly ignores sys-package-mgr messages. Comment correction only in org.python.util.jythonTest. files: tests/java/javatests/Issue1972.java | 29 +++++---- tests/java/org/python/util/jythonTest.java | 4 +- 2 files changed, 18 insertions(+), 15 deletions(-) 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 @@ -20,7 +20,7 @@ import org.junit.Test; /** - * Tests investigating issues with readline() first raised in Jython Issue #1972. these involve + * Tests investigating issues with readline() first raised in Jython Issue #1972. These involve * sub-process input and output through the console streams. Although the console streams are used, * the JLine console handler is not engaged, as the test {@link #jythonJLineConsole()} verifies. You * could run this as a straight JUnit test, or in various debugging configurations, including remote @@ -48,10 +48,10 @@ 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"}; + 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"}; + static String[] STDERR_IGNORE = {"Jython 2", "\\*sys-package-mgr"}; /** * Extra JVM options used when debugging is enabled. DEBUG_PORT will be substituted @@ -475,16 +475,21 @@ // Get the escaped form of the byte buffers in the queue List results = queue.asStrings(); - // Count through the results, stopping when either results or expected strings run out + // Count through the results, comparing what we can't ignore to what was expected int count = 0; for (String r : results) { - if (count >= expected.length) { - break; - } else if (!matchesAnyOf(r, toIgnore)) { - assertEquals(message, expected[count++], r); + if (!beginsWithAnyOf(r, toIgnore)) { + if (count < expected.length) { + // Check the line against the expected text + assertEquals(message, expected[count++], r); + } else { + // Extra line will be a failure but continue to count + count++; + } } } - assertEquals(message, expected.length, results.size()); + // Check number of lines we can't ignore against the number expected + assertEquals(message, expected.length, count); } /** Compiled regular expressions for the lines to ignore (on stdout). */ @@ -515,15 +520,15 @@ } /** - * Compute whether a string matches any of a set of strings. + * Compute whether a string begins with 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 patterns) { + private static boolean beginsWithAnyOf(String s, List patterns) { for (Pattern p : patterns) { - if (p.matcher(s).matches()) { + if (p.matcher(s).lookingAt()) { return true; } } 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 @@ -31,12 +31,10 @@ private static String[] commands = {"-c", "import sys; print type(sys._jy_console)"}; /** - * 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. + * Test that the default behaviour is to provide a JLineConsole. */ @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 From jython-checkins at python.org Sun Sep 8 23:25:51 2013 From: jython-checkins at python.org (jeff.allen) Date: Sun, 8 Sep 2013 23:25:51 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Eclipse_Ant_exposer_task_-_?= =?utf-8?q?genericise_JRE_spec_=28v=2E_minor=29?= Message-ID: <3cY5CM6G1Dz7Lkl@mail.python.org> http://hg.python.org/jython/rev/d0df9ed89215 changeset: 7121:d0df9ed89215 user: Jeff Allen date: Sun Sep 08 19:11:37 2013 +0100 summary: Eclipse Ant exposer task - genericise JRE spec (v. minor) files: .externalToolBuilders/ant_expose.launch | 38 ++++++------ 1 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.externalToolBuilders/ant_expose.launch b/.externalToolBuilders/ant_expose.launch --- a/.externalToolBuilders/ant_expose.launch +++ b/.externalToolBuilders/ant_expose.launch @@ -1,19 +1,19 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Sep 8 23:25:53 2013 From: jython-checkins at python.org (jeff.allen) Date: Sun, 8 Sep 2013 23:25:53 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Break_assumption_in_test=5F?= =?utf-8?q?java=5Fintegration_about_System=2Ein?= Message-ID: <3cY5CP0dFyz7LlH@mail.python.org> http://hg.python.org/jython/rev/8dc4a27f705e changeset: 7122:8dc4a27f705e user: Jeff Allen date: Sun Sep 08 21:50:00 2013 +0100 summary: Break assumption in test_java_integration about System.in When test runs interactively, System.in may be a wrapped console library, not a BufferedInputStream. files: Lib/test/test_java_integration.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py --- a/Lib/test/test_java_integration.py +++ b/Lib/test/test_java_integration.py @@ -153,7 +153,7 @@ def test_system_in(self): s = System.in - self.assert_("java.io.BufferedInputStream" in str(s)) + self.assert_("method" in str(s.read)) def test_runtime_exec(self): e = Runtime.getRuntime().exec -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Sep 8 23:25:54 2013 From: jython-checkins at python.org (jeff.allen) Date: Sun, 8 Sep 2013 23:25:54 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_test=5Fjava=5Fintegration_p?= =?utf-8?q?roxy_test_fixes_for_Windows=2E?= Message-ID: <3cY5CQ2MzMz7LlN@mail.python.org> http://hg.python.org/jython/rev/7629a805d9f4 changeset: 7123:7629a805d9f4 user: Jeff Allen date: Sun Sep 08 22:14:21 2013 +0100 summary: test_java_integration proxy test fixes for Windows. Used os.pathsep and universal_newlines for cross-platform success. files: Lib/test/test_java_integration.py | 15 ++++++++------- 1 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py --- a/Lib/test/test_java_integration.py +++ b/Lib/test/test_java_integration.py @@ -652,12 +652,13 @@ # Jython runtime) is initialized for the proxy jars = find_jython_jars() jars.append(proxies_jar_path) - classpath = ":".join(jars) + classpath = os.pathsep.join(jars) env = dict(os.environ) env.update(JYTHONPATH=os.path.normpath(os.path.join(__file__, ".."))) cmd = [os.path.join(System.getProperty("java.home"), "bin/java"), "-classpath", classpath, "ProxyDeserialization", cat_path] - self.assertEqual(subprocess.check_output(cmd, env=env), "meow\n") + self.assertEqual(subprocess.check_output(cmd, env=env, universal_newlines=True), + "meow\n") finally: org.python.core.Options.proxyDebugDirectory = old_proxy_debug_dir shutil.rmtree(tempdir) @@ -695,7 +696,7 @@ """ jars = find_jython_jars() jars.append(proxies_jar_path) - classpath = ":".join(jars) + classpath = os.pathsep.join(jars) compile_java_source( ["-classpath", classpath, "-d", tempdir], "BarkTheDog", source) @@ -705,15 +706,15 @@ # message printed to stdout, which in turn ensures that # PySystemState (and Jython runtime) is initialized for # the proxy - classpath += ":" + tempdir + classpath += os.pathsep + tempdir cmd = [os.path.join(System.getProperty("java.home"), "bin/java"), "-classpath", classpath, "BarkTheDog"] env = dict(os.environ) env.update(JYTHONPATH=os.path.normpath(os.path.join(__file__, ".."))) self.assertEqual( - subprocess.check_output(cmd, env=env), - "Class defined on CLASSPATH \n" - "Rover barks 42 times\n") + subprocess.check_output(cmd, env=env, universal_newlines=True), + "Class defined on CLASSPATH \n" + "Rover barks 42 times\n") finally: pass # print "Will not remove", tempdir -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Sep 15 00:47:45 2013 From: jython-checkins at python.org (jeff.allen) Date: Sun, 15 Sep 2013 00:47:45 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_raw=5Finput=28=29_console_p?= =?utf-8?q?rompt_always_delivered_via_sys=2Estdout=3A_fixes_=232082?= Message-ID: <3ccpl5095QzRbl@mail.python.org> http://hg.python.org/jython/rev/80927ba94032 changeset: 7124:80927ba94032 user: Jeff Allen date: Sat Sep 14 18:57:50 2013 +0100 summary: raw_input() console prompt always delivered via sys.stdout: fixes #2082 Installable consoles (JLineConsole, ReadlineConsole) now intercept sys.stdin and stdout so that prompts via raw_input() are handled correctly, and we meet the espectations of programs (like test_pdb) that substitute their own sdin/stdout then call raw_input(). files: src/org/python/core/Console.java | 28 -- src/org/python/core/PlainConsole.java | 52 +---- src/org/python/core/__builtin__.java | 14 +- src/org/python/util/ConsoleStream.java | 35 +-- src/org/python/util/ConsoleOutputStream.java | 96 +++++++++ src/org/python/util/JLineConsole.java | 59 ++--- src/org/python/util/ReadlineConsole.java | 97 +++------ 7 files changed, 181 insertions(+), 200 deletions(-) diff --git a/src/org/python/core/Console.java b/src/org/python/core/Console.java --- a/src/org/python/core/Console.java +++ b/src/org/python/core/Console.java @@ -1,9 +1,7 @@ // 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 python.console must implement this @@ -31,30 +29,4 @@ */ 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 raw_input 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 input 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 --- a/src/org/python/core/PlainConsole.java +++ b/src/org/python/core/PlainConsole.java @@ -20,8 +20,9 @@ * also be installed by the Jython command-line application when in non-interactive mode. *

* Unlike some consoles, PlainConsole does not install a replacement for - * System.in or use a native library. It prompts on System.out and reads - * from System.in (wrapped with the console encoding). + * System.in or System.out, or use a native library. It prompts on + * System.out and reads from System.in (wrapped with the console + * encoding). */ public class PlainConsole implements Console { @@ -31,14 +32,12 @@ /** Encoding to use for line input as a Charset. */ 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 System.in, - * and does not add any line-editing capability to what is standard for your OS console. - * + * must be one supported by the JVM. The PlainConsole does not replace System.in or + * System.out, and does not add any line-editing capability to what is standard for + * your OS console. + * * @param encoding name of a supported encoding or null for * Charset.defaultCharset() */ @@ -53,15 +52,14 @@ @Override public void install() { - // Create a Reader with the right character encoding - reader = new BufferedReader(new InputStreamReader(System.in, encodingCharset)); + // Nothing to do! } /** * A PlainConsole may be uninstalled. This method assumes any sub-class may not be * uninstalled. Sub-classes that permit themselves to be uninstalled must override (and * not call) this method. - * + * * @throws UnsupportedOperationException unless this class is exactly PlainConsole */ @Override @@ -69,37 +67,7 @@ Class myClass = this.getClass(); if (myClass != PlainConsole.class) { throw new UnsupportedOperationException(myClass.getSimpleName() - + " console may not be uninstalled."); - } - } - - /** - * {@inheritDoc} - *

- * 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 - * input. - */ - @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 System.out and System.in. - @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; + + " console may not be uninstalled."); } } 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 @@ -1038,21 +1038,17 @@ /** * Implementation of raw_input(prompt) built-in function using the console - * directly. + * indirectly via sys.stdin and sys.stdin. * * @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) { - 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); + PyObject stdin = Py.getSystemState().stdin; + if (stdin instanceof PyAttributeDeleted) { + throw Py.RuntimeError("[raw_]input: lost sys.stdin"); } + return raw_input(prompt, stdin); } /** diff --git a/src/org/python/util/ConsoleStream.java b/src/org/python/util/ConsoleInputStream.java rename from src/org/python/util/ConsoleStream.java rename to src/org/python/util/ConsoleInputStream.java --- a/src/org/python/util/ConsoleStream.java +++ b/src/org/python/util/ConsoleInputStream.java @@ -4,7 +4,7 @@ import java.io.EOFException; import java.io.FilterInputStream; import java.io.IOException; -import java.nio.BufferOverflowException; +import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; @@ -22,7 +22,7 @@ * io stack, whether we are using the io module or file built-in, that * should deal with encoding. */ -public abstract class ConsoleStream extends FilterInputStream { +public abstract class ConsoleInputStream extends FilterInputStream { /** * Enumeration used to specify whether an end-of-line should be added or replaced at the end of @@ -38,7 +38,7 @@ 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. */ + /** The character encoding specified in the constructor. */ protected final Charset encoding; /** Bytes decoded from the last line read. */ private ByteBuffer buf; @@ -56,14 +56,15 @@ * REPLACE (remove a trailing '\n', '\r', or '\r\n' provided by the library, then * add eol). * + * @param in stream to wrap, normally System.in * @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 eolPolicy is not LEAVE */ - ConsoleStream(Charset encoding, EOLPolicy eolPolicy, String eol) { + ConsoleInputStream(InputStream in, Charset encoding, EOLPolicy eolPolicy, String eol) { - // Wrap original System.in so StreamIO.isatty() will find it reflectively - super(System.in); + // Wrap original in so StreamIO.isatty() will find it reflectively + super(in); // But our real input comes from (re-)encoding the console line this.encoding = encoding; @@ -74,7 +75,7 @@ buf = EMPTY_BUF; } - /** + /** * Get one line of input from the console. Override this method with the actions specific to the * library in use. * @@ -137,16 +138,14 @@ } /** - * Reads the next byte of data from the buffered input line. - * + * Read 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 + * data are 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()) { @@ -162,8 +161,8 @@ /** * 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 + * returned. This implementation calls {@link #getLine()} at most once to get a line of + * characters from the console, and encodes them as bytes to be read * back from the stream. */ @Override @@ -215,16 +214,6 @@ return buf.remaining(); } - /** - * If possible, restore the standard System.in. Override this if necessary to - * perform close down actions on the console library, then call super.close(). - */ - @Override - public void close() throws IOException { - // Restore original System.in - System.setIn(in); - } - /** Mark is not supported. */ @Override public synchronized void mark(int readlimit) {} diff --git a/src/org/python/util/ConsoleOutputStream.java b/src/org/python/util/ConsoleOutputStream.java new file mode 100644 --- /dev/null +++ b/src/org/python/util/ConsoleOutputStream.java @@ -0,0 +1,96 @@ +// Copyright (c) 2013 Jython Developers +package org.python.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +/** + * This class may be used to wrap and replace System.out so that the console handling + * library (JLine or Java Readline) can treat an incomplete line (one without a newline) as a prompt + * string, and so know the true position of the cursor. It achieves this by buffering bytes written + * from upstream until a newline arrives, or a defined capacity is reached. This interferes + * necessarily with upstream expectations about flush(). If client code requests the + * partial line as a prompt, that action empties the buffer. In that case, the client (which is the + * console object) is responsible for making the prompt emerge on the real console. + */ +public class ConsoleOutputStream extends FilterOutputStream { + + /** The storage buffer for accumulating the partial line */ + protected ByteBuffer buf; + + /** + * Create a wrapper on an OutputStream that holds back the last incomplete line + * written to it (as bytes), in case it is needed as a console prompt. + * + * @param out the stream wrapped (normally System.out) + * @param promptCapacity maximum number of bytes to buffer + */ + public ConsoleOutputStream(OutputStream out, int promptCapacity) { + super(out); + buf = ByteBuffer.allocate(Math.max(4, promptCapacity)); + } + + /** + * This write method buffers up the bytes until there is a complete line, then this is written + * all at once either to the wrapped stream, or by the client as a console prompt. + */ + @Override + public void write(int b) throws IOException { + buf.put((byte)b); + if (b == '\n' || buf.remaining() == 0) { + // Send these bytes downstream + writeBufOut(); + } + } + + /** + * This class does not flush on request, only at line endings. + */ + @Override + public void flush() throws IOException { + // Flush disabled until we are ready to write the buffer out + } + + /** + * Ensure bytes stored in the buffer are written (and flushed), then close downstream. + */ + @Override + public void close() throws IOException { + writeBufOut(); + super.close(); + } + + /** + * Write the stored bytes downstream, with a flush following. We do this when an end-of-line + * byte is written, since the buffer contents cannot then be intended as a prompt, and when we + * run out of space. + * + * @throws IOException + */ + private void writeBufOut() throws IOException { + // Could flip before and compact after, but it's really not necessary. + out.write(buf.array(), 0, buf.position()); + buf.position(0); + out.flush(); + } + + /** + * Return the stored bytes encoded as characters instead of sending them downstream. Whatever is + * in the buffer at the point this method is called will be returned, decoded as a CharSequence + * (from which a String can easily be got) and the buffer left empty. The expectation is that + * the characters will be issued by the caller as a prompt. + * + * @param encoding with which to decode the bytes + * @return the decoded prompt + */ + protected CharSequence getPrompt(Charset encoding) { + buf.flip(); + CharSequence prompt = encoding.decode(buf); + buf.compact(); + return prompt; + } + +} 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 @@ -10,6 +10,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; +import java.io.PrintStream; import java.io.PrintWriter; import java.io.Writer; import java.util.Arrays; @@ -31,18 +32,18 @@ /** Main interface to JLine. */ public ConsoleReader reader; - /** Callable object set by readline.set_startup_hook. */ protected PyObject startup_hook; - /** Not currently set by readline.set_pre_input_hook. Why not? */ protected PyObject pre_input_hook; - /** Whether reader is a WindowsTerminal. */ private boolean windows; - /** The ctrl-z character String. */ protected static final String CTRL_Z = "\u001a"; + /** The longest we expect a console prompt to be (in encoded bytes) */ + public static final int MAX_PROMPT = 512; + /** Stream wrapping System.out in order to capture the last prompt. */ + private ConsoleOutputStream outWrapper; /** * Errno strerrors possibly caused by a SIGSTP (ctrl-z). They may propagate up to IOException @@ -58,7 +59,7 @@ * 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 * System.in. - * + * * @param encoding name of a supported encoding or null for * Charset.defaultCharset() */ @@ -81,7 +82,8 @@ * {@inheritDoc} *

* This implementation overrides that by setting System.in to a - * FilterInputStream object that wraps JLine. + * FilterInputStream object that wraps JLine, and System.out + * with a layer that buffers incomplete lines for use as the console prompt. */ @Override public void install() { @@ -97,6 +99,14 @@ */ Writer out = new PrintWriter(new OutputStreamWriter(System.out, encoding)); + /* + * Everybody else, using sys.stdout or java.lang.System.out gets to write on a special + * PrintStream that keeps the last incomplete line in case it turns out to be a console + * prompt. + */ + outWrapper = new ConsoleOutputStream(System.out, MAX_PROMPT); + System.setOut(new PrintStream(outWrapper, true, encoding)); + // Get the key bindings (built in ones treat TAB Pythonically). InputStream bindings = getBindings(userHomeSpec, getClass().getClassLoader()); @@ -127,41 +137,23 @@ System.setIn(wrapper); } - // Inherited raw_input() is adequate: calls input() - - /** - * {@inheritDoc} - *

- * This console implements input 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; - } - } - /** * Class to wrap the line-oriented interface to JLine with an InputStream that can replace * System.in. */ - private class Stream extends ConsoleStream { + private class Stream extends ConsoleInputStream { /** Create a System.in replacement with JLine that adds system-specific line endings */ Stream() { - super(encodingCharset, EOLPolicy.ADD, LINE_SEPARATOR); + super(System.in, encodingCharset, EOLPolicy.ADD, LINE_SEPARATOR); } @Override protected CharSequence getLine() throws IOException, EOFException { - // Get a line and hope to be done. Suppress any remembered prompt. - String line = readerReadLine(""); + // Get a line and hope to be done. The prompt is the current partial output line. + String prompt = outWrapper.getPrompt(encodingCharset).toString(); + String line = readerReadLine(prompt); if (!isEOF(line)) { return line; @@ -170,13 +162,14 @@ 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 + * ctrl-Z to background Jython, then 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) @@ -221,10 +214,10 @@ /** * Return the JLine bindings file. - * + * * 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 static InputStream getBindings(String userHomeSpec, ClassLoader loader) { 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 @@ -4,6 +4,7 @@ import java.io.EOFException; import java.io.FilterInputStream; import java.io.IOException; +import java.io.PrintStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; @@ -18,6 +19,11 @@ */ public class ReadlineConsole extends PlainConsole { + /** The longest we expect a console prompt to be (in encoded bytes) */ + public static final int MAX_PROMPT = 512; + /** Stream wrapping System.out in order to capture the last prompt. */ + private ConsoleOutputStream outWrapper; + /** * 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 @@ -58,7 +64,8 @@ * {@inheritDoc} *

* This implementation overrides that by setting System.in to a - * FilterInputStream object that wraps the configured console library. + * FilterInputStream object that wraps the configured console library, and wraps + * System.out in a stream that captures the prompt. */ @Override public void install() { @@ -73,60 +80,44 @@ // parseAndBind not supported by this readline } + /* + * Wrap System.out in a special PrintStream that keeps the last incomplete line in case it + * turns out to be a console prompt. + */ + try { + outWrapper = new ConsoleOutputStream(System.out, MAX_PROMPT); + System.setOut(new PrintStream(outWrapper, true, encoding)); + } catch (IOException e) { + throw new RuntimeException(e); + } + // Replace System.in FilterInputStream wrapper = new Stream(); System.setIn(wrapper); } /** - * {@inheritDoc} - *

- * This console implements input 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. + * Class to wrap the line-oriented interface to Readline with an InputStream that can replace + * System.in. */ - @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} - *

- * This console implements input 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 { + protected class Stream extends ConsoleInputStream { /** Create a System.in replacement with Readline that adds Unix-like line endings */ Stream() { - super(encodingCharset, EOLPolicy.ADD, LINE_SEPARATOR); + super(System.in, encodingCharset, EOLPolicy.ADD, LINE_SEPARATOR); } @Override protected CharSequence getLine() throws IOException, EOFException { - // The Py3k input method does exactly what we want - return input(""); + // The prompt is the current partial output line. + CharSequence prompt = outWrapper.getPrompt(encodingCharset).toString(); + // 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); } } @@ -179,30 +170,6 @@ } } - /** - * Decode the line (a return from code>Readline.readline) to bytes in the console - * encoding. The actual GNU readline function returns a C char array in the console encoding, - * but the wrapper Readline.readline 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 null 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; } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Sep 17 23:53:10 2013 From: jython-checkins at python.org (jeff.allen) Date: Tue, 17 Sep 2013 23:53:10 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Ensure_embedded_interpreter?= =?utf-8?q?_gets_PlainConsole_by_default=2E?= Message-ID: <3cfdNk3s0rz7Lk6@mail.python.org> http://hg.python.org/jython/rev/dbb3710ed6f4 changeset: 7125:dbb3710ed6f4 user: Jeff Allen date: Tue Sep 17 22:24:40 2013 +0100 summary: Ensure embedded interpreter gets PlainConsole by default. When console selection moved into PySystemState, the JLineConsole was invoked in a wider range of cases. This change ensures only org.python.util.jython invokes it. files: registry | 12 +++++-- src/org/python/util/jython.java | 10 ++++++ tests/java/org/python/util/InterpreterTest.java | 15 ++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/registry b/registry --- a/registry +++ b/registry @@ -30,10 +30,14 @@ # this option is set from the command line. #python.verbose = message -# Jython ships with a JLine console (http://jline.sourceforge.net/) -# out of the box. -python.console=org.python.util.JLineConsole -# To activate explicitly the featureless Jython console, choose: +# Jython ships with a JLine console (http://jline.sourceforge.net/) out of the +# box. This is selected by default in the Jython command-line application +# (org.python.util,jython) if you do not define python.console to be another +# class on the command line. Alternatively, you could set python.console here, +# but be aware that this will also affect the console in applications that +# embed a PythonInterpreter, or use Jython as a JSR-223 script engine. +#python.console=org.python.util.JLineConsole +# To activate the featureless Jython console explicitly, 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: 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 @@ -38,6 +38,9 @@ public class jython { + // An instance of this class will provide the console (python.console) by default. + private static final String PYTHON_CONSOLE_CLASS = "org.python.util.JLineConsole"; + private static final String COPYRIGHT = "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information."; @@ -212,6 +215,13 @@ if (!opts.fixInteractive || opts.interactive) { // The options suggest System.in is interactive: but only if isatty() agrees opts.interactive = Py.isInteractive(); + if (opts.interactive) { + // Set the default console type if nothing else has + String consoleClassName = preProperties.getProperty("python.console"); + if (consoleClassName==null) { + preProperties.setProperty("python.console", PYTHON_CONSOLE_CLASS); + } + } } // Setup the basic python system state from these options diff --git a/tests/java/org/python/util/InterpreterTest.java b/tests/java/org/python/util/InterpreterTest.java --- a/tests/java/org/python/util/InterpreterTest.java +++ b/tests/java/org/python/util/InterpreterTest.java @@ -4,6 +4,8 @@ import junit.framework.TestCase; +import org.python.core.Console; +import org.python.core.PlainConsole; import org.python.core.Py; import org.python.core.PyDictionary; import org.python.core.PyInteger; @@ -63,4 +65,17 @@ assertEquals(++base, blahInstance.invoke("incval").__tojava__(Integer.class)); } } + + /** + * Show that a PythonInterpreter comes by default with a PlainConsole (not JLine say). + */ + public void testConsoleIsPlain() throws Exception { + PythonInterpreter interp = new PythonInterpreter(); + interp.exec("import sys"); + Console console = Py.tojava(interp.eval("sys._jy_console"), Console.class); + assertEquals(PlainConsole.class, console.getClass()); + Console console2 = Py.getConsole(); + assertEquals(PlainConsole.class, console2.getClass()); + } + } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Sep 21 03:00:49 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sat, 21 Sep 2013 03:00:49 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Issue1746_-_Fix_issue_with_?= =?utf-8?q?static_codecs_causing_out_of_PermGen_memory_errors?= Message-ID: <3chYPs0FPgz7Ljf@mail.python.org> http://hg.python.org/jython/rev/7ff23feeb526 changeset: 7126:7ff23feeb526 user: Brandon Pedersen date: Fri Sep 20 18:00:18 2013 -0700 summary: Issue1746 - Fix issue with static codecs causing out of PermGen memory errors files: ACKNOWLEDGMENTS | 1 + src/org/python/core/PySystemState.java | 16 + src/org/python/core/codecs.java | 178 ++++++------ 3 files changed, 109 insertions(+), 86 deletions(-) diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS --- a/ACKNOWLEDGMENTS +++ b/ACKNOWLEDGMENTS @@ -103,6 +103,7 @@ Christian Klein Jezreel Ng Santoso Wijaya + Brandon Pedersen Local Variables: mode: indented-text 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 @@ -166,6 +166,8 @@ private int recursionlimit = 1000; + private codecs.CodecState codecState; + /** true when a SystemRestart is triggered. */ public boolean _systemRestart = false; @@ -342,6 +344,20 @@ } } + public synchronized codecs.CodecState getCodecState() { + if (codecState == null) { + codecState = new codecs.CodecState(); + try { + imp.load("encodings"); + } catch (PyException exc) { + if (exc.type != Py.ImportError) { + throw exc; + } + } + } + return codecState; + } + // xxx fix this accessors @Override public PyObject __findattr_ex__(String name) { diff --git a/src/org/python/core/codecs.java b/src/org/python/core/codecs.java --- a/src/org/python/core/codecs.java +++ b/src/org/python/core/codecs.java @@ -33,97 +33,35 @@ public static final String REPLACE = "replace"; public static final String XMLCHARREFREPLACE = "xmlcharrefreplace"; private static char Py_UNICODE_REPLACEMENT_CHARACTER = 0xFFFD; - private static PyList searchPath; - private static PyStringMap searchCache; - private static PyStringMap errorHandlers; - /** Used to synchronize registry_init. */ - private static final Object INIT_LOCK = new Object(); - private static String default_encoding = "ascii"; public static String getDefaultEncoding() { - return default_encoding; + return Py.getSystemState().getCodecState().getDefaultEncoding(); } public static void setDefaultEncoding(String encoding) { - lookup(encoding); - default_encoding = encoding; + Py.getSystemState().getCodecState().setDefaultEncoding(encoding); } public static PyObject lookup_error(String handlerName) { - registry_init(); - if (handlerName == null) { - handlerName = "strict"; - } - PyObject handler = errorHandlers.__finditem__(handlerName.intern()); - if (handler == null) { - throw new PyException(Py.LookupError, "unknown error handler name '" + handlerName - + "'"); - } - return handler; + return Py.getSystemState().getCodecState().lookup_error(handlerName); } public static void register_error(String name, PyObject error) { - registry_init(); - if (!error.isCallable()) { - throw Py.TypeError("argument must be callable"); - } - errorHandlers.__setitem__(name.intern(), error); + Py.getSystemState().getCodecState().register_error(name, error); } public static void register(PyObject search_function) { - registry_init(); - if (!search_function.isCallable()) { - throw Py.TypeError("argument must be callable"); - } - searchPath.append(search_function); + Py.getSystemState().getCodecState().register(search_function); } public static PyTuple lookup(String encoding) { - registry_init(); - PyString v = new PyString(normalizestring(encoding)); - PyObject cached = searchCache.__finditem__(v); - if (cached != null) { - return (PyTuple)cached; - } - - if (searchPath.__len__() == 0) { - throw new PyException(Py.LookupError, - "no codec search functions registered: can't find encoding '" + encoding + "'"); - } - - for (PyObject func : searchPath.asIterable()) { - PyObject created = func.__call__(v); - if (created == Py.None) { - continue; - } - if (!(created instanceof PyTuple) || created.__len__() != 4) { - throw Py.TypeError("codec search functions must return 4-tuples"); - } - searchCache.__setitem__(v, created); - return (PyTuple)created; - } - throw new PyException(Py.LookupError, "unknown encoding '" + encoding + "'"); + return Py.getSystemState().getCodecState().lookup(encoding); } private static String normalizestring(String string) { return string.toLowerCase().replace(' ', '-'); } - private static boolean import_encodings_called; - - private static void import_encodings() { - if (!import_encodings_called) { - import_encodings_called = true; - try { - imp.load("encodings"); - } catch (PyException exc) { - if (exc.type != Py.ImportError) { - throw exc; - } - } - } - } - /** * Decode the bytes v using the codec registered for the encoding. * The encoding defaults to the system default encoding @@ -422,24 +360,6 @@ } } - private static void registry_init() { - synchronized (INIT_LOCK) { - if (searchPath != null) { - return; - } - searchPath = new PyList(); - searchCache = new PyStringMap(); - errorHandlers = new PyStringMap(); - String[] builtinErrorHandlers = - new String[] {"strict", IGNORE, REPLACE, XMLCHARREFREPLACE, BACKSLASHREPLACE}; - for (String builtinErrorHandler : builtinErrorHandlers) { - register_error(builtinErrorHandler, - Py.newJavaFunc(codecs.class, builtinErrorHandler + "_errors")); - } - import_encodings(); - } - } - /* --- UTF-7 Codec -------------------------------------------------------- */ /* @@ -1723,6 +1643,92 @@ } return newPosition; } + + public static class CodecState { + private PyList searchPath; + private PyStringMap searchCache; + private PyStringMap errorHandlers; + private String default_encoding = "ascii"; + + public static final String[] BUILTIN_ERROR_HANDLERS = new String[]{"strict", + IGNORE, + REPLACE, + XMLCHARREFREPLACE, + BACKSLASHREPLACE + }; + + public CodecState() { + searchPath = new PyList(); + searchCache = new PyStringMap(); + errorHandlers = new PyStringMap(); + + for (String builtinErrorHandler : BUILTIN_ERROR_HANDLERS) { + register_error(builtinErrorHandler, Py.newJavaFunc(codecs.class, + builtinErrorHandler + "_errors")); + } + } + + public String getDefaultEncoding() { + return default_encoding; + } + + public void setDefaultEncoding(String encoding) { + lookup(encoding); + default_encoding = encoding; + } + + public void register_error(String name, PyObject error) { + if (!error.isCallable()) { + throw Py.TypeError("argument must be callable"); + } + errorHandlers.__setitem__(name.intern(), error); + } + + public void register(PyObject search_function) { + if (!search_function.isCallable()) { + throw Py.TypeError("argument must be callable"); + } + searchPath.append(search_function); + } + + public PyTuple lookup(String encoding) { + PyString v = new PyString(normalizestring(encoding)); + PyObject cached = searchCache.__finditem__(v); + if (cached != null) { + return (PyTuple)cached; + } + + if (searchPath.__len__() == 0) { + throw new PyException(Py.LookupError, + "no codec search functions registered: can't find encoding '" + encoding + "'"); + } + + for (PyObject func : searchPath.asIterable()) { + PyObject created = func.__call__(v); + if (created == Py.None) { + continue; + } + if (!(created instanceof PyTuple) || created.__len__() != 4) { + throw Py.TypeError("codec search functions must return 4-tuples"); + } + searchCache.__setitem__(v, created); + return (PyTuple)created; + } + throw new PyException(Py.LookupError, "unknown encoding '" + encoding + "'"); + } + + public PyObject lookup_error(String handlerName) { + if (handlerName == null) { + handlerName = "strict"; + } + PyObject handler = errorHandlers.__finditem__(handlerName.intern()); + if (handler == null) { + throw new PyException(Py.LookupError, + "unknown error handler name '" + handlerName + "'"); + } + return handler; + } + } } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Sep 27 20:46:58 2013 From: jython-checkins at python.org (jim.baker) Date: Fri, 27 Sep 2013 20:46:58 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Ensure_that_the_site_module?= =?utf-8?q?_is_imported_when_going_through?= Message-ID: <3cmhnG2M4Dz7Ljd@mail.python.org> http://hg.python.org/jython/rev/7dac33227bb7 changeset: 7127:7dac33227bb7 user: Jim Baker date: Fri Sep 27 12:46:53 2013 -0600 summary: Ensure that the site module is imported when going through Py#initProxy, if Options.importSite is true (default). This step enables site-packages to be available before attempting to import the module corresponding to the proxy. This code path is exercised when directly using Python classes from Java, as seen in clamp. files: Lib/test/bark.py | 4 ++++ Lib/test/test_java_integration.py | 17 +++++++++++------ src/org/python/core/Py.java | 6 ++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Lib/test/bark.py b/Lib/test/bark.py --- a/Lib/test/bark.py +++ b/Lib/test/bark.py @@ -1,5 +1,6 @@ from __future__ import print_function +import sys from java.io import Serializable from java.util.concurrent import Callable @@ -25,6 +26,9 @@ def call(self): # Using print forces use of PySystemState and shows it's initialized print("%s barks %s times" % (self.name, self.number)) + # Verify that site has been imported and therefore + # site-packages and distutils/setuptools goodness is available + return "site" in sys.modules def __eq__(self, other): return self.name == other.name and self.number == other.number diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py --- a/Lib/test/test_java_integration.py +++ b/Lib/test/test_java_integration.py @@ -668,7 +668,7 @@ tempdir = tempfile.mkdtemp() try: SerializableProxies.serialized_path = tempdir - import bark #importlib.import_module("bark") + import bark dog = bark.Dog() self.assertEqual(dog.whoami(), "Rover") self.assertEqual(dog.serialVersionUID, 1) @@ -686,7 +686,10 @@ public static void main(String[] args) { Dog dog = new Dog(); try { - dog.call(); + Boolean b = (Boolean)(dog.call()); + if (!b) { + throw new RuntimeException("Expected site module to be imported"); + } } catch(Exception e) { System.err.println(e); @@ -711,10 +714,12 @@ "-classpath", classpath, "BarkTheDog"] env = dict(os.environ) env.update(JYTHONPATH=os.path.normpath(os.path.join(__file__, ".."))) - self.assertEqual( - subprocess.check_output(cmd, env=env, universal_newlines=True), - "Class defined on CLASSPATH \n" - "Rover barks 42 times\n") + self.assertRegexpMatches( + subprocess.check_output(cmd, env=env, universal_newlines=True, + stderr=subprocess.STDOUT), + r"^\*sys-package-mgr\*: processing new jar, '.+?/proxies.jar'\n" + "Class defined on CLASSPATH \n" + "Rover barks 42 times\n$".format(tempdir)) finally: pass # print "Will not remove", tempdir 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 @@ -950,6 +950,12 @@ return; } + if (Options.importSite) { + // Ensure site-packages are available before attempting to import module. + // This step enables supporting modern Python apps when using proxies + // directly from Java (eg through clamp). + imp.load("site"); + } PyObject mod = imp.importName(module.intern(), false); PyType pyc = (PyType)mod.__getattr__(pyclass.intern()); -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Sep 27 21:17:17 2013 From: jython-checkins at python.org (jeff.allen) Date: Fri, 27 Sep 2013 21:17:17 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=5Btrivial=5D_Regression_te?= =?utf-8?q?sts_include_memoryview=2E?= Message-ID: <3cmjSF4QzJz7LjM@mail.python.org> http://hg.python.org/jython/rev/c48f1bdee828 changeset: 7128:c48f1bdee828 parent: 7126:7ff23feeb526 user: Jeff Allen date: Fri Sep 27 08:35:39 2013 +0100 summary: [trivial] Regression tests include memoryview. Enables test_memoryview in regrtest, and makes comment change to PyMemoryView files: Lib/test/regrtest.py | 3 +-- src/org/python/core/PyMemoryView.java | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -28,7 +28,7 @@ -L: runleaks -- run the leaks(1) command just before exit -R: huntrleaks -- search for reference leaks (needs debug build, v. slow) -M: memlimit -- run very large memory-consuming tests --e: expected -- run only tests that are expected to run and pass +-e: expected -- run only tests that are expected to run and pass If non-option arguments are present, they are names for tests to run, unless -x is given, in which case they are names for tests not to run. @@ -1222,7 +1222,6 @@ test_longexp test_macfs test_macostools - test_memoryview test_mhlib test_mmap test_modulefinder diff --git a/src/org/python/core/PyMemoryView.java b/src/org/python/core/PyMemoryView.java --- a/src/org/python/core/PyMemoryView.java +++ b/src/org/python/core/PyMemoryView.java @@ -1,3 +1,4 @@ +// Copyright (c) 2013 Jython Developers package org.python.core; import org.python.core.buffer.BaseBuffer; @@ -9,9 +10,8 @@ import org.python.expose.MethodType; /** - * Class implementing the Python memoryview type, at present highly incomplete. It - * provides a wrapper around the Jython buffer API, but slice operations and most others are - * missing. + * Class implementing the Python memoryview type. It provides a wrapper around the + * Jython buffer API. */ @ExposedType(name = "memoryview", doc = BuiltinDocs.memoryview_doc, base = PyObject.class, isBaseType = false) @@ -660,6 +660,7 @@ * @throws PyException(AttributeError) if value cannot be converted to an integer * @throws PyException(ValueError) if value<0 or value>255 */ + @Override public synchronized void pyset(int index, PyObject value) throws PyException { // Our chance to check the memoryview is still alive checkNotReleased(); -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Sep 27 21:17:19 2013 From: jython-checkins at python.org (jeff.allen) Date: Fri, 27 Sep 2013 21:17:19 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Basic_implementation_of_buf?= =?utf-8?q?fer_type=2E?= Message-ID: <3cmjSH6Pnbz7Lm0@mail.python.org> http://hg.python.org/jython/rev/2751805d22a5 changeset: 7129:2751805d22a5 user: Jeff Allen date: Fri Sep 27 09:11:34 2013 +0100 summary: Basic implementation of buffer type. Reasonably complete buffer implementation Py2kBuffer with slicing. Enable regression test for that. (Bug revealed & fixed in buffers on strings.) Many gaps exist in acceptability of buffer as an argument in library types. files: CoreExposed.includes | 1 + Lib/test/regrtest.py | 1 - Lib/test/test_unicode.py | 13 +- Misc/make_pydocs.py | 2 +- src/org/python/core/BuiltinDocs.java | 712 +++++---- src/org/python/core/Py2kBuffer.java | 619 ++++++++ src/org/python/core/PyUnicode.java | 25 +- src/org/python/core/__builtin__.java | 1 + src/org/python/core/buffer/SimpleStringBuffer.java | 10 +- 9 files changed, 1057 insertions(+), 327 deletions(-) diff --git a/CoreExposed.includes b/CoreExposed.includes --- a/CoreExposed.includes +++ b/CoreExposed.includes @@ -1,5 +1,6 @@ org/python/core/AstList.class org/python/core/ClasspathPyImporter.class +org/python/core/Py2kBuffer.class org/python/core/PyArray.class org/python/core/PyBaseString.class org/python/core/PyBaseException.class diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -1187,7 +1187,6 @@ test_applesingle test_ascii_formatd test_audioop - test_buffer test_bsddb test_bsddb185 test_bsddb3 diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -452,15 +452,10 @@ u'strings are decoded to unicode' ) - if not sys.platform.startswith('java'): - self.assertEqual( - unicode( - buffer('character buffers are decoded to unicode'), - 'utf-8', - 'strict' - ), - u'character buffers are decoded to unicode' - ) + self.assertEqual( + unicode('strings are decoded to unicode', 'utf-8', 'strict'), + u'strings are decoded to unicode' + ) self.assertRaises(TypeError, unicode, 42, 42, 42) diff --git a/Misc/make_pydocs.py b/Misc/make_pydocs.py --- a/Misc/make_pydocs.py +++ b/Misc/make_pydocs.py @@ -61,7 +61,7 @@ frozenset, BaseException, bytearray, -#buffer, +buffer, memoryview, # + type(f), diff --git a/src/org/python/core/BuiltinDocs.java b/src/org/python/core/BuiltinDocs.java --- a/src/org/python/core/BuiltinDocs.java +++ b/src/org/python/core/BuiltinDocs.java @@ -2196,9 +2196,7 @@ "Return a copy of the string S, where all characters occurring\n" + "in the optional argument deletechars are removed, and the\n" + "remaining characters have been mapped through the given\n" + - "translation table, which must be a string of length 256.\n" + - "If the table argument is None, no translation is applied and\n" + - "the operation simply removes the characters in deletechars."; + "translation table, which must be a string of length 256."; public final static String str_upper_doc = "S.upper() -> string\n" + @@ -4049,84 +4047,96 @@ "Pad a numeric string B with zeros on the left, to fill a field\n" + "of the specified width. B is never truncated."; - // Docs for - public final static String memoryview___class___doc = + // Docs for + public final static String buffer___add___doc = + "x.__add__(y) <==> x+y"; + + public final static String buffer___class___doc = "type(object) -> the object's type\n" + "type(name, bases, dict) -> a new type"; - public final static String memoryview___delattr___doc = + public final static String buffer___cmp___doc = + "x.__cmp__(y) <==> cmp(x,y)"; + + public final static String buffer___delattr___doc = "x.__delattr__('name') <==> del x.name"; - public final static String memoryview___delitem___doc = + public final static String buffer___delitem___doc = "x.__delitem__(y) <==> del x[y]"; - public final static String memoryview_doc = - "memoryview(object)\n" + - "\n" + - "Create a new memoryview object which references the given object."; - - public final static String memoryview___eq___doc = - "x.__eq__(y) <==> x==y"; - - public final static String memoryview___format___doc = + public final static String buffer___delslice___doc = + "x.__delslice__(i, j) <==> del x[i:j]\n" + + " \n" + + " Use of negative indices is not supported."; + + public final static String buffer_doc = + "buffer(object [, offset[, size]])\n" + + "\n" + + "Create a new buffer object which references the given object.\n" + + "The buffer will reference a slice of the target object from the\n" + + "start of the object (or at the specified offset). The slice will\n" + + "extend to the end of the target object (or with the specified size)."; + + public final static String buffer___format___doc = "default object formatter"; - public final static String memoryview___ge___doc = - "x.__ge__(y) <==> x>=y"; - - public final static String memoryview___getattribute___doc = + public final static String buffer___getattribute___doc = "x.__getattribute__('name') <==> x.name"; - public final static String memoryview___getitem___doc = + public final static String buffer___getitem___doc = "x.__getitem__(y) <==> x[y]"; - public final static String memoryview___gt___doc = - "x.__gt__(y) <==> x>y"; - - public final static String memoryview___hash___doc = + public final static String buffer___getslice___doc = + "x.__getslice__(i, j) <==> x[i:j]\n" + + " \n" + + " Use of negative indices is not supported."; + + public final static String buffer___hash___doc = "x.__hash__() <==> hash(x)"; - public final static String memoryview___init___doc = + public final static String buffer___init___doc = "x.__init__(...) initializes x; see help(type(x)) for signature"; - public final static String memoryview___le___doc = - "x.__le__(y) <==> x<=y"; - - public final static String memoryview___len___doc = + public final static String buffer___len___doc = "x.__len__() <==> len(x)"; - public final static String memoryview___lt___doc = - "x.__lt__(y) <==> x x!=y"; - - public final static String memoryview___new___doc = + public final static String buffer___mul___doc = + "x.__mul__(n) <==> x*n"; + + public final static String buffer___new___doc = "T.__new__(S, ...) -> a new object with type S, a subtype of T"; - public final static String memoryview___reduce___doc = + public final static String buffer___reduce___doc = "helper for pickle"; - public final static String memoryview___reduce_ex___doc = + public final static String buffer___reduce_ex___doc = "helper for pickle"; - public final static String memoryview___repr___doc = + public final static String buffer___repr___doc = "x.__repr__() <==> repr(x)"; - public final static String memoryview___setattr___doc = + public final static String buffer___rmul___doc = + "x.__rmul__(n) <==> n*x"; + + public final static String buffer___setattr___doc = "x.__setattr__('name', value) <==> x.name = value"; - public final static String memoryview___setitem___doc = + public final static String buffer___setitem___doc = "x.__setitem__(i, y) <==> x[i]=y"; - public final static String memoryview___sizeof___doc = + public final static String buffer___setslice___doc = + "x.__setslice__(i, j, y) <==> x[i:j]=y\n" + + " \n" + + " Use of negative indices is not supported."; + + public final static String buffer___sizeof___doc = "__sizeof__() -> int\n" + "size of object in memory, in bytes"; - public final static String memoryview___str___doc = + public final static String buffer___str___doc = "x.__str__() <==> str(x)"; - public final static String memoryview___subclasshook___doc = + public final static String buffer___subclasshook___doc = "Abstract classes can override this to customize issubclass().\n" + "\n" + "This is invoked early on by abc.ABCMeta.__subclasscheck__().\n" + @@ -4135,117 +4145,84 @@ "overrides the normal algorithm (and the outcome is cached).\n" + ""; - public final static String memoryview_format_doc = - ""; - - public final static String memoryview_itemsize_doc = - ""; - - public final static String memoryview_ndim_doc = - ""; - - public final static String memoryview_readonly_doc = - ""; - - public final static String memoryview_shape_doc = - ""; - - public final static String memoryview_strides_doc = - ""; - - public final static String memoryview_suboffsets_doc = - ""; - - public final static String memoryview_tobytes_doc = - ""; - - public final static String memoryview_tolist_doc = - ""; - - // Docs for - public final static String function___call___doc = - "x.__call__(...) <==> x(...)"; - - public final static String function___class___doc = + // Docs for + public final static String memoryview___class___doc = "type(object) -> the object's type\n" + "type(name, bases, dict) -> a new type"; - public final static String function___closure___doc = - ""; - - public final static String function___code___doc = - ""; - - public final static String function___defaults___doc = - ""; - - public final static String function___delattr___doc = + public final static String memoryview___delattr___doc = "x.__delattr__('name') <==> del x.name"; - public final static String function___dict___doc = - ""; - - public final static String function_doc = - "function(code, globals[, name[, argdefs[, closure]]])\n" + - "\n" + - "Create a function object from a code object and a dictionary.\n" + - "The optional name string overrides the name from the code object.\n" + - "The optional argdefs tuple specifies the default argument values.\n" + - "The optional closure tuple supplies the bindings for free variables."; - - public final static String function___format___doc = + public final static String memoryview___delitem___doc = + "x.__delitem__(y) <==> del x[y]"; + + public final static String memoryview_doc = + "memoryview(object)\n" + + "\n" + + "Create a new memoryview object which references the given object."; + + public final static String memoryview___eq___doc = + "x.__eq__(y) <==> x==y"; + + public final static String memoryview___format___doc = "default object formatter"; - public final static String function___get___doc = - "descr.__get__(obj[, type]) -> value"; - - public final static String function___getattribute___doc = + public final static String memoryview___ge___doc = + "x.__ge__(y) <==> x>=y"; + + public final static String memoryview___getattribute___doc = "x.__getattribute__('name') <==> x.name"; - public final static String function___globals___doc = - ""; - - public final static String function___hash___doc = + public final static String memoryview___getitem___doc = + "x.__getitem__(y) <==> x[y]"; + + public final static String memoryview___gt___doc = + "x.__gt__(y) <==> x>y"; + + public final static String memoryview___hash___doc = "x.__hash__() <==> hash(x)"; - public final static String function___init___doc = + public final static String memoryview___init___doc = "x.__init__(...) initializes x; see help(type(x)) for signature"; - public final static String function___module___doc = - "str(object) -> string\n" + - "\n" + - "Return a nice string representation of the object.\n" + - "If the argument is a string, the return value is the same object."; - - public final static String function___name___doc = - "str(object) -> string\n" + - "\n" + - "Return a nice string representation of the object.\n" + - "If the argument is a string, the return value is the same object."; - - public final static String function___new___doc = + public final static String memoryview___le___doc = + "x.__le__(y) <==> x<=y"; + + public final static String memoryview___len___doc = + "x.__len__() <==> len(x)"; + + public final static String memoryview___lt___doc = + "x.__lt__(y) <==> x x!=y"; + + public final static String memoryview___new___doc = "T.__new__(S, ...) -> a new object with type S, a subtype of T"; - public final static String function___reduce___doc = + public final static String memoryview___reduce___doc = "helper for pickle"; - public final static String function___reduce_ex___doc = + public final static String memoryview___reduce_ex___doc = "helper for pickle"; - public final static String function___repr___doc = + public final static String memoryview___repr___doc = "x.__repr__() <==> repr(x)"; - public final static String function___setattr___doc = + public final static String memoryview___setattr___doc = "x.__setattr__('name', value) <==> x.name = value"; - public final static String function___sizeof___doc = + public final static String memoryview___setitem___doc = + "x.__setitem__(i, y) <==> x[i]=y"; + + public final static String memoryview___sizeof___doc = "__sizeof__() -> int\n" + "size of object in memory, in bytes"; - public final static String function___str___doc = + public final static String memoryview___str___doc = "x.__str__() <==> str(x)"; - public final static String function___subclasshook___doc = + public final static String memoryview___subclasshook___doc = "Abstract classes can override this to customize issubclass().\n" + "\n" + "This is invoked early on by abc.ABCMeta.__subclasscheck__().\n" + @@ -4254,90 +4231,117 @@ "overrides the normal algorithm (and the outcome is cached).\n" + ""; - public final static String function_func_closure_doc = + public final static String memoryview_format_doc = ""; - public final static String function_func_code_doc = + public final static String memoryview_itemsize_doc = ""; - public final static String function_func_defaults_doc = + public final static String memoryview_ndim_doc = ""; - public final static String function_func_dict_doc = + public final static String memoryview_readonly_doc = ""; - public final static String function_func_doc_doc = + public final static String memoryview_shape_doc = ""; - public final static String function_func_globals_doc = + public final static String memoryview_strides_doc = ""; - public final static String function_func_name_doc = + public final static String memoryview_suboffsets_doc = ""; - // Docs for - public final static String instancemethod___call___doc = + public final static String memoryview_tobytes_doc = + ""; + + public final static String memoryview_tolist_doc = + ""; + + // Docs for + public final static String function___call___doc = "x.__call__(...) <==> x(...)"; - public final static String instancemethod___class___doc = + public final static String function___class___doc = "type(object) -> the object's type\n" + "type(name, bases, dict) -> a new type"; - public final static String instancemethod___cmp___doc = - "x.__cmp__(y) <==> cmp(x,y)"; - - public final static String instancemethod___delattr___doc = + public final static String function___closure___doc = + ""; + + public final static String function___code___doc = + ""; + + public final static String function___defaults___doc = + ""; + + public final static String function___delattr___doc = "x.__delattr__('name') <==> del x.name"; - public final static String instancemethod_doc = - "instancemethod(function, instance, class)\n" + - "\n" + - "Create an instance method object."; - - public final static String instancemethod___format___doc = + public final static String function___dict___doc = + ""; + + public final static String function_doc = + "function(code, globals[, name[, argdefs[, closure]]])\n" + + "\n" + + "Create a function object from a code object and a dictionary.\n" + + "The optional name string overrides the name from the code object.\n" + + "The optional argdefs tuple specifies the default argument values.\n" + + "The optional closure tuple supplies the bindings for free variables."; + + public final static String function___format___doc = "default object formatter"; - public final static String instancemethod___func___doc = - "the function (or other callable) implementing a method"; - - public final static String instancemethod___get___doc = + public final static String function___get___doc = "descr.__get__(obj[, type]) -> value"; - public final static String instancemethod___getattribute___doc = + public final static String function___getattribute___doc = "x.__getattribute__('name') <==> x.name"; - public final static String instancemethod___hash___doc = + public final static String function___globals___doc = + ""; + + public final static String function___hash___doc = "x.__hash__() <==> hash(x)"; - public final static String instancemethod___init___doc = + public final static String function___init___doc = "x.__init__(...) initializes x; see help(type(x)) for signature"; - public final static String instancemethod___new___doc = + public final static String function___module___doc = + "str(object) -> string\n" + + "\n" + + "Return a nice string representation of the object.\n" + + "If the argument is a string, the return value is the same object."; + + public final static String function___name___doc = + "str(object) -> string\n" + + "\n" + + "Return a nice string representation of the object.\n" + + "If the argument is a string, the return value is the same object."; + + public final static String function___new___doc = "T.__new__(S, ...) -> a new object with type S, a subtype of T"; - public final static String instancemethod___reduce___doc = + public final static String function___reduce___doc = "helper for pickle"; - public final static String instancemethod___reduce_ex___doc = + public final static String function___reduce_ex___doc = "helper for pickle"; - public final static String instancemethod___repr___doc = + public final static String function___repr___doc = "x.__repr__() <==> repr(x)"; - public final static String instancemethod___self___doc = - "the instance to which a method is bound; None for unbound methods"; - - public final static String instancemethod___setattr___doc = + public final static String function___setattr___doc = "x.__setattr__('name', value) <==> x.name = value"; - public final static String instancemethod___sizeof___doc = + public final static String function___sizeof___doc = "__sizeof__() -> int\n" + "size of object in memory, in bytes"; - public final static String instancemethod___str___doc = + public final static String function___str___doc = "x.__str__() <==> str(x)"; - public final static String instancemethod___subclasshook___doc = + public final static String function___subclasshook___doc = "Abstract classes can override this to customize issubclass().\n" + "\n" + "This is invoked early on by abc.ABCMeta.__subclasscheck__().\n" + @@ -4346,85 +4350,90 @@ "overrides the normal algorithm (and the outcome is cached).\n" + ""; - public final static String instancemethod_im_class_doc = - "the class associated with a method"; - - public final static String instancemethod_im_func_doc = - "the function (or other callable) implementing a method"; - - public final static String instancemethod_im_self_doc = - "the instance to which a method is bound; None for unbound methods"; - - // Docs for - public final static String code___class___doc = + public final static String function_func_closure_doc = + ""; + + public final static String function_func_code_doc = + ""; + + public final static String function_func_defaults_doc = + ""; + + public final static String function_func_dict_doc = + ""; + + public final static String function_func_doc_doc = + ""; + + public final static String function_func_globals_doc = + ""; + + public final static String function_func_name_doc = + ""; + + // Docs for + public final static String instancemethod___call___doc = + "x.__call__(...) <==> x(...)"; + + public final static String instancemethod___class___doc = "type(object) -> the object's type\n" + "type(name, bases, dict) -> a new type"; - public final static String code___cmp___doc = + public final static String instancemethod___cmp___doc = "x.__cmp__(y) <==> cmp(x,y)"; - public final static String code___delattr___doc = + public final static String instancemethod___delattr___doc = "x.__delattr__('name') <==> del x.name"; - public final static String code_doc = - "code(argcount, nlocals, stacksize, flags, codestring, constants, names,\n" + - " varnames, filename, name, firstlineno, lnotab[, freevars[, cellvars]])\n" + - "\n" + - "Create a code object. Not for the faint of heart."; - - public final static String code___eq___doc = - "x.__eq__(y) <==> x==y"; - - public final static String code___format___doc = + public final static String instancemethod_doc = + "instancemethod(function, instance, class)\n" + + "\n" + + "Create an instance method object."; + + public final static String instancemethod___format___doc = "default object formatter"; - public final static String code___ge___doc = - "x.__ge__(y) <==> x>=y"; - - public final static String code___getattribute___doc = + public final static String instancemethod___func___doc = + "the function (or other callable) implementing a method"; + + public final static String instancemethod___get___doc = + "descr.__get__(obj[, type]) -> value"; + + public final static String instancemethod___getattribute___doc = "x.__getattribute__('name') <==> x.name"; - public final static String code___gt___doc = - "x.__gt__(y) <==> x>y"; - - public final static String code___hash___doc = + public final static String instancemethod___hash___doc = "x.__hash__() <==> hash(x)"; - public final static String code___init___doc = + public final static String instancemethod___init___doc = "x.__init__(...) initializes x; see help(type(x)) for signature"; - public final static String code___le___doc = - "x.__le__(y) <==> x<=y"; - - public final static String code___lt___doc = - "x.__lt__(y) <==> x x!=y"; - - public final static String code___new___doc = + public final static String instancemethod___new___doc = "T.__new__(S, ...) -> a new object with type S, a subtype of T"; - public final static String code___reduce___doc = + public final static String instancemethod___reduce___doc = "helper for pickle"; - public final static String code___reduce_ex___doc = + public final static String instancemethod___reduce_ex___doc = "helper for pickle"; - public final static String code___repr___doc = + public final static String instancemethod___repr___doc = "x.__repr__() <==> repr(x)"; - public final static String code___setattr___doc = + public final static String instancemethod___self___doc = + "the instance to which a method is bound; None for unbound methods"; + + public final static String instancemethod___setattr___doc = "x.__setattr__('name', value) <==> x.name = value"; - public final static String code___sizeof___doc = + public final static String instancemethod___sizeof___doc = "__sizeof__() -> int\n" + "size of object in memory, in bytes"; - public final static String code___str___doc = + public final static String instancemethod___str___doc = "x.__str__() <==> str(x)"; - public final static String code___subclasshook___doc = + public final static String instancemethod___subclasshook___doc = "Abstract classes can override this to customize issubclass().\n" + "\n" + "This is invoked early on by abc.ABCMeta.__subclasscheck__().\n" + @@ -4433,93 +4442,85 @@ "overrides the normal algorithm (and the outcome is cached).\n" + ""; - public final static String code_co_argcount_doc = - ""; - - public final static String code_co_cellvars_doc = - ""; - - public final static String code_co_code_doc = - ""; - - public final static String code_co_consts_doc = - ""; - - public final static String code_co_filename_doc = - ""; - - public final static String code_co_firstlineno_doc = - ""; - - public final static String code_co_flags_doc = - ""; - - public final static String code_co_freevars_doc = - ""; - - public final static String code_co_lnotab_doc = - ""; - - public final static String code_co_name_doc = - ""; - - public final static String code_co_names_doc = - ""; - - public final static String code_co_nlocals_doc = - ""; - - public final static String code_co_stacksize_doc = - ""; - - public final static String code_co_varnames_doc = - ""; - - // Docs for - public final static String frame___class___doc = + public final static String instancemethod_im_class_doc = + "the class associated with a method"; + + public final static String instancemethod_im_func_doc = + "the function (or other callable) implementing a method"; + + public final static String instancemethod_im_self_doc = + "the instance to which a method is bound; None for unbound methods"; + + // Docs for + public final static String code___class___doc = "type(object) -> the object's type\n" + "type(name, bases, dict) -> a new type"; - public final static String frame___delattr___doc = + public final static String code___cmp___doc = + "x.__cmp__(y) <==> cmp(x,y)"; + + public final static String code___delattr___doc = "x.__delattr__('name') <==> del x.name"; - public final static String frame_doc = - ""; - - public final static String frame___format___doc = + public final static String code_doc = + "code(argcount, nlocals, stacksize, flags, codestring, constants, names,\n" + + " varnames, filename, name, firstlineno, lnotab[, freevars[, cellvars]])\n" + + "\n" + + "Create a code object. Not for the faint of heart."; + + public final static String code___eq___doc = + "x.__eq__(y) <==> x==y"; + + public final static String code___format___doc = "default object formatter"; - public final static String frame___getattribute___doc = + public final static String code___ge___doc = + "x.__ge__(y) <==> x>=y"; + + public final static String code___getattribute___doc = "x.__getattribute__('name') <==> x.name"; - public final static String frame___hash___doc = + public final static String code___gt___doc = + "x.__gt__(y) <==> x>y"; + + public final static String code___hash___doc = "x.__hash__() <==> hash(x)"; - public final static String frame___init___doc = + public final static String code___init___doc = "x.__init__(...) initializes x; see help(type(x)) for signature"; - public final static String frame___new___doc = + public final static String code___le___doc = + "x.__le__(y) <==> x<=y"; + + public final static String code___lt___doc = + "x.__lt__(y) <==> x x!=y"; + + public final static String code___new___doc = "T.__new__(S, ...) -> a new object with type S, a subtype of T"; - public final static String frame___reduce___doc = + public final static String code___reduce___doc = "helper for pickle"; - public final static String frame___reduce_ex___doc = + public final static String code___reduce_ex___doc = "helper for pickle"; - public final static String frame___repr___doc = + public final static String code___repr___doc = "x.__repr__() <==> repr(x)"; - public final static String frame___setattr___doc = + public final static String code___setattr___doc = "x.__setattr__('name', value) <==> x.name = value"; - public final static String frame___sizeof___doc = - "F.__sizeof__() -> size of F in memory, in bytes"; - - public final static String frame___str___doc = + public final static String code___sizeof___doc = + "__sizeof__() -> int\n" + + "size of object in memory, in bytes"; + + public final static String code___str___doc = "x.__str__() <==> str(x)"; - public final static String frame___subclasshook___doc = + public final static String code___subclasshook___doc = "Abstract classes can override this to customize issubclass().\n" + "\n" + "This is invoked early on by abc.ABCMeta.__subclasscheck__().\n" + @@ -4528,88 +4529,93 @@ "overrides the normal algorithm (and the outcome is cached).\n" + ""; - public final static String frame_f_back_doc = + public final static String code_co_argcount_doc = ""; - public final static String frame_f_builtins_doc = + public final static String code_co_cellvars_doc = ""; - public final static String frame_f_code_doc = + public final static String code_co_code_doc = ""; - public final static String frame_f_exc_traceback_doc = + public final static String code_co_consts_doc = ""; - public final static String frame_f_exc_type_doc = + public final static String code_co_filename_doc = ""; - public final static String frame_f_exc_value_doc = + public final static String code_co_firstlineno_doc = ""; - public final static String frame_f_globals_doc = + public final static String code_co_flags_doc = ""; - public final static String frame_f_lasti_doc = + public final static String code_co_freevars_doc = ""; - public final static String frame_f_lineno_doc = + public final static String code_co_lnotab_doc = ""; - public final static String frame_f_locals_doc = + public final static String code_co_name_doc = ""; - public final static String frame_f_restricted_doc = + public final static String code_co_names_doc = ""; - public final static String frame_f_trace_doc = + public final static String code_co_nlocals_doc = ""; - // Docs for - public final static String traceback___class___doc = + public final static String code_co_stacksize_doc = + ""; + + public final static String code_co_varnames_doc = + ""; + + // Docs for + public final static String frame___class___doc = "type(object) -> the object's type\n" + "type(name, bases, dict) -> a new type"; - public final static String traceback___delattr___doc = + public final static String frame___delattr___doc = "x.__delattr__('name') <==> del x.name"; - public final static String traceback_doc = + public final static String frame_doc = ""; - public final static String traceback___format___doc = + public final static String frame___format___doc = "default object formatter"; - public final static String traceback___getattribute___doc = + public final static String frame___getattribute___doc = "x.__getattribute__('name') <==> x.name"; - public final static String traceback___hash___doc = + public final static String frame___hash___doc = "x.__hash__() <==> hash(x)"; - public final static String traceback___init___doc = + public final static String frame___init___doc = "x.__init__(...) initializes x; see help(type(x)) for signature"; - public final static String traceback___new___doc = + public final static String frame___new___doc = "T.__new__(S, ...) -> a new object with type S, a subtype of T"; - public final static String traceback___reduce___doc = + public final static String frame___reduce___doc = "helper for pickle"; - public final static String traceback___reduce_ex___doc = + public final static String frame___reduce_ex___doc = "helper for pickle"; - public final static String traceback___repr___doc = + public final static String frame___repr___doc = "x.__repr__() <==> repr(x)"; - public final static String traceback___setattr___doc = + public final static String frame___setattr___doc = "x.__setattr__('name', value) <==> x.name = value"; - public final static String traceback___sizeof___doc = - "__sizeof__() -> int\n" + - "size of object in memory, in bytes"; - - public final static String traceback___str___doc = + public final static String frame___sizeof___doc = + "F.__sizeof__() -> size of F in memory, in bytes"; + + public final static String frame___str___doc = "x.__str__() <==> str(x)"; - public final static String traceback___subclasshook___doc = + public final static String frame___subclasshook___doc = "Abstract classes can override this to customize issubclass().\n" + "\n" + "This is invoked early on by abc.ABCMeta.__subclasscheck__().\n" + @@ -4618,6 +4624,96 @@ "overrides the normal algorithm (and the outcome is cached).\n" + ""; + public final static String frame_f_back_doc = + ""; + + public final static String frame_f_builtins_doc = + ""; + + public final static String frame_f_code_doc = + ""; + + public final static String frame_f_exc_traceback_doc = + ""; + + public final static String frame_f_exc_type_doc = + ""; + + public final static String frame_f_exc_value_doc = + ""; + + public final static String frame_f_globals_doc = + ""; + + public final static String frame_f_lasti_doc = + ""; + + public final static String frame_f_lineno_doc = + ""; + + public final static String frame_f_locals_doc = + ""; + + public final static String frame_f_restricted_doc = + ""; + + public final static String frame_f_trace_doc = + ""; + + // Docs for + public final static String traceback___class___doc = + "type(object) -> the object's type\n" + + "type(name, bases, dict) -> a new type"; + + public final static String traceback___delattr___doc = + "x.__delattr__('name') <==> del x.name"; + + public final static String traceback_doc = + ""; + + public final static String traceback___format___doc = + "default object formatter"; + + public final static String traceback___getattribute___doc = + "x.__getattribute__('name') <==> x.name"; + + public final static String traceback___hash___doc = + "x.__hash__() <==> hash(x)"; + + public final static String traceback___init___doc = + "x.__init__(...) initializes x; see help(type(x)) for signature"; + + public final static String traceback___new___doc = + "T.__new__(S, ...) -> a new object with type S, a subtype of T"; + + public final static String traceback___reduce___doc = + "helper for pickle"; + + public final static String traceback___reduce_ex___doc = + "helper for pickle"; + + public final static String traceback___repr___doc = + "x.__repr__() <==> repr(x)"; + + public final static String traceback___setattr___doc = + "x.__setattr__('name', value) <==> x.name = value"; + + public final static String traceback___sizeof___doc = + "__sizeof__() -> int\n" + + "size of object in memory, in bytes"; + + public final static String traceback___str___doc = + "x.__str__() <==> str(x)"; + + public final static String traceback___subclasshook___doc = + "Abstract classes can override this to customize issubclass().\n" + + "\n" + + "This is invoked early on by abc.ABCMeta.__subclasscheck__().\n" + + "It should return True, False or NotImplemented. If it returns\n" + + "NotImplemented, the normal algorithm is used. Otherwise, it\n" + + "overrides the normal algorithm (and the outcome is cached).\n" + + ""; + public final static String traceback_tb_frame_doc = ""; diff --git a/src/org/python/core/Py2kBuffer.java b/src/org/python/core/Py2kBuffer.java new file mode 100644 --- /dev/null +++ b/src/org/python/core/Py2kBuffer.java @@ -0,0 +1,619 @@ +// Copyright (c) 2013 Jython Developers +package org.python.core; + +import org.python.core.buffer.BaseBuffer; +import org.python.core.buffer.SimpleStringBuffer; +import org.python.core.util.StringUtil; +import org.python.expose.ExposedMethod; +import org.python.expose.ExposedNew; +import org.python.expose.ExposedType; +import org.python.expose.MethodType; + +/** + * Class implementing the Python buffer type. buffer is being superseded + * in Python 2.7 by memoryview, and is provided here to support legacy Python code. Use + * memoryview if you can. buffer and memoryview both wrap the + * same Jython buffer API, the one designed for memoryview, whereas in CPython the C + * APIs supporting each are different. Because of this, they may be applied to exactly the same + * underlying object types. Their behaviour differs in detail. + */ + at ExposedType(name = "buffer", doc = BuiltinDocs.buffer_doc, base = PyObject.class, + isBaseType = false) +public class Py2kBuffer extends PySequence implements BufferProtocol { + + public static final PyType TYPE = PyType.fromClass(Py2kBuffer.class); + + /** The underlying object on which the buffer was created. */ + private final BufferProtocol object; + /** The offset (in bytes) into the offered object at which the buffer starts */ + private final int offset; + /** Number of bytes to include in the buffer (or -1 for all available). */ + private final int size; + + /** + * Construct a Py2kBuffer from an object supporting the {@link BufferProtocol}. The + * buffer takes no lease on the PyBuffer at present, but for each + * action performed obtains a new one and releases it. (Major difference from + * memoryview.) Note that when size=-1 is given, the buffer reflects + * the changing size of the underlying object. + * + * @param object the object on which this is to be a buffer + * @param offset into the array exposed by the object (0 for start) + * @param size of the slice or -1 for all of the object + */ + public Py2kBuffer(BufferProtocol object, int offset, int size) { + super(TYPE); + + if (object instanceof Py2kBuffer) { + // Special behaviour when the source object is another of our kind. + Py2kBuffer source = (Py2kBuffer)object; + offset = source.offset + offset; + if (source.size >= 0) { + // The source imposes a size limit, or rather it imposes an end + int end = source.offset + source.size; + if (size < 0 || offset + size > end) { + // We are asked for unlimited/excessive length, but must impose source end. + size = end - offset; + } + } + // This will be a Py2kBuffer with the derived offset and size on the same object. + object = source.object; + } + this.object = object; + this.offset = offset; + this.size = size; + } + + /** + * Every action on the buffer must obtain a new {@link PyBuffer} reflecting (this + * buffer's slice of) the contents of the backing object. + * + * @return a PyBuffer onto the specified slice + */ + private PyBuffer getBuffer() { + /* + * Ask for the full set of facilities (strides, indirect, etc.) from the object in case they + * are necessary for navigation, but only ask for read access. If the object is writable, + * the PyBuffer will be writable. + */ + final int flags = PyBUF.FULL_RO; + PyBuffer buf = object.getBuffer(PyBUF.FULL_RO); + + // This may already be what we need, or this buffer may be a sub-range of the object + if (offset > 0 || size >= 0) { + /* + * It's a sub-range so we have to construct a slice buffer on the first buffer. Take + * care that the bounds of the slice are within the object, which may have changed size + * since the buffer was created. + */ + PyBuffer first = buf; + int start = offset; + int length = first.getLen() - start; + + if (length <= 0) { + // Range now lies outside object: zero length slice + start = length = 0; + } else if (size >= 0 && size < length) { + // A size less than the available bytes was specified (size==-1 => all of them) + length = size; + } + + // Now offset and length specify a feasible slice + buf = first.getBufferSlice(flags, offset, length); + + // We should release our first lease because the slice buf holds one. + // That lease will be released when buf is released. + first.release(); + } + return buf; + } + + private static String[] paramNames = {"object", "offset", "size"}; + + @ExposedNew + static PyObject buffer_new(PyNewWrapper new_, boolean init, PyType subtype, PyObject[] args, + String[] keywords) { + + // Use the ArgParser to access the arguments + ArgParser ap = new ArgParser("buffer", args, keywords, paramNames, 1); + PyObject obj = ap.getPyObject(0); + int offset = ap.getIndex(1, 0); + int size = ap.getInt(2, -1); + + // Get the object as a BufferProtocol if possible + BufferProtocol object = null; + if (obj instanceof PyUnicode) { + /* + * Jython unicode does not support the buffer protocol (so that you can't take a + * memoryview of one). But to be compatible with CPython we allow buffer(unicode) to + * export two-byte UTF-16. Fortunately, a buffer is read-only, so we can use a copy. + */ + String bytes = codecs.encode((PyString)obj, "UTF-16BE", "replace"); + object = new PyString(bytes); + + } else if (obj instanceof BufferProtocol) { + // That will do directly + object = (BufferProtocol)obj; + + } + + // Checks + if (object == null) { + throw Py.TypeError("object must support the buffer protocol (or be unicode)"); + } else if (offset < 0) { + throw Py.ValueError("offset must be zero or positive"); + } else if (size < -1) { + throw Py.ValueError("size must be zero or positive"); + } else { + // Checks ok + return new Py2kBuffer(object, offset, size); + } + } + + @Override + public int __len__() { + PyBuffer buf = getBuffer(); + try { + return buf.getLen(); + } finally { + buf.release(); + } + } + + @Override + public PyString __repr__() { + String fmt = ""; + String ret = String.format(fmt, Py.idstr((PyObject)object), size, offset, Py.idstr(this)); + return new PyString(ret); + } + + @Override + public PyString __str__() { + PyBuffer buf = getBuffer(); + try { + if (buf instanceof BaseBuffer) { + // In practice, it always is + return new PyString(buf.toString()); + } else { + // But just in case ... + String s = StringUtil.fromBytes(buf); + return new PyString(s); + } + } finally { + buf.release(); + } + } + + /** + * Equivalent to the standard Python __add__ method, that for a buffer + * treats it as a str ({@link PyString}) containing the same bytes. + */ + @Override + public PyObject __add__(PyObject other) { + return buffer___add__(other); + } + + @ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.buffer___add___doc) + final PyObject buffer___add__(PyObject other) { + return __str__().__add__(other); + } + + /** + * Equivalent to the standard Python __mul__ method, that for a buffer + * returns a str containing the same thing n times. + */ + @Override + public PyObject __mul__(PyObject o) { + return buffer___mul__(o); + } + + @ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.buffer___mul___doc) + final PyObject buffer___mul__(PyObject o) { + if (!o.isIndex()) { + return null; + } + return repeat(o.asIndex(Py.OverflowError)); + } + + /** + * Equivalent to the standard Python __rmul__ method, that for a + * buffer returns a str containing the same thing n + * times. + */ + @Override + public PyObject __rmul__(PyObject o) { + return buffer___rmul__(o); + } + + @ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.buffer___rmul___doc) + final PyObject buffer___rmul__(PyObject o) { + if (!o.isIndex()) { + return null; + } + return repeat(o.asIndex(Py.OverflowError)); + } + + /* + * ============================================================================================ + * Python API comparison operations + * ============================================================================================ + */ + + /** + * Comparison function between two buffers of bytes, returning 1, 0 or -1 as a>b, + * a==b, or a<b respectively. The comparison is by value, using Python unsigned byte + * conventions, left-to-right (low to high index). Zero bytes are significant, even at the end + * of the array: [65,66,67]<"ABC\u0000", for example and [] is less + * than every non-empty b, while []=="". + * + * @param a left-hand wrapped array in the comparison + * @param b right-hand wrapped object in the comparison + * @return 1, 0 or -1 as a>b, a==b, or a<b respectively + */ + private static int compare(PyBuffer a, PyBuffer b) { + + // Compare elements one by one in these ranges: + int ap = 0; + int aEnd = ap + a.getLen(); + int bp = 0; + int bEnd = b.getLen(); + + while (ap < aEnd) { + if (bp >= bEnd) { + // a is longer than b + return 1; + } else { + // Compare the corresponding bytes + int aVal = a.intAt(ap++); + int bVal = b.intAt(bp++); + int diff = aVal - bVal; + if (diff != 0) { + return (diff < 0) ? -1 : 1; + } + } + } + + // All the bytes matched and we reached the end of a + if (bp < bEnd) { + // But we didn't reach the end of b + return -1; + } else { + // And the end of b at the same time, so they're equal + return 0; + } + } + + /** + * Comparison function between this buffer and any other object. The inequality + * comparison operators are based on this. + * + * @param b + * @return 1, 0 or -1 as this>b, this==b, or this<b respectively, or -2 if the comparison is + * not implemented + */ + private int buffer_cmp(PyObject b) { + + // Check the memeoryview is still alive: works here for all the inequalities + PyBuffer buf = getBuffer(); + try { + + // Try to get a byte-oriented view + PyBuffer bv = BaseBytes.getView(b); + + if (bv == null) { + // Signifies a type mis-match. See PyObject._cmp_unsafe() and related code. + return -2; + + } else { + + try { + if (bv == buf) { + // Same buffer: quick result + return 0; + } else { + // Actually compare the contents + return compare(buf, bv); + } + + } finally { + // Must always let go of the buffer + bv.release(); + } + } + } finally { + buf.release(); + } + + } + + /** + * Fail-fast comparison function between byte array types and any other object, for when the + * test is only for equality. The inequality comparison operators __eq__ and + * __ne__ are based on this. + * + * @param b + * @return 0 if this==b, or +1 or -1 if this!=b, or -2 if the comparison is not implemented + */ + private int buffer_cmpeq(PyObject b) { + + // Get a view on the underlying object + PyBuffer buf = getBuffer(); + try { + + // Try to get a byte-oriented view + PyBuffer bv = BaseBytes.getView(b); + + if (bv == null) { + // Signifies a type mis-match. See PyObject._cmp_unsafe() and related code. + return -2; + + } else { + + try { + if (bv == buf) { + // Same buffer: quick result + return 0; + } else if (bv.getLen() != buf.getLen()) { + // Different size: can't be equal, and we don't care which is bigger + return 1; + } else { + // Actually compare the contents + return compare(buf, bv); + } + + } finally { + // Must always let go of the buffer + bv.release(); + } + } + } finally { + buf.release(); + } + + } + + /* + * These strings are adapted from the patch in CPython issue 15855 and the on-line documentation + * most attributes do not come with any docstrings in CPython 2.7, so the make_pydocs trick + * won't work. This is a complete set, although not all are needed in Python 2.7. + */ + private final static String tobytes_doc = "M.tobytes() -> bytes\n\n" + + "Return the data in the buffer as a bytestring (an object of class str)."; + + private final static String tolist_doc = "M.tolist() -> list\n\n" + + "Return the data in the buffer as a list of elements."; + + /* + * ============================================================================================ + * Support for the Buffer API + * ============================================================================================ + */ + + /** + * {@inheritDoc} + *

+ * The {@link PyBuffer} returned from this method is provided directly by the underlying object + * on which this buffer was constructed, taking account of the slicing arguments (offset and + * size), if these were given when the buffer was constructed. + */ + @Override + public PyBuffer getBuffer(int flags) { + + // Get a simple buffer meeting the specification of tha caller + PyBuffer buf = object.getBuffer(flags); + + // This may already be what we need, or this buffer may be a sub-range of the object + if (offset > 0 || size >= 0) { + /* + * It's a sub-range so we have to construct a slice buffer on the first buffer. Take + * care that the bounds of the slice are within the object, which may have changed size + * since the buffer was created. + */ + PyBuffer first = buf; + int start = offset; + int length = first.getLen() - start; + + if (length <= 0) { + // Range now lies outside object: zero length slice + start = length = 0; + } else if (size >= 0 && size < length) { + // A size less than the available bytes was specified (size==-1 => all of them) + length = size; + } + + // Now offset and length specify a feasible slice + buf = first.getBufferSlice(flags, offset, length); + + // We should release our first lease because the slice buf holds one. + // That lease will be released when buf is released. + first.release(); + } + return buf; + } + + /* + * ============================================================================================ + * API for org.python.core.PySequence + * ============================================================================================ + */ + /** + * Gets the indexed element of the buffer as a one byte string. This is an + * extension point called by PySequence in its implementation of {@link #__getitem__}. It is + * guaranteed by PySequence that the index is within the bounds of the buffer. + * + * @param index index of the element to get. + * @return one-character string formed from the byte at the index + */ + @Override + protected PyString pyget(int index) { + // Our chance to check the buffer is still alive + PyBuffer buf = getBuffer(); + try { + // Treat the byte at the index as a character code + return new PyString(String.valueOf((char)buf.intAt(index))); + } finally { + buf.release(); + } + } + + /** + * Returns a slice of elements from this sequence as a PyString. + * + * @param start the position of the first element. + * @param stop one more than the position of the last element. + * @param step the step size. + * @return a PyString corresponding the the given range of elements. + */ + @Override + protected synchronized PyString getslice(int start, int stop, int step) { + // Our chance to check the buffer is still alive + PyBuffer buf = getBuffer(); + try { + int n = sliceLength(start, stop, step); + PyBuffer first = buf; + buf = first.getBufferSlice(PyBUF.FULL_RO, start, n, step); + first.release(); // We've finished (buf holds a lease) + PyString ret = Py.newString(buf.toString()); + return ret; + } finally { + buf.release(); + } + } + + /** + * buffer*int represent repetition in Python, and returns a str ( + * bytes) object. + * + * @param count the number of times to repeat this. + * @return a PyString repeating this buffer (as a str) that many times + */ + @Override + protected synchronized PyString repeat(int count) { + PyBuffer buf = getBuffer(); + try { + PyString ret = Py.newString(buf.toString()); + return (PyString)ret.repeat(count); + } finally { + buf.release(); + } + } + + /** + * Sets the indexed element of the buffer to the given value, treating the + * operation as assignment to a slice of length one. This is different from the same operation + * on a byte array, where the assigned value must be an int: here it must have the buffer API + * and length one. This is an extension point called by PySequence in its implementation of + * {@link #__setitem__} It is guaranteed by PySequence that the index is within the bounds of + * the buffer. Any other clients calling pyset(int, PyObject) must make + * the same guarantee. + * + * @param index index of the element to set. + * @param value to set this element to, regarded as a buffer of length one unit. + * @throws PyException(AttributeError) if value cannot be converted to an integer + * @throws PyException(ValueError) if value<0 or value>255 + */ + @Override + public synchronized void pyset(int index, PyObject value) throws PyException { + // Our chance to check the buffer is still alive + PyBuffer buf = getBuffer(); + try { + + // Get a buffer API on the value being assigned + PyBuffer valueBuf = BaseBytes.getViewOrError(value); + try { + if (valueBuf.getLen() != 1) { + // CPython 2.7 message + throw Py.ValueError("cannot modify size of buffer object"); + } + buf.storeAt(valueBuf.byteAt(0), index); + } finally { + valueBuf.release(); + } + } finally { + buf.release(); + } + } + + /** + * Sets the given range of elements according to Python slice assignment semantics. If the step + * size is one, it is a simple slice and the operation is equivalent to replacing that slice, + * with the value, accessing the value via the buffer protocol. + * + *

+     * a = bytearray(b'abcdefghijklmnopqrst')
+     * m = buffer(a)
+     * m[2:7] = "ABCDE"
+     * 
+ * + * Results in a=bytearray(b'abABCDEhijklmnopqrst'). + *

+ * If the step size is one, but stop-start does not match the length of the right-hand-side a + * ValueError is thrown. + *

+ * If the step size is not one, and start!=stop, the slice defines a certain number of elements + * to be replaced. This function is not available in Python 2.7 (but it is in Python 3.3). + *

+ * + *

+     * a = bytearray(b'abcdefghijklmnopqrst')
+     * a[2:12:2] = iter( [65, 66, 67, long(68), "E"] )
+     * 
+ * + * Results in a=bytearray(b'abAdBfChDjElmnopqrst') in Python 3.3. + * + * @param start the position of the first element. + * @param stop one more than the position of the last element. + * @param step the step size. + * @param value an object consistent with the slice assignment + */ + @Override + protected synchronized void setslice(int start, int stop, int step, PyObject value) { + // Our chance to check the buffer is still alive + PyBuffer buf = getBuffer(); + try { + + if (step == 1 && stop < start) { + // Because "b[5:2] = v" means insert v just before 5 not 2. + // ... although "b[5:2:-1] = v means b[5]=v[0], b[4]=v[1], b[3]=v[2] + stop = start; + } + + // Get a buffer API on the value being assigned + PyBuffer valueBuf = BaseBytes.getViewOrError(value); + + // We'll also regard the assigned slice as a buffer. + PyBuffer bufSlice = null; + + try { + // How many destination items? Has to match size of value. + int n = sliceLength(start, stop, step); + if (n != valueBuf.getLen()) { + // CPython 2.7 message + throw Py.ValueError("cannot modify size of buffer object"); + } + + /* + * In the next section, we get a sliced view of the buf and write the value to it. + * The approach to errors is unusual for compatibility with CPython. We pretend we + * will not need a WRITABLE buffer in order to avoid throwing a BufferError. This + * does not stop the returned object being writable, simply avoids the check. If in + * fact it is read-only, then trying to write raises TypeError. + */ + + bufSlice = buf.getBufferSlice(PyBUF.FULL_RO, start, n, step); + bufSlice.copyFrom(valueBuf); + + } finally { + + // Release the buffers we obtained (if we did) + if (bufSlice != null) { + bufSlice.release(); + } + valueBuf.release(); + } + } finally { + buf.release(); + } + } + +} diff --git a/src/org/python/core/PyUnicode.java b/src/org/python/core/PyUnicode.java --- a/src/org/python/core/PyUnicode.java +++ b/src/org/python/core/PyUnicode.java @@ -428,20 +428,33 @@ } } + /** + * Helper used many times to "coerce" a method argument into a PyUnicode (which it + * may already be). A null argument or a PyNone causes + * null to be returned. + * + * @param o the object to coerce + * @return an equivalent PyUnicode (or o itself, or null) + */ private PyUnicode coerceToUnicode(PyObject o) { if (o == null) { return null; } else if (o instanceof PyUnicode) { - return (PyUnicode) o; - } else if (o instanceof PyString) { - return new PyUnicode(o.toString()); + return (PyUnicode)o; } else if (o == Py.None) { return null; + } else if (o instanceof BufferProtocol) { + // PyString or PyByteArray, PyMemoryView, Py2kBuffer ... + PyBuffer buf = ((BufferProtocol)o).getBuffer(PyBUF.FULL_RO); + try { + return new PyUnicode(buf.toString()); + } finally { + buf.release(); + } } else { - throw Py.TypeError("coercing to Unicode: need string or buffer, " + - o.getType().fastGetName() + "found"); + throw Py.TypeError("coercing to Unicode: need string or buffer, " + + o.getType().fastGetName() + " found"); } - } @ExposedMethod(doc = BuiltinDocs.unicode___contains___doc) 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 @@ -294,6 +294,7 @@ dict.__setitem__("False", Py.False); dict.__setitem__("bytes", PyString.TYPE); dict.__setitem__("bytearray", PyByteArray.TYPE); + dict.__setitem__("buffer", Py2kBuffer.TYPE); dict.__setitem__("memoryview", PyMemoryView.TYPE); // Work in debug mode by default diff --git a/src/org/python/core/buffer/SimpleStringBuffer.java b/src/org/python/core/buffer/SimpleStringBuffer.java --- a/src/org/python/core/buffer/SimpleStringBuffer.java +++ b/src/org/python/core/buffer/SimpleStringBuffer.java @@ -90,8 +90,14 @@ */ @Override public PyBuffer getBufferSlice(int flags, int start, int length) { - // The new string content is just a sub-string. (Non-copy operation in Java.) - return new SimpleStringView(getRoot(), flags, bufString.substring(start, start + length)); + if (length > 0) { + // The new string content is just a sub-string. (Non-copy operation in Java.) + return new SimpleStringView(getRoot(), flags, + bufString.substring(start, start + length)); + } else { + // Special case for length==0 where start out of bounds sometimes raises exception. + return new ZeroByteBuffer.View(getRoot(), flags); + } } /** -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Sep 27 21:17:21 2013 From: jython-checkins at python.org (jeff.allen) Date: Fri, 27 Sep 2013 21:17:21 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge_buffer_development?= Message-ID: <3cmjSK1rKXz7LjM@mail.python.org> http://hg.python.org/jython/rev/f731a595b90a changeset: 7130:f731a595b90a parent: 7129:2751805d22a5 parent: 7127:7dac33227bb7 user: Jeff Allen date: Fri Sep 27 20:15:57 2013 +0100 summary: Merge buffer development files: Lib/test/bark.py | 4 ++++ Lib/test/test_java_integration.py | 17 +++++++++++------ src/org/python/core/Py.java | 6 ++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Lib/test/bark.py b/Lib/test/bark.py --- a/Lib/test/bark.py +++ b/Lib/test/bark.py @@ -1,5 +1,6 @@ from __future__ import print_function +import sys from java.io import Serializable from java.util.concurrent import Callable @@ -25,6 +26,9 @@ def call(self): # Using print forces use of PySystemState and shows it's initialized print("%s barks %s times" % (self.name, self.number)) + # Verify that site has been imported and therefore + # site-packages and distutils/setuptools goodness is available + return "site" in sys.modules def __eq__(self, other): return self.name == other.name and self.number == other.number diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py --- a/Lib/test/test_java_integration.py +++ b/Lib/test/test_java_integration.py @@ -668,7 +668,7 @@ tempdir = tempfile.mkdtemp() try: SerializableProxies.serialized_path = tempdir - import bark #importlib.import_module("bark") + import bark dog = bark.Dog() self.assertEqual(dog.whoami(), "Rover") self.assertEqual(dog.serialVersionUID, 1) @@ -686,7 +686,10 @@ public static void main(String[] args) { Dog dog = new Dog(); try { - dog.call(); + Boolean b = (Boolean)(dog.call()); + if (!b) { + throw new RuntimeException("Expected site module to be imported"); + } } catch(Exception e) { System.err.println(e); @@ -711,10 +714,12 @@ "-classpath", classpath, "BarkTheDog"] env = dict(os.environ) env.update(JYTHONPATH=os.path.normpath(os.path.join(__file__, ".."))) - self.assertEqual( - subprocess.check_output(cmd, env=env, universal_newlines=True), - "Class defined on CLASSPATH \n" - "Rover barks 42 times\n") + self.assertRegexpMatches( + subprocess.check_output(cmd, env=env, universal_newlines=True, + stderr=subprocess.STDOUT), + r"^\*sys-package-mgr\*: processing new jar, '.+?/proxies.jar'\n" + "Class defined on CLASSPATH \n" + "Rover barks 42 times\n$".format(tempdir)) finally: pass # print "Will not remove", tempdir 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 @@ -950,6 +950,12 @@ return; } + if (Options.importSite) { + // Ensure site-packages are available before attempting to import module. + // This step enables supporting modern Python apps when using proxies + // directly from Java (eg through clamp). + imp.load("site"); + } PyObject mod = imp.importName(module.intern(), false); PyType pyc = (PyType)mod.__getattr__(pyclass.intern()); -- Repository URL: http://hg.python.org/jython