Reputation: 4282
In a Python script, programmers can import readline which then gives input() extended abilities (readline has many other uses). I am wanting to use select.select() in my scripts instead of input() because I like the timeout feature. However, when readline is imported, I cannot use the features that input() gains with readline. An example of the "extended features" I am referring to is being able to press the up-key and see the previous input or using the left and right arrow-keys to move the inline cursor to make changes to the input.
Question: How can I make select.select() have GNU-readline features? Is this even possible?
EDIT: Just in case any of you are curious as to what I am trying to accomplish, I made a terminal-based chatbot (kind of like Alicebot). I want the bot to get bored and do something else if the bit does not receive any input within a set amount of time. (https://launchpad.net/neobot)
Upvotes: 5
Views: 1461
Reputation: 198
It seems like readline was designed with this ability in mind (link).
An alternate interface is available to plain readline(). Some applications need to interleave keyboard I/O with file, device, or window system I/O, typically by using a main loop to select() on various file descriptors. To accomodate this need, readline can also be invoked as a `callback' function from an event loop. There are functions available to make this easy.
However it seems these bindings aren't implemented in Python. Doing a quick search, there's a chap over here who's done a POC by loading the function calls using CTypes. This isn't ideal, but lacking any alternative, perhaps this is your only course of action.
Upvotes: 0
Reputation: 28036
You could accomplish this using the readline.set_pre_input_hook([function]) mechanism of the readline module.
Here's an example that times out after 10 seconds of no input - not implemented is a mechanism to disable the alarm if input is provided.
This would have to be done differently with threads since signals can't traverse threads. But, you get the basic idea..
I apologize for advance for this code, I'm at a coffee shop on my laptop and just kinda slapped it together. This is python2.7 code, but should basically be compatible with python3 - the concept is the important part.
I think you would want to put the alarm disable somewhere at the beginning of the input_loop() function if you wanted every line of input to have a timeout.
You should also take a look at the readline.c source in the Python module tree for more ideas.
#!/usr/bin/python
import readline
import logging
import signal
import os
LOG_FILENAME = '/tmp/completer.log'
HISTORY_FILENAME = '/tmp/completer.hist'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG,)
class YouAreTooSlow(Exception): pass
def get_history_items():
return [ readline.get_history_item(i)
for i in xrange(1, readline.get_current_history_length() + 1)
]
class HistoryCompleter(object):
def __init__(self):
self.matches = []
return
def complete(self, text, state):
response = None
if state == 0:
history_values = get_history_items()
logging.debug('history: %s', history_values)
if text:
self.matches = sorted(h
for h in history_values
if h and h.startswith(text))
else:
self.matches = []
logging.debug('matches: %s', self.matches)
try:
response = self.matches[state]
except IndexError:
response = None
logging.debug('complete(%s, %s) => %s',
repr(text), state, repr(response))
return response
def input_loop():
if os.path.exists(HISTORY_FILENAME):
readline.read_history_file(HISTORY_FILENAME)
print 'Max history file length:', readline.get_history_length()
print 'Startup history:', get_history_items()
try:
while True:
line = raw_input('Prompt ("stop" to quit): ')
if line == 'stop':
break
if line:
print 'Adding "%s" to the history' % line
finally:
print 'Final history:', get_history_items()
readline.write_history_file(HISTORY_FILENAME)
# Register our completer function
def slow_handler(signum, frame):
print 'Signal handler called with signal', signum
raise YouAreTooSlow()
def pre_input_hook():
signal.signal(signal.SIGALRM, slow_handler)
signal.alarm(10)
readline.set_pre_input_hook(pre_input_hook)
readline.set_completer(HistoryCompleter().complete)
# Use the tab key for completion
readline.parse_and_bind('tab: complete')
# Prompt the user for text
input_loop()
Upvotes: 4