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
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
-