codeowners-checker 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interactive_ops'
4
+
5
+ module Codeowners
6
+ module Cli
7
+ module Wizards
8
+ # Attempt to find a name similar to one provided in the owners list.
9
+ # Suggest to add new owner to the owners list.
10
+ # Only return decision without applying any modifications.
11
+ class NewOwnerWizard
12
+ include InteractiveOps
13
+
14
+ DEFAULT_OPTIONS = {
15
+ 'a' => '(a) add a new owner',
16
+ 'r' => '(r) rename owner',
17
+ 'i' => '(i) ignore owner in this session',
18
+ 'q' => '(q) quit and save'
19
+ }.freeze
20
+
21
+ def initialize(owners_list)
22
+ @owners_list = owners_list
23
+ end
24
+
25
+ def suggest_fixing(line, new_owner)
26
+ suggested_owner = suggest_name_from_owners_list(new_owner)
27
+ case prompt(line, new_owner, suggested_owner)
28
+ when 'y' then [:rename, suggested_owner]
29
+ when 'a' then :add
30
+ when 'r' then [:rename, keep_asking_until_valid_owner]
31
+ when 'i' then :ignore
32
+ when 'q' then :quit
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def suggest_name_from_owners_list(new_owner)
39
+ require 'fuzzy_match'
40
+ search = FuzzyMatch.new(@owners_list.owners)
41
+ (suggested_owner, dice, _lev) = search.find_with_score(new_owner)
42
+ return suggested_owner if dice && dice > 0.6
43
+ end
44
+
45
+ def prompt(line, new_owner, suggested_owner)
46
+ prompt_options = build_prompt_options(suggested_owner)
47
+ ask(<<~QUESTION, limited_to: prompt_options.keys)
48
+ #{question_body(line, new_owner, suggested_owner)}
49
+ #{question_options(prompt_options)}
50
+ QUESTION
51
+ end
52
+
53
+ def question_body(line, new_owner, suggested_owner)
54
+ prompt = "Unknown owner: #{new_owner} for pattern: #{line.pattern}."
55
+ if suggested_owner
56
+ prompt + " Did you mean #{suggested_owner}?"
57
+ else
58
+ prompt + ' Choose an option:'
59
+ end
60
+ end
61
+
62
+ def question_options(accepted_options)
63
+ accepted_options.values.join("\n")
64
+ end
65
+
66
+ def build_prompt_options(suggested_owner)
67
+ return DEFAULT_OPTIONS unless suggested_owner
68
+
69
+ { 'y' => "(y) correct to #{suggested_owner}" }.merge(DEFAULT_OPTIONS)
70
+ end
71
+
72
+ def keep_asking_until_valid_owner
73
+ loop do
74
+ owner = ask('New owner: ')
75
+ owner = '@' + owner unless owner[0] == '@'
76
+ return owner if @owners_list.valid_owner?(owner)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interactive_ops'
4
+
5
+ module Codeowners
6
+ module Cli
7
+ module Wizards
8
+ # Suggests to fix unrecognized lines in the codeowners file.
9
+ # Only returns decision without applying any modifications.
10
+ class UnrecognizedLineWizard
11
+ include InteractiveOps
12
+
13
+ def suggest_fixing(line)
14
+ case prompt(line)
15
+ when 'i' then :ignore
16
+ when 'y' then [:replace, keep_asking_until_valid_line]
17
+ when 'd' then :delete
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def prompt(line)
24
+ ask(<<~QUESTION, limited_to: %w[y i d])
25
+ #{line.to_s.inspect} is in unrecognized format. Would you like to edit?
26
+ (y) yes
27
+ (i) ignore
28
+ (d) delete the line
29
+ QUESTION
30
+ end
31
+
32
+ def keep_asking_until_valid_line
33
+ line = nil
34
+ loop do
35
+ new_line_string = ask('New line: ')
36
+ line = Codeowners::Checker::Group::Line.build(new_line_string)
37
+ break unless line.is_a?(Codeowners::Checker::Group::UnrecognizedLine)
38
+ end
39
+ line
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interactive_ops'
4
+
5
+ module Codeowners
6
+ module Cli
7
+ module Wizards
8
+ # Suggests to fix useless patterns in the codeowners file.
9
+ # Only returns decision without applying any modifications.
10
+ class UselessPatternWizard
11
+ include InteractiveOps
12
+
13
+ def suggest_fixing(line)
14
+ puts "Pattern #{line.pattern.inspect} doesn't match."
15
+ suggestion = Codeowners::Cli::SuggestFileFromPattern.new(line.pattern).pick_suggestion
16
+
17
+ # TODO: Handle duplicate patterns.
18
+ if suggestion
19
+ apply_suggestion(line, suggestion)
20
+ else
21
+ pattern_fix(line)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def apply_suggestion(line, suggestion)
28
+ case make_suggestion(suggestion)
29
+ when 'i' then :ignore
30
+ when 'y' then [:replace, suggestion]
31
+ when 'e' then edit_pattern(line)
32
+ when 'd' then :delete
33
+ when 'q' then :quit
34
+ end
35
+ end
36
+
37
+ def make_suggestion(suggestion)
38
+ ask(<<~QUESTION, limited_to: %w[y i e d q])
39
+ Replace with: #{suggestion.inspect}?
40
+ (y) yes
41
+ (i) ignore
42
+ (e) edit the pattern
43
+ (d) delete the pattern
44
+ (q) quit and save
45
+ QUESTION
46
+ end
47
+
48
+ def pattern_fix(line)
49
+ case pattern_suggest_fixing
50
+ when 'e' then edit_pattern(line)
51
+ when 'i' then :ignore
52
+ when 'd' then :delete
53
+ when 'q' then :quit
54
+ end
55
+ end
56
+
57
+ def pattern_suggest_fixing
58
+ ask(<<~QUESTION, limited_to: %w[i e d q])
59
+ (e) edit the pattern
60
+ (d) delete the pattern
61
+ (i) ignore
62
+ (q) quit and save
63
+ QUESTION
64
+ end
65
+
66
+ def edit_pattern(line)
67
+ new_pattern = ask("Replace pattern #{line.pattern.inspect} with: ")
68
+ return :nop if new_pattern.empty?
69
+
70
+ [:replace, new_pattern]
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rest-client'
4
+ require 'json'
5
+
6
+ module Codeowners
7
+ # Fetch teams and members from GitHub and return them in list
8
+ class GithubFetcher
9
+ class << self
10
+ GITHUB_URL = 'https://api.github.com'
11
+
12
+ # Fetch teams and members from GitHub.
13
+ # authorization_token is GitHub PAT with read:org scope
14
+ # @return <Array> with GitHub teams and individuals belonging to a given GitHub organization
15
+ def get_owners(github_org, authorization_token)
16
+ headers = get_headers(authorization_token)
17
+ base_url = GITHUB_URL + '/orgs/' + github_org
18
+ owners = []
19
+ list_entities(base_url + '/teams', headers) { |team| owners << "@#{github_org}/#{team['slug']}" }
20
+ list_entities(base_url + '/members', headers) { |member| owners << "@#{member['login']}" }
21
+ owners
22
+ end
23
+
24
+ private
25
+
26
+ # Helper method to get properly formatted HTTP headers
27
+ def get_headers(authorization_token)
28
+ {
29
+ Accept: 'application/vnd.github.v3+json',
30
+ Authorization: "token #{authorization_token}"
31
+ }
32
+ end
33
+
34
+ # Helper method that loops through all pages if GitHub returns a paged response
35
+ def list_entities(first_page, headers)
36
+ next_page = first_page
37
+ loop do
38
+ response = RestClient.get(next_page, headers)
39
+ response_json = JSON.parse(response.body)
40
+ response_json.each { |entity| yield entity }
41
+ next_page = get_next_page(response)
42
+ break unless next_page
43
+ end
44
+ end
45
+
46
+ # Helper method to parse and get URL of the next page from 'link' response header
47
+ def get_next_page(response)
48
+ return nil unless response.headers[:link]
49
+
50
+ matches = response.headers[:link].match('<([^>]+)>; rel="next"')
51
+ return matches[1] if matches
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Codeowners
4
+ # This class is responsible for print the reports
5
+ class Reporter
6
+ LABELS = {
7
+ missing_ref: 'No owner defined',
8
+ useless_pattern: 'Useless patterns',
9
+ invalid_owner: 'Invalid owner',
10
+ unrecognized_line: 'Unrecognized line'
11
+ }.freeze
12
+
13
+ class << self
14
+ def print_delimiter_line(error_type)
15
+ raise ArgumentError, "unknown error type '#{error_type}'" unless LABELS.key?(error_type)
16
+
17
+ print('-' * 30, LABELS[error_type], '-' * 30)
18
+ end
19
+
20
+ def print_error(error_type, inconsistencies, meta)
21
+ case error_type
22
+ when :invalid_owner then print("#{inconsistencies} MISSING: #{meta.join(', ')}")
23
+ else print(inconsistencies.to_s)
24
+ end
25
+ end
26
+
27
+ def print(*args)
28
+ puts(*args)
29
+ end
30
+ end
31
+ end
32
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: codeowners-checker
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jônatas Davi Paganini
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-09-10 00:00:00.000000000 Z
13
+ date: 2019-12-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: fuzzy_match
@@ -40,6 +40,34 @@ dependencies:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
42
  version: '1.5'
