summaryrefslogtreecommitdiff
path: root/lib/rubygems/commands/query_command.rb
blob: ea83b93bbb79a0b72401d9506d1197ef62bd7c11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/source_info_cache'
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.source, options[:version] then
        say "true"
      else
        say "false"
        exit_code |= 1
      end

      raise Gem::SystemExitException, exit_code
    end

    if local? then
      say
      say "*** LOCAL GEMS ***"
      say

      output_query_results Gem.source_index.search(name)
    end

    if remote? then
      say
      say "*** REMOTE GEMS ***"
      say

      all = options[:all]

      begin
        Gem::SourceInfoCache.cache all
      rescue Gem::RemoteFetcher::FetchError
        # no network
      end

      output_query_results Gem::SourceInfoCache.search(name, false, all)
    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(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
        versions = list_of_matching.map { |s| s.version }.uniq
        entry << " (#{versions.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