Skip to content

Commit

Permalink
A substantial re-organisation of the interactive console.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jeff5 committed Sep 7, 2013
1 parent 1342be0 commit 4e46911
Show file tree
Hide file tree
Showing 14 changed files with 1,147 additions and 308 deletions.
17 changes: 9 additions & 8 deletions Lib/readline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -38,7 +39,7 @@ def _setup_history():
# 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")
Expand Down Expand Up @@ -68,7 +69,7 @@ def get_line_buffer():

def insert_text(string):
_reader.putString(string)

def read_init_file(filename=None):
warn("read_init_file: %s" % (filename,), NotImplementedWarning, "module", 2)

Expand Down Expand Up @@ -128,8 +129,8 @@ 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)

Expand Down Expand Up @@ -161,7 +162,7 @@ def complete_handler(buffer, cursor, candidates):
return start

_reader.addCompletor(complete_handler)


def get_completer():
return _completer_function
Expand Down
1 change: 1 addition & 0 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,7 @@ The readme text for the next release will be like:
<fileset dir="${test.source.dir}" includes="**/*Test*.java">
<exclude name="javatests/**/*" />
<exclude name="**/InterpTestCase.java" />
<exclude name="**/jythonTest*" /> <!-- Must run interactively -->
<exclude name="org/python/antlr/**" />
<exclude name=".classpath" />
<exclude name=".project" />
Expand Down
15 changes: 8 additions & 7 deletions registry
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@ python.packages.directories = java.ext.dirs
#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

Expand Down
60 changes: 60 additions & 0 deletions src/org/python/core/Console.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) 2013 Jython Developers
package org.python.core;

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
* A class named in configuration as the value of <code>python.console</code> must implement this
* interface, and provide a constructor with a single <code>String</code> argument, to be acceptable
* during initialization of the interpreter. The argument to the constructor names the encoding in
* use on the console. Such a class may provide line editing and history recall to an interactive
* console. A default implementation (that does not provide any such facilities) is available as
* {@link PlainConsole}.
*/
public interface Console {

/**
* Complete initialization and (optionally) install a stream object with line-editing as the
* replacement for <code>System.in</code>.
*
* @throws IOException in case of failure related to i/o
*/
public void install() throws IOException;

/**
* Uninstall the Console (if possible). A Console that installs a replacement for
* <code>System.in</code> should put back the original value.
*
* @throws UnsupportedOperationException if the Console cannot be uninstalled
*/
public void uninstall() throws UnsupportedOperationException;

/**
* Write a prompt and read a line from standard input. The returned line does not include the
* trailing newline. When the user enters the EOF key sequence, an EOFException should be
* raised. The built-in function <code>raw_input</code> calls this method on the installed
* console.
*
* @param prompt to output before reading a line
* @return the line read in (encoded as bytes)
* @throws IOException in case of failure related to i/o
* @throws EOFException when the user enters the EOF key sequence
*/
public ByteBuffer raw_input(CharSequence prompt) throws IOException, EOFException;

/**
* Write a prompt and read a line from standard input. The returned line does not include the
* trailing newline. When the user enters the EOF key sequence, an EOFException should be
* raised. The Py3k built-in function <code>input</code> calls this method on the installed
* console.
*
* @param prompt to output before reading a line
* @return the line read in
* @throws IOException in case of failure related to i/o
* @throws EOFException when the user enters the EOF key sequence
*/
public CharSequence input(CharSequence prompt) throws IOException, EOFException;

}
106 changes: 106 additions & 0 deletions src/org/python/core/PlainConsole.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) 2013 Jython Developers
package org.python.core;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;

