codeowner_validator 0.3.1 → 0.4.0

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 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