In previous posts, Boson was introduced as a command framework, usable from the commandline and irb. In this post, we’ll focus on how Boson enhances irb by treating methods like shell commands.

Preface

If you’re an avid irb user like me, your ~/.irbrc is littered with methods. These methods are sometimes favorite tweaks to Ruby’s core libraries and sometimes interfaces to gems. But regardless of what they represent, when you use them they’re just like shell commands. With this user in mind, Boson aims to give methods command-like functionality. This means giving a method the ability to have simple yet powerful options. This means aliasing and annotating methods are commonplace. This means searching and sharing your commands with others are easy to do. And if you want, you can even give a method instant views.

Here’s the order we’ll be covering using Boson with irb:

To use Boson in irb, drop this in your ~/.irbrc. For any other ruby console application the setup is the same:

require 'boson' Boson . start

Boson.start comes with some options. If you don’t like that Boson tells you every library it loads, set :verbose=>false .

When Boson starts, it loads libraries of commands. Boson libraries are just Ruby modules and Boson commands are just methods in those modules. Most libraries are in their own file under ~/.boson/commands. Each library can have an optional namespace defined in a config file. With this organization commands are independent of Boson. There are no namespace dsl methods as with Rake and no required Thor subclassing as with Thor. This is on purpose. Boson strives to allow all of its functionality to be written in plain ruby. This way you are free to easily test your commands and use them with other applications independent of Boson. Also, if you decided to stop using Boson, all libraries you’ve built for it can still be used in irb without modifying them. To do this you would simply extend your library’s modules into Ruby’s top-level object main .

Boson comes with a handful of core commands but let’s focus on three you’ll probably use the most: commands , libraries and load_library . Since these commands have options, you should familiarize yourself with the default global options they have. Also, you should be aware that option values can be aliased.

This command searches your commands in a myriad of ways.

# With no arguments commands() lists all loaded commands >> commands +------------------------+----------------------+-------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | full_name | lib | alias | usage | description | +------------------------+----------------------+-------+---------------------------------------------------------------------------+---------------------------------------------------------------------------+ | usage | core | | [name][--verbose] | Print a command's usage | | libraries | core | lib | [query=''][--index] [--query_fields=name] | List or search libraries | | render | core | v | [object] [options={}] | Render any object using Hirb | | load_library | core | ll | [library][--verbose] [--reload] | Load/reload a library | | commands | core | com | [query=''][--index] [--query_fields=full_name] | List or search commands | | menu | core | | [output] [options={}] [&block] | Provide a menu to multi-select elements from a given array | # ... # By default searches in a command's full_name >> commands 'usa' +-----------+------+-------+-------------------+-------------------------+ | full_name | lib | alias | usage | description | +-----------+------+-------+-------------------+-------------------------+ | usage | core | | [name][--verbose] | Print a command's usage | +-----------+------+-------+-------------------+-------------------------+ 1 row in set # Since searches are regular expressions, we can search multiple terms >> commands 'usa|lib' +--------------------+-------+-------+-------------------------------------------+---------------------------------------------+ | full_name | lib | alias | usage | description | +--------------------+-------+-------+-------------------------------------------+---------------------------------------------+ | usage | core | | [name][--verbose] | Print a command's usage | | libraries | core | lib | [query=''][--index] [--query_fields=name] | List or search libraries | | load_library | core | ll | [library][--verbose] [--reload] | Load/reload a library | +--------------------+-------+-------+-------------------------------------------+---------------------------------------------+ 3 rows in set => true # Before we can use a command's options we have to know what options it has >> commands '-h' # or commands '--help' commands [query=''][--index] => nil # another way of getting a command's usage >> usage 'commands' # To search full_name and description fields for 'lib' >> commands 'full_name,description:lib' +--------------------+----------+-------+-------------------------------------------------------------------------------+-----------------------------------------------------------------------------+ | full_name | lib | alias | usage | description | +--------------------+----------+-------+-------------------------------------------------------------------------------+-----------------------------------------------------------------------------+ | libraries | core | lib | [query=''][--index] [--query_fields=name] | List or search libraries | | load_library | core | ll | [library][--verbose] [--reload] | Load/reload a library | | install | web_core | | [url][--force] [--module_wrap] [--name=NAME] [--method_wrap] | Installs a library by url. Library should then be loaded with load_library. | +--------------------+----------+-------+-------------------------------------------------------------------------------+-----------------------------------------------------------------------------+ 3 rows in set => true # With option name and value aliasing, the above can also be >> commands 'lib -q=f,d'

Since commands() comes with Boson’s global rendering options, commands can be sorted by command attribute and have only certain attributes displayed.

# Before sorting and selecting command attributes, we need to know what attributes we can choose from in our options. # Let's call up verbose help: >> commands '-hv' # or commands '--help --verbose' commands [query=''][--index] COMMAND OPTIONS +----------------+-------+---------+-------------------+------------------------------------------------------------------------+ | Option | Alias | type | Description | Values | +----------------+-------+---------+-------------------+------------------------------------------------------------------------+ | --index | -i | boolean | Searches index | | +----------------+-------+---------+-------------------+------------------------------------------------------------------------+ GLOBAL/RENDER OPTIONS +----------------+-------+---------+-----------------------------------------------------------+------------------------------------------------------------------------+ | Option | Alias | type | Description | Values | +----------------+-------+---------+-----------------------------------------------------------+------------------------------------------------------------------------+ | --class | -c | string | Hirb helper class which renders | | | --fields | -f | array | Displays fields in the order given | name,lib,alias,description,options,args,usage,full_name,render_options | | --help | -h | boolean | Display a command's help | | | --max_width | -m | numeric | Max width of a table | | | --pretend | -p | boolean | Display what a command would execute without executing it | | | --render | -r | boolean | Toggle a command's default rendering behavior | | | --reverse_sort | -R | boolean | Reverse a given sort | | | --sort | -s | string | Sort by given field | name,lib,alias,description,options,args,usage,full_name,render_options | | --verbose | -v | boolean | Increase verbosity for help, errors, etc. | | | --vertical | -V | boolean | Display a vertical table | | +----------------+-------+---------+-----------------------------------------------------------+------------------------------------------------------------------------+ => nil # Looking under values column, we can see the available values for --sort and --fields i.e. # name,lib,alias,description,options,args,usage,full_name,render_options # To sort by library >> commands '--sort=lib' # or commands '-s=l' +--------------+----------+-------+--------------------------------------------------------------+-----------------------------------------------------------------------------+ | full_name | lib | alias | usage | description | +--------------+----------+-------+--------------------------------------------------------------+-----------------------------------------------------------------------------+ | usage | core | | [name][--verbose] | Print a command's usage | | libraries | core | lib | [query=''][--index] [--query_fields=name] | List or search libraries | | render | core | v | [object] [options={}] | Render any object using Hirb | | load_library | core | ll | [library][--verbose] [--reload] | Load/reload a library | | commands | core | com | [query=''][--index] [--query_fields=full_name] | List or search commands | | menu | core | | [output] [options={}] [&block] | Provide a menu to multi-select elements from a given array | | get | web_core | | [url] [options={}] | Gets the body of a url | # ... # To reverse sort >> commands '--sort=lib --reverse_sort' # or commands '-s=l -R' +--------------+----------+-------+--------------------------------------------------------------+-----------------------------------------------------------------------------+ | full_name | lib | alias | usage | description | +--------------+----------+-------+--------------------------------------------------------------+-----------------------------------------------------------------------------+ | browser | web_core | | [*urls] | Opens urls in a browser on a Mac | | install | web_core | | [url][--force] [--module_wrap] [--name=NAME] [--method_wrap] | Installs a library by url. Library should then be loaded with load_library. | | post | web_core | | [url] [options={}] | Posts to url | | get | web_core | | [url] [options={}] | Gets the body of a url | | menu | core | | [output] [options={}] [&block] | Provide a menu to multi-select elements from a given array | # ... # To only display command attributes full_name and description >> commands '--fields=full_name,description # or commands '-f=f,d' +--------------+-----------------------------------------------------------------------------+ | full_name | description | +--------------+-----------------------------------------------------------------------------+ | usage | Print a command's usage | | libraries | List or search libraries | | render | Render any object using Hirb | | load_library | Load/reload a library | | commands | List or search commands | | menu | Provide a menu to multi-select elements from a given array | | get | Gets the body of a url | | post | Posts to url | | install | Installs a library by url. Library should then be loaded with load_library. | | browser | Opens urls in a browser on a Mac | +--------------+-----------------------------------------------------------------------------+ 10 rows in set => true # Don't forget that rendering can be switched off to give us the actual command objects. Any of the above # examples could yield command objects if prefixed with --render. >> commands('--render usa|lib').size # or commands '-r usa|lib' => 3

Like commands, libraries can be searched, sorted and displayed with a number of library attributes:

# These examples are specific to my libraries. # To find out valid library attributes and option values >> libraries '-hv' # ... # Sort by name >> libraries '--sort=name' # or libraries '-s=n' +----------------------+----------+-----------------------+--------------+ | name | commands | gems | library_type | +----------------------+----------+-----------------------+--------------+ | alias | 6 | | file | | boson | 10 | | file | | boson_method_missing | 3 | | file | | completion | 3 | | file | # ... # See the module for each library >> libraries '--fields=name,module' # or libraries '-f=n,m' +----------------------+-------------------------------------+ | name | module | +----------------------+-------------------------------------+ | core | Boson::Commands::Core | | web_core | Boson::Commands::WebCore | | boson_method_missing | Boson::Commands::BosonMethodMissing | | completion | Boson::Commands::Completion | | alias | Boson::Commands::AliasLib | # ... # Search inside all libraries (that Boson has indexed) # for libraries of type gem >> libraries 'library_type:gem --index' # or libraries 'l:gem -i' +---------------+----------+--------------------------------------------+--------------+ | name | commands | gems | library_type | +---------------+----------+--------------------------------------------+--------------+ | httparty | 2 | httparty,crack | gem | | method_lister | 3 | matthew-method_lister | gem | | sketches | 5 | sketches | gem | | andand | 4 | andand | gem | | local_gem | 2 | local_gem | gem | | what_methods | 2 | what_methods | gem | | unroller | 1 | colored,unroller,quality_extensions,facets | gem | | restclient | 1 | adamwiggins-rest-client | gem | +---------------+----------+--------------------------------------------+--------------+ 8 rows in set => true

This command loads a library and its commands. This allows you to load the bare minimum functionality you need in irb and then load commands as needed.

# Verify the desired ri command doesn't exist yet >> ri 'method_mis' NameError: undefined local variable or method `ri' for main:Object from (irb):1 # Load my ri library and its commands >> load_library 'ri' Loaded library ri => true >> ri 'method_mis' +--------+------------------------------------+ | number | full_name | +--------+------------------------------------+ | 1 | DRb::DRbObject#method_missing | | 2 | Delegator#method_missing | | 3 | Kernel#method_missing | | 4 | REXML::Functions::method_missing | | 5 | REXML::QuickPath::method_missing | | 6 | SOAP::SOAPReference#method_missing | +--------+------------------------------------+ 6 rows in set Choose: # ...

To make your life even easier, you can put Boson in a mode where unloaded commands are automatically loaded once they’re executed. How does this work? Since commands are just methods on main , Boson redefines main ’s method_missing …

# To enable this you need to modify your ~/.irbrc: # Boson.start :autoload_libraries=>true # Assuming irb doesn't have the ri command again >> ri 'method_mes' Loaded library 'ri' +--------+------------------------------------+ | number | full_name | +--------+------------------------------------+ | 1 | DRb::DRbObject#method_missing | | 2 | Delegator#method_missing | | 3 | Kernel#method_missing | | 4 | REXML::Functions::method_missing | | 5 | REXML::QuickPath::method_missing | | 6 | SOAP::SOAPReference#method_missing | +--------+------------------------------------+ 6 rows in set Choose: # ...

Up to now, Boson libraries have been described as a module sitting in a file. While this type of library, a Boson::FileLibrary is the most common and functionally-rich, there are other library types.

Boson::GemLibrary makes commands from a gem. Let’s see this library in action by loading the sketches gem:

# Without any configuration, let's load sketches as a library >> load_library 'sketches' Loaded library sketches => true # Let's see what commands Boson picked up >> commands 'l:sket' # or commands 'lib:sket' +-------------+----------+-------+-------+-------------+ | full_name | lib | alias | usage | description | +-------------+----------+-------+-------+-------------+ | sketch_from | sketches | | | | | name_sketch | sketches | | | | | save_sketch | sketches | | | | | sketches | sketches | | | | | sketch | sketches | | | | +-------------+----------+-------+-------+-------------+ 5 rows in set # Begin sketching >> sketch # ...

Is sketches already built to work with Boson? Not quite. Rather, this demonstrates how Boson safeguards main by tracking what gems dump in Object and Kernel . Unfortunately this is all too common with irb-related gems. Now let’s use a gem that doesn’t dump methods on us:

# Create commands put and delete from HTTParty's API. # Values of :class_commands hash can point to any ruby code ending with a method call. >> load_library 'httparty', :class_commands=>{'put'=>'HTTParty.put', 'delete'=>'HTTParty.delete' } => true >> commands 'l:http' # or commands 'lib:http' +-----------+----------+-------+-------+-------------+ | full_name | lib | alias | usage | description | +-----------+----------+-------+-------+-------------+ | blank? | httparty | | | | | dclone | httparty | | | | | delete | httparty | | | | | put | httparty | | | | +-----------+----------+-------+-------+-------------+ 4 rows in set => true # Well, we spoke to soon. blank? and dclone are again monkeypatched methods. # If you'd rather have Boson not detect these methods for you: # load_library 'httparty', :object_methods=>false, :class_commands ... # To avoid typing out configurations for each gem you use, save them in the config file # ~/.boson/config/boson.yml under the :libraries key : # # :libraries: # httparty: # :class_commands: # put: HTTParty.put # delete: HTTParty.delete # Next irb session we can just load httparty and use our preconfigured commands bash> irb >> load_library 'httparty' => true >> put 'http://blah/blah' # ...

