Module: Mutineer::Pairing
- Defined in:
- lib/mutineer/pairing.rb
Overview
Source -> test pairing by path convention (#11). Pure stdlib path logic: no Rails, no class loading, no process. Two jobs:
* expand_sources — a directory argument becomes its sorted **/*.rb files.
* infer_test — a source's test file by convention (app/ and lib/ sources
map to test/.../_test.rb or spec/.../_spec.rb), preserving
namespaced subdirectories. First EXISTING candidate wins.
Independently unit-testable: every method is pure in/out over the filesystem, so the pairing contract is exercised with plain fixtures, no Rails, no fork.
Class Method Summary collapse
-
.candidates(base, lib, prefer) ⇒ Object
Ordered candidate test paths.
-
.expand_sources(args, project_root:) ⇒ Object
Expand each positional source: a directory -> its sorted **/*.rb files (relative to project_root); a file (or glob, or anything non-directory) -> itself.
-
.infer_test(source_rel, project_root:, prefer: "minitest") ⇒ Object
The first EXISTING candidate test path for a source (relative to project_root), or nil.
-
.logical_path(source_rel) ⇒ Object
Strip the source root to a logical path (no ".rb") and flag lib/ sources.
Class Method Details
.candidates(base, lib, prefer) ⇒ Object
Ordered candidate test paths. lib/ sources also get test/lib/... and spec/lib/... (Rails apps put lib tests under either layout).
59 60 61 62 63 64 65 |
# File 'lib/mutineer/pairing.rb', line 59 def candidates(base, lib, prefer) minitest = ["test/#{base}_test.rb"] minitest << "test/lib/#{base}_test.rb" if lib rspec = ["spec/#{base}_spec.rb"] rspec << "spec/lib/#{base}_spec.rb" if lib prefer == "rspec" ? rspec + minitest : minitest + rspec end |
.expand_sources(args, project_root:) ⇒ Object
Expand each positional source: a directory -> its sorted **/*.rb files (relative to project_root); a file (or glob, or anything non-directory) -> itself. Flattened, deduped, order-stable.
19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/mutineer/pairing.rb', line 19 def (args, project_root:) root = File.(project_root) Array(args).flat_map do |arg| abs = File.(arg, root) if File.directory?(abs) Dir.glob(File.join(abs, "**", "*.rb")).sort.map { |f| f.delete_prefix("#{root}/") } else [arg] end end.uniq end |
.infer_test(source_rel, project_root:, prefer: "minitest") ⇒ Object
The first EXISTING candidate test path for a source (relative to
project_root), or nil. prefer is the resolved framework ("minitest" |
"rspec"): its candidates are tried first, the other framework's as fallback,
so a minitest default still finds a spec and vice-versa.
35 36 37 38 39 40 |
# File 'lib/mutineer/pairing.rb', line 35 def infer_test(source_rel, project_root:, prefer: "minitest") base, lib = logical_path(source_rel) candidates(base, lib, prefer).find do |rel| File.exist?(File.(rel, project_root)) end end |
.logical_path(source_rel) ⇒ Object
Strip the source root to a logical path (no ".rb") and flag lib/ sources. app/foo/bar.rb and lib/foo/bar.rb both -> "foo/bar"; anything else -> the path minus ".rb" (still attempted). Namespaced subdirs are preserved verbatim — structural, never constant resolution.
46 47 48 49 50 51 52 53 54 55 |
# File 'lib/mutineer/pairing.rb', line 46 def logical_path(source_rel) no_ext = source_rel.sub(/\.rb\z/, "") if no_ext.start_with?("app/") [no_ext.sub(%r{\Aapp/}, ""), false] elsif no_ext.start_with?("lib/") [no_ext.sub(%r{\Alib/}, ""), true] else [no_ext, false] end end |