github-reaper 0.0.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.
@@ -0,0 +1 @@
1
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org/'
2
+
3
+ gem 'octokit', '~> 3.0'
4
+ gem 'slop', '~>3.6.0'
5
+ gem 'colorize'
6
+ gem 'github-reaper', :path => '.'
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ github-reaper (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.3.6)
10
+ colorize (0.7.3)
11
+ faraday (0.9.0)
12
+ multipart-post (>= 1.2, < 3)
13
+ multipart-post (2.0.0)
14
+ octokit (3.4.2)
15
+ sawyer (~> 0.5.3)
16
+ sawyer (0.5.5)
17
+ addressable (~> 2.3.5)
18
+ faraday (~> 0.8, < 0.10)
19
+ slop (3.6.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ colorize
26
+ github-reaper!
27
+ octokit (~> 3.0)
28
+ slop (~> 3.6.0)
@@ -0,0 +1,2 @@
1
+ reaper
2
+ ======
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'github-reaper'
4
+ require 'slop'
5
+
6
+ command = nil
7
+ opts = Slop.parse(ARGV, strict: true, help: true) do
8
+ on 'u', 'user=', 'Username for Github login credentials.'
9
+ on 'p', 'password=', 'Password for Github login credentials.'
10
+ on 'r', 'repository=', 'Repository to access, e.g "amfeng/reaper"'
11
+ on 't', 'threshold=', 'Number of months to set as reap threshold.', as: Integer, default: 3
12
+ on 's', 'skip', 'Skip confirmation prompts.'
13
+
14
+ on 'version', 'Print the version.' do
15
+ puts Reaper::VERSION
16
+ exit
17
+ end
18
+ end
19
+
20
+
21
+ Reaper::CLI.new(opts).run!
@@ -0,0 +1,16 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+ require 'reaper/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'github-reaper'
6
+ s.version = Reaper::VERSION
7
+ s.summary = 'Reaper'
8
+ s.description = "Reaper helps find and close stale Github issues."
9
+ s.authors = ["Amber Feng"]
10
+ s.email = 'amber.feng@gmail.com'
11
+
12
+ s.files = `git ls-files`.split("\n")
13
+ s.test_files = `git ls-files -- test/test_*.rb`.split("\n")
14
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
+ s.require_paths = ['lib']
16
+ end
@@ -0,0 +1,119 @@
1
+ #!/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'colorize'
5
+ require 'time'
6
+
7
+ require 'reaper/version'
8
+ require 'reaper/issue'
9
+ require 'reaper/client'
10
+
11
+ module Reaper
12
+ REAPER_WARNING = <<-eos
13
+ Hi! This is a friendly (automated) warning from the reaper that this
14
+ issue hasn't been updated in 3 months and will be automatically closed
15
+ in 1 week.
16
+
17
+ If you don't want this to be closed yet, you should remove the `to-reap`
18
+ label and the 3 month timeout will reset. If you never want this to be
19
+ reaped, add the label `do-not-reap`.
20
+
21
+ Thanks! (:
22
+ eos
23
+
24
+ class CLI
25
+ def initialize(opts)
26
+ @client = Reaper::Client.instance
27
+ @client.set_repo(opts[:repository])
28
+
29
+ @stale_threshold = 3600 * 24 * 30 * opts[:threshold]
30
+ @skip_confirm = opts[:skip]
31
+ end
32
+
33
+ def run!
34
+ now = Time.now
35
+
36
+ puts "Welcome to Reaper! ⟝⦆ (fetching from `#{@client.repo}`)".white.bold
37
+ issues_reaped = false
38
+
39
+ # Fetch to-reap issues.
40
+
41
+ options = {
42
+ labels: 'to-reap'
43
+ }
44
+
45
+ puts "Finding issues to reap..."
46
+ issues = @client.list_issues(options)
47
+ issues.each do |issue|
48
+ issues_reaped = true
49
+ issue = Reaper::Issue.new(issue)
50
+ # TODO: Add force-all flag.
51
+
52
+ issue_action(issue, "Close issue?", @skip_confirm) do |issue|
53
+ issue.reap
54
+ puts "Issue was reaped.".green
55
+ end
56
+ end
57
+
58
+ # Fetch issues in ascending updated order.
59
+ options = {
60
+ sort: :updated,
61
+ direction: :asc
62
+ }
63
+
64
+ puts "Finding next reapable issues..."
65
+ issues = @client.list_issues(options)
66
+ issues.each do |issue|
67
+ issue = Reaper::Issue.new(issue)
68
+
69
+ # Skip if this task has already been closed or has a do-not-reap tag.
70
+ next if issue.closed?
71
+ next if issue.labels.include?('do-not-reap')
72
+
73
+ # If there's a to-reap tag, close it. Else, add a to-reap tag and
74
+ # a warning comment.
75
+ # FIXME: The timeout for closing an issue after a warning should be
76
+ # customizable and not based on run-time of the reaper (e.g. running
77
+ # reaper should be idempotent).
78
+
79
+ puts "\n"
80
+
81
+ # Break out of the whole loop if the issue's updated date is outside
82
+ # the range.
83
+ break if issue.updated_at > now - @stale_threshold
84
+
85
+ issues_reaped = true
86
+ issue_action(issue, "Add warning?", @skip_confirm) do |issue|
87
+ issue.warn(Reaper::REAPER_WARNING)
88
+ puts "Added `to-reap` to #{issue.number}"
89
+ end
90
+ end
91
+
92
+ if issues_reaped
93
+ puts "Nice, you're done!".green
94
+ else
95
+ puts "No reap-able issues, woohoo!".green
96
+ end
97
+ end
98
+
99
+ def issue_action(issue, action_label, skip_confirm=false, show_title=true, &blk)
100
+ return yield issue if skip_confirm
101
+
102
+ puts "= Issue ##{issue.number}: #{issue.title}".white if show_title
103
+ print "#{action_label} [Y]es, [N]o, or n[E]ver: ".yellow
104
+ input = $stdin.gets.chomp.downcase
105
+
106
+ case input
107
+ when 'y'
108
+ yield issue
109
+ when 'n'
110
+ puts "OK, skipping.".red
111
+ when 'e'
112
+ issue.protect
113
+ puts "OK, added `do-not-reap`.".green
114
+ else
115
+ issue_action(issue, action_label, false, &blk)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,30 @@
1
+ require 'octokit'
2
+ require 'singleton'
3
+
4
+ module Reaper
5
+ class Client
6
+ include Singleton
7
+
8
+ attr_accessor :client, :repo
9
+
10
+ def initialize
11
+ # Look for .netrc credentials first; if they don't exist then prompt
12
+ # for login.
13
+ # TODO
14
+ @client = Octokit::Client.new(access_token: ENV['GITHUB_ACCESS_TOKEN'])
15
+ @repo = nil
16
+
17
+ # TODO: Add things other than Github.
18
+ end
19
+
20
+ def set_repo(repo)
21
+ @repo = repo
22
+ end
23
+
24
+ def method_missing(meth, *args, &block)
25
+ raise "Must provide a Github repository." unless @repo
26
+
27
+ @client.send(meth, *([@repo] + args), &block)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,60 @@
1
+ module Reaper
2
+ class Issue
3
+ attr_accessor :state, :title, :body, :labels
4
+
5
+ def initialize(issue)
6
+ @issue = issue
7
+
8
+ @title = @issue.title
9
+ @body = @issue.body
10
+ @state = @issue.state
11
+ @labels = issue.labels.map(&:name)
12
+ @buffered_comments = []
13
+
14
+ @client = Reaper::Client.instance
15
+ end
16
+
17
+ # Action methods
18
+
19
+ def reap
20
+ @labels << 'reaped'
21
+ @labels -= ['to-reap']
22
+ @client.close_issue(@issue.number, labels: @labels)
23
+ end
24
+
25
+ def warn(warning)
26
+ @labels << 'to-reap'
27
+ comment(warning)
28
+ save
29
+ end
30
+
31
+ def protect
32
+ @labels << 'do-not-reap'
33
+ @labels -= ['to-reap']
34
+ save
35
+ end
36
+
37
+ def closed?
38
+ @state == 'closed'
39
+ end
40
+
41
+ private
42
+
43
+ def comment(comment)
44
+ @buffered_comments << comment
45
+ end
46
+
47
+ def save
48
+ @client.update_issue(@issue.number, @title, @body, labels: @labels)
49
+
50
+ @buffered_comments.each do |comment|
51
+ @client.add_comment(@issue.number, comment)
52
+ end
53
+ @buffered_comments = []
54
+ end
55
+
56
+ def method_missing(meth, *args, &block)
57
+ @issue.send(meth, *args, &block)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module Reaper
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: github-reaper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Amber Feng
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-05-22 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Reaper helps find and close stale Github issues.
15
+ email: amber.feng@gmail.com
16
+ executables:
17
+ - reaper
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - README.md
25
+ - bin/reaper
26
+ - github-reaper.gemspec
27
+ - lib/github-reaper.rb
28
+ - lib/reaper/client.rb
29
+ - lib/reaper/issue.rb
30
+ - lib/reaper/version.rb
31
+ homepage:
32
+ licenses: []
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 1.8.23
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: Reaper
55
+ test_files: []
56
+ has_rdoc: