pipeawesome2 0.1.3

Loops, branches and joins to UNIX pipes… in a sane way…
Documentation
use std::collections::BTreeMap;
use std::collections::HashSet;
use crate::config::{ ComponentType, Connection };
use crate::connectable::OutputPort;
use std::collections::HashMap;

use graphviz_rust::dot_structures::*;
use graphviz_rust::dot_generator::*;
use graphviz_rust::printer::{PrinterContext,DotPrinter};

const BUFFER_COLOR: &str = "lightgray";
const REGULATOR_COLOR: &str = "lightgray";
const DRAIN_COLOR: &str = "lightpink";
const FAUCET_COLOR: &str = "darkseagreen1";
const JUNCTION_COLOR: &str = "papayawhip";
const LAUNCH_COLOR: &str = "lightblue";


pub struct GraphConnection<'a> {
    src: (&'a ComponentType, &'a str, &'a Option<String>),
    via: &'a OutputPort,
    dst: (&'a ComponentType, &'a str, &'a Option<String>),
}

pub fn convert_connection_to_graph_connection<'a>(mut acc: Vec<GraphConnection<'a>>, connections: Vec<&'a Connection>) -> Vec<GraphConnection<'a>> {

    let mut src: Option<(&ComponentType, &str, Option<&OutputPort>, &Option<String>)> = None;
    let mut dst: Option<(&ComponentType, &str, Option<&OutputPort>, &Option<String>)>;
    let mut r: Vec<GraphConnection> = vec![];

    for connection in connections {
        dst = match connection {
            Connection::MiddleConnection { component_type, component_name, output_port, connection_set, .. } => Some((component_type, component_name, Some(output_port), connection_set)),
            Connection::StartConnection { component_type, component_name, output_port, connection_set } => Some((component_type, component_name, Some(output_port), connection_set)),
            Connection::EndConnection { component_type, component_name, connection_set, .. } => Some((component_type, component_name, None, connection_set)),
        };
        if let (Some((src_0, src_1, Some(src_2), connection_set)), Some(dst)) = (src, dst) {
            r.push(GraphConnection {
                src: (src_0, src_1, connection_set),
                via: src_2,
                dst: (dst.0, dst.1, dst.3),
            });
        }
        src = match dst {
            Some((dst_type, dst_name, Some(output_port), connection_set)) => Some((dst_type, dst_name, Some(output_port), connection_set)),
            _ => None,
        };
    }

    acc.append(&mut r);
    acc

}


pub fn convert_connection_components_fold<'a>(mut acc: HashMap<&'a ComponentType, Vec<&'a str>>, connections: Vec<&'a Connection>) -> HashMap<&'a ComponentType, Vec<&'a str>> {

    for connection in connections {
        let comp: (&ComponentType, &str) = match connection {
            Connection::MiddleConnection { component_type, component_name, .. } => (component_type, component_name),
            Connection::StartConnection { component_type, component_name, .. } => (component_type, component_name),
            Connection::EndConnection { component_type, component_name, .. } => (component_type, component_name),
        };

        acc.entry(comp.0)
            .and_modify(|x| {
                x.push(comp.1);
            })
            .or_insert_with(|| vec![comp.1]);
    }


    acc
}

pub fn get_graph(subgraph: Vec<Subgraph>) -> String {
    let mut stmts: Vec<Stmt> = vec![Stmt::Attribute(attr!("labeljust","l")),Stmt::Attribute(attr!("newrank","true"))];
    let mut more_stmts = subgraph.into_iter().map(|sg| { Stmt::Subgraph(sg) }).collect();
    stmts.append(&mut more_stmts);

    let graph = Graph::DiGraph {
        id: Id::Plain("g_get_graph".to_string()),
        strict: true,
        stmts
    };

    let mut ctx = PrinterContext::default();
    graph.print(&mut ctx)
}


pub fn get_diagram(components: HashMap<&ComponentType, Vec<&str>>, connections: Vec<GraphConnection>) -> Subgraph {

    fn component_type_to_letter(ct: &ComponentType) -> &str {
        match ct {
            ComponentType::Buffer => "b",
            ComponentType::Drain => "d",
            ComponentType::Faucet => "f",
            ComponentType::Junction => "j",
            ComponentType::Launch => "l",
            ComponentType::Regulator => "r",
        }
    }

    fn get_node_shape(component_type: &ComponentType) -> Attribute {
        match component_type {
            ComponentType::Faucet => attr!("shape","trapezium"),
            ComponentType::Drain => attr!("shape","invtrapezium"),
            ComponentType::Junction => attr!("shape","oval"),
            ComponentType::Buffer => attr!("shape","invhouse"),
            ComponentType::Launch => attr!("shape","box"),
            ComponentType::Regulator => attr!("shape","house"),
        }
    }

    fn get_node_color(component_type: &ComponentType) -> Attribute {
        match component_type {
            ComponentType::Faucet => attr!("fillcolor",FAUCET_COLOR),
            ComponentType::Drain => attr!("fillcolor",DRAIN_COLOR),
            ComponentType::Junction => attr!("fillcolor",JUNCTION_COLOR),
            ComponentType::Buffer => attr!("fillcolor",BUFFER_COLOR),
            ComponentType::Regulator => attr!("fillcolor",REGULATOR_COLOR),
            ComponentType::Launch => attr!("fillcolor",LAUNCH_COLOR),
        }
    }

    fn node_id(component_type: &ComponentType, name: &str) -> NodeId {
        NodeId(Id::Plain(format!("{}_{}", component_type_to_letter(component_type), name)), None)
    }

    fn get_connection_set_name(possible_graph_connection_for_connection_set: Option<&GraphConnection>, component_type: &ComponentType, component_name: &str) -> String {

        fn taker(to_take: &Option<String>) -> String {
            match to_take {
                None => "".to_string(),
                Some(ref_str) => {
                    ref_str.to_owned()
                },
            }
        }

        if let Some(gc) = possible_graph_connection_for_connection_set {
            if (gc.src.1 == component_name) && (gc.src.0 == component_type) {
                return taker(gc.src.2)
            }
            return taker(gc.dst.2)
        }
        "".to_string()
    }

    let (connection_set_with_faucet, connection_set_with_drain) = connections.iter().fold(
        (HashSet::new(), HashSet::new()),
        |(mut acc_faucet, mut acc_drain), connection| {
            match connection {
                GraphConnection { src: (ComponentType::Faucet, _, Some(connection_set)), .. } => {
                    acc_faucet.insert(connection_set.clone());
                    (acc_faucet, acc_drain)
                },
                GraphConnection { dst: (ComponentType::Drain, _, Some(connection_set)), .. } => {
                    acc_drain.insert(connection_set.clone());
                    (acc_faucet, acc_drain)
                },
                _ => (acc_faucet, acc_drain)
            }
        }
    );

    let graph_components: BTreeMap<String, Vec<Node>> = components.iter().fold(
        BTreeMap::new(),
        |mut acc, (component_type, component_names)| {
            for component_name in component_names {
                let label = component_name;
                let connection_set_graph_connection: Option<&GraphConnection> = connections.iter().find(|connection| {
                    (
                        (connection.src.1 == *component_name) && (connection.src.0 == *component_type)
                    ) || (
                        (connection.dst.1 == *component_name) && (connection.dst.0 == *component_type)
                    )
                });

                acc.entry(get_connection_set_name(connection_set_graph_connection, component_type, component_name)).or_insert_with(Vec::new).push(
                    Node::new(
                        node_id(component_type, component_name),
                        vec![attr!("label", label), attr!("style", "filled"), get_node_shape(component_type), get_node_color(component_type)]
                    )
                );
            }
            acc
        }
    );

    struct FrontAndBack<T> {
        front: Vec<T>,
        middle: Vec<T>,
        back: Vec<T>,
    }

    impl<T> FrontAndBack<T> {
        fn append(&mut self, mut a: Vec<T>) {
            self.middle.append(&mut a);
        }
        fn push_back(&mut self, a: T) {
            self.back.push(a);
        }
        fn push_front(&mut self, a: T) {
            self.front.push(a);
        }
        fn push_middle(&mut self, a: T) {
            self.middle.push(a);
        }
        fn ret(&mut self) -> Vec<T> {
            let mut v = vec![];
            std::mem::swap(&mut v, &mut self.front);
            v.append(&mut self.middle);
            v.append(&mut self.back);
            v
        }
        fn new() -> FrontAndBack<T> {
            FrontAndBack { front: vec![], middle: vec![], back: vec![] }
        }
    }

    let mut stmts: Vec<Stmt> = graph_components.into_iter().fold(FrontAndBack::new(), |mut acc, (connection_set, nodes)| {
        let title = format!("\"{}:\"", connection_set);
        let rank = if connection_set_with_faucet.contains(&connection_set) { "min" }
            else if connection_set_with_drain.contains(&connection_set) { "max" }
            else { "same" };
        let mut stmts = vec![Stmt::Attribute(attr!("label", title)),Stmt::Attribute(attr!("rank", rank))];
        let stmts_to_add: Vec<Stmt> = nodes.into_iter().map(Stmt::Node).collect();
        if connection_set.is_empty() {
            acc.append(stmts_to_add);
            return acc
        }
        stmts.append(&mut stmts_to_add.into_iter().collect::<Vec<Stmt>>());
        let subgraph = Stmt::Subgraph(Subgraph {
            id: id!(format!("cluster_nodes_{}", connection_set)),
            stmts
        });
        if connection_set_with_faucet.contains(&connection_set) {
            acc.push_front(subgraph);
        } else if connection_set_with_drain.contains(&connection_set) {
            acc.push_back(subgraph);
        } else {
            acc.push_middle(subgraph);
        }
        acc
    }).ret();

    let mut edgs = connections.iter().map(|gc| {
        let mut lbl = format!("{:?}", gc.via);
        if gc.via == &OutputPort::Out {
            lbl = "\"\"".to_string()
        }
        Stmt::Edge(Edge {
            ty: EdgeTy::Pair(Vertex::N(node_id(gc.src.0, gc.src.1)), Vertex::N(node_id(gc.dst.0, gc.dst.1))),
            attributes: vec![attr!("label", lbl)]
        })
    }).collect();

    stmts.append(&mut edgs);

    Subgraph {
        id: id!("diagram"),
        stmts: stmts.into_iter().collect::<Vec<Stmt>>()
    }

}

