Автор оригинала: Doug Hellmann.
Цель:
Предоставляет интерфейс к библиотеке чтения GNU для взаимодействие с пользователем в командной строке.
Модуль readline
можно использовать для улучшения интерактивных программ командной строки, чтобы упростить их использование. Он в основном используется для завершения текста в командной строке или «завершения по табуляции».
Примечание
Поскольку readline
взаимодействует с содержимым консоли, печать сообщений отладки затрудняет просмотр того, что происходит в примере кода, по сравнению с тем, что readline делает бесплатно. В следующих примерах модуль ведения журнала используется для записи отладочной информации в отдельный файл. Вывод журнала показан для каждого примера.
Примечание
Библиотеки GNU, необходимые для readline
, по умолчанию доступны не на всех платформах. Если ваша система не включает их, вам может потребоваться перекомпилировать интерпретатор Python, чтобы включить модуль, после установки зависимостей. А
Особая благодарность Джиму Бейкеру за указание на этот пакет.
Настройка
Существует два способа настроить базовую библиотеку readline с помощью файла конфигурации или функции parse_and_bind ()
. Параметры конфигурации включают привязку клавиш для вызова завершения, режимы редактирования ( vi
или emacs
) и многие другие значения. За подробностями обращайтесь к документации библиотеки GNU readline.
Самый простой способ включить автозавершение табуляции – это вызвать parse_and_bind ()
. Другие параметры можно установить одновременно. В этом примере изменяется
readline_parse_and_bind.py
try: import gnureadline as readline except ImportError: import readline readline.parse_and_bind('tab: complete') readline.parse_and_bind('set editing-mode vi') while True: line input('Prompt ("stop" to quit): ') if line 'stop': break print('ENTERED: {!r}'.format(line))
Та же самая конфигурация может быть сохранена в виде инструкций в файле, читаемом библиотекой за один вызов. Если myreadline.rc
содержит
myreadline.rc
# Turn on tab completion tab: complete # Use vi editing mode instead of emacs set editing-mode vi
файл можно прочитать с помощью read_init_file ()
readline_read_init_file.py
try: import gnureadline as readline except ImportError: import readline readline.read_init_file('myreadline.rc') while True: line input('Prompt ("stop" to quit): ') if line 'stop': break print('ENTERED: {!r}'.format(line))
Завершение текста
Эта программа имеет встроенный набор возможных команд и использует табуляцию, когда пользователь вводит инструкции.
readline_completer.py
try: import gnureadline as readline except ImportError: import readline import logging LOG_FILENAME '/tmp/completer.log' logging.basicConfig( format'%(message)s', filenameLOG_FILENAME, levellogging.DEBUG, ) class SimpleCompleter: def __init__(self, options): self.options sorted(options) def complete(self, text, state): response None if state 0: # This is the first time for this text, # so build a match list. if text: self.matches [ s for s in self.options if s and s.startswith(text) ] logging.debug('%s matches: %s', repr(text), self.matches) else: self.matches self.options[:] logging.debug('(empty input) matches: %s', self.matches) # Return the state'th item from the match list, # if we have that many. 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(): line '' while line 'stop': line input('Prompt ("stop" to quit): ') print('Dispatch {}'.format(line)) # Register the completer function OPTIONS ['start', 'stop', 'list', 'print'] readline.set_completer(SimpleCompleter(OPTIONS).complete) # Use the tab key for completion readline.parse_and_bind('tab: complete') # Prompt the user for text input_loop()
Функция input_loop ()
читает одну строку за другой, пока входное значение не станет "stop"
. Более сложная программа может анализировать строку ввода и
Класс SimpleCompleter
хранит список «параметров», которые являются кандидатами на автозаполнение. Метод complete ()
для экземпляра предназначен для регистрации с помощью readline
в качестве источника завершений. Аргументы – это строка text
для завершения и значение state
, указывающее, сколько раз функция вызывалась с одним и тем же текстом. Функция вызывается повторно, каждый раз состояние увеличивается. Он должен возвращать строку, если есть кандидат для этого значения состояния, или None
, если кандидатов больше нет. Реализация complete ()
здесь ищет набор совпадений, когда состояние равно 0
, а затем возвращает все совпадения кандидатов по одному при последующих вызовах.
При запуске начальный результат:
$ python3 readline_completer.py Prompt ("stop" to quit):
Если дважды нажать TAB
, будет напечатан список параметров.
$ python3 readline_completer.py Prompt ("stop" to quit): list print start stop Prompt ("stop" to quit):
Файл журнала показывает, что complete ()
был вызван с двумя отдельными последовательностями
$ tail -f /tmp/completer.log (empty input) matches: ['list', 'print', 'start', 'stop'] complete('', 0) => 'list' complete('', 1) => 'print' complete('', 2) => 'start' complete('', 3) => 'stop' complete('', 4) => None (empty input) matches: ['list', 'print', 'start', 'stop'] complete('', 0) => 'list' complete('', 1) => 'print' complete('', 2) => 'start' complete('', 3) => 'stop' complete('', 4) => None
Первая последовательность начинается с первого нажатия клавиши TAB. Алгоритм завершения запрашивает всех кандидатов, но не расширяет пустую строку ввода. Затем на второй вкладке список кандидатов пересчитывается, чтобы он
Если следующий ввод – « l
», за которым следует еще одна TAB, на экране отображается:
Prompt ("stop" to quit): list
а журнал отражает различные аргументы функции complete ()
:
'l' matches: ['list'] complete('l', 0) => 'list' complete('l', 1) => None
При нажатии RETURN input ()
возвращается значение, а цикл while
Dispatch list Prompt ("stop" to quit):
Есть два возможных завершения для команды, начинающейся с « s
». Набрав « s
», а затем нажав TAB, вы обнаружите, что « start
» и « stop
» являются кандидатами, но только частично завершают текст на экране добавив “ t
“.
Файл журнала показывает:
's' matches: ['start', 'stop'] complete('s', 0) => 'start' complete('s', 1) => 'stop' complete('s', 2) => None
и экран:
Prompt ("stop" to quit): st
Примечание
Если функция завершения вызывает исключение, оно автоматически игнорируется, и readline
предполагает, что совпадения завершений нет.
Доступ к буферу завершения
Алгоритм завершения в SimpleCompleter
смотрит только на текстовый аргумент, переданный в функцию, но больше не использует внутреннее состояние строки чтения. Также можно использовать функции readline
для управления текстом
readline_buffer.py
try: import gnureadline as readline except ImportError: import readline import logging LOG_FILENAME '/tmp/completer.log' logging.basicConfig( format'%(message)s', filenameLOG_FILENAME, levellogging.DEBUG, ) class BufferAwareCompleter: def __init__(self, options): self.options options self.current_candidates [] def complete(self, text, state): response None if state 0: # This is the first time for this text, # so build a match list. origline readline.get_line_buffer() begin readline.get_begidx() end readline.get_endidx() being_completed origline[begin:end] words origline.split() logging.debug(%s', repr(origline)) logging.debug(%s', begin) logging.debug(%s', end) logging.debug(%s', being_completed) logging.debug(%s', words) if not words: self.current_candidates sorted( self.options.keys() ) else: try: if begin 0: # first word candidates self.options.keys() else: # later word first words[0] candidates self.options[first] if being_completed: # match options with portion of input # being completed self.current_candidates [ w for w in candidates if w.startswith(being_completed) ] else: # matching empty string, # use all candidates self.current_candidates candidates logging.debug(%s', self.current_candidates) except (KeyError, IndexError) as err: logging.error('completion error: %s', err) self.current_candidates [] try: response self.current_candidates[state] except IndexError: response None logging.debug('complete(%s, %s) => %s', repr(text), state, response) return response def input_loop(): line '' while line 'stop': line input('Prompt ("stop" to quit): ') print('Dispatch {}'.format(line)) # Register our completer function completer BufferAwareCompleter({ 'list': ['files', 'directories'], 'print': ['byname', 'bysize'], 'stop': [], }) readline.set_completer(completer.complete) # Use the tab key for completion readline.parse_and_bind('tab: complete') # Prompt the user for text input_loop()
В этом примере завершаются команды с подопциями. Метод complete ()
должен смотреть на позицию завершения во входном буфере, чтобы определить, является ли оно частью первого слова или более позднего слова. Если целью является первое слово, ключи словаря опций используются в качестве кандидатов. Если это не первое слово,
Есть три команды верхнего уровня, две из которых имеют подкоманды.
- list
- каталоги файлов
- файлы
- каталоги
- печать
- по имени по размеру
- по имени
- по размеру
- остановка
Следуя той же последовательности действий, что и раньше, нажатие дважды TAB дает
$ python3 readline_buffer.py Prompt ("stop" to quit): list print stop Prompt ("stop" to quit):
и в журнале:
origline='' begin=0 end=0 being_completed= complete('', 0) => list complete('', 1) => print complete('', 2) => stop complete('', 3) => None origline='' begin=0 end=0 being_completed= complete('', 0) => list complete('', 1) => print complete('', 2) => stop complete('', 3) => None
Если первое слово – "список"
(с пробелом после слова), кандидаты на завершение разные.
Prompt ("stop" to quit): list directories files
Журнал показывает, что заполняемый текст – это не полная строка, а часть после list
.
origline='list ' begin=5 end=5 being_completed= words=['list'] candidates=['files', 'directories'] complete('', 0) => files complete('', 1) => directories complete('', 2) => None ' begin=5 end=5 being_completed= words=['list'] candidates=['files', 'directories'] complete('', 0) => files complete('', 1) => directories complete('', 2) => None
История ввода
строка чтения
автоматически отслеживает историю ввода. Для работы с историей существует два разных набора функций. К истории текущего сеанса можно получить доступ с помощью get_current_history_length ()
и get_history_item ()
. Эту же историю можно сохранить в файл для последующей перезагрузки с помощью write_history_file ()
и read_history_file ()
. По умолчанию вся история сохраняется, но максимальную длину файла можно установить с помощью set_history_length ()
. Длина -1 означает, что нет
readline_history.py
try: import gnureadline as readline except ImportError: import readline import logging import os LOG_FILENAME '/tmp/completer.log' HISTORY_FILENAME '/tmp/completer.hist' logging.basicConfig( format'%(message)s', filenameLOG_FILENAME, levellogging.DEBUG, ) def get_history_items(): num_items readline.get_current_history_length() + 1 return [ readline.get_history_item(i) for i in range(1, num_items) ] class HistoryCompleter: def __init__(self): self.matches [] 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 input('Prompt ("stop" to quit): ') if line 'stop': break if line: print('Adding {!r} to the history'.format(line)) finally: print('Final history:', get_history_items()) readline.write_history_file(HISTORY_FILENAME) # Register our completer function readline.set_completer(HistoryCompleter().complete) # Use the tab key for completion readline.parse_and_bind('tab: complete') # Prompt the user for text input_loop()
HistoryCompleter
запоминает все набранное и использует эти значения при завершении последующих вводов.
$ python3 readline_history.py Max history file length: -1 Startup history: [] Prompt ("stop" to quit): foo Adding 'foo' to the history Prompt ("stop" to quit): bar Adding 'bar' to the history Prompt ("stop" to quit): blah Adding 'blah' to the history Prompt ("stop" to quit): b bar blah Prompt ("stop" to quit): b Prompt ("stop" to quit): stop Final history: ['foo', 'bar', 'blah', 'stop']
Журнал показывает этот вывод, когда за “ b
” следуют две табуляции.
history: ['foo', 'bar', 'blah'] matches: ['bar', 'blah'] complete('b', 0) => 'bar' complete('b', 1) => 'blah' complete('b', 2) => None history: ['foo', 'bar', 'blah'] matches: ['bar', 'blah'] complete('b', 0) => 'bar' complete('b', 1) => 'blah' complete('b', 2) => None
Когда сценарий запускается во второй раз, вся история считывается из файла.
$ python3 readline_history.py Max history file length: -1 Startup history: ['foo', 'bar', 'blah', 'stop'] Prompt ("stop" to quit):
Есть функции для удаления отдельных элементов истории и очистки всей истории,
Крючки
Доступно несколько хуков для запуска действий как части последовательности взаимодействия. Ловушка startup вызывается непосредственно перед печатью приглашения, а ловушка pre-input запускается после приглашения, но перед чтением текста из
readline_hooks.py
try: import gnureadline as readline except ImportError: import readline def startup_hook(): readline.insert_text('from startup_hook') def pre_input_hook(): readline.insert_text(' from pre_input_hook') readline.redisplay() readline.set_startup_hook(startup_hook) readline.set_pre_input_hook(pre_input_hook) readline.parse_and_bind('tab: complete') while True: line input('Prompt ("stop" to quit): ') if line 'stop': break print('ENTERED: {!r}'.format(line))
Любой из этих хуков – потенциально хорошее место для использования insert_text ()
для изменения
$ python3 readline_hooks.py Prompt ("stop" to quit): from startup_hook from pre_input_hook
Если буфер изменен внутри обработчика предварительного ввода, необходимо вызвать redisplay ()
для обновления экрана.
Смотрите также
- стандартная библиотечная документация для readline
- GNU readline – документация для библиотеки GNU readline.
- формат файла инициализации readline – формат файла инициализации и конфигурации.
- effbot: модуль readline – руководство Effbot по модулю readline.
- gnureadline –