@@ -24,3 +24,55 @@ def current_repo() -> str:
2424 return repo .parent .name
2525 else :
2626 return Path .cwd ().name
27+
28+
29+ class PathFinder :
30+ """A class to search across multiple paths
31+
32+ Given either an environment variable name, or a list of paths, provide access
33+ methods to search for files across all of them."""
34+
35+ __slots__ = ("paths" ,)
36+
37+ paths : list [Path ]
38+
39+ def __init__ (self , paths : str | list [str ] | list [Path ]):
40+ if isinstance (paths , str ):
41+ import os
42+
43+ paths = os .environ .get (paths .replace ("$" , "" ), "." ).split (":" )
44+
45+ self .paths = [Path (p ) for p in paths ]
46+
47+ def __repr__ (self ) -> str :
48+ return f"PathFinder({ self .paths } )"
49+
50+ def __rich_repr__ (self ):
51+ for path in self .paths :
52+ yield str (path )
53+
54+ def find (self , name : str ) -> Path :
55+ """Find a file within the paths, or raise an error if it's not found"""
56+
57+ if path := self .get (name ):
58+ return path
59+
60+ raise FileNotFoundError (
61+ f"Could not find '{ name } '. Searched within the following paths:\n "
62+ + "\n " .join ([" - " + str (p ) for p in self .paths ])
63+ )
64+
65+ def get (self , name : str ) -> Path | None :
66+ """Find a file within the paths, or return None if it's not found"""
67+
68+ for path in self .paths :
69+ path = path .joinpath (name )
70+ if path .exists ():
71+ return path
72+
73+ return None
74+
75+ def __truediv__ (self , other : str ) -> Path :
76+ """Find files as if the finder was a Path: finder / 'file.txt'"""
77+
78+ return self .find (other )
0 commit comments