strafe-type 0.1.1

Types for strafe
Documentation
use std::fmt::{Display, Formatter, Result as FmtResult};

use numfmt::{Numeric, Precision};

pub struct DisplayTable {
    column_names: Vec<String>,
    column_widths: Vec<usize>,
    row_names: Vec<String>,
    data: Vec<Vec<String>>,
    max_print: usize,
    over_max: bool,
}

impl DisplayTable {
    pub fn new<D: ToString + Numeric>(
        column_names: Vec<String>,
        row_names: Vec<String>,
        data: Vec<Vec<D>>,
        max_print: Option<usize>,
    ) -> Self {
        let mut number_formatter = numfmt::Formatter::new().precision(Precision::Significance(5));

        let max_print = max_print.unwrap_or(10);
        let mut over_max = false;

        let data = data
            .into_iter()
            .map(|d| {
                d.into_iter()
                    .map(|v| number_formatter.fmt2(v).to_string())
                    .collect::<Vec<_>>()
            })
            .collect::<Vec<_>>();

        let data = if data.len() > max_print {
            over_max = true;
            data.iter()
                .take((max_print as f64 / 2.0).ceil() as usize)
                .chain(
                    data.iter()
                        .rev()
                        .take((max_print as f64 / 2.0).floor() as usize)
                        .rev(),
                )
                .cloned()
                .collect()
        } else {
            data.clone()
        };
        let row_names = if row_names.len() > max_print {
            row_names
                .iter()
                .take((max_print as f64 / 2.0).ceil() as usize)
                .chain(
                    row_names
                        .iter()
                        .rev()
                        .take((max_print as f64 / 2.0).floor() as usize)
                        .rev(),
                )
                .cloned()
                .collect()
        } else {
            row_names.clone()
        };

        let mut column_widths = column_names.iter().map(|c| c.len()).collect::<Vec<_>>();

        for row in &data {
            for (column, width) in row.iter().zip(column_widths.iter_mut()) {
                *width = (*width).max(column.len());
            }
        }

        if !row_names.is_empty() {
            column_widths.insert(0, row_names.iter().map(|n| n.len()).max().unwrap());
        };

        Self {
            column_names,
            column_widths,
            row_names,
            data,
            max_print,
            over_max,
        }
    }

    fn write_table_bars(
        &self,
        formatter: &mut Formatter,
        left_bar: &str,
        middle_blank: &str,
        middle_bar: &str,
        right_bar: &str,
    ) -> FmtResult {
        write!(formatter, "{left_bar}")?;
        for (index, &len) in self.column_widths.iter().enumerate() {
            if len > 0 {
                for _ in 0..len + 2 {
                    write!(formatter, "{middle_blank}")?;
                }
                if index != self.column_widths.len() - 1 {
                    write!(formatter, "{middle_bar}")?;
                }
            }
        }
        writeln!(formatter, "{right_bar}")?;

        Ok(())
    }

    fn write_data(&self, formatter: &mut Formatter, bar: &str) -> FmtResult {
        for (row_index, row) in self.data.iter().enumerate() {
            if self.over_max && row_index == (self.max_print as f64 / 2.0).floor() as usize {
                for column_index in 0..self.column_widths.len() {
                    write!(formatter, "│ …")?;
                    for _ in 0..self.column_widths[column_index] - 1 {
                        write!(formatter, " ")?;
                    }
                    write!(formatter, " ")?;
                }
                writeln!(formatter, "")?;
            }
            for (column_index, value) in row.iter().enumerate() {
                write!(formatter, "{bar} ")?;
                let column_index = if !self.row_names.is_empty() && column_index == 0 {
                    self.row_names[row_index].fmt(formatter)?;
                    for _ in 0..self.column_widths[column_index] - self.row_names[row_index].len() {
                        write!(formatter, " ")?;
                    }
                    write!(formatter, " {bar} ")?;
                    value.fmt(formatter)?;
                    column_index + 1
                } else {
                    value.fmt(formatter)?;
                    if !self.row_names.is_empty() {
                        column_index + 1
                    } else {
                        column_index
                    }
                };
                if self.column_widths[column_index] > value.to_string().len() {
                    for _ in 0..self.column_widths[column_index] - value.to_string().len() {
                        write!(formatter, " ")?;
                    }
                }
                write!(formatter, " ")?;
            }
            writeln!(formatter, "{bar}")?;
        }

        Ok(())
    }
}

impl Display for DisplayTable {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        // Print top of the table
        self.write_table_bars(f, "", "", "", "")?;

        // Print column headers
        let mut column_names = self.column_names.clone();
        if !self.row_names.is_empty() {
            column_names.insert(0, "".to_string());
        }
        for (column_index, name) in column_names.iter().enumerate() {
            write!(f, "")?;
            name.fmt(f)?;
            if self.column_widths[column_index] > name.len() {
                for _ in 0..self.column_widths[column_index] - name.len() {
                    write!(f, " ")?;
                }
            }
            write!(f, " ")?;
        }
        writeln!(f, "")?;

        // Print divider
        self.write_table_bars(f, "", "", "", "")?;

        // Print data
        self.write_data(f, "")?;

        // Print bottom of the table
        self.write_table_bars(f, "", "", "", "")?;

        Ok(())
    }
}