pipecircuit-visualization 0.0.8

Code-derived architectural visualizations for Pipecircuit programs
Documentation
mod program;
pub use program::{InstallationDiagram, ProgramDiagram};
mod behavior;
mod derive;
mod render;

use std::{error::Error, fmt, path::Path};

pub use behavior::{BehaviorCheck, BehaviorError, BehaviorInventory, CheckDisposition};
pub use render::DiagramTarget;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ArchitectureError {
    message: String,
}

impl ArchitectureError {
    fn new(message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
        }
    }
}

impl fmt::Display for ArchitectureError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(&self.message)
    }
}

impl Error for ArchitectureError {}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CrateArchitecture {
    state_machines: Vec<StateMachineDiagram>,
}

impl CrateArchitecture {
    pub fn read(crate_root: impl AsRef<Path>) -> Result<Self, ArchitectureError> {
        derive::read_crate(crate_root.as_ref())
    }

    pub fn state_machines(&self) -> &[StateMachineDiagram] {
        &self.state_machines
    }

    pub fn state_machine(&self, name: &str) -> Result<&StateMachineDiagram, ArchitectureError> {
        let mut matches = self
            .state_machines
            .iter()
            .filter(|state_machine| state_machine.name == name);
        let first = matches.next().ok_or_else(|| {
            ArchitectureError::new(format!("state machine `{name}` was not found"))
        })?;
        if matches.next().is_some() {
            return Err(ArchitectureError::new(format!(
                "state machine name `{name}` was ambiguous; use its qualified name"
            )));
        }
        Ok(first)
    }

    pub fn state_machine_qualified(
        &self,
        qualified_name: &str,
    ) -> Result<&StateMachineDiagram, ArchitectureError> {
        self.state_machines
            .iter()
            .find(|state_machine| state_machine.qualified_name == qualified_name)
            .ok_or_else(|| {
                ArchitectureError::new(format!("state machine `{qualified_name}` was not found"))
            })
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StateMachineDiagram {
    name: String,
    qualified_name: String,
    steps: Vec<StepDiagram>,
}

impl StateMachineDiagram {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn qualified_name(&self) -> &str {
        &self.qualified_name
    }

    pub fn steps(&self) -> &[StepDiagram] {
        &self.steps
    }

    pub fn render(&self, target: DiagramTarget) -> String {
        render::render(self, target)
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StepDiagram {
    input: String,
    outcome: DiagramNode,
}

impl StepDiagram {
    pub fn input(&self) -> &str {
        &self.input
    }

    pub fn outcome(&self) -> &DiagramNode {
        &self.outcome
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DiagramNode {
    label: String,
    children: Vec<DiagramNode>,
}

impl DiagramNode {
    pub fn label(&self) -> &str {
        &self.label
    }

    pub fn children(&self) -> &[DiagramNode] {
        &self.children
    }
}

#[cfg(test)]
mod tests;