pub fn get_legend(draw_border: bool) -> Subgraph {
    let graph_name = if draw_border { "cluster_legend" } else { "graph_legend" };
    subgraph!(
        graph_name;
        attr!("color","black"),
        attr!("label","Legend"),
        subgraph!(
            "cluster_legend_launch";attr!("label","launch"),
            node!("legend_launch";attr!("fillcolor",LAUNCH_COLOR),attr!("label","\"\""),attr!("shape","box"),attr!("width","0.3"),attr!("style","filled"),attr!("height","0.3"))
        ),
        subgraph!(
            "cluster_legend_buffer";attr!("label","buffer"),
            node!("legend_buffer";attr!("fillcolor",BUFFER_COLOR),attr!("label","\"\""),attr!("shape","invhouse"),attr!("width","0.3"),attr!("style","filled"),attr!("height","0.3"))
        ),
        subgraph!(
            "cluster_legend_regulator";attr!("label","regulator"),
            node!("legend_regulator";attr!("fillcolor",REGULATOR_COLOR),attr!("label","\"\""),attr!("shape","house"),attr!("width","0.3"),attr!("style","filled"),attr!("height","0.3"))
        ),
        subgraph!(
            "cluster_legend_junction";attr!("label","junction"),
            node!("legend_junction";attr!("fillcolor",JUNCTION_COLOR),attr!("label","\"\""),attr!("shape","oval"),attr!("width","0.3"),attr!("style","filled"),attr!("height","0.3"))
        ),
        subgraph!(
            "cluster_legend_faucet";attr!("label","faucet"),
            node!("legend_faucet";attr!("fillcolor",FAUCET_COLOR),attr!("label","\"\""),attr!("shape","trapezium"),attr!("width","0.3"),attr!("style","filled"),attr!("height","0.3"))
        ),
        subgraph!(
            "cluster_legend_drain";attr!("label","drain"),
            node!("legend_drain";attr!("fillcolor",DRAIN_COLOR),attr!("label","\"\""),attr!("shape","invtrapezium"),attr!("width","0.3"),attr!("style","filled"),attr!("height","0.3"))
        )
    )
}