You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1442 lines
40 KiB
1442 lines
40 KiB
#--
|
|
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
|
|
# All rights reserved.
|
|
# See LICENSE.txt for permissions.
|
|
#++
|
|
|
|
|
|
module Gem
|
|
|
|
class CommandLineError < Gem::Exception; end
|
|
|
|
####################################################################
|
|
# The following mixin methods aid in the retrieving of information
|
|
# from the command line.
|
|
#
|
|
module CommandAids
|
|
|
|
# Get the single gem name from the command line. Fail if there is
|
|
# no gem name or if there is more than one gem name given.
|
|
def get_one_gem_name
|
|
args = options[:args]
|
|
if args.nil? or args.empty?
|
|
fail Gem::CommandLineError,
|
|
"Please specify a gem name on the command line (e.g. gem build GEMNAME)"
|
|
end
|
|
if args.size > 1
|
|
fail Gem::CommandLineError,
|
|
"Too many gem names (#{args.join(', ')}); please specify only one"
|
|
end
|
|
args.first
|
|
end
|
|
|
|
# Get a single optional argument from the command line. If more
|
|
# than one argument is given, return only the first. Return nil if
|
|
# none are given.
|
|
def get_one_optional_argument
|
|
args = options[:args] || []
|
|
args.first
|
|
end
|
|
|
|
# True if +long+ begins with the characters from +short+.
|
|
def begins?(long, short)
|
|
return false if short.nil?
|
|
long[0, short.length] == short
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
# Mixin methods for handling the local/remote command line options.
|
|
#
|
|
module LocalRemoteOptions
|
|
|
|
# Add the local/remote options to the command line parser.
|
|
def add_local_remote_options
|
|
add_option('-l', '--local',
|
|
'Restrict operations to the LOCAL domain (default)') do
|
|
|value, options|
|
|
options[:domain] = :local
|
|
end
|
|
|
|
add_option('-r', '--remote',
|
|
'Restrict operations to the REMOTE domain') do
|
|
|value, options|
|
|
options[:domain] = :remote
|
|
end
|
|
|
|
add_option('-b', '--both',
|
|
'Allow LOCAL and REMOTE operations') do
|
|
|value, options|
|
|
options[:domain] = :both
|
|
end
|
|
end
|
|
|
|
# Is local fetching enabled?
|
|
def local?
|
|
options[:domain] == :local || options[:domain] == :both
|
|
end
|
|
|
|
# Is remote fetching enabled?
|
|
def remote?
|
|
options[:domain] == :remote || options[:domain] == :both
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
# Mixin methods and OptionParser options specific to the gem install
|
|
# command.
|
|
#
|
|
module InstallUpdateOptions
|
|
|
|
# Add the install/update options to the option parser.
|
|
def add_install_update_options
|
|
add_option('-i', '--install-dir DIR',
|
|
'Gem repository directory to get installed gems.') do
|
|
|value, options|
|
|
options[:install_dir] = File.expand_path(value)
|
|
end
|
|
|
|
add_option('-d', '--[no-]rdoc',
|
|
'Generate RDoc documentation for the gem on install') do
|
|
|value, options|
|
|
options[:generate_rdoc] = value
|
|
end
|
|
|
|
add_option('--[no-]ri',
|
|
'Generate RI documentation for the gem on install') do
|
|
|value, options|
|
|
options[:generate_ri] = value
|
|
end
|
|
|
|
add_option('-f', '--[no-]force',
|
|
'Force gem to install, bypassing dependency checks') do
|
|
|value, options|
|
|
options[:force] = value
|
|
end
|
|
|
|
add_option('-t', '--[no-]test',
|
|
'Run unit tests prior to installation') do
|
|
|value, options|
|
|
options[:test] = value
|
|
end
|
|
|
|
add_option('-w', '--[no-]wrappers',
|
|
'Use bin wrappers for executables',
|
|
'Not available on dosish platforms') do
|
|
|value, options|
|
|
options[:wrappers] = value
|
|
end
|
|
|
|
add_option('-P', '--trust-policy POLICY',
|
|
'Specify gem trust policy.') do
|
|
|value, options|
|
|
options[:security_policy] = value
|
|
end
|
|
|
|
add_option('--ignore-dependencies',
|
|
'Do not install any required dependent gems') do
|
|
|value, options|
|
|
options[:ignore_dependencies] = value
|
|
end
|
|
|
|
add_option('-y', '--include-dependencies',
|
|
'Unconditionally install the required dependent gems') do
|
|
|value, options|
|
|
options[:include_dependencies] = value
|
|
end
|
|
end
|
|
|
|
# Default options for the gem install command.
|
|
def install_update_defaults_str
|
|
'--rdoc --no-force --no-test --wrappers'
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
# Mixin methods for the version command.
|
|
#
|
|
module VersionOption
|
|
|
|
# Add the options to the option parser.
|
|
def add_version_option(taskname)
|
|
add_option('-v', '--version VERSION',
|
|
"Specify version of gem to #{taskname}") do
|
|
|value, options|
|
|
options[:version] = value
|
|
end
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
# Gem install command.
|
|
#
|
|
class InstallCommand < Command
|
|
include CommandAids
|
|
include VersionOption
|
|
include LocalRemoteOptions
|
|
include InstallUpdateOptions
|
|
|
|
def initialize
|
|
super(
|
|
'install',
|
|
'Install a gem into the local repository',
|
|
{
|
|
:domain => :both,
|
|
:generate_rdoc => true,
|
|
:generate_ri => true,
|
|
:force => false,
|
|
:test => false,
|
|
:wrappers => true,
|
|
:version => "> 0",
|
|
:install_dir => Gem.dir,
|
|
:security_policy => nil,
|
|
})
|
|
add_version_option('install')
|
|
add_local_remote_options
|
|
add_install_update_options
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} GEMNAME [options]
|
|
or: #{program_name} GEMNAME [options] -- --build-flags"
|
|
end
|
|
|
|
def arguments
|
|
"GEMNAME name of gem to install"
|
|
end
|
|
|
|
def defaults_str
|
|
"--both --version '> 0' --rdoc --ri --no-force --no-test\n" +
|
|
"--install-dir #{Gem.dir}"
|
|
end
|
|
|
|
def execute
|
|
ENV['GEM_PATH'] = options[:install_dir]
|
|
if(options[:args].empty?)
|
|
fail Gem::CommandLineError,
|
|
"Please specify a gem name on the command line (e.g. gem build GEMNAME)"
|
|
end
|
|
options[:args].each do |gem_name|
|
|
if local?
|
|
begin
|
|
entries = []
|
|
if(File.exist?(gem_name) && !File.directory?(gem_name))
|
|
entries << gem_name
|
|
else
|
|
filepattern = gem_name + "*.gem"
|
|
entries = Dir[filepattern]
|
|
end
|
|
unless entries.size > 0
|
|
if options[:domain] == :local
|
|
alert_error "Local gem file not found: #{filepattern}"
|
|
end
|
|
else
|
|
result = Gem::Installer.new(entries.last, options).install(
|
|
options[:force],
|
|
options[:install_dir])
|
|
installed_gems = [result].flatten
|
|
say "Successfully installed #{installed_gems[0].name}, " +
|
|
"version #{installed_gems[0].version}" if installed_gems
|
|
end
|
|
rescue LocalInstallationError => e
|
|
say " -> Local installation can't proceed: #{e.message}"
|
|
rescue Gem::LoadError => e
|
|
say " -> Local installation can't proceed due to LoadError: #{e.message}"
|
|
rescue => e
|
|
# TODO: Fix this handle to allow the error to propagate to
|
|
# the top level handler. Examine the other errors as
|
|
# well. This implementation here looks suspicious to me --
|
|
# JimWeirich (4/Jan/05)
|
|
alert_error "Error installing gem #{gem_name}[.gem]: #{e.message}"
|
|
return
|
|
end
|
|
end
|
|
|
|
if remote? && installed_gems.nil?
|
|
installer = Gem::RemoteInstaller.new(options)
|
|
installed_gems = installer.install(
|
|
gem_name,
|
|
options[:version],
|
|
options[:force],
|
|
options[:install_dir])
|
|
if installed_gems
|
|
installed_gems.compact!
|
|
installed_gems.each do |spec|
|
|
say "Successfully installed #{spec.full_name}"
|
|
end
|
|
end
|
|
end
|
|
|
|
unless installed_gems
|
|
alert_error "Could not install a local " +
|
|
"or remote copy of the gem: #{gem_name}"
|
|
terminate_interaction(1)
|
|
end
|
|
|
|
# NOTE: *All* of the RI documents must be generated first.
|
|
# For some reason, RI docs cannot be generated after any RDoc
|
|
# documents are generated.
|
|
|
|
if options[:generate_ri]
|
|
installed_gems.each do |gem|
|
|
Gem::DocManager.new(gem, options[:rdoc_args]).generate_ri
|
|
end
|
|
end
|
|
|
|
if options[:generate_rdoc]
|
|
installed_gems.each do |gem|
|
|
Gem::DocManager.new(gem, options[:rdoc_args]).generate_rdoc
|
|
end
|
|
end
|
|
|
|
if options[:test]
|
|
installed_gems.each do |spec|
|
|
gem_spec = Gem::SourceIndex.from_installed_gems.search(spec.name, spec.version.version).first
|
|
result = Gem::Validator.new.unit_test(gem_spec)
|
|
unless result.passed?
|
|
unless ask_yes_no("...keep Gem?", true) then
|
|
Gem::Uninstaller.new(spec.name, spec.version.version).uninstall
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
####################################################################
|
|
class UninstallCommand < Command
|
|
include VersionOption
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super('uninstall', 'Uninstall a gem from the local repository', {:version=>"> 0"})
|
|
add_option('-a', '--[no-]all',
|
|
'Uninstall all matching versions'
|
|
) do |value, options|
|
|
options[:all] = value
|
|
end
|
|
add_option('-i', '--[no-]ignore-dependencies',
|
|
'Ignore dependency requirements while uninstalling'
|
|
) do |value, options|
|
|
options[:ignore] = value
|
|
end
|
|
add_option('-x', '--[no-]executables',
|
|
'Uninstall applicable executables without confirmation'
|
|
) do |value, options|
|
|
options[:executables] = value
|
|
end
|
|
add_version_option('uninstall')
|
|
end
|
|
|
|
def defaults_str
|
|
"--version '> 0' --no-force"
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} GEMNAME"
|
|
end
|
|
|
|
def arguments
|
|
"GEMNAME name of gem to uninstall"
|
|
end
|
|
|
|
def execute
|
|
gem_name = get_one_gem_name
|
|
Gem::Uninstaller.new(gem_name, options).uninstall
|
|
end
|
|
end
|
|
|
|
class CertCommand < Command
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super(
|
|
'cert',
|
|
'Adjust RubyGems certificate settings',
|
|
{
|
|
})
|
|
|
|
add_option('-a', '--add CERT', 'Add a trusted certificate.') do |value, options|
|
|
cert = OpenSSL::X509::Certificate.new(File.read(value))
|
|
Gem::Security.add_trusted_cert(cert)
|
|
puts "Added #{cert.subject.to_s}"
|
|
end
|
|
|
|
add_option('-l', '--list', 'List trusted certificates.') do |value, options|
|
|
glob_str = File::join(Gem::Security::OPT[:trust_dir], '*.pem')
|
|
Dir::glob(glob_str) do |path|
|
|
cert = OpenSSL::X509::Certificate.new(File.read(path))
|
|
# this could proably be formatted more gracefully
|
|
puts cert.subject.to_s
|
|
end
|
|
end
|
|
|
|
add_option('-r', '--remove STRING', 'Remove trusted certificates containing STRING.') do |value, options|
|
|
trust_dir = Gem::Security::OPT[:trust_dir]
|
|
glob_str = File::join(trust_dir, '*.pem')
|
|
|
|
Dir::glob(glob_str) do |path|
|
|
cert = OpenSSL::X509::Certificate.new(File.read(path))
|
|
if cert.subject.to_s.downcase.index(value)
|
|
puts "Removing '#{cert.subject.to_s}'"
|
|
File.unlink(path)
|
|
end
|
|
end
|
|
end
|
|
|
|
add_option('-b', '--build EMAIL_ADDR',
|
|
'Build private key and self-signed certificate for EMAIL_ADDR.'
|
|
) do |value, options|
|
|
vals = Gem::Security::build_self_signed_cert(value)
|
|
File::chmod(0600, vals[:key_path])
|
|
puts "Public Cert: #{vals[:cert_path]}",
|
|
"Private Key: #{vals[:key_path]}",
|
|
"Don't forget to move the key file to somewhere private..."
|
|
end
|
|
|
|
add_option('-C', '--certificate CERT',
|
|
'Certificate for --sign command.'
|
|
) do |value, options|
|
|
cert = OpenSSL::X509::Certificate.new(File.read(value))
|
|
Gem::Security::OPT[:issuer_cert] = cert
|
|
end
|
|
|
|
add_option('-K', '--private-key KEY',
|
|
'Private key for --sign command.'
|
|
) do |value, options|
|
|
key = OpenSSL::PKey::RSA.new(File.read(value))
|
|
Gem::Security::OPT[:issuer_key] = key
|
|
end
|
|
|
|
|
|
add_option('-s', '--sign NEWCERT',
|
|
'Sign a certificate with my key and certificate.'
|
|
) do |value, options|
|
|
cert = OpenSSL::X509::Certificate.new(File.read(value))
|
|
my_cert = Gem::Security::OPT[:issuer_cert]
|
|
my_key = Gem::Security::OPT[:issuer_key]
|
|
cert = Gem::Security.sign_cert(cert, my_key, my_cert)
|
|
File::open(value, 'wb') { |file| file.write(cert.to_pem) }
|
|
end
|
|
|
|
end
|
|
|
|
def execute
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class DependencyCommand < Command
|
|
include VersionOption
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super('dependency',
|
|
'Show the dependencies of an installed gem',
|
|
{:version=>"> 0"})
|
|
add_version_option('uninstall')
|
|
add_option('-r', '--[no-]reverse-dependencies',
|
|
'Include reverse dependencies in the output'
|
|
) do |value, options|
|
|
options[:reverse_dependencies] = value
|
|
end
|
|
add_option('-p', '--pipe', "Pipe Format (name --version ver)") do |value, options|
|
|
options[:pipe_format] = value
|
|
end
|
|
end
|
|
|
|
def defaults_str
|
|
"--version '> 0' --no-reverse"
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} GEMNAME"
|
|
end
|
|
|
|
def arguments
|
|
"GEMNAME name of gems to show"
|
|
end
|
|
|
|
def execute
|
|
specs = {}
|
|
srcindex = SourceIndex.from_installed_gems
|
|
options[:args] << '.' if options[:args].empty?
|
|
options[:args].each do |name|
|
|
speclist = srcindex.search(name, options[:version])
|
|
if speclist.empty?
|
|
say "No match found for #{name} (#{options[:version]})"
|
|
else
|
|
speclist.each do |spec|
|
|
specs[spec.full_name] = spec
|
|
end
|
|
end
|
|
end
|
|
reverse = Hash.new { |h, k| h[k] = [] }
|
|
if options[:reverse_dependencies]
|
|
specs.values.each do |spec|
|
|
reverse[spec.full_name] = find_reverse_dependencies(spec, srcindex)
|
|
end
|
|
end
|
|
if options[:pipe_format]
|
|
specs.values.sort.each do |spec|
|
|
unless spec.dependencies.empty?
|
|
spec.dependencies.each do |dep|
|
|
puts "#{dep.name} --version '#{dep.version_requirements}'"
|
|
end
|
|
end
|
|
end
|
|
else
|
|
response = ''
|
|
specs.values.sort.each do |spec|
|
|
response << print_dependencies(spec)
|
|
unless reverse[spec.full_name].empty?
|
|
response << " Used by\n"
|
|
reverse[spec.full_name].each do |sp, dep|
|
|
response << " #{sp} (#{dep})\n"
|
|
end
|
|
end
|
|
response << "\n"
|
|
end
|
|
say response
|
|
end
|
|
end
|
|
|
|
def print_dependencies(spec, level = 0)
|
|
response = ''
|
|
response << ' ' * level + "Gem #{spec.full_name}\n"
|
|
unless spec.dependencies.empty?
|
|
# response << ' ' * level + " Requires\n"
|
|
spec.dependencies.each do |dep|
|
|
response << ' ' * level + " #{dep}\n"
|
|
end
|
|
end
|
|
response
|
|
end
|
|
|
|
# Retuns list of [specification, dep] that are satisfied by spec.
|
|
def find_reverse_dependencies(spec, srcindex)
|
|
result = []
|
|
srcindex.each do |name, sp|
|
|
sp.dependencies.each do |dep|
|
|
if spec.name == dep.name &&
|
|
dep.version_requirements.satisfied_by?(spec.version)
|
|
result << [sp.full_name, dep]
|
|
end
|
|
end
|
|
end
|
|
result
|
|
end
|
|
|
|
end
|
|
|
|
####################################################################
|
|
class CheckCommand < Command
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super('check', 'Check installed gems', {:verify => false, :alien => false})
|
|
add_option('-v', '--verify FILE', 'Verify gem file against its internal checksum') do |value, options|
|
|
options[:verify] = value
|
|
end
|
|
add_option('-a', '--alien', "Report 'unmanaged' or rogue files in the gem repository") do |value, options|
|
|
options[:alien] = true
|
|
end
|
|
add_option('-t', '--test', "Run unit tests for gem") do |value, options|
|
|
options[:test] = true
|
|
end
|
|
add_option('-V', '--version', "Specify version for which to run unit tests") do |value, options|
|
|
options[:version] = value
|
|
end
|
|
end
|
|
|
|
def execute
|
|
if options[:test]
|
|
version = options[:version] || "> 0.0.0"
|
|
gem_spec = Gem::SourceIndex.from_installed_gems.search(get_one_gem_name, version).first
|
|
Gem::Validator.new.unit_test(gem_spec)
|
|
end
|
|
if options[:alien]
|
|
say "Performing the 'alien' operation"
|
|
Gem::Validator.new.alien.each do |key, val|
|
|
if(val.size > 0)
|
|
say "#{key} has #{val.size} problems"
|
|
val.each do |error_entry|
|
|
say "\t#{error_entry.path}:"
|
|
say "\t#{error_entry.problem}"
|
|
say
|
|
end
|
|
else
|
|
say "#{key} is error-free"
|
|
end
|
|
say
|
|
end
|
|
end
|
|
if options[:verify]
|
|
gem_name = options[:verify]
|
|
unless gem_name
|
|
alert_error "Must specify a .gem file with --verify NAME"
|
|
return
|
|
end
|
|
unless File.exist?(gem_name)
|
|
alert_error "Unknown file: #{gem_name}."
|
|
return
|
|
end
|
|
say "Verifying gem: '#{gem_name}'"
|
|
begin
|
|
Gem::Validator.new.verify_gem_file(gem_name)
|
|
rescue Exception => e
|
|
alert_error "#{gem_name} is invalid."
|
|
end
|
|
end
|
|
end
|
|
|
|
end # class
|
|
|
|
####################################################################
|
|
class BuildCommand < Command
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super('build', 'Build a gem from a gemspec')
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} GEMSPEC_FILE"
|
|
end
|
|
|
|
def arguments
|
|
"GEMSPEC_FILE name of gemspec file used to build the gem"
|
|
end
|
|
|
|
def execute
|
|
gemspec = get_one_gem_name
|
|
if File.exist?(gemspec)
|
|
specs = load_gemspecs(gemspec)
|
|
specs.each do |spec|
|
|
Gem::Builder.new(spec).build
|
|
end
|
|
return
|
|
else
|
|
alert_error "Gemspec file not found: #{gemspec}"
|
|
end
|
|
end
|
|
|
|
def load_gemspecs(filename)
|
|
if yaml?(filename)
|
|
require 'yaml'
|
|
result = []
|
|
open(filename) do |f|
|
|
begin
|
|
while spec = Gem::Specification.from_yaml(f)
|
|
result << spec
|
|
end
|
|
rescue EndOfYAMLException => e
|
|
# OK
|
|
end
|
|
end
|
|
else
|
|
result = [Gem::Specification.load(filename)]
|
|
end
|
|
result
|
|
end
|
|
|
|
def yaml?(filename)
|
|
line = open(filename) { |f| line = f.gets }
|
|
result = line =~ %r{^--- *!ruby/object:Gem::Specification}
|
|
result
|
|
end
|
|
|
|
end
|
|
|
|
####################################################################
|
|
class QueryCommand < Command
|
|
include LocalRemoteOptions
|
|
|
|
def initialize(name='query', summary='Query gem information in local or remote repositories')
|
|
super(name,
|
|
summary,
|
|
{:name=>/.*/, :domain=>:local, :details=>false}
|
|
)
|
|
add_option('-n', '--name-matches REGEXP', 'Name of gem(s) to query on matches the provided REGEXP') do |value, options|
|
|
options[:name] = /#{value}/i
|
|
end
|
|
add_option('-d', '--[no-]details', 'Display detailed information of gem(s)') do |value, options|
|
|
options[:details] = value
|
|
end
|
|
add_local_remote_options
|
|
end
|
|
|
|
def defaults_str
|
|
"--local --name-matches '.*' --no-details"
|
|
end
|
|
|
|
def execute
|
|
if local?
|
|
say
|
|
say "*** LOCAL GEMS ***"
|
|
output_query_results(Gem::cache.search(options[:name]))
|
|
end
|
|
if remote?
|
|
say
|
|
say "*** REMOTE GEMS ***"
|
|
output_query_results(Gem::RemoteInstaller.new(options).search(options[:name]))
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def output_query_results(gemspecs)
|
|
gem_list_with_version = {}
|
|
gemspecs.flatten.each do |gemspec|
|
|
gem_list_with_version[gemspec.name] ||= []
|
|
gem_list_with_version[gemspec.name] << gemspec
|
|
end
|
|
|
|
gem_list_with_version = gem_list_with_version.sort do |first, second|
|
|
first[0].downcase <=> second[0].downcase
|
|
end
|
|
gem_list_with_version.each do |gem_name, list_of_matching|
|
|
say
|
|
list_of_matching = list_of_matching.sort_by { |x| x.version }.reverse
|
|
seen_versions = []
|
|
list_of_matching.delete_if do |item|
|
|
if(seen_versions.member?(item.version))
|
|
true
|
|
else
|
|
seen_versions << item.version
|
|
false
|
|
end
|
|
end
|
|
say "#{gem_name} (#{list_of_matching.map{|gem| gem.version.to_s}.join(", ")})"
|
|
say format_text(list_of_matching[0].summary, 68, 4)
|
|
end
|
|
end
|
|
|
|
##
|
|
# Used for wrapping and indenting text
|
|
#
|
|
def format_text(text, wrap, indent=0)
|
|
result = []
|
|
pattern = Regexp.new("^(.{0,#{wrap}})[ \n]")
|
|
work = text.dup
|
|
while work.length > wrap
|
|
if work =~ pattern
|
|
result << $1
|
|
work.slice!(0, $&.length)
|
|
else
|
|
result << work.slice!(0, wrap)
|
|
end
|
|
end
|
|
result << work if work.length.nonzero?
|
|
result.join("\n").gsub(/^/, " " * indent)
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class ListCommand < QueryCommand
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super(
|
|
'list',
|
|
'Display all gems whose name starts with STRING'
|
|
)
|
|
remove_option('--name-matches')
|
|
end
|
|
|
|
def defaults_str
|
|
"--local --no-details"
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} [STRING]"
|
|
end
|
|
|
|
def arguments
|
|
"STRING start of gem name to look for"
|
|
end
|
|
|
|
def execute
|
|
string = get_one_optional_argument || ''
|
|
options[:name] = /^#{string}/i
|
|
super
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class SearchCommand < QueryCommand
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super(
|
|
'search',
|
|
'Display all gems whose name contains STRING'
|
|
)
|
|
remove_option('--name-matches')
|
|
end
|
|
|
|
def defaults_str
|
|
"--local --no-details"
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} [STRING]"
|
|
end
|
|
|
|
def arguments
|
|
"STRING fragment of gem name to look for"
|
|
end
|
|
|
|
def execute
|
|
string = get_one_optional_argument
|
|
options[:name] = /#{string}/i
|
|
super
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class UpdateCommand < Command
|
|
include InstallUpdateOptions
|
|
|
|
def initialize
|
|
super(
|
|
'update',
|
|
'Update the named gem (or all installed gems) in the local repository',
|
|
{
|
|
:generate_rdoc => true,
|
|
:generate_ri => true,
|
|
:force => false,
|
|
:test => false,
|
|
:install_dir => Gem.dir
|
|
})
|
|
add_install_update_options
|
|
add_option('--system',
|
|
'Update the RubyGems system software') do |value, options|
|
|
options[:system] = value
|
|
end
|
|
end
|
|
|
|
def defaults_str
|
|
"--rdoc --ri --no-force --no-test\n" +
|
|
"--install-dir #{Gem.dir}"
|
|
end
|
|
|
|
def arguments
|
|
"GEMNAME(s) name of gem(s) to update"
|
|
end
|
|
|
|
|
|
def execute
|
|
if options[:system]
|
|
say "Updating RubyGems..."
|
|
if ! options[:args].empty?
|
|
fail "No gem names are allowed with the --system option"
|
|
end
|
|
options[:args] = ["rubygems-update"]
|
|
else
|
|
say "Updating installed gems..."
|
|
end
|
|
hig = highest_installed_gems = {}
|
|
Gem::SourceIndex.from_installed_gems.each do |name, spec|
|
|
if hig[spec.name].nil? or hig[spec.name].version < spec.version
|
|
hig[spec.name] = spec
|
|
end
|
|
end
|
|
remote_gemspecs = Gem::RemoteInstaller.new(options).search(//)
|
|
# For some reason, this is an array of arrays. The actual list
|
|
# of specifications is the first and only element. If there
|
|
# were more remote sources, perhaps there would be more.
|
|
remote_gemspecs = remote_gemspecs.flatten
|
|
gems_to_update = if(options[:args].empty?) then
|
|
which_to_update(highest_installed_gems, remote_gemspecs)
|
|
else
|
|
options[:args]
|
|
end
|
|
options[:domain] = :remote # install from remote source
|
|
install_command = command_manager['install']
|
|
gems_to_update.uniq.sort.each do |name|
|
|
say "Attempting remote update of #{name}"
|
|
options[:args] = [name]
|
|
install_command.merge_options(options)
|
|
install_command.execute
|
|
end
|
|
if gems_to_update.include?("rubygems-update")
|
|
latest_ruby_gem = remote_gemspecs.select { |s|
|
|
s.name == 'rubygems-update'
|
|
}.sort_by { |s|
|
|
s.version
|
|
}.last
|
|
say "Updating version of RubyGems to #{latest_ruby_gem.version}"
|
|
do_rubygems_update(latest_ruby_gem.version.to_s)
|
|
end
|
|
if(options[:system]) then
|
|
say "RubyGems system software updated"
|
|
else
|
|
say "Gems: [#{gems_to_update.uniq.sort.collect{|g| g.to_s}.join(', ')}] updated"
|
|
end
|
|
end
|
|
|
|
def do_rubygems_update(version_string)
|
|
update_dir = File.join(Gem.dir, "gems", "rubygems-update-#{version_string}")
|
|
Dir.chdir(update_dir) do
|
|
puts "Installing RubyGems #{version_string}"
|
|
system "#{Gem.ruby} setup.rb"
|
|
end
|
|
end
|
|
|
|
def which_to_update(highest_installed_gems, remote_gemspecs)
|
|
result = []
|
|
highest_installed_gems.each do |l_name, l_spec|
|
|
highest_remote_gem =
|
|
remote_gemspecs.select { |spec| spec.name == l_name }.
|
|
sort_by { |spec| spec.version }.
|
|
last
|
|
if highest_remote_gem and l_spec.version < highest_remote_gem.version
|
|
result << l_name
|
|
end
|
|
end
|
|
result
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class CleanupCommand < Command
|
|
def initialize
|
|
super(
|
|
'cleanup',
|
|
'Cleanup old versions of installed gems in the local repository',
|
|
{
|
|
:force => false,
|
|
:test => false,
|
|
:install_dir => Gem.dir
|
|
})
|
|
add_option('-d', '--dryrun', "") do |value, options|
|
|
options[:dryrun] = true
|
|
end
|
|
end
|
|
|
|
def defaults_str
|
|
"--no-dryrun"
|
|
end
|
|
|
|
def arguments
|
|
"GEMNAME(s) name of gem(s) to cleanup"
|
|
end
|
|
|
|
def execute
|
|
say "Cleaning up installed gems..."
|
|
srcindex = Gem::SourceIndex.from_installed_gems
|
|
primary_gems = {}
|
|
srcindex.each do |name, spec|
|
|
if primary_gems[spec.name].nil? or primary_gems[spec.name].version < spec.version
|
|
primary_gems[spec.name] = spec
|
|
end
|
|
end
|
|
gems_to_cleanup = []
|
|
if ! options[:args].empty?
|
|
options[:args].each do |gem_name|
|
|
specs = Gem.cache.search(/^#{gem_name}$/i)
|
|
specs.each do |spec|
|
|
gems_to_cleanup << spec
|
|
end
|
|
end
|
|
else
|
|
srcindex.each do |name, spec|
|
|
gems_to_cleanup << spec
|
|
end
|
|
end
|
|
gems_to_cleanup = gems_to_cleanup.select { |spec|
|
|
primary_gems[spec.name].version != spec.version
|
|
}
|
|
uninstall_command = command_manager['uninstall']
|
|
deplist = DependencyList.new
|
|
gems_to_cleanup.uniq.each do |spec| deplist.add(spec) end
|
|
deplist.dependency_order.each do |spec|
|
|
if options[:dryrun]
|
|
say "Dry Run Mode: Would uninstall #{spec.full_name}"
|
|
else
|
|
say "Attempting uninstall on #{spec.full_name}"
|
|
options[:args] = [spec.name]
|
|
options[:version] = "= #{spec.version}"
|
|
options[:executables] = true
|
|
uninstall_command.merge_options(options)
|
|
begin
|
|
uninstall_command.execute
|
|
rescue Gem::DependencyRemovalException => ex
|
|
say "Unable to uninstall #{spec.full_name} ... continuing with remaining gems"
|
|
end
|
|
end
|
|
end
|
|
say "Clean Up Complete"
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class RDocCommand < Command
|
|
include VersionOption
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super('rdoc',
|
|
'Generates RDoc for pre-installed gems',
|
|
{
|
|
:version => "> 0.0.0",
|
|
:include_rdoc => true,
|
|
:include_ri => true,
|
|
})
|
|
add_option('--all',
|
|
'Generate RDoc/RI documentation for all installed gems'
|
|
) do |value, options|
|
|
options[:all] = value
|
|
end
|
|
add_option('--[no-]rdoc',
|
|
'Include RDoc generated documents') do
|
|
|value, options|
|
|
options[:include_rdoc] = value
|
|
end
|
|
add_option('--[no-]ri',
|
|
'Include RI generated documents'
|
|
) do |value, options|
|
|
options[:include_ri] = value
|
|
end
|
|
add_version_option('rdoc')
|
|
end
|
|
|
|
def defaults_str
|
|
"--version '> 0.0.0' --rdoc --ri"
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} [args]"
|
|
end
|
|
|
|
def arguments
|
|
"GEMNAME The gem to generate RDoc for (unless --all)"
|
|
end
|
|
|
|
def execute
|
|
if options[:all]
|
|
specs = Gem::SourceIndex.from_installed_gems.collect { |name, spec|
|
|
spec
|
|
}
|
|
else
|
|
gem_name = get_one_gem_name
|
|
specs = Gem::SourceIndex.from_installed_gems.search(
|
|
gem_name, options[:version])
|
|
end
|
|
|
|
if specs.empty?
|
|
fail "Failed to find gem #{gem_name} to generate RDoc for #{options[:version]}"
|
|
end
|
|
if options[:include_ri]
|
|
specs.each do |spec|
|
|
Gem::DocManager.new(spec).generate_ri
|
|
end
|
|
end
|
|
if options[:include_rdoc]
|
|
specs.each do |spec|
|
|
Gem::DocManager.new(spec).generate_rdoc
|
|
end
|
|
end
|
|
|
|
true
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class EnvironmentCommand < Command
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super('environment', 'Display RubyGems environmental information')
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} [args]"
|
|
end
|
|
|
|
def arguments
|
|
args = <<-EOF
|
|
packageversion display the package version
|
|
gemdir display the path where gems are installed
|
|
gempath display path used to search for gems
|
|
version display the gem format version
|
|
remotesources display the remote gem servers
|
|
<omitted> display everything
|
|
EOF
|
|
return args.gsub(/^\s+/, '')
|
|
end
|
|
|
|
def execute
|
|
out = ''
|
|
arg = options[:args][0]
|
|
if begins?("packageversion", arg)
|
|
out = Gem::RubyGemsPackageVersion.to_s
|
|
elsif begins?("version", arg)
|
|
out = Gem::RubyGemsVersion.to_s
|
|
elsif begins?("gemdir", arg)
|
|
out = Gem.dir
|
|
elsif begins?("gempath", arg)
|
|
Gem.path.collect { |p| out << "#{p}\n" }
|
|
elsif begins?("remotesources", arg)
|
|
Gem::RemoteInstaller.new.sources.collect do |s|
|
|
out << "#{s}\n"
|
|
end
|
|
elsif arg
|
|
fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]"
|
|
else
|
|
out = "Rubygems Environment:\n"
|
|
out << " - VERSION: #{Gem::RubyGemsVersion} (#{Gem::RubyGemsPackageVersion})\n"
|
|
out << " - INSTALLATION DIRECTORY: #{Gem.dir}\n"
|
|
out << " - GEM PATH:\n"
|
|
Gem.path.collect { |p| out << " - #{p}\n" }
|
|
out << " - REMOTE SOURCES:\n"
|
|
Gem::RemoteInstaller.new.sources.collect do |s|
|
|
out << " - #{s}\n"
|
|
end
|
|
end
|
|
say out
|
|
true
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class SpecificationCommand < Command
|
|
include VersionOption
|
|
include LocalRemoteOptions
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super('specification', 'Display gem specification (in yaml)', {:domain=>:local, :version=>"> 0.0.0"})
|
|
add_version_option('examine')
|
|
add_local_remote_options
|
|
add_option('--all', 'Output specifications for all versions of the gem') do
|
|
options[:all] = true
|
|
end
|
|
end
|
|
|
|
def defaults_str
|
|
"--local --version '(latest)'"
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} GEMFILE"
|
|
end
|
|
|
|
def arguments
|
|
"GEMFILE Name of a .gem file to examine"
|
|
end
|
|
|
|
def execute
|
|
if local?
|
|
gem = get_one_gem_name
|
|
gem_specs = Gem::SourceIndex.from_installed_gems.search(gem, options[:version])
|
|
unless gem_specs.empty?
|
|
require 'yaml'
|
|
output = lambda { |spec| say spec.to_yaml; say "\n" }
|
|
if options[:all]
|
|
gem_specs.each(&output)
|
|
else
|
|
spec = gem_specs.sort_by { |spec| spec.version }.last
|
|
output[spec]
|
|
end
|
|
else
|
|
alert_error "Unknown gem #{gem}"
|
|
end
|
|
end
|
|
|
|
if remote?
|
|
say "(Remote 'info' operation is not yet implemented.)"
|
|
# NOTE: when we do implement remote info, make sure we don't
|
|
# duplicate huge swabs of local data. If it's the same, just
|
|
# say it's the same.
|
|
end
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class UnpackCommand < Command
|
|
include VersionOption
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super(
|
|
'unpack',
|
|
'Unpack an installed gem to the current directory',
|
|
{ :version => '> 0' }
|
|
)
|
|
add_version_option('unpack')
|
|
end
|
|
|
|
def defaults_str
|
|
"--version '> 0'"
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} GEMNAME"
|
|
end
|
|
|
|
def arguments
|
|
"GEMNAME Name of the gem to unpack"
|
|
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
|
|
require 'fileutils'
|
|
target_dir = File.basename(path).sub(/\.gem$/, '')
|
|
FileUtils.mkdir_p target_dir
|
|
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 = SourceIndex.from_installed_gems.search(gemname, 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'
|
|
return File.join(Gem.dir, 'cache', filename)
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class HelpCommand < Command
|
|
include CommandAids
|
|
|
|
def initialize
|
|
super('help', "Provide help on the 'gem' command")
|
|
end
|
|
|
|
def usage
|
|
"#{program_name} ARGUMENT"
|
|
end
|
|
|
|
def arguments
|
|
args = <<-EOF
|
|
commands List all 'gem' commands
|
|
examples Show examples of 'gem' usage
|
|
<command> Show specific help for <command>
|
|
EOF
|
|
return args.gsub(/^\s+/, '')
|
|
end
|
|
|
|
def execute
|
|
arg = options[:args][0]
|
|
if begins?("commands", arg)
|
|
require 'stringio'
|
|
out = StringIO.new
|
|
out.puts "\nGEM commands are:\n\n"
|
|
desc_indent = command_manager.command_names.collect {|n| n.size}.max + 4
|
|
format = " %-#{desc_indent}s %s\n"
|
|
command_manager.command_names.each do |cmd_name|
|
|
out.printf format, "#{cmd_name}", command_manager[cmd_name].summary
|
|
end
|
|
out.puts "\nFor help on a particular command, use 'gem help COMMAND'."
|
|
out.puts "\nCommands may be abbreviated, so long as they are unambiguous."
|
|
out.puts "e.g. 'gem i rake' is short for 'gem install rake'."
|
|
say out.string
|
|
elsif begins?("options", arg)
|
|
say Gem::HELP
|
|
elsif begins?("examples", arg)
|
|
say Gem::EXAMPLES
|
|
elsif options[:help]
|
|
command = command_manager[options[:help]]
|
|
if command
|
|
# help with provided command
|
|
command.invoke("--help")
|
|
else
|
|
alert_error "Unknown command #{options[:help]}. Try 'gem help commands'"
|
|
end
|
|
elsif arg
|
|
possibilities = command_manager.find_command_possibilities(arg.downcase)
|
|
if possibilities.size == 1
|
|
command = command_manager[possibilities.first]
|
|
command.invoke("--help")
|
|
elsif possibilities.size > 1
|
|
alert_warning "Ambiguous command #{arg} (#{possibilities.join(', ')})"
|
|
else
|
|
alert_warning "Unknown command #{arg}. Try gem help commands"
|
|
end
|
|
else
|
|
say Gem::HELP
|
|
end
|
|
end
|
|
end
|
|
|
|
####################################################################
|
|
class ContentsCommand < Command
|
|
include CommandAids
|
|
def initialize
|
|
super('contents','Display the contents of the installed gems', {:list => true, :specdirs => [] })
|
|
add_option("-l","--list",'List the files inside a Gem') do |v,o|
|
|
o[:list] = true
|
|
end
|
|
|
|
add_option("-V","--version","Specify version for gem to view") do |v,o|
|
|
o[:version] = v
|
|
end
|
|
|
|
add_option('-s','--spec-dir a,b,c', Array, "Search for gems under specific paths") do |v,o|
|
|
o[:specdirs] = v
|
|
end
|
|
|
|
add_option('-v','--verbose','Be verbose when showing status') do |v,o|
|
|
o[:verbose] = v
|
|
end
|
|
end
|
|
|
|
def execute(io=STDOUT)
|
|
if options[:list]
|
|
version = options[:version] || "> 0.0.0"
|
|
gem = get_one_gem_name
|
|
|
|
s = options[:specdirs].map do |i|
|
|
[i, File.join(i,"specifications")]
|
|
end.flatten
|
|
|
|
if s.empty?
|
|
path_kind = "default gem paths"
|
|
system = true
|
|
else
|
|
path_kind = "specified path"
|
|
system = false
|
|
end
|
|
|
|
si = Gem::SourceIndex.from_gems_in(*s)
|
|
|
|
gem_spec = si.search(gem, version).first
|
|
unless gem_spec
|
|
io.puts "Unable to find gem '#{gem}' in #{path_kind}"
|
|
if options[:verbose]
|
|
io.puts "\nDirectories searched:"
|
|
if system
|
|
Gem.path.each do |p|
|
|
io.puts p
|
|
end
|
|
else
|
|
s.each do |p|
|
|
io.puts p
|
|
end
|
|
end
|
|
end
|
|
return
|
|
end
|
|
# show the list of files.
|
|
gem_spec.files.each do |f|
|
|
io.puts File.join(gem_spec.full_gem_path, f)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
end # module
|
|
|
|
######################################################################
|
|
# Documentation Constants
|
|
#
|
|
module Gem
|
|
|
|
HELP = %{
|
|
RubyGems is a sophisticated package manager for Ruby. This is a
|
|
basic help message containing pointers to more information.
|
|
|
|
Usage:
|
|
gem -h/--help
|
|
gem -v/--version
|
|
gem command [arguments...] [options...]
|
|
|
|
Examples:
|
|
gem install rake
|
|
gem list --local
|
|
gem build package.gemspec
|
|
gem help install
|
|
|
|
Further help:
|
|
gem help commands list all 'gem' commands
|
|
gem help examples show some examples of usage
|
|
gem help <COMMAND> show help on COMMAND
|
|
(e.g. 'gem help install')
|
|
Further information:
|
|
http://rubygems.rubyforge.org
|
|
}.gsub(/^ /, "")
|
|
|
|
EXAMPLES = %{
|
|
Some examples of 'gem' usage.
|
|
|
|
* Install 'rake', either from local directory or remote server:
|
|
|
|
gem install rake
|
|
|
|
* Install 'rake', only from remote server:
|
|
|
|
gem install rake --remote
|
|
|
|
* Install 'rake' from remote server, and run unit tests,
|
|
and generate RDocs:
|
|
|
|
gem install --remote rake --test --rdoc --ri
|
|
|
|
* Install 'rake', but only version 0.3.1, even if dependencies
|
|
are not met, and into a specific directory:
|
|
|
|
gem install rake --version 0.3.1 --force --install-dir $HOME/.gems
|
|
|
|
* List local gems whose name begins with 'D':
|
|
|
|
gem list D
|
|
|
|
* List local and remote gems whose name contains 'log':
|
|
|
|
gem search log --both
|
|
|
|
* List only remote gems whose name contains 'log':
|
|
|
|
gem search log --remote
|
|
|
|
* Uninstall 'rake':
|
|
|
|
gem uninstall rake
|
|
|
|
* Create a gem:
|
|
|
|
See http://rubygems.rubyforge.org/wiki/wiki.pl?CreateAGemInTenMinutes
|
|
|
|
* See information about RubyGems:
|
|
|
|
gem environment
|
|
|
|
}.gsub(/^ /, "")
|
|
|
|
end
|