Class: Rigor::ModuleGraph::ZeitwerkResolver
- Inherits:
-
Object
- Object
- Rigor::ModuleGraph::ZeitwerkResolver
- Defined in:
- lib/rigor/module_graph/zeitwerk_resolver.rb
Overview
Converts a Ruby source path into the fully-qualified constant the Zeitwerk convention says it should define.
Pure function, no I/O. The plugin instantiates one per run from ‘.rigor.yml` config and asks for `resolve(path)` per file. Two configuration knobs:
-
‘autoload_paths`: roots stripped from the path before camelising. Defaults to the standard Rails layout.
-
‘concern_dirs`: directories that act as transparent namespaces under Zeitwerk (`app/models/concerns/auditable.rb` resolves to `Auditable`, not `Concerns::Auditable`).
The resolver is order-sensitive: longer / more specific roots MUST be tried before their parents so ‘app/models/concerns/foo.rb` picks up the concern root, not `app/models`. We sort by length descending at construction time, so config order does not matter.
Constant Summary collapse
- DEFAULT_AUTOLOAD_PATHS =
%w[ app/models app/controllers app/services app/jobs app/mailers app/helpers app/channels app/workers lib ].freeze
- DEFAULT_CONCERN_DIRS =
%w[ app/models/concerns app/controllers/concerns ].freeze
Instance Attribute Summary collapse
-
#autoload_paths ⇒ Object
readonly
Returns the value of attribute autoload_paths.
-
#concern_dirs ⇒ Object
readonly
Returns the value of attribute concern_dirs.
Instance Method Summary collapse
- #camelise_path(rel_no_ext) ⇒ Object
- #camelise_segment(segment) ⇒ Object
-
#initialize(autoload_paths: DEFAULT_AUTOLOAD_PATHS, concern_dirs: DEFAULT_CONCERN_DIRS, project_root: nil) ⇒ ZeitwerkResolver
constructor
A new instance of ZeitwerkResolver.
-
#matches?(actual, inferred) ⇒ Boolean
True when ‘inferred` matches the (probably syntax-derived) `actual` constant under Zeitwerk’s conventions.
- #normalise_roots(roots) ⇒ Object
- #relativise(path) ⇒ Object
-
#resolve(path) ⇒ String?
The inferred constant name, or nil when the path is not under any configured root or has no .rb extension.
- #strip_leading(name) ⇒ Object
Constructor Details
#initialize(autoload_paths: DEFAULT_AUTOLOAD_PATHS, concern_dirs: DEFAULT_CONCERN_DIRS, project_root: nil) ⇒ ZeitwerkResolver
Returns a new instance of ZeitwerkResolver.
42 43 44 45 46 47 48 49 |
# File 'lib/rigor/module_graph/zeitwerk_resolver.rb', line 42 def initialize(autoload_paths: DEFAULT_AUTOLOAD_PATHS, concern_dirs: DEFAULT_CONCERN_DIRS, project_root: nil) @project_root = project_root && File.(project_root) @autoload_paths = normalise_roots(autoload_paths) @concern_dirs = normalise_roots(concern_dirs) @sorted_roots = (@concern_dirs + @autoload_paths).sort_by { |r| -r.length }.uniq end |
Instance Attribute Details
#autoload_paths ⇒ Object (readonly)
Returns the value of attribute autoload_paths.
40 41 42 |
# File 'lib/rigor/module_graph/zeitwerk_resolver.rb', line 40 def autoload_paths @autoload_paths end |
#concern_dirs ⇒ Object (readonly)
Returns the value of attribute concern_dirs.
40 41 42 |
# File 'lib/rigor/module_graph/zeitwerk_resolver.rb', line 40 def concern_dirs @concern_dirs end |
Instance Method Details
#camelise_path(rel_no_ext) ⇒ Object
107 108 109 |
# File 'lib/rigor/module_graph/zeitwerk_resolver.rb', line 107 def camelise_path(rel_no_ext) rel_no_ext.split("/").map { |seg| camelise_segment(seg) }.join("::") end |
#camelise_segment(segment) ⇒ Object
111 112 113 |
# File 'lib/rigor/module_graph/zeitwerk_resolver.rb', line 111 def camelise_segment(segment) segment.split("_").map(&:capitalize).join end |
#matches?(actual, inferred) ⇒ Boolean
True when ‘inferred` matches the (probably syntax-derived) `actual` constant under Zeitwerk’s conventions. We compare ignoring leading “::” since absolute / relative are not a meaningful distinction here.
75 76 77 78 79 |
# File 'lib/rigor/module_graph/zeitwerk_resolver.rb', line 75 def matches?(actual, inferred) return false if actual.nil? || inferred.nil? strip_leading(actual) == strip_leading(inferred) end |
#normalise_roots(roots) ⇒ Object
99 100 101 |
# File 'lib/rigor/module_graph/zeitwerk_resolver.rb', line 99 def normalise_roots(roots) Array(roots).map { |r| r.to_s.sub(%r{/+\z}, "") }.reject(&:empty?).freeze end |
#relativise(path) ⇒ Object
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/rigor/module_graph/zeitwerk_resolver.rb', line 81 def relativise(path) absolute = File.(path) if @project_root && absolute.start_with?(@project_root + "/") absolute[(@project_root.length + 1)..] elsif path.start_with?("/") # Absolute path with no project root configured: try every # autoload root as a suffix match. Used by integration runs # where files live in a tmpdir. suffix = @sorted_roots.find { |r| absolute.include?("/" + r + "/") } if suffix idx = absolute.rindex("/" + suffix + "/") absolute[(idx + 1)..] end else path end end |
#resolve(path) ⇒ String?
Returns the inferred constant name, or nil when the path is not under any configured root or has no .rb extension.
57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/rigor/module_graph/zeitwerk_resolver.rb', line 57 def resolve(path) return nil unless path rel = relativise(path) return nil unless rel return nil unless rel.end_with?(".rb") root = @sorted_roots.find { |r| rel.start_with?(r + "/") } return nil unless root suffix = rel[(root.length + 1)..] camelise_path(suffix.delete_suffix(".rb")) end |
#strip_leading(name) ⇒ Object
103 104 105 |
# File 'lib/rigor/module_graph/zeitwerk_resolver.rb', line 103 def strip_leading(name) name.sub(/\A::/, "") end |