| 95 | |
| 96 | |
| 97 | class SourceFinder: |
| 98 | def __init__(self, fscache: FileSystemCache, options: Options) -> None: |
| 99 | self.fscache = fscache |
| 100 | self.explicit_package_bases = get_explicit_package_bases(options) |
| 101 | self.namespace_packages = options.namespace_packages |
| 102 | self.exclude = options.exclude |
| 103 | self.exclude_gitignore = options.exclude_gitignore |
| 104 | self.verbosity = options.verbosity |
| 105 | |
| 106 | def is_explicit_package_base(self, path: str) -> bool: |
| 107 | assert self.explicit_package_bases |
| 108 | return normalise_package_base(path) in self.explicit_package_bases |
| 109 | |
| 110 | def find_sources_in_dir(self, path: str) -> list[BuildSource]: |
| 111 | sources = [] |
| 112 | |
| 113 | seen: set[str] = set() |
| 114 | names = sorted(self.fscache.listdir(path), key=keyfunc) |
| 115 | for name in names: |
| 116 | # Skip certain names altogether |
| 117 | if name in ("__pycache__", "site-packages", "node_modules") or name.startswith("."): |
| 118 | continue |
| 119 | subpath = os.path.join(path, name) |
| 120 | |
| 121 | if matches_exclude(subpath, self.exclude, self.fscache, self.verbosity >= 2): |
| 122 | continue |
| 123 | if self.exclude_gitignore and matches_gitignore( |
| 124 | subpath, self.fscache, self.verbosity >= 2 |
| 125 | ): |
| 126 | continue |
| 127 | |
| 128 | if self.fscache.isdir(subpath): |
| 129 | sub_sources = self.find_sources_in_dir(subpath) |
| 130 | if sub_sources: |
| 131 | seen.add(name) |
| 132 | sources.extend(sub_sources) |
| 133 | else: |
| 134 | stem, suffix = os.path.splitext(name) |
| 135 | if stem not in seen and suffix in PY_EXTENSIONS: |
| 136 | seen.add(stem) |
| 137 | module, base_dir = self.crawl_up(subpath) |
| 138 | sources.append(BuildSource(subpath, module, None, base_dir)) |
| 139 | |
| 140 | return sources |
| 141 | |
| 142 | def crawl_up(self, path: str) -> tuple[str, str]: |
| 143 | """Given a .py[i] filename, return module and base directory. |
| 144 | |
| 145 | For example, given "xxx/yyy/foo/bar.py", we might return something like: |
| 146 | ("foo.bar", "xxx/yyy") |
| 147 | |
| 148 | If namespace packages is off, we crawl upwards until we find a directory without |
| 149 | an __init__.py |
| 150 | |
| 151 | If namespace packages is on, we crawl upwards until the nearest explicit base directory. |
| 152 | Failing that, we return one past the highest directory containing an __init__.py |
| 153 | |
| 154 | We won't crawl past directories with invalid package names. |
no outgoing calls
searching dependent graphs…