Finding Libraries by Querying Gem Respositories






Finding Libraries by Querying Gem Respositories

Problem

You want to find new gems to install on your system, or see which gems you already have installed.

Solution

From the command line, use gem's query command:

	$ gem query
	*** LOCAL GEMS ***

	sources (0.0.1)
	   This package provides download sources for remote gem installation

	$ gem query --remote
	*** REMOTE GEMS ***
	actionmailer (1.1.1, 1.0.1, 1.0.0, 0.9.1, 0.9.0, 0.8.1, …)
	     Service layer for easy email delivery and testing.

	actionpack (1.10.1, 1.9.1, 1.9.0, 1.8.1, 1.8.0, 1.7.0, …)
	    Web-flow and rendering framework putting the VC in MVC.

	[… Much more output omitted ….]

From Ruby code, use Gem::cache to query your locally installed gems, and Gem::RemoteInstaller#search to query the gems on some other site. Gem::cache can be treated as an Enumerable full of tasty Gem::Specification objects. Gem::Remote-Installer#search returns an Array containing an Array of Gem::Specification objects for every remote source it searched. Usually there will only be one remote sourcethe main gem repository on rubyforge.org.

This Ruby code iterates over the locally installed gems:

	require 'rubygems'

	Gem::cache.each do |name, gem|
	  puts %{"#{gem.name}" gem version #{gem.version} is installed.}
	end
	# "sources" gem version 0.0.1 is installed

The format_gems method defined below gives a convenient way of looking at a large set of Gem::Specification objects. It groups the gems by name and version, then prints a formatted list:

	require 'rubygems/remote_installer'
	require 'yaml'

	def  
format_gems(gems)
	  gem_versions = gems.inject({}) { |h, gem| (h[gem.name] ||= []) << gem; h}
	  gem_versions.keys.sort.each do |name|
	   versions = gem_versions[name].collect { |gem| gem.version.to_s }
	    puts "#{name} is available in these versions: #{versions.join(', ')}"
	  end
	end 

Here it is being run on the gems available from RubyForge:

	format_gems(Gem::RemoteInstaller.new.search(/.*/).flatten)
	# Asami is available in these versions: 0.04
	# Bangkok is available in these versions: 0.1.0
	# Bloglines4R is available in these versions: 0.1.0
	# BlueCloth is available in these versions: 0.0.2, 0.0.3, 0.0.4, 1.0.0
	# …

Discussion

Not only are Ruby gems a convenient packaging mechanism, they're an excellent way to find out about new pieces of Ruby code. The gem repository at rubyforge.org is the canonical location for Ruby libraries, so you've got one place to find new code.

You can query the gems library for gems whose names match a certain regular expression:

	$ gem query --remote --name-matches "test"
	** REMOTE GEMS ***

	lazytest (0.1.0)
	    Testing and benchmarking for lazy people

	test-unit-mock (0.30)
	    Test::Unit::Mock is a class for conveniently building mock objects
	    in Test::Unit test cases.

	testunitxml (0.1.4, 0.1.3)
	    Unit test suite for XML documents
	ZenTest (3.1.0, 3.0.0)
	    == FEATURES/PROBLEMS

Or, from Ruby code:

	format_ 
gems(Gem::RemoteInstaller.new.search(/test/i).flatten)
	# ZenTest is available in these versions: 3.0.0, 3.1.0
	# lazytest is available in these versions: 0.1.0
	# test-unit-mock is available in these versions: 0.30
	# testunitxml is available in these versions: 0.1.3, 0.1.4

This method finds gems that are newer than a certain date. It has to keep around both a Date and a Time object for comparisons, because RubyForge stores some gems' dates as Date objects, some as Time objects, and some as string representations of dates.[1]

[1] This is because of differences in the underlying gem specification files. Different people build their gemspecs in different ways.

	require 'date'

	def gems_newer_than(date,  
query=/.*/)
	  time = Time.local(date.year, date.month, date.day, 0, 0, 0)
	  gems = Gem::RemoteInstaller.new.search(query).flatten
	  gems.reject do |gem|
	    gem_date = gem.date
	    gem_date = DateTime.parse(gem_date) if gem_date.respond_to? :to_str
	    gem_date < (gem_date.is_a?(Date) ? date : time)
	  end
	end

	todays_gems = gems_newer_than(Date.today-1)
	todays_gems.size                                           #=> 7
	format_gems(todays_gems)
	# filament is available in these versions: 0.3.0
	# mechanize is available in these versions: 0.4.1
	# mongrel is available in these versions: 0.3.12.1, 0.3.12.1
	# rake is available in these versions: 0.7.1
	# rspec is available in these versions: 0.5.0
	# tzinfo is available in these versions: 0.2.0

By default, remote queries look only at the main gem repository on rubyforge.org:

	Gem::RemoteInstaller.new.sources                # => ["http://gems.rubyforge.org"]

To query a gem repository other than rubyforge.org, pass in the URL to the repository as the --source argument from the command line. This code starts a gem server on the local machine (it can serve all of your installed gems to other machines), and queries it:

	$ gem_server &

	$ gem query --remote --source http://localhost:8808
	# *** REMOTE GEMS ***
	# Updating Gem source index for: http://localhost:8808
	# sources (0.0.1)
	#    This package provides download sources for remote gem  
installation

From Ruby code, modify the Gem.sources variable to retrieve gems from another source:

	Gem.sources.replace(['http://localhost:8808'])
	format_ 
gems(Gem::RemoteInstaller.new.search(/.*/).flatten)
	# sources is available in these versions: 0.0.1

See Also

  • Recipe 18.7, "Distributing Your Gems," for more on hosting your own gem repository

  • The Ruby Application Archive is a companion to rubyforge.org: rather than hosting Ruby projects, it links to Ruby packages hosted all around the Web; you're more likely to see projects on the RAA that aren't packaged as gems (see Recipe 18.8 for tips on installing them)



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows