ruby-bugzilla 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/bin/bzconsole CHANGED
@@ -890,7 +890,7 @@ begin
890
890
  opt.on('-t', '--timeout=SEC', 'Set XMLRPC timeout in a second.') {|v| opts[:timeout] = v.to_i}
891
891
  opt.on('-h', '--help', 'show this message') {|x| opts[:help] = true}
892
892
 
893
- cmds = BzConsole.constants.sort.map {|x| (k = eval("BzConsole::#{x}")).class == Class && x != :CommandTemplate ? x.downcase : nil}.compact
893
+ cmds = BzConsole.constants.sort.map {|x| (k = eval("BzConsole::#{x}")).class == Class && x != :CommandTemplate ? x.downcase.to_sym : nil}.compact
894
894
 
895
895
  subargv = opt.order(ARGV);
896
896
 
@@ -899,7 +899,7 @@ begin
899
899
  if subargv.length > 0 then
900
900
  n = cmds.index(command.to_sym)
901
901
  unless n.nil? then
902
- opts[:instance] = eval("BzConsole::#{cmds[n].capitalize}.new(Bugzilla::Plugin::Template.new)")
902
+ opts[:instance] = eval("BzConsole::#{cmds[n].to_s.capitalize}.new(Bugzilla::Plugin::Template.new)")
903
903
  subargv = opts[:instance].parse(opt, subargv[1..-1], opts[:command])
904
904
  else
905
905
  STDERR.printf("E: Unknown command: %s\n", subargv[0])
@@ -28,6 +28,6 @@
28
28
 
29
29
  module Bugzilla
30
30
 
31
- VERSION = "0.4.0"
31
+ VERSION = "0.4.1"
32
32
 
33
33
  end # module Bugzilla
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-bugzilla
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-07 00:00:00.000000000 Z
12
+ date: 2012-08-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -97,7 +97,6 @@ email:
97
97
  - akira@tagoh.org
98
98
  executables:
99
99
  - bzconsole
100
- - bzconsole~
101
100
  extensions: []
102
101
  extra_rdoc_files: []
103
102
  files:
@@ -121,7 +120,6 @@ files:
121
120
  - README.rdoc
122
121
  - COPYING
123
122
  - bin/bzconsole
124
- - bin/bzconsole~
125
123
  homepage: http://github.com/tagoh/ruby-bugzilla
126
124
  licenses: []
127
125
  post_install_message:
data/bin/bzconsole~ DELETED
@@ -1,836 +0,0 @@
1
- #! /usr/bin/env ruby
2
- # bzconsole.rb
3
- # Copyright (C) 2010-2012 Red Hat, Inc.
4
- #
5
- # Authors:
6
- # Akira TAGOH <tagoh@redhat.com>
7
- #
8
- # This library is free software; you can redistribute it and/or
9
- # modify it under the terms of the GNU Lesser General Public
10
- # License as published by the Free Software Foundation; either
11
- # version 2 of the License, or (at your option) any later version.
12
- #
13
- # This library is distributed in the hope that it will be useful,
14
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
- # Lesser General Public License for more details.
17
- #
18
- # You should have received a copy of the GNU Lesser General Public
19
- # License along with this library; if not, write to the
20
- # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21
- # Boston, MA 02111-1307, USA.
22
-
23
- require 'optparse'
24
- require 'time'
25
- require 'yaml'
26
- require 'rubygems'
27
- require 'pp'
28
- require 'gruff'
29
- require 'uri'
30
- require 'highline/import'
31
-
32
- begin
33
- require 'bugzilla'
34
- rescue LoadError
35
- $:.push(File.join(File.dirname(__FILE__), '..', 'lib'))
36
- require 'bugzilla'
37
- end
38
-
39
- $KCODE = 'u'
40
-
41
- module BzConsole
42
-
43
- class CommandTemplate
44
-
45
- def initialize(plugin)
46
- @n_args = 0
47
- @defaultyamlfile = File.join(ENV['HOME'], ".bzconsole.yml")
48
- @plugin = plugin
49
- end # def initialize
50
-
51
- attr_reader :n_args
52
-
53
- def parse(parser, argv, opts)
54
- raise RuntimeError, sprintf("No implementation for %s", self.class) if self.class =~ /CommandTemplate/
55
-
56
- parser.on('-h', '--help', 'show this message') {|x| opts[:help] = true}
57
-
58
- read_config(opts)
59
- @plugin.run(:parser, nil, parser, argv, opts)
60
-
61
- parser.order(argv)
62
- end # def parse
63
-
64
- def do(argv)
65
- raise RuntimeError, sprintf("No implementation for %s", self.class)
66
- end # def do
67
-
68
- protected
69
-
70
- def read_config(opts)
71
- fname = opts[:config].nil? ? @defaultyamlfile : opts[:config]
72
- begin
73
- conf = YAML.load(File.open(fname).read)
74
- rescue Errno::ENOENT
75
- conf = {}
76
- end
77
- conf.each do |k, v|
78
- if v.kind_of?(Hash) && v.include?(:Plugin) then
79
- load(v[:Plugin])
80
- end
81
- end
82
- conf
83
- end # def read_config
84
-
85
- def save_config(opts, conf)
86
- fname = opts[:config].nil? ? @defaultyamlfile : opts[:config]
87
- if File.exist?(fname) then
88
- st = File.lstat(fname)
89
- if st.mode & 0600 != 0600 then
90
- raise RuntimeError, sprintf("The permissions of %s has to be 0600", fname)
91
- end
92
- end
93
- File.open(fname, "w") {|f| f.chmod(0600); f.write(conf.to_yaml)}
94
- end # def save_config
95
-
96
- private
97
-
98
- def get_proxy(info)
99
- uri = info[:Proxy] || ENV["http_proxy"]
100
- proxy_uri = uri.nil? ? nil : URI.parse(uri)
101
- proxy_host = proxy_uri.nil? ? nil : proxy_uri.host
102
- proxy_port = proxy_uri.nil? ? nil : proxy_uri.port
103
- return proxy_host, proxy_port
104
- end
105
-
106
- end # class CommandTemplate
107
-
108
- class Setup < CommandTemplate
109
-
110
- def initialize(plugin)
111
- super
112
-
113
- @n_args = 0
114
- end # def initialize
115
-
116
- def parse(parser, argv, opts)
117
- parser.banner = sprintf("Usage: %s [global options] setup", File.basename(__FILE__))
118
- parser.separator ""
119
- parser.separator "Command options:"
120
-
121
- super
122
- end # def parse
123
-
124
- def do(argv, opts)
125
- template = {
126
- "rhbz"=>{
127
- :Name=>"Red Hat Bugzilla",
128
- :URL=>"https://bugzilla.redhat.com",
129
- :User=>"account@example.com",
130
- :Password=>"blahblahblah",
131
- :ProductAliases=>{
132
- 'RHEL3'=>'Red Hat Enterprise Linux 3',
133
- 'RHEL4'=>'Red Hat Enterprise Linux 4',
134
- 'RHEL5'=>'Red Hat Enterprise Linux 5',
135
- 'RHEL6'=>'Red Hat Enterprise Linux 6'
136
- },
137
- :Plugin=>"ruby-bugzilla/rhbugzilla.rb"
138
- },
139
- "gnbz"=>{
140
- :Name=>"GNOME Bugzilla",
141
- :URL=>"https://bugzilla.gnome.org",
142
- :User=>"account@example.com",
143
- :Password=>"blahblahblah",
144
- },
145
- "fdobz"=>{
146
- :Name=>"FreeDesktop Bugzilla",
147
- :URL=>"https://bugs.freedesktop.org",
148
- :User=>"account@example.com",
149
- :Password=>"blahblahblah",
150
- },
151
- "mzbz"=>{
152
- :Name=>"Mozilla Bugzilla",
153
- :URL=>"https://bugzilla.mozilla.org",
154
- :User=>"account@example.com",
155
- :Password=>"blahblahblah",
156
- }
157
- }
158
-
159
- template.each do |k, v|
160
- @plugin.run(:pre, v[:URL].sub(/\Ahttps?:\/\//, '').sub(/\/\Z/, ''), :setup, v)
161
- end
162
-
163
- fname = opts[:config].nil? ? @defaultyamlfile : opts[:config]
164
- if File.exist?(fname) then
165
- raise RuntimeError, ".bzconsole.yml already exists. please remove it first to proceed to the next step."
166
- end
167
- File.open(fname, File::CREAT|File::WRONLY, 0600) do |f|
168
- f.write(template.to_yaml)
169
- end
170
- printf("%s has been created. please modify your account information before operating.\n", fname)
171
- end # def do
172
-
173
- end # class Setup
174
-
175
- class Login < CommandTemplate
176
-
177
- def initialize(plugin)
178
- super
179
-
180
- @n_args = 1
181
- end # def initialize
182
-
183
- def parse(parser, argv, opts)
184
- opts[:output] = File.join(ENV['HOME'], '.ruby-bugzilla-cookie.yml')
185
- parser.banner = sprintf("Usage: %s [global options] login [command options] <prefix> ...", File.basename(__FILE__))
186
- parser.separator ""
187
- parser.separator "Command options:"
188
- parser.on('-o', '--output=FILE', 'FILE to store the cookie') {|v| opts[:output] = v}
189
-
190
- super
191
- end # def parse
192
-
193
- def do(argv, opts)
194
- conf = read_config(opts)
195
- conf.freeze
196
- cconf = read_config({:config=>opts[:command][:output]})
197
- argv.each do |prefix|
198
- if prefix.nil? then
199
- raise ArgumentError, "No prefix to log in"
200
- end
201
- unless conf.include?(prefix) then
202
- raise RuntimeError, sprintf("No host information for %s", prefix)
203
- end
204
-
205
- info = conf[prefix]
206
- uri = URI.parse(info[:URL])
207
- host = uri.host
208
- port = uri.port
209
- path = uri.path.empty? ? nil : uri.path
210
- login = info[:User].nil? ? ask("Bugzilla ID: ") : info[:User]
211
- pass = info[:Password].nil? ? ask("Bugzilla password: ") {|q| q.echo = false} : info[:Password]
212
- proxy_host, proxy_port = get_proxy(info)
213
- timeout = opts[:timeout].nil? ? 60 : opts[:timeout]
214
-
215
- xmlrpc = Bugzilla::XMLRPC.new(host, port, path, proxy_host, proxy_port, timeout)
216
- user = Bugzilla::User.new(xmlrpc)
217
- begin
218
- result = user.login({'login'=>login, 'password'=>pass, 'remember'=>true})
219
- cconf[host] = xmlrpc.cookie
220
- save_config({:config=>opts[:command][:output]}, cconf)
221
- rescue XMLRPC::FaultException => e
222
- printf("E: %s\n", e.message)
223
- end
224
- end
225
- end # def do
226
-
227
- end # class Login
228
-
229
- class Getbug < CommandTemplate
230
-
231
- def initialize(plugin)
232
- super
233
-
234
- @n_args = 1
235
- end # def initialize
236
-
237
- def parse(parser, argv, opts)
238
- parser.banner = sprintf("Usage: %s [global options] getbug [command options] <prefix:bug number> ...", File.basename(__FILE__))
239
- parser.separator ""
240
- parser.separator "Command options:"
241
- parser.on('-s', '--summary', 'Displays bugs summary only') {opts[:summary] = true}
242
- parser.on('-d', '--details', 'Displays detailed bugs information') {opts[:details] = true}
243
- parser.on('-a', '--all', 'Displays the whole data in bugs') {opts[:all] = true}
244
- parser.on('--anonymous', 'Access to Bugzilla anonymously') {opts[:anonymous] = true}
245
-
246
- super
247
- end # def parse
248
-
249
- def do(argv, opts)
250
- real_do(argv, opts) do |result|
251
- if opts[:command][:summary] == true then
252
- printf("Bug#%s, %s, %s[%s, %s] %s\n",
253
- result['id'],
254
- result['product'],
255
- result['component'],
256
- result['status'],
257
- result['severity'],
258
- result['summary'])
259
- elsif opts[:command][:details] == true then
260
- printf("Bug#%s, %s, %s, %s[%s, %s, %s, %s] %s\n",
261
- result['id'],
262
- result['product'],
263
- result['assigned_to'],
264
- result['component'],
265
- result['status'],
266
- result['resolution'],
267
- result['priority'],
268
- result['severity'],
269
- result['summary'])
270
- else
271
- printf("Bug#%s - %s\n", result['id'], result['summary'])
272
- printf("Status:\t\t%s%s\n", result['status'], !result['resolution'].empty? ? sprintf("[%s]", result['resolution']) : "")
273
- printf("Product:\t%s\n", result['product'])
274
- printf("Component:\t%s\n", result['component'])
275
- printf("Assinged To:\t%s\n", result['assigned_to'])
276
- printf("Priority:\t%s\n", result['priority'])
277
- printf("Severity:\t%s\n", result['severity'])
278
- printf("Comments:\t%d\n\n", result['comments'].length)
279
- i = 0
280
- result['comments'].each do |c|
281
- printf("Comment#%d%s %s %s\n", i, c['is_private'] == true ? "[private]" : "", c['author'], c['time'].to_time)
282
- printf("\n %s\n\n", c['text'].split("\n").join("\n "))
283
- i += 1
284
- end
285
- end
286
- end
287
- end # def do
288
-
289
- private
290
-
291
- def real_do(argv, opts)
292
- conf = read_config(opts)
293
- conf.freeze
294
- argv.each do |bugn|
295
- bugn =~ /(.*):(.*)/
296
- prefix = $1
297
- nnn = $2
298
- if prefix.nil? then
299
- raise ArgumentError, sprintf("No prefix specified for Bug#%s", bugn)
300
- end
301
- unless conf.include?(prefix) then
302
- raise RuntimeError, sprintf("No host information for %s", prefix)
303
- end
304
-
305
- info = conf[prefix]
306
- uri = URI.parse(info[:URL])
307
- host = uri.host
308
- port = uri.port
309
- path = uri.path.empty? ? nil : uri.path
310
- login = opts[:command][:anonymous] == true ? nil : info[:User]
311
- pass = opts[:command][:anonymous] == true ? nil : info[:Password]
312
- proxy_host, proxy_port = get_proxy(info)
313
- timeout = opts[:timeout].nil? ? 60 : opts[:timeout]
314
-
315
- @plugin.run(:pre, host, :getbug, opts)
316
-
317
- xmlrpc = Bugzilla::XMLRPC.new(host, port, path, proxy_host, proxy_port, timeout)
318
- user = Bugzilla::User.new(xmlrpc)
319
- user.session(login, pass) do
320
- bug = Bugzilla::Bug.new(xmlrpc)
321
-
322
- result = nil
323
- if opts[:command][:summary] == true then
324
- result = bug.get_bugs(nnn.split(','))
325
- elsif opts[:command][:details] == true then
326
- result = bug.get_bugs(nnn.split(','), ::Bugzilla::Bug::FIELDS_DETAILS)
327
- else
328
- result = bug.get_bugs(nnn.split(','), nil)
329
- end
330
-
331
- @plugin.run(:post, host, :getbug, result)
332
-
333
- result.each do |r|
334
- yield r
335
- end
336
- end
337
- end
338
- end # def real_do
339
-
340
- end # class Getbug
341
-
342
- class Search < CommandTemplate
343
-
344
- def initialize(plugin)
345
- super
346
-
347
- @n_args = 1
348
- end # def initialize
349
-
350
- def parse(parser, argv, opts)
351
- opts[:query] ||= {}
352
- parser.banner = sprintf("Usage: %s [global options] search [command options] <prefix> ...", File.basename(__FILE__))
353
- parser.separator ""
354
- parser.separator "Search options:"
355
- parser.on('--alias=ALIASES', 'filter out the result by the alias') {|v| opts[:query][:alias] ||= []; opts[:query][:alias].push(*v.split(','))}
356
- parser.on('-a', '--assignee=ASSIGNEES', 'filter out the result by the specific assignees') {|v| opts[:query][:assigned_to] ||= []; opts[:query][:assigned_to].push(*v.split(','))}
357
- parser.on('--bug=BUGS', 'filter out the result by the specific bug number') {|v| opts[:query][:id] ||= []; opts[:query][:id].push(*v.split(','))}
358
- parser.on('-c', '--component=COMPONENTS', 'filter out the result by the specific components') {|v| opts[:query][:component] ||= []; opts[:query][:component].push(*v.split(','))}
359
- parser.on('--create-time=TIME', 'Searches for bugs that were created at this time or later') {|v| opts[:query][:creation_time] = Time.parse(v)}
360
- parser.on('--creator=CREATER', 'filter out the result by the specific user who reported bugs') {|v| opts[:query][:creator] ||= []; opts[:query][:creator].push(*v.split(','))}
361
- parser.on('--last-change-time=TIME', 'Searches for bugs that were modified at this time or later') {|v| opts[:query][:last_change_time] = Time.parse(v)}
362
- parser.on('--op-sys=NAMES', 'filter out the result by the operating system') {|v| opts[:query][:op_sys] ||= []; opts[:query][:op_sys].push(*v.split(','))}
363
- parser.on('--platform=PLATFORMS', 'filter out the result by the platform') {|v| opts[:query][:platform] ||= []; opts[:query][:platform].push(*v.split(','))}
364
- parser.on('--priority=PRIORITY', 'filter out the result by the priority') {|v| opts[:query][:priority] ||= []; opts[:query][:priority].push(*v.split(','))}
365
- parser.on('-p', '--product=PRODUCTS', 'filter out the result by the specific products') {|v| opts[:query][:product] ||= []; opts[:query][:product].push(*v.split(','))}
366
- parser.on('--resolution=RESOLUSIONS', 'filter out the result by the resolusions') {|v| opts[:query][:resolution] ||= []; opts[:query][:resolusion].push(*v.split(','))}
367
- parser.on('--severity=SEVERITY', 'filter out the result by the severity') {|v| opts[:query][:severity] ||= []; opts[:query][:severity].push(*v.split(','))}
368
- parser.on('-s', '--status=STATUSES', 'filter out the result by the status') {|v| opts[:query][:status] ||= []; opts[:query][:status].push(*v.split(','))}
369
- parser.on('--summary=SUMMARY', 'filter out the result by the summary') {|v| opts[:query][:summary] ||= []; opts[:query][:summary] << v}
370
- parser.on('--milestone=MILESTONE', 'filter out the result by the target milestone') {|v| opts[:query][:target_milestone] ||= []; opts[:query][:target_milestone].push(*v.split(','))}
371
- parser.on('--whiteboard=STRING', 'filter out the result by the specific words in the status whiteboard') {|v| opts[:query][:whiteboard] ||= []; opts[:query][:whiteboard] << v}
372
- parser.separator ""
373
- parser.separator "Command options:"
374
- parser.on('--short-list', 'Displays bugs summary only') {opts[:summary] = true}
375
- parser.on('--detailed-list', 'Displays detailed bugs information') {opts[:details] = true}
376
- parser.on('--anonymous', 'Access to Bugzilla anonymously') {opts[:anonymous] = true}
377
-
378
- super
379
- end # def parse
380
-
381
- def do(argv, opts)
382
- c = 0
383
- real_do(argv, opts) do |result|
384
- if opts[:command][:summary] == true then
385
- printf("Bug#%s, %s, %s[%s, %s] %s\n",
386
- result['id'] || result['bug_id'],
387
- result['product'],
388
- result['component'],
389
- result['status'],
390
- result['severity'],
391
- result['summary'])
392
- elsif opts[:command][:details] == true then
393
- printf("Bug#%s, %s, %s, %s[%s, %s, %s, %s] %s\n",
394
- result['id'],
395
- result['product'],
396
- result['assigned_to'],
397
- result['component'],
398
- result['status'],
399
- result['resolution'],
400
- result['priority'],
401
- result['severity'],
402
- result['summary'])
403
- end
404
- c += 1
405
- end
406
- printf("\n%d bug(s) found\n", c)
407
- end # def do
408
-
409
- private
410
-
411
- def real_do(argv, opts)
412
- conf = read_config(opts)
413
- conf.freeze
414
- argv.each do |prefix|
415
- unless conf.include?(prefix) then
416
- raise RuntimeError, sprintf("No host information for %s", prefix)
417
- end
418
-
419
- info = conf[prefix]
420
- uri = URI.parse(info[:URL])
421
- host = uri.host
422
- port = uri.port
423
- path = uri.path.empty? ? nil : uri.path
424
- login = opts[:command][:anonymous] == true ? nil : info[:User]
425
- pass = opts[:command][:anonymous] == true ? nil : info[:Password]
426
- proxy_host, proxy_port = get_proxy(info)
427
- timeout = opts[:timeout].nil? ? 60 : opts[:timeout]
428
-
429
- @plugin.run(:pre, host, :search, opts[:command][:query])
430
-
431
- xmlrpc = Bugzilla::XMLRPC.new(host, port, path, proxy_host, proxy_port, timeout)
432
- user = Bugzilla::User.new(xmlrpc)
433
- user.session(login, pass) do
434
- bug = Bugzilla::Bug.new(xmlrpc)
435
-
436
- opts[:command][:query][:product].map! {|x| info.include?(:ProductAliases) && info[:ProductAliases].include?(x) ? info[:ProductAliases][x] : x} if opts[:command][:query].include?(:product)
437
-
438
- result = bug.search(opts[:command][:query])
439
-
440
- @plugin.run(:post, host, :search, result)
441
-
442
- if result.include?('bugs') then
443
- result['bugs'].each do |r|
444
- yield r
445
- end
446
- end
447
- end
448
- end
449
- end # def real_do
450
-
451
- end # class Search
452
-
453
- class Show < CommandTemplate
454
-
455
- def initialize(plugin)
456
- super
457
-
458
- @n_args = 1
459
- end # def initialize
460
-
461
- def parse(parser, argv, opts)
462
- opts[:show] ||= {}
463
- opts[:show][:mode] = :component
464
- opts[:show][:field] = []
465
- parser.banner = sprintf("Usage: %s [global options] show [command options] <prefix> ...", File.basename(__FILE__))
466
- parser.separator ""
467
- parser.separator "Command options:"
468
- parser.on('-f', '--field', 'Displays available field informations') {|v| opts[:show][:mode] = :field}
469
- parser.on('--field-name=NAME', 'Displays NAME field information') {|v| opts[:show][:field] << v.split(',')}
470
- parser.on('-p', '--product', 'Displays available product names') {|v| opts[:show][:mode] = :product}
471
- parser.on('-c', '--component', 'Displays available component names (default)') {|v| opts[:show][:mode] = :component}
472
- parser.on('--anonymous', 'Access to Bugzilla anonymously') {opts[:anonymous] = true}
473
-
474
- super
475
- end # def parse
476
-
477
- def do(argv, opts)
478
- real_do(argv, opts) do |*result|
479
- if opts[:command][:show][:mode] == :product then
480
- printf("%s\n", result[0])
481
- elsif opts[:command][:show][:mode] == :component then
482
- printf("%s:\n", result[0])
483
- printf(" %s\n", result[1].join("\n "))
484
- end
485
- end
486
- end # def do
487
-
488
- private
489
-
490
- def real_do(argv, opts)
491
- conf = read_config(opts)
492
- conf.freeze
493
- argv.each do |prefix|
494
- unless conf.include?(prefix) then
495
- raise RuntimeError, sprintf("No host information for %s", prefix)
496
- end
497
-
498
- info = conf[prefix]
499
- uri = URI.parse(info[:URL])
500
- host = uri.host
501
- port = uri.port
502
- path = uri.path.empty? ? nil : uri.path
503
- login = opts[:command][:anonymous] == true ? nil : info[:User]
504
- pass = opts[:command][:anonymous] == true ? nil : info[:Password]
505
- proxy_host, proxy_port = get_proxy(info)
506
- timeout = opts[:timeout].nil? ? 60 : opts[:timeout]
507
-
508
- @plugin.run(:pre, host, :show, opts)
509
-
510
- xmlrpc = Bugzilla::XMLRPC.new(host, port, path, proxy_host, proxy_port, timeout)
511
- user = Bugzilla::User.new(xmlrpc)
512
- user.session(login, pass) do
513
- if opts[:command][:show][:mode] == :field then
514
- bug = Bugzilla::Bug.new(xmlrpc)
515
-
516
- result = bug.fields(opts[:command][:show][:field].flatten)
517
-
518
- @plugin.run(:post, host, :show, result)
519
-
520
- pp result
521
- else
522
- product = Bugzilla::Product.new(xmlrpc)
523
-
524
- result = product.selectable_products
525
-
526
- @plugin.run(:post, host, :show, result)
527
-
528
- products = result.keys.sort
529
- products.each do |p|
530
- if opts[:command][:show][:mode] == :product then
531
- yield p
532
- elsif opts[:command][:show][:mode] == :component then
533
- yield p, result[p][0].sort
534
- end
535
- end
536
- end
537
- end
538
- end
539
- end # def real_do
540
-
541
- end # class Show
542
-
543
- class Metrics < CommandTemplate
544
-
545
- def initialize(plugin)
546
- super
547
-
548
- @n_args = 1
549
- end # def initialize
550
-
551
- def parse(parser, argv, opts)
552
- opts[:metrics] = {}
553
- opts[:query] = {}
554
- opts[:metrics][:output] = 'bz-metrics.png'
555
- opts[:metrics][:x_coordinate] = :monthly
556
-
557
- parser.banner = sprintf("Usage: %s [global options] metrics [command options] <prefix> ...", File.basename(__FILE__))
558
- parser.separator ""
559
- parser.separator "Search options:"
560
- parser.on('--alias=ALIASES', 'filter out the result by the alias') {|v| opts[:query][:alias] ||= []; opts[:query][:alias].push(*v.split(','))}
561
- parser.on('-a', '--assignee=ASSIGNEES', 'filter out the result by the specific assignees') {|v| opts[:query][:assigned_to] ||= []; opts[:query][:assigned_to].push(*v.split(','))}
562
- parser.on('--bug=BUGS', 'filter out the result by the specific bug number') {|v| opts[:query][:id] ||= []; opts[:query][:id].push(*v.split(','))}
563
- parser.on('-c', '--component=COMPONENTS', 'filter out the result by the specific components') {|v| opts[:query][:component] ||= []; opts[:query][:component].push(*v.split(','))}
564
- parser.on('--creator=CREATER', 'filter out the result by the specific user who reported bugs') {|v| opts[:query][:creator] ||= []; opts[:query][:creator].push(*v.split(','))}
565
- parser.on('--op-sys=NAMES', 'filter out the result by the operating system') {|v| opts[:query][:op_sys] ||= []; opts[:query][:op_sys].push(*v.split(','))}
566
- parser.on('--platform=PLATFORMS', 'filter out the result by the platform') {|v| opts[:query][:platform] ||= []; opts[:query][:platform].push(*v.split(','))}
567
- parser.on('--priority=PRIORITY', 'filter out the result by the priority') {|v| opts[:query][:priority] ||= []; opts[:query][:priority].push(*v.split(','))}
568
- parser.on('-p', '--product=PRODUCTS', 'filter out the result by the specific products') {|v| opts[:query][:product] ||= []; opts[:query][:product].push(*v.split(','))}
569
- parser.on('--resolution=RESOLUSIONS', 'filter out the result by the resolusions') {|v| opts[:query][:resolution] ||= []; opts[:query][:resolusion].push(*v.split(','))}
570
- parser.on('--severity=SEVERITY', 'filter out the result by the severity') {|v| opts[:query][:severity] ||= []; opts[:query][:severity].push(*v.split(','))}
571
- parser.on('--summary=SUMMARY', 'filter out the result by the summary') {|v| opts[:query][:summary] ||= []; opts[:query][:summary] << v}
572
- parser.on('--milestone=MILESTONE', 'filter out the result by the target milestone') {|v| opts[:query][:target_milestone] ||= []; opts[:query][:target_milestone].push(*v.split(','))}
573
- parser.on('--whiteboard=STRING', 'filter out the result by the specific words in the status whiteboard') {|v| opts[:query][:whiteboard] ||= []; opts[:query][:whiteboard] << v}
574
- parser.separator ""
575
- parser.separator "Command options:"
576
- parser.on('-t', '--title=TITLE', 'add TITLE to the graph') {|v| opts[:metrics][:title] = v}
577
- parser.on('--begin-date=DATE', 'generate the graph since the beginning of month of DATE.') {|v| x = Time.parse(v); opts[:metrics][:begin_date] = Time.utc(x.year, x.month, 1, 0, 0, 0)}
578
- parser.on('--end-date=DATE', 'generate the graph until the end of month of DATE.') {|v| x = Time.parse(v); opts[:metrics][:end_date] = Time.utc(x.year, x.month + 1, 1, 0, 0, 0) - 1}
579
- parser.on('-o', '--output=FILE', 'generate the graph to FILE') {|v| opts[:metrics][:output] = v}
580
- parser.on('--anonymous', 'access to Bugzilla anonymously') {|v| opts[:anonymous] = true}
581
- parser.on('--weekly', 'genereate the graph with weekly X-coordinate') {|v| opts[:metrics][:x_coordinate] = :weekly}
582
- parser.on('--monthly', 'genereate the graph with monthly X-coordinate (default)') {|v| opts[:metrics][:x_coordinate] = :monthly}
583
-
584
- super
585
- end # def parse
586
-
587
- def do(argv, opts)
588
- data = {
589
- :label=>[],
590
- 'NEW'=>[],
591
- 'ASSIGNED'=>[],
592
- 'MODIFIED'=>[],
593
- 'ON_QA'=>[],
594
- 'CLOSED'=>[],
595
- 'OPEN'=>[]
596
- }
597
- last_label = nil
598
- real_do(argv, opts) do |t, new, assigned, modified, on_qa, closed, open|
599
- printf("%s, new: %d, assigned: %d, modified %d, on_qa %d, closed %d / open %d\n",
600
- opts[:command][:metrics][:x_coordinate] == :weekly ? sprintf("week %d", Date.new(t.year, t.month, t.day).cweek) : t.strftime("%Y-%m"), new, assigned, modified, on_qa, closed, open)
601
- data['NEW'] << new
602
- data['ASSIGNED'] << assigned
603
- data['MODIFIED'] << modified
604
- data['ON_QA'] << on_qa
605
- data['CLOSED'] << closed
606
- data['OPEN'] << open
607
- label = t.strftime("%Y/%m")
608
- if last_label != label then
609
- data[:label] << label
610
- last_label = label
611
- else
612
- data[:label] << nil
613
- end
614
- end
615
-
616
- timeline = data[:label]
617
- data.delete(:label)
618
- def timeline.to_hash
619
- ret = {}
620
- (0..self.length-1).each do |i|
621
- ret[i] = self[i] unless self[i].nil?
622
- end
623
- ret
624
- end # def timeline.to_hash
625
-
626
- # output the trend graph
627
- g = Gruff::Line.new
628
- g.title = sprintf("Trend: %s", opts[:command][:metrics][:title])
629
- g.labels = timeline.to_hash
630
- data.each do |k, v|
631
- next unless k == 'NEW' || k == 'OPEN' || k == 'CLOSED'
632
- g.data(k, v)
633
- end
634
- g.write(sprintf("trend-%s", opts[:command][:metrics][:output]))
635
-
636
- # output the activity graph
637
- g = Gruff::StackedBar.new
638
- g.title = sprintf("Activity: %s", opts[:command][:metrics][:title])
639
- g.labels = timeline.to_hash
640
- g.data('Resolved', data['CLOSED'])
641
- x = []
642
- (0..data['ASSIGNED'].length-1).each do |i|
643
- x[i] = data['ASSIGNED'][i] + data['MODIFIED'][i] + data['ON_QA'][i]
644
- end
645
- g.data('Unresolved', x)
646
- a = []
647
- (0..data['OPEN'].length-1).each do |i|
648
- a[i] = data['OPEN'][i] - x[i]
649
- end
650
- g.data('non-activity bugs', a)
651
- g.write(sprintf("activity-%s", opts[:command][:metrics][:output]))
652
- end # def do
653
-
654
- private
655
-
656
- def real_do(argv, opts)
657
- conf = read_config(opts)
658
- conf.freeze
659
- argv.each do |prefix|
660
- unless conf.include?(prefix) then
661
- raise RuntimeError, sprintf("No host information for %s", prefix)
662
- end
663
-
664
- info = conf[prefix]
665
- uri = URI.parse(info[:URL])
666
- host = uri.host
667
- port = uri.port
668
- path = uri.path.empty? ? nil : uri.path
669
- login = opts[:command][:anonymous] == true ? nil : info[:User]
670
- pass = opts[:command][:anonymous] == true ? nil : info[:Password]
671
- proxy_host, proxy_port = get_proxy(info)
672
- timeout = opts[:timeout].nil? ? 60 : opts[:timeout]
673
-
674
- xmlrpc = Bugzilla::XMLRPC.new(host, port, path, proxy_host, proxy_port, timeout)
675
- user = Bugzilla::User.new(xmlrpc)
676
- user.session(login, pass) do
677
- bug = Bugzilla::Bug.new(xmlrpc)
678
-
679
- opts[:command][:query][:product].map! {|x| info.include?(:ProductAliases) && info[:ProductAliases].include?(x) ? info[:ProductAliases][x] : x} if opts[:command][:query].include?(:product)
680
-
681
- ts = opts[:command][:metrics][:begin_date] || Time.utc(Time.new.year, 1, 1)
682
- te = opts[:command][:metrics][:end_date] || Time.utc(Time.new.year+1, 1, 1) - 1
683
- if opts[:command][:metrics][:x_coordinate] == :weekly then
684
- # align to the week
685
- d = Date.new(ts.year, ts.month, ts.day)
686
- ds = Date.commercial(d.year, d.cweek, 1)
687
- d = Date.new(te.year, te.month, te.day)
688
- de = Date.commercial(d.year, d.cweek, 7)
689
- ts = Time.utc(ds.year, ds.month, ds.day)
690
- te = Time.utc(de.year, de.month, de.day)
691
- end
692
-
693
- searchopts = opts[:command][:query].clone
694
-
695
- @plugin.run(:pre, host, :metrics, searchopts, opts[:metrics])
696
-
697
- if searchopts == opts[:command][:query] then
698
- raise NoMethodError, "No method to deal with the query"
699
- end
700
-
701
- while ts < te do
702
- searchopts = opts[:command][:query].clone
703
-
704
- # don't rely on the status to deal with NEW bugs.
705
- # unable to estimate the case bugs closed quickly
706
- if opts[:command][:metrics][:x_coordinate] == :weekly then
707
- d = Date.new(ts.year, ts.month, ts.day)
708
- de = Date.commercial(d.year, d.cweek, 7)
709
- drange = [ts, Time.utc(de.year, de.month, de.day, 23, 59, 59)]
710
- else
711
- drange = [ts, Time.utc(ts.year, ts.month + 1, 1) - 1]
712
- end
713
- searchopts[:creation_time] = drange
714
-
715
- @plugin.run(:pre, host, :metrics, searchopts)
716
-
717
- result = bug.search(searchopts)
718
-
719
- @plugin.run(:post, host, :search, result)
720
-
721
- new = result.include?('bugs') ? result['bugs'].length : 0
722
-
723
- # for open bugs
724
- # what we are interested in here would be how many bugs still keeps open.
725
- searchopts = opts[:command][:query].clone
726
- searchopts[:last_change_time] = drange
727
- searchopts[:status] = '__open__'
728
-
729
- @plugin.run(:pre, host, :metrics, searchopts)
730
-
731
- result = bug.search(searchopts)
732
-
733
- @plugin.run(:post, host, :search, result)
734
-
735
- assigned = result.include?('bugs') ? result['bugs'].map{|x| x['status'] == 'ASSIGNED' ? x : nil}.compact.length : 0
736
- modified = result.include?('bugs') ? result['bugs'].map{|x| x['status'] == 'MODIFIED' ? x : nil}.compact.length : 0
737
- on_qa = result.include?('bugs') ? result['bugs'].map{|x| x['status'] == 'ON_QA' ? x : nil}.compact.length : 0
738
-
739
- # send a separate query for closed.
740
- # just counting CLOSED the above is meaningless.
741
- # what we are interested in here would be how much bugs are
742
- # actually closed, but not how many closed bugs one worked on.
743
- searchopts = opts[:command][:query].clone
744
- searchopts[:last_change_time] = drange
745
- searchopts[:status] = 'CLOSED'
746
-
747
- @plugin.run(:pre, host, :metrics, searchopts)
748
-
749
- result = bug.search(searchopts)
750
-
751
- @plugin.run(:post, host, :search, result)
752
-
753
- closed = result.include?('bugs') ? result['bugs'].length : 0
754
-
755
- # obtain the information for open bugs that closed now
756
- searchopts = opts[:command][:query].clone
757
- searchopts[:status] = 'CLOSED'
758
- searchopts[:metrics_closed_after] = drange[1] + 1
759
-
760
- @plugin.run(:pre, host, :metrics, searchopts)
761
-
762
- result = bug.search(searchopts)
763
-
764
- @plugin.run(:post, host, :search, result)
765
-
766
- open_bugs = result.include?('bugs') ? result['bugs'].length : 0
767
-
768
- # obtain the information for open bugs
769
- searchopts = opts[:command][:query].clone
770
- searchopts[:metrics_not_closed] = drange[1]
771
-
772
- @plugin.run(:pre, host, :metrics, searchopts)
773
-
774
- result = bug.search(searchopts)
775
-
776
- @plugin.run(:post, host, :search, result)
777
-
778
- open_bugs += result.include?('bugs') ? result['bugs'].length : 0
779
-
780
- yield ts, new, assigned, modified, on_qa, closed, open_bugs
781
-
782
- ts = drange[1] + 1
783
- end
784
- end
785
- end
786
- end # def real_do
787
-
788
- end # class Metrics
789
-
790
- end # module BzConsole
791
-
792
- begin
793
- opts = {}
794
- opts[:command] = {}
795
- subargv = []
796
-
797
- o = ARGV.options do |opt|
798
- opt.banner = sprintf("Usage: %s [global options] <command> ...", File.basename(__FILE__))
799
- opt.separator ""
800
- opt.separator "Global options:"
801
- opt.on('-c', '--config=FILE', 'read FILE as the configuration file.') {|v| opts[:config] = v}
802
- opt.on('-t', '--timeout=SEC', 'Set XMLRPC timeout in a second.') {|v| opts[:timeout] = v.to_i}
803
- opt.on('-h', '--help', 'show this message') {|x| opts[:help] = true}
804
-
805
- cmds = BzConsole.constants.sort.map {|x| (k = eval("BzConsole::#{x}")).class == Class && x != "CommandTemplate" ? x.downcase : nil}.compact
806
-
807
- subargv = opt.order(ARGV);
808
-
809
- command = subargv[0]
810
-
811
- if subargv.length > 0 then
812
- n = cmds.index(command)
813
- unless n.nil? then
814
- opts[:instance] = eval("BzConsole::#{cmds[n].capitalize}.new(Bugzilla::Plugin::Template.new)")
815
- subargv = opts[:instance].parse(opt, subargv[1..-1], opts[:command])
816
- else
817
- STDERR.printf("E: Unknown command: %s\n", subargv[0])
818
- STDERR.printf(" Available commands: %s\n", cmds.join(' '))
819
- exit 1
820
- end
821
- else
822
- opt.separator ""
823
- opt.separator "Available commands:"
824
- opt.separator sprintf(" %s", cmds.join(' '))
825
- end
826
-
827
- if opts[:instance].nil? && subargv.length == 0 ||
828
- opts[:help] == true ||
829
- subargv.length < opts[:instance].n_args then
830
- puts opt.help
831
- exit
832
- end
833
- end
834
-
835
- opts[:instance].do(subargv, opts)
836
- end