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)
}