Skip to main content

chub_cli/commands/
detect.rs

1use clap::Args;
2use owo_colors::OwoColorize;
3
4use chub_core::registry::MergedRegistry;
5use chub_core::team::detect;
6
7use crate::output;
8
9#[derive(Args)]
10pub struct DetectArgs {
11    /// Auto-pin all detected docs
12    #[arg(long)]
13    pin: bool,
14}
15
16pub fn run(args: DetectArgs, json: bool, merged: &MergedRegistry) {
17    let cwd = std::env::current_dir().unwrap_or_default();
18    let deps = detect::detect_dependencies(&cwd);
19
20    if deps.is_empty() {
21        if json {
22            println!(
23                "{}",
24                serde_json::json!({ "dependencies": [], "matches": [] })
25            );
26        } else {
27            eprintln!(
28                "{}",
29                "No dependency files found (package.json, Cargo.toml, requirements.txt, etc.)."
30                    .yellow()
31            );
32        }
33        return;
34    }
35
36    // Build doc ID list from registry
37    let doc_ids: Vec<(String, String)> = merged
38        .docs
39        .iter()
40        .map(|e| (e.id().to_string(), e.name().to_string()))
41        .collect();
42
43    let matches = detect::match_deps_to_docs(&deps, &doc_ids);
44
45    if json {
46        println!(
47            "{}",
48            serde_json::to_string_pretty(&serde_json::json!({
49                "dependencies": deps.len(),
50                "matches": matches.iter().map(|m| serde_json::json!({
51                    "dependency": m.dep.name,
52                    "language": m.dep.language,
53                    "version": m.dep.version,
54                    "doc_id": m.doc_id,
55                    "doc_name": m.doc_name,
56                    "confidence": m.confidence,
57                })).collect::<Vec<_>>(),
58                "unmatched": deps.iter()
59                    .filter(|d| !matches.iter().any(|m| m.dep.name == d.name))
60                    .map(|d| &d.name)
61                    .collect::<Vec<_>>(),
62            }))
63            .unwrap_or_default()
64        );
65    } else {
66        eprintln!(
67            "Detected {} dependencies with {} available docs:\n",
68            deps.len(),
69            matches.len()
70        );
71
72        for m in &matches {
73            let ver = m
74                .dep
75                .version
76                .as_deref()
77                .map(|v| format!(" ({})", v))
78                .unwrap_or_default();
79            let conf = if m.confidence >= 0.8 {
80                "[pinnable]".green().to_string()
81            } else {
82                "[partial match]".yellow().to_string()
83            };
84            eprintln!(
85                "  {} {}{}  →  {} {}",
86                m.dep.name.bold(),
87                m.dep.language.dimmed(),
88                ver.dimmed(),
89                m.doc_id.cyan(),
90                conf,
91            );
92        }
93
94        // Show unmatched
95        let unmatched: Vec<&str> = deps
96            .iter()
97            .filter(|d| !matches.iter().any(|m| m.dep.name == d.name))
98            .map(|d| d.name.as_str())
99            .collect();
100        if !unmatched.is_empty() {
101            eprintln!();
102            for name in &unmatched {
103                eprintln!("  {} {}", "✗".red(), name.dimmed());
104            }
105        }
106
107        if !matches.is_empty() {
108            eprintln!("\nPin all? {}", "chub detect --pin".bold());
109        }
110    }
111
112    // Auto-pin if requested
113    if args.pin {
114        let mut pinned = 0;
115        for m in &matches {
116            if m.confidence >= 0.5 {
117                let lang = if m.dep.language == "javascript" || m.dep.language == "python" {
118                    Some(m.dep.language.clone())
119                } else {
120                    None
121                };
122                if chub_core::team::pins::add_pin(&m.doc_id, lang, None, None, None).is_ok() {
123                    pinned += 1;
124                }
125            }
126        }
127        if !json {
128            output::success(&format!("Pinned {} docs.", pinned));
129        }
130    }
131}