Skip to main content

cargo_mate/cmd/
utils.rs

1use anyhow::{Result, Context};
2use colored::*;
3use chrono;
4use std::fs;
5use std::path::PathBuf;
6use dirs;
7use crate::captain::license;
8use crate::captain::wtf;
9use crate::captain::version;
10use crate::display;
11use crate::captain::captain_log;
12pub fn run_cargo_with_wrapper(args: &[&str]) -> Result<()> {
13    if !args.is_empty() {
14        let license_manager = crate::captain::license::LicenseManager::new();
15        if let Err(e) = license_manager?.enforce_license(&format!("cargo-{}", args[0])) {
16            eprintln!("❌ License enforcement failed: {}", e);
17            crate::captain::wtf::display_api_failure_art();
18            std::process::exit(1);
19        }
20    }
21    if let Err(e) = version::pre_operation_hook(None) {
22        eprintln!("⚠️  Version auto-increment failed: {}", e);
23    }
24    display::run_cargo_with_display(args);
25    if let Ok(mut log) = captain_log::CaptainLog::new() {
26        let build_result = captain_log::BuildResult {
27            success: true,
28            error_count: 0,
29            warning_count: 0,
30            duration_seconds: 0.0,
31        };
32        if let Err(e) = log
33            .log_command(&format!("cargo {}", args.join(" ")), build_result)
34        {
35            eprintln!("⚠️  Captain's Log recording failed: {}", e);
36        }
37        println!("\n📝 {}", "Captain's Log: Session recorded".dimmed());
38    }
39    if let Err(e) = version::post_operation_hook(None, true) {
40        eprintln!("⚠️  Version post-operation hook failed: {}", e);
41    }
42    Ok(())
43}
44pub fn run_tracked_command(command: &str, session_id: &str) -> Result<()> {
45    use std::process::Command;
46    use std::io::{BufRead, BufReader};
47    let parts: Vec<&str> = command.split_whitespace().collect();
48    if parts.is_empty() {
49        return Err(anyhow::anyhow!("Empty command"));
50    }
51    let mut log = captain_log::CaptainLog::new()?;
52    let parser = captain_log::CargoOutputParser::new();
53    let mut cmd = Command::new(parts[0]);
54    cmd.args(&parts[1..]);
55    if parts[0] == "cargo" {
56        // Only add --message-format=json for commands that support it
57        let command_supports_json = parts.get(1).map_or(false, |cmd| {
58            matches!(*cmd, "build" | "check" | "test" | "doc" | "clippy" | "fmt")
59        });
60        if command_supports_json {
61            cmd.arg("--message-format=json");
62        }
63
64        // Handle cargo publish with automatic version checking
65        if parts.get(1) == Some(&"publish") {
66            // We can't do the version check here because this function doesn't have access to the right context
67            // The version check happens in run_cargo_with_display instead
68        }
69    }
70    let start_time = std::time::Instant::now();
71    let mut child = cmd
72        .stdout(std::process::Stdio::piped())
73        .stderr(std::process::Stdio::piped())
74        .spawn()?;
75    if let Some(stdout) = child.stdout.take() {
76        let reader = BufReader::new(stdout);
77        for line in reader.lines() {
78            let line = line?;
79            println!("{}", line);
80            if let Some(msg) = parser.parse_message(&line)? {
81                if let Some(diagnostic) = msg.message {
82                    let entry = parser
83                        .create_log_entry_from_diagnostic(&diagnostic, session_id);
84                    log.log(&entry.message, entry.tags)?;
85                }
86            }
87        }
88    }
89    if let Some(stderr) = child.stderr.take() {
90        let reader = BufReader::new(stderr);
91        for line in reader.lines() {
92            let line = line?;
93            eprintln!("{}", line);
94            if let Some(msg) = parser.parse_message(&line)? {
95                if let Some(diagnostic) = msg.message {
96                    let entry = parser
97                        .create_log_entry_from_diagnostic(&diagnostic, session_id);
98                    log.log(&entry.message, entry.tags)?;
99                }
100            }
101        }
102    }
103    let status = child.wait()?;
104    let duration = start_time.elapsed();
105    let build_result = captain_log::BuildResult {
106        success: status.success(),
107        error_count: 0,
108        warning_count: 0,
109        duration_seconds: duration.as_secs_f64(),
110    };
111    log.log_command(command, build_result)?;
112    println!("\n🔍 Analysis:");
113    let entries = log.get_recent(1000);
114    let entries_owned: Vec<captain_log::LogEntry> = entries
115        .into_iter()
116        .cloned()
117        .collect();
118    let detector = captain_log::PatternDetector::new(entries_owned);
119    let recurring = detector.find_recurring_errors();
120    if !recurring.is_empty() {
121        println!("\n⚠️  Recurring Issues:");
122        for (error_key, count, _) in recurring.into_iter().take(5) {
123            println!("   {} ({})", error_key.cyan(), count);
124        }
125    }
126    let regressions = detector.detect_build_time_regression();
127    if !regressions.is_empty() {
128        println!("\n📈 Build Time Regressions:");
129        for (command, old_time, new_time) in regressions {
130            println!(
131                "   {}: {:.2}s → {:.2}s ({:.1}%)", command.cyan(), old_time, new_time,
132                ((new_time - old_time) / old_time) * 100.0
133            );
134        }
135    }
136    Ok(())
137}
138pub fn get_recent_errors(count: usize) -> Result<Vec<String>> {
139    let home_dir = dirs::home_dir()
140        .ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
141    let shipwreck = home_dir.join(".shipwreck");
142    let error_file = shipwreck.join("errors").join("latest.txt");
143    if error_file.exists() {
144        let content = std::fs::read_to_string(&error_file)?;
145        let errors: Vec<String> = content
146            .lines()
147            .filter(|line| !line.trim().is_empty())
148            .take(count)
149            .map(|s| s.to_string())
150            .collect();
151        if errors.is_empty() {
152            Ok(vec!["No recent errors found in the error logs.".to_string()])
153        } else {
154            Ok(errors)
155        }
156    } else {
157        Ok(
158            vec![
159                "No error log file found. Try running some cargo commands first."
160                .to_string()
161            ],
162        )
163    }
164}
165pub fn show_loading_messages() {
166    let messages = [
167        "⚓ Hoisting the sails... preparing to set sail for knowledge!",
168        "🌊 Riding the waves... surfing through the digital ocean!",
169        "🧭 Checking the compass... navigating to the answer!",
170        "🚢 Batten down the hatches... stormy seas of computation ahead!",
171        "🦜 Arr, matey! Consulting the ancient tomes of wisdom!",
172        "⚡ Charging the canons... ready to fire the knowledge salvo!",
173        "🧜‍♀️ Singing sea shanties... luring the answers from the deep!",
174        "🗺️ Reading the treasure map... X marks the spot of knowledge!",
175        "🦈 Dodging digital sharks... swimming towards the answer!",
176        "🌟 Aligning the stars... consulting the celestial database!",
177    ];
178    let mut index = 0;
179    let start_time = std::time::Instant::now();
180    while start_time.elapsed().as_secs() < 30 {
181        println!("⏳ {}", messages[index]);
182        std::thread::sleep(std::time::Duration::from_secs(3));
183        index = (index + 1) % messages.len();
184    }
185}
186pub fn parse_bool(s: &str) -> Result<bool, std::num::ParseIntError> {
187    match s.to_lowercase().as_str() {
188        "true" | "1" | "yes" | "on" => Ok(true),
189        "false" | "0" | "no" | "off" => Ok(false),
190        _ => Ok(s.parse::<u8>()? != 0),
191    }
192}
193pub fn is_cm_command(cmd: &str) -> bool {
194    matches!(
195        cmd, "anchor" | "journey" | "log" | "tide" | "map" | "mutiny" | "config" |
196        "version" | "view" | "optimize" | "test" | "history" | "init" | "install" |
197        "activate" | "register" | "idea" | "wtf" | "checklist" | "add" | "done" | "clear"
198        | "show" | "list" | "user" | "debug" | "help" | "--help" | "-h" | "tool" |
199        "tools" | "strip" | "scat"
200    )
201}
202pub fn handle_license_check(command: &str) -> Result<()> {
203    let license_manager = crate::captain::license::LicenseManager::new();
204    license_manager?.enforce_license(command)
205}
206pub fn check_command_license(command: &str) -> Result<()> {
207    let license_manager = crate::captain::license::LicenseManager::new();
208    match license_manager?.enforce_license(command) {
209        Ok(_) => Ok(()),
210        Err(e) => Err(e),
211    }
212}