ruby-issues 0.1.0 → 0.1.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.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/bin/issues +376 -159
  3. data/bin/todos +1 -0
  4. metadata +15 -22
  5. data/bin/todos +0 -551
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f6deed01a2a59cd3b896a927a85b51eae83ca73c
4
+ data.tar.gz: 1812367238a2c59990e949bbe4070ec5c217847b
5
+ SHA512:
6
+ metadata.gz: 9b26f17dbae9bfa96b8316fba969aa06366455678addce3bd383e23f6bc18862639ff031d8041b2a69ceae0a6816cdf2a5bfac775817731ba76f19a8b6ba945b
7
+ data.tar.gz: cad3041f6a80b68ecf71e0f8d4048ce552cbc57b34cea295191dd7a0cdfa921e60a813c45e8a4a9ff79f084233a152a0c6a3dfb042a96ebfa2d48bfcc5a3f2e9
data/bin/issues CHANGED
@@ -1,30 +1,56 @@
1
1
  #!/usr/bin/env ruby
2
2
  # encoding: utf-8
3
- #======================================================================================================================#
4
-
3
+ #===========================================================================================
5
4
  require 'rubygems'
6
5
  require 'fattr'
7
6
  require 'io/console'
8
- require 'SecureRandom'
7
+ require 'securerandom'
9
8
  require 'PrettyComment'
10
9
  require 'trollop'
11
- require 'tempfile'
12
10
  require 'yaml'
11
+ require 'git'
13
12
 
14
13
  #YAML::ENGINE.yamler = 'psych'
15
14
 
16
- #======================================================================================================================#
15
+ #===========================================================================================
17
16
 
17
+ def git_get_current_branch()
18
+ current_branch = `git branch`.split(/\n/).find{ |l| l.start_with?("*") }
19
+
20
+ if current_branch
21
+ current_branch =~ /\*\s+(.+)/
22
+ $~[1]
23
+ end
24
+ end
18
25
 
26
+ #-------------------------------------------------------------------------------------------
27
+
28
+ def is_issue_branch()
29
+ (git_get_current_branch() =~ /(feature|improvement|bug|task)\/[a-f0-9]{6}/) != nil
30
+ end
19
31
 
20
- #======================================================================================================================#
32
+ #-------------------------------------------------------------------------------------------
33
+
34
+ def max(a,b)
35
+ a > b ? a : b
36
+ end
37
+
38
+ #-------------------------------------------------------------------------------------------
39
+
40
+ def edit_string(aString)
41
+ tempfile_name = "/tmp/issues-" << SecureRandom.hex.force_encoding("UTF-8")
42
+ File.write(tempfile_name, aString)
43
+ system("$EDITOR #{tempfile_name}")
44
+ new_string = File.read(tempfile_name)
45
+ File.unlink(tempfile_name)
46
+ new_string
47
+ end
48
+
49
+ #===========================================================================================
21
50
 
22
51
  class LogEntry
23
52
  fattr :date, :message
24
53
 
25
-
26
- #----------------------------------------------------------------------------------------------------------------------#
27
-
28
54
  def initialize(message)
29
55
  @date = Time.new
30
56
  @message = message.dup
@@ -32,21 +58,19 @@ class LogEntry
32
58
  end
33
59
 
34
60
 
35
- #======================================================================================================================#
61
+ #===========================================================================================
36
62
 
37
63
  class Issue
38
- fattr :id, :created, :type, :title, :description, :status
64
+ fattr :id, :created, :type, :title, :description, :status, :milestone, :estimate
39
65
  attr_accessor :history
40
66
 
41
-
42
- #----------------------------------------------------------------------------------------------------------------------#
67
+ #-------------------------------------------------------------------------------------------
43
68
 
44
69
  def initialize
45
70
  @history = []
46
71
  end
47
72
 
48
-
49
- #----------------------------------------------------------------------------------------------------------------------#
73
+ #-------------------------------------------------------------------------------------------
50
74
 
51
75
  def self.createNewIssue(title, type="bug")
52
76
  newIssue = Issue.new
@@ -59,100 +83,103 @@ class Issue
59
83
  newIssue
60
84
  end
61
85
 
62
-
63
- #----------------------------------------------------------------------------------------------------------------------#
86
+ #-------------------------------------------------------------------------------------------
64
87
 
65
88
  def copy_from(a_issue)
