This blog is also available on Gemini (What's Gemini?)

Perl Modules: AnyEvent::ReadLine::Gnu


REPLs (Read-Eval–Print Loop) can be handy little things. In UAV::Pilot[1], the `uav` shell takes arbitrary Perl expressions and `eval()`'s them.

Before integrating with AnyEvent[2], handling the prompt was done by Term::ReadLine::Gnu[3]. When AnyEvent was integrated, I wanted the shell to use AnyEvent's non-blocking I/O, so it was migrated to AnyEvent::ReadLine::Gnu[4].

This also handles command history. Hit the 'Up' arrow to get your previous command. No code is necessary; AnyEvent::ReadLine::Gnu does it for you.

ReadLine also has options for tab-completion. I would like to add this to the `uav` shell eventually.

Using the AnyEvent version is quite simple. You pass a callback that takes input. In the `uav` callback, we only run the code when it ends with a semicolon (ignoring trailing whitespace). If it doesn't, we save it in a buffer and wait for more input.

Here's how this is implemented in UAV::Pilot:

my $readline; $readline = AnyEvent::ReadLine::Gnu->new(
    prompt => 'uav> ',
    on\_line => sub {
        my ($line) = @\_;
        add\_cmd( $line );
        if( $line =~ /; \\s\* \\z/x ) {
            my $cmd = full\_cmd;
            my $do\_continue = run\_cmd( $cmd, $repl );

$cv->send( $do\_continue ) unless $do\_continue; } }, );

The `add_cmd()` method adds the input to the buffer. If that did end with a semicolon, then we call `full_cmd()` to get back the text of the code. It also clears the buffer. `run_cmd()` then `eval()`'s the code. If we're meant to exit the program, it returns false, which we handle with the `$cv->send`.

The `$readline->hide` and `$readline->show` calls stop ReadLine from outputting the prompt when we might have other output going.