Because Gem Names Are Like Domains in the 90's
Created at: 30.03.2010 18:00, source: RailsTips - Home , tagged: gems testing
One of my favorite parts of every new gem is naming it. The other day, when I was trying to name joint, it occurred to me that I should always check if a gem name is available before I create my project. I did a quick search on RubyGems and discovered it was available.
Last night, I decided I should whip together a tiny gem that allows you to issue a whois command to see if a gem name is taken. Why leave the command line, eh?
Installation
gem install gemwhois
This adds the whois command to gem. Which means usage is pretty fun.
Usage
$ gem whois httparty
gem name: httparty
owners: John Nunemaker, Sandro Turriate
info: Makes http fun! Also, makes consuming restful web services dead easy.
version: 0.5.2
downloads: 40714
$ gem whois somenonexistantgem
Gem not found. It will be mine. Oh yes. It will be mine. *sinister laugh*
If the gem is found, you will see some details about the project (maybe you can convince them to hand over rights if they are squatting). If the gem is not found, you will receive a creepy message in the same vein as the RubyGems 404 page.
The Fun Parts
The fun part of this gem was recently I noticed that other gems have been adding commands to the gem command. I thought that was interesting so I did a bit of research. I knew that both gemedit and gemcutter added commands so I downloaded both from Github and began to peruse the source. Turns out it is quite easy.
First, you have to have a rubygems_plugin.rb file in your gems lib directory. This is mostly ripped from gemcutter:
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.3.6')
require File.join(File.dirname(__FILE__), 'gemwhois')
end
Next, you have to create a command. At the time of this post, here is the entirety of the whois command:
require 'rubygems/gemcutter_utilities'
class Gem::Commands::WhoisCommand < Gem::Command
include Gem::GemcutterUtilities
def description
'Perform a whois lookup based on a gem name so you can see if it is available or not'
end
def arguments
"GEM name of gem"
end
def usage
"#{program_name} GEM"
end
def initialize
super 'whois', description
end
def execute
whois get_one_gem_name
end
def whois(gem_name)
response = rubygems_api_request(:get, "api/v1/gems/#{gem_name}.json") do |request|
request.set_form_data("gem_name" => gem_name)
end
with_response(response) do |resp|
json = Crack::JSON.parse(resp.body)
puts <<-STR.unindent
gem name: #{json['name']}
owners: #{json['authors']}
info: #{json['info']}
version: #{json['version']}
downloads: #{json['downloads']}
STR
end
end
def with_response(resp)
case resp
when Net::HTTPSuccess
block_given? ? yield(resp) : say(resp.body)
else
if resp.body == 'This rubygem could not be found.'
puts '','Gem not found. It will be mine. Oh yes. It will be mine. *sinister laugh*',''
else
say resp.body
end
end
end
end
The important part is inheriting from Gem::Command. Be sure to require 'rubygems/command_manager' at some point as well. Once you have the rubygems_plugin file and a command created, you simple register the command:
Gem::CommandManager.instance.register_command(:whois)
The comments and code in RubyGems itself is pretty helpful if you are curious about what you can do.
Testing
The trickier part was testing the command. Obviously, building the gem from gemspec and installing over and over does not a happy tester make. I did a bit of research and found the following testing output helpers and the unindent gem:
module Helpers
module Output
def assert_output(expected, &block)
keep_stdout do |stdout|
block.call
if expected.is_a?(Regexp)
assert_match expected, stdout.string
else
assert_equal expected.to_s, stdout.string
end
end
end
def keep_stdout(&block)
begin
orig_stream, $stdout = $stdout, StringIO.new
block.call($stdout)
ensure
s, $stdout = $stdout.string, orig_stream
s
end
end
end
end
With these little helpers, it was quite easy to setup the command and run it in an automated way:
require 'helper'
class TestGemwhois < Test::Unit::TestCase
context 'Whois for found gem' do
setup do
@gem = 'httparty'
stub_gem(@gem)
@command = Gem::Commands::WhoisCommand.new
@command.handle_options([@gem])
end
should "work" do
output = <<-STR.unindent
gem name: httparty
owners: John Nunemaker, Sandro Turriate
info: Makes http fun! Also, makes consuming restful web services dead easy.
version: 0.5.2
downloads: 40707
STR
assert_output(output) { @command.execute }
end
end
context "Whois for missing gem" do
setup do
@gem = 'missing'
stub_gem(@gem, :status => ["404", "Not Found"])
@command = Gem::Commands::WhoisCommand.new
@command.handle_options([@gem])
end
should "work" do
output = <<-STR.unindent
Gem not found. It will be mine. Oh yes. It will be mine. *sinister laugh*
STR
assert_output(output) { @command.execute }
end
end
end
The only other piece of the puzzle was using FakeWeb to stub the http responses for the found and missing gems. You can see more on that in the test helper file.
Conclusion
At any rate, the gem is pretty tiny and possibly useless to others, but it was fun. Gave me a chance to play around with testing STDOUT and creating RubyGem commands. Plus, now I know if the gem name I want is available in just a few keystrokes.
