Wednesday, April 30, 2008

Day 90: Gemification 2, the revenge

Sinuses... stuffed! Laptop... dying! Must... complete... article... on... gems!

(Part 1)

So I've got a gem. 'gem install kuiper-0.0.1.gem' went well. I even added /var/lib/gems/1.8/bin/ to my $PATH so I could run the script. I started up a new terminal and did this:

roger@roger-laptop:~$ kuiper
roger@roger-laptop:~$

Gwuh? Now this was an unexpected result! Obviously it's finding the file, but running it had no effect. I tracked this down to a Ruby idiom we've all seen before:

if $0 == __FILE__
start_kuiper
end

But this doesn't work with gems! Rubygems doesn't actually install your script in /var/lib/gems/1.8/bin/, they install their own script there, which itself calls yours. Luckily, Rubygems names this script after the one it's calling, so you can modify the usual idiom as so:

if File.basename($0) ==
File.basename(__FILE__)

start_kuiper
end

Now when I ran the game, I got what I was expecting, which is to say a giant list of tracebacks because it couldn't find the resources it needed. (Put in a bug to fix that gracefully: #121) Locating your data files turns out to be relatively simple:

begin
require 'rubygems'
Gem.activate('kuiper',false)
rl.dirs << Gem.loaded_specs['kuiper'].
full_gem_path
rescue LoadError
# Do something else
end

The begin/rescue part in there serves two reasons: Obviously, it'll catch the case where Rubygems isn't loaded. Less obviously, Gem.activate will also throw a LoadError if it can't find its gem. The Gem.activate bit looks for a gem by the name you give it, and 'activates' it - so far as I can tell, this is the same as requiring it via a 'gem' command (which I'll show later). This step is necessary to populate Gem.loaded_specs with our gem's spec. Finally, full_gem_path is - like it sounds - a full path to where the gem installed. In my case, that's:

ls /var/lib/gems/1.8/gems/kuiper-0.0.1/
bin data images lib tests

These directories are all the stuff I told the spec to include (via spec.files) - which includes my data files.

Finally, a bit on interacting with other gems. I made my gem depend on the rubygame gem being installed - now how do I go about letting my program require it?

begin
require 'rubygems'
gem 'rubygame', '>=3.0.0'
rescue LoadError
# Nope, either no gems or no rubygame
end

require 'rubygame'

Like before, the begin/end block there will make sure I have both gems and rubygame installed. Here instead of using the Gem API directly, I use a convenience function to see if Rubygame is there. The require line is outside of the begin/end so that, if we don't have gems and/or don't have the rubygame gem, we can still run so long as the user has rubygame installed somewhere on their system.

And that, as they say, is that. I'm off to drown my troubles in pseudoephedrine!

No comments: