Class: Rigor::ModuleGraph::PackwerkOverlay
- Inherits:
-
Object
- Object
- Rigor::ModuleGraph::PackwerkOverlay
- Defined in:
- lib/rigor/module_graph/packwerk_overlay.rb
Overview
Discovers Packwerk-style packages (‘package.yml`) inside a project tree and maps source file paths to their owning package.
Treats every directory that contains a package.yml as a package root. The package’s name is its path relative to the project root with a leading ./ stripped — that’s how packwerk itself reports them, and it’s stable across Packwerk versions which gives the renderer something to use as the cluster label.
Files map to the deepest ancestor package — if a nested ‘packages/billing/invoices/package.yml` lives under `packages/billing/package.yml`, a file under the inner one belongs to packages/billing/invoices, not packages/billing.
Defined Under Namespace
Classes: Package
Instance Attribute Summary collapse
-
#packages ⇒ Object
readonly
Returns the value of attribute packages.
-
#project_root ⇒ Object
readonly
Returns the value of attribute project_root.
Class Method Summary collapse
Instance Method Summary collapse
-
#any? ⇒ Boolean
True when at least one package.yml was found.
-
#groups_for(edges) ⇒ Object
Build a {node_name => package_name} mapping from a list of edges.
-
#initialize(project_root:, packages:) ⇒ PackwerkOverlay
constructor
A new instance of PackwerkOverlay.
-
#package_for(path) ⇒ Object
Find the deepest package whose root is an ancestor of
path. - #realpath_of(path) ⇒ Object
- #realpath_or_expand(path) ⇒ Object
Constructor Details
#initialize(project_root:, packages:) ⇒ PackwerkOverlay
Returns a new instance of PackwerkOverlay.
38 39 40 41 42 43 44 |
# File 'lib/rigor/module_graph/packwerk_overlay.rb', line 38 def initialize(project_root:, packages:) @project_root = (project_root) @packages = packages .map { |pkg| Package.new(name: pkg.name, root: (pkg.root)) } .sort_by { |p| -p.root.length } .freeze end |
Instance Attribute Details
#packages ⇒ Object (readonly)
Returns the value of attribute packages.
33 34 35 |
# File 'lib/rigor/module_graph/packwerk_overlay.rb', line 33 def packages @packages end |
#project_root ⇒ Object (readonly)
Returns the value of attribute project_root.
33 34 35 |
# File 'lib/rigor/module_graph/packwerk_overlay.rb', line 33 def project_root @project_root end |
Class Method Details
.discover(project_root) ⇒ PackwerkOverlay
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/rigor/module_graph/packwerk_overlay.rb', line 48 def self.discover(project_root) root = File.(project_root) packages = [] Find.find(root) do |path| base = File.basename(path) if File.directory?(path) && EXCLUDED_DIRS.include?(base) && path != root Find.prune next end next unless File.file?(path) && base == "package.yml" pkg_root = File.dirname(path) packages << Package.new(name: package_name(pkg_root, root), root: pkg_root) end new(project_root: root, packages: packages) end |
.package_name(pkg_root, project_root) ⇒ Object
65 66 67 68 69 70 71 72 73 |
# File 'lib/rigor/module_graph/packwerk_overlay.rb', line 65 def self.package_name(pkg_root, project_root) # The root package (a `package.yml` at the project root) is # canonically called `.` in Packwerk output. Match that so # users see the familiar label. return "." if pkg_root == project_root rel = pkg_root.sub(%r{\A#{Regexp.escape(project_root)}/?}, "") rel.empty? ? "." : rel end |
Instance Method Details
#any? ⇒ Boolean
Returns true when at least one package.yml was found.
77 78 79 |
# File 'lib/rigor/module_graph/packwerk_overlay.rb', line 77 def any? !@packages.empty? end |
#groups_for(edges) ⇒ Object
Build a {node_name => package_name} mapping from a list of edges. We only attribute a node to a package when we have evidence the node is declared under that package’s root — that is, the node appears as edge.from for at least one edge, and that edge’s path lives under the package. The to side is just a reference; using it would mis-attribute base classes (ApplicationRecord) and any other external constant to whichever package happens to reference them first.
131 132 133 134 135 136 137 138 139 140 |
# File 'lib/rigor/module_graph/packwerk_overlay.rb', line 131 def groups_for(edges) node_paths = {} edges.each do |edge| node_paths[edge.from] ||= edge.path end node_paths.each_with_object({}) do |(name, path), acc| pkg = package_for(path) acc[name] = pkg.name if pkg end end |
#package_for(path) ⇒ Object
Find the deepest package whose root is an ancestor of path. Returns nil when the path is outside every package’s root.
Both sides are normalised through realpath when possible so a macOS /tmp ↔ /private/tmp symlink (or any other symlink in the project root path) doesn’t make the comparison spuriously miss.
89 90 91 92 93 94 95 96 |
# File 'lib/rigor/module_graph/packwerk_overlay.rb', line 89 def package_for(path) return nil if path.nil? || path.empty? absolute = realpath_of(File.(path, @project_root)) @packages.find do |pkg| absolute == pkg.root || absolute.start_with?(pkg.root + "/") end end |
#realpath_of(path) ⇒ Object
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/rigor/module_graph/packwerk_overlay.rb', line 104 def realpath_of(path) File.realpath(path) rescue Errno::ENOENT # Tests and synthetic edges may carry paths whose tail # doesn't exist on disk; walk up to the deepest existing # ancestor, realpath that, then reattach the missing tail. # That makes a macOS +/tmp+ ↔ +/private/tmp+ symlink # transparent even for synthetic paths. parent = path until parent == File.dirname(parent) parent = File.dirname(parent) if File.exist?(parent) return File.realpath(parent) + path[parent.length..] end end path end |
#realpath_or_expand(path) ⇒ Object
98 99 100 101 102 |
# File 'lib/rigor/module_graph/packwerk_overlay.rb', line 98 def (path) File.realpath(File.(path)) rescue Errno::ENOENT File.(path) end |