From: Joel VanderWerf Date: 2007-03-18T01:42:42+09:00 Subject: Re: IRB as test interface to existing object 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 > 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") > => # > irb(main):013:0> irb obj > irb#1(#):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 -- vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407