prettytable/
format.rs

1//! Define table formatting utilities
2
3use std::io::{
4  Error,
5  Write,
6};
7
8use encode_unicode::Utf8Char;
9
10use super::utils::NEWLINE;
11
12/// Alignment for cell's content
13#[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)]
14pub enum Alignment {
15  /// Align left
16  LEFT,
17  /// Align in the center
18  CENTER,
19  /// Align right
20  RIGHT,
21}
22
23/// Position of a line separator in a table
24#[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)]
25pub enum LinePosition {
26  /// Table's border on top
27  Top,
28  /// Line separator between the titles row,
29  /// and the first data row
30  Title,
31  /// Line separator between data rows
32  Intern,
33  /// Bottom table's border
34  Bottom,
35}
36
37/// Position of a column separator in a row
38#[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)]
39pub enum ColumnPosition {
40  /// Left table's border
41  Left,
42  /// Internal column separators
43  Intern,
44  /// Rigth table's border
45  Right,
46}
47
48/// Contains the character used for printing a line separator
49#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
50pub struct LineSeparator {
51  /// Line separator
52  line: char,
53  /// Internal junction separator
54  junc: char,
55  /// Left junction separator
56  ljunc: char,
57  /// Right junction separator
58  rjunc: char,
59}
60
61impl LineSeparator {
62  /// Create a new line separator instance where `line` is the character used to separate 2 lines
63  /// and `junc` is the one used for junctions between columns and lines
64  pub fn new(line: char, junc: char, ljunc: char, rjunc: char) -> LineSeparator {
65    LineSeparator {
66      line,
67      junc,
68      ljunc,
69      rjunc,
70    }
71  }
72
73  /// Print a full line separator to `out`. `col_width` is a slice containing the width of each column.
74  /// Returns the number of printed lines
75  fn print<T: Write + ?Sized>(
76    &self,
77    out: &mut T,
78    col_width: &[usize],
79    padding: (usize, usize),
80    colsep: bool,
81    lborder: bool,
82    rborder: bool,
83  ) -> Result<usize, Error> {
84    if lborder {
85      out.write_all(Utf8Char::from(self.ljunc).as_bytes())?;
86    }
87    let mut iter = col_width.iter().peekable();
88    while let Some(width) = iter.next() {
89      for _ in 0..width + padding.0 + padding.1 {
90        out.write_all(Utf8Char::from(self.line).as_bytes())?;
91      }
92      if colsep && iter.peek().is_some() {
93        out.write_all(Utf8Char::from(self.junc).as_bytes())?;
94      }
95    }
96    if rborder {
97      out.write_all(Utf8Char::from(self.rjunc).as_bytes())?;
98    }
99    out.write_all(NEWLINE)?;
100    Ok(1)
101  }
102}
103
104impl Default for LineSeparator {
105  fn default() -> Self {
106    LineSeparator::new('-', '+', '+', '+')
107  }
108}
109
110/// Contains the table formatting rules
111#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
112pub struct TableFormat {
113  /// Optional column separator character
114  csep: Option<char>,
115  /// Optional left border character
116  lborder: Option<char>,
117  /// Optional right border character
118  rborder: Option<char>,
119  /// Optional internal line separator
120  lsep: Option<LineSeparator>,
121  /// Optional title line separator
122  tsep: Option<LineSeparator>,
123  /// Optional top line separator
124  top_sep: Option<LineSeparator>,
125  /// Optional bottom line separator
126  bottom_sep: Option<LineSeparator>,
127  /// Left padding
128  pad_left: usize,
129  /// Right padding
130  pad_right: usize,
131  /// Global indentation when rendering the table
132  indent: usize,
133}
134
135impl TableFormat {
136  /// Create a new empty TableFormat.
137  pub fn new() -> TableFormat {
138    TableFormat {
139      csep: None,
140      lborder: None,
141      rborder: None,
142      lsep: None,
143      tsep: None,
144      top_sep: None,
145      bottom_sep: None,
146      pad_left: 0,
147      pad_right: 0,
148      indent: 0,
149    }
150  }
151
152  /// Return a tuple with left and right padding
153  pub fn get_padding(&self) -> (usize, usize) {
154    (self.pad_left, self.pad_right)
155  }
156
157  /// Set left and right padding
158  pub fn padding(&mut self, left: usize, right: usize) {
159    self.pad_left = left;
160    self.pad_right = right;
161  }
162
163  /// Set the character used for internal column separation
164  pub fn column_separator(&mut self, separator: char) {
165    self.csep = Some(separator);
166  }
167
168  /// Set the character used for table borders
169  pub fn borders(&mut self, border: char) {
170    self.lborder = Some(border);
171    self.rborder = Some(border);
172  }
173
174  /// Set the character used for left table border
175  pub fn left_border(&mut self, border: char) {
176    self.lborder = Some(border);
177  }
178
179  /// Set the character used for right table border
180  pub fn right_border(&mut self, border: char) {
181    self.rborder = Some(border);
182  }
183
184  /// Set a line separator
185  pub fn separator(&mut self, what: LinePosition, separator: LineSeparator) {
186    *match what {
187      LinePosition::Top => &mut self.top_sep,
188      LinePosition::Bottom => &mut self.bottom_sep,
189      LinePosition::Title => &mut self.tsep,
190      LinePosition::Intern => &mut self.lsep,
191    } = Some(separator);
192  }
193
194  /// Set format for multiple kind of line separator
195  pub fn separators(&mut self, what: &[LinePosition], separator: LineSeparator) {
196    for pos in what {
197      self.separator(*pos, separator);
198    }
199  }
200
201  fn get_sep_for_line(&self, pos: LinePosition) -> &Option<LineSeparator> {
202    match pos {
203      LinePosition::Intern => &self.lsep,
204      LinePosition::Top => &self.top_sep,
205      LinePosition::Bottom => &self.bottom_sep,
206      LinePosition::Title => match &self.tsep {
207        s @ &Some(_) => s,
208        &None => &self.lsep,
209      },
210    }
211  }
212
213  /// Set global indentation in spaces used when rendering a table
214  pub fn indent(&mut self, spaces: usize) {
215    self.indent = spaces;
216  }
217
218  /// Get global indentation in spaces used when rendering a table
219  pub fn get_indent(&self) -> usize {
220    self.indent
221  }
222
223  /// Print a full line separator to `out`. `col_width` is a slice containing the width of each column.
224  /// Returns the number of printed lines
225  // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
226  pub(crate) fn print_line_separator<T: Write + ?Sized>(
227    &self,
228    out: &mut T,
229    col_width: &[usize],
230    pos: LinePosition,
231  ) -> Result<usize, Error> {
232    match *self.get_sep_for_line(pos) {
233      Some(ref l) => {
234        //TODO: Wrap this into dedicated function one day
235        out.write_all(&vec![b' '; self.get_indent()])?;
236        l.print(
237          out,
238          col_width,
239          self.get_padding(),
240          self.csep.is_some(),
241          self.lborder.is_some(),
242          self.rborder.is_some(),
243        )
244      },
245      None => Ok(0),
246    }
247  }
248
249  /// Returns the character used to separate columns.
250  /// `pos` specify if the separator is left/right final or internal to the table
251  pub fn get_column_separator(&self, pos: ColumnPosition) -> Option<char> {
252    match pos {
253      ColumnPosition::Left => self.lborder,
254      ColumnPosition::Intern => self.csep,
255      ColumnPosition::Right => self.rborder,
256    }
257  }
258
259  /// Print a column separator or a table border
260  // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
261  pub(crate) fn print_column_separator<T: Write + ?Sized>(
262    &self,
263    out: &mut T,
264    pos: ColumnPosition,
265  ) -> Result<(), Error> {
266    match self.get_column_separator(pos) {
267      Some(s) => out.write_all(Utf8Char::from(s).as_bytes()),
268      None => Ok(()),
269    }
270  }
271}
272
273impl Default for TableFormat {
274  fn default() -> Self {
275    TableFormat::new()
276  }
277}
278
279/// A builder to create a `TableFormat`
280#[derive(Default)]
281pub struct FormatBuilder {
282  format: Box<TableFormat>,
283}
284
285impl FormatBuilder {
286  /// Creates a new builder
287  pub fn new() -> FormatBuilder {
288    FormatBuilder {
289      format: Box::new(TableFormat::new()),
290    }
291  }
292
293  /// Set left and right padding
294  pub fn padding(mut self, left: usize, right: usize) -> Self {
295    self.format.padding(left, right);
296    self
297  }
298
299  /// Set the character used for internal column separation
300  pub fn column_separator(mut self, separator: char) -> Self {
301    self.format.column_separator(separator);
302    self
303  }
304
305  /// Set the character used for table borders
306  pub fn borders(mut self, border: char) -> Self {
307    self.format.borders(border);
308    self
309  }
310
311  /// Set the character used for left table border
312  pub fn left_border(mut self, border: char) -> Self {
313    self.format.left_border(border);
314    self
315  }
316
317  /// Set the character used for right table border
318  pub fn right_border(mut self, border: char) -> Self {
319    self.format.right_border(border);
320    self
321  }
322
323  /// Set a line separator format
324  pub fn separator(mut self, what: LinePosition, separator: LineSeparator) -> Self {
325    self.format.separator(what, separator);
326    self
327  }
328
329  /// Set separator format for multiple kind of line separators
330  pub fn separators(mut self, what: &[LinePosition], separator: LineSeparator) -> Self {
331    self.format.separators(what, separator);
332    self
333  }
334
335  /// Set global indentation in spaces used when rendering a table
336  pub fn indent(mut self, spaces: usize) -> Self {
337    self.format.indent(spaces);
338    self
339  }
340
341  /// Return the generated `TableFormat`
342  pub fn build(&self) -> TableFormat {
343    *self.format
344  }
345}
346
347impl From<TableFormat> for FormatBuilder {
348  fn from(fmt: TableFormat) -> Self {
349    FormatBuilder {
350      format: Box::new(fmt),
351    }
352  }
353}
354
355/// Predifined formats. Those constants are lazily evaluated when
356/// the corresponding struct is dereferenced
357pub mod consts {
358  use super::{
359    FormatBuilder,
360    LinePosition,
361    LineSeparator,
362    TableFormat,
363  };
364  use once_cell::sync::Lazy;
365
366  /// A line separator made of `-` and `+`
367  static MINUS_PLUS_SEP: Lazy<LineSeparator> = Lazy::new(|| LineSeparator::new('-', '+', '+', '+'));
368  /// A line separator made of `=` and `+`
369  static EQU_PLUS_SEP: Lazy<LineSeparator> = Lazy::new(|| LineSeparator::new('=', '+', '+', '+'));
370
371  /// Default table format
372  ///
373  /// # Example
374  /// ```text
375  /// +----+----+
376  /// | T1 | T2 |
377  /// +====+====+
378  /// | a  | b  |
379  /// +----+----+
380  /// | d  | c  |
381  /// +----+----+
382  /// ```
383  pub static FORMAT_DEFAULT: Lazy<TableFormat> = Lazy::new(|| {
384    FormatBuilder::new()
385      .column_separator('|')
386      .borders('|')
387      .separator(LinePosition::Intern, *MINUS_PLUS_SEP)
388      .separator(LinePosition::Title, *EQU_PLUS_SEP)
389      .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
390      .separator(LinePosition::Top, *MINUS_PLUS_SEP)
391      .padding(1, 1)
392      .build()
393  });
394
395  /// Similar to `FORMAT_DEFAULT` but without special separator after title line
396  ///
397  /// # Example
398  /// ```text
399  /// +----+----+
400  /// | T1 | T2 |
401  /// +----+----+
402  /// | a  | b  |
403  /// +----+----+
404  /// | c  | d  |
405  /// +----+----+
406  /// ```
407  pub static FORMAT_NO_TITLE: Lazy<TableFormat> = Lazy::new(|| {
408    FormatBuilder::new()
409      .column_separator('|')
410      .borders('|')
411      .separator(LinePosition::Intern, *MINUS_PLUS_SEP)
412      .separator(LinePosition::Title, *MINUS_PLUS_SEP)
413      .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
414      .separator(LinePosition::Top, *MINUS_PLUS_SEP)
415      .padding(1, 1)
416      .build()
417  });
418
419  /// With no line separator, but with title separator
420  ///
421  /// # Example
422  /// ```text
423  /// +----+----+
424  /// | T1 | T2 |
425  /// +----+----+
426  /// | a  | b  |
427  /// | c  | d  |
428  /// +----+----+
429  /// ```
430  pub static FORMAT_NO_LINESEP_WITH_TITLE: Lazy<TableFormat> = Lazy::new(|| {
431    FormatBuilder::new()
432      .column_separator('|')
433      .borders('|')
434      .separator(LinePosition::Title, *MINUS_PLUS_SEP)
435      .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
436      .separator(LinePosition::Top, *MINUS_PLUS_SEP)
437      .padding(1, 1)
438      .build()
439  });
440
441  /// With no line or title separator
442  ///
443  /// # Example
444  /// ```text
445  /// +----+----+
446  /// | T1 | T2 |
447  /// | a  | b  |
448  /// | c  | d  |
449  /// +----+----+
450  /// ```
451  pub static FORMAT_NO_LINESEP: Lazy<TableFormat> = Lazy::new(|| {
452    FormatBuilder::new()
453      .column_separator('|')
454      .borders('|')
455      .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
456      .separator(LinePosition::Top, *MINUS_PLUS_SEP)
457      .padding(1, 1)
458      .build()
459  });
460
461  /// No column separator
462  ///
463  /// # Example
464  /// ```text
465  /// --------
466  ///  T1  T2
467  /// ========
468  ///  a   b
469  /// --------
470  ///  d   c
471  /// --------
472  /// ```
473  pub static FORMAT_NO_COLSEP: Lazy<TableFormat> = Lazy::new(|| {
474    FormatBuilder::new()
475      .separator(LinePosition::Intern, *MINUS_PLUS_SEP)
476      .separator(LinePosition::Title, *EQU_PLUS_SEP)
477      .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
478      .separator(LinePosition::Top, *MINUS_PLUS_SEP)
479      .padding(1, 1)
480      .build()
481  });
482
483  /// Format for printing a table without any separators (only alignment)
484  ///
485  /// # Example
486  /// ```text
487  ///  T1  T2
488  ///  a   b
489  ///  d   c
490  /// ```
491  pub static FORMAT_CLEAN: Lazy<TableFormat> = Lazy::new(|| FormatBuilder::new().padding(1, 1).build());
492
493  /// Format for a table with only external borders and title separator
494  ///
495  /// # Example
496  /// ```text
497  /// +--------+
498  /// | T1  T2 |
499  /// +========+
500  /// | a   b  |
501  /// | c   d  |
502  /// +--------+
503  /// ```
504  pub static FORMAT_BORDERS_ONLY: Lazy<TableFormat> = Lazy::new(|| {
505    FormatBuilder::new()
506      .padding(1, 1)
507      .separator(LinePosition::Title, *EQU_PLUS_SEP)
508      .separator(LinePosition::Bottom, *MINUS_PLUS_SEP)
509      .separator(LinePosition::Top, *MINUS_PLUS_SEP)
510      .borders('|')
511      .build()
512  });
513
514  /// A table with no external border
515  ///
516  /// # Example
517  /// ```text
518  ///  T1 | T2
519  /// ====+====
520  ///  a  | b
521  /// ----+----
522  ///  c  | d
523  /// ```
524  pub static FORMAT_NO_BORDER: Lazy<TableFormat> = Lazy::new(|| {
525    FormatBuilder::new()
526      .padding(1, 1)
527      .separator(LinePosition::Intern, *MINUS_PLUS_SEP)
528      .separator(LinePosition::Title, *EQU_PLUS_SEP)
529      .column_separator('|')
530      .build()
531  });
532
533  /// A table with no external border and no line separation
534  ///
535  /// # Example
536  /// ```text
537  ///  T1 | T2
538  /// ----+----
539  ///  a  | b
540  ///  c  | d
541  /// ```
542  pub static FORMAT_NO_BORDER_LINE_SEPARATOR: Lazy<TableFormat> = Lazy::new(|| {
543    FormatBuilder::new()
544      .padding(1, 1)
545      .separator(LinePosition::Title, *MINUS_PLUS_SEP)
546      .column_separator('|')
547      .build()
548  });
549
550  /// A table with borders and delimiters made with box characters
551  ///
552  /// # Example
553  /// ```text
554  /// ┌────┬────┬────┐
555  /// │ t1 │ t2 │ t3 │
556  /// ├────┼────┼────┤
557  /// │ 1  │ 1  │ 1  │
558  /// ├────┼────┼────┤
559  /// │ 2  │ 2  │ 2  │
560  /// └────┴────┴────┘
561  /// ```
562  pub static FORMAT_BOX_CHARS: Lazy<TableFormat> = Lazy::new(|| {
563    FormatBuilder::new()
564      .column_separator('│')
565      .borders('│')
566      .separators(&[LinePosition::Top], LineSeparator::new('─', '┬', '┌', '┐'))
567      .separators(&[LinePosition::Intern], LineSeparator::new('─', '┼', '├', '┤'))
568      .separators(&[LinePosition::Bottom], LineSeparator::new('─', '┴', '└', '┘'))
569      .padding(1, 1)
570      .build()
571  });
572}