table_output/
lib.rs

1use std::collections::HashMap;
2use serde::{Deserialize, Serialize};
3
4#[cfg(test)]
5mod tests {
6    #[test]
7    fn it_works() {
8        let result = 2 + 2;
9        assert_eq!(result, 4);
10    }
11}
12
13#[derive(Clone, Debug, Deserialize, Serialize)]
14pub struct Table {
15    num_cols: usize,
16    rows: Vec<Vec<String>>,
17}
18
19#[derive(Clone, Debug)]
20pub enum TableError {
21    WrongNumberCols,
22    NotEnoughCols,
23}
24
25impl Table {
26    pub fn new(headers: Vec<String>) -> Self {
27        let mut rows  = Vec::new();
28        let cols = headers.len();
29        rows.push(headers);
30        Self {
31            num_cols: cols,
32            rows: rows,
33        }
34    }
35    pub fn add_row(&mut self, row: Vec<String>) -> Result<(), TableError> {
36        if row.len() == self.num_cols {
37            self.rows.push(row);
38            return Ok(());
39        }
40        return Err(TableError::WrongNumberCols);
41    }
42    pub fn output_csv(&self) -> String {
43        let mut wtr = csv::Writer::from_writer(vec![]);
44        for row in self.rows.clone() {
45            wtr.write_record(row.as_slice()).unwrap();
46        }
47        String::from_utf8(wtr.into_inner().unwrap()).unwrap()
48    }
49    pub fn output_json(&self) -> String {
50        let mut json_vec: Vec<HashMap<String, String>> = Vec::new();
51        let headers = self.rows[0].clone();
52        for row in self.rows.clone()[1..].iter() {
53            let mut json_hashmap: HashMap<String, String> = HashMap::new();
54            for spot_header in headers.iter().enumerate() {
55                json_hashmap.insert(spot_header.1.clone(), row[spot_header.0].clone());
56            }
57            json_vec.push(json_hashmap);
58        }
59        serde_json::to_string_pretty(&json_vec).unwrap()
60    }
61    pub fn output_table_html(&self, name: Option<impl Into<String>>) -> String {
62        let mut data = String::new();
63        if let Some(table_name) = name {
64            data.push_str(format!("<p>{}</p>\n", html_escape::encode_text(table_name.into().as_str())).as_str())
65        }
66        data.push_str("<table>\n");
67        data.push_str(format!("<th>{}</th>", self.rows[0].iter().map(|x| html_escape::encode_text(x).to_string()).collect::<Vec<String>>().join("</th><th>")).as_str());
68        data.push_str("\n");
69        for row in self.rows[1..].iter() {
70            data.push_str("<tr>\n");
71            data.push_str(format!("<td>\n{}</td>", row.iter().map(|x| html_escape::encode_text(x).to_string()).collect::<Vec<String>>().join("</td>\n<td>")).as_str());
72            data.push_str("\n</tr>\n");
73        }
74        data.push_str("</table>\n");
75        data
76    }
77    fn create_line(input: Vec<String>, wrap_space: usize) -> String {
78        let mut line = String::new();
79        line.push_str("|");
80        //let spacing = wrap_space
81        for in_string in input {
82            //println!("'{}'", in_string);
83            line.push_str(format!(" {}{}|", in_string, vec![" "; wrap_space + 1 - in_string.chars().count()].join("")).as_str());
84        }
85        line.push('\n');
86        line
87    }
88    pub fn output_pretty_table(&self, width: Option<usize>) -> Result<String, TableError> {
89        let mut data = String::new();
90        let term_cols: usize;
91        if let Some(w) = width {
92            term_cols = w;
93        } else if let Some(term_size) = termsize::get() {
94            term_cols = term_size.cols.into();
95        } else {
96            term_cols = 200;
97        }
98        let headers = self.rows[0].clone();
99        let mut total_chars: usize = (headers.iter().map(|x| x.chars().count() + 3).max().unwrap()) * headers.len() + 1;
100        if total_chars > term_cols {
101            return Err(TableError::NotEnoughCols);
102        }
103        let mut test_add = 1;
104        while (headers.iter().map(|x| x.chars().count() + 3 + test_add).max().unwrap()) * headers.len() + 1 <= term_cols {
105            total_chars = (headers.iter().map(|x| x.chars().count() + 3 + test_add).max().unwrap()) * headers.len() + 1;
106            test_add += 1
107        }
108        //println!("termcols {} totalchars {}", term_cols, total_chars);
109        //println!("{}", total_chars);
110        let spacing: usize = (total_chars / headers.len()) - 1;
111        //assert_eq!(spacing * headers.len(), total_chars)
112        //println!("{}", spacing);
113        let wrap_space = spacing - 2;
114        //println!("{}", wrap_space);
115        
116        //let mut lines: Vec<String> = Vec::new();
117        let mut horiz_border = String::new();
118        horiz_border.push('+');
119        for _count in 0..headers.len() {
120            for _count2 in 0..spacing {
121                horiz_border.push('=');
122            }
123            horiz_border.push('+');
124        }
125        horiz_border.push('\n');
126        data.push_str(horiz_border.as_str());
127        data.push_str(Self::create_line(headers, wrap_space).as_str());
128        data.push_str(horiz_border.as_str());
129        horiz_border = horiz_border.replace("=", "-");
130        for row in self.rows[1..].iter() {
131            let mut max_num_lines = 1;
132            let mut vals_lines_vec: Vec<Vec<String>> = Vec::new();
133            for val in row {
134                let lines = textwrap::fill(val, wrap_space).replace("\r", "").split("\n").map(|x| x.to_string()).collect::<Vec<String>>();
135                let num = lines.len();
136                vals_lines_vec.push(lines);
137                if num > max_num_lines {
138                    max_num_lines = num;
139                }
140            }
141            for count in 0..max_num_lines {
142                //let mut spots: Vec<String> = Vec::new();
143                let val_lines_data: Vec<String> = vals_lines_vec.iter().map(|x| {
144                    if x.len() - 1 < count {
145                        String::new()
146                    } else {
147                        x[count].clone()
148                    }
149                }).collect();
150                data.push_str(Self::create_line(val_lines_data, wrap_space).as_str());
151            }
152            data.push_str(horiz_border.as_str());
153        }
154        //data.push_str(horiz_border.as_str());
155        Ok(data)
156    }
157}