C++ обертка библиотеки readline

Author / Автор: Сергей Сацкий
Publication date / Опубликовано: 09.05.2006
Version / Версия текста: 1.1

Назначение

Предлагаемая обертка упрощает для C++ разработчика использование библиотек readline и history. Не все возможности библиотеки readline поддержаны (не поддерживаются, например, возможность отмены/повтора действий). Однако наиболее часто требуемая функциональность поддержана:

  • Редактирование строки ввода с учетом предпочтений пользователя
  • Поддержка истории команд в текущем сеансе
  • Сохранение / восстановление истории команд в / из указанный файл
  • Завершители команд, предоставляемые пользователем
  • Привязка вызовов boost::function к клавишам (keymap)

Пример

Используя предлагаемую обертку можно написать код, подобный приведенному ниже:

    // The wrapper is in a single header file.
#include "SReadline.h"
using namespace swift;

#include <iostream>
#include <vector>
#include <string>
using namespace std;

int  main( int  argc, char **  argv )
{ 
        // First parameter is a file name to save / load the
        // commands history. The second is a max number of stored commands
        // Both parameters could be ommited. In this case the history size
        // will be 64 and no save/restore operations will be performed.
    SReadline           Reader( "/tmp/.testhist", 32 );


        // Prepare the list of my own completers
    vector< string >    Completers;

        // The following is supported:
        // - "identifiers"
        // - special identifier %file - means to perform a file name completion
    Completers.push_back( "command1 opt1" );
    Completers.push_back( "command1 opt2" );
    Completers.push_back( "command1 opt3 %file" );
    Completers.push_back( "command2 opt4" );
    Completers.push_back( "command2 opt5 %file %file" );


        // Now register the completers.
        // Actually it is possible to re-register another set at any time
    Reader.RegisterCompletions( Completers );

        // Now we can ask user for a line
    string      UserInput;
    bool        EndOfInput( false );

    for ( ; ; )
    {
            // The last parameter could be ommited
        UserInput = Reader.GetLine( "Please input your command> ", EndOfInput );
        if ( EndOfInput )
        {
            cout << "End of the session. Exiting." << endl;
            break;
        }
        cout << "User input: '" << UserInput << "'." << endl;
        cout << "Press Ctrl+D for gracefull exit" << endl;
    }

        // The history could be saved to an arbitrary file at any time
    Reader.SaveHistory( "/tmp/BackupFileJustInCase" );

        // The history could be cleared
    Reader.ClearHistory();

        // And the history could be loaded at any time
    Reader.LoadHistory( "/tmp/BackupFileJustInCase" );

    return 0;
}

Протокол работы с программой может выглядеть так:

[swift@swifthome examples]# ./SReadlineExample
Please input your command> command
command1  command2
Please input your command> command1 opt
opt1  opt2  opt3
Please input your command> command1 opt2
User input: 'command1 opt2'.
Press Ctrl+D for gracefull exit
Please input your command> End of the session. Exiting.
[swift@swifthome examples]#

В приведенном протоколе клавиша TAB нажималась для завершения поддерживаемых команд в строках 2 и 4.

Замечания:

  • Функции SaveHistory/LoadHistory могут работать и с файловыми потоками.
  • Если файл истории команд указан при конструировании обертки, то конструктор сделает попытку восстановить историю команд из указанного файла, а деструктор попытается сохранить историю команд в этом файле.
  • Конструктор обертки запоминает текущий завершитель команд пользователя, а деструктор восстановит его.
  • Обертка не устанавливает никаких обработчиков сигналов. Библиотека readline производит всю необходимую ей обработку сигналов в то время, когда поток управления находится в вызове readline( . . . ).
  • Функция RegisterCompletions() - шаблонная. Она ожидает контейнер, в котором перебираются все элементы и к каждому элементу применяется статическое преобразование к строке. Статическое преобразование к строке сделано для возможности использования контейнера произвольных элементов с единственным требованием - получить строку описания завершителей пользователя.

Предположим, что разработчику удобно воспользоваться следующим контейнером для хранения завершителей:

typedef pair< string, int >   Element;
typedef list< Element >       Container;

(Здесь тип int в паре взят только для упрощения записи)

В таком случае потребуется предоставить преобразование от Element к string. Это можно сделать, например, так:

struct MyElement : public Element
{
    operator string () const { return first; }
    MyElement( const string & Arg1, int  Arg2 ) : Element( Arg1, Arg2 ) {}
};

Затем

typedef list< MyElement >     MyContainer;

И наконец

. . .
MyContainer   Completers;

Completers.push_back( MyElement( "command1 opt1",             1 ) );
Completers.push_back( MyElement( "command1 opt2",             2 ) );
Completers.push_back( MyElement( "command1 opt3 %file",       3 ) );
Completers.push_back( MyElement( "command2 opt4",             4 ) );
Completers.push_back( MyElement( "command2 opt5 %file %file", 5 ) );

. . .
Reader.RegisterCompletions( Completers );
. . .

Подобный подход может быть полезен, если указатели на функции (или что-нибудь аналогичное), которые обрабатывают команды, хранятся в том же контейнере, что и описатели завершителей ввода.

Исходные тексты приведенного выше простого примера, примера с более сложными элементами контейнера заполнителей и doxygen документацию можно найти на сайте автора по адресу http://satsky.spb.ru

Требования к окружению

Обертка использует следующие библиотеки:

  • libreadline
  • libncurses
  • библиотеку boost

Предупреждения

Будьте внимательны, SReadline не имеет средств защиты от некорректной работы в многопоточном окружении. Главная причина этого состоит в том, что библиотека readline, которую использует обертка, предлагает C интерфейс и один сеанс использования библиотеки readline предполагает неизвестное количество обратных вызовов функций обработчиков. Тем не менее, обертка может быть полезна во многих случаях, так как большому количеству приложений не требуется конкурентный интерактивный ввод из нескольких потоков.

Кроме того, не гарантируется правильная работа обертки в случае использования обертки из различных единиц компиляции. Это связано с тем, что данные находятся в анонимном пространстве имен в заголовочном файле, и каждая единица компиляции будет иметь свою копию данных. Решение этой проблемы - перемещение данных в отдельную единицу компиляции.

Тестовое окружение

Код тестировался с gcc 3.4.4 на Linux. Будьте внимательны, в библиотеке libreadline версии 4.3 имеются утечки памяти. Они возникают каждый раз при получении сигнала SIGWINCH. Как только библиотека readline была обновлена до версии 5.1, утечки памяти прекратились. Скорее всего, код обертки будет работать правильно без каких - либо изменений и на других версиях UNIX.

Альтернативы

Существуют, по крайней мере, две альтернативы:

Литература

  1. Документация библиотеки readline: http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html
  2. документация библиотеки boost: http://www.boost.org

Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved.

Разрешается копирование и распространение этой статьи любым способом без внесения изменений, при условии, что это разрешение сохраняется.
Last Updated: May 09, 2006