43
+ - !ruby/object:Gem::Dependency
44
+ name: json
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '2.1'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '2.1'
57
+ - !ruby/object:Gem::Dependency
58
+ name: rest-client
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '2.1'
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: '2.1'
43
71
  - !ruby/object:Gem::Dependency
44
72
  name: thor
45
73
  requirement: !ruby/object:Gem::Requirement
@@ -208,14 +236,25 @@ files:
208
236
  - lib/codeowners/checker/group/unrecognized_line.rb
209
237
  - lib/codeowners/checker/line_grouper.rb
210
238
  - lib/codeowners/checker/owner.rb
239
+ - lib/codeowners/checker/owners_list.rb
211
240
  - lib/codeowners/checker/version.rb
212
241
  - lib/codeowners/cli/base.rb
213
- - lib/codeowners/cli/check.rb
214
242
  - lib/codeowners/cli/config.rb
215
243
  - lib/codeowners/cli/filter.rb
244
+ - lib/codeowners/cli/interactive_ops.rb
245
+ - lib/codeowners/cli/interactive_resolver.rb
246
+ - lib/codeowners/cli/interactive_runner.rb
216
247
  - lib/codeowners/cli/main.rb
248
+ - lib/codeowners/cli/owners_list_handler.rb
217
249
  - lib/codeowners/cli/suggest_file_from_pattern.rb
250
+ - lib/codeowners/cli/wizards.rb
251
+ - lib/codeowners/cli/wizards/new_file_wizard.rb
252
+ - lib/codeowners/cli/wizards/new_owner_wizard.rb
253
+ - lib/codeowners/cli/wizards/unrecognized_line_wizard.rb
254
+ - lib/codeowners/cli/wizards/useless_pattern_wizard.rb
218
255
  - lib/codeowners/config.rb
256
+ - lib/codeowners/github_fetcher.rb
257
+ - lib/codeowners/reporter.rb
219
258
  homepage: https://github.com/toptal/codeowners-checker
220
259
  licenses:
221
260
  - MIT
@@ -235,8 +274,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
235
274
  - !ruby/object:Gem::Version
236
275
  version: '0'
237
276
  requirements: []
238
- rubyforge_project:
239
- rubygems_version: 2.7.6
277
+ rubygems_version: 3.0.3
240
278
  signing_key:
241
279
  specification_version: 4
242
280
  summary: Check consistency of Github CODEOWNERS and git changes.
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Codeowners
4
- module Cli
5
- class Check < Base
6
- end
7
- end
8
- end