prettytable/
format.rs

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