Boson::RequireLibrary makes its commands centered around any require-able library (i.e. standard libraries and rip packages):

# Let's make a command from the standard library abbrev >> load_library 'abbrev', :class_commands=>{'abbrev'=>'Abbrev.abbrev'}, :command_aliases=>{'abbrev'=>'ab'} => true # abbrev() shows a hash of all unique strings that abbreviate an array >> abbrev %w{awesome audacious asinine} => {"audacious"=>"audacious", "asini"=>"asinine", "aw"=>"awesome", "asi"=>"asinine", "awesome"=>"awesome", "awe"=>"awesome", "aweso"=>"awesome", "asin"=>"asinine", "aud"=>"audacious", "audaci"=>"audacious", "asinin"=>"asinine", "audacio"=>"audacious", "awesom"=>"awesome", "as"=>"asinine", "audac"=>"audacious", "audaciou"=>"audacious", "awes"=>"awesome", "asinine"=>"asinine", "au"=>"audacious", "auda"=>"audacious"} # Even though we haven't hooked up abbrev() to Boson/Hirb rendering # we can still use it with the default command render >> render ab(%w{awesome audacious asinine}).sort +-----------+-----------+ | 0 | 1 | +-----------+-----------+ | as | asinine | | asi | asinine | | asin | asinine | | asini | asinine | | asinin | asinine | | asinine | asinine | # ... # Let's make irb feel more like a shell with fileutils >> load_library 'fileutils', :class_commands=>%w{cd cp ln mv chmod}.inject({}) {|h,e| h[e] = "FileUtils.#{e}"; h} => true # Let's see what commands we have now >> commands 'l:fileu' # or commands 'lib:fileu' +-----------+-----------+-------+-------+-------------+ | full_name | lib | alias | usage | description | +-----------+-----------+-------+-------+-------------+ | chmod | fileutils | | | | | ln | fileutils | | | | | cd | fileutils | | | | | cp | fileutils | | | | | mv | fileutils | | | | +-----------+-----------+-------+-------+-------------+ 5 rows in set => true

Boson::ModuleLibrary simply makes commands from a module’s class methods:

>> load_library Math, :commands=>%w{sin cos tan} => true >> commands 'l:math' # or commands 'lib:math' +-----------+------+-------+-------+-------------+ | full_name | lib | alias | usage | description | +-----------+------+-------+-------+-------------+ | cos | math | | | | | sin | math | | | | | tan | math | | | | +-----------+------+-------+-------+-------------+ 3 rows in set # Let's brush up on ol trig >> sin (Math::PI/2) => 1.0 >> tan (Math::PI/4) => 1.0 # Close enough :) >> cos (Math::PI/2) => 6.12323399573677e-17

Before migrating your methods or irb commands, remember Boson’s philosophy. Whatever time you spend organizing your irbrc isn’t in vain. You can always take what you’ve made and use it without Boson.

The easiest way to migrate is to simply wrap all your irbrc methods in a module and explicitly pass that module to Boson.start :

# in ~/.irbrc module Easy def command1 end # ... end require 'boson' Boson . start :libraries =>[ Easy ]

The downside to this is that your commands don’t have options or views

If you want those features and still want the simplest thing possible, put all your methods in a module and drop that module into a file under ~/.boson/commands/.

If you’re serious about organizing your irbrc, you should break your methods up into libraries. If any of your methods are simply wrappers around a gem or a require-able library, consider making a gem library or a require library. Otherwise make each library by wrapping it in a module and placing it in under ~/.boson/commands/.

Next you should consider adding aliases and descriptions to your commands. Command aliasing is explained here. To add a command description, simply add a comment directly above it. If your description spans multiple lines than use the desc method attribute:

#@desc My description spans multiple lines # because I have a lot to say and # not enough space to say it def talk # ... end

Lastly, you should decide which libraries to load at startup. The default is to load all libraries. You’ll probably want to change that once your libraries load multiple gems. To specify these libraries, open up Boson’s main config file at ~/.boson/config/boson.yml. Add a :console_defaults key and give it an array of libraries. See my config file as an example.

Postface

In this post, we’ve seen how Boson treats methods like shell commands. In doing so, it makes irb (or any ruby console) a simple yet powerful method-based shell. For more traditional ruby shell implementations, see rubish, rush and pope. If you’re interested in giving autocompletion to your commands, check out Bond.