Рубрики
Без рубрики

readline – Библиотека чтения GNU

Модуль readline можно использовать для улучшения интерактивных программ командной строки, чтобы упростить их использование.

Автор оригинала: 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 () для обновления экрана.

Смотрите также