ruby-issues 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
data/bin/todos ADDED
@@ -0,0 +1 @@
1
+ issues
metadata CHANGED
@@ -1,62 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-issues
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
5
- prerelease:
4
+ version: 0.1.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Wolfgang Steiner
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-06-20 00:00:00.000000000 Z
11
+ date: 2018-05-07 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: PrettyComment
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
19
  version: 0.1.2
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: 0.1.2
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: trollop
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - ">="
36
32
  - !ruby/object:Gem::Version
37
33
  version: 1.16.2
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - ">="
44
39
  - !ruby/object:Gem::Version
45
40
  version: 1.16.2
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: fattr
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - ">="
52
46
  - !ruby/object:Gem::Version
53
47
  version: 2.2.1
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ! '>='
52
+ - - ">="
60
53
  - !ruby/object:Gem::Version
61
54
  version: 2.2.1
62
55
  description: Issues is a lightweight, git-style command-line issue tracker.
@@ -70,27 +63,27 @@ files:
70
63
  - bin/issues
71
64
  - bin/todos
72
65
  homepage: https://github.com/WolfgangSteiner/Issues
73
- licenses: []
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
74
69
  post_install_message:
75
70
  rdoc_options: []
76
71
  require_paths:
77
72
  - lib
78
73
  required_ruby_version: !ruby/object:Gem::Requirement
79
- none: false
80
74
  requirements:
81
- - - ! '>='
75
+ - - ">="
82
76
  - !ruby/object:Gem::Version
83
77
  version: '0'
84
78
  required_rubygems_version: !ruby/object:Gem::Requirement
85
- none: false
86
79
  requirements:
87
- - - ! '>='
80
+ - - ">="
88
81
  - !ruby/object:Gem::Version
89
82
  version: '0'
90
83
  requirements: []
91
84
  rubyforge_project:
92
- rubygems_version: 1.8.23
85
+ rubygems_version: 2.5.1
93
86
  signing_key:
94
- specification_version: 3
87
+ specification_version: 4
95
88
  summary: Git-style issue tracker.
96
89
  test_files: []
data/bin/todos DELETED
@@ -1,551 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # encoding: utf-8
3
- #======================================================================================================================#
4
-
5
- require 'rubygems'
6
- require 'fattr'
7
- require 'io/console'
8
- require 'SecureRandom'
9
- require 'PrettyComment'
10
- require 'trollop'
11
- require 'tempfile'
12
- require 'yaml'
13
-
14
- #YAML::ENGINE.yamler = 'psych'
15
-
16
- #======================================================================================================================#
17
-
18
-
19
-
20
- #======================================================================================================================#
21
-
22
- class LogEntry
23
- fattr :date, :message
24
-
25
-
26
- #----------------------------------------------------------------------------------------------------------------------#
27
-
28
- def initialize(message)
29
- @date = Time.new
30
- @message = message.dup
31
- end
32
- end
33
-
34
-
35
- #======================================================================================================================#
36
-
37
- class Issue
38
- fattr :id, :created, :type, :title, :description, :status
39
- attr_accessor :history
40
-
41
-
42
- #----------------------------------------------------------------------------------------------------------------------#
43
-
44
- def initialize
45
- @history = []
46
- end
47
-
48
-
49
- #----------------------------------------------------------------------------------------------------------------------#
50
-
51
- def self.createNewIssue(title, type="bug")
52
- newIssue = Issue.new
53
- newIssue.id = SecureRandom.hex.force_encoding("UTF-8")
54
- newIssue.created = Time.new
55
- newIssue.title = title
56
- newIssue.status = "open"
57
- newIssue.type = type
58
- newIssue.history = [LogEntry.new("Issue created")]
59
- newIssue
60
- end
61
-
62
-
63
- #----------------------------------------------------------------------------------------------------------------------#
64
-
65
- def copy_from(a_issue)
66
- self.class.fattrs.each do |a|
67
- a_issue.send(a) && self.send(a, a_issue.send(a).dup)
68
- end
69
- end
70
-
71
-
72
- #----------------------------------------------------------------------------------------------------------------------#
73
-
74
- def log(message)
75
- @history ||= []
76
- @history << LogEntry.new(message)
77
- end
78
-
79
-
80
-
81
- #----------------------------------------------------------------------------------------------------------------------#
82
-
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, "#")
88
-
89
- if @description
90
- puts PrettyComment.sub_heading("Description:")
91
- @description.split("\n").each do |l|
92
- puts PrettyComment.format_line(@description, "#", false, "#")
93
- end
94
- end
95
-
96
- 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, "#", "#") }
99
- end
100
-
101
- puts PrettyComment.separator
102
- puts
103
- puts
104
- end
105
-
106
-
107
- #----------------------------------------------------------------------------------------------------------------------#
108
-
109
- def format_list
110
- puts PrettyComment.format_line(@title, "#{short_id} (#{@type[0,1].capitalize})", true)
111
- end
112
-
113
-
114
- #----------------------------------------------------------------------------------------------------------------------#
115
-
116
- def edit_description
117
- file = Tempfile.new('issues')
118
- file.write(@description)
119
- file.close
120
- system("$EDITOR #{file.path}")
121
-
122
- file.open
123
- new_description = file.read
124
-
125
- if new_description != @description
126
- @description = new_description.dup
127
- return true
128
- end
129
-
130
- return false
131
- end
132
-
133
-
134
- #----------------------------------------------------------------------------------------------------------------------#
135
-
136
- def edit_all
137
- edit_file = file = Tempfile.new('issues')
138
- file.write(self.to_yaml)
139
- file.close
140
-
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)
148
- return true
149
- end
150
-
151
- return false
152
- end
153
-
154
-
155
- #----------------------------------------------------------------------------------------------------------------------#
156
-
157
- def short_id
158
- @id[0,6]
159
- end
160
-
161
- end
162
-
163
-
164
- #======================================================================================================================#
165
- # Main Program Logic
166
- #======================================================================================================================#
167
-
168
- class IssuesDb
169
- fattr :issues_array
170
-
171
- #----------------------------------------------------------------------------------------------------------------------#
172
-
173
- def initialize(database_file)
174
- @database_file = database_file
175
- @issues_array = FileTest.exists?(database_file) && YAML.load_file(database_file) || []
176
- end
177
-
178
-
179
- #----------------------------------------------------------------------------------------------------------------------#
180
-
181
- def select_issues(&select_proc)
182
- return @issues_array.select(&select_proc)
183
- end
184
-
185
-
186
- #----------------------------------------------------------------------------------------------------------------------#
187
-
188
- def select_issue(&select_proc)
189
- result = select_issues(&select_proc)
190
-
191
- if result.count == 1
192
- return result[0]
193
-
194
- elsif result.count > 1
195
- puts "Found more than one issue that match this query:"
196
- result.each{|i| puts("#{i.id} #{i.title}")}
197
- exit
198
-
199
- else
200
- puts "Error: No issue found for query."
201
- exit
202
- end
203
-
204
- nil
205
- end
206
-
207
-
208
- #----------------------------------------------------------------------------------------------------------------------#
209
-
210
- def has_issue(issue_id)
211
- @issues_array.any? { |issue| issue.id.start_with?(issue_id) }
212
- end
213
-
214
-
215
- #----------------------------------------------------------------------------------------------------------------------#
216
-
217
- def save_db()
218
- FileTest.exists?('.issues') || Dir.mkdir('.issues')
219
- File.open(@database_file, 'w' ) { |out| YAML.dump(@issues_array, out) }
220
- end
221
-
222
-
223
- #----------------------------------------------------------------------------------------------------------------------#
224
-
225
- 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 }
229
-
230
- issue_type && (return issue_type)
231
-
232
- case opts[:title]
233
- when /\b(improve|implement)/i
234
- "improvement"
235
- when /\b(fix|bug|crash)/i
236
- "bug"
237
- else
238
- "task"
239
- end
240
- end
241
-
242
-
243
- #----------------------------------------------------------------------------------------------------------------------#
244
-
245
- def create_issue(opts)
246
- type = determine_issue_type(opts)
247
- new_issue = Issue.createNewIssue(opts[:title], type)
248
- @issues_array << new_issue
249
- save_db()
250
- puts "Created issue #{new_issue.short_id} #{new_issue.title}"
251
- end
252
-
253
-
254
- #----------------------------------------------------------------------------------------------------------------------#
255
-
256
- def list_issues(opts)
257
- if opts[:issue_id]
258
- list_issue(opts[:issue_id])
259
- else
260
- 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}
270
- end
271
- end
272
-
273
-
274
- #----------------------------------------------------------------------------------------------------------------------#
275
-
276
- def list_issue(issue_id)
277
- issue = select_issue {|i| i.id.start_with?(issue_id) }
278
- issue.format_verbose()
279
- end
280
-
281
-
282
- #----------------------------------------------------------------------------------------------------------------------#
283
-
284
- def resolve_issues(opts)
285
- issue_id = opts[:issue_id]
286
- resolved_issue = select_issue{|i| i.id.start_with?(issue_id) && i.status == "open"}
287
-
288
- duplicate_of_id = opts[:cmd] == "duplicate" && select_issue {|i| i.id.start_with?(opts[:duplicate_of_id]) }.id
289
-
290
- status, message =
291
- case opts[:cmd]
292
- when "resolve"
293
- ["resolved", "Resolved"]
294
- when "wontfix"
295
- ["wontfix", "Won't fix"]
296
- when "duplicate"
297
- ["duplicate(#{duplicate_of_id})", "Duplicate"]
298
- end
299
-
300
- resolved_issue.status = status
301
- resolved_issue.log "Changed status to #{status}"
302
-
303
- message = "#{message} issue #{resolved_issue.short_id} #{resolved_issue.title}"
304
- puts message
305
-
306
-
307
- save_db()
308
- opts[:commit] && exec("git commit -a -m \"#{message}\"")
309
- end
310
-
311
-
312
- #----------------------------------------------------------------------------------------------------------------------#
313
-
314
- def delete_issues(opts)
315
- delete_issues = []
316
-
317
- opts[:issue_ids].each do |issue_id|
318
- delete_issues << select_issue{|i| i.id.start_with?(issue_id)}
319
- end
320
-
321
- puts "Ok to delete issues: "
322
- delete_issues.each { |issue| puts "#{issue.short_id} \"#{issue.title}\"" }
323
- puts "[y/N]"
324
-
325
- answer = STDIN.getch
326
-
327
- if /y/i =~ answer
328
- @issues_array -= delete_issues
329
- save_db()
330
-
331
- if delete_issues.count == 1
332
- puts "Removed issue #{delete_issues[0].short_id} \"#{delete_issues[0].title}\" from database."
333
- else
334
- puts "Removed issues "
335
- delete_issues.each { |issue| puts "#{issue.short_id} \"#{issue.title}\"" }
336
- puts "from database."
337
- end
338
- end
339
-
340
- end
341
-
342
-
343
- #----------------------------------------------------------------------------------------------------------------------#
344
-
345
- def edit_issue(opts)
346
- issue_id = opts[:issue_id]
347
- issue = select_issue { |i| i.id.start_with?(issue_id) }
348
-
349
- did_change_issue = false
350
-
351
- if (opts[:description])
352
- did_change_issue = issue.edit_description && issue.log("Edited description")
353
- else
354
- did_change_issue = issue.edit_all && issue.log("Edited issue")
355
- end
356
-
357
- did_change_issue && save_db()
358
- end
359
-
360
-
361
- #----------------------------------------------------------------------------------------------------------------------#
362
-
363
- def set_type(opts)
364
- new_type = opts[:new_type]
365
- opts[:issue_ids].each do |issue_id|
366
- 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}."
373
- end
374
- end
375
-
376
- save_db()
377
- end
378
-
379
-
380
- #----------------------------------------------------------------------------------------------------------------------#
381
-
382
-
383
- end
384
-
385
-
386
- #======================================================================================================================#
387
- # Command Line Parsing
388
- #======================================================================================================================#
389
-
390
- def get_issue_ids(num_ids, usage)
391
- result = []
392
- count = num_ids >= 0 ? num_ids : ARGV.count
393
-
394
- begin
395
- count.times do
396
- ARGV.count > 0 && /^\h{1,32}$/ =~ ARGV[0] || raise
397
- result << ARGV.shift
398
- end
399
-
400
- result.count == 1 && num_ids > 0 ? result[0] : result
401
-
402
- rescue
403
- abort("Usage: issues #{usage}")
404
- end
405
- end
406
-
407
-
408
- #----------------------------------------------------------------------------------------------------------------------#
409
-
410
- EXECUTABLE_NAME=File.basename($0)
411
- DATABASE_NAME= ".issues/" << EXECUTABLE_NAME << ".yaml"
412
-
413
- 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"}
422
-
423
- LeftFieldLength =
424
- SUB_COMMANDS.collect { |key, value| key.length }.max
425
-
426
- SubCommandHelp =
427
- SUB_COMMANDS.collect {|key,value| " #{key.ljust(LeftFieldLength)} #{value}"}.join("\n")
428
-
429
-
430
- global_opts = Trollop::options do
431
- banner <<-EOL
432
- issues: lightweight distributed issue management.
433
-
434
- Usage:
435
- ------
436
- issues [<command>] [<options] [<args>]
437
-
438
- Commands are:
439
- -------------
440
- #{SubCommandHelp}
441
-
442
- Global Options:
443
- ---------------
444
- EOL
445
- stop_on SUB_COMMANDS.keys
446
- end
447
-
448
-
449
- cmd = ARGV.shift # get the subcommand
450
- cmd ||= 'list'
451
-
452
- cmd_opts = {}
453
-
454
- if cmd == 'list'
455
- cmd_opts =
456
- 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'
464
- end
465
-
466
- ARGV.count > 0 && cmd_opts[:issue_id] = get_issue_ids(1, "list ID")
467
-
468
- elsif cmd == "create"
469
- cmd_opts =
470
- 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'
474
- end
475
- cmd_opts[:title] = ARGV.shift || Trollop::die( "Please enter a title for the new issue!")
476
-
477
-
478
- elsif cmd == "resolve" || cmd == "wontfix" || cmd == "duplicate"
479
- cmd_opts =
480
- Trollop::options do
481
- opt :commit, "do a git commit", :short => 'c'
482
- end
483
-
484
- if cmd == "duplicate"
485
- cmd_opts[:issue_id], cmd_opts[:duplicate_of_id] = get_issue_ids(2, "duplicate ID(issue) ID(duplicate of)")
486
- else
487
- cmd_opts[:issue_id] = get_issue_ids(1, "#{cmd} [-c] ID")
488
- end
489
-
490
-
491
- elsif cmd == "edit"
492
- cmd_opts =
493
- Trollop::options do
494
- opt :description, "edit the issue description", :short => 'd'
495
- end
496
- cmd_opts[:issue_id] = get_issue_ids(1, "edit ID")
497
-
498
-
499
- elsif cmd == "set-type"
500
- Trollop::options do
501
- banner <<-EOL
502
- Usage:
503
- ------
504
- issues set-type {bug|improvement|task} ID
505
-
506
- Options:
507
- --------
508
- EOL
509
- end
510
-
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")
515
-
516
-
517
- elsif cmd == "delete"
518
- cmd_opts[:issue_ids] = get_issue_ids(-1, "#{cmd} ID")
519
-
520
- else
521
- Trollop::die "unknown command #{cmd.inspect}"
522
- end
523
-
524
-
525
- cmd_opts[:cmd] = cmd
526
-
527
-
528
- #======================================================================================================================#
529
- # Main
530
- #======================================================================================================================#
531
-
532
- Issues = IssuesDb.new(DATABASE_NAME)
533
-
534
- case cmd
535
- when "create"
536
- Issues.create_issue(cmd_opts)
537
- when "list"
538
- Issues.list_issues(cmd_opts)
539
- when "resolve", "wontfix", "duplicate"
540
- Issues.resolve_issues(cmd_opts)
541
- when "edit"
542
- Issues.edit_issue(cmd_opts)
543
- when "set-type"
544
- Issues.set_type(cmd_opts)
545
- when "delete"
546
- Issues.delete_issues(cmd_opts)
547
- end
548
-
549
-
550
- #======================================================================================================================#
551
-