qpprint/
lib.rs

1pub mod tbl;
2
3use terminal_size::{terminal_size, Height, Width};
4
5pub use yansi::{Color, Painted};
6
7pub enum Types {
8  Paragraph(String)
9}
10
11#[derive(Copy, Clone, Debug)]
12pub enum Align {
13  Left,
14  Center,
15  Right
16}
17
18pub struct Column {
19  pub title: String,
20  pub title_align: Align,
21  pub align: Align
22}
23
24impl Column {
25  #[allow(clippy::needless_pass_by_value)]
26  pub fn new(heading: impl ToString) -> Self {
27    Self {
28      title: heading.to_string(),
29      title_align: Align::Left,
30      align: Align::Left
31    }
32  }
33
34  #[must_use]
35  pub const fn title_align(mut self, align: Align) -> Self {
36    self.title_align = align;
37    self
38  }
39
40  #[must_use]
41  pub const fn align(mut self, align: Align) -> Self {
42    self.align = align;
43    self
44  }
45}
46
47
48fn wordify(s: &str) -> Vec<String> {
49  let mut words = Vec::new();
50  let splt = s.split_whitespace();
51
52  for w in splt {
53    words.push(w.to_string());
54  }
55  words
56}
57
58pub struct PPrint {
59  indent: u16,
60  hang: i16,
61  maxwidth: u16
62}
63
64impl Default for PPrint {
65  fn default() -> Self {
66    Self::new()
67  }
68}
69
70impl PPrint {
71  #[must_use]
72  pub fn new() -> Self {
73    let size = terminal_size();
74    let mut maxwidth: u16 = 80;
75    if let Some((Width(w), Height(_h))) = size {
76      maxwidth = w;
77    }
78
79    Self {
80      indent: 0,
81      hang: 0,
82      maxwidth
83    }
84  }
85
86  pub const fn set_indent(&mut self, indent: u16) -> &mut Self {
87    self.indent = indent;
88    self
89  }
90
91  /// Set a relative offset for the first line in a paragraph.
92  pub const fn set_hang(&mut self, hang: i16) -> &mut Self {
93    self.hang = hang;
94    self
95  }
96
97  /*
98  pub(crate) fn set_maxwidth(&mut self, maxwidth: u16) -> &mut Self {
99    self.maxwidth = maxwidth;
100    self
101  }
102  */
103
104  /// # Panics
105  /// Writes must be successful.
106  #[allow(clippy::cast_possible_truncation)]
107  #[allow(clippy::cast_possible_wrap)]
108  #[allow(clippy::cast_sign_loss)]
109  pub fn print_words<I, S>(&self, out: &mut dyn std::io::Write, words: I)
110  where
111    I: IntoIterator<Item = S>,
112    S: AsRef<str>
113  {
114    let mut firstline = true;
115    let mut newline = true;
116    let mut space: u16 = 0;
117    let mut col: u16 = 0;
118
119    for w in words {
120      let w = w.as_ref();
121      if col + space + w.len() as u16 > self.maxwidth {
122        out.write_all(b"\n").unwrap();
123        newline = true;
124      }
125
126      if newline {
127        let mut indent: i16 = 0;
128
129        indent += self.indent as i16;
130        if firstline {
131          indent += self.hang;
132        }
133
134        out
135          .write_all(" ".repeat(indent as usize).as_bytes())
136          .unwrap();
137        col = indent as u16;
138
139        newline = false;
140        space = 0;
141        firstline = false;
142      }
143
144      out
145        .write_all(" ".repeat(space as usize).as_bytes())
146        .unwrap();
147      col += space;
148
149      out.write_all(w.as_bytes()).unwrap();
150      col += w.len() as u16;
151
152      let ch = w.chars().last().unwrap();
153      match ch {
154        '.' | '?' | '!' => {
155          space = 2;
156        }
157        _ => {
158          space = 1;
159        }
160      }
161    }
162    out.write_all(b"\n").unwrap();
163  }
164
165  pub fn print_p(&self, out: &mut dyn std::io::Write, para: &str) {
166    let words = wordify(para);
167    self.print_words(out, &words);
168  }
169
170  pub fn print_plist<I, S>(&self, out: &mut dyn std::io::Write, parit: I)
171  where
172    I: IntoIterator<Item = S>,
173    S: AsRef<str>
174  {
175    for p in parit {
176      self.print_p(out, p.as_ref());
177    }
178  }
179}
180
181
182#[allow(clippy::similar_names)]
183pub fn print_table(cols: &[Column], body: &Vec<Vec<String>>) {
184  // Used to keep track of the maximum column width.
185  let mut colw: Vec<usize> = cols.iter().map(|col| col.title.len()).collect();
186
187  // Iterate over body cells
188  for row in body {
189    for (i, col) in row.iter().enumerate() {
190      // Ignore any body colums which are not in the column list
191      if i == colw.len() {
192        break;
193      }
194      if col.len() > colw[i] {
195        colw[i] = col.len();
196      }
197    }
198  }
199
200  // print header
201  let mut fields = Vec::new();
202  for (i, col) in cols.iter().enumerate() {
203    match col.title_align {
204      Align::Left => {
205        fields.push(format!("{1:<0$}", colw[i], col.title));
206      }
207      Align::Center => {
208        fields.push(format!("{1:^0$}", colw[i], col.title));
209      }
210      Align::Right => {
211        fields.push(format!("{1:>0$}", colw[i], col.title));
212      }
213    }
214  }
215  println!("{}", fields.join("  "));
216
217  // print heading underline
218  fields.clear();
219  for w in &colw {
220    fields.push("~".repeat(*w).to_string());
221  }
222  println!("{}", fields.join("  "));
223
224  for row in body {
225    fields.clear();
226
227    for (i, cell) in row.iter().enumerate() {
228      match cols[i].align {
229        Align::Left => {
230          fields.push(format!("{1:<0$}", colw[i], cell));
231        }
232        Align::Center => {
233          fields.push(format!("{1:^0$}", colw[i], cell));
234        }
235        Align::Right => {
236          fields.push(format!("{1:>0$}", colw[i], cell));
237        }
238      }
239    }
240    println!("{}", fields.join("  "));
241  }
242}
243
244// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :