doteur 0.3.3

Tool to automate the visualisation of UML dependencies from a SQL file
Documentation
use std::fmt;

use super::super::add_traits::{Trim};

/// A dot table is the corresponding rendering of a sql table in a dot file
pub struct DotTable {
    /// The header of the table
    header: String,
    /// The attribute of the table
    attributes: Vec<(String, String)>,
    /// The footer of the table
    footer: String,
    /// Changes the rendering of the file if true
    dark_mode: bool
}

impl fmt::Display for DotTable {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{0}\n{1}\n\n\t{2}\n", self.header, self.attributes.clone().into_iter().map(|(_, value)| value).collect::<Vec<String>>().join("\n"), self.footer)
    }
}

impl DotTable {

    /// Creates a new table
    ///
    /// # Arguments
    ///
    /// * `table_name` - The table to render in dot
    /// * `dark_mode` - Changes the rendering of the file if true
    pub fn new(table_name: &str, dark_mode: bool) -> DotTable {
        let header : String = generate_table_header(table_name, dark_mode);
        DotTable {
            header,
            attributes: Vec::new(),
            footer: String::from("</TABLE> >]"),
            dark_mode
        }
    }

    /// Returns the index of an attribute
    ///
    /// # Arguments
    ///
    /// * `attr_name` - name of the attribute
    fn index_of_attribute(&mut self, attr_name: &str) -> Result<usize, &'static str> {
        let filtered_key_map : Vec<(usize, String)> = self.attributes
            .clone()
            .into_iter()
            .enumerate()
            .map(|(i, (key, _))| (i, key))
            .filter(|(_, key)| key == attr_name)
            .collect();
        match filtered_key_map.is_empty() {
            true => Err("No such attribute"),
            false => Ok(filtered_key_map.first().unwrap().0)
        }
    }

    /// Replace an already existing attribute
    ///
    /// # Arguments
    ///
    /// * `attr_name` - name of the attribute
    /// * `new_value` - new value of the attribute
    fn replace_attribute(&mut self, attr_name: &str, new_value: String) -> Result<usize, &'static str>  {
        match self.index_of_attribute(attr_name) {
            Ok(index) => {let _ = std::mem::replace(&mut self.attributes[index], (attr_name.to_string(), new_value)); Ok(index)},
            Err(err) => Err(err)
        }
    }

    /// Replace the attribute if it already exists,
    /// otherwise append the attribute
    ///
    /// # Arguments
    ///
    /// * `attr_name` - name of the attribute
    /// * `value` - value of the attribute
    fn push_or_replace_attribute(&mut self, attr_name: &str, value: String) {
        if self.replace_attribute(attr_name, value.clone()).is_err() {
            self.attributes.push((attr_name.to_string(), value));
        }
    }

    /// Adds an attribute to the table
    ///
    /// # Arguments
    ///
    /// * `title` - The title of the attribute
    /// * `desc` - The description of the attribute
    pub fn add_attribute(&mut self, title: &str, desc : &str) {
        self.attributes.push((title.to_string(), generate_attribute(title, desc, self.dark_mode)));
    }


    /// Adds a foreign key attribute
    ///
    /// # Arguments
    ///
    /// * `key` - The key of the attribute in the table
    /// * `fk_table` - The refered table
    /// * `fk_col` - The refered key
    pub fn add_attribute_fk(&mut self, key: &str, fk_table : &str, fk_col : &str) {
        self.push_or_replace_attribute(key, generate_fk_attribute(key, fk_table, fk_col, self.dark_mode));
    }

}

/// Generate the .dot table header.
///
/// # Arguments
///
/// * `name` - The name of the table
/// * `dark_mode` - Changes the rendering of the table header if true
fn generate_table_header(name: &str, dark_mode: bool) -> String {
    let styles : (&str, &str) = match dark_mode {
            true => ("grey20", "grey10"),
            false => ("grey95", "indigo")
    };
    format!("
    {0} [label=<
        <TABLE BGCOLOR=\"{1}\" BORDER=\"1\" CELLBORDER=\"0\" CELLSPACING=\"0\">

        <TR><TD COLSPAN=\"2\" CELLPADDING=\"5\" ALIGN=\"CENTER\" BGCOLOR=\"{2}\">
        <FONT FACE=\"Roboto\" COLOR=\"white\" POINT-SIZE=\"12\">
        <B>{0}</B>
        </FONT></TD></TR>", name.trim_leading_trailing(), styles.0, styles.1)
}


/// Generate an attribute
///
/// # Arguments
///
/// * `title` - The name of the attribute
/// * `desc` - The description of the attribute
/// * `dark_mode` - Changes the rendering of the table header if true
fn generate_attribute(title: &str, desc: &str, dark_mode: bool) -> String {
    let font_color : &str = match dark_mode {
            true => "white",
            false => "black"
    };
    format!("
        <TR><TD ALIGN=\"LEFT\" BORDER=\"0\">
        <FONT COLOR=\"{0}\" FACE=\"Roboto\"><B>{1}</B></FONT>
        </TD><TD ALIGN=\"LEFT\">
        <FONT COLOR=\"{0}\" FACE=\"Roboto\">{2}</FONT>
        </TD></TR>", font_color, title.trim_leading_trailing(), desc.trim_leading_trailing()
    )
}

/// Generas a foreign key attribute
///
/// # Arguments
///
/// * `key` - The key of the attribute in the table
/// * `fk_table` - The refered table
/// * `fk_col` - The refered key
/// * `dark_mode` - Changes the rendering of the table header if true
fn generate_fk_attribute(key: &str, fk_table: &str, fk_col: &str, dark_mode: bool) -> String {
    let font_color : &str = match dark_mode {
            true => "white",
            false => "black"
    };
    let refer_sign : &str = match cfg!(unix) {
        true => "\u{1F5DD}",
        _ => "[FK]"
    };
    format!("
        <TR><TD ALIGN=\"LEFT\" BORDER=\"0\">
        <FONT COLOR=\"{0}\" FACE=\"Roboto\"><B>{1} {2}</B></FONT>
        </TD><TD ALIGN=\"LEFT\">
        <FONT FACE=\"Roboto\" COLOR=\"{0}\">Refers to <I>{3}[{4}]</I></FONT>
        </TD></TR>", font_color,  key.trim_leading_trailing(), refer_sign, fk_table.trim_leading_trailing(), fk_col.trim_leading_trailing()
    )
}