[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