aurora-autodebug 0.1.0

Log analysis, process inspection, and error diagnosis
Documentation
use std::fs;
use std::path::Path;
use std::io::{BufRead, BufReader};
use aurora_core::{AuroraResult, AuroraError, Pipeline, Value};
use regex::Regex;

pub fn analyze_target(target: &str) -> AuroraResult<Pipeline> {
    if target.chars().all(|c| c.is_ascii_digit()) {
        return analyze_process(target);
    }

    let path = Path::new(target);
    if path.exists() {
        if path.is_file() {
            let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
            match ext {
                "log" | "txt" => analyze_log_file(target),
                _ => {
                    let size = fs::metadata(target)
                        .map(|m| m.len())
                        .unwrap_or(0);
                    Ok(Pipeline::single(Value::String(
                        format!("File: {target} ({size} bytes). Use 'aurora autodebug log {target}' to analyze.")
                    )))
                }
            }
        } else {
            Err(AuroraError::AutoDebugError(format!("Not a file: {target}")))
        }
    } else {
        Err(AuroraError::NotFound(format!("Target not found: {target}")))
    }
}

fn analyze_process(pid: &str) -> AuroraResult<Pipeline> {
    let status_path = format!("/proc/{pid}/status");
    let status = match fs::read_to_string(&status_path) {
        Ok(s) => s,
        Err(_) => return Err(AuroraError::AutoDebugError(format!("Process {pid} not found"))),
    };

    let mut results = Vec::new();
    for line in status.lines() {
        if let Some((key, val)) = line.split_once(":\t") {
            let mut record = std::collections::BTreeMap::new();
            record.insert("key".to_string(), Value::String(key.trim().to_string()));
            record.insert("value".to_string(), Value::String(val.trim().to_string()));
            results.push(Value::Record(record));
        }
    }

    Ok(Pipeline::table(
        vec!["key".to_string(), "value".to_string()],
        results.into_iter().map(|r| {
            match r {
                Value::Record(map) => {
                    vec![
                        map.get("key").cloned().unwrap_or(Value::Null),
                        map.get("value").cloned().unwrap_or(Value::Null),
                    ]
                }
                _ => vec![],
            }
        }).collect(),
    ))
}

pub fn analyze_log_file(path: &str) -> AuroraResult<Pipeline> {
    let file = fs::File::open(path)
        .map_err(|e| AuroraError::AutoDebugError(format!("Cannot open log: {e}")))?;
    let reader = BufReader::new(file);

    let error_re = Regex::new(r"(?i)(ERROR|FATAL|PANIC|CRASH|SEGFAULT|SEGV|CORE DUMP|KILLED|OOM)").ok();
    let warn_re = Regex::new(r"(?i)(WARNING|WARN|DEPRECATED|SLOW)").ok();

    let mut results = Vec::new();
    for (i, line_result) in reader.lines().enumerate() {
        let line = line_result.map_err(|e| AuroraError::AutoDebugError(format!("Read error: {e}")))?;
        if let Some(ref re) = error_re {
            if let Some(m) = re.find(&line) {
                let mut record = std::collections::BTreeMap::new();
                record.insert("severity".to_string(), Value::String("ERROR".to_string()));
                record.insert("line".to_string(), Value::Int((i + 1) as i64));
                record.insert("match".to_string(), Value::String(m.as_str().to_string()));
                record.insert("content".to_string(), Value::String(line.clone()));
                results.push(Value::Record(record));
            }
        }
        if let Some(ref re) = warn_re {
            if let Some(m) = re.find(&line) {
                let mut record = std::collections::BTreeMap::new();
                record.insert("severity".to_string(), Value::String("WARNING".to_string()));
                record.insert("line".to_string(), Value::Int((i + 1) as i64));
                record.insert("match".to_string(), Value::String(m.as_str().to_string()));
                record.insert("content".to_string(), Value::String(line));
                results.push(Value::Record(record));
            }
        }
    }

    if results.is_empty() {
        return Ok(Pipeline::single(Value::String(
            "No errors or warnings found in log.".to_string()
        )));
    }

    Ok(Pipeline::table(
        vec!["severity".to_string(), "line".to_string(), "match".to_string(), "content".to_string()],
        results.into_iter().map(|r| {
            match r {
                Value::Record(map) => {
                    vec![
                        map.get("severity").cloned().unwrap_or(Value::Null),
                        map.get("line").cloned().unwrap_or(Value::Null),
                        map.get("match").cloned().unwrap_or(Value::Null),
                        map.get("content").cloned().unwrap_or(Value::Null),
                    ]
                }
                _ => vec![],
            }
        }).collect(),
    ))
}

pub fn find_log_files(path: &str) -> Vec<String> {
    let mut logs = Vec::new();
    if let Ok(entries) = fs::read_dir(path) {
        for entry in entries.flatten() {
            let p = entry.path();
            if let Some(ext) = p.extension() {
                if ext == "log" {
                    logs.push(p.to_string_lossy().to_string());
                }
            }
        }
    }
    logs
}