IRB as test interface to existing object

Hello,

I have some object classes to which I want to add an interactive
command-line shell for testing. At the moment, at the end of the source file
I have something like

if __FILE__ == $0
  obj = MyClass.new(*ARGV)
  CLI.run(obj, "MyClass> ")
end

Now, I had started writing a CLI module which reads lines from stdin, splits
them into an array, runs obj.send(*args), and prints the response. Then it
occurred to me that this is what IRB is for. But I don't know how to start
IRB in such a way that the default "self" receiver is an object which I
created.

Here's an example of what I'd like to end up with:

class Foo
  attr_reader :addr
  def initialize(addr)
    @addr = addr
  end

  def do_stuff(n)
    n.times { puts "doing stuff with #{@addr}" }
  end
end

if __FILE__ == $0
  obj = Foo.new(*ARGV)
  prompt = "Foo(#{obj.addr})> "
  IRB.run(obj, prompt) # <<<--- what do I put here?
end

$ ruby foo.rb 127.0.0.1
Foo(127.0.0.1)> do_stuff 3
doing stuff with 127.0.0.1
doing stuff with 127.0.0.1
doing stuff with 127.0.0.1
nil
Foo(127.0.0.1)>

Reading the irb manpage, I can see that you can use the 'irb' command
(interactively) to set the default receiver in a subshell:

irb(main):012:0> obj = Foo.new("127.0.0.1")
=> #<Foo:0xb7c46730 @addr="127.0.0.1">
irb(main):013:0> irb obj
irb#1(#<Foo:0xb7c46730>):001:0> do_stuff 3
doing stuff with 127.0.0.1
doing stuff with 127.0.0.1
doing stuff with 127.0.0.1
=> 3

But I can't work out how to invoke an initial IRB instance with my own
'main' object from the start. I've tried going through the source, but I get
lost in Contexts and Workspaces well before I get to subirb.rb :slight_smile: I'd also
prefer to implement it in a way which uses a 'standard' interface to Irb,
such that it won't break with a future version of Ruby.

Any clues gratefully received.

Thanks,

Brian.

Brian Candler wrote:

Hello,

I have some object classes to which I want to add an interactive
command-line shell for testing. At the moment, at the end of the source file
I have something like

if __FILE__ == $0
  obj = MyClass.new(*ARGV)
  CLI.run(obj, "MyClass> ")
end

Now, I had started writing a CLI module which reads lines from stdin, splits
them into an array, runs obj.send(*args), and prints the response. Then it
occurred to me that this is what IRB is for. But I don't know how to start
IRB in such a way that the default "self" receiver is an object which I
created.

Here's an example of what I'd like to end up with:

class Foo
  attr_reader :addr
  def initialize(addr)
    @addr = addr
  end

  def do_stuff(n)
    n.times { puts "doing stuff with #{@addr}" }
  end
end

if __FILE__ == $0
  obj = Foo.new(*ARGV)
  prompt = "Foo(#{obj.addr})> "
  IRB.run(obj, prompt) # <<<--- what do I put here?
end

$ ruby foo.rb 127.0.0.1
Foo(127.0.0.1)> do_stuff 3
doing stuff with 127.0.0.1
nil
Foo(127.0.0.1)>

Reading the irb manpage, I can see that you can use the 'irb' command
(interactively) to set the default receiver in a subshell:

irb(main):012:0> obj = Foo.new("127.0.0.1")
=> #<Foo:0xb7c46730 @addr="127.0.0.1">
irb(main):013:0> irb obj
irb#1(#<Foo:0xb7c46730>):001:0> do_stuff 3
doing stuff with 127.0.0.1
=> 3

But I can't work out how to invoke an initial IRB instance with my own
'main' object from the start. I've tried going through the source, but I get
lost in Contexts and Workspaces well before I get to subirb.rb :slight_smile: I'd also
prefer to implement it in a way which uses a 'standard' interface to Irb,
such that it won't break with a future version of Ruby.

Any clues gratefully received.

Thanks,

Brian.

The following drops into (or _back_ into) an irb session. (Customizing the prompt is another matter, and I don't remember how to do that, but it might be findable on ruby-talk.)

(I use typically this in a long running process. I set my INT handler and top-level exception handlers to start_session on some main object. So ^C drops into irb, and ^D jumps back out.)

#!/usr/bin/env ruby

require 'irb'
require 'irb/completion'

module IRB
   def IRB.parse_opts
     # Don't touch ARGV, which belongs to the app which called this module.
   end

   def IRB.start_session(*args)
     unless $irb
       IRB.setup nil
       ## maybe set some opts here, as in parse_opts in irb/init.rb?
     end

     workspace = WorkSpace.new(*args)

     if @CONF[:SCRIPT] ## normally, set by parse_opts
       $irb = Irb.new(workspace, @CONF[:SCRIPT])
     else
       $irb = Irb.new(workspace)
     end

     @CONF[:IRB_RC].call($irb.context) if @CONF[:IRB_RC]
     @CONF[:MAIN_CONTEXT] = $irb.context

     trap 'INT' do
       $irb.signal_handle
     end

     custom_configuration if defined?(IRB.custom_configuration)

     catch :IRB_EXIT do
       $irb.eval_input
     end

     ## might want to reset your app's interrupt handler here
   end
end

class Object
   include IRB::ExtendCommandBundle # so that Marshal.dump works
end

if __FILE__ == $0
   x = Object.new
   puts "\nStarted irb shell for x"
   IRB.start_session(x)
   puts "\nStarted irb shell for x with current binding"
   IRB.start_session(binding, x)
   puts "\nRestarted irb shell for x with current binding"
   $irb.eval_input
   puts "\nExited irb shell"
   p x
end

···

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

The following drops into (or _back_ into) an irb session.

This works perfectly, thank you (great to get command history!)

It also looks suspiciously like IRB.start in /usr/lib/ruby/1.8/irb.rb -
shame that doesn't take a second optional parameter for the workspace.

(Customizing
the prompt is another matter, and I don't remember how to do that, but
it might be findable on ruby-talk.)

Just defining a to_s method on the object works well enough for me. I have
also found more details about IRB.conf in "man irb" (actually "man irb1.8"
on this Ubuntu box)

Thanks again,

Brian.

···

On Sun, Mar 18, 2007 at 01:42:42AM +0900, Joel VanderWerf wrote: