| 106 | } |
| 107 | |
| 108 | fn walk(dir: &Path, out_dir: &Path, found: &mut Vec<PathBuf>) { |
| 109 | let Ok(entries) = fs::read_dir(dir) else { return }; |
| 110 | for entry in entries.flatten() { |
| 111 | let path = entry.path(); |
| 112 | if path == out_dir { continue; } |
| 113 | let name = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); |
| 114 | if name.starts_with('.') || name == "node_modules" || name == "target" { |
| 115 | continue; |
| 116 | } |
| 117 | if path.is_dir() { |
| 118 | walk(&path, out_dir, found); |
| 119 | } else if path.extension().and_then(|e| e.to_str()) == Some("py") { |
| 120 | found.push(path); |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | /// Cheap import scanner: regex-free, picks up `import X` and `from X import …` at the top level of a line. |
| 126 | fn crawl_imports(scripts: &[PathBuf]) -> BTreeSet<String> { |