nadi_core 0.8.1

Core library for Nadi systems, for use by plugins
Documentation
use nadi_plugin::nadi_internal_plugin;

#[nadi_internal_plugin]
mod gviz {
    use crate::attrs::FromAttribute;
    use crate::functions::FunctionRet;
    use crate::nadi_plugin::network_func;

    use crate::template::Template;
    use crate::{network::Network, node::NodeInner, return_on_err};
    use abi_stable::std_types::Tuple2;
    use std::{fs::File, io::Write, path::Path};

    /// Load Node positions from the graphviz file
    #[network_func(factor = 0.1)]
    fn load_positions(net: &Network, gv_file: &Path, factor: f64) -> Result<(), String> {
        let contents = std::fs::read_to_string(gv_file).map_err(|e| e.to_string())?;
        let tokens = crate::parser::tokenizer::get_tokens(&contents);
        let gvnet = crate::parser::graphviz::parse(tokens).map_err(|e| e.to_string())?;
        for Tuple2(name, node) in &net.nodes_map {
            let attrs = gvnet
                .nodes
                .get(name.as_str())
                .ok_or(format!("Node {name:?} not found"))?;
            let a = attrs.get("pos").ok_or(
                "pos attr not found, use a graphviz file generated by a layout engine".to_string(),
            )?;
            let pos = String::try_from_attr(a).map_err(|e| e.to_string())?;
            let (x, y) = pos
                .split_once(",")
                .ok_or(format!("Invalid position {pos:?}"))?;
            let x: f64 = x.trim().parse().ok().ok_or(format!("Invalid x: {x:?}"))?;
            let y: f64 = y.trim().parse().ok().ok_or(format!("Invalid y: {y:?}"))?;
            node.lock().set_pos((x * factor, y * factor));
        }
        Ok(())
    }

    /// Save the network as a graphviz file
    /// # Arguments:
    /// - `outfile` - Path to the output file
    /// - `name` - Name of the graph
    #[network_func(name = "network", global_attrs = "")]
    fn save_gv(
        net: &mut Network,
        outfile: &Path,
        name: &str,
        global_attrs: &str,
        node_attr: Option<Template>,
        edge_attr: Option<Template>,
    ) -> FunctionRet {
        let mut file = return_on_err!(File::create(outfile));
        return_on_err!(writeln!(file, "digraph {} {{", name));
        return_on_err!(writeln!(file, "{}", global_attrs));
        for node in net.nodes() {
            let (name, outputs) = {
                let node: &NodeInner = &node.lock();
                (node.name().to_string(), node.outputs().to_vec())
            };
            if let Some(templ) = &node_attr {
                let node: &NodeInner = &node.lock();
                let attr = return_on_err!(templ.render(node));
                return_on_err!(writeln!(file, "{:?} [{attr}]", name));
            }

            let attrs = if let Some(templ) = &edge_attr {
                let node: &NodeInner = &node.lock();
                let attr = return_on_err!(templ.render(node));
                format!(" [{attr}]")
            } else {
                "".into()
            };
            for out in outputs {
                return_on_err!(writeln!(
                    file,
                    "{:?} -> {:?}{attrs}",
                    name,
                    out.lock().name()
                ));
            }
        }
        return_on_err!(writeln!(file, "}}"));
        FunctionRet::None
    }
}