summaryrefslogtreecommitdiff
path: root/lib/rubygems/gem_path_searcher.rb
blob: dadad662899a55c3a21baa63606d43dee8ce266c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++

require 'rubygems'

#
# GemPathSearcher has the capability to find loadable files inside
# gems.  It generates data up front to speed up searches later.
#
class Gem::GemPathSearcher

  #
  # Initialise the data we need to make searches later.
  #
  def initialize
    # We want a record of all the installed gemspecs, in the order
    # we wish to examine them.
    @gemspecs = init_gemspecs
    # Map gem spec to glob of full require_path directories.
    # Preparing this information may speed up searches later.
    @lib_dirs = {}
    @gemspecs.each do |spec|
      @lib_dirs[spec.object_id] = lib_dirs_for(spec)
    end
  end

  # 
  # Look in all the installed gems until a matching _path_ is found.
  # Return the _gemspec_ of the gem where it was found.  If no match
  # is found, return nil.
  #
  # The gems are searched in alphabetical order, and in reverse
  # version order.
  #
  # For example:
  #
  #   find('log4r')              # -> (log4r-1.1 spec)
  #   find('log4r.rb')           # -> (log4r-1.1 spec)
  #   find('rake/rdoctask')      # -> (rake-0.4.12 spec)
  #   find('foobarbaz')          # -> nil
  #
  # Matching paths can have various suffixes ('.rb', '.so', and
  # others), which may or may not already be attached to _file_.
  # This method doesn't care about the full filename that matches;
  # only that there is a match.
  # 
  def find(path)
    @gemspecs.each do |spec|
      return spec if matching_file(spec, path)
    end
    nil
  end

  private

  # Attempts to find a matching path using the require_paths of the
  # given _spec_.
  #
  # Some of the intermediate results are cached in @lib_dirs for
  # speed.
  def matching_file(spec, path)  # :doc:
    glob = File.join @lib_dirs[spec.object_id], "#{path}#{Gem.suffix_pattern}"
    return true unless Dir[glob].select { |f| File.file?(f.untaint) }.empty?
  end

  # Return a list of all installed gemspecs, sorted by alphabetical
  # order and in reverse version order.
  def init_gemspecs
    Gem.source_index.map { |_, spec| spec }.sort { |a,b|
      (a.name <=> b.name).nonzero? || (b.version <=> a.version)
    }
  end

  # Returns library directories glob for a gemspec.  For example,
  #   '/usr/local/lib/ruby/gems/1.8/gems/foobar-1.0/{lib,ext}'
  def lib_dirs_for(spec)
    "#{spec.full_gem_path}/{#{spec.require_paths.join(',')}}"
  end

end