diff options
Diffstat (limited to 'lib/rubygems/commands')
26 files changed, 2265 insertions, 0 deletions
diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb new file mode 100644 index 0000000000..c2e1abc92f --- /dev/null +++ b/lib/rubygems/commands/build_command.rb @@ -0,0 +1,53 @@ +require 'rubygems/command' +require 'rubygems/builder' + +class Gem::Commands::BuildCommand < Gem::Command + + def initialize + super('build', 'Build a gem from a gemspec') + end + + def arguments # :nodoc: + "GEMSPEC_FILE gemspec file name to build a gem for" + end + + def usage # :nodoc: + "#{program_name} GEMSPEC_FILE" + 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 + else + alert_error "Gemspec file not found: #{gemspec}" + end + end + + def load_gemspecs(filename) + if yaml?(filename) + result = [] + open(filename) do |f| + begin + while not f.eof? and spec = Gem::Specification.from_yaml(f) + result << spec + end + rescue Gem::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 diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb new file mode 100644 index 0000000000..2c32099254 --- /dev/null +++ b/lib/rubygems/commands/cert_command.rb @@ -0,0 +1,86 @@ +require 'rubygems/command' +require 'rubygems/security' + +class Gem::Commands::CertCommand < Gem::Command + + def initialize + super 'cert', 'Manage RubyGems certificates and signing 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) + say "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| + begin + cert = OpenSSL::X509::Certificate.new(File.read(path)) + # this could proably be formatted more gracefully + say cert.subject.to_s + rescue OpenSSL::X509::CertificateError + next + end + 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| + begin + cert = OpenSSL::X509::Certificate.new(File.read(path)) + if cert.subject.to_s.downcase.index(value) + say "Removed '#{cert.subject.to_s}'" + File.unlink(path) + end + rescue OpenSSL::X509::CertificateError + next + 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] + say "Public Cert: #{vals[:cert_path]}" + say "Private Key: #{vals[:key_path]}" + say "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 + diff --git a/lib/rubygems/commands/check_command.rb b/lib/rubygems/commands/check_command.rb new file mode 100644 index 0000000000..ca5e14b12d --- /dev/null +++ b/lib/rubygems/commands/check_command.rb @@ -0,0 +1,74 @@ +require 'rubygems/command' +require 'rubygems/version_option' +require 'rubygems/validator' + +class Gem::Commands::CheckCommand < Gem::Command + + include Gem::VersionOption + + def initialize + super 'check', 'Check installed gems', + :verify => false, :alien => false + + add_option( '--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_version_option 'run tests for' + end + + def execute + if options[:test] + version = options[:version] || Gem::Requirement.default + 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 diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb new file mode 100644 index 0000000000..f6deac9829 --- /dev/null +++ b/lib/rubygems/commands/cleanup_command.rb @@ -0,0 +1,93 @@ +require 'rubygems/command' +require 'rubygems/source_index' +require 'rubygems/dependency_list' + +module Gem + module Commands + class CleanupCommand < Command + def initialize + super( + 'cleanup', + 'Clean up 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 arguments # :nodoc: + "GEMNAME name of gem to cleanup" + end + + def defaults_str # :nodoc: + "--no-dryrun" + end + + def usage # :nodoc: + "#{program_name} [GEMNAME ...]" + 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 = [] + + unless options[:args].empty? then + 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 = Gem::CommandManager.instance['uninstall'] + deplist = DependencyList.new + gems_to_cleanup.uniq.each do |spec| deplist.add(spec) end + + deplist.dependency_order.each do |spec| + if options[:dryrun] then + 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 + + end +end diff --git a/lib/rubygems/commands/contents_command.rb b/lib/rubygems/commands/contents_command.rb new file mode 100644 index 0000000000..5060403fd8 --- /dev/null +++ b/lib/rubygems/commands/contents_command.rb @@ -0,0 +1,74 @@ +require 'rubygems/command' +require 'rubygems/version_option' + +class Gem::Commands::ContentsCommand < Gem::Command + + include Gem::VersionOption + + def initialize + super 'contents', 'Display the contents of the installed gems', + :specdirs => [], :lib_only => false + + add_version_option + + add_option('-s', '--spec-dir a,b,c', Array, + "Search for gems under specific paths") do |spec_dirs, options| + options[:specdirs] = spec_dirs + end + + add_option('-l', '--[no-]lib-only', + "Only return files in the Gem's lib_dirs") do |lib_only, options| + options[:lib_only] = lib_only + end + end + + def arguments # :nodoc: + "GEMNAME name of gem to list contents for" + end + + def defaults_str # :nodoc: + "--no-lib-only" + end + + def usage # :nodoc: + "#{program_name} GEMNAME" + end + + def execute + version = options[:version] || Gem::Requirement.default + gem = get_one_gem_name + + s = options[:specdirs].map do |i| + [i, File.join(i, "specifications")] + end.flatten + + path_kind = if s.empty? then + s = Gem::SourceIndex.installed_spec_directories + "default gem paths" + else + "specified path" + end + + si = Gem::SourceIndex.from_gems_in(*s) + + gem_spec = si.search(/\A#{gem}\z/, version).last + + unless gem_spec then + say "Unable to find gem '#{gem}' in #{path_kind}" + + if Gem.configuration.verbose then + say "\nDirectories searched:" + s.each { |dir| say dir } + end + + terminate_interaction + end + + files = options[:lib_only] ? gem_spec.lib_files : gem_spec.files + files.each do |f| + say File.join(gem_spec.full_gem_path, f) + end + end + +end + diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb new file mode 100644 index 0000000000..1a43505d7c --- /dev/null +++ b/lib/rubygems/commands/dependency_command.rb @@ -0,0 +1,150 @@ +require 'rubygems/command' +require 'rubygems/local_remote_options' +require 'rubygems/version_option' +require 'rubygems/source_info_cache' + +class Gem::Commands::DependencyCommand < Gem::Command + + include Gem::LocalRemoteOptions + include Gem::VersionOption + + def initialize + super 'dependency', + 'Show the dependencies of an installed gem', + :version => Gem::Requirement.default, :domain => :local + + add_version_option + add_platform_option + + 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 + + add_local_remote_options + end + + def arguments # :nodoc: + "GEMNAME name of gem to show dependencies for" + end + + def defaults_str # :nodoc: + "--local --version '#{Gem::Requirement.default}' --no-reverse-dependencies" + end + + def usage # :nodoc: + "#{program_name} GEMNAME" + end + + def execute + options[:args] << '.' if options[:args].empty? + specs = {} + + source_indexes = [] + + if local? then + source_indexes << Gem::SourceIndex.from_installed_gems + end + + if remote? then + Gem::SourceInfoCache.cache_data.map do |_, sice| + source_indexes << sice.source_index + end + end + + options[:args].each do |name| + new_specs = nil + source_indexes.each do |source_index| + new_specs = find_gems(name, source_index) + end + + say "No match found for #{name} (#{options[:version]})" if + new_specs.empty? + + specs = specs.merge new_specs + end + + terminate_interaction 1 if specs.empty? + + reverse = Hash.new { |h, k| h[k] = [] } + + if options[:reverse_dependencies] then + specs.values.each do |source_index, spec| + reverse[spec.full_name] = find_reverse_dependencies spec, source_index + end + end + + if options[:pipe_format] then + specs.values.sort_by { |_, spec| spec }.each do |_, spec| + unless spec.dependencies.empty? + spec.dependencies.each do |dep| + say "#{dep.name} --version '#{dep.version_requirements}'" + end + end + end + else + response = '' + + specs.values.sort_by { |_, spec| spec }.each do |_, spec| + response << print_dependencies(spec) + unless reverse[spec.full_name].empty? then + 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? then + 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, source_index) + result = [] + + source_index.each do |name, sp| + sp.dependencies.each do |dep| + dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep + + if spec.name == dep.name and + dep.version_requirements.satisfied_by?(spec.version) then + result << [sp.full_name, dep] + end + end + end + + result + end + + def find_gems(name, source_index) + specs = {} + + spec_list = source_index.search name, options[:version] + + spec_list.each do |spec| + specs[spec.full_name] = [source_index, spec] + end + + specs + end +end + diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb new file mode 100644 index 0000000000..337d74893b --- /dev/null +++ b/lib/rubygems/commands/environment_command.rb @@ -0,0 +1,80 @@ +require 'rubygems/command' + +class Gem::Commands::EnvironmentCommand < Gem::Command + + def initialize + super 'environment', 'Display information about the RubyGems environment' + end + + def arguments # :nodoc: + 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 usage # :nodoc: + "#{program_name} [arg]" + end + + def execute + out = '' + arg = options[:args][0] + if begins?("packageversion", arg) then + out << Gem::RubyGemsPackageVersion + elsif begins?("version", arg) then + out << Gem::RubyGemsVersion + elsif begins?("gemdir", arg) then + out << Gem.dir + elsif begins?("gempath", arg) then + out << Gem.path.join("\n") + elsif begins?("remotesources", arg) then + out << Gem.sources.join("\n") + elsif arg then + fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]" + else + out = "RubyGems Environment:\n" + + out << " - RUBYGEMS VERSION: #{Gem::RubyGemsVersion} (#{Gem::RubyGemsPackageVersion})\n" + + out << " - RUBY VERSION: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}" + out << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL + out << ") [#{RUBY_PLATFORM}]\n" + + out << " - INSTALLATION DIRECTORY: #{Gem.dir}\n" + + out << " - RUBYGEMS PREFIX: #{Gem.prefix}\n" unless Gem.prefix.nil? + + out << " - RUBY EXECUTABLE: #{Gem.ruby}\n" + + out << " - RUBYGEMS PLATFORMS:\n" + Gem.platforms.each do |platform| + out << " - #{platform}\n" + end + + out << " - GEM PATHS:\n" + Gem.path.each do |p| + out << " - #{p}\n" + end + + out << " - GEM CONFIGURATION:\n" + Gem.configuration.each do |name, value| + out << " - #{name.inspect} => #{value.inspect}\n" + end + + out << " - REMOTE SOURCES:\n" + Gem.sources.each do |s| + out << " - #{s}\n" + end + end + say out + true + end + +end + diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb new file mode 100644 index 0000000000..7db365eba0 --- /dev/null +++ b/lib/rubygems/commands/fetch_command.rb @@ -0,0 +1,62 @@ +require 'rubygems/command' +require 'rubygems/local_remote_options' +require 'rubygems/version_option' +require 'rubygems/source_info_cache' + +class Gem::Commands::FetchCommand < Gem::Command + + include Gem::LocalRemoteOptions + include Gem::VersionOption + + def initialize + super 'fetch', 'Download a gem and place it in the current directory' + + add_bulk_threshold_option + add_proxy_option + add_source_option + + add_version_option + add_platform_option + end + + def arguments # :nodoc: + 'GEMNAME name of gem to download' + end + + def defaults_str # :nodoc: + "--version '#{Gem::Requirement.default}'" + end + + def usage # :nodoc: + "#{program_name} GEMNAME [GEMNAME ...]" + end + + def execute + version = options[:version] || Gem::Requirement.default + + gem_names = get_all_gem_names + + gem_names.each do |gem_name| + dep = Gem::Dependency.new gem_name, version + specs_and_sources = Gem::SourceInfoCache.search_with_source dep, true + + specs_and_sources.sort_by { |spec,| spec.version } + + spec, source_uri = specs_and_sources.last + + gem_file = "#{spec.full_name}.gem" + + gem_path = File.join source_uri, 'gems', gem_file + + gem = Gem::RemoteFetcher.fetcher.fetch_path gem_path + + File.open gem_file, 'wb' do |fp| + fp.write gem + end + + say "Downloaded #{gem_file}" + end + end + +end + diff --git a/lib/rubygems/commands/generate_index_command.rb b/lib/rubygems/commands/generate_index_command.rb new file mode 100644 index 0000000000..1bd87569ed --- /dev/null +++ b/lib/rubygems/commands/generate_index_command.rb @@ -0,0 +1,57 @@ +require 'rubygems/command' +require 'rubygems/indexer' + +class Gem::Commands::GenerateIndexCommand < Gem::Command + + def initialize + super 'generate_index', + 'Generates the index files for a gem server directory', + :directory => '.' + + add_option '-d', '--directory=DIRNAME', + 'repository base dir containing gems subdir' do |dir, options| + options[:directory] = File.expand_path dir + end + end + + def defaults_str # :nodoc: + "--directory ." + end + + def description # :nodoc: + <<-EOF +The generate_index command creates a set of indexes for serving gems +statically. The command expects a 'gems' directory under the path given to +the --directory option. When done, it will generate a set of files like this: + + gems/ # .gem files you want to index + quick/index + quick/index.rz # quick index manifest + quick/<gemname>.gemspec.rz # legacy YAML quick index file + quick/Marshal.<version>/<gemname>.gemspec.rz # Marshal quick index file + Marshal.<version> + Marshal.<version>.Z # Marshal full index + yaml + yaml.Z # legacy YAML full index + +The .Z and .rz extension files are compressed with the inflate algorithm. The +Marshal version number comes from ruby's Marshal::MAJOR_VERSION and +Marshal::MINOR_VERSION constants. It is used to ensure compatibility. The +yaml indexes exist for legacy RubyGems clients and fallback in case of Marshal +version changes. + EOF + end + + def execute + if not File.exist?(options[:directory]) or + not File.directory?(options[:directory]) then + alert_error "unknown directory name #{directory}." + terminate_interaction 1 + else + indexer = Gem::Indexer.new options[:directory] + indexer.generate_index + end + end + +end + diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb new file mode 100644 index 0000000000..05ea3f7a71 --- /dev/null +++ b/lib/rubygems/commands/help_command.rb @@ -0,0 +1,172 @@ +require 'rubygems/command' + +class Gem::Commands::HelpCommand < Gem::Command + + # :stopdoc: + EXAMPLES = <<-EOF +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 + +* Update all gems on your system: + + gem update + EOF + + PLATFORMS = <<-'EOF' +RubyGems platforms are composed of three parts, a CPU, an OS, and a +version. These values are taken from values in rbconfig.rb. You can view +your current platform by running `gem environment`. + +RubyGems matches platforms as follows: + + * The CPU must match exactly, unless one of the platforms has + "universal" as the CPU. + * The OS must match exactly. + * The versions must match exactly unless one of the versions is nil. + +For commands that install, uninstall and list gems, you can override what +RubyGems thinks your platform is with the --platform option. The platform +you pass must match "#{cpu}-#{os}" or "#{cpu}-#{os}-#{version}". On mswin +platforms, the version is the compiler version, not the OS version. (Ruby +compiled with VC6 uses "60" as the compiler version, VC8 uses "80".) + +Example platforms: + + x86-freebsd # Any FreeBSD version on an x86 CPU + universal-darwin-8 # Darwin 8 only gems that run on any CPU + x86-mswin32-80 # Windows gems compiled with VC8 + +When building platform gems, set the platform in the gem specification to +Gem::Platform::CURRENT. This will correctly mark the gem with your ruby's +platform. + EOF + # :startdoc: + + def initialize + super 'help', "Provide help on the 'gem' command" + end + + def arguments # :nodoc: + 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 usage # :nodoc: + "#{program_name} ARGUMENT" + end + + def execute + command_manager = Gem::CommandManager.instance + arg = options[:args][0] + + if begins? "commands", arg then + out = [] + out << "GEM commands are:" + out << nil + + margin_width = 4 + + desc_width = command_manager.command_names.map { |n| n.size }.max + 4 + + summary_width = 80 - margin_width - desc_width + wrap_indent = ' ' * (margin_width + desc_width) + format = "#{' ' * margin_width}%-#{desc_width}s%s" + + command_manager.command_names.each do |cmd_name| + summary = command_manager[cmd_name].summary + summary = wrap(summary, summary_width).split "\n" + out << sprintf(format, cmd_name, summary.shift) + until summary.empty? do + out << "#{wrap_indent}#{summary.shift}" + end + end + + out << nil + out << "For help on a particular command, use 'gem help COMMAND'." + out << nil + out << "Commands may be abbreviated, so long as they are unambiguous." + out << "e.g. 'gem i rake' is short for 'gem install rake'." + + say out.join("\n") + + elsif begins? "options", arg then + say Gem::Command::HELP + + elsif begins? "examples", arg then + say EXAMPLES + + elsif begins? "platforms", arg then + say PLATFORMS + + elsif options[:help] then + 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 then + 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::Command::HELP + end + end + +end + diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb new file mode 100644 index 0000000000..4c67c0487b --- /dev/null +++ b/lib/rubygems/commands/install_command.rb @@ -0,0 +1,125 @@ +require 'rubygems/command' +require 'rubygems/doc_manager' +require 'rubygems/install_update_options' +require 'rubygems/dependency_installer' +require 'rubygems/local_remote_options' +require 'rubygems/validator' +require 'rubygems/version_option' + +class Gem::Commands::InstallCommand < Gem::Command + + include Gem::VersionOption + include Gem::LocalRemoteOptions + include Gem::InstallUpdateOptions + + def initialize + defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({ + :generate_rdoc => true, + :generate_ri => true, + :install_dir => Gem.dir, + :test => false, + :version => Gem::Requirement.default, + }) + + super 'install', 'Install a gem into the local repository', defaults + + add_install_update_options + add_local_remote_options + add_platform_option + add_version_option + end + + def arguments # :nodoc: + "GEMNAME name of gem to install" + end + + def defaults_str # :nodoc: + "--both --version '#{Gem::Requirement.default}' --rdoc --ri --no-force\n" \ + "--no-test --install-dir #{Gem.dir}" + end + + def usage # :nodoc: + "#{program_name} GEMNAME [GEMNAME ...] [options] -- --build-flags" + end + + def execute + if options[:include_dependencies] then + alert "`gem install -y` is now default and will be removed" + alert "use --ignore-dependencies to install only the gems you list" + end + + installed_gems = [] + + ENV['GEM_PATH'] = options[:install_dir] # HACK what does this do? + + install_options = { + :env_shebang => options[:env_shebang], + :domain => options[:domain], + :force => options[:force], + :ignore_dependencies => options[:ignore_dependencies], + :install_dir => options[:install_dir], + :security_policy => options[:security_policy], + :wrappers => options[:wrappers], + } + + get_all_gem_names.each do |gem_name| + begin + inst = Gem::DependencyInstaller.new gem_name, options[:version], + install_options + inst.install + + inst.installed_gems.each do |spec| + say "Successfully installed #{spec.full_name}" + end + + installed_gems.push(*inst.installed_gems) + rescue Gem::InstallError => e + alert_error "Error installing #{gem_name}:\n\t#{e.message}" + rescue Gem::GemNotFoundException => e + alert_error 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}: #{e.message}" +# return + end + end + + unless installed_gems.empty? then + gems = installed_gems.length == 1 ? 'gem' : 'gems' + say "#{installed_gems.length} #{gems} installed" + 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] then + installed_gems.each do |gem| + Gem::DocManager.new(gem, options[:rdoc_args]).generate_ri + end + end + + if options[:generate_rdoc] then + installed_gems.each do |gem| + Gem::DocManager.new(gem, options[:rdoc_args]).generate_rdoc + end + end + + if options[:test] then + 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) + if result and not result.passed? + unless ask_yes_no("...keep Gem?", true) then + Gem::Uninstaller.new(spec.name, :version => spec.version.version).uninstall + end + end + end + end + end + +end + diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb new file mode 100644 index 0000000000..e179ff57ee --- /dev/null +++ b/lib/rubygems/commands/list_command.rb @@ -0,0 +1,35 @@ +require 'rubygems/command' +require 'rubygems/commands/query_command' + +module Gem + module Commands + class ListCommand < QueryCommand + + def initialize + super( + 'list', + 'Display all gems whose name starts with STRING' + ) + remove_option('--name-matches') + end + + def arguments # :nodoc: + "STRING start of gem name to look for" + end + + def defaults_str # :nodoc: + "--local --no-details" + end + + def usage # :nodoc: + "#{program_name} [STRING]" + end + + def execute + string = get_one_optional_argument || '' + options[:name] = /^#{string}/i + super + end + end + end +end diff --git a/lib/rubygems/commands/lock_command.rb b/lib/rubygems/commands/lock_command.rb new file mode 100644 index 0000000000..3a3dcc0c6b --- /dev/null +++ b/lib/rubygems/commands/lock_command.rb @@ -0,0 +1,101 @@ +require 'rubygems/command' + +class Gem::Commands::LockCommand < Gem::Command + + def initialize + super 'lock', 'Generate a lockdown list of gems', + :strict => false + + add_option '-s', '--[no-]strict', + 'fail if unable to satisfy a dependency' do |strict, options| + options[:strict] = strict + end + end + + def arguments # :nodoc: + "GEMNAME name of gem to lock\nVERSION version of gem to lock" + end + + def defaults_str # :nodoc: + "--no-strict" + end + + def description # :nodoc: + <<-EOF +The lock command will generate a list of +gem+ statements that will lock down +the versions for the gem given in the command line. It will specify exact +versions in the requirements list to ensure that the gems loaded will always +be consistent. A full recursive search of all effected gems will be +generated. + +Example: + + gemlock rails-1.0.0 > lockdown.rb + +will produce in lockdown.rb: + + require "rubygems" + gem 'rails', '= 1.0.0' + gem 'rake', '= 0.7.0.1' + gem 'activesupport', '= 1.2.5' + gem 'activerecord', '= 1.13.2' + gem 'actionpack', '= 1.11.2' + gem 'actionmailer', '= 1.1.5' + gem 'actionwebservice', '= 1.0.0' + +Just load lockdown.rb from your application to ensure that the current +versions are loaded. Make sure that lockdown.rb is loaded *before* any +other require statements. + +Notice that rails 1.0.0 only requires that rake 0.6.2 or better be used. +Rake-0.7.0.1 is the most recent version installed that satisfies that, so we +lock it down to the exact version. + EOF + end + + def usage # :nodoc: + "#{program_name} GEMNAME-VERSION [GEMNAME-VERSION ...]" + end + + def complain(message) + if options.strict then + raise message + else + say "# #{message}" + end + end + + def execute + say 'require "rubygems"' + + locked = {} + + pending = options[:args] + + until pending.empty? do + full_name = pending.shift + + spec = Gem::SourceIndex.load_specification spec_path(full_name) + + say "gem '#{spec.name}', '= #{spec.version}'" unless locked[spec.name] + locked[spec.name] = true + + spec.dependencies.each do |dep| + next if locked[dep.name] + candidates = Gem.source_index.search dep.name, dep.requirement_list + + if candidates.empty? then + complain "Unable to satisfy '#{dep}' from currently installed gems." + else + pending << candidates.last.full_name + end + end + end + end + + def spec_path(gem_full_name) + File.join Gem.path, "specifications", "#{gem_full_name }.gemspec" + end + +end + diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb new file mode 100644 index 0000000000..74f6970e9e --- /dev/null +++ b/lib/rubygems/commands/mirror_command.rb @@ -0,0 +1,105 @@ +require 'yaml' +require 'zlib' + +require 'rubygems/command' +require 'rubygems/gem_open_uri' + +class Gem::Commands::MirrorCommand < Gem::Command + + def initialize + super 'mirror', 'Mirror a gem repository' + end + + def description # :nodoc: + <<-EOF +The mirror command uses the ~/.gemmirrorrc config file to mirror remote gem +repositories to a local path. The config file is a YAML document that looks +like this: + + --- + - from: http://gems.example.com # source repository URI + to: /path/to/mirror # destination directory + +Multiple sources and destinations may be specified. + EOF + end + + def execute + config_file = File.join Gem.user_home, '.gemmirrorrc' + + raise "Config file #{config_file} not found" unless File.exist? config_file + + mirrors = YAML.load_file config_file + + raise "Invalid config file #{config_file}" unless mirrors.respond_to? :each + + mirrors.each do |mir| + raise "mirror missing 'from' field" unless mir.has_key? 'from' + raise "mirror missing 'to' field" unless mir.has_key? 'to' + + get_from = mir['from'] + save_to = File.expand_path mir['to'] + + raise "Directory not found: #{save_to}" unless File.exist? save_to + raise "Not a directory: #{save_to}" unless File.directory? save_to + + gems_dir = File.join save_to, "gems" + + if File.exist? gems_dir then + raise "Not a directory: #{gems_dir}" unless File.directory? gems_dir + else + Dir.mkdir gems_dir + end + + sourceindex_data = '' + + say "fetching: #{get_from}/Marshal.#{Gem.marshal_version}.Z" + + get_from = URI.parse get_from + + if get_from.scheme.nil? then + get_from = get_from.to_s + elsif get_from.scheme == 'file' then + get_from = get_from.to_s[5..-1] + end + + open File.join(get_from, "Marshal.#{Gem.marshal_version}.Z"), "rb" do |y| + sourceindex_data = Zlib::Inflate.inflate y.read + open File.join(save_to, "Marshal.#{Gem.marshal_version}"), "wb" do |out| + out.write sourceindex_data + end + end + + sourceindex = Marshal.load(sourceindex_data) + + progress = ui.progress_reporter sourceindex.size, + "Fetching #{sourceindex.size} gems" + sourceindex.each do |fullname, gem| + gem_file = "#{fullname}.gem" + gem_dest = File.join gems_dir, gem_file + + unless File.exist? gem_dest then + begin + open "#{get_from}/gems/#{gem_file}", "rb" do |g| + contents = g.read + open gem_dest, "wb" do |out| + out.write contents + end + end + rescue + old_gf = gem_file + gem_file = gem_file.downcase + retry if old_gf != gem_file + alert_error $! + end + end + + progress.updated gem_file + end + + progress.done + end + end + +end + diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb new file mode 100644 index 0000000000..9c0062019b --- /dev/null +++ b/lib/rubygems/commands/outdated_command.rb @@ -0,0 +1,30 @@ +require 'rubygems/command' +require 'rubygems/local_remote_options' +require 'rubygems/source_info_cache' +require 'rubygems/version_option' + +class Gem::Commands::OutdatedCommand < Gem::Command + + include Gem::LocalRemoteOptions + include Gem::VersionOption + + def initialize + super 'outdated', 'Display all gems that need updates' + + add_local_remote_options + add_platform_option + end + + def execute + locals = Gem::SourceIndex.from_installed_gems + + locals.outdated.sort.each do |name| + local = locals.search(/^#{name}$/).last + remotes = Gem::SourceInfoCache.search_with_source(/^#{name}$/, true) + remote = remotes.last.first + say "#{local.name} (#{local.version} < #{remote.version})" + end + end + +end + diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb new file mode 100644 index 0000000000..2900e7e739 --- /dev/null +++ b/lib/rubygems/commands/pristine_command.rb @@ -0,0 +1,133 @@ +require 'fileutils' +require 'rubygems/command' +require 'rubygems/format' +require 'rubygems/installer' +require 'rubygems/version_option' + +class Gem::Commands::PristineCommand < Gem::Command + + include Gem::VersionOption + + def initialize + super 'pristine', + 'Restores installed gems to pristine condition from files located in the gem cache', + :version => Gem::Requirement.default + + add_option('--all', + 'Restore all installed gems to pristine', + 'condition') do |value, options| + options[:all] = value + end + + add_version_option('restore to', 'pristine condition') + end + + def arguments # :nodoc: + "GEMNAME gem to restore to pristine condition (unless --all)" + end + + def defaults_str # :nodoc: + "--all" + end + + def description # :nodoc: + <<-EOF +The pristine command compares the installed gems with the contents of the +cached gem and restores any files that don't match the cached gem's copy. + +If you have made modifications to your installed gems, the pristine command +will revert them. After all the gem's files have been checked all bin stubs +for the gem are regenerated. + +If the cached gem cannot be found, you will need to use `gem install` to +revert the gem. + EOF + end + + def usage # :nodoc: + "#{program_name} [args]" + end + + def execute + gem_name = nil + + specs = if options[:all] then + Gem::SourceIndex.from_installed_gems.map do |name, spec| + spec + end + else + gem_name = get_one_gem_name + Gem::SourceIndex.from_installed_gems.search(gem_name, + options[:version]) + end + + if specs.empty? then + raise Gem::Exception, + "Failed to find gem #{gem_name} #{options[:version]}" + end + + install_dir = Gem.dir # TODO use installer option + + raise Gem::FilePermissionError.new(install_dir) unless + File.writable?(install_dir) + + say "Restoring gem(s) to pristine condition..." + + specs.each do |spec| + gem = Dir[File.join(Gem.dir, 'cache', "#{spec.full_name}.gem")].first + + if gem.nil? then + alert_error "Cached gem for #{spec.full_name} not found, use `gem install` to restore" + next + end + + # TODO use installer options + installer = Gem::Installer.new gem, :wrappers => true + + gem_file = File.join install_dir, "cache", "#{spec.full_name}.gem" + + security_policy = nil # TODO use installer option + + format = Gem::Format.from_file_by_path gem_file, security_policy + + target_directory = File.join(install_dir, "gems", format.spec.full_name) + target_directory.untaint + + pristine_files = format.file_entries.collect { |data| data[0]["path"] } + file_map = {} + + format.file_entries.each do |entry, file_data| + file_map[entry["path"]] = file_data + end + + Dir.chdir target_directory do + deployed_files = Dir.glob(File.join("**", "*")) + + Dir.glob(File.join("**", ".*")) + + pristine_files = pristine_files.map { |f| File.expand_path f } + deployed_files = deployed_files.map { |f| File.expand_path f } + + to_redeploy = (pristine_files - deployed_files) + to_redeploy = to_redeploy.map { |path| path.untaint} + + if to_redeploy.length > 0 then + say "Restoring #{to_redeploy.length} file#{to_redeploy.length == 1 ? "" : "s"} to #{spec.full_name}..." + + to_redeploy.each do |path| + say " #{path}" + FileUtils.mkdir_p File.dirname(path) + File.open(path, "wb") do |out| + out.write file_map[path] + end + end + else + say "#{spec.full_name} is in pristine condition" + end + end + + installer.generate_bin + end + end + +end + diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb new file mode 100644 index 0000000000..581d4bb734 --- /dev/null +++ b/lib/rubygems/commands/query_command.rb @@ -0,0 +1,118 @@ +require 'rubygems/command' +require 'rubygems/local_remote_options' +require 'rubygems/source_info_cache' + +class Gem::Commands::QueryCommand < Gem::Command + + include Gem::LocalRemoteOptions + + def initialize(name = 'query', + summary = 'Query gem information in local or remote repositories') + super name, summary, + :name => /.*/, :domain => :local, :details => false, :versions => true + + 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_option( '--[no-]versions', + 'Display only gem names') do |value, options| + options[:versions] = value + options[:details] = false unless value + end + + add_local_remote_options + end + + def defaults_str # :nodoc: + "--local --name-matches '.*' --no-details --versions" + end + + def execute + name = options[:name] + + if local? then + say + say "*** LOCAL GEMS ***" + say + output_query_results Gem.cache.search(name) + end + + if remote? then + say + say "*** REMOTE GEMS ***" + say + output_query_results Gem::SourceInfoCache.search(name) + end + end + + private + + def output_query_results(gemspecs) + output = [] + 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_by do |name, spec| + name.downcase + end + + gem_list_with_version.each do |gem_name, list_of_matching| + list_of_matching = list_of_matching.sort_by { |x| x.version.to_ints }.reverse + seen_versions = {} + + list_of_matching.delete_if do |item| + if seen_versions[item.version] then + true + else + seen_versions[item.version] = true + false + end + end + + entry = gem_name.dup + if options[:versions] then + entry << " (#{list_of_matching.map{|gem| gem.version.to_s}.join(", ")})" + end + + entry << "\n" << format_text(list_of_matching[0].summary, 68, 4) if + options[:details] + output << entry + end + + say output.join(options[:details] ? "\n\n" : "\n") + end + + ## + # Used for wrapping and indenting text + # + def format_text(text, wrap, indent=0) + result = [] + work = text.dup + + while work.length > wrap + if work =~ /^(.{0,#{wrap}})[ \n]/o then + 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 + diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb new file mode 100644 index 0000000000..f2e677c115 --- /dev/null +++ b/lib/rubygems/commands/rdoc_command.rb @@ -0,0 +1,78 @@ +require 'rubygems/command' +require 'rubygems/version_option' +require 'rubygems/doc_manager' + +module Gem + module Commands + class RdocCommand < Command + include VersionOption + + def initialize + super('rdoc', + 'Generates RDoc for pre-installed gems', + { + :version => Gem::Requirement.default, + :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 + end + + def arguments # :nodoc: + "GEMNAME gem to generate documentation for (unless --all)" + end + + def defaults_str # :nodoc: + "--version '#{Gem::Requirement.default}' --rdoc --ri" + end + + def usage # :nodoc: + "#{program_name} [args]" + 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 + + end +end diff --git a/lib/rubygems/commands/search_command.rb b/lib/rubygems/commands/search_command.rb new file mode 100644 index 0000000000..96da19c0f7 --- /dev/null +++ b/lib/rubygems/commands/search_command.rb @@ -0,0 +1,37 @@ +require 'rubygems/command' +require 'rubygems/commands/query_command' + +module Gem + module Commands + + class SearchCommand < QueryCommand + + def initialize + super( + 'search', + 'Display all gems whose name contains STRING' + ) + remove_option('--name-matches') + end + + def arguments # :nodoc: + "STRING fragment of gem name to search for" + end + + def defaults_str # :nodoc: + "--local --no-details" + end + + def usage # :nodoc: + "#{program_name} [STRING]" + end + + def execute + string = get_one_optional_argument + options[:name] = /#{string}/i + super + end + end + + end +end diff --git a/lib/rubygems/commands/server_command.rb b/lib/rubygems/commands/server_command.rb new file mode 100644 index 0000000000..34e5e46fec --- /dev/null +++ b/lib/rubygems/commands/server_command.rb @@ -0,0 +1,48 @@ +require 'rubygems/command' +require 'rubygems/server' + +class Gem::Commands::ServerCommand < Gem::Command + + def initialize + super 'server', 'Documentation and gem repository HTTP server', + :port => 8808, :gemdir => Gem.dir, :daemon => false + + add_option '-p', '--port=PORT', + 'port to listen on' do |port, options| + options[:port] = port + end + + add_option '-d', '--dir=GEMDIR', + 'directory from which to serve gems' do |gemdir, options| + options[:gemdir] = gemdir + end + + add_option '--[no]-daemon', 'run as a daemon' do |daemon, options| + options[:daemon] = daemon + end + end + + def defaults_str # :nodoc: + "--port 8808 --dir #{Gem.dir} --no-daemon" + end + + def description # :nodoc: + <<-EOF +The server command starts up a web server that hosts the RDoc for your +installed gems and can operate as a server for installation of gems on other +machines. + +The cache files for installed gems must exist to use the server as a source +for gem installation. + +To install gems from a running server, use `gem install GEMNAME --source +http://gem_server_host:8808` + EOF + end + + def execute + Gem::Server.run options + end + +end + diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb new file mode 100644 index 0000000000..def9c01a3f --- /dev/null +++ b/lib/rubygems/commands/sources_command.rb @@ -0,0 +1,115 @@ +require 'rubygems/command' +require 'rubygems/remote_fetcher' +require 'rubygems/source_info_cache' +require 'rubygems/source_info_cache_entry' + +class Gem::Commands::SourcesCommand < Gem::Command + + def initialize + super 'sources', + 'Manage the sources and cache file RubyGems uses to search for gems' + + add_option '-a', '--add SOURCE_URI', 'Add source' do |value, options| + options[:add] = value + end + + add_option '-l', '--list', 'List sources' do |value, options| + options[:list] = value + end + + add_option '-r', '--remove SOURCE_URI', 'Remove source' do |value, options| + options[:remove] = value + end + + add_option '-u', '--update', 'Update source cache' do |value, options| + options[:update] = value + end + + add_option '-c', '--clear-all', + 'Remove all sources (clear the cache)' do |value, options| + options[:clear_all] = value + end + end + + def defaults_str + '--list' + end + + def execute + options[:list] = !(options[:add] || options[:remove] || options[:clear_all] || options[:update]) + + if options[:clear_all] then + remove_cache_file("user", Gem::SourceInfoCache.user_cache_file) + remove_cache_file("system", Gem::SourceInfoCache.system_cache_file) + end + + if options[:add] then + source_uri = options[:add] + + sice = Gem::SourceInfoCacheEntry.new nil, nil + begin + sice.refresh source_uri + + Gem::SourceInfoCache.cache_data[source_uri] = sice + Gem::SourceInfoCache.cache.update + Gem::SourceInfoCache.cache.flush + + Gem.sources << source_uri + Gem.configuration.write + + say "#{source_uri} added to sources" + rescue URI::Error, ArgumentError + say "#{source_uri} is not a URI" + rescue Gem::RemoteFetcher::FetchError => e + say "Error fetching #{source_uri}:\n\t#{e.message}" + end + end + + if options[:update] then + Gem::SourceInfoCache.cache.refresh + Gem::SourceInfoCache.cache.flush + + say "source cache successfully updated" + end + + if options[:remove] then + source_uri = options[:remove] + + unless Gem.sources.include? source_uri then + say "source #{source_uri} not present in cache" + else + Gem::SourceInfoCache.cache_data.delete source_uri + Gem::SourceInfoCache.cache.update + Gem::SourceInfoCache.cache.flush + Gem.sources.delete source_uri + Gem.configuration.write + + say "#{source_uri} removed from sources" + end + end + + if options[:list] then + say "*** CURRENT SOURCES ***" + say + + Gem.sources.each do |source_uri| + say source_uri + end + end + end + + private + + def remove_cache_file(desc, fn) + FileUtils.rm_rf fn rescue nil + if ! File.exist?(fn) + say "*** Removed #{desc} source cache ***" + elsif ! File.writable?(fn) + say "*** Unable to remove #{desc} source cache (write protected) ***" + else + say "*** Unable to remove #{desc} source cache ***" + end + end + +end + diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb new file mode 100644 index 0000000000..954b38ac37 --- /dev/null +++ b/lib/rubygems/commands/specification_command.rb @@ -0,0 +1,72 @@ +require 'yaml' +require 'rubygems/command' +require 'rubygems/local_remote_options' +require 'rubygems/version_option' +require 'rubygems/source_info_cache' + +class Gem::Commands::SpecificationCommand < Gem::Command + + include Gem::LocalRemoteOptions + include Gem::VersionOption + + def initialize + super 'specification', 'Display gem specification (in yaml)', + :domain => :local, :version => Gem::Requirement.default + + add_version_option('examine') + add_platform_option + + add_option('--all', 'Output specifications for all versions of', + 'the gem') do |value, options| + options[:all] = true + end + + add_local_remote_options + end + + def arguments # :nodoc: + "GEMFILE name of gem to show the gemspec for" + end + + def defaults_str # :nodoc: + "--local --version '#{Gem::Requirement.default}'" + end + + def usage # :nodoc: + "#{program_name} [GEMFILE]" + end + + def execute + specs = [] + gem = get_one_gem_name + + if local? then + source_index = Gem::SourceIndex.from_installed_gems + specs.push(*source_index.search(/\A#{gem}\z/, options[:version])) + end + + if remote? then + alert_warning "Remote information is not complete\n\n" + + Gem::SourceInfoCache.cache_data.each do |_,sice| + specs.push(*sice.source_index.search(gem, options[:version])) + end + end + + if specs.empty? then + alert_error "Unknown gem '#{gem}'" + terminate_interaction 1 + end + + output = lambda { |spec| say spec.to_yaml; say "\n" } + + if options[:all] then + specs.each(&output) + else + spec = specs.sort_by { |spec| spec.version }.last + output[spec] + end + end + +end + diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb new file mode 100644 index 0000000000..7d2908836c --- /dev/null +++ b/lib/rubygems/commands/uninstall_command.rb @@ -0,0 +1,56 @@ +require 'rubygems/command' +require 'rubygems/version_option' +require 'rubygems/uninstaller' + +module Gem + module Commands + class UninstallCommand < Command + + include VersionOption + + def initialize + super 'uninstall', 'Uninstall gems from the local repository', + :version => Gem::Requirement.default + + 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 + add_platform_option + end + + def arguments # :nodoc: + "GEMNAME name of gem to uninstall" + end + + def defaults_str # :nodoc: + "--version '#{Gem::Requirement.default}' --no-force" + end + + def usage # :nodoc: + "#{program_name} GEMNAME [GEMNAME ...]" + end + + def execute + get_all_gem_names.each do |gem_name| + Gem::Uninstaller.new(gem_name, options).uninstall + end + end + end + end +end diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb new file mode 100644 index 0000000000..ece24745a2 --- /dev/null +++ b/lib/rubygems/commands/unpack_command.rb @@ -0,0 +1,76 @@ +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 + 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 + target_dir = File.basename(path).sub(/\.gem$/, '') + FileUtils.mkdir_p target_dir + Gem::Installer.new(path).unpack(File.expand_path(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::SourceIndex.from_installed_gems.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' + return File.join(Gem.dir, 'cache', filename) + else + return nil + end + end + +end + diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb new file mode 100644 index 0000000000..e17ba2516a --- /dev/null +++ b/lib/rubygems/commands/update_command.rb @@ -0,0 +1,149 @@ +require 'rubygems/command' +require 'rubygems/install_update_options' +require 'rubygems/local_remote_options' +require 'rubygems/source_info_cache' +require 'rubygems/version_option' + +module Gem + module Commands + class UpdateCommand < Command + + include Gem::InstallUpdateOptions + include Gem::LocalRemoteOptions + include Gem::VersionOption + + def initialize + super( + 'update', + 'Update the named gems (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 + + add_local_remote_options + + add_platform_option + end + + def arguments # :nodoc: + "GEMNAME name of gem to update" + end + + def defaults_str # :nodoc: + "--rdoc --ri --no-force --no-test\n" + + "--install-dir #{Gem.dir}" + end + + def usage # :nodoc: + "#{program_name} GEMNAME [GEMNAME ...]" + end + + def execute + if options[:system] then + say "Updating RubyGems..." + + unless options[:args].empty? then + 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::SourceInfoCache.search(//) + + 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 + + # HACK use the real API + install_command = Gem::CommandManager.instance['install'] + + gems_to_update.uniq.sort.each do |name| + say "Attempting remote update of #{name}" + options[:args] = [name] + options[:ignore_dependencies] = true # HACK skip seen gems instead + install_command.merge_options(options) + install_command.execute + end + + if gems_to_update.include?("rubygems-update") then + 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}" + installed = do_rubygems_update(latest_ruby_gem.version.to_s) + + say "RubyGems system software updated" if installed + else + say "Gems: [#{gems_to_update.uniq.sort.collect{|g| g.to_s}.join(', ')}] updated" + end + end + + def do_rubygems_update(version_string) + args = [] + args.push '--prefix', Gem.prefix unless Gem.prefix.nil? + args << '--no-rdoc' unless options[:generate_rdoc] + args << '--no-ri' unless options[:generate_ri] + + update_dir = File.join(Gem.dir, 'gems', + "rubygems-update-#{version_string}") + + success = false + + Dir.chdir update_dir do + say "Installing RubyGems #{version_string}" + setup_cmd = "#{Gem.ruby} setup.rb #{args.join ' '}" + + # Make sure old rubygems isn't loaded + if Gem.win_platform? then + system "set RUBYOPT= & #{setup_cmd}" + else + system "RUBYOPT=\"\" #{setup_cmd}" + end + 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 + end +end diff --git a/lib/rubygems/commands/which_command.rb b/lib/rubygems/commands/which_command.rb new file mode 100644 index 0000000000..b42244ce7d --- /dev/null +++ b/lib/rubygems/commands/which_command.rb @@ -0,0 +1,86 @@ +require 'rubygems/command' +require 'rubygems/gem_path_searcher' + +class Gem::Commands::WhichCommand < Gem::Command + + EXT = %w[.rb .rbw .so .dll] # HACK + + def initialize + super 'which', 'Find the location of a library', + :search_gems_first => false, :show_all => false + + add_option '-a', '--[no-]all', 'show all matching files' do |show_all, options| + options[:show_all] = show_all + end + + add_option '-g', '--[no-]gems-first', + 'search gems before non-gems' do |gems_first, options| + options[:search_gems_first] = gems_first + end + end + + def arguments # :nodoc: + "FILE name of file to find" + end + + def defaults_str # :nodoc: + "--no-gems-first --no-all" + end + + def usage # :nodoc: + "#{program_name} FILE [FILE ...]" + end + + def execute + searcher = Gem::GemPathSearcher.new + + options[:args].each do |arg| + dirs = $LOAD_PATH + spec = searcher.find arg + + if spec then + if options[:search_gems_first] then + dirs = gem_paths(spec) + $LOAD_PATH + else + dirs = $LOAD_PATH + gem_paths(spec) + end + + say "(checking gem #{spec.full_name} for #{arg})" if + Gem.configuration.verbose + end + + paths = find_paths arg, dirs + + if paths.empty? then + say "Can't find #{arg}" + else + say paths + end + end + end + + def find_paths(package_name, dirs) + result = [] + + dirs.each do |dir| + EXT.each do |ext| + full_path = File.join dir, "#{package_name}#{ext}" + if File.exist? full_path then + result << full_path + return result unless options[:show_all] + end + end + end + + result + end + + def gem_paths(spec) + spec.require_paths.collect { |d| File.join spec.full_gem_path, d } + end + + def usage # :nodoc: + "#{program_name} FILE [...]" + end + +end |