Improving IRB history

in irb

I’ve started to consider irb configurations as a way to improve my productivity. Following this path I’ve got some nice stuff in my irbrc. In the last days I was thinking about how to make irb history more similar to the bash one. So, I thought there was a need for the following features:

History command
History execute
History grep

Also, ignoring duplicated lines on irb exit would be great.

The methods

That’s why the best thing I can do now is to show you the code I wrote to achieve the above mentioned features:

def history_a(n=Readline::HISTORY.size)
    size=Readline::HISTORY.size
    Readline::HISTORY.to_a[(size - n)..size-1]
end

def decorate_h(n)
    size=Readline::HISTORY.size
    ((size - n)..size-1).zip(history_a(n)).map {|e| e.join(" ")}
end

def h(n=10)
    entries = decorate_h(n)
    puts entries
    entries.size
end

def hgrep(word)
    matched=decorate_h(Readline::HISTORY.size - 1).select {|h| h.match(word)}
    puts matched
    matched.size
end

def h!(start, stop=nil)
    stop=start unless stop
    code = history_a[start..stop]
    code.each_with_index { |e,i|
        irb_context.evaluate(e,i)
    }
    Readline::HISTORY.pop
    code.each { |l|
        Readline::HISTORY.push l
    }
    puts code
end

Let me confess I don’t like a lot the naming of some methods :) By the way, these methods make the following output possibile:

ruby-1.9.2-p0 > h
199 h
200 h
201 hgrep "json"
202 h
203 h
204 h "toy"
205 h
206 hgrep "toy"
207 h! 197
208 h
 => 10
ruby-1.9.2-p0 > hgrep "toy"
89 a=Arra.toy
97 a=Array.toy
189 a=Arra.toy
197 a=Array.toy
204 h "toy"
206 hgrep "toy"
209 hgrep "toy"
 => 7
ruby-1.9.2-p0 > h! 197
a=Array.toy
 => nil
ruby-1.9.2-p0 > a=Array.toy
 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

I do know there are many posts around the web on this subject but I don’t like some aspects of the solutions I came across. I wanted a bash like history and none of the solutions I found has a real working re-execute command. I found some solutions but all of them use eval to execute code and don’t replace the re-executed command in the history. Actually the h! method I wrote uses irb_context to evaluate the input lines. The issue with the eval version is easy to explain with an example:

ruby-1.9.2-p0 > eval("a=Array.toy")
 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ruby-1.9.2-p0 > a
NameError: undefined local variable or method `a' for main:Object
    from (irb):2
    from /home/lucapette/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'
ruby-1.9.2-p0 > a=Array.toy
 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ruby-1.9.2-p0 > a="l"
 => "l"
ruby-1.9.2-p0 > h 5
200 eval("a=Array.toy")
201 a
202 a=Array.toy
203 a="l"
204 h 5
 => 5
ruby-1.9.2-p0 > h! 202
a=Array.toy
 => nil
ruby-1.9.2-p0 > a
 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Not a very big deal but I really prefer my implementation of the re-execute command. However, I can’t really explain the difference between the two implementations. I think it could be a scope problem, please tell me if I’m wrong.

Erasing duplicates

The last feature I wanted to have in my irb history is an equivalent to bash:

export HISTCONTROL=erasedups

this of course means erasing your duplicates command lines. So I came up with the following:

# don't save duplicates
IRB.conf[:AT_EXIT].unshift Proc.new {
    no_dups = []
    Readline::HISTORY.each_with_index { |e,i|
        begin
            no_dups << e if Readline::HISTORY[i] != Readline::HISTORY[i+1]
        rescue IndexError
        end
    }
    Readline::HISTORY.clear
    no_dups.each { |e|
        Readline::HISTORY.push e
    }
}

I confess I don’t like a lot the final implementation of the algorithm I wrote. But it does his job: IRB.conf[:AT_EXIT] is an array of proc that irb would call when you leave it. Thus, I simply added a proc that willy rewrite your irb history with uniq lines. Please tell me if you can do the same using a more elegant solution.

yaih

Well, the history commands we talked about could be a nice improvement in your irbrc too. I published a gem to share these methods with you. Install it with:

gem install yaih

and then require it in your irbrc. I published yaih just a couple of days ago and it has some lacks. I would like to write some tests for it but I need some suggestion because I don’t know how I can write effective tests in this kind of situation. Then I’d like to add some options, especially if someone will kindly suggest me some :)

Fork me on GitHub