summaryrefslogtreecommitdiff
path: root/lib/rubygems/commands/unpack_command.rb
blob: d187f8a9ea493e153e04406ed8ea022cd1c79e04 (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
85
86
87
88
89
90
91
92
93
94
95
require 'fileutils'
require 'rubygems/command'
require 'rubygems/installer'
require 'rubygems/version_option'

class Gem::Commands::UnpackCommand < Gem::Command

  include Gem::VersionOption

  def initialize
    super 'unpack', 'Unpack an installed gem to the current directory',
          :version => Gem::Requirement.default,
          :target  => Dir.pwd

    add_option('--target', 'target directory for unpacking') do |value, options|
      options[:target] = value
    end

    add_version_option
  end

  def arguments # :nodoc:
    "GEMNAME       name of gem to unpack"
  end

  def defaults_str # :nodoc:
    "--version '#{Gem::Requirement.default}'"
  end

  def usage # :nodoc:
    "#{program_name} GEMNAME"
  end

  #--
  # TODO: allow, e.g., 'gem unpack rake-0.3.1'.  Find a general solution for
  # this, so that it works for uninstall as well.  (And check other commands
  # at the same time.)
  def execute
    gemname = get_one_gem_name
    path = get_path(gemname, options[:version])

    if path then
      basename = File.basename(path).sub(/\.gem$/, '')
      target_dir = File.expand_path File.join(options[:target], basename)
      FileUtils.mkdir_p target_dir
      Gem::Installer.new(path).unpack target_dir
      say "Unpacked gem: '#{target_dir}'"
    else
      alert_error "Gem '#{gemname}' not installed."
    end
  end

  # Return the full path to the cached gem file matching the given
  # name and version requirement.  Returns 'nil' if no match.
  #
  # Example:
  #
  #   get_path('rake', '> 0.4')   # -> '/usr/lib/ruby/gems/1.8/cache/rake-0.4.2.gem'
  #   get_path('rake', '< 0.1')   # -> nil
  #   get_path('rak')             # -> nil (exact name required)
  #--
  # TODO: This should be refactored so that it's a general service. I don't
  # think any of our existing classes are the right place though.  Just maybe
  # 'Cache'?
  #
  # TODO: It just uses Gem.dir for now.  What's an easy way to get the list of
  # source directories?
  def get_path(gemname, version_req)
    return gemname if gemname =~ /\.gem$/i

    specs = Gem::source_index.search(/\A#{gemname}\z/, version_req)

    selected = specs.sort_by { |s| s.version }.last

    return nil if selected.nil?

    # We expect to find (basename).gem in the 'cache' directory.
    # Furthermore, the name match must be exact (ignoring case).
    if gemname =~ /^#{selected.name}$/i
      filename = selected.full_name + '.gem'
      path = nil

      Gem.path.find do |gem_dir|
        path = File.join gem_dir, 'cache', filename
        File.exist? path
      end

      path
    else
      nil
    end
  end

end