bombshell
Ever wanted to give dudes the ability to explore your library interactively? Like, with a custom IRB-like shell/console?
Really, you did? Weird.
Simple example
(The source code for this example is in doc/pizza.)
pizza/bin/pizza:
#!/usr/bin/env ruby
$:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
require 'rubygems'
require 'pizza'
Bombshell.launch(Pizza::Shell)pizza/lib/pizza/shell.rb:
require 'bombshell'
module Pizza
 class Shell < Bombshell::Environment
   include Bombshell::Shell
   
   prompt_with 'pizzabot'
   
   def order(size)
     Pizza::Order.new(:size => size).place!
     puts 'Your pizza has been ordered! Super!'
   end
 end
endLet's try it out:
$ pizza
pizzabot> order 'large'
Your pizza has been ordered! Super!
pizzabot>
If you have Bombshell's source checked out, you can try this at home:
$ cd doc/pizza
$ ./bin/pizzaPrompts
You set your prompt like this:
    prompt_with 'pizza_bot_loves_you'Or like this:
    prompt_with do
     "pizza_bot / #{Time.now}" # binding is on your shell *class*
    endOr even like this:
    prompt_with do |shell|
     "pizza_bot / #{shell.size}" # the block gets the shell *instance* when it asks for it
    endCallbacks
You can set callbacks like this:
    before_launch do
     init # binding is on your shell *class*
    end
    before_launch do |size|
     Pizza.default_size = size # the block gets as many command-line parameters as you ask for
    end
    having_launched do
     puts size if size # binding is on your shell *instance*
    endSubshells
If you dump all of your functionality into one shell, things could get a little messy. That's why we have subshells.
(The source code for this example is in doc/pizza2.)
pizza/lib/pizza/shell.rb:
require 'bombshell'
module Pizza
 class Shell < Bombshell::Environment
   include Bombshell::Shell
   prompt_with 'pizzabot'
   
   def pizza
     Order.launch
   end
 end
end
require 'pizza/shell/order'pizza/lib/pizza/shell/order.rb:
module Pizza
 class Shell
   class Order < Bombshell::Environment
     include Bombshell::Shell
     prompt_with 'new order'
   
     def size(s)
       @size = s
       puts 'You got it!'
     end
     
     def topping(t)
       @toppings ||= []
       @toppings << t
       puts "Added #{t}"
     end
     
     def order
       Pizza::Order.new :size => @size, :toppings => @toppings
       puts 'Coming right up!'
       quit
     end
   end
 end
endLet's try it out:
pizzabot> pizza
new order> size 'large'
You got it!
new order> topping 'pepperoni'
Added pepperoni
new order> order
Coming right up!
pizzabot>
If you have Bombshell's source checked out, you can try this at home:
$ cd doc/pizza2
$ ./bin/pizzaTab completion
It's there. Give it a whirl with TAB.
To use:
- 
Create a class for your shell and include Bombshell::Shell. You should also set this class to inherit fromBombshell::Environmentas that will ensure your shell doesn't have any extraneous "commands" (i.e. methods) inherited from Object. (If you'd rather use a different basis--likeCleanSlate--orundefmethods yourself, go right ahead.)
- 
Define your commands as instance methods on this class. There's nothing too funny going on here, it's just Ruby. 
- 
Kick off the shell with Bombshell.launch(YourShellClass). It's possible to do this from IRB but it's kind of messy (constant reassignment warnings). Instead, set up a "binary" for yourself likepizza/bin/pizzaat the top of this file.
Hints:
- Give your users a helpcommand!
- Use subshells for hierarchical interactivity!
- Provide as thin of a wrapper you can above your library! We want to see what's going on!
Copyright
Copyright (c) 2011 Andy Rossmeissl. See LICENSE.txt for further details.