codeowner_validator 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 624518c4812a8172b2b08fb3afc5f5c67f862333bcf46e4ded910962d8219d8c
4
- data.tar.gz: af12af2b285d3e7232e00933b8a594b4d2798d1bdc7fb79aec4177b93fa2cbdb
3
+ metadata.gz: ac6b80b200b35f8995987761579322d31d1755d571d66bdb8216b4b83acdb28b
4
+ data.tar.gz: 7898cbea2c42cf3406575622c4acca6541e9233ad9da9be67b15081010972088
5
5
  SHA512:
6
- metadata.gz: ebebb11fdb1788a4599c8e691f1752bb0e6bd259e4130bc388407566312e0825aa0f46181ac1f021158cbbc2091b1d38eb39773ce052adba753e0117e9dc5532
7
- data.tar.gz: 03d6770e812e540f641e8ddc2c193c3ded2a07a37eea2e75c4b8beb991f773501ce8911cde8e474eebe39944c69eeec5b48bf4acf389006df339e023c0e7d821
6
+ metadata.gz: 7cbbcb4882fd2b3348dfdd4adcfd6c5ae6babc7ee9d375869a682377ee0a37bf27eccccff0c8cba3ea0bb96464aeb95e84cb8b8a871126fcaf6cd0fa55b91623
7
+ data.tar.gz: '090ceb4dc06a0eb4f793928dd691fdb27c28c8f9674a0efb902f0c96fa4cd9a3898a060f9d291c9d0d46757a8ab95ba55c5afe63d6f3560552460d3247746d20'
data/CHANGELOG.md CHANGED
@@ -11,4 +11,7 @@
11
11
  - Update to support ruby 3.2 ([#9](https://github.com/cerner/codeowner_validator/pull/9))
12
12
 
13
13
  # 0.3.1
14
- - Back version of ruby to be in RVM supported set ([#10](https://github.com/cerner/codeowner_validator/pull/10))
14
+ - Back version of ruby to be in RVM supported set ([#10](https://github.com/cerner/codeowner_validator/pull/10))
15
+
16
+ # 0.4.0
17
+ - Remove usage of codeowner-checker from project and pulled in required classes ([#11](https://github.com/cerner/codeowner_validator/pull/11))
@@ -33,12 +33,12 @@ Gem::Specification.new do |spec|
33
33
  # rubocop:enable Gemspec/RequiredRubyVersion
34
34
 
35
35
  spec.add_dependency 'rainbow', '>= 2.0', '< 4.0.0'
36
- spec.add_dependency 'thor', '>= 0.19'
36
+ spec.add_dependency 'thor', '>= 1.0'
37
37
 
38
38
  spec.add_dependency 'tty-prompt', '~> 0.12'
39
39
  spec.add_dependency 'tty-spinner', '~> 0.4'
40
40
  spec.add_dependency 'tty-table', '~> 0.8'
41
41
 
42
- spec.add_dependency 'codeowners-checker', '~> 1.1'
43
42
  spec.add_dependency 'git', '~> 1.0'
43
+ spec.add_dependency 'pathspec', '>= 0.2'
44
44
  end
@@ -3,8 +3,7 @@
3
3
  require 'pathname'
4
4
  require_relative 'helpers/utility_helper'
5
5
  require 'codeowner_validator/lists/whitelist'
6
- require 'codeowners/checker/group'
7
- require_relative '../codeowners/checker/group/line'
6
+ require_relative '../codeowners/checker/group'
8
7
 
9
8
  # rubocop:disable Style/ImplicitRuntimeError
10
9
  module CodeownerValidator
@@ -13,7 +13,14 @@ module CodeownerValidator
13
13
  def in_folder(folder)
14
14
  raise "The folder location '#{folder}' does not exists" unless File.directory?(folder)
15
15
 
16
- with_clean_env do
16
+ if defined?(Bundler)
17
+ method = Bundler.respond_to?(:with_unbundled_env) ? :with_unbundled_env : :with_clean_env
18
+ Bundler.send(method) do
19
+ Dir.chdir folder do
20
+ yield
21
+ end
22
+ end
23
+ else
17
24
  Dir.chdir folder do
18
25
  yield
19
26
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CodeownerValidator
4
- VERSION = '0.3.1'
4
+ VERSION = '0.4.0'
5
5
 
6
6
  # version module
7
7
  module Version
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
- require 'codeowners/checker'
5
- # pull in monkeypatch for codeowners-checker
6
- require_relative 'codeowners/checker/group/line'
7
4
  Dir.glob(File.join(File.dirname(__FILE__), 'codeowner_validator', '**/*.rb'), &method(:require))
5
+ Dir.glob(File.join(File.dirname(__FILE__), 'codeowners', '**/*.rb'), &method(:require))
8
6
 
9
7
  # Public: The code owner validator space is utilized for validations against
10
8
  # the code owner file for a given repository.
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Codeowners
4
+ class Checker
5
+ # Array.delete in contrary to Ruby documentation uses == instead of equal? for comparison.
6
+ # safe_delete removes an object from an array comparing objects by equal? method.
7
+ module Array
8
+ def safe_delete(object)
9
+ delete_at(index { |item| item.equal?(object) })
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Array.prepend(Codeowners::Checker::Array)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'line'
4
+
5
+ module Codeowners
6
+ class Checker
7
+ class Group
8
+ # Define and manage comment line.
9
+ class Comment < Line
10
+ # Matches if the line is a comment.
11
+ # @return [Boolean] if the line start with `#`
12
+ def self.match?(line)
13
+ line.start_with?('#')
14
+ end
15
+
16
+ # Return the comment level if the comment works like a markdown
17
+ # headers.
18
+ # @return [Integer] with the heading level.
19
+ #
20
+ # @example
21
+ # Comment.new('# First level').level # => 1
22
+ # Comment.new('## Second').level # => 2
23
+ def level
24
+ (@line[/^#+/] || '').size
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ require_relative 'group_begin_comment'
32
+ require_relative 'group_end_comment'
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'line'
4
+
5
+ module Codeowners
6
+ class Checker
7
+ class Group
8
+ # Define line type empty line.
9
+ class Empty < Line
10
+ def self.match?(line)
11
+ line.empty?
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'comment'
4
+
5
+ module Codeowners
6
+ class Checker
7
+ class Group
8
+ # Define line type GroupBeginComment which is used for defining the beggining
9
+ # of a group.
10
+ class GroupBeginComment < Comment
11
+ def self.match?(line)
12
+ line.lstrip =~ /^#+ BEGIN/
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'comment'
4
+
5
+ module Codeowners
6
+ class Checker
7
+ class Group
8
+ # Define line type GroupEndComment which is used for defining the end
9
+ # of a group.
10
+ class GroupEndComment < Comment
11
+ def self.match?(line)
12
+ line.lstrip =~ /^#+ END/
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,12 +1,69 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # monkeypatch class to include line number
3
+ require 'pathname'
4
4
  module Codeowners
5
5
  class Checker
6
6
  class Group
7
+ # It sorts lines from CODEOWNERS file to different line types and holds
8
+ # shared methods for all lines.
7
9
  class Line
8
- attr_accessor :line_number
10
+ attr_accessor :parent, :line_number
11
+
12
+ def self.build(line)
13
+ subclasses.each do |klass|
14
+ return klass.new(line) if klass.match?(line)
15
+ end
16
+ UnrecognizedLine.new(line)
17
+ end
18
+
19
+ def self.subclasses
20
+ [Empty, GroupBeginComment, GroupEndComment, Comment, Pattern]
21
+ end
22
+
23
+ def initialize(line)
24
+ @line = line
25
+ end
26
+
27
+ def to_s
28
+ @line
29
+ end
30
+
31
+ def to_content
32
+ to_s
33
+ end
34
+
35
+ def to_file
36
+ to_s
37
+ end
38
+
39
+ def pattern?
40
+ is_a?(Pattern)
41
+ end
42
+
43
+ def to_tree(indentation)
44
+ indentation + to_s
45
+ end
46
+
47
+ def remove!
48
+ parent&.remove(self)
49
+ self.parent = nil
50
+ end
51
+
52
+ def ==(other)
53
+ return false unless other.is_a?(self.class)
54
+
55
+ other.to_s == to_s
56
+ end
57
+
58
+ def <=>(other)
59
+ to_s <=> other.to_s
60
+ end
9
61
  end
10
62
  end
11
63
  end
12
64
  end
65
+
66
+ require_relative 'empty'
67
+ require_relative 'comment'
68
+ require_relative 'pattern'
69
+ require_relative 'unrecognized_line'
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'line'
4
+ require_relative '../owner'
5
+ require 'pathspec'
6
+
7
+ module Codeowners
8
+ class Checker
9
+ class Group
10
+ # Defines and manages line type pattern.
11
+ # Parse the line into pattern, owners and whitespaces.
12
+ class Pattern < Line
13
+ attr_accessor :owners, :whitespace
14
+ attr_reader :pattern, :spec
15
+
16
+ def self.match?(line)
17
+ _pattern, *owners = line.split(/\s+/)
18
+ Owner.valid?(*owners)
19
+ end
20
+
21
+ def initialize(line)
22
+ super
23
+ parse(line)
24
+ end
25
+
26
+ def owner
27
+ owners.first
28
+ end
29
+
30
+ def rename_owner(owner, new_owner)
31
+ owners.delete(owner)
32
+ owners << new_owner unless owners.include?(new_owner)
33
+ end
34
+
35
+ # Parse the line counting whitespaces between pattern and owners.
36
+ def parse(line)
37
+ @pattern, *@owners = line.split(/\s+/)
38
+ @whitespace = line.split('@').first.count(' ') - 1
39
+ @spec = parse_spec(@pattern)
40
+ end
41
+
42
+ def match_file?(file)
43
+ spec.match file
44
+ end
45
+
46
+ def pattern=(new_pattern)
47
+ @whitespace += @pattern.size - new_pattern.size
48
+ @whitespace = 1 if @whitespace < 1
49
+
50
+ @spec = parse_spec(new_pattern)
51
+ @pattern = new_pattern
52
+ end
53
+
54
+ # @return String with the pattern and owners
55
+ # Use @param preserve_whitespaces to keep the previous identation.
56
+ def to_file(preserve_whitespaces: true)
57
+ line = pattern
58
+ spaces = preserve_whitespaces ? whitespace : 0
59
+ line << ' ' * spaces
60
+ [line, *owners].join(' ')
61
+ end
62
+
63
+ def to_s
64
+ to_file(preserve_whitespaces: false)
65
+ end
66
+
67
+ def parse_spec(pattern)
68
+ PathSpec.from_lines(pattern)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'line'
4
+
5
+ module Codeowners
6
+ class Checker
7
+ class Group
8
+ # Hold lines which are not defined in other line classes.
9
+ class UnrecognizedLine < Line
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'line_grouper'
4
+ require_relative 'group/line'
5
+ require_relative 'array'
6
+
7
+ module Codeowners
8
+ class Checker
9
+ # Manage the groups content and handle operations on the groups.
10
+ class Group
11
+ include Enumerable
12
+
13
+ attr_accessor :parent
14
+
15
+ def self.parse(lines)
16
+ new.parse(lines)
17
+ end
18
+
19
+ def initialize
20
+ @list = []
21
+ end
22
+
23
+ def each(&block)
24
+ @list.each do |object|
25
+ if object.is_a?(Group)
26
+ object.each(&block)
27
+ else
28
+ yield(object)
29
+ end
30
+ end
31
+ end
32
+
33
+ def parse(lines)
34
+ LineGrouper.call(self, lines)
35
+ end
36
+
37
+ def to_content
38
+ @list.flat_map(&:to_content)
39
+ end
40
+
41
+ def to_file
42
+ @list.flat_map(&:to_file)
43
+ end
44
+
45
+ # Returns an array of strings representing the structure of the group.
46
+ # It indent internal subgroups for readability and debugging purposes.
47
+ def to_tree(indentation = '')
48
+ @list.each_with_index.flat_map do |item, index|
49
+ if indentation.empty?
50
+ item.to_tree(indentation + ' ')
51
+ elsif index.zero?
52
+ item.to_tree(indentation + '+ ')
53
+ elsif index == @list.length - 1
54
+ item.to_tree(indentation + '\\ ')
55
+ else
56
+ item.to_tree(indentation + '| ')
57
+ end
58
+ end
59
+ end
60
+ # rubocop:enable Metrics/MethodLength
61
+ # rubocop:enable Metrics/AbcSize
62
+
63
+ def owner
64
+ owners.first
65
+ end
66
+
67
+ # Owners are ordered by the amount of occurrences
68
+ def owners
69
+ all_owners.group_by(&:itself).sort_by do |_owner, occurrences|
70
+ -occurrences.count
71
+ end.map(&:first)
72
+ end
73
+
74
+ def subgroups_owned_by(owner)
75
+ @list.flat_map do |item|
76
+ next unless item.is_a?(Group)
77
+
78
+ a = []
79
+ a << item if item.owner == owner
80
+ a += item.subgroups_owned_by(owner)
81
+ a
82
+ end.compact
83
+ end
84
+
85
+ def title
86
+ @list.first.to_s
87
+ end
88
+
89
+ def create_subgroup
90
+ group = self.class.new
91
+ group.parent = self
92
+ @list << group
93
+ group
94
+ end
95
+
96
+ def add(line)
97
+ line.parent = self
98
+ @list << line
99
+ end
100
+
101
+ def insert(line)
102
+ line.parent = self
103
+ index = insert_at_index(line)
104
+ @list.insert(index, line)
105
+ end
106
+
107
+ def remove(line)
108
+ @list.safe_delete(line)
109
+ remove! unless any? { |object| object.is_a? Pattern }
110
+ end
111
+
112
+ def remove!
113
+ @list.clear
114
+ parent&.remove(self)
115
+ self.parent = nil
116
+ end
117
+
118
+ def ==(other)
119
+ other.is_a?(Group) && other.list == list
120
+ end
121
+
122
+ protected
123
+
124
+ attr_accessor :list
125
+
126
+ private
127
+
128
+ def all_owners
129
+ flat_map do |item|
130
+ item.owners if item.pattern?
131
+ end.compact
132
+ end
133
+
134
+ # rubocop:disable Metrics/AbcSize
135
+ def insert_at_index(line)
136
+ new_patterns_sorted = @list.grep(Pattern).dup.push(line).sort
137
+ previous_line_index = new_patterns_sorted.index { |l| l.equal? line } - 1
138
+ previous_line = new_patterns_sorted[previous_line_index]
139
+ padding = previous_line.pattern.size + previous_line.whitespace - line.pattern.size
140
+ line.whitespace = [1, padding].max
141
+
142
+ if previous_line_index >= 0
143
+ @list.index { |l| l.equal? previous_line } + 1
144
+ else
145
+ find_last_line_of_initial_comments
146
+ end
147
+ end
148
+ # rubocop:enable Metrics/AbcSize
149
+
150
+ def find_last_line_of_initial_comments
151
+ @list.each_with_index do |item, index|
152
+ return index unless item.is_a?(Comment)
153
+ end
154
+ 0
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Codeowners
4
+ class Checker
5
+ # Create groups and subgroups structure for the lines in the CODEOWNERS file.
6
+ class LineGrouper
7
+ def self.call(group, lines)
8
+ new(group, lines).call
9
+ end
10
+
11
+ def initialize(group, lines)
12
+ @group_buffer = [group]
13
+ @lines = lines
14
+ end
15
+
16
+ def call
17
+ lines.each_with_index do |line, index|
18
+ case line
19
+ when Codeowners::Checker::Group::Empty
20
+ ensure_groups_structure
21
+ when Codeowners::Checker::Group::GroupBeginComment
22
+ trim_groups(line.level)
23
+ create_groups_structure(line.level)
24
+ when Codeowners::Checker::Group::GroupEndComment
25
+ trim_subgroups(line.level)
26
+ create_groups_structure(line.level)
27
+ when Codeowners::Checker::Group::Comment
28
+ if previous_line_empty?(index)
29
+ trim_groups(line.level)
30
+ else
31
+ trim_subgroups(line.level)
32
+ end
33
+ create_groups_structure(line.level)
34
+ when Codeowners::Checker::Group::Pattern
35
+ if new_owner?(line, index)
36
+ trim_groups(current_level)
37
+ new_group
38
+ end
39
+ ensure_groups_structure
40
+ when Codeowners::Checker::Group::UnrecognizedLine
41
+ ensure_groups_structure
42
+ else
43
+ raise StandardError, "Do not know how to handle line: #{line.inspect}"
44
+ end
45
+ current_group.add(line)
46
+ end
47
+ group_buffer.first
48
+ end
49
+ # rubocop:enable Metrics/AbcSize
50
+ # rubocop:enable Metrics/CyclomaticComplexity
51
+ # rubocop:enable Metrics/MethodLength
52
+
53
+ private
54
+
55
+ attr_reader :group_buffer, :lines
56
+
57
+ def previous_line_empty?(index)
58
+ index.positive? && lines[index - 1].is_a?(Codeowners::Checker::Group::Empty)
59
+ end
60
+
61
+ def new_owner?(line, index) # rubocop:disable Metrics/MethodLength
62
+ if previous_line_empty?(index)
63
+ offset = 2
64
+ while (index - offset).positive?
65
+ case lines[index - offset]
66
+ when Codeowners::Checker::Group::GroupEndComment
67
+ nil
68
+ when Codeowners::Checker::Group::Comment
69
+ return false
70
+ when Codeowners::Checker::Group::Pattern
71
+ return line.owner != lines[index - offset].owner
72
+ end
73
+ offset += 1
74
+ end
75
+ end
76
+ false
77
+ end
78
+
79
+ def current_group
80
+ group_buffer.last
81
+ end
82
+
83
+ def current_level
84
+ group_buffer.length - 1
85
+ end
86
+
87
+ def new_group
88
+ group = current_group.create_subgroup
89
+ group_buffer << group
90
+ end
91
+
92
+ def ensure_groups_structure
93
+ new_group if current_level.zero?
94
+ end
95
+
96
+ def create_groups_structure(level)
97
+ new_group while current_level < level
98
+ end
99
+
100
+ def trim_groups(level)
101
+ group_buffer.slice!(level..-1)
102
+ end
103
+
104
+ def trim_subgroups(level)
105
+ trim_groups(level + 1)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Codeowners
4
+ class Checker
5
+ # Owner shared methods.
6
+ module Owner
7
+ def self.valid?(*owners)
8
+ owners.any? && owners.all? { |owner| owner.include?('@') }
9
+ end
10
+ end
11
+ end
12
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: codeowner_validator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Howdeshell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-06 00:00:00.000000000 Z
11
+ date: 2023-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rainbow
@@ -36,14 +36,14 @@ dependencies:
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '0.19'
39
+ version: '1.0'
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: '0.19'
46
+ version: '1.0'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: tty-prompt
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -87,33 +87,33 @@ dependencies:
87
87
  - !ruby/object:Gem::Version
88
88
  version: '0.8'
89
89
  - !ruby/object:Gem::Dependency
90
- name: codeowners-checker
90
+ name: git
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '1.1'
95
+ version: '1.0'
96
96
  type: :runtime
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '1.1'
102
+ version: '1.0'
103
103
  - !ruby/object:Gem::Dependency
104
- name: git
104
+ name: pathspec
105
105
  requirement: !ruby/object:Gem::Requirement
106
106
  requirements:
107
- - - "~>"
107
+ - - ">="
108
108
  - !ruby/object:Gem::Version
109
- version: '1.0'
109
+ version: '0.2'
110
110
  type: :runtime
111
111
  prerelease: false
112
112
  version_requirements: !ruby/object:Gem::Requirement
113
113
  requirements:
114
- - - "~>"
114
+ - - ">="
115
115
  - !ruby/object:Gem::Version
116
- version: '1.0'
116
+ version: '0.2'
117
117
  description: 'GitHub CODEOWNERS validator
118
118
 
119
119
  '
@@ -178,7 +178,17 @@ files:
178
178
  - lib/codeowner_validator/tasks/syntax_checker.rb
179
179
  - lib/codeowner_validator/validator.rb
180
180
  - lib/codeowner_validator/version.rb
181
+ - lib/codeowners/checker/array.rb
182
+ - lib/codeowners/checker/group.rb
183
+ - lib/codeowners/checker/group/comment.rb
184
+ - lib/codeowners/checker/group/empty.rb
185
+ - lib/codeowners/checker/group/group_begin_comment.rb
186
+ - lib/codeowners/checker/group/group_end_comment.rb
181
187
  - lib/codeowners/checker/group/line.rb
188
+ - lib/codeowners/checker/group/pattern.rb
189
+ - lib/codeowners/checker/group/unrecognized_line.rb
190
+ - lib/codeowners/checker/line_grouper.rb
191
+ - lib/codeowners/checker/owner.rb
182
192
  homepage: https://github.com/cerner/codeowner_validator
183
193
  licenses:
184
194
  - Apache-2.0