[Rubygems-developers] CVS unlock...but with caveats (LONG)

Richard Kilmer rich at infoether.com
Tue Jun 15 16:52:04 EDT 2004


All,

OK...here are the results of my longish lock on CVS.  First, I apologize for
the amount of time it took to do this.  The weekend yielded little time for
coding, but my changes are non-destructive (for now ;-) so things should
still purr along as always.

Anyway, here is the jist of what I did:

1) added the lib/rubygems/cmd_manager.rb file

2) added the bin/gem2 file

Now, as you can see from the source of bin/gem2 its pretty small:

  #!/usr/bin/env ruby

  $:.unshift '../lib'

  require 'rubygems'
  Gem.manage_gems

  Gem::CommandManager.instance.process_args ARGV

Notice I unshift the lib directory, this is to be able to run gem2 without
installing rubygems.  gem2 is a TEMPORARY FILE...it will eventually replace
gem after we have tested things and feel comfortable with it.  The real work
was done in the cmd_manager.rb file.  That file introduces a main class:

Gem::CommandManager

Which has a single instance method (.instance) which you then can access the
following methods on:

#process_args(arg array or string)
  process a string (like '--name foo') or array of strings (like ARGV)

#[](cmd_name)
  return the Gem::CommandManager::Command instace for the named command

#command_names
  a sorted list of registered command names

#add_command(cmd_name, summary)
  add a new command instance (yields command to initializer)

#add_common_option(short, long, description, &handler)
  add a common option that is added to the options of any particular
command.

The singleton instance thing is the key.  About midway down inside the
cmd_manager.rb file you will see that I add all the commands:

rich% ./gem2 --help-commands
GEM commands are:
  build        Build a gem from a gemspec
  check        Check installed gems
  install      Install a gem from a local file or remote server
               into the local repository
  query        Query gem information in local or remote repositories
  uninstall    Uninstall a gem from the local repository
  update       Upgrade all currently installed gems in the local repository

Each command has its own options.  Those are used to construct an option
parser (which adds the common options too).  So, in each command you add
options with handlers:

cmd.add_option('-l', '--local',
 'Restrict operations to the LOCAL domain') do |value, options|
  options[:domain] = :local
end
cmd.add_option('-r', '--remote',
 'Restrict operations to the REMOTE domain') do |value, options|
  options[:domain] = :remote
End

So each option handler (block) gets called with the (option parsed) value
and sent the options hash to update.  As you can see above the local and
remote options update the same options key (:domain).

Commands can each have default values and a summary like the example from
the 'query' command:

cmd = @cmd_manager.add_command('query')
cmd.summary = 'Query gem information in local or remote repositories'
cmd.defaults = {:name=>/.*/, :domain=>:local, :details=>false}

cmd.add_option('-n', '--name-matches REGEXP',
 'Name of gem(s) to query on ...') do |value, options|
  options[:name] = Regexp.compile(value)
end

Anyway, so the defaults are used (per command) to adjust options  and it
keeps them in a nice easy to administer place.

When a command is called (via the 'invoke' method) it parses its options
with an OptionParser then calls a command's registered 'when_invoked' block:

cmd.when_invoked &method(:process_query_command)

This is a Ruby way of passing a method in to 'act as a block'.  The
'process_query_command' method is a common pattern in the cmd_manager file.
For each command there is a add_COMMAND_command method and a
process_COMMAND_command method.  The when_invoked block binds the two
together.  The actual behavior of installing, etc is in the
process_COMMAND_method.  What is nice about having the when_invoked method
is you can mock out the actual command invocation by just doing this:

Gem::CommandManager.instance['install'].when_invoked do |options|
 ...test stuff out
End

An then when options are passed in your block gets called with the values +
defaults.  Also, the options themselves are a hash, and so if you wanted to
drive the install method without passing command arguments you just do:

Gem::CommandManager.process_install_command(
  :name => 'foobar',
  :domain => :remote,
  :test => true
)

Or something like that.  This makes the 'procedural oriented' code highly
reusable across multiple user interfaces.  Actually with this and the change
I did last week of abstracting the UI interactions, you can totally drive
RubyGems functionality from any source you want without loosing
capabilities.

Anyway, what is needed now is lots of tests using the hooks I just
mentioned.  That, and please study the command options, text, etc and make
sure folks like them.  Also, I don't have a few things working yet, and I
know how to, just have not done them yet.  One is the config file stuff.  A
question is whether we want to have a config line per command like:

install: "--test --gen-rdoc"

Or just the gem: 'options' line.  I would think the former would be more
powerful.  Anyway, here it is...explore the gem --help[-options | -commands
| -examples] and then each command gem --help install, etc and provide
feedback.  I will be working to ensure things work, and then move on to
writing unit tests.

So, I guess the caveat of the CVS unlock (in the subject) is that although
you can change stuff, the bin/gem file is going away...so changes there
should be minimized (or mirrored in the cmd_manager.rb file).

Best,

Rich




More information about the Rubygems-developers mailing list