Skip to main content

redskull_lib/
runtime_deps.rs

1//! Detect non-Rust runtime dependencies from source tree file patterns.
2//! Scans file paths for indicators of R, Python, or other scripting languages
3//! that would need corresponding conda run dependencies.
4
5/// A detected runtime dependency hint.
6pub struct RuntimeHint {
7    /// Suggested conda package (e.g., "r-base", "python").
8    pub package: &'static str,
9    /// Human-readable reason for the suggestion.
10    pub reason: String,
11}
12
13/// Scan a list of file paths for R/Python runtime dependency indicators.
14/// Returns hints about potential run deps the user should consider adding.
15pub fn detect_runtime_hints(file_paths: &[String]) -> Vec<RuntimeHint> {
16    let mut hints = Vec::new();
17
18    let r_scripts: Vec<&str> = file_paths
19        .iter()
20        .filter(|p| {
21            let lower = p.to_lowercase();
22            lower.ends_with(".r") || lower.ends_with(".rmd") || lower.ends_with(".rscript")
23        })
24        .map(|p| p.as_str())
25        .collect();
26
27    let py_scripts: Vec<&str> = file_paths
28        .iter()
29        .filter(|p| {
30            let lower = p.to_lowercase();
31            lower.ends_with(".py") && !lower.contains("setup.py") && !lower.contains("conf.py")
32        })
33        .map(|p| p.as_str())
34        .collect();
35
36    let has_r_description =
37        file_paths.iter().any(|p| p == "DESCRIPTION" || p.ends_with("/DESCRIPTION"));
38    let has_renv = file_paths.iter().any(|p| p.ends_with("renv.lock"));
39    let has_requirements_txt = file_paths.iter().any(|p| {
40        let name = p.rsplit('/').next().unwrap_or(p);
41        name == "requirements.txt"
42    });
43
44    if !r_scripts.is_empty() || has_r_description || has_renv {
45        let count = r_scripts.len();
46        let examples: Vec<&str> = r_scripts.iter().take(3).copied().collect();
47        let detail = if count > 0 {
48            format!("found {count} R script(s): {}", examples.join(", "))
49        } else if has_r_description {
50            "found DESCRIPTION file (R package)".to_string()
51        } else {
52            "found renv.lock file".to_string()
53        };
54        hints.push(RuntimeHint { package: "r-base", reason: detail });
55    }
56
57    if !py_scripts.is_empty() || has_requirements_txt {
58        let count = py_scripts.len();
59        let examples: Vec<&str> = py_scripts.iter().take(3).copied().collect();
60        let detail = if count > 0 {
61            format!("found {count} Python script(s): {}", examples.join(", "))
62        } else {
63            "found requirements.txt file".to_string()
64        };
65        hints.push(RuntimeHint { package: "python", reason: detail });
66    }
67
68    hints
69}