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
}