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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 147eb2a1f8bb87dd4236aba99b6ff495fbb16a6b09cedf7ecb01edcac8124305
4
- data.tar.gz: 1304c1b36bc36d3066a876dc530f1dfa3dc11c38b8cc31767b77e6a211c3c390
3
+ metadata.gz: e7807c64c30fb214d1532da9a97d6abc4f5173b52bd2f1256aa9a3b4b44c43a8
4
+ data.tar.gz: 84ab17dfada0424db92341ab7911a338716d04cda02c35f2e6779cfb1cbb75bd
5
5
  SHA512:
6
- metadata.gz: 88ce61019ef7992ee575befae3902a2b5c359125ab1bc338a598e64273e27c01a134bc16dd33d4d27a6c944c49e6b0da18e4602e028794d9ba1ccbf4c484f2ca
7
- data.tar.gz: 9f6d158d879cce2ed941f88370d09bcc3d608cb505f1caabba690a0cc5a054215d9b41e72a7fee0f1536cdc200318f0e4e8169a67db2bae7a3980532f1a042ed
6
+ metadata.gz: d89f9e455b2048c657b6e3a152a0ac7d3691af7826eba6df3798065641d75d6cf6d39c368e34f9f5fa4d0961ecea820114279d649547610b34e50d06c6b1fae5
7
+ data.tar.gz: e1a8370c604d5de524384bce470dce5d3ee87bb2e052c2f2b7f98f8ad0e2f04af6698ac24ffc7c28a93fcbd0eca62ddf5f8fcd9f3e07d353e2d04b46308351cb
data/.rubocop.yml CHANGED
@@ -1,2 +1,5 @@
1
1
  Style/Documentation:
2
- Enabled: false
2
+ Enabled: false
3
+
4
+ Metrics/LineLength:
5
+ Max: 120
data/Gemfile.lock CHANGED
@@ -1,42 +1,26 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- michael (0.1.0)
4
+ michael (0.2.0)
5
5
  octokit (~> 4.0)
6
6
  parallel (~> 1.18.0)
7
7
  pastel (~> 0.7.2)
8
8
  ruby-duration (~> 3.2.3)
9
9
  thor (~> 0.20.0)
10
- tty-box (~> 0.4.1)
11
- tty-color (~> 0.5)
12
- tty-command (~> 0.9.0)
13
10
  tty-config (~> 0.3.2)
14
- tty-cursor (~> 0.7)
15
11
  tty-editor (~> 0.5)
16
- tty-file (~> 0.8.0)
17
- tty-font (~> 0.4.0)
18
12
  tty-logger (~> 0.2.0)
19
- tty-markdown (~> 0.6.0)
20
- tty-pager (~> 0.12)
21
- tty-pie (~> 0.3.0)
22
- tty-platform (~> 0.2)
23
- tty-progressbar (~> 0.17)
24
13
  tty-prompt (~> 0.19)
25
- tty-screen (~> 0.7)
26
- tty-spinner (~> 0.9)
27
- tty-table (~> 0.11.0)
28
- tty-tree (~> 0.3)
29
- tty-which (~> 0.4)
30
14
 
31
15
  GEM
32
16
  remote: https://rubygems.org/
33
17
  specs:
34
- activesupport (6.0.0)
18
+ activesupport (6.0.1)
35
19
  concurrent-ruby (~> 1.0, >= 1.0.2)
36
20
  i18n (>= 0.7, < 2)
37
21
  minitest (~> 5.1)
38
22
  tzinfo (~> 1.1)
39
- zeitwerk (~> 2.1, >= 2.1.8)
23
+ zeitwerk (~> 2.2)
40
24
  addressable (2.7.0)
41
25
  public_suffix (>= 2.0.2, < 5.0)
42
26
  coderay (1.1.2)
@@ -50,9 +34,8 @@ GEM
50
34
  concurrent-ruby (~> 1.0)
51
35
  iso8601 (0.12.1)
52
36
  json (2.2.0)
53
- kramdown (1.16.2)
54
37
  method_source (0.9.2)
55
- minitest (5.12.2)
38
+ minitest (5.13.0)
56
39
  multipart-post (2.1.1)
57
40
  necromancer (0.5.0)
58
41
  octokit (4.14.0)
@@ -66,7 +49,6 @@ GEM
66
49
  method_source (~> 0.9.0)
67
50
  public_suffix (4.0.1)
68
51
  rake (10.5.0)
69
- rouge (3.12.0)
70
52
  rspec (3.9.0)
71
53
  rspec-core (~> 3.9.0)
72
54
  rspec-expectations (~> 3.9.0)
@@ -92,52 +74,16 @@ GEM
92
74
  json (>= 1.8, < 3)
93
75
  simplecov-html (~> 0.10.0)
94
76
  simplecov-html (0.10.2)
95
- strings (0.1.6)
96
- strings-ansi (~> 0.1)
97
- unicode-display_width (~> 1.5)
98
- unicode_utils (~> 1.4)
99
- strings-ansi (0.1.0)
100
77
  thor (0.20.3)
101
78
  thread_safe (0.3.6)
102
- tty-box (0.4.1)
103
- pastel (~> 0.7.2)
104
- strings (~> 0.1.6)
105
- tty-cursor (~> 0.7)
106
79
  tty-color (0.5.0)
107
- tty-command (0.9.0)
108
- pastel (~> 0.7.0)
109
80
  tty-config (0.3.2)
110
81
  tty-cursor (0.7.0)
111
82
  tty-editor (0.5.1)
112
83
  tty-prompt (~> 0.19)
113
84
  tty-which (~> 0.4)
114
- tty-file (0.8.0)
115
- diff-lcs (~> 1.3)
116
- pastel (~> 0.7.2)
117
- tty-prompt (~> 0.18)
118
- tty-font (0.4.0)
119
85
  tty-logger (0.2.0)
120
86
  pastel (~> 0.7.0)
121
- tty-markdown (0.6.0)
122
- kramdown (~> 1.16.2)
123
- pastel (~> 0.7.2)
124
- rouge (~> 3.3)
125
- strings (~> 0.1.4)
126
- tty-color (~> 0.4)
127
- tty-screen (~> 0.6)
128
- tty-pager (0.12.1)
129
- strings (~> 0.1.4)
130
- tty-screen (~> 0.6)
131
- tty-which (~> 0.4)
132
- tty-pie (0.3.0)
133
- pastel (~> 0.7.3)
134
- tty-cursor (~> 0.7)
135
- tty-platform (0.2.1)
136
- tty-progressbar (0.17.0)
137
- strings-ansi (~> 0.1.0)
138
- tty-cursor (~> 0.7)
139
- tty-screen (~> 0.7)
140
- unicode-display_width (~> 1.6)
141
87
  tty-prompt (0.19.0)
142
88
  necromancer (~> 0.5.0)
143
89
  pastel (~> 0.7.0)
@@ -147,22 +93,11 @@ GEM
147
93
  tty-screen (~> 0.7)
148
94
  wisper (~> 2.0.0)
149
95
  tty-screen (0.7.0)
150
- tty-spinner (0.9.1)
151
- tty-cursor (~> 0.7)
152
- tty-table (0.11.0)
153
- equatable (~> 0.6)
154
- necromancer (~> 0.5)
155
- pastel (~> 0.7.2)
156
- strings (~> 0.1.5)
157
- tty-screen (~> 0.7)
158
- tty-tree (0.3.0)
159
96
  tty-which (0.4.1)
160
97
  tzinfo (1.2.5)
161
98
  thread_safe (~> 0.1)
162
- unicode-display_width (1.6.0)
163
- unicode_utils (1.4.0)
164
99
  wisper (2.0.1)
165
- zeitwerk (2.2.0)
100
+ zeitwerk (2.2.1)
166
101
 
167
102
  PLATFORMS
168
103
  ruby
@@ -170,7 +105,7 @@ PLATFORMS
170
105
  DEPENDENCIES
171
106
  bundler (~> 1.17)
172
107
  michael!
173
- pry
108
+ pry (~> 0.12.2)
174
109
  rake (~> 10.0)
175
110
  rspec (~> 3.0)
176
111
  simplecov (~> 0.17.0)
data/exe/michael CHANGED
@@ -17,7 +17,7 @@ begin
17
17
  rescue TTY::Reader::InputInterrupt
18
18
  warn('interrupted')
19
19
  exit(1)
20
- rescue Michael::CLI::Error => e
21
- puts "ERROR: #{e.message}"
20
+ rescue Michael::Error => e
21
+ puts "Error: #{e.message}"
22
22
  exit 1
23
23
  end
data/lib/michael.rb CHANGED
@@ -4,5 +4,6 @@ require 'michael/version'
4
4
 
5
5
  module Michael
6
6
  class Error < StandardError; end
7
+ class Fatal < StandardError; end
7
8
 
8
9
  end
data/lib/michael/cli.rb CHANGED
@@ -1,6 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
+ require 'tty-prompt'
5
+ require 'tty-config'
6
+
7
+ require_relative 'constants'
8
+ require_relative 'services/github/token'
9
+ require_relative 'services/configuration'
4
10
 
5
11
  module Michael
6
12
  # Handle the application command line parsing
@@ -35,7 +41,15 @@ module Michael
35
41
  invoke :help, ['auth']
36
42
  else
37
43
  require_relative 'commands/auth'
38
- Michael::Commands::Auth.new(options).execute
44
+ ttycfg = TTY::Config.new
45
+ ttycfg.append_path(Michael::CONFIG_DIR_ABSOLUTE_PATH)
46
+ ttycfg.filename = Michael::CONFIG_FILENAME
47
+
48
+ cfg = Michael::Services::Configuration.new(ttycfg)
49
+
50
+ token = Michael::Services::Github::Token.new(cfg)
51
+
52
+ Michael::Commands::Auth.new(TTY::Prompt.new, token, options).execute
39
53
  end
40
54
  end
41
55
  end
@@ -1,35 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'tty-prompt'
4
-
5
- require_relative '../command'
6
- require_relative '../models/github/token_validator'
7
-
8
3
  module Michael
9
4
  module Commands
10
- class Auth < Michael::Command
11
- attr_reader :prompt
12
-
13
- def initialize(options)
14
- @prompt = TTY::Prompt.new
5
+ class Auth
6
+ def initialize(prompt, token, options)
7
+ @prompt = prompt
8
+ @token = token
15
9
  @options = options
16
10
  end
17
11
 
18
- def execute(out: $stdout)
19
- token = read_token
20
- unless Michael::Models::Github::TokenValidator.token_valid?(token)
21
- return out.puts 'Specified token is invalid'
22
- end
23
-
24
- unless Michael::Models::Github::TokenValidator.save_token(token)
25
- return out.puts 'Failed to save the token'
26
- end
27
-
28
- out.puts 'Token saved'
12
+ def execute
13
+ tkn = read_token
14
+ token.validate(tkn)
15
+ token.store(tkn)
16
+ puts 'Token saved!'
29
17
  end
30
18
 
31
19
  private
32
20
 
21
+ attr_reader :prompt, :token, :options
22
+
33
23
  def read_token
34
24
  prompt.mask('Please specify github token:', echo: false) do |q|
35
25
  q.modify :strip
@@ -1,6 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
+ require 'tty-editor'
5
+
6
+ require 'michael/constants'
7
+ require 'michael/services/github/users'
8
+ require 'michael/services/configuration'
9
+ require 'michael/services/github/token'
10
+ require 'michael/services/github/pull_requests'
11
+ require 'michael/services/repositories'
4
12
 
5
13
  module Michael
6
14
  module Commands
@@ -15,7 +23,22 @@ module Michael
15
23
  invoke :help, ['edit']
16
24
  else
17
25
  require_relative 'repos/edit'
18
- Michael::Commands::Repos::Edit.new(options).execute
26
+ ttycfg = TTY::Config.new
27
+ ttycfg.append_path(Michael::CONFIG_DIR_ABSOLUTE_PATH)
28
+ ttycfg.filename = Michael::CONFIG_FILENAME
29
+
30
+ ttycfgrepos = TTY::Config.new
31
+ ttycfgrepos.append_path(Michael::CONFIG_DIR_ABSOLUTE_PATH)
32
+ ttycfgrepos.filename = Michael::CONFIG_REPOS_FILENAME
33
+
34
+ cfg = Michael::Services::Configuration.new(ttycfg)
35
+ repocfg = Michael::Services::Configuration.new(ttycfgrepos)
36
+
37
+ token = Michael::Services::Github::Token.new(cfg)
38
+ token.validate(cfg.fetch(:token))
39
+
40
+ repos_filepath = Michael::CONFIG_DIR_ABSOLUTE_PATH + '/' + Michael::CONFIG_REPOS_FILENAME + '.yml'
41
+ Michael::Commands::Repos::Edit.new(repos_filepath, repocfg, TTY::Editor, options).execute
19
42
  end
20
43
  end
21
44
 
@@ -41,7 +64,21 @@ module Michael
41
64
  invoke :help, ['prs']
42
65
  else
43
66
  require_relative 'repos/pull_requests'
44
- Michael::Commands::Repos::PullRequests.new(options).execute
67
+ ttycfgrepos = TTY::Config.new
68
+ ttycfgrepos.append_path(Michael::CONFIG_DIR_ABSOLUTE_PATH)
69
+ ttycfgrepos.filename = Michael::CONFIG_REPOS_FILENAME
70
+
71
+ ttycfgtkn = TTY::Config.new
72
+ ttycfgtkn.append_path(Michael::CONFIG_DIR_ABSOLUTE_PATH)
73
+ ttycfgtkn.filename = Michael::CONFIG_FILENAME
74
+
75
+ repocfg = Michael::Services::Configuration.new(ttycfgrepos)
76
+ tkncfg = Michael::Services::Configuration.new(ttycfgtkn)
77
+
78
+ prs = Michael::Services::Github::PullRequests.new(tkncfg)
79
+ users = Michael::Services::Github::Users.new(tkncfg)
80
+ repos = Michael::Services::Repositories.new(prs)
81
+ Michael::Commands::Repos::PullRequests.new(repocfg, users, repos, options).execute
45
82
  end
46
83
  end
47
84
  end
@@ -1,24 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../command'
4
- require_relative '../../models/guard'
5
-
6
3
  module Michael
7
4
  module Commands
8
5
  class Repos
9
- class Edit < Michael::Models::Guard
10
- def initialize(options)
6
+ class Edit
7
+ def initialize(repos_filepath, cfg, editor, options)
11
8
  super()
12
9
 
10
+ @repos_filepath = repos_filepath
11
+ @cfg = cfg
12
+ @editor = editor
13
13
  @options = options
14
14
  end
15
15
 
16
- def execute(input: $stdin, output: $stdout)
17
- list = repos_config.fetch(:repos)
18
- repos_config.append('org/repo', to: :repos) if list.nil? || list.empty?
16
+ def execute
17
+ list = cfg.fetch(:repos)
18
+ cfg.append('org/repo', to: :repos) if list.nil? || list.empty?
19
19
 
20
- editor.open(repos_config.config_file_path)
20
+ editor.open(repos_filepath)
21
21
  end
22
+
23
+ private
24
+
25
+ attr_reader :repos_filepath, :cfg, :editor
22
26
  end
23
27
  end
24
28
  end
@@ -2,130 +2,71 @@
2
2
 
3
3
  require 'parallel'
4
4
 
5
- require_relative '../../command'
6
- require_relative '../../models/github/pull_request'
7
- require_relative '../../models/guard'
8
- require_relative '../../models/pull_request_formatter'
9
- require_relative '../../models/repository_formatter'
10
- require_relative '../../models/github/user'
11
-
12
5
  module Michael
13
6
  module Commands
14
7
  class Repos
15
- class PullRequests < Models::Guard
16
- def initialize(options)
17
- super()
18
-
19
- @prs = Michael::Models::Github::PullRequest.new
20
- @user = Michael::Models::Github::User.new
21
- @repos = repos_config.fetch(:repos)
22
- abort 'No repositories configured' if @repos.nil? || @repos.empty?
8
+ class PullRequests
9
+ attr_reader :config, :users, :repos, :options
23
10
 
11
+ def initialize(config, users, repos, options)
12
+ @config = config
13
+ @users = users
14
+ @repos = repos
24
15
  @options = options
25
16
  end
26
17
 
27
- def execute(out: $stdout)
28
- list = get_repos_with_spinner(out)
18
+ def execute
19
+ q = Queue.new
20
+ waiting = print_waiting(q)
21
+ list = config.fetch(:repos)
29
22
 
30
- print_good_prs(out, repos_with_prs(list))
31
- print_repos_w_no_prs(out, repos_no_prs(list)) if options[:show_empty]
32
- print_broken_repos(out, list.select { |item| item[:state] == :failed })
23
+ list = repos.pull_requests(list, q)
24
+ waiting.join
25
+
26
+ puts [filter_repos_w_prs(list), get_empty(list), get_broken(list)]
27
+ .reject(&:nil?).join("\n\n")
33
28
  end
34
29
 
35
30
  private
36
31
 
37
- attr_reader :prs, :options, :repos, :user
38
-
39
- def get_repos_with_spinner(out)
40
- progress = 0
41
- spin = spinner(
42
- "[:spinner] :progress/#{repos.length} processing...",
43
- output: out
44
- )
45
-
46
- spin.update(progress: 0)
47
-
48
- result = Parallel.map(repos, in_threads: 5) do |repo|
49
- progress += 1
50
- spin.update(progress: progress)
51
- spin.spin
52
- out = prs.process_repo(repo)
53
- spin.spin
54
- out
32
+ def filter_repos_w_prs(list)
33
+ list.each do |r|
34
+ r.prs.reject! { |pr| pr.author?(users.user.username) } if options[:skip_self]
35
+ r.prs.reject!(&:approved?) if options[:hide_approved]
36
+ r.prs.select!(&:needs_review?) if options[:needs_review]
37
+ r.prs.select! { |pr| pr.actionable?(users.user.username) } if options[:actionable]
55
38
  end
56
39
 
57
- spin.stop('done')
58
-
59
- result
40
+ list.select(&:has_prs?).map!(&:pretty_print).join("\n\n")
60
41
  end
61
42
 
62
- def repos_no_prs(list_all)
63
- list_all
64
- .select { |item| item[:state] == :success && item[:prs].empty? }
65
- .map { |item| item[:repo] }
66
- end
67
-
68
- def repos_with_prs(list)
69
- list = needs_review(list) if options[:needs_review]
70
- list = skip_self(list) if options[:skip_self]
71
- list = hide_approved_or_commented(list) if options[:hide_approved]
72
- list = actionable(list) if options[:actionable]
73
- list = select_repos_w_prs(list)
43
+ def get_broken(list)
44
+ broken = list.select(&:broken?)
45
+ return nil if broken.none?
74
46
 
75
- list.map { |item| Michael::Models::RepositoryFormatter.new(item[:repo], item[:prs]).pretty }
47
+ 'Broken repos: ' + broken.map(&:pretty_print).join(', ')
76
48
  end
77
49
 
78
- def skip_self(list)
79
- list.each do |item|
80
- item[:prs] = item[:prs].reject { |pr| pr.author == user.username }
81
- end
82
- end
50
+ def get_empty(list)
51
+ return nil unless options[:show_empty]
83
52
 
84
- def hide_approved_or_commented(list)
85
- list.each do |item|
86
- item[:prs] = item[:prs].reject do |pr|
87
- pr.reviews.all? { |review| review.approved? || review.commented? }
88
- end
89
- end
90
- end
53
+ empty = list.reject(&:has_prs?)
54
+ return nil if empty.none?
91
55
 
92
- def select_repos_w_prs(list)
93
- list.select { |item| item[:state] == :success && item[:prs].any? }
56
+ 'No PRs: ' + empty.map(&:pretty_print).join(', ')
94
57
  end
95
58
 
96
- def needs_review(list)
97
- list.each do |item|
98
- item[:prs] = item[:prs].reject do |pr|
99
- pr.reviews.any?
59
+ def print_waiting(queue)
60
+ Thread.new do
61
+ until queue.closed?
62
+ queue.pop
63
+ print '.'
64
+ $stdout.flush
100
65
  end
101
- end
102
- end
103
66
 
104
- def actionable(list)
105
- list.each do |item|
106
- item[:prs] = item[:prs].reject do |pr|
107
- reviewed = pr.reviews.any? { |review| review.author == user.username }
108
- new_changes = pr.last_update_head?
109
-
110
- reviewed && !new_changes
111
- end
67
+ puts
112
68
  end
113
69
  end
114
-
115
- def print_good_prs(out, list)
116
- out.puts "\n" + list.join("\n\n")
117
- end
118
-
119
- def print_repos_w_no_prs(out, list)
120
- out.puts "\nRepos with no opened PRs: #{list.join(', ')}" if list.any?
121
- end
122
-
123
- def print_broken_repos(out, list)
124
- return if list.empty?
125
-
126
- list = list.map { |repo| pastel.on_red.black(repo[:repo]) }
127
- out.puts "\nBad repos: #{list.join(', ')}"
128
- end
129
70
  end
130
71
  end
131
72
  end