1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*!
 * Formatted output and pretty printing
 */
use colored::*;
use std::cmp;
use term_size;

// Colors used when outputting with color
pub(crate) const CRATE_LIST_HEADING_COLOR: &'static str = "blue";
pub(crate) const SECTION_HEADING_COLOR: &'static str = "yellow";
pub(crate) const ENUM_HEADING_COLOR: &'static str = "green";

// Space between columns when pretty printing
const SPACER: &'static str = "  ";

// Default maximum output width when pretty printing
const DEFAULT_TERM_WIDTH: usize = 90;

/// A simple colored header for sections
pub(crate) fn header(s: &str, color: &str) -> String {
    format!("{} {}", "::".color(color).bold(), s.bold())
}

/// Print a vec of strings as column spaced rows
pub(crate) fn pprint_as_columns(elems: Vec<String>) -> String {
    let col_width = elems.iter().map(String::len).max().unwrap();
    let per_row = max_width() / (col_width + 1);

    elems
        .chunks(per_row)
        .map(|chunk| {
            chunk
                .iter()
                .map(|c| format!("{:01$}", c, col_width))
                .collect::<Vec<String>>()
                .join(" ")
        })
        .collect::<Vec<String>>()
        .join("\n")
}

fn max_width() -> usize {
    if let Some((w, _)) = term_size::dimensions() {
        w
    } else {
        DEFAULT_TERM_WIDTH
    }
}

// A single row within a table layout
struct Row {
    cells: Vec<String>,
}

impl Row {
    fn formatted(&self, column_widths: &Vec<usize>) -> String {
        self.cells
            .iter()
            .zip(column_widths)
            .map(|e| format!("{:01$}", e.0, e.1))
            .collect::<Vec<String>>()
            .join(SPACER)
    }

    fn two_column_wrapped(&self, column_widths: &Vec<usize>) -> String {
        if self.cells.len() != 2 {
            panic!(
                "two_column_wrapped called on row with {} columns",
                self.cells.len()
            );
        }

        let max = max_width();
        if column_widths.iter().sum::<usize>() + SPACER.len() <= max {
            return self.formatted(&column_widths);
        }

        let mut buf = String::new();
        let mut current = String::new();
        let offset = vec![" "; column_widths[0] + SPACER.len() + 1].join("");
        current.push_str(&(format!("{:01$}", self.cells[0], column_widths[0]) + SPACER));

        for word in self.cells[1].split_whitespace() {
            if current.len() + word.len() + 1 < max {
                current.push_str(&format!(" {}", word));
            } else {
                buf.push_str(&format!("{}\n", current));
                current.clear();
                current.push_str(&format!("{}{}", offset, word));
            }
        }

        buf.push_str(&current);
        return buf;
    }
}

/// A very simple plain text table that knows the width of each of its columns.
pub(crate) struct Table {
    rows: Vec<Row>,
    column_widths: Vec<usize>,
}

impl Table {
    /// Create a new empty Table without any rows
    pub fn new() -> Self {
        Table {
            rows: vec![],
            column_widths: vec![],
        }
    }

    /**
     * Create a new Table from a vec of vecs as a one time operation.
     * This is a convenience method that is intended to be used at the
     * end of an iterator chain that make use of 'add_row' to construct
     * the table from the input.
     */
    pub fn from_rows(rows: Vec<Vec<String>>) -> Self {
        let mut t = Table::new();
        rows.iter().for_each(|r| t.add_row(r.clone()));
        return t;
    }

    /// Add a single row to an existing table and update column widths if needed.
    pub fn add_row(&mut self, cells: Vec<String>) {
        let diff = cells.len() - self.column_widths.len();
        if diff > 0 {
            self.column_widths.extend(vec![0; diff])
        }

        cells.iter().enumerate().for_each(|(i, cell)| {
            self.column_widths[i] = cmp::max(cell.len(), self.column_widths[i])
        });

        self.rows.push(Row { cells });
    }

    /// Convert this table to a left justified, column aligned string
    pub fn as_string(&self) -> String {
        self.rows
            .iter()
            .map(|r| r.two_column_wrapped(&self.column_widths))
            .collect::<Vec<String>>()
            .join("\n")
    }
}