Ever wanted to give dudes the ability to explore your library interactively? Like, with a custom IRB-like shell/console?
Really, you did? Weird.
(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
end
Let'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/pizza
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*
end
Or even like this:
prompt_with do |shell|
"pizza_bot / #{shell.size}" # the block gets the shell *instance* when it asks for it
end
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*
end
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
end
Let'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/pizza
It's there. Give it a whirl with TAB.
-
Create a class for your shell and
include Bombshell::Shell
. You should also set this class to inherit fromBombshell::Environment
as 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
--orundef
methods 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/pizza
at the top of this file.
- Give your users a
help
command! - 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 (c) 2011 Andy Rossmeissl. See LICENSE.txt for further details.