shapes_converter 0.1.3

RDF data shapes implementation in Rust
Documentation
use super::Name;
use super::NodeId;
use super::UmlCardinality;
use super::UmlComponent;
use super::UmlEntry;
use super::UmlError;
use super::UmlLink;
use super::ValueConstraint;
use std::collections::hash_map::*;
use std::collections::HashMap;
use std::io::Write;

#[derive(Debug, PartialEq, Default)]
pub struct Uml {
    labels_counter: usize,
    labels: HashMap<Name, NodeId>,
    components: HashMap<NodeId, UmlComponent>,
    links: Vec<UmlLink>,
}

impl Uml {
    pub fn new() -> Uml {
        Default::default()
    }

    pub fn add_label(&mut self, label: &Name) -> NodeId {
        match self.labels.entry(label.clone()) {
            Entry::Occupied(c) => *c.get(),
            Entry::Vacant(v) => {
                self.labels_counter += 1;
                let n = NodeId::new(self.labels_counter);
                v.insert(n);
                n
            }
        }
    }

    pub fn add_component(&mut self, node: NodeId, component: UmlComponent) -> Result<(), UmlError> {
        match self.components.entry(node) {
            Entry::Occupied(_c) => Err(UmlError::NodeIdHasComponent { node_id: node }),
            Entry::Vacant(v) => {
                v.insert(component);
                Ok(())
            }
        }
    }

    pub fn add_link(
        &mut self,
        source: NodeId,
        target: Name,
        link_name: Name,
        card: UmlCardinality,
    ) -> Result<(), UmlError> {
        match self.labels.entry(target) {
            Entry::Occupied(entry) => {
                let link = UmlLink::new(source, *entry.get(), link_name, card);
                self.links.push(link);
                Ok(())
            }
            Entry::Vacant(v) => {
                self.labels_counter += 1;
                let target_node_id = NodeId::new(self.labels_counter);
                v.insert(target_node_id);
                let link = UmlLink::new(source, target_node_id, link_name, card);
                self.links.push(link);
                Ok(())
            }
        }
    }

    pub fn as_plantuml<W: Write>(&self, writer: &mut W) -> Result<(), UmlError> {
        writeln!(writer, "@startuml")?;
        for (node_id, component) in self.components.iter() {
            component2plantuml(node_id, component, writer)?;
        }
        for link in self.links.iter() {
            link2plantuml(link, writer)?;
        }
        writeln!(writer, "@enduml")?;
        Ok(())
    }
}

fn component2plantuml<W: Write>(
    node_id: &NodeId,
    component: &UmlComponent,
    writer: &mut W,
) -> Result<(), UmlError> {
    match component {
        UmlComponent::UmlClass(class) => {
            let name = class.name();
            let href = if let Some(href) = class.href() {
                format!("[[{href} {name}]]")
            } else {
                "".to_string()
            };
            writeln!(
                writer,
                "class \"{}\" as {} <<(S,#FF7700)>> {} {{ ",
                name, node_id, href
            )?;
            for entry in class.entries() {
                entry2plantuml(entry, writer)?;
            }
            writeln!(writer, "}}")?;
        }
    }
    Ok(())
}

fn link2plantuml<W: Write>(link: &UmlLink, writer: &mut W) -> Result<(), UmlError> {
    let source = format!("{}", link.source);
    let card = card2plantuml(&link.card);
    let target = format!("{}", link.target);
    let name = name2plantuml(&link.name);
    writeln!(writer, "{source} --> \"{card}\" {target} {name}")?;
    Ok(())
}

fn entry2plantuml<W: Write>(entry: &UmlEntry, writer: &mut W) -> Result<(), UmlError> {
    let property = name2plantuml(&entry.name);
    let value_constraint = value_constraint2plantuml(&entry.value_constraint);
    let card = card2plantuml(&entry.card);
    writeln!(writer, "{} : {} {}", property, value_constraint, card)?;
    writeln!(writer, "--")?;
    Ok(())
}

fn name2plantuml(name: &Name) -> String {
    if let Some(href) = name.href() {
        format!("[[{href} {}]]", name.name())
    } else {
        name.name()
    }
}

fn value_constraint2plantuml(vc: &ValueConstraint) -> String {
    match vc {
        ValueConstraint::Any => ".".to_string(),
        ValueConstraint::Datatype(dt) => name2plantuml(dt),
        ValueConstraint::Ref(r) => format!("@{}", name2plantuml(r)),
        ValueConstraint::None => "".to_string(),
    }
}

fn card2plantuml(card: &UmlCardinality) -> String {
    match card {
        UmlCardinality::OneOne => "".to_string(),
        UmlCardinality::Star => "*".to_string(),
        UmlCardinality::Plus => "+".to_string(),
        UmlCardinality::Optional => "?".to_string(),
        UmlCardinality::Range(m, n) => format!("{m}-{n}"),
        UmlCardinality::Fixed(m) => format!("{{{m}}}"),
    }
}