codeowner_validator 0.1.1

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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  3. data/.github/ISSUE_TEMPLATE/config.yml +6 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
  5. data/.github/pull_request_template.md +32 -0
  6. data/.github/workflows/cd.yml +53 -0
  7. data/.github/workflows/ci.yml +39 -0
  8. data/.gitignore +23 -0
  9. data/.rspec +4 -0
  10. data/.rubocop.yml +926 -0
  11. data/.ruby-gemset +1 -0
  12. data/.ruby-version +1 -0
  13. data/.vscode/launch.json +53 -0
  14. data/CHANGELOG.md +5 -0
  15. data/CODE_OF_CONDUCT.md +74 -0
  16. data/CONTRIBUTING.md +40 -0
  17. data/CONTRIBUTORS.md +3 -0
  18. data/Gemfile +20 -0
  19. data/LICENSE +205 -0
  20. data/NOTICE +13 -0
  21. data/README.md +160 -0
  22. data/Rakefile +8 -0
  23. data/bin/bundle-audit +29 -0
  24. data/bin/bundler-audit +29 -0
  25. data/bin/codeowner_validator +8 -0
  26. data/bin/rake +29 -0
  27. data/bin/rspec +29 -0
  28. data/bin/rubocop +29 -0
  29. data/codeowner_validator.gemspec +41 -0
  30. data/lib/codeowner_validator/cli/validator_cli.rb +64 -0
  31. data/lib/codeowner_validator/code_owners.rb +308 -0
  32. data/lib/codeowner_validator/common/command.rb +40 -0
  33. data/lib/codeowner_validator/common/logging.rb +86 -0
  34. data/lib/codeowner_validator/common/tasks/base.rb +62 -0
  35. data/lib/codeowner_validator/group/comment/error.rb +21 -0
  36. data/lib/codeowner_validator/group/comment/info.rb +21 -0
  37. data/lib/codeowner_validator/group/comment/verbose.rb +21 -0
  38. data/lib/codeowner_validator/group/comment/warn.rb +21 -0
  39. data/lib/codeowner_validator/group/comment.rb +55 -0
  40. data/lib/codeowner_validator/helpers/config_helper.rb +73 -0
  41. data/lib/codeowner_validator/helpers/utility_helper.rb +30 -0
  42. data/lib/codeowner_validator/lists/whitelist.rb +145 -0
  43. data/lib/codeowner_validator/tasks/duplicate_checker.rb +32 -0
  44. data/lib/codeowner_validator/tasks/file_exists_checker.rb +35 -0
  45. data/lib/codeowner_validator/tasks/missing_assignment_checker.rb +32 -0
  46. data/lib/codeowner_validator/tasks/syntax_checker.rb +32 -0
  47. data/lib/codeowner_validator/validator.rb +76 -0
  48. data/lib/codeowner_validator/version.rb +11 -0
  49. data/lib/codeowner_validator.rb +32 -0
  50. data/lib/codeowners/checker/group/line.rb +12 -0
  51. metadata +204 -0
