How To Write A RubyGem Command Plugin
The latest rubygems has a hot new feature, the ability to plugin in your own commands. In this post, I’ll walk you through how to write a gem command plugin, using my own gem grep command as an example. Then I’ll introduce gem grep and show some examples of how it enhances the standard gem search.
If you haven’t already, update to at least rubygems version 1.3.2.
bash> gem update --system
Here’s the basic overview of creating a gem command plugin:
Step 1: Create rubygems_plugin.rb
In a new or existing gem directory create the file lib/rubygems_plugin.rb. For my grep command, I dabbled enough into rubygem internals that I didn’t want to package it with my existing gem. But in other cases, like zenspider’s graph command, distributing it with your gem makes sense.
Step 2: Register Your Command
In rubygems_plugin.rb you need to register your gem command. You also need to require Gem::CommandManager
.
Here’s what my rubygems_plugin.rb looks like:
require 'rubygems/command_manager'
Gem::CommandManager.instance.register_command :grep
Step 3: Create Your Command File
In order for rubygems to automatically require your command, place your command file in lib/rubygems/commands/ and have your camel case class map to an underscored file. For my command class Gem::Commands::GrepCommand
, I created the file lib/rubygems/commands/grep_command.rb. One side note, if for whatever reason you can’t or don’t want to create a separate file you can just drop your command code in rubygems_plugin.rb.
Step 4: Customize Your Command
This is the meat and potatoes of a command. In my examples, I’ll be referring to my grep command. Let’s look at the different methods you can define to customize your command: new, execute, arguments, defaults_str, description and usage.
new()
In this method you should define your command’s name, summary and options. Here’s my command’s new() along with its class definition and dependencies:
require 'rubygems/commands/query_command'
require 'rubygems/super_search'
require 'hirb'
class Gem::Commands::GrepCommand < Gem::Commands::QueryCommand
def initialize
super 'grep', "Enhances search command by providing extra search options and displaying results as a table"
defaults.merge!(:columns=>[:name,:summary,:author])
add_option('-c', '--columns STRING', 'Gemspec columns/attributes to display per gem') do |value, options|
options[:columns] = value.split(/\s*,\s*/).map {|e|
self.class.valid_gemspec_columns.detect {|c| c =~ /^#{e}/ }
}.compact.map {|e| e.to_sym}
end
add_option('-f', '--fields STRING', 'Gemspec fields/attributes to search (only for local gems)') do |value, options|
options[:fields] = value.split(/\s*,\s*/).map {|e|
self.class.valid_gemspec_columns.detect {|c| c =~ /^#{e}/ }
}.compact
end
remove_option '--name-matches'
remove_option '-d'
remove_option '--versions'
end
Some things to point out:
- Normally when creating a command from scratch, you subclass from
Gem::Command
. I subclassed fromGem::Commands::QueryCommand
since I’m extending the query command’s functionality. - The
super()
call is needed in order to define your command’s name, summary and default options. In my case, my command’s superclass didn’t take an argument for default options. I was able to modify default options by modifying thedefaults
accessor directly. add_option
andremove_option
do what their names indicate. They use OptionParser to define options.
execute()
Whatever business your command has should be done here. Here, as in all of your command’s methods, you have access to your options through options
. My command’s execute()
isn’t much since it delegates it’s business elsewhere.
def execute
string = get_one_optional_argument
options[:name] = /#{string}/i
Gem.source_index.extend Gem::SuperSearch
super
end
arguments(), defaults_str(), description() and usage()
These are all optional methods that describe different sections of your commands help page. Here’s a brief description of each:
arguments()
: Describes your command’s arguments one per line, left justified.defaults_str()
: Describe your command’s default options.description
: Gives a detailed description of your command with any necessary instructions.usage()
: Describes commands and their order in one line.
My two cents:
def arguments # :nodoc:
"REGEXP regular expression string to search specified gemspec attributes"
end
def usage # :nodoc:
"#{program_name} [REGEXP]"
end
def defaults_str # :nodoc:
"--local --columns name,summary,author --fields name --no-installed"
end
def description # :nodoc:
'Enhances search command by providing options to search (--fields) and display (--columns) ' +
'gemspec attributes. Results are displayed in an ascii table. Gemspec attributes can be specified '+
'by the first unique string that it starts with i.e. "su" for "summary". To specify multiple gemspec attributes, delimit ' +
"them with commas. Gemspec attributes available to options are: #{self.class.valid_gemspec_columns.join(', ')}."
end
Introducing Gem Grep
Now that you’ve seen how a gem command plugin is built, let’s look at the my gem grep
command. It enhances the gem search command in three ways:
- It displays results in a well formatted ascii table. This format allows for detail while still maintaining one gem per line.
- It allows you to specify an aliased list of any gemspec attributes you’d like to see for the results using the columns option.
- For local gems, it can search any number of gemspec attributes passed as an aliased list to the fields option. Note that any attributes includes attributes that are arrays so you can even search your gems by files and/or dependencies.
Gem Grep Examples
Time to see what gem grep does! First, it formats your search results in a table using hirb.
bash> gem grep irb *** LOCAL GEMS *** +----------------+-------------------------------------------------------------------------------------------+----------------+ | name | summary | author | +----------------+-------------------------------------------------------------------------------------------+----------------+ | cldwalker-hirb | A mini view framework for irb that's easy to use, even while under its influence. | Gabriel Horner | | irb_callbacks | Adds three callbacks to the prompt, eval, and output phases of irb | Mike Judge | | irb_rocket | irb plugin that makes irb #=> rocket | Genki Takiuchi | | sirb | Descriptive statistics + IRB + any other useful numerical library you may have around | David Richards | | wirble | Handful of common Irb features, made easy. | Paul Duncan | +----------------+-------------------------------------------------------------------------------------------+----------------+ 5 rows in set
If you want to choose what gemspec attributes (columns) you see in your results, no problem. Pass a comma delimited list to -c
in the order you want to see them. For any gemspec attribute, you can pass its alias which is the first unique string that it starts with.
# n,ho,da are shortcuts for name,homepage,date bash> gem grep -c n,ho,da irb *** LOCAL GEMS *** +----------------+-------------------------------------------------+--------------------------------+ | name | homepage | date | +----------------+-------------------------------------------------+--------------------------------+ | cldwalker-hirb | http://github.com/cldwalker/hirb | Thu Mar 12 00:00:00 -0400 2009 | | irb_callbacks | http://rubysideshow.rubyforge.org/irb_callbacks | Tue May 13 00:00:00 -0400 2008 | | irb_rocket | http://blog.s21g.com/genki | Sat Feb 07 00:00:00 -0500 2009 | | sirb | http://github.com/davidrichards/sirb | Sun Mar 22 00:00:00 -0400 2009 | | wirble | http://pablotron.org/software/wirble/ | Fri Sep 08 00:00:00 -0400 2006 | +----------------+-------------------------------------------------+--------------------------------+ 5 rows in set
Choosing your columns works for remote gems as well.
bash> gem grep console -r -c n,ho,des *** REMOTE GEMS *** +---------------------------+---------------------------------------------+--------------------------------------------------------------------------------------------------------------------------+ | name | homepage | description | +---------------------------+---------------------------------------------+--------------------------------------------------------------------------------------------------------------------------+ | CapConsole | http://handle.rubyforge.org | This adds a new capistrano task called console:shell which, when run, opens a script/console shell on your remote pro... | | cldwalker-console_update | http://github.com/cldwalker/console_update | A rails plugin which allows you to edit your database records via the console and your favorite editor. | | Console | http://www.nebulargauntlet.org | | | jtrupiano-timecop-console | http://github.com/jtrupiano/timecop-console | | | live_console | http://debu.gs/live-console | | | mongrel_console | | Provides a combined Mongrel and Rails IRB console. | | simpleconsole | http://simpleconsole.rubyforge.org | | | takai-twitty-console | http://github.com/takai/twitty-console/ | TwittyConsole is a console based client for Twitter. | | timecop-console | http://github.com/jtrupiano/timecop-console | | | win32console | http://rubyforge.org/projects/winconsole | This gem packages Gonzalo Garramuno's Win32::Console project, and includes a compiled binary for speed. The Win32::Co... | +---------------------------+---------------------------------------------+--------------------------------------------------------------------------------------------------------------------------+ 10 rows in set
For local gems only, you get increased searching ability with -f
. Pass it a comma delimited list of gemspec attributes you want to search on, aliased as shown above. Let’s see my gems that were created/authored by jamis:
bash> gem grep jamis -f a *** LOCAL GEMS *** +-----------------+------------------------------------------------------------------------------------------------------------------------------------------+------------+ | name | summary | authors | +-----------------+------------------------------------------------------------------------------------------------------------------------------------------+------------+ | capistrano | Capistrano is a utility and framework for executing commands in parallel on multiple remote machines, via SSH. | Jamis Buck | | needle | Needle is a Dependency Injection/Inversion of Control container for Ruby. It supports both type-2 (setter) and type-3 (constructor) i... | Jamis Buck | | net-scp | A pure Ruby implementation of the SCP client protocol | Jamis Buck | | net-sftp | Net::SFTP is a pure-Ruby implementation of the SFTP client protocol. | Jamis Buck | | net-ssh | Net::SSH is a pure-Ruby implementation of the SSH2 client protocol. | Jamis Buck | | net-ssh-gateway | A simple library to assist in establishing tunneled Net::SSH connections | Jamis Buck | | sqlite3-ruby | SQLite3/Ruby is a module to allow Ruby scripts to interface with a SQLite3 database. | Jamis Buck | | syntax | Syntax is Ruby library for performing simple syntax highlighting. | Jamis Buck | +-----------------+------------------------------------------------------------------------------------------------------------------------------------------+------------+ 8 rows in set
As already mentioned, you can search multiple attributes. Let’s search for irb inside gemspec attributes name, description and summary:
bash> gem grep irb -f n,su,des *** LOCAL GEMS *** +-----------------------+----------------------------------------------------------------------------------------------------------+------------------------------+ | name | summary | authors | +-----------------------+----------------------------------------------------------------------------------------------------------+------------------------------+ | cldwalker-hirb | A mini view framework for irb that's easy to use, even while under its influence. | Gabriel Horner | | irb_callbacks | Adds three callbacks to the prompt, eval, and output phases of irb | Mike Judge | | irb_rocket | irb plugin that makes irb #=> rocket | Genki Takiuchi | | matthew-method_lister | Pretty method listers and finders, for use in IRB. | Matthew O'Connor | | rtags | rtags is a Ruby replacement for ctags - allowing for name navigation in source code using vim, emacs ... | Pjotr Prins, Keiju Ishitsuka | | sirb | Descriptive statistics + IRB + any other useful numerical library you may have around | David Richards | | utility_belt | A grab-bag of IRB power user madness. | Giles Bowkett | | wirble | Handful of common Irb features, made easy. | Paul Duncan | +-----------------------+----------------------------------------------------------------------------------------------------------+------------------------------+
Like the default search and query commands, grep converts your search term to a regular expression. Let’s use this to find out my gems that were created from 2000-2005. Hopefully not too many.
bash> gem grep 200[0-5] -f da -c n,da,su *** LOCAL GEMS *** +-------------+--------------------------------+-----------------------------------------------------------------------------------------------------------------------------------+ | name | date | summary | +-------------+--------------------------------+-----------------------------------------------------------------------------------------------------------------------------------+ | dnssd | Thu Oct 07 00:00:00 -0400 2004 | DNS Service Discovery (aka Rendezvous) API for Ruby | | extensions | Thu Dec 09 00:00:00 -0500 2004 | 'extensions' is a set of extensions to Ruby's built-in classes. It gathers common idioms, useful additions, and aliases, comp... | | mysql | Sun Oct 09 00:00:00 -0400 2005 | MySQL/Ruby provides the same functions for Ruby programs that the MySQL C API provides for C programs. | | needle | Fri Dec 23 00:00:00 -0500 2005 | Needle is a Dependency Injection/Inversion of Control container for Ruby. It supports both type-2 (setter) and type-3 (constru... | | Platform | Thu Dec 01 00:00:00 -0500 2005 | Hopefully robust platform sensing | | RedCloth | Thu Sep 15 00:00:00 -0400 2005 | RedCloth is a module for using Textile and Markdown in Ruby. Textile and Markdown are text formats. A very simple text format... | | stemmer | Wed Apr 20 00:00:00 -0400 2005 | Word stemming algorithm(s) | | syntax | Sat Jun 18 00:00:00 -0400 2005 | Syntax is Ruby library for performing simple syntax highlighting. | | termios | Fri Sep 24 00:00:00 -0400 2004 | Termios module are simple wrapper for termios(3). It can be included into IO-family classes and can extend IO-family objects. ... | | text-format | Fri Jun 24 00:00:00 -0400 2005 | Text::Format formats fixed-width text nicely. | | text-hyphen | Mon Dec 20 00:00:00 -0500 2004 | Multilingual word hyphenation according to modified TeX hyphenation pattern files. | | tree | Thu Dec 29 00:00:00 -0500 2005 | Ruby implementation of the Tree data structure. | +-------------+--------------------------------+-----------------------------------------------------------------------------------------------------------------------------------+ 12 rows in set
gem grep can also search inside gemspec attributes that are arrays i.e. dependencies
. Let’s see my gems that depend on rake:
bash> gem grep rake -f dep -c n,dep *** LOCAL GEMS *** +---------------+----------------------------------------------------------------------------------------------------------------------------------------------+ | name | dependencies | +---------------+----------------------------------------------------------------------------------------------------------------------------------------------+ | bones | rake (>= 0.8.3, runtime) | | echoe | rake (>= 0, runtime),rubyforge (>= 1.0.0, runtime),highline (>= 0, runtime) | | ferret | rake (> 0.0.0, runtime) | | hoe | rubyforge (>= 1.0.1, runtime),rake (>= 0.8.3, runtime) | | launchy | rake (>= 0.8.1, runtime),configuration (>= 0.0.5, runtime) | | mislav-hanna | rdoc (~> 2.2.0, runtime),haml (~> 2.0.4, runtime),rake (~> 0.8.2, runtime),echoe (>= 0, runtime) | | mocha | rake (>= 0, runtime) | | rails | rake (>= 0.7.2, runtime),activesupport (= 1.4.4, runtime),activerecord (= 1.15.6, runtime),actionpack (= 1.13.6, runtime),actionmailer (=... | | red | ParseTree (~> 2.2.0, runtime),rake (~> 0.8.3, runtime),newgem (>= 1.0.6, development),hoe (>= 1.8.0, development) | | www-delicious | rake (>= 0.8, development),echoe (>= 3.1, development),mocha (>= 0.9, development) | +---------------+----------------------------------------------------------------------------------------------------------------------------------------------+ 10 rows in set
If you think gem grep may be handy for you, install it with:
gem source -a http://gems.github.com && sudo gem install cldwalker-gem_grep
Further Reading
- Rubygems documentation for command plugins is here.
- If you aren’t familiar with gemspec attributes, read up on their docs.
- zenspider has a graph command which creates a dependency graph of your rubygems.
- Eric Hodel has a rubypan command which does a fulltext search of gems.