pub mod tbl; use terminal_size::{terminal_size, Height, Width}; pub enum Types { Paragraph(String) } pub enum Align { Left, Center, Right } pub struct Column { pub title: String, pub title_align: Align, pub align: Align } impl Column { #[allow(clippy::needless_pass_by_value)] pub fn new(heading: impl ToString) -> Self { Self { title: heading.to_string(), title_align: Align::Left, align: Align::Left } } #[must_use] pub const fn title_align(mut self, align: Align) -> Self { self.title_align = align; self } #[must_use] pub const fn align(mut self, align: Align) -> Self { self.align = align; self } } fn wordify(s: &str) -> Vec { let mut words = Vec::new(); let splt = s.split_whitespace(); for w in splt { words.push(w.to_string()); } words } pub struct PPrint { indent: u16, hang: i16, maxwidth: u16 } impl Default for PPrint { fn default() -> Self { Self::new() } } impl PPrint { #[must_use] pub fn new() -> Self { let size = terminal_size(); let mut maxwidth: u16 = 80; if let Some((Width(w), Height(_h))) = size { maxwidth = w; } Self { indent: 0, hang: 0, maxwidth } } pub fn set_indent(&mut self, indent: u16) -> &mut Self { self.indent = indent; self } /// Set a relative offset for the first line in a paragraph. pub fn set_hang(&mut self, hang: i16) -> &mut Self { self.hang = hang; self } /* pub(crate) fn set_maxwidth(&mut self, maxwidth: u16) -> &mut Self { self.maxwidth = maxwidth; self } */ /// # Panics /// Writes must be successful. #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_sign_loss)] pub fn print_words(&self, out: &mut dyn std::io::Write, words: I) where I: IntoIterator, S: AsRef { let mut firstline = true; let mut newline = true; let mut space: u16 = 0; let mut col: u16 = 0; for w in words { let w = w.as_ref(); if col + space + w.len() as u16 > self.maxwidth { out.write_all(b"\n").unwrap(); newline = true; } if newline { let mut indent: i16 = 0; indent += self.indent as i16; if firstline { indent += self.hang; } out .write_all(" ".repeat(indent as usize).as_bytes()) .unwrap(); col = indent as u16; newline = false; space = 0; firstline = false; } out .write_all(" ".repeat(space as usize).as_bytes()) .unwrap(); col += space; out.write_all(w.as_bytes()).unwrap(); col += w.len() as u16; let ch = w.chars().last().unwrap(); match ch { '.' | '?' | '!' => { space = 2; } _ => { space = 1; } } } out.write_all(b"\n").unwrap(); } pub fn print_p(&self, out: &mut dyn std::io::Write, para: &str) { let words = wordify(para); self.print_words(out, &words); } pub fn print_plist(&self, out: &mut dyn std::io::Write, parit: I) where I: IntoIterator, S: AsRef { for p in parit { self.print_p(out, p.as_ref()); } } } #[allow(clippy::similar_names)] pub fn print_table(cols: &[Column], body: &Vec>) { // Used to keep track of the maximum column width. let mut colw: Vec = cols.iter().map(|col| col.title.len()).collect(); // Iterate over body cells for row in body { for (i, col) in row.iter().enumerate() { // Ignore any body colums which are not in the column list if i == colw.len() { break; } if col.len() > colw[i] { colw[i] = col.len(); } } } // print header let mut fields = Vec::new(); for (i, col) in cols.iter().enumerate() { match col.title_align { Align::Left => { fields.push(format!("{1:<0$}", colw[i], col.title)); } Align::Center => { fields.push(format!("{1:^0$}", colw[i], col.title)); } Align::Right => { fields.push(format!("{1:>0$}", colw[i], col.title)); } } } println!("{}", fields.join(" ")); // print heading underline fields.clear(); for w in &colw { fields.push("~".repeat(*w).to_string()); } println!("{}", fields.join(" ")); for row in body { fields.clear(); for (i, cell) in row.iter().enumerate() { match cols[i].align { Align::Left => { fields.push(format!("{1:<0$}", colw[i], cell)); } Align::Center => { fields.push(format!("{1:^0$}", colw[i], cell)); } Align::Right => { fields.push(format!("{1:>0$}", colw[i], cell)); } } } println!("{}", fields.join(" ")); } } // vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :