use miette::{Result, IntoDiagnostic, Context}; use std::fs; use regex::Regex; use tracing::{info, debug}; use crate::sal::traits::{PlatformSal, EnvironmentCtx}; use crate::sal::dell_xps_9380::DellXps9380Sal; use crate::sal::generic_linux::GenericLinuxSal; use crate::sal::heuristic::schema::HardwareDb; use crate::sal::heuristic::discovery::{discover_facts, SystemFactSheet}; pub struct HeuristicEngine; impl HeuristicEngine { /// Loads the hardware database, probes the system, and builds the appropriate SAL. pub fn detect_and_build(ctx: EnvironmentCtx) -> Result<(Box, SystemFactSheet)> { // 1. Load Hardware DB let db_path = "assets/hardware_db.toml"; let db_content = fs::read_to_string(db_path) .into_diagnostic() .with_context(|| format!("Failed to read hardware database at {}", db_path))?; let db: HardwareDb = toml::from_str(&db_content) .into_diagnostic() .context("Failed to parse hardware_db.toml")?; // 2. Discover Facts let facts = discover_facts(&ctx.sysfs_base, ctx.runner.as_ref(), &db.discovery, &db.conflicts, db.benchmarking.clone()); info!("System Identity: {} {}", facts.vendor, facts.model); // 3. Routing Logic // --- Special Case: Dell XPS 13 9380 --- if is_match(&facts.vendor, "(?i)Dell.*") && is_match(&facts.model, "(?i)XPS.*13.*9380.*") { info!("Specialized SAL Match Found: Dell XPS 13 9380"); let sal = DellXps9380Sal::init(ctx, facts.clone()).map_err(|e| miette::miette!(e))?; return Ok((Box::new(sal), facts)); } // --- Fallback: Generic Linux SAL --- debug!("No specialized SAL match. Falling back to GenericLinuxSal with DB quirks."); // Validation: Ensure we found at least a temperature sensor if required if facts.temp_path.is_none() { return Err(miette::miette!("No temperature sensor discovered. Generic fallback impossible.")); } if facts.rapl_paths.is_empty() { return Err(miette::miette!("No RAPL power interface discovered. Generic fallback impossible.")); } Ok((Box::new(GenericLinuxSal::new(ctx, facts.clone(), db)), facts)) } } fn is_match(input: &str, pattern: &str) -> bool { if let Ok(re) = Regex::new(pattern) { re.is_match(input) } else { false } }