prettytable/
format.rs

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