codeowner_validator 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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