Quick and dirty libreadline wrapper in Ruby

I have a program (vanilla minecraft server) running in console mode which reads commands from stdin. Unfortunately it doesn’t come with all the nice things you usually want in a shell-like environment : mainly history and tab-completion.

That’s why I was looking for a way to wrap it in some script that would harness the power of libreadline for my own comfort.

I wanted to have something very simple and customizable. This way I can add my own commands and whatnots. If I got to get my hands in it, it better be in Ruby, because that’s the only language I’m efficient in (as in ‘I can write stuff without having to read documentation every two symbols’). This is important because there is already something in C to do that there. But C requires too much thinking. I want to be able to hit the keyboard with my forehead repeatedly and expect the resulting code to work.

Fortunately this time, Ruby comes with its own libreadline bindings. This website even displays a nice tutorial, but I couldn’t find any simple example on the Web showing me how to use it to wrap another process, and especially display its output without messing up Readline stuff.

This is why I write this post, because I couldn’t understand why no one else before had the same problem as I do, and shared it to the world. I got mad, ranted, ran around waving my arms yelling in despair: “FUCK! I’m gonna have to do some work myself”. Life is so fucking hard.

So here comes the dirty code, half stolen from Joseph Pecoraro’s tutorial :

    # Here goes the list of words
    # you want to be able to use completion on
    $LIST = [ 
        'help', 'kick', 'ban', 
        'pardon', 'ban-ip', 'pardon-ip',
        'op', 'deop', 'tp', 'give',
        'tell', 'stop', 'save-all',
        'save-off', 'save-on', 'list',
        'say', 'time'
    ].sort

    $PROMPT="> " # common prompt

    comp = proc{ |s| $LIST.grep( /^#{Regexp.escape(s)}/ ) }

    Readline.completion_append_character = " "
    Readline.completion_proc = comp

    $stdout.sync=true

    # $COMMAND will be the command-line program
    # to wrap and its args
    $io=IO.popen($COMMAND,"w+") 

    # Reading process
    Thread.new() {
        while true
            sleep 0.1 

    # every time the wrapped
            # process wants to talk 

    while a=$io.gets()

                # save what the user was writing
                buf = Readline.line_buffer 

                # clear the current prompt and stuff
                $stdout.print "\b \b"*(buf.size+$PROMPT.size) 

                # display the wrapped process output
                print a 

                # restore the prompt
                print ("#{$PROMPT}#{buf}") 

            end
        end
    }

    loop do
            # Let's summon some readline magic
            line = Readline::readline($PROMPT)  
            case line
            when /^\s*$/
                # We don't store empty lines in the History,
                # but we still want to send them 
                # to the wrapped process. 
                # Maybe that's something you don't want to do
                $io.puts(line)
            else
                Readline::HISTORY.push(line)
                $io.puts(line)
            end
    end

This code won’t work with Ruby < 1.9, because Readline.line_buffer doesn’t exist there. For 1.8.x you can use Luis Lavena’s full ruby implementation of readline here.

Also it hasn’t been fully tested, it may not work at all, break everything on your system, dig some obsidian stone and suck you into the Nether or whatever. I won’t be held responsible.

Hateful comments about code quality are welcome.