pub mod cli {
#[cfg(feature = "cli-table")]
pub mod table {
use std::fmt;
use std::fmt::Formatter;
pub struct Table {
headers: Vec<String>,
rows: Vec<Vec<String>>,
}
impl Table {
const CELL_PADDING: usize = 2;
const ROW_OUTER_BORDERS: usize = 2;
pub fn new(headers: Vec<String>, data: Vec<Vec<String>>) -> Result<Self, &'static str> {
if data.iter().any(|row| row.len() != headers.len()) {
return Err("Rows should have same length as headers");
}
Ok(Table {
headers,
rows: data,
})
}
fn all_rows(&self) -> Vec<Vec<String>> {
let mut result = Vec::new();
result.push(self.headers.to_owned());
self.rows.iter().for_each(|row| result.push(row.to_owned()));
result
}
fn row_width(&self) -> usize {
let result = self.row_inner_width();
result + Self::ROW_OUTER_BORDERS
}
fn row_inner_width(&self) -> usize {
let mut result = 0;
for column_index in 0..self.headers.len() {
result = result + self.cell_width(column_index) + Self::CELL_PADDING;
}
result + self.row_inner_borders()
}
fn row_inner_borders(&self) -> usize {
self.headers.len() - 1
}
fn cell_width(&self, column: usize) -> usize {
self.all_rows()
.iter()
.map(|row| row.get(column).expect("column out of bounds"))
.map(|cell| cell.chars().count())
.max()
.unwrap_or(1)
}
fn format_row(&self, row_content: &Vec<String>) -> String {
let mut row = String::new();
let mut column_index = 0;
for cell_content in row_content {
if column_index == 0 {
row.push('#');
}
row.push(' ');
row.push_str(cell_content);
row.push_str(&self.filling(column_index, cell_content.chars().count()));
row.push(' ');
column_index = column_index + 1;
if column_index == row_content.len() {
row.push('#');
} else {
row.push('|');
}
}
format!("{row}\n")
}
fn filling(&self, cell_index: usize, content_len: usize) -> String {
" ".repeat(self.cell_width(cell_index) - content_len)
}
}
impl fmt::Display for Table {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
let outer_border = &format!("{}\n", &"#".repeat(self.row_width()));
let header_row_border = &format!("#{}#\n", &"=".repeat(self.row_inner_width()));
let row_border = &format!("#{}#\n", &"-".repeat(self.row_inner_width()));
let mut result = Ok(());
result = result.and(formatter.write_str(outer_border));
result = result.and(formatter.write_str(&self.format_row(&self.headers)));
result = result.and(formatter.write_str(header_row_border));
for row_index in 0..self.rows.len() {
let row = self.rows.get(row_index).expect("Programming error: wrong loop index");
result = result.and(formatter.write_str(&self.format_row(&row)));
if row_index == self.rows.len() - 1 {
result = result.and(formatter.write_str(outer_border));
} else {
result = result.and(formatter.write_str(row_border));
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn table_creation_is_successful() {
let headers = vec!["Name".to_string(), "Age".to_string()];
let data = vec![
vec!["Alice".to_string(), "25".to_string()],
vec!["Bob".to_string(), "30".to_string()],
];
let table = Table::new(headers, data).unwrap();
assert_eq!(table.headers, vec!["Name".to_string(), "Age".to_string()]);
assert_eq!(table.rows.len(), 2);
assert_eq!(table.rows[0], vec!["Alice".to_string(), "25".to_string()]);
assert_eq!(table.rows[1], vec!["Bob".to_string(), "30".to_string()]);
}
#[test]
fn table_formats_well() {
let headers = vec!["Name".to_string(), "Age".to_string()];
let data = vec![
vec!["Alice".to_string(), "25".to_string()],
vec!["Bob".to_string(), "30".to_string()],
];
let table = Table::new(headers, data).unwrap();
let display_output = format!("{}", table);
assert_eq!(display_output, "###############\n\
# Name | Age #\n\
#=============#\n\
# Alice | 25 #\n\
#-------------#\n\
# Bob | 30 #\n\
###############\n");
}
}
}
}