1use std::io::{
4 Error,
5 Write,
6};
7
8use encode_unicode::Utf8Char;
9
10use super::utils::NEWLINE;
11
12#[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)]
14pub enum Alignment {
15 LEFT,
17 CENTER,
19 RIGHT,
21}
22
23#[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)]
25pub enum LinePosition {
26 Top,
28 Title,
31 Intern,
33 Bottom,
35}
36
37#[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)]
39pub enum ColumnPosition {
40 Left,
42 Intern,
44 Right,
46}
47
48#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
50pub struct LineSeparator {
51 line: char,
53 junc: char,
55 ljunc: char,
57 rjunc: char,
59}
60
61impl LineSeparator {
62 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 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#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
112pub struct TableFormat {
113 csep: Option<char>,
115 lborder: Option<char>,
117 rborder: Option<char>,
119 lsep: Option<LineSeparator>,
121 tsep: Option<LineSeparator>,
123 top_sep: Option<LineSeparator>,
125 bottom_sep: Option<LineSeparator>,
127 pad_left: usize,
129 pad_right: usize,
131 indent: usize,
133}
134
135impl TableFormat {
136 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 pub fn get_padding(&self) -> (usize, usize) {
154 (self.pad_left, self.pad_right)
155 }
156
157 pub fn padding(&mut self, left: usize, right: usize) {
159 self.pad_left = left;
160 self.pad_right = right;
161 }
162
163 pub fn column_separator(&mut self, separator: char) {
165 self.csep = Some(separator);
166 }
167
168 pub fn borders(&mut self, border: char) {
170 self.lborder = Some(border);
171 self.rborder = Some(border);
172 }
173
174 pub fn left_border(&mut self, border: char) {
176 self.lborder = Some(border);
177 }
178
179 pub fn right_border(&mut self, border: char) {
181 self.rborder = Some(border);
182 }
183
184 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 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 pub fn indent(&mut self, spaces: usize) {
215 self.indent = spaces;
216 }
217
218 pub fn get_indent(&self) -> usize {
220 self.indent
221 }
222
223 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 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 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 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#[derive(Default)]
281pub struct FormatBuilder {
282 format: Box<TableFormat>,
283}
284
285impl FormatBuilder {
286 pub fn new() -> FormatBuilder {
288 FormatBuilder {
289 format: Box::new(TableFormat::new()),
290 }
291 }
292
293 pub fn padding(mut self, left: usize, right: usize) -> Self {
295 self.format.padding(left, right);
296 self
297 }
298
299 pub fn column_separator(mut self, separator: char) -> Self {
301 self.format.column_separator(separator);
302 self
303 }
304
305 pub fn borders(mut self, border: char) -> Self {
307 self.format.borders(border);
308 self
309 }
310
311 pub fn left_border(mut self, border: char) -> Self {
313 self.format.left_border(border);
314 self
315 }
316
317 pub fn right_border(mut self, border: char) -> Self {
319 self.format.right_border(border);
320 self
321 }
322
323 pub fn separator(mut self, what: LinePosition, separator: LineSeparator) -> Self {
325 self.format.separator(what, separator);
326 self
327 }
328
329 pub fn separators(mut self, what: &[LinePosition], separator: LineSeparator) -> Self {
331 self.format.separators(what, separator);
332 self
333 }
334
335 pub fn indent(mut self, spaces: usize) -> Self {
337 self.format.indent(spaces);
338 self
339 }
340
341 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
355pub mod consts {
358 use super::{
359 FormatBuilder,
360 LinePosition,
361 LineSeparator,
362 TableFormat,
363 };
364 use once_cell::sync::Lazy;
365
366 static MINUS_PLUS_SEP: Lazy<LineSeparator> = Lazy::new(|| LineSeparator::new('-', '+', '+', '+'));
368 static EQU_PLUS_SEP: Lazy<LineSeparator> = Lazy::new(|| LineSeparator::new('=', '+', '+', '+'));
370
371 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 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 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 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 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 pub static FORMAT_CLEAN: Lazy<TableFormat> = Lazy::new(|| FormatBuilder::new().padding(1, 1).build());
492
493 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 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 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 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}