data/bin/rake ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
data/bin/rspec ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
data/bin/rubocop ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rubocop", "rubocop")
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'codeowner_validator/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'codeowner_validator'
9
+ spec.version = CodeownerValidator::VERSION
10
+ spec.authors = ['Greg Howdeshell']
11
+ spec.email = ['greg.howdeshell@gmail.com']
12
+
13
+ spec.summary = 'Write a short summary, because RubyGems requires one.'
14
+ spec.description = <<~DESC
15
+ GitHub CODEOWNERS validator
16
+ DESC
17
+ spec.homepage = 'https://github.com/cerner/codeowner_validator'
18
+ spec.license = 'Apache-2.0'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files =
23
+ Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(/^(test|spec|features)\//) }
25
+ end
26
+ spec.bindir = 'bin'
27
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.required_ruby_version = '>= 2.6.2'
31
+
32
+ spec.add_dependency 'rainbow', '>= 2.0', '< 4.0.0'
33
+ spec.add_dependency 'thor', '~> 0.19'
34
+
35
+ spec.add_dependency 'tty-prompt', '~> 0.12'
36
+ spec.add_dependency 'tty-spinner', '~> 0.4'
37
+ spec.add_dependency 'tty-table', '~> 0.8'
38
+
39
+ spec.add_dependency 'codeowners-checker', '~> 1.1'
40
+ spec.add_dependency 'git', '~> 1.0'
41
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'codeowner_validator/validator'
4
+
5
+ module CodeownerValidator
6
+ # Public: Class utilized for housing all validator executions
7
+ class ValidatorCLI < Thor
8
+ REPO_PATH_DESC = 'Absolute path to repository location for evaluation'
9
+ VERBOSE_DESC = 'Verbose output'
10
+
11
+ default_task :validate
12
+ desc '', 'validates the codeowners file'
13
+
14
+ method_option :repo_path, aliases: %w[-p --repo-path], desc: REPO_PATH_DESC
15
+ method_option :verbose, aliases: %w[-v --verbose], desc: VERBOSE_DESC, type: :boolean, default: false
16
+ # Public: Entry point execution to being the codeowner validation
17
+ def validate
18
+ get_user_input(options)
19
+
20
+ validator = Validator.new options
21
+ validator.validate
22
+ end
23
+
24
+ no_commands do
25
+ # combination of options from input and environment variables
26
+ def options
27
+ return @new_options if @new_options
28
+
29
+ original_options = super
30
+
31
+ # add any environment variables as overrides
32
+ @new_options =
33
+ {}.tap do |h|
34
+ original_options.each do |key, value|
35
+ h[key.to_sym] = value
36
+ end
37
+
38
+ h[:verbose] = ENV['VERBOSE'] unless ENV['VERBOSE'].nil?
39
+ h[:repo_path] = ENV['REPO_PATH'] unless ENV['REPO_PATH'].nil?
40
+ end
41
+
42
+ # setup initial configuration
43
+ CodeownerValidator.configure! @new_options
44
+
45
+ @new_options
46
+ end
47
+
48
+ # steps needed to ask for input that is deemed missing
49
+ def get_user_input(options = {})
50
+ return if options[:repo_path]
51
+
52
+ ConfigHelper.ask(
53
+ ident: :repo_path,
54
+ prompt: REPO_PATH_DESC,
55
+ required: true,
56
+ default: Dir.pwd
57
+ )
58
+
59
+ # set response on the options hash
60
+ options[:repo_path] = CodeownerValidator.repo_path
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require_relative 'helpers/utility_helper'
5
+ require 'codeowner_validator/lists/whitelist'
6
+ require 'codeowners/checker/group'
7
+ require_relative '../codeowners/checker/group/line'
8
+
9
+ # rubocop:disable Style/ImplicitRuntimeError
10
+ module CodeownerValidator
11
+ # Public: Manages the interactions with the GitHub CODEOWNERS file. Information such as
12
+ # assignments, missing assignments, etc are retrieved though the usage of this class.
13
+ class CodeOwners
14
+ include UtilityHelper
15
+
16
+ # Public: The absolute path the the repository for evaluation
17
+ attr_reader :repo_path
18
+
19
+ class << self
20
+ # Public: Returns a instance of the [CodeOwners] object
21
+ #
22
+ # @param [Hash] args A hash of arguments allowed for creation of a [CodeOwners] object
23
+ # @option args [String] :repo_path The absolute path to the repository to be evaluated
24
+ def persist!(repo_path:, **args)
25
+ new(repo_path: repo_path, **args)
26
+ end
27
+ end
28
+
29
+ # Public: Returns a instance of the [CodeOwners] object
30
+ #
31
+ # @param [Hash] _args A hash of arguments allowed for creation of a [CodeOwners] object
32
+ # @option args [String] :repo_path The absolute path to the repository to be evaluated
33
+ def initialize(repo_path:, **_args)
34
+ @repo_path = repo_path
35
+
36
+ # initialize params to suppress warnings about instance variables not initialized
37
+ @list = nil
38
+ @codeowner_file = nil
39
+ @codeowner_file_paths = nil
40
+ @included_files = nil
41
+ end
42
+
43
+ # Public: Returns a [Hash] of key/value pairs of relative file name to git status ('A', 'M', 'D')
44
+ # between two commits.
45
+ #
46
+ # @param [String] from The staring point for analyzing. Defaults to 'HEAD'
47
+ # @param [String] to The end point for analyzing. Defaults to 'HEAD^'
48
+ # @return [Hash] of key/value pairs of relative file name to git status ('A', 'M', 'D')
49
+ #
50
+ # @example
51
+ # {
52
+ # "config/tenants/cert-int/eds04.yml" => "M",
53
+ # "config/tenants/dev-int/edd03.yml" => "M"
54
+ # }
55
+ def changes_to_analyze(from: 'HEAD', to: 'HEAD^')
56
+ git.diff(from, to).name_status.select(&whitelist)
57
+ end
58
+
59
+ # Public: Returns an [Array] of patterns that have no files associated to them
60
+ #
61
+ # @return [Array] [Array] of patterns that have no files associated to them
62
+ def useless_pattern
63
+ @useless_pattern ||=
64
+ list.select do |line|
65
+ line.pattern? && !pattern_has_files(line.pattern)
66
+ end
67
+ end
68
+
69
+ # Public: Returns a [Hash] of key/value pairs of relative file name to git details for a supplied pattern
70
+ #
71
+ # @param [String] pattern The pattern to search for files within the repository
72
+ # @return [Hash] of key/value pairs of relative file name to git details for a supplied pattern
73
+ #
74
+ # @example
75
+ # pattern_has_files('config/tenants/dev')
76
+ # {
77
+ # {
78
+ # "config/tenants/dev/64dev.yml" => {
79
+ # path: "config/tenants/dev/64dev.yml",
80
+ # mode_index: "100644",
81
+ # sha_index: "598b2193b22bc006ff000e3a51f6805b336ebed8",
82
+ # stage: "0"
83
+ # },
84
+ # "config/tenants/dev/deveng.yml" => {
85
+ # path: "config/tenants/dev/deveng.yml",
86
+ # mode_index: "100644",
87
+ # sha_index: "ee63d8c6e9ae7f432aafa7bd3436fae222cb3f5c",
88
+ # stage: "0"
89
+ # }
90
+ # }
91
+ # }
92
+ def pattern_has_files(pattern)
93
+ git.ls_files(pattern.gsub(/^\//, '')).reject(&whitelist).any?
94
+ end
95
+
96
+ # Public: Return a list of files from the repository that do not have an owner assigned
97
+ #
98
+ # @return [Array] of files from the repository that are missing an assignment
99
+ def missing_assignments
100
+ @missing_assignments ||= included_files.reject(&method(:defined_owner?))
101
+ end
102
+
103
+ # Public: Return a list of files from the codowners file that are missing the owner assignment
104
+ #
105
+ # @return [Array] of unrecognized lines from the codeowners file missing owner assignment
106
+ def unrecognized_assignments
107
+ list.select do |line|
108
+ line.is_a?(Codeowners::Checker::Group::UnrecognizedLine)
109
+ end
110
+ end
111
+
112
+ # Public: Return a list of files from the codeowners file that are noted but do not exist
113
+ #
114
+ # @return [Array] of <Codeowners::Checker::Group::UnrecognizedLine>s or <Codeowners::Checker::Group::Pattern>s
115
+ def invalid_reference_lines
116
+ list.select do |line|
117
+ next unless line.pattern? || line.is_a?(Codeowners::Checker::Group::UnrecognizedLine)
118
+
119
+ filename = line.pattern? ? line.pattern : line.to_file
120
+ File.exist?(File.join(repo_path, filename)) == false
121
+ end
122
+ end
123
+
124
+ # Public: Returns a [Hash] of keyed item to array of patterns that are duplicated within the code owners file
125
+ #
126
+ # @return [Hash] of keyed [String] patterns to an [Array] of [Pattern]s
127
+ #
128
+ # Example:
129
+ # {
130
+ # "config/domains/cert-int/CaseCartCoordinator_CERTIFICATION.yml": [
131
+ # {Codeowners::Checker::Group::Pattern},
132
+ # {Codeowners::Checker::Group::Pattern}
133
+ # ]
134
+ # }
135
+ def duplicated_patterns
136
+ list.select { |l| l.pattern? }.group_by { |e| e.pattern }.select { |_k, v| v.size > 1 }
137
+ end
138
+
139
+ # Public: Return all relative paths to files for evaluation if to be owned by a set of code owners
140
+ #
141
+ # @return [Array] of relative paths to files that are to be included within the code owner evaluation
142
+ def included_files
143
+ return @included_files if @included_files
144
+
145
+ @included_files = []
146
+ in_folder repo_path do
147
+ Dir.glob(File.join(Dir.pwd, '**/*')) do |f|
148
+ p = Pathname.new(f)
149
+ # only return files for evaluation
150
+ next unless p.file?
151
+
152
+ # in order to properly match, must evaluate relative to the repo location that is
153
+ # being evaluated. absolute paths of the files do not match with relative matches
154
+ relative_path_to_file = p.relative_path_from(Dir.pwd)
155
+ @included_files << relative_path_to_file.to_s if whitelist.whitelisted?(relative_path_to_file)
156
+ end
157
+ end
158
+
159
+ @included_files
160
+ end
161
+
162
+ # Public: Returns the patterns utilized with an array association to those patterns
163
+ #
164
+ # @return [Hash] The patterns keyed by the team with an array of associations
165
+ #
166
+ # Example Response:
167
+ # {
168
+ # "@orion-delivery/delivery-team": [
169
+ # "*"
170
+ # ],
171
+ # "@orion-delivery/orion-delivery-ets": [
172
+ # "config/domains/production/**/*",
173
+ # "config/domains/sandbox/**/*"
174
+ # ],
175
+ # "@orion-delivery/orion-shells": [
176
+ # "config/feature_definitions/authn-android-enable_biometric_unlock.yml",
177
+ # "config/domains/cert-int/IONServer_CERTIFICATION.yml"
178
+ # ]
179
+ # }
180
+ def patterns_by_owner
181
+ @patterns_by_owner ||=
182
+ main_group.each_with_object(hash_of_arrays) do |line, patterns_by_owner|
183
+ next unless line.pattern?
184
+
185
+ line.owners.each { |owner| patterns_by_owner[owner] << line.pattern.gsub(/^\//, '') }
186
+ end
187
+ end
188
+
189
+ # Public: Returns the lines associated to a specific owner
190
+ #
191
+ # @param [String] owner The owner to search for patterns
192
+ # @return [Array] of <Codeowners::Checker::Group::Pattern> objects
193
+ def find_by_owner(owner)
194
+ main_group.find.select do |line|
195
+ next unless line.pattern?
196
+
197
+ line.owner == owner
198
+ end
199
+ end
200
+
201
+ # Public: Returns <true> if there is a defined owner for a given file
202
+ #
203
+ # @param [String] file The file to search if there is an owner assigned
204
+ # @return <true> if found; otherwise, <false>
205
+ def defined_owner?(file)
206
+ main_group.find do |line|
207
+ next unless line.pattern?
208
+
209
+ return true if line.match_file?(file)
210
+ end
211
+
212
+ false
213
+ end
214
+
215
+ # Public: Returns an [Array] of [Codeowners::Checker::Group] objects indicating
216
+ # the grouping of code owners
217
+ #
218
+ # @return [Array] of [Codeowners::Checker::Group] objects indicating the grouping
219
+ # of code owners
220
+ def main_group
221
+ @main_group ||= ::Codeowners::Checker::Group.parse(list)
222
+ end
223
+
224
+ # Public: Returns a [String] for the code owner file if it exists; otherwise, raise exception
225
+ #
226
+ # @return [String] the path to the codeowners file; otherwise, raise exception if not exists
227
+ def codeowner_file
228
+ return @codeowner_file if @codeowner_file
229
+
230
+ codeowner_file_paths.each do |path|
231
+ current_file_path = File.join(repo_path, path)
232
+ return current_file_path if File.exist?(current_file_path)
233
+ end
234
+
235
+ raise "Unable to locate a code owners file located [#{codeowner_file_paths.join(',')}]"
236
+ end
237
+
238
+ # Public: Returns <true> if the provided file is deemed whitelisted per the configuration;
239
+ # otherwise, <false>
240
+ #
241
+ # @return [true|false] if the provided file is deemed whitelisted per the configuration
242
+ def whitelisted?(file)
243
+ whitelist.whitelisted?(file)
244
+ end
245
+
246
+ private
247
+
248
+ # returns the git object representation of the repo provided
249
+ def git
250
+ @git ||= Git.open(@repo_path, log: Logger.new(IO::NULL))
251
+ end
252
+
253
+ # returns the configured whitelist of patterns to include
254
+ def whitelist
255
+ @whitelist ||= ::CodeownerValidator::Lists::Whitelist.new(repo_path: repo_path)
256
+ end
257
+
258
+ # locations to search for the codeowners file
259
+ def codeowner_file_paths
260
+ return @codeowner_file_paths if @codeowner_file_paths
261
+
262
+ @codeowner_file_paths = %w[CODEOWNERS docs/CODEOWNERS .github/CODEOWNERS]
263
+
264
+ # allow customization of the locations to search for the file
265
+ if ENV['CODEOWNER_FILE_PATHS']
266
+ ENV['CODEOWNER_FILE_PATHS']&.split(',')&.each(&:strip!)&.each do |str|
267
+ @codeowner_file_paths << str
268
+ end
269
+ end
270
+
271
+ @codeowner_file_paths
272
+ end
273
+
274
+ # constructs a new hash-to-array mapping
275
+ def hash_of_arrays
276
+ Hash.new { |h, k| h[k] = [] }
277
+ end
278
+
279
+ # returns an array of built lines
280
+ def list
281
+ return @list if @list
282
+
283
+ @list =
284
+ content.each_with_index.map do |line, index|
285
+ l = build_line(line)
286
+ l.line_number = index + 1
287
+ l
288
+ end
289
+
290
+ @list.compact!
291
+
292
+ @list
293
+ end
294
+
295
+ # builds a group line identifier
296
+ def build_line(line)
297
+ ::Codeowners::Checker::Group::Line.build(line)
298
+ end
299
+
300
+ # @return <Array> of lines chomped
301
+ def content
302
+ @content ||= ::File.readlines(codeowner_file).map(&:chomp)
303
+ rescue Errno::ENOENT
304
+ @content = []
305
+ end
306
+ end
307
+ end
308
+ # rubocop:enable Style/ImplicitRuntimeError
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'codeowner_validator/common/logging'
4
+ require 'open3'
5
+
6
+ module CodeownerValidator
7
+ # Public: Module utilized for housing the interactions with the terminal
8
+ module Command
9
+ include ::CodeownerValidator::Logging
10
+
11
+ # Executes system commands
12
+ def run(*args, log: true)
13
+ output = []
14
+ status = nil
15
+ log_command(*args) if log
16
+
17
+ Open3.popen2e(*args) do |_stdin, stdout_and_stderr, wait_thr|
18
+ until (line = stdout_and_stderr.gets).nil?
19
+ output.push line
20
+ log_info line.chop
21
+ end
22
+
23
+ status = wait_thr.value
24
+ end
25
+
26
+ return if status.success?
27
+
28
+ message =
29
+ [].tap do |ar|
30
+ ar << "Status: #{status.exitstatus}"
31
+ # because some commands contain sensitive information (ie passwords),
32
+ # may not want to display the actual command
33
+ ar << "Command: #{args.join(' ')}" if log
34
+ end
35
+
36
+ log_error message.join(', ')
37
+ raise message.join(', ')
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rainbow'
4
+ require 'logger'
5
+
6
+ module CodeownerValidator
7
+ # Public: Mixin for adding methods needed for logging directly to the stdout
8
+ module Logging
9
+ # Internal: Reader for the logger attribute.
10
+ #
11
+ # Returns a Logger instance.
12
+ def logger
13
+ return @logger if @logger
14
+
15
+ # depending of the context, utilize existing rails logger if exists
16
+ @logger = rails_logger || default_logger
17
+ end
18
+
19
+ # Public: Designation if to show verbose output
20
+ def log_verbose(*messages)
21
+ return unless verbose?
22
+
23
+ messages.flatten.each do |message|
24
+ logger.info Rainbow(message || yield).magenta.bright
25
+ end
26
+ end
27
+
28
+ # Public: Displays the command message to the console
29
+ def log_command(*messages)
30
+ messages.flatten.each do |message|
31
+ logger.info Rainbow(message).bright
32
+ end
33
+ end
34
+
35
+ # Public: Displays an informational message to the console
36
+ def log_info(*messages)
37
+ messages.flatten.each do |message|
38
+ logger.info Rainbow(message).blue.bright
39
+ end
40
+ end
41
+
42
+ # Public: Displays a warning message to the console
43
+ def log_warn(*messages)
44
+ messages.flatten.each do |message|
45
+ logger.warn Rainbow(message).yellow
46
+ end
47
+ end
48
+
49
+ # Public: Displays an error message to the console
50
+ def log_error(*messages)
51
+ messages.flatten.each do |message|
52
+ logger.error Rainbow(message).red.bold
53
+ end
54
+ end
55
+
56
+ # Public: Displays the stderr output to the console
57
+ def log_stderr(*args)
58
+ args.each do |message|
59
+ logger.error message
60
+ end
61
+ end
62
+
63
+ # Public: Displays the current program running name
64
+ def program_name
65
+ @program_name ||= File.basename($PROGRAM_NAME)
66
+ end
67
+
68
+ private
69
+
70
+ # Get the Rails logger if it's defined.
71
+ #
72
+ # @example Get Rails' logger.
73
+ # Loggable.rails_logger
74
+ #
75
+ # @return [ Logger ] The Rails logger.
76
+ def rails_logger
77
+ defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
78
+ end
79
+
80
+ def default_logger
81
+ logger = ::Logger.new(STDOUT)
82
+ logger.level = ::Logger::INFO
83
+ logger
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'codeowner_validator/common/logging'
4
+ require 'codeowner_validator/common/command'
5
+ require 'codeowner_validator/helpers/utility_helper'
6
+
7
+ module CodeownerValidator
8
+ module Tasks
9
+ # Public: The tasks base class is used for defining the basis for task executions from the
10
+ # importer or merger executors.
11
+ class Base
12
+ include ::CodeownerValidator::Logging
13
+ include ::CodeownerValidator::Command
14
+ include ::CodeownerValidator::UtilityHelper
15
+
16
+ # Public: Initializing the task with provided arguments
17
+ # @param [Hash] _args The hash of arguments to utilize for the initialization
18
+ # @options _args [Boolean] :verbose Indicate if verbose output should be included
19
+ # @options _args [String] :repo_path The absolute path to the repo for evaluation
20
+ def initialize(verbose: false, repo_path:, **_args)
21
+ @verbose = verbose
22
+ @repo_path = repo_path
23
+ end
24
+
25
+ # Public: Returns the summary of what the task is to accomplish. Expectation that each
26
+ # task will define its intent which overrides just the class name output.
27
+ def summary
28
+ self.class.name
29
+ end
30
+
31
+ # Public: Executes task's commands
32
+ def execute
33
+ comments&.each { |c| log_error c.comment }
34
+ end
35
+
36
+ # Public: Executes all tasks and responds with an array of comments
37
+ #
38
+ # @return [Array] Returns an array of comments from the execution of the tasks to be utilized by the consumer
39
+ def comments; end
40
+
41
+ # Public: Returns the codeowner object associated to the repository selected
42
+ #
43
+ # @return [CodeownerValidator::CodeOwners] object associate to the repository selected
44
+ def codeowners
45
+ @codeowners ||= CodeOwners.new(repo_path: @repo_path)
46
+ end
47
+
48
+ private
49
+
50
+ def in_repo_folder(&block)
51
+ in_folder(@repo_path, &block)
52
+ end
53
+
54
+ def verbose?
55
+ return CodeownerValidator.verbose if CodeownerValidator.respond_to?(:verbose)
56
+
57
+ env_verbose = %w[true yes].include? ENV['VERBOSE']&.downcase
58
+ @verbose || env_verbose
59
+ end
60
+ end
61
+ end
62
+ end