readline C++ Wrapper

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

Purpose

The wrapper simplifies usage of the readline and history libraries for C++ programmers. Not all the readline features are wrapped however the most often used are covered including:

  • editing the input line basing on the user preferences
  • commands history
  • saving/loading commands history to/from specified files
  • custom completers via standard containers
  • binding keys to boost::function calls

Example

Using the wrapper it is possible to write the following code:

    // 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;
}

The following dialog with a user can take place:

[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]#

The TAB key was pressed at the lines 2 and 4 to get the supported commands completions.

Notes:

  • The SaveHistory/LoadHistory functions can also work with file streams.
  • If the history file is specified the constructor will try to load the commands and the destructor will try to save the history.
  • The constructor saves the current readline completer function and it will be restored in the destructor
  • The wrapper does not set up any signal handlers. The readline library performs all the signal handling while the thread is in the readline(. . .) call.
  • The RegisterCompletions() function is actually a template one. It expects a container. Internally the function iterates through the container and does a static cast to string for each container element. The purpose of the static cast is to make it possible to pass an arbitrary container of any elements with the only requirement: to be able to get a string which describes user completions.

Suppose somebody wants to have the following container to store the completers:

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

(Here the int type is taken just to simplify the story)

In this case it will be required to provide a way to convert the Element into a string. That could be done as follows:

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

Then

typedef list< MyElement >     MyContainer;

And now

. . .
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 );
. . .

A similar approach could be useful in case if the command processing function pointers (or a functional equivalent) and commands variations are stored in the same container.

The wrapper doxygen documentation, the source code of the example given above and the source code of a more complicated example with more complicated completions container elements are available at the author's web site http://satsky.spb.ru

Prerequisites

The wrapper relies on the following libraries:

  • libreadline
  • libncurses
  • the boost library

Cautions

Be aware that the SReadline wrapper class is not thread safe. The main reason is the nature of the underlying readline library. It is based on a pure C interface and one session of the library usage supposes unpredictable number of calls of the callback functions. Nevertheless the wrapper is still useful as soon as many applications do not need to have a user input concurrently from different threads.

It is also not garanteed that the wrapper works correctly in case of using it from many compilation units. It is caused by the fact that data are located in a header file inside an anonymous name space. So each compilation unit will have its own copy of the data. This problem can be resolved by moving the data declarations into a separate compilation unit.

Test Environment

The code has been tested on gcc 3.4.4 and Linux.

Be aware that the libreadline 4.3 has a memory leak. It leaks each time when SIGWINCH signal comes. As soon as the readline was updated to 5.1 no leaks were found.

Most probably the code will work perfectly without any changes on other UNIX platforms.

Alternatives

There are at least two alternatives:

References

  1. the readline project documentation could be found here: http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html
  2. the boost library documentation: 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