michael 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/Gemfile.lock +6 -71
  4. data/exe/michael +2 -2
  5. data/lib/michael.rb +1 -0
  6. data/lib/michael/cli.rb +15 -1
  7. data/lib/michael/commands/auth.rb +11 -21
  8. data/lib/michael/commands/repos.rb +39 -2
  9. data/lib/michael/commands/repos/edit.rb +13 -9
  10. data/lib/michael/commands/repos/pull_requests.rb +38 -97
  11. data/lib/michael/constants.rb +7 -0
  12. data/lib/michael/models/pull_request.rb +139 -0
  13. data/lib/michael/models/repository.rb +45 -0
  14. data/lib/michael/models/review.rb +57 -0
  15. data/lib/michael/models/status.rb +43 -0
  16. data/lib/michael/models/user.rb +18 -0
  17. data/lib/michael/services/configuration.rb +44 -0
  18. data/lib/michael/{models/github/octokit_initializer.rb → services/github/initializer.rb} +9 -7
  19. data/lib/michael/services/github/pull_requests.rb +46 -0
  20. data/lib/michael/services/github/token.rb +41 -0
  21. data/lib/michael/services/github/users.rb +16 -0
  22. data/lib/michael/services/repositories.rb +34 -0
  23. data/lib/michael/version.rb +1 -1
  24. data/michael.gemspec +2 -18
  25. metadata +19 -245
  26. data/lib/michael/command.rb +0 -131
  27. data/lib/michael/models/configuration.rb +0 -69
  28. data/lib/michael/models/github/pr_wrapper.rb +0 -84
  29. data/lib/michael/models/github/pull_request.rb +0 -51
  30. data/lib/michael/models/github/review.rb +0 -41
  31. data/lib/michael/models/github/reviewer.rb +0 -17
  32. data/lib/michael/models/github/status.rb +0 -25
  33. data/lib/michael/models/github/team.rb +0 -17
  34. data/lib/michael/models/github/token_validator.rb +0 -30
  35. data/lib/michael/models/github/user.rb +0 -21
  36. data/lib/michael/models/guard.rb +0 -20
  37. data/lib/michael/models/pull_request_formatter.rb +0 -116
  38. data/lib/michael/models/repository_formatter.rb +0 -26
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'fileutils'
4
-
5
- require 'tty-config'
6
-
7
- module Michael
8
- module Models
9
- class Configuration
10
- CONFIG_ALLOWED_SYMBOLS = %i[set append fetch remove].freeze
11
-
12
- def initialize(
13
- config_dir: default_config_dir, config_name: default_filename
14
- )
15
- @config_dir = config_dir
16
- @config_name = config_name
17
- @config = create_config
18
- end
19
-
20
- def nuke
21
- create_config.write(force: true)
22
- self
23
- end
24
-
25
- def config_file_path
26
- config_dir + '/' + config_name + '.yml'
27
- end
28
-
29
- def respond_to_missing?(symbol)
30
- CONFIG_ALLOWED_SYMBOLS.include?(symbol)
31
- end
32
-
33
- def method_missing(symbol, *args)
34
- return super unless CONFIG_ALLOWED_SYMBOLS.include?(symbol)
35
-
36
- mkdir_once
37
- config.read if config.exist?
38
- resp = config.public_send(symbol, *args)
39
- config.write(force: true)
40
-
41
- resp
42
- end
43
-
44
- private
45
-
46
- attr_reader :config_dir, :config_name, :config
47
-
48
- def create_config
49
- config = TTY::Config.new
50
- config.filename = config_name
51
- config.append_path(config_dir)
52
-
53
- config
54
- end
55
-
56
- def default_config_dir
57
- "#{ENV['HOME']}/.config/kudrykv/michael"
58
- end
59
-
60
- def default_filename
61
- 'config'
62
- end
63
-
64
- def mkdir_once
65
- @mkdir_once ||= FileUtils.mkdir_p(config_dir)
66
- end
67
- end
68
- end
69
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'reviewer'
4
- require_relative 'team'
5
-
6
- module Michael
7
- module Models
8
- module Github
9
- class PRWrapper
10
- attr_accessor :statuses, :reviews
11
-
12
- def initialize(pull_request)
13
- @pr = pull_request
14
- @statuses = []
15
- @reviews = []
16
- end
17
-
18
- def title
19
- pr[:title]
20
- end
21
-
22
- def number
23
- pr[:number]
24
- end
25
-
26
- def author
27
- pr[:user][:login]
28
- end
29
-
30
- def head_sha
31
- pr[:head][:sha]
32
- end
33
-
34
- def reviewed?
35
- reviewers.any? || teams.any?
36
- end
37
-
38
- def reviewers
39
- pr[:requested_reviewers].map { |reviewer| Reviewer.new(reviewer) }
40
- end
41
-
42
- def teams
43
- pr[:requested_teams].map { |team| Team.new(team) }
44
- end
45
-
46
- def labels
47
- pr[:labels].map { |label| label[:name] }
48
- end
49
-
50
- def requested_changes
51
- reviews.select(&:changes_requested?).map(&:author)
52
- end
53
-
54
- def commented_on_pr
55
- reviews
56
- .reject { |review| author == review.author }
57
- .select(&:commented?).map(&:author)
58
- end
59
-
60
- def last_update_head?
61
- pr_last_update = pr[:updated_at]
62
- review_last_update = pr.reviews&.map(&:submitted_at)&.sort&.pop
63
-
64
- return false unless review_last_update
65
-
66
- pr_last_update > review_last_update
67
- end
68
-
69
- def last_updated_at
70
- updates = [pr[:updated_at]]
71
-
72
- updates.concat pr.statuses.map(&:updated_at) unless pr.statuses.nil? || pr.statuses.any?
73
- updates.concat pr.reviews.map(&:submitted_at) unless pr.reviews.nil? || pr.reviews.any?
74
-
75
- updates.sort.pop
76
- end
77
-
78
- private
79
-
80
- attr_reader :pr
81
- end
82
- end
83
- end
84
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'octokit'
4
-
5
- require_relative 'octokit_initializer'
6
- require_relative 'pr_wrapper'
7
- require_relative 'review'
8
- require_relative 'status'
9
-
10
- module Michael
11
- module Models
12
- module Github
13
- class PullRequest < OctokitInitializer
14
- def process_repo(org_repo, hsh = {})
15
- list = search(org_repo, hsh)
16
-
17
- return {repo: org_repo, state: :failed} unless list
18
-
19
- {repo: org_repo, state: :success, prs: list}
20
- end
21
-
22
- private
23
-
24
- def search(org_repo, state: 'open', with_statuses: true, with_reviews: true)
25
- octokit.pull_requests(org_repo, state: state).map do |pr|
26
- pr = PRWrapper.new(pr)
27
- pr.statuses = statuses(org_repo, pr.head_sha) if with_statuses
28
- pr.reviews = reviews(org_repo, pr.number) if with_reviews
29
-
30
- pr
31
- end
32
- rescue Octokit::InvalidRepository
33
- false
34
- end
35
-
36
- def statuses(org_repo, ref)
37
- combined = octokit.combined_status(org_repo, ref)
38
- combined[:statuses]&.map { |status| Status.new(status) }
39
- end
40
-
41
- def reviews(org_repo, pr_number)
42
- octokit
43
- .pull_request_reviews(org_repo, pr_number)
44
- .map { |review| Review.new(review) }
45
- .group_by(&:author).to_a
46
- .map { |_author, reviews| reviews.sort_by(&:submitted_at).pop }
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Michael
4
- module Models
5
- module Github
6
- class Review
7
- def initialize(review)
8
- @review = review
9
- end
10
-
11
- def author
12
- review[:user][:login]
13
- end
14
-
15
- def state
16
- review[:state].to_sym
17
- end
18
-
19
- def submitted_at
20
- review[:submitted_at]
21
- end
22
-
23
- def approved?
24
- state == :APPROVED
25
- end
26
-
27
- def changes_requested?
28
- state == :CHANGES_REQUESTED
29
- end
30
-
31
- def commented?
32
- state == :COMMENTED
33
- end
34
-
35
- private
36
-
37
- attr_reader :review
38
- end
39
- end
40
- end
41
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Michael
4
- module Models
5
- module Github
6
- class Reviewer
7
- def initialize(reviewer)
8
- @reviewer = reviewer
9
- end
10
-
11
- private
12
-
13
- attr_reader :reviewer
14
- end
15
- end
16
- end
17
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Michael
4
- module Models
5
- module Github
6
- class Status
7
- def initialize(status)
8
- @status = status
9
- end
10
-
11
- def state
12
- status[:state].to_sym
13
- end
14
-
15
- def updated_at
16
- status[:updated_at]
17
- end
18
-
19
- private
20
-
21
- attr_reader :status
22
- end
23
- end
24
- end
25
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Michael
4
- module Models
5
- module Github
6
- class Team
7
- def initialize(team)
8
- @team = team
9
- end
10
-
11
- private
12
-
13
- attr_reader :team
14
- end
15
- end
16
- end
17
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'octokit'
4
-
5
- require_relative '../configuration'
6
-
7
- module Michael
8
- module Models
9
- module Github
10
- class TokenValidator
11
- class << self
12
- def token_valid?(token)
13
- scopes = Octokit::Client.new(access_token: token).scopes
14
-
15
- return true if scopes.include?('repo')
16
-
17
- puts 'Token should have `repo` scope'
18
- false
19
- rescue StandardError
20
- false
21
- end
22
-
23
- def save_token(token)
24
- Configuration.new.set(:token, value: token)
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'octokit_initializer'
4
-
5
- module Michael
6
- module Models
7
- module Github
8
- class User < OctokitInitializer
9
- def username
10
- me[:login]
11
- end
12
-
13
- private
14
-
15
- def me
16
- @me ||= octokit.user
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../command'
4
- require_relative 'github/token_validator'
5
-
6
- module Michael
7
- module Models
8
- class Guard < Michael::Command
9
- attr_reader :config, :repos_config
10
-
11
- def initialize
12
- @config = Configuration.new
13
- @repos_config = Configuration.new(config_name: 'repositories')
14
-
15
- token_valid = Github::TokenValidator.token_valid?(config.fetch(:token))
16
- abort 'Your token is invalid' unless token_valid
17
- end
18
- end
19
- end
20
- end
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'pastel'
4
- require 'ruby-duration'
5
-
6
- module Michael
7
- module Models
8
- class PullRequestFormatter
9
- class << self
10
- def pretty(reponame, prs)
11
- [
12
- pastel.bold(reponame + ':'),
13
- prs.map { |pr| PullRequestFormatter.new(pr).pretty }.join("\n")
14
- ].join("\n")
15
- end
16
- end
17
-
18
- def initialize(pr)
19
- @pastel = Pastel.new
20
- @pr = pr
21
- end
22
-
23
- def pretty
24
- format(
25
- '#%<number>s %<statuses>s %<reviews>s %<title>s %<variable>s',
26
- number: pastel.bold(pr.number),
27
- statuses: statuses_in_dots,
28
- reviews: reviews_in_dots,
29
- title: pr.title,
30
- variable: variable_line
31
- )
32
- end
33
-
34
- private
35
-
36
- attr_reader :pastel, :pr
37
-
38
- def statuses_in_dots
39
- return '-' if pr.statuses.empty?
40
-
41
- pr.statuses.map { |status| dot_status[status.state] }.join
42
- end
43
-
44
- def reviews_in_dots
45
- return pastel.yellow('-') unless pr.reviewed? || pr.reviews.any?
46
- return pastel.yellow('.') if pr.reviews.empty?
47
-
48
- pr.reviews
49
- .reject { |review| review.author == pr.author }
50
- .map { |review| dot_status[review.state] }
51
- .join
52
- end
53
-
54
- def variable_line
55
- [
56
- labels_stringified(pr.labels),
57
- pastel.cyan(pr.author),
58
- last_update,
59
- who_req_changes_stringified,
60
- who_commented_stringified,
61
- ].reject(&:empty?).join(' ')
62
- end
63
-
64
- def labels_stringified(labels)
65
- return '' if labels.empty?
66
-
67
- pastel.bold.yellow("[#{labels.join(', ')}]")
68
- end
69
-
70
- def who_commented_stringified
71
- names = pr.commented_on_pr
72
- return '' if names.empty?
73
-
74
- '| Commented: ' + names.join(', ')
75
- end
76
-
77
- def who_req_changes_stringified
78
- names = pr.requested_changes
79
- return '' if names.empty?
80
-
81
- '| ' + pastel.bold('Requested changes: ') << names.map { |name| pastel.underscore(name) }.join(', ')
82
- end
83
-
84
- def last_update
85
- time_diff = Duration.new(Time.now - pr.last_updated_at)
86
- 'last update ' + last_update_when(time_diff) + ' ago'
87
- end
88
-
89
- def last_update_when(duration)
90
- if duration.weeks.positive?
91
- pastel.yellow.bold("#{duration.weeks} week(s)")
92
- elsif duration.days.positive?
93
- pastel.yellow("#{duration.days} day(s)")
94
- elsif duration.hours.positive?
95
- "#{duration.hours} hour(s)"
96
- elsif duration.minutes.positive?
97
- "#{duration.minutes} minute(s)"
98
- else
99
- 'seconds'
100
- end
101
- end
102
-
103
- def dot_status
104
- @dot_status ||= {
105
- success: pastel.green('+'),
106
- pending: pastel.yellow('.'),
107
- error: pastel.red('x'),
108
- failure: pastel.red('x'),
109
- APPROVED: pastel.green('^'),
110
- CHANGES_REQUESTED: pastel.red('X'),
111
- COMMENTED: '?'
112
- }
113
- end
114
- end
115
- end
116
- end