ruby-bugzilla 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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