Skip to main content

phago_runtime/
project_context.rs

1//! Project context — file tree awareness for code indexing.
2//!
3//! Scans a project directory for source files and provides
4//! context about the project structure.
5
6use std::path::{Path, PathBuf};
7
8/// Information about a source file in the project.
9#[derive(Debug, Clone)]
10pub struct SourceFile {
11    pub path: PathBuf,
12    pub relative_path: String,
13    pub extension: String,
14    pub size_bytes: u64,
15}
16
17/// Scan a directory for Rust source files.
18pub fn scan_rust_files(root: &Path) -> Vec<SourceFile> {
19    let mut files = Vec::new();
20    scan_recursive(root, root, &mut files);
21    files.sort_by(|a, b| a.relative_path.cmp(&b.relative_path));
22    files
23}
24
25fn scan_recursive(root: &Path, dir: &Path, files: &mut Vec<SourceFile>) {
26    let entries = match std::fs::read_dir(dir) {
27        Ok(entries) => entries,
28        Err(_) => return,
29    };
30
31    for entry in entries.filter_map(|e| e.ok()) {
32        let path = entry.path();
33
34        // Skip hidden directories and target/
35        if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
36            if name.starts_with('.') || name == "target" || name == "node_modules" {
37                continue;
38            }
39        }
40
41        if path.is_dir() {
42            scan_recursive(root, &path, files);
43        } else if path.extension().map_or(false, |ext| ext == "rs") {
44            let relative = path.strip_prefix(root)
45                .unwrap_or(&path)
46                .to_string_lossy()
47                .to_string();
48            let size = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
49
50            files.push(SourceFile {
51                path: path.clone(),
52                relative_path: relative,
53                extension: "rs".to_string(),
54                size_bytes: size,
55            });
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn scan_finds_rust_files() {
66        let root = Path::new(env!("CARGO_MANIFEST_DIR"))
67            .parent()
68            .unwrap()
69            .parent()
70            .unwrap();
71        let files = scan_rust_files(root);
72        assert!(!files.is_empty(), "Should find .rs files in the project");
73        assert!(files.iter().any(|f| f.relative_path.contains("colony.rs")));
74    }
75}