1require 'rubygems/command'
2require 'rubygems/local_remote_options'
3require 'rubygems/spec_fetcher'
4require 'rubygems/version_option'
5require 'rubygems/text'
6
7class Gem::Commands::QueryCommand < Gem::Command
8
9  include Gem::Text
10  include Gem::LocalRemoteOptions
11  include Gem::VersionOption
12
13  def initialize(name = 'query',
14                 summary = 'Query gem information in local or remote repositories')
15    super name, summary,
16         :name => //, :domain => :local, :details => false, :versions => true,
17         :installed => nil, :version => Gem::Requirement.default
18
19    add_option('-i', '--[no-]installed',
20               'Check for installed gem') do |value, options|
21      options[:installed] = value
22    end
23
24    add_option('-I', 'Equivalent to --no-installed') do |value, options|
25      options[:installed] = false
26    end
27
28    add_version_option command, "for use with --installed"
29
30    add_option('-n', '--name-matches REGEXP',
31               'Name of gem(s) to query on matches the',
32               'provided REGEXP') do |value, options|
33      options[:name] = /#{value}/i
34    end
35
36    add_option('-d', '--[no-]details',
37               'Display detailed information of gem(s)') do |value, options|
38      options[:details] = value
39    end
40
41    add_option(      '--[no-]versions',
42               'Display only gem names') do |value, options|
43      options[:versions] = value
44      options[:details] = false unless value
45    end
46
47    add_option('-a', '--all',
48               'Display all gem versions') do |value, options|
49      options[:all] = value
50    end
51
52    add_option(      '--[no-]prerelease',
53               'Display prerelease versions') do |value, options|
54      options[:prerelease] = value
55    end
56
57    add_local_remote_options
58  end
59
60  def defaults_str # :nodoc:
61    "--local --name-matches // --no-details --versions --no-installed"
62  end
63
64  def execute
65    exit_code = 0
66
67    name = options[:name]
68    prerelease = options[:prerelease]
69
70    unless options[:installed].nil? then
71      if name.source.empty? then
72        alert_error "You must specify a gem name"
73        exit_code |= 4
74      else
75        installed = installed? name, options[:version]
76        installed = !installed unless options[:installed]
77
78        if installed then
79          say "true"
80        else
81          say "false"
82          exit_code |= 1
83        end
84      end
85
86      terminate_interaction exit_code
87    end
88
89    req = Gem::Requirement.default
90    # TODO: deprecate for real
91    dep = Gem::Deprecate.skip_during { Gem::Dependency.new name, req }
92    dep.prerelease = prerelease
93
94    if local? then
95      if prerelease and not both? then
96        alert_warning "prereleases are always shown locally"
97      end
98
99      if ui.outs.tty? or both? then
100        say
101        say "*** LOCAL GEMS ***"
102        say
103      end
104
105      specs = Gem::Specification.find_all { |s|
106        s.name =~ name and req =~ s.version
107      }
108
109      spec_tuples = specs.map do |spec|
110        [spec.name_tuple, spec]
111      end
112
113      output_query_results spec_tuples
114    end
115
116    if remote? then
117      if ui.outs.tty? or both? then
118        say
119        say "*** REMOTE GEMS ***"
120        say
121      end
122
123      fetcher = Gem::SpecFetcher.fetcher
124
125      type = if options[:all]
126               if options[:prerelease]
127                 :complete
128               else
129                 :released
130               end
131             elsif options[:prerelease]
132               :prerelease
133             else
134               :latest
135             end
136
137      if options[:name].source.empty?
138        spec_tuples = fetcher.detect(type) { true }
139      else
140        spec_tuples = fetcher.detect(type) do |name_tuple|
141          options[:name] === name_tuple.name
142        end
143      end
144
145      output_query_results spec_tuples
146    end
147  end
148
149  private
150
151  ##
152  # Check if gem +name+ version +version+ is installed.
153
154  def installed?(name, req = Gem::Requirement.default)
155    Gem::Specification.any? { |s| s.name =~ name and req =~ s.version }
156  end
157
158  def output_query_results(spec_tuples)
159    output = []
160    versions = Hash.new { |h,name| h[name] = [] }
161
162    spec_tuples.each do |spec_tuple, source|
163      versions[spec_tuple.name] << [spec_tuple, source]
164    end
165
166    versions = versions.sort_by do |(n,_),_|
167      n.downcase
168    end
169
170    output_versions output, versions
171
172    say output.join(options[:details] ? "\n\n" : "\n")
173  end
174
175  def output_versions output, versions
176    versions.each do |gem_name, matching_tuples|
177      matching_tuples = matching_tuples.sort_by { |n,_| n.version }.reverse
178
179      platforms = Hash.new { |h,version| h[version] = [] }
180
181      matching_tuples.each do |n, _|
182        platforms[n.version] << n.platform if n.platform
183      end
184
185      seen = {}
186
187      matching_tuples.delete_if do |n,_|
188        if seen[n.version] then
189          true
190        else
191          seen[n.version] = true
192          false
193        end
194      end
195
196      output << make_entry(matching_tuples, platforms)
197    end
198  end
199
200  def entry_details entry, detail_tuple, specs, platforms
201    return unless options[:details]
202
203    name_tuple, spec = detail_tuple
204
205    spec = spec.fetch_spec name_tuple unless Gem::Specification === spec
206
207    entry << "\n"
208
209    spec_platforms   entry, platforms
210    spec_authors     entry, spec
211    spec_homepage    entry, spec
212    spec_license     entry, spec
213    spec_loaded_from entry, spec, specs
214    spec_summary     entry, spec
215  end
216
217  def entry_versions entry, name_tuples, platforms
218    return unless options[:versions]
219
220    list =
221      if platforms.empty? or options[:details] then
222        name_tuples.map { |n| n.version }.uniq
223      else
224        platforms.sort.reverse.map do |version, pls|
225          if pls == [Gem::Platform::RUBY] then
226            version
227          else
228            ruby = pls.delete Gem::Platform::RUBY
229            platform_list = [ruby, *pls.sort].compact
230            "#{version} #{platform_list.join ' '}"
231          end
232        end
233      end
234
235    entry << " (#{list.join ', '})"
236  end
237
238  def make_entry entry_tuples, platforms
239    detail_tuple = entry_tuples.first
240
241    name_tuples, specs = entry_tuples.flatten.partition do |item|
242      Gem::NameTuple === item
243    end
244
245    entry = [name_tuples.first.name]
246
247    entry_versions entry, name_tuples, platforms
248    entry_details  entry, detail_tuple, specs, platforms
249
250    entry.join
251  end
252
253  def spec_authors entry, spec
254    authors = "Author#{spec.authors.length > 1 ? 's' : ''}: "
255    authors << spec.authors.join(', ')
256    entry << format_text(authors, 68, 4)
257  end
258
259  def spec_homepage entry, spec
260    return if spec.homepage.nil? or spec.homepage.empty?
261
262    entry << "\n" << format_text("Homepage: #{spec.homepage}", 68, 4)
263  end
264
265  def spec_license entry, spec
266    return if spec.license.nil? or spec.license.empty?
267
268    licenses = "License#{spec.licenses.length > 1 ? 's' : ''}: "
269    licenses << spec.licenses.join(', ')
270    entry << "\n" << format_text(licenses, 68, 4)
271  end
272
273  def spec_loaded_from entry, spec, specs
274    return unless spec.loaded_from
275
276    if specs.length == 1 then
277      default = spec.default_gem? ? ' (default)' : nil
278      entry << "\n" << "    Installed at#{default}: #{spec.base_dir}"
279    else
280      label = 'Installed at'
281      specs.each do |s|
282        version = s.version.to_s
283        version << ', default' if s.default_gem?
284        entry << "\n" << "    #{label} (#{version}): #{s.base_dir}"
285        label = ' ' * label.length
286      end
287    end
288  end
289
290  def spec_platforms entry, platforms
291    non_ruby = platforms.any? do |_, pls|
292      pls.any? { |pl| pl != Gem::Platform::RUBY }
293    end
294
295    return unless non_ruby
296
297    if platforms.length == 1 then
298      title = platforms.values.length == 1 ? 'Platform' : 'Platforms'
299      entry << "    #{title}: #{platforms.values.sort.join ', '}\n"
300    else
301      entry << "    Platforms:\n"
302      platforms.sort_by do |version,|
303        version
304      end.each do |version, pls|
305        label = "        #{version}: "
306        data = format_text pls.sort.join(', '), 68, label.length
307        data[0, label.length] = label
308        entry << data << "\n"
309      end
310    end
311  end
312
313  def spec_summary entry, spec
314    entry << "\n\n" << format_text(spec.summary, 68, 4)
315  end
316
317end
318
319