fusible 0.9.0

Electronic linter: a fuse that blows to block firmware code generation
use std::path::Path;

use serde::Serialize;

use crate::commands::check::{load_circuit_source, resolve_mcu};
use crate::config::FusibleConfig;
use crate::linter::{LintContext, Linter};
use crate::netlist::Netlist;
use crate::parser;
use crate::svg::{render_pcb_svg, render_schematic_svg};

#[derive(Debug, Clone, Serialize)]
pub struct DevDiagnostic {
    pub rule: String,
    pub message: String,
    pub help: Option<String>,
}

#[derive(Debug, Clone, Serialize)]
pub struct DevPayload {
    pub board_name: String,
    pub fuse_blown: bool,
    pub diagnostics: Vec<DevDiagnostic>,
    pub pcb_svg: String,
    pub schematic_svg: String,
    pub circuit_json: Option<serde_json::Value>,
}

pub fn execute_pipeline(entry: &Path) -> DevPayload {
    let board_name = FusibleConfig::find_and_load(Path::new("."))
        .ok()
        .flatten()
        .and_then(|(_, cfg)| cfg.name)
        .unwrap_or_else(|| {
            entry
                .file_stem()
                .map(|s| s.to_string_lossy().to_string())
                .unwrap_or_else(|| "Board".to_string())
        });

    let (json_path, source) = match load_circuit_source(entry) {
        Ok(res) => res,
        Err(err) => {
            return DevPayload {
                board_name,
                fuse_blown: true,
                diagnostics: vec![DevDiagnostic {
                    rule: "source_error".to_string(),
                    message: err.to_string(),
                    help: Some(
                        "Ensure your circuit file compiles and is valid JSON/TSX".to_string(),
                    ),
                }],
                pcb_svg: String::new(),
                schematic_svg: String::new(),
                circuit_json: None,
            };
        }
    };

    let circuit_json = serde_json::from_str::<serde_json::Value>(&source).ok();
    let pcb_svg = render_pcb_svg(&source);

    let circuit = match parser::parse(&source, &json_path) {
        Ok(c) => c,
        Err(err) => {
            return DevPayload {
                board_name,
                fuse_blown: true,
                diagnostics: vec![DevDiagnostic {
                    rule: "parse_error".to_string(),
                    message: err.to_string(),
                    help: Some("Check circuit JSON syntax".to_string()),
                }],
                pcb_svg,
                schematic_svg: String::new(),
                circuit_json,
            };
        }
    };

    let netlist = Netlist::from_circuit(&circuit);
    let schematic_svg = render_schematic_svg(&circuit, &netlist);

    let target_mcu = resolve_mcu(None);
    let ctx = LintContext::new(&circuit, &netlist).with_mcu(target_mcu.as_deref());
    let violations = Linter::default().run_with_context(&ctx);

    let fuse_blown = !violations.is_empty();
    let diagnostics = violations
        .into_iter()
        .map(|v| DevDiagnostic {
            rule: v.rule,
            message: v.message,
            help: v.help,
        })
        .collect();

    DevPayload {
        board_name,
        fuse_blown,
        diagnostics,
        pcb_svg,
        schematic_svg,
        circuit_json,
    }
}