IRB as test interface to existing object

B

Brian Candler

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 :) 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.
 
J

Joel VanderWerf

Brian said:
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 :) 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
 
B

Brian Candler

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,710
Latest member
bernietqt

Latest Threads

Top