codeowners-checker 1.0.4 → 1.0.5
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.
- checksums.yaml +4 -4
- data/README.md +29 -0
- data/bin/codeowners-checker +0 -1
- data/codeowners-checker.gemspec +2 -0
- data/lib/codeowners/checker.rb +25 -28
- data/lib/codeowners/checker/code_owners.rb +14 -13
- data/lib/codeowners/checker/file_as_array.rb +9 -8
- data/lib/codeowners/checker/group/pattern.rb +5 -0
- data/lib/codeowners/checker/owners_list.rb +63 -0
- data/lib/codeowners/checker/version.rb +1 -1
- data/lib/codeowners/cli/interactive_ops.rb +22 -0
- data/lib/codeowners/cli/interactive_resolver.rb +128 -0
- data/lib/codeowners/cli/interactive_runner.rb +34 -0
- data/lib/codeowners/cli/main.rb +36 -227
- data/lib/codeowners/cli/owners_list_handler.rb +32 -0
- data/lib/codeowners/cli/wizards.rb +11 -0
- data/lib/codeowners/cli/wizards/new_file_wizard.rb +94 -0
- data/lib/codeowners/cli/wizards/new_owner_wizard.rb +82 -0
- data/lib/codeowners/cli/wizards/unrecognized_line_wizard.rb +44 -0
- data/lib/codeowners/cli/wizards/useless_pattern_wizard.rb +75 -0
- data/lib/codeowners/github_fetcher.rb +55 -0
- data/lib/codeowners/reporter.rb +32 -0
- metadata +43 -5
- data/lib/codeowners/cli/check.rb +0 -8
@@ -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
|
+
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-
|
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
|
-
|
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.
|