ommui_data 0.39.0

OMMUI data structures
Documentation
use crate::{error, Result};
use indexmap::IndexMap;
use snafu::{OptionExt, ResultExt};
use std::io;
use xio_instructionset::{Instruction, InstructionMap};
use xio_jobset::Job;

pub trait WriteJob {
    fn write_job(
        &mut self,
        job: &Job,
        instructions: &InstructionMap,
    ) -> Result<()>;
}

impl<W: io::Write> WriteJob for W {
    fn write_job(
        &mut self,
        job: &Job,
        instructions: &InstructionMap,
    ) -> Result<()> {
        writeln!(self, "blockdiag {{").context(error::Io {})?;
        writeln!(self, "    node_width=300").context(error::Io {})?;
        writeln!(self, "    node_height=60").context(error::Io {})?;
        writeln!(self, "    start[label=\"Start\",shape=\"beginpoint\"];")
            .context(error::Io {})?;
        let mut source_nodes = vec!["start".to_string()];
        let mut source_label = "";

        for (i, command) in job.commands.iter().enumerate() {
            let command_name =
                format!("command_{}_{}", i, command.command_type);
            for node in source_nodes.drain(..) {
                writeln!(
                    self,
                    "    {} -> {} [folded, label=\"{}\"];",
                    node, command_name, source_label
                )
                .context(error::Io {})?;
            }
            let instruction = instructions
                .get(&command.command_type)
                .context(error::XioInstructionNotFound {
                    name: command.command_type.to_string(),
                })?;
            let instruction_string =
                format_instruction(instruction, &command.parameters)?;
            let node_label = if instruction_string.is_empty() {
                format!(
                    "{}\n[{}]",
                    command.command_type,
                    command.description.replace('"', "\\\"")
                )
            } else {
                format!(
                    "{}\\n[{}]\\n{}",
                    command.command_type,
                    command.description.replace('"', "\\\""),
                    instruction_string
                )
            };
            let node_color = if command.conditions.is_empty() {
                "#ccccff"
            } else {
                "#ffff77"
            };
            writeln!(
                self,
                "    {}[label=\"{}\", color=\"{}\"];",
                command_name, node_label, node_color
            )
            .context(error::Io {})?;
            let mut condition_source_node = command_name.to_string();
            let mut group_nodes = vec![];
            source_label = "";
            let mut abort_node_name = "".to_string();
            for (j, condition) in command.conditions.iter().enumerate() {
                let condition_name = format!(
                    "{}_condition_{}_{}",
                    command_name, j, condition.command_type
                );
                let instruction = instructions
                    .get(&condition.command_type)
                    .context(error::XioInstructionNotFound {
                        name: condition.command_type.to_string(),
                    })?;
                let instruction_string = format_instruction(
                    instruction,
                    &condition.parameters,
                )?;
                let node_label = if instruction_string.is_empty() {
                    format!(
                        "{}\n[{}]",
                        condition.command_type,
                        condition.description.replace('"', "\\\"")
                    )
                } else {
                    format!(
                        "{}\\n[{}]\\n{}",
                        condition.command_type,
                        condition.description.replace('"', "\\\""),
                        instruction_string
                    )
                };
                writeln!(
                    self,
                    "    {} -> {};",
                    condition_source_node, condition_name
                )
                .context(error::Io {})?;
                writeln!(
                    self,
                    "    {}[label=\"{}\"];",
                    condition_name, node_label
                )
                .context(error::Io {})?;

                if condition.exit_job {
                    abort_node_name = format!("{}_abort", command_name);
                    writeln!(
                        self,
                        "    {} -> {}[folded, label=\"yes\"]",
                        condition_name, abort_node_name
                    )
                    .context(error::Io {})?;
                } else {
                    source_nodes.push(condition_name.to_string());
                }

                group_nodes.push(condition_name.to_string());
                condition_source_node = condition_name;
                source_label = "yes";
            }
            if !abort_node_name.is_empty() {
                writeln!(
                    self,
                    "    {}[label=\"abort\", color=\"#ff7777\"];",
                    abort_node_name
                )
                .context(error::Io {})?;
                group_nodes.push(abort_node_name);
            }
            if condition_source_node != command_name {
                writeln!(
                    self,
                    "    {} -> {} [label=\"no\"];",
                    condition_source_node, command_name
                )
                .context(error::Io {})?;
            }
            if !group_nodes.is_empty() {
                writeln!(
                    self,
                    "    group {{ {} }}",
                    group_nodes.join("; ")
                )
                .context(error::Io {})?;
            }
            if source_nodes.is_empty() {
                source_nodes.push(command_name);
            }
        }
        for node in source_nodes.drain(..) {
            writeln!(
                self,
                "    {} -> end [folded, label=\"{}\"];",
                node, source_label
            )
            .context(error::Io {})?;
        }

        writeln!(self, "    end[label=\"End\",shape=\"endpoint\"];")
            .context(error::Io {})?;

        writeln!(self, "}}").context(error::Io {})?;

        Ok(())
    }
}

fn format_instruction(
    instruction: &Instruction,
    parameters: &IndexMap<String, String>,
) -> Result<String> {
    let parameter_names = instruction
        .parameters
        .iter()
        .map(|param| param.id.to_string())
        .collect::<Vec<_>>();

    ensure!(
        parameter_names.len() == parameters.len(),
        error::XioParameterCountMismatch {
            names: parameter_names.len(),
            parameters: parameters.len(),
        }
    );

    let parameters = parameter_names
        .into_iter()
        .map(|p| {
            parameters.get(&p).context(error::XioParameterNotFound {
                name: p.to_string(),
            })
        })
        .collect::<Result<Vec<_>>>()?;

    let mut label = instruction.formatstring.to_string();
    for (i, parameter) in parameters.into_iter().enumerate() {
        label = label.replace(&format!("%{}", i), parameter);
    }
    Ok(label)
}