require 'rubygems/command' require 'rubygems/local_remote_options' require 'rubygems/spec_fetcher' require 'rubygems/version_option' class Gem::Commands::QueryCommand < Gem::Command include Gem::LocalRemoteOptions include Gem::VersionOption def initialize(name = 'query', summary = 'Query gem information in local or remote repositories') super name, summary, :name => //, :domain => :local, :details => false, :versions => true, :installed => false, :version => Gem::Requirement.default add_option('-i', '--[no-]installed', 'Check for installed gem') do |value, options| options[:installed] = value end add_version_option 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_option('-a', '--all', 'Display all gem versions') do |value, options| options[:all] = value end add_local_remote_options end def defaults_str # :nodoc: "--local --name-matches // --no-details --versions --no-installed" end def execute exit_code = 0 name = options[:name] if options[:installed] then if name.source.empty? then alert_error "You must specify a gem name" exit_code |= 4 elsif installed? name, options[:version] then say "true" else say "false" exit_code |= 1 end raise Gem::SystemExitException, exit_code end dep = Gem::Dependency.new name, Gem::Requirement.default if local? then if ui.outs.tty? or both? then say say "*** LOCAL GEMS ***" say end specs = Gem.source_index.search dep spec_tuples = specs.map do |spec| [[spec.name, spec.version, spec.original_platform, spec], :local] end output_query_results spec_tuples end if remote? then if ui.outs.tty? or both? then say say "*** REMOTE GEMS ***" say end all = options[:all] begin fetcher = Gem::SpecFetcher.fetcher spec_tuples = fetcher.find_matching dep, all, false rescue Gem::RemoteFetcher::FetchError => e raise unless fetcher.warn_legacy e do require 'rubygems/source_info_cache' dep.name = '' if dep.name == // specs = Gem::SourceInfoCache.search_with_source dep, false, all spec_tuples = specs.map do |spec, source_uri| [[spec.name, spec.version, spec.original_platform, spec], source_uri] end end end output_query_results spec_tuples end end private ## # Check if gem +name+ version +version+ is installed. def installed?(name, version = Gem::Requirement.default) dep = Gem::Dependency.new name, version !Gem.source_index.search(dep).empty? end def output_query_results(spec_tuples) output = [] versions = Hash.new { |h,name| h[name] = [] } spec_tuples.each do |spec_tuple, source_uri| versions[spec_tuple.first] << [spec_tuple, source_uri] end versions = versions.sort_by do |(name,_),_| name.downcase end versions.each do |gem_name, matching_tuples| matching_tuples = matching_tuples.sort_by do |(name, version,_),_| version end.reverse seen = {} matching_tuples.delete_if do |(name, version,_),_| if seen[version] then true else seen[version] = true false end end entry = gem_name.dup if options[:versions] then versions = matching_tuples.map { |(name, version,_),_| version }.uniq entry << " (#{versions.join ', '})" end if options[:details] then detail_tuple = matching_tuples.first spec = if detail_tuple.first.length == 4 then detail_tuple.first.last else uri = URI.parse detail_tuple.last Gem::SpecFetcher.fetcher.fetch_spec detail_tuple.first, uri end entry << "\n" authors = "Author#{spec.authors.length > 1 ? 's' : ''}: " authors << spec.authors.join(', ') entry << format_text(authors, 68, 4) if spec.rubyforge_project and not spec.rubyforge_project.empty? then rubyforge = "Rubyforge: http://rubyforge.org/projects/#{spec.rubyforge_project}" entry << "\n" << format_text(rubyforge, 68, 4) end if spec.homepage and not spec.homepage.empty? then entry << "\n" << format_text("Homepage: #{spec.homepage}", 68, 4) end if spec.loaded_from then if matching_tuples.length == 1 then loaded_from = File.dirname File.dirname(spec.loaded_from) entry << "\n" << " Installed at: #{loaded_from}" else label = 'Installed at' matching_tuples.each do |(_,version,_,s),| loaded_from = File.dirname File.dirname(s.loaded_from) entry << "\n" << " #{label} (#{version}): #{loaded_from}" label = ' ' * label.length end end end entry << "\n\n" << format_text(spec.summary, 68, 4) end 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