/**
* A base class for classes that can install a console wrapper for a specific console-handling
* library. The Jython command-line application, when it detects that the console is an interactive
* session, chooses and installs a class named in registry item <code>python.console</code>, and
* implementing interface {@link Console}. <code>PlainConsole</code> may be selected by the user
* through that registry, and is the default console when the selected one fails to load. It will
* also be installed by the Jython command-line application when in non-interactive mode.
* <p>
* Unlike some consoles, <code>PlainConsole</code> does not install a replacement for
* <code>System.in</code> or use a native library. It prompts on <code>System.out</code> and reads
* from <code>System.in</code> (wrapped with the console encoding).
*/
public class PlainConsole implements Console {

/** Encoding to use for line input. */
public final String encoding;

/** Encoding to use for line input as a <code>Charset</code>. */
public final Charset encodingCharset;

/** BufferedReader used by {@link #input(CharSequence)} */
private BufferedReader reader;

/**
* Construct an instance of the console class specifying the character encoding. This encoding
* must be one supported by the JVM. The PlainConsole does not replace <code>System.in</code>,
* and does not add any line-editing capability to what is standard for your OS console.
*
* @param encoding name of a supported encoding or <code>null</code> for
* <code>Charset.defaultCharset()</code>
*/
public PlainConsole(String encoding) throws IllegalCharsetNameException,
UnsupportedCharsetException {
if (encoding == null) {
encoding = Charset.defaultCharset().name();
}
this.encoding = encoding;
encodingCharset = Charset.forName(encoding);
}

@Override
public void install() {
// Create a Reader with the right character encoding
reader = new BufferedReader(new InputStreamReader(System.in, encodingCharset));
}

/**
* A <code>PlainConsole</code> may be uninstalled. This method assumes any sub-class may not be
* uninstalled. Sub-classes that permit themselves to be uninstalled <b>must</b> override (and
* not call) this method.
*
* @throws UnsupportedOperationException unless this class is exactly <code>PlainConsole</code>
*/
@Override
public void uninstall() throws UnsupportedOperationException {
Class<? extends Console> myClass = this.getClass();
if (myClass != PlainConsole.class) {
throw new UnsupportedOperationException(myClass.getSimpleName()
+ " console may not be uninstalled.");
}
}

/**
* {@inheritDoc}
* <p>
* The base implementation calls {@link #input(CharSequence)} and applies the console encoding
* to obtain the bytes. This may be a surprise. Line-editing consoles necessarily operate in
* terms of characters rather than bytes, and therefore support a direct implementation of
* <code>input</code>.
*/
@Override
public ByteBuffer raw_input(CharSequence prompt) throws IOException, EOFException {
CharSequence line = input(prompt);
return encodingCharset.encode(CharBuffer.wrap(line));
}

// The base implementation simply uses <code>System.out</code> and <code>System.in</code>.
@Override
public CharSequence input(CharSequence prompt) throws IOException, EOFException {

// Issue the prompt with no newline
System.out.print(prompt);

// Get the line from the console via java.io
String line = reader.readLine();
if (line == null) {
throw new EOFException();
} else {
return line;
}
}

}
77 changes: 74 additions & 3 deletions src/org/python/core/Py.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -1386,6 +1390,73 @@ public static void setFrame(PyFrame f) {
getThreadState().frame = f;
}

/**
* The handler for interactive consoles, set by {@link #installConsole(Console)} and accessed by
* {@link #getConsole()}.
*/
private static Console console;

/**
* Get the Jython Console (used for <code>input()</code>, <code>raw_input()</code>, etc.) as
* constructed and set by {@link PySystemState} initialization.
*
* @return the Jython Console
*/
public static Console getConsole() {
if (console == null) {
// We really shouldn't ask for a console before PySystemState initialization but ...
try {
// ... something foolproof that we can supersede.
installConsole(new PlainConsole("ascii"));
} catch (Exception e) {
// This really, really shouldn't happen
throw Py.RuntimeError("Could not create fall-back PlainConsole: " + e);
}
}
return console;
}

/**
* Install the provided Console, first uninstalling any current one. The Jython Console is used
* for <code>raw_input()</code> etc., and may provide line-editing and history recall at the
* prompt. A Console may replace <code>System.in</code> with its line-editing input method.
*
* @param console The new Console object
* @throws UnsupportedOperationException if some prior Console refuses to uninstall
* @throws IOException if {@link Console#install()} raises it
*/
public static void installConsole(Console console) throws UnsupportedOperationException,
IOException {
if (Py.console != null) {
// Some Console class already installed: may be able to uninstall
Py.console.uninstall();
Py.console = null;
}

// Install the specified Console
console.install();
Py.console = console;

// Cause sys (if it exists) to export the console handler that was installed
if (Py.defaultSystemState != null) {
Py.defaultSystemState.__setattr__("_jy_console", Py.java2py(console));
}
}

/**
* Check (using the {@link POSIX} library) whether we are in an interactive environment. Amongst
* other things, this affects the type of console that may be legitimately installed during
* system initialisation.
*
* @return
*/
public static boolean isInteractive() {
// Decide if System.in is interactive
POSIX posix = POSIXFactory.getPOSIX();
FileDescriptor in = FileDescriptor.in;
return posix.isatty(in);
}

/* A collection of functions for implementing the print statement */
public static StdoutWrapper stderr;
static StdoutWrapper stdout;
Expand Down
Loading

0 comments on commit 4e46911

Please sign in to comment.