cleo_codeowners 0.3.0 → 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: 9ab9ff74ae21e83712d2331afa49e4a58fb361afb592489c608662d190adfaac
4
- data.tar.gz: af9b4ec93a30218aef50319329d3594ca97cff39c4b203282abf7ab763ed968b
3
+ metadata.gz: 95dabb73fd9d44328c2c7baed5ffbf4f91996ef2bad026d2f79175b98ba252f9
4
+ data.tar.gz: 4a65dca6d6efef99d01587612451b72293283a30aa0a99f3ada7f192b05added
5
5
  SHA512:
6
- metadata.gz: bcf8aebdba6312e71130c7ddcd9cf66f39c6e90c7b63d66fb57dd59d0410391938a676e895f9f82245543acfec10fa44acc4db30b49cdc794619c7bfa4cb0ee6
7
- data.tar.gz: 87892f76e750c5ebbbead2dabf13ac1727e6c6fc1104e5ea237b06f4c22e05fb0038f6bbfe66bb73b1ae1a22384c2a756e162927f6fd114f9d405cd678f13601
6
+ metadata.gz: 55b1e75f9bfbf6affb08442adb631c5fb96ab8a7ac3a1a31a433fc44235f5875964a98d4d77038965cd7c20db7cf022c5a8989db3c7a1acdfe80f32a864c792a
7
+ data.tar.gz: c69046a48d695beb1129a005fff5b87163b0cd4935fbb625017a123b1bc2f1ed291ee02d833cf6a2ee4ef0448d14a388a2f5324547cb7fb5fe79bfbe680b3f98
@@ -3,5 +3,5 @@
3
3
  ##
4
4
  # Root module for this gem
5
5
  module CleoCodeowners
6
- VERSION = '0.3.0'
6
+ VERSION = '0.4.0'
7
7
  end
@@ -1,6 +1,8 @@
1
1
  # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'pathname'
5
+
4
6
  module Codeowners
5
7
  class Generator
6
8
  DEFAULT_OUTPUT_PATH = '.github/CODEOWNERS'
@@ -105,32 +107,40 @@ module Codeowners
105
107
  calculated_owners.push({ path:, owner: })
106
108
  end
107
109
 
110
+ # GitHub applies the *last* matching CODEOWNERS rule, so rules are listed
111
+ # least-specific first: by number of path segments (`/app/` is broader than
112
+ # `/app/models/user.rb`), then by path so a wildcard such as `/foo/*` sorts
113
+ # ahead of the `/foo/bar/` it would otherwise shadow.
108
114
  def output_in_machine_format
109
- file_rules, folder_rules = calculated_owners.partition { |owner| owner[:path].include?('.') }
110
- group_by_path_segments_and_output(folder_rules)
111
- group_by_path_segments_and_output(file_rules)
115
+ merge_owners_by_path(calculated_owners)
116
+ .sort_by { |path, _owners| [path_depth(path), path] }
117
+ .each { |path, rule_owners| output_file << "#{path} #{rule_owners.join(' ')}\n" }
112
118
  end
113
119
 
114
- def group_by_path_segments_and_output(rules)
115
- grouped_by_path_segments_number = rules
116
- .group_by { |owner| sanitised_path(owner[:path]).split('/').reject(&:empty?).size }
117
- .each_value do |rule|
118
- rule.sort_by! do |owner|
119
- owner[:path]
120
- end
120
+ # Combines the owners of rules that share a path onto one line.
121
+ def merge_owners_by_path(rules)
122
+ rules_by_path = rules.group_by { |owner| sanitised_path(owner[:path]) }
123
+ reject_trailing_slash_conflicts(rules_by_path.keys)
124
+ rules_by_path.map do |path, owners_for_path|
125
+ [path, owners_for_path.map { |owner| owner[:owner] }.uniq.sort]
121
126
  end
122
- .sort
123
- .to_h
124
-
125
- grouped_by_path_segments_number
126
- .each_value
127
- .map { |files_and_owners| files_and_owners.group_by { |owner| owner[:path] } }
128
- .each do |files_and_owners|
129
- files_and_owners.each do |file, owners_list|
130
- rule_owners = owners_list.map { |owner| owner[:owner] }.uniq.sort
131
- output_file << "#{sanitised_path(file)} #{rule_owners.join(' ')}\n"
132
- end
133
- end
127
+ end
128
+
129
+ # A path written both with and without a trailing slash is rejected rather
130
+ # than guessed at: `/foo` and `/foo/` are effectively same rule, but on disk
131
+ # they differ (a directory needs its slash to match recursively, a file must
132
+ # not have one), so the definitions must pick one.
133
+ def reject_trailing_slash_conflicts(paths)
134
+ paths.group_by { |path| path.chomp('/') }.each_value do |variants|
135
+ next if variants.uniq.size == 1
136
+
137
+ raise "#{variants.min} is declared both with and without a trailing slash " \
138
+ "(#{variants.uniq.sort.join(', ')}); pick one so it is clear whether it is a file or a directory."
139
+ end
140
+ end
141
+
142
+ def path_depth(path)
143
+ Pathname.new(path).each_filename.count
134
144
  end
135
145
 
136
146
  def blurb
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cleo_codeowners
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - "@agentAngelope"