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}