66
89
  self.class.fattrs.each do |a|
67
- a_issue.send(a) && self.send(a, a_issue.send(a).dup)
90
+ value = a_issue.send(a)
91
+ new_value = value.is_a?(Numeric) || value == nil ? value : value.dup
92
+ a_issue.send(a) && self.send(a, new_value)
68
93
  end
69
94
  end
70
-
71
95
 
72
- #----------------------------------------------------------------------------------------------------------------------#
96
+ #-------------------------------------------------------------------------------------------
73
97
 
74
98
  def log(message)
75
99
  @history ||= []
76
100
  @history << LogEntry.new(message)
77
101
  end
78
102
 
79
-
80
-
81
- #----------------------------------------------------------------------------------------------------------------------#
103
+ #-------------------------------------------------------------------------------------------
82
104
 
83
- def format_verbose
84
- puts PrettyComment.separator
85
- puts PrettyComment.comment("#{@id[0,6]} #{@type.capitalize} (#{@status}) #{@created.to_s[0,16]}")
86
- puts PrettyComment.comment("")
87
- puts PrettyComment.format_line(@title, "#", false, "#")
105
+ def format_verbose(opts)
106
+ milestone = @milestone ? "#{@milestone}" : "NO MILESTONE"
107
+ estimate = @estimate ? "#{@estimate}h" : "NO ESTIMATE"
108
+ milestone_estimate = "[#{estimate}, #{milestone}]"
109
+
110
+ result = []
111
+ result << PrettyComment.separator
112
+ result << PrettyComment.comment("#{@id[0,6]} #{@type.capitalize} (#{@status}) #{milestone_estimate} #{@created.to_s[0,16]}")
113
+ result << PrettyComment.comment("")
114
+ result << PrettyComment.format_line(@title, "#", false, "#")
88
115
 
89
116
  if @description
90
- puts PrettyComment.sub_heading("Description:")
117
+ result << PrettyComment.sub_heading("Description:")
91
118
  @description.split("\n").each do |l|
92
- puts PrettyComment.format_line(@description, "#", false, "#")
119
+ result << PrettyComment.format_line(l, "#", false, "#")
93
120
  end
94
121
  end
95
122
 
96
123
  if @history && @history.count > 0
97
- puts PrettyComment.sub_heading("Log:")
98
- @history.each { |l| puts PrettyComment.format_line("#{l.message}", "# #{l.date.to_s[0,16]}", true, "#", "#") }
124
+ result << PrettyComment.sub_heading("Log:")
125
+ @history.each { |l| result << PrettyComment.format_line("#{l.message}", "# #{l.date.to_s[0,16]}", true, "#", "#") }
99
126
  end
100
127
 
101
- puts PrettyComment.separator
102
- puts
103
- puts
128
+ result << PrettyComment.separator
129
+ result << ""
130
+ result << ""
131
+
132
+ result.join("\n")
104
133
  end
105
134
 
135
+ #-------------------------------------------------------------------------------------------
106
136
 
107
- #----------------------------------------------------------------------------------------------------------------------#
137
+ def format_list(opts)
138
+ entry = @title.dup
139
+ info_string = "#{short_id} (#{@type[0,1].capitalize})"
108
140
 
109
- def format_list
110
- puts PrettyComment.format_line(@title, "#{short_id} (#{@type[0,1].capitalize})", true)
111
- end
141
+ if opts[:list_milestone] == true
142
+ width = opts[:milestone_column_width]
143
+ info_string << " " << (@milestone ? @milestone.ljust(width) : "UNASSIGNED".ljust(width))
144
+ end
112
145
 
146
+ if opts[:list_estimate] == true
147
+ info_string << " " << (@estimate ? sprintf("%.2fh", @estimate).rjust(4) : " ")
148
+ end
113
149
 
114
- #----------------------------------------------------------------------------------------------------------------------#
150
+ #suffix = estimate_milestone.count > 0 ? "[#{estimate_milestone.join(' => ')}]" : ""
151
+
152
+ PrettyComment.format_line(entry, info_string, true)
153
+ end
115
154
 
116
- def edit_description
117
- file = Tempfile.new('issues')
118
- file.write(@description)
119
- file.close
120
- system("$EDITOR #{file.path}")
155
+ #-------------------------------------------------------------------------------------------
121
156
 
122
- file.open
123
- new_description = file.read
157
+ def edit_description
158
+ new_description = edit_string(@description)
124
159
 
125
160
  if new_description != @description
126
161
  @description = new_description.dup
127
162
  return true
128
163
  end
129
-
164
+
130
165
  return false
131
166
  end
132
167
 
133
-
134
- #----------------------------------------------------------------------------------------------------------------------#
168
+ #-------------------------------------------------------------------------------------------
135
169
 
136
170
  def edit_all
137
- edit_file = file = Tempfile.new('issues')
138
- file.write(self.to_yaml)
139
- file.close
171
+ original_yaml = self.to_yaml
172
+ new_yaml = edit_string(original_yaml)
140
173
 
141
- system("$EDITOR #{file.path}")
142
-
143
- file.open
144
-
145
- if (file.read != self.to_yaml)
146
- new_issue = YAML::load_file(file.path)
147
- self.copy_from(new_issue)
174
+ if (new_yaml != original_yaml)
175
+ self.copy_from(YAML::parse(new_string))
148
176
  return true
149
177
  end
150
178
 
151
179
  return false
152
180
  end
153
181
 
154
-
155
- #----------------------------------------------------------------------------------------------------------------------#
182
+ #-------------------------------------------------------------------------------------------
156
183
 
157
184
  def short_id
158
185
  @id[0,6]
@@ -160,30 +187,27 @@ class Issue
160
187
 
161
188
  end
162
189
 
163
-
164
- #======================================================================================================================#
165
- # Main Program Logic
166
- #======================================================================================================================#
190
+ #===========================================================================================
191
+ # Main Program Logic
192
+ #===========================================================================================
167
193
 
168
194
  class IssuesDb
169
195
  fattr :issues_array
170
196
 
171
- #----------------------------------------------------------------------------------------------------------------------#
197
+ #-------------------------------------------------------------------------------------------
172
198
 
173
199
  def initialize(database_file)
174
200
  @database_file = database_file
175
201
  @issues_array = FileTest.exists?(database_file) && YAML.load_file(database_file) || []
176
202
  end
177
203
 
178
-
179
- #----------------------------------------------------------------------------------------------------------------------#
204
+ #-------------------------------------------------------------------------------------------
180
205
 
181
206
  def select_issues(&select_proc)
182
207
  return @issues_array.select(&select_proc)
183
208
  end
184
209
 
185
-
186
- #----------------------------------------------------------------------------------------------------------------------#
210
+ #-------------------------------------------------------------------------------------------
187
211
 
188
212
  def select_issue(&select_proc)
189
213
  result = select_issues(&select_proc)
@@ -204,34 +228,32 @@ class IssuesDb
204
228
  nil
205
229
  end
206
230
 
207
-
208
- #----------------------------------------------------------------------------------------------------------------------#
231
+ #-------------------------------------------------------------------------------------------
209
232
 
210
233
  def has_issue(issue_id)
211
234
  @issues_array.any? { |issue| issue.id.start_with?(issue_id) }
212
235
  end
213
236
 
214
-
215
- #----------------------------------------------------------------------------------------------------------------------#
237
+ #-------------------------------------------------------------------------------------------
216
238
 
217
239
  def save_db()
218
240
  FileTest.exists?('.issues') || Dir.mkdir('.issues')
219
241
  File.open(@database_file, 'w' ) { |out| YAML.dump(@issues_array, out) }
220
242
  end
221
243
 
222
-
223
- #----------------------------------------------------------------------------------------------------------------------#
244
+ #-------------------------------------------------------------------------------------------
224
245
 
225
246
  def determine_issue_type(opts)
226
- issue_types = %w{bug improvement task}
227
- issue_type = nil
228
- issue_types.each { |t| opts[t.to_sym] == true && issue_type = t }
247
+ issue_types = %w{bug improvement task feature}
248
+ issue_type = opts[:type] ? opts[:type].downcase : nil
229
249
 
230
250
  issue_type && (return issue_type)
231
251
 
232
252
  case opts[:title]
233
- when /\b(improve|implement)/i
253
+ when /\b(improve)/i
234
254
  "improvement"
255
+ when /\b(implement)/i
256
+ "feature"
235
257
  when /\b(fix|bug|crash)/i
236
258
  "bug"
237
259
  else
@@ -239,47 +261,100 @@ class IssuesDb
239
261
  end
240
262
  end
241
263
 
242
-
243
- #----------------------------------------------------------------------------------------------------------------------#
264
+ #-------------------------------------------------------------------------------------------
244
265
 
245
266
  def create_issue(opts)
246
267
  type = determine_issue_type(opts)
247
268
  new_issue = Issue.createNewIssue(opts[:title], type)
269
+ new_issue.milestone = opts[:milestone]
270
+ new_issue.estimate = opts[:estimate]
248
271
  @issues_array << new_issue
249
272
  save_db()
250
273
  puts "Created issue #{new_issue.short_id} #{new_issue.title}"
251
274
  end
252
275
 
276
+ #-------------------------------------------------------------------------------------------
277
+
278
+ def select_issues_with_opts(opts)
279
+ did_select_issue_types = opts[:type]
280
+ status_regex = opts[:all] ? /^(open|resolved|duplicate|wontfix)/ : /^open$/
281
+ did_select_milestone = opts[:milestone] != nil
282
+ milestone = opts[:milestone]
283
+
284
+ return @issues_array.select do |issue|
285
+ (status_regex =~ issue.status) \
286
+ && (issue.type == opts[:type] || !did_select_issue_types) \
287
+ && (issue.milestone == opts[:milestone] \
288
+ || !did_select_milestone \
289
+ || milestone.end_with?(".") && issue.milestone.start_with?(milestone)\
290
+ || milestone.start_with?(".") && issue.milestone.end_with?(milestone)) \
291
+ && (issue.milestone == nil || opts["no-milestone"] == false)
292
+ end
293
+ end
294
+
295
+ #-------------------------------------------------------------------------------------------
296
+
297
+ def has_open_issues_with_milestone?
298
+ @issues_array.any? { |issue| issue.status == "open" && issue.milestone != nil }
299
+ end
300
+
301
+ #-------------------------------------------------------------------------------------------
302
+
303
+ def has_open_issues_with_estimate?
304
+ @issues_array.any? { |issue| issue.status == "open" && issue.estimate != nil }
305
+ end
306
+
307
+ #-------------------------------------------------------------------------------------------
308
+
309
+ def milestone_column_width(array = @issues_array)
310
+ result = 0
311
+
312
+ array.each { |issue| issue.milestone != nil && result = max(issue.milestone.length, result) }
313
+
314
+ result
315
+ end
316
+
317
+ #-------------------------------------------------------------------------------------------
318
+
319
+ def sort_none(a, b)
320
+ return 0
321
+ end
322
+ #-------------------------------------------------------------------------------------------
323
+
324
+ def sort_milestone(a, b)
325
+ return a.milestone <=> b.milestone
326
+ end
253
327
 
254
- #----------------------------------------------------------------------------------------------------------------------#
328
+ #-------------------------------------------------------------------------------------------
255
329
 
256
330
  def list_issues(opts)
257
331
  if opts[:issue_id]
258
- list_issue(opts[:issue_id])
332
+ list_issue(opts)
333
+ sort_proc = "sort_milestone"
259
334
  else
260
335
  list_proc = opts[:verbose] ? "format_verbose" : "format_list"
261
-
262
- issue_types = %w{bug improvement task}
263
- did_select_issue_types = opts.any? { |key,value| issue_types.include?(key.to_s.chomp('s')) && value == true }
264
- did_select_issue_types && issue_types.delete_if { |issue_type| opts["#{issue_type}s".to_sym] == false }
265
-
266
- status_regex = opts[:all] ? /^(open|resolved|duplicate|wontfix)/ : /^open$/
267
-
268
- issues = @issues_array.select { |issue| (status_regex =~ issue.status) && issue_types.include?(issue.type) }
269
- issues.each {|issue| issue.method(list_proc).call}
336
+ select_issues_with_opts(opts).\
337
+ sort {|a,b| sort_milestone(a,b)}.\
338
+ each {|issue| puts issue.method(list_proc).call(opts)}
270
339
  end
271
340
  end
272
341
 
342
+ #-------------------------------------------------------------------------------------------
273
343
 
274
- #----------------------------------------------------------------------------------------------------------------------#
275
-
276
- def list_issue(issue_id)
344
+ def list_issue(opts)
345
+ issue_id = opts[:issue_id]
277
346
  issue = select_issue {|i| i.id.start_with?(issue_id) }
278
- issue.format_verbose()
347
+ puts issue.format_verbose(opts)
279
348
  end
280
349
 
350
+ #-------------------------------------------------------------------------------------------
351
+
352
+ def count_issues(opts)
353
+ num_issues = select_issues_with_opts(opts).count
354
+ puts "#{num_issues} issues."
355
+ end
281
356
 
282
- #----------------------------------------------------------------------------------------------------------------------#
357
+ #-------------------------------------------------------------------------------------------
283
358
 
284
359
  def resolve_issues(opts)
285
360
  issue_id = opts[:issue_id]
@@ -293,6 +368,8 @@ class IssuesDb
293
368
  ["resolved", "Resolved"]
294
369
  when "wontfix"
295
370
  ["wontfix", "Won't fix"]
371
+ when "cantreproduce"
372
+ ["cantreproduce", "Can't reproduce"]
296
373
  when "duplicate"
297
374
  ["duplicate(#{duplicate_of_id})", "Duplicate"]
298
375
  end
@@ -300,16 +377,25 @@ class IssuesDb
300
377
  resolved_issue.status = status
301
378
  resolved_issue.log "Changed status to #{status}"
302
379
 
303
- message = "#{message} issue #{resolved_issue.short_id} #{resolved_issue.title}"
380
+ message = "#{message} issue #{resolved_issue.short_id}: #{resolved_issue.title}"
304
381
  puts message
305
382
 
306
-
307
383
  save_db()
308
- opts[:commit] && exec("git commit -a -m \"#{message}\"")
309
- end
310
384
 
385
+ if opts[:commit]
386
+ system "git add .issues"
387
+ system "git commit -m \"#{message}\""
388
+ end
389
+
390
+ if opts[:close_branch] == true
391
+ issue_branch = git_get_current_branch()
392
+ system "git checkout dev"
393
+ system "git merge #{issue_branch}"
394
+ system "git branch -d #{issue_branch}"
395
+ end
396
+ end
311
397
 
312
- #----------------------------------------------------------------------------------------------------------------------#
398
+ #-------------------------------------------------------------------------------------------
313
399
 
314
400
  def delete_issues(opts)
315
401
  delete_issues = []
@@ -339,8 +425,7 @@ class IssuesDb
339
425
 
340
426
  end
341
427
 
342
-
343
- #----------------------------------------------------------------------------------------------------------------------#
428
+ #-------------------------------------------------------------------------------------------
344
429
 
345
430
  def edit_issue(opts)
346
431
  issue_id = opts[:issue_id]
@@ -357,35 +442,123 @@ class IssuesDb
357
442
  did_change_issue && save_db()
358
443
  end
359
444
 
445
+ #-------------------------------------------------------------------------------------------
446
+
447
+ def select_issue_for_estimate?(issue, milestone)
448
+ issue.milestone != nil \
449
+ && issue.status == "open" \
450
+ && (issue.milestone == milestone \
451
+ || milestone.start_with?(".") && issue.milestone.end_with?(milestone) \
452
+ || milestone.end_with?(".") && issue.milestone.start_with?(milestone) )
453
+ end
454
+
455
+ #-------------------------------------------------------------------------------------------
456
+
457
+ def get_milestones_for_pattern(pattern)
458
+ result_hash = {}
459
+ issues_array.each { |i| select_issue_for_estimate?(i, pattern) && result_hash[i.milestone] = true }
460
+ result_hash.keys.sort
461
+ end
462
+
463
+ #-------------------------------------------------------------------------------------------
360
464
 
361
- #----------------------------------------------------------------------------------------------------------------------#
465
+ def estimate(opts)
466
+ milestone = opts[:milestone]
467
+ total = 0.0
468
+
469
+ issues_to_estimate = @issues_array.select { |i| select_issue_for_estimate?(i, milestone) }
470
+ issues_without_estimates = @issues_array.select { |i| select_issue_for_estimate?(i, milestone) && !i.estimate }
471
+
472
+ if issues_to_estimate.empty?
473
+ puts "No open issues found for milestone " << milestone << "!"
474
+ exit
475
+ end
476
+
477
+ if issues_without_estimates.count > 0
478
+ puts PrettyComment.h3("Warning: Found issues without estimates")
479
+ issues_without_estimates.each {|i| puts i.format_list(opts)}
480
+ puts PrettyComment.separator("-")
481
+ puts
482
+ end
483
+
484
+ issues_to_estimate.each { |i| (i.estimate && total += i.estimate) }
485
+
486
+ milestones = get_milestones_for_pattern(milestone)
362
487
 
363
- def set_type(opts)
364
- new_type = opts[:new_type]
488
+ result_str = "Milestone" << (milestones.length > 1 ? "s" : "")
489
+
490
+ puts result_str << " #{milestones.join(", ")} will take #{total} man-hours to complete."
491
+ puts
492
+ end
493
+
494
+ #-------------------------------------------------------------------------------------------
495
+
496
+ def update(opts)
497
+ new_type = opts[:type]
498
+ new_milestone = opts[:milestone]
499
+ new_estimate = opts[:estimate]
500
+
365
501
  opts[:issue_ids].each do |issue_id|
502
+ did_update = false
366
503
  issue = select_issue { |i| i.id.start_with?(issue_id) }
367
- if issue.type != new_type
368
- issue.type = new_type
369
- issue.log("Changed typed to #{issue.type}")
370
- issue.format_list
371
- else
372
- puts "Issue #{issue.short_id} already of type #{issue.type}."
504
+
505
+ if new_type
506
+ if issue.type != new_type
507
+ issue.type = new_type
508
+ issue.log("Changed type to #{issue.type}")
509
+ did_update = true
510
+ else
511
+ puts "Issue #{issue.short_id} already of type #{issue.type}."
512
+ end
513
+ end
514
+
515
+ if new_milestone
516
+ if issue.milestone != new_milestone
517
+ issue.milestone = new_milestone
518
+ issue.log("Changed milestone to #{issue.milestone}")
519
+ did_update = true
520
+ else
521
+ puts "Issue #{issue.short_id} already in milestone #{issue.milestone}."
522
+ end
523
+ end
524
+
525
+ if new_estimate
526
+ if issue.estimate != new_estimate
527
+ issue.estimate = new_estimate
528
+ issue.log("Changed estimate to #{issue.estimate}")
529
+ did_update = true
530
+ else
531
+ puts "Issue #{issue.short_id} already has estimate #{issue.estimate}h."
532
+ end
533
+ end
534
+
535
+ if did_update == true
536
+ puts issue.format_list(opts)
537
+ save_db()
373
538
  end
374
539
  end
375
-
376
- save_db()
377
540
  end
378
541
 
542
+ #-------------------------------------------------------------------------------------------
379
543
 
380
- #----------------------------------------------------------------------------------------------------------------------#
544
+ def start_branch(opts)
545
+ git_get_current_branch() == "dev" || Trollop::die("Must be on dev branch to start new issue branch.")
546
+
547
+ issue_id = opts[:issue_id]
548
+ issue = select_issue{|i| i.id.start_with?(issue_id) && i.status == "open"}
549
+
550
+ system("git checkout -b #{issue.type}/#{issue.short_id}")
551
+ end
552
+
553
+ #-------------------------------------------------------------------------------------------
381
554
 
382
555
 
383
556
  end
384
557
 
385
558
 
386
- #======================================================================================================================#
559
+ #===========================================================================================
387
560
  # Command Line Parsing
388
- #======================================================================================================================#
561
+ #===========================================================================================
389
562
 
390
563
  def get_issue_ids(num_ids, usage)
391
564
  result = []
@@ -404,21 +577,24 @@ def get_issue_ids(num_ids, usage)
404
577
  end
405
578
  end
406
579
 
407
-
408
- #----------------------------------------------------------------------------------------------------------------------#
580
+ #-------------------------------------------------------------------------------------------
409
581
 
410
582
  EXECUTABLE_NAME=File.basename($0)
411
583
  DATABASE_NAME= ".issues/" << EXECUTABLE_NAME << ".yaml"
412
584
 
413
585
  SUB_COMMANDS = {
414
- "list" => "list issues",
415
- "create" => "create a new issue",
416
- "resolve" => "set status of issue to \"resolved\"",
417
- "wontfix" => "set status of issue to \"won't fix\"",
418
- "duplicate" => "mark issue as duplicate of another issue",
419
- "edit" => "edit an existing issue",
420
- "delete" => "delete an issue",
421
- "set-type" => "set the type of an issue"}
586
+ "list" => "list issues",
587
+ "count" => "count issues",
588
+ "create" => "create a new issue",
589
+ "resolve" => "set status of issue to \"resolved\"",
590
+ "wontfix" => "set status of issue to \"won't fix\"",
591
+ "cantreproduce" => "set status of issue to \"can't reproduce\"",
592
+ "duplicate" => "mark issue as duplicate of another issue",
593
+ "edit" => "edit an existing issue",
594
+ "delete" => "delete an issue",
595
+ "estimate" => "calculate estimated man-hours for a milestone",
596
+ "update" => "update issue fields.",
597
+ "startbranch" => "start a new git branch with to work on issue"}
422
598
 
423
599
  LeftFieldLength =
424
600
  SUB_COMMANDS.collect { |key, value| key.length }.max
@@ -451,31 +627,33 @@ cmd ||= 'list'
451
627
 
452
628
  cmd_opts = {}
453
629
 
454
- if cmd == 'list'
630
+ if cmd == 'list' || cmd == 'count'
455
631
  cmd_opts =
456
632
  Trollop::options do
457
- opt :all, "list all issues", :short => 'a'
458
- opt :newest, "list newest issues first"
459
- opt :oldest, "list oldest issues first"
460
- opt :verbose, "verbose list of issues", :short => 'v'
461
- opt :bugs, "list bugs", :short => 'b'
462
- opt :improvements, "list improvements", :short => 'i'
463
- opt :tasks, "list tasks", :short => 't'
633
+ opt :all, "#{cmd} all issues", :short => 'a'
634
+ opt :newest, "#{cmd} newest issues first" if cmd == 'list'
635
+ opt :oldest, "#{cmd} oldest issues first" if cmd == 'list'
636
+ opt :verbose, "verbose list of issues", :short => 'v' if cmd == 'list'
637
+ opt :type, "#{cmd} issues of given type (bug, improvement, task, feature)", :short => 't', :type => String
638
+ opt :milestone, "#{cmd} issues for given milestone", :short => 'm', :type => String
639
+ opt "no-milestone", "#{cmd} all issues not assigned to any milestone"
464
640
  end
465
641
 
466
- ARGV.count > 0 && cmd_opts[:issue_id] = get_issue_ids(1, "list ID")
642
+ if cmd == 'list'
643
+ ARGV.count > 0 && cmd_opts[:issue_id] = get_issue_ids(1, "list ID")
644
+ end
467
645
 
468
646
  elsif cmd == "create"
469
647
  cmd_opts =
470
648
  Trollop::options do
471
- opt :bug, "create a bug", :short => 'b'
472
- opt :improvement, "create an improvement", :short => 'i'
473
- opt :task, "create a task", :short => 't'
649
+ opt :type, "create issue of specific type (e.g. bug, feature, improvement, task)", :short => 't', :type => String
650
+ opt :milestone, "specify the milestone of the new issue", :short => 'm', :type => String
651
+ opt :estimate, "specify the estimated effort", :short => 'e', :type => Float
474
652
  end
475
653
  cmd_opts[:title] = ARGV.shift || Trollop::die( "Please enter a title for the new issue!")
476
654
 
477
655
 
478
- elsif cmd == "resolve" || cmd == "wontfix" || cmd == "duplicate"
656
+ elsif cmd == "resolve" || cmd == "wontfix" || cmd == "duplicate" || cmd == "cantreproduce"
479
657
  cmd_opts =
480
658
  Trollop::options do
481
659
  opt :commit, "do a git commit", :short => 'c'
@@ -483,6 +661,10 @@ elsif cmd == "resolve" || cmd == "wontfix" || cmd == "duplicate"
483
661
 
484
662
  if cmd == "duplicate"
485
663
  cmd_opts[:issue_id], cmd_opts[:duplicate_of_id] = get_issue_ids(2, "duplicate ID(issue) ID(duplicate of)")
664
+ elsif cmd == "resolve" && is_issue_branch()
665
+ cmd_opts[:issue_id] = git_get_current_branch().split('/')[1]
666
+ cmd_opts[:close_branch] = true
667
+ cmd_opts[:commit] = true
486
668
  else
487
669
  cmd_opts[:issue_id] = get_issue_ids(1, "#{cmd} [-c] ID")
488
670
  end
@@ -494,29 +676,56 @@ elsif cmd == "edit"
494
676
  opt :description, "edit the issue description", :short => 'd'
495
677
  end
496
678
  cmd_opts[:issue_id] = get_issue_ids(1, "edit ID")
497
-
498
-
499
- elsif cmd == "set-type"
679
+
680
+ elsif cmd == "estimate"
500
681
  Trollop::options do
501
682
  banner <<-EOL
502
683
  Usage:
503
684
  ------
504
- issues set-type {bug|improvement|task} ID
685
+ issues estimate MILESTONE
686
+
687
+ Options:
688
+ --------
689
+ EOL
690
+ end
691
+
692
+ ARGV.count > 0 || Trollop::die("Usage: issues estmiate MILSTONE")
693
+ cmd_opts[:milestone] = ARGV.shift
694
+
695
+ elsif cmd == "update"
696
+ my_banner = <<-EOL
697
+ Usage:
698
+ ------
699
+ issues update \
700
+ [--type (bug|improvement|task|feature)]\
701
+ [--milestone MILESTONE]\
702
+ [--estimate ESTIMATE]\
703
+ ID{,ID2,...}
505
704
 
506
705
  Options:
507
706
  --------
508
707
  EOL
708
+
709
+ cmd_opts = Trollop::options do
710
+ banner my_banner
711
+ opt :type, "update the issue type", :short => 't', :type => String
712
+ opt :milestone, "update the milestone", :short => 'm', :type => String
713
+ opt :estimate, "update the estimate", :short => 'e', :type => Float
509
714
  end
510
715
 
511
- new_type = ARGV.shift
512
- %w{bug improvement task}.include?(new_type) || Trollop::die("Please specify one of [bug, improvement, task] as new issue type")
513
- cmd_opts[:new_type] = new_type
514
- ARGV.count > 0 && cmd_opts[:issue_ids] = get_issue_ids(-1, "set-type {bug|improvement|task} ID")
716
+ cmd_opts[:type] && (%w{bug improvement task feature}.include?(cmd_opts[:type]) || Trollop::die("Please specify one of [bug, improvement, task, freature] as new issue type"))
717
+ cmd_opts[:type] || cmd_opts[:milestone] || cmd_opts[:estimate] || Trollop::die("You must specify one of (--type, --milestone, --estimate)")
718
+
719
+ ARGV.count > 0 && cmd_opts[:issue_ids] = get_issue_ids(-1, "set-type {bug|improvement|task|feature} ID")
515
720
 
516
721
 
517
722
  elsif cmd == "delete"
518
723
  cmd_opts[:issue_ids] = get_issue_ids(-1, "#{cmd} ID")
519
724
 
725
+
726
+ elsif cmd == "startbranch"
727
+ cmd_opts[:issue_id] = get_issue_ids(1, "list ID") || Trollop::die( "Please enter an id for the issue to work on in the new branch!")
728
+
520
729
  else
521
730
  Trollop::die "unknown command #{cmd.inspect}"
522
731
  end
@@ -525,27 +734,35 @@ end
525
734
  cmd_opts[:cmd] = cmd
526
735
 
527
736
 
528
- #======================================================================================================================#
529
- # Main
530
- #======================================================================================================================#
737
+ #===========================================================================================
738
+ # Main
739
+ #===========================================================================================
531
740
 
532
741
  Issues = IssuesDb.new(DATABASE_NAME)
742
+ cmd_opts[:list_estimate] = Issues.has_open_issues_with_estimate?()
743
+ cmd_opts[:milestone_column_width] = Issues.milestone_column_width()
744
+ cmd_opts[:list_milestone] = Issues.has_open_issues_with_milestone?()
533
745
 
534
746
  case cmd
535
747
  when "create"
536
748
  Issues.create_issue(cmd_opts)
537
749
  when "list"
538
750
  Issues.list_issues(cmd_opts)
539
- when "resolve", "wontfix", "duplicate"
751
+ when "count"
752
+ Issues.count_issues(cmd_opts)
753
+ when "resolve", "wontfix", "duplicate", "cantreproduce"
540
754
  Issues.resolve_issues(cmd_opts)
541
755
  when "edit"
542
756
  Issues.edit_issue(cmd_opts)
543
- when "set-type"
544
- Issues.set_type(cmd_opts)
757
+ when "estimate"
758
+ Issues.estimate(cmd_opts)
759
+ when "update"
760
+ Issues.update(cmd_opts)
545
761
  when "delete"
546
762
  Issues.delete_issues(cmd_opts)
763
+ when "startbranch"
764
+ Issues.start_branch(cmd_opts)
547
765
  end
548
766
 
549
-
550
- #======================================================================================================================#
767
+ #===========================================================================================
551
768