1 | //! The module contains a [`CompactGrid`] structure, |
2 | //! which is a relatively strict grid. |
3 | |
4 | use core::{ |
5 | borrow::Borrow, |
6 | fmt::{self, Display, Write}, |
7 | }; |
8 | |
9 | use crate::{ |
10 | ansi::{ANSIFmt, ANSIStr}, |
11 | colors::{Colors, NoColors}, |
12 | config::{AlignmentHorizontal, Borders, HorizontalLine, Indent, Sides}, |
13 | dimension::Dimension, |
14 | records::{IntoRecords, Records}, |
15 | util::string::string_width, |
16 | }; |
17 | |
18 | use crate::config::compact::CompactConfig; |
19 | |
20 | /// Grid provides a set of methods for building a text-based table. |
21 | #[derive (Debug, Clone)] |
22 | pub struct CompactGrid<R, D, G, C> { |
23 | records: R, |
24 | config: G, |
25 | dimension: D, |
26 | colors: C, |
27 | } |
28 | |
29 | impl<R, D, G> CompactGrid<R, D, G, NoColors> { |
30 | /// The new method creates a grid instance with default styles. |
31 | pub fn new(records: R, dimension: D, config: G) -> Self { |
32 | CompactGrid { |
33 | records, |
34 | config, |
35 | dimension, |
36 | colors: NoColors, |
37 | } |
38 | } |
39 | } |
40 | |
41 | impl<R, D, G, C> CompactGrid<R, D, G, C> { |
42 | /// Sets colors map. |
43 | pub fn with_colors<Colors>(self, colors: Colors) -> CompactGrid<R, D, G, Colors> { |
44 | CompactGrid { |
45 | records: self.records, |
46 | config: self.config, |
47 | dimension: self.dimension, |
48 | colors, |
49 | } |
50 | } |
51 | |
52 | /// Builds a table. |
53 | pub fn build<F>(self, mut f: F) -> fmt::Result |
54 | where |
55 | R: Records, |
56 | <R::Iter as IntoRecords>::Cell: AsRef<str>, |
57 | D: Dimension, |
58 | C: Colors, |
59 | G: Borrow<CompactConfig>, |
60 | F: Write, |
61 | { |
62 | if self.records.count_columns() == 0 { |
63 | return Ok(()); |
64 | } |
65 | |
66 | let config = self.config.borrow(); |
67 | print_grid(&mut f, self.records, config, &self.dimension, &self.colors) |
68 | } |
69 | |
70 | /// Builds a table into string. |
71 | /// |
72 | /// Notice that it consumes self. |
73 | #[cfg (feature = "std" )] |
74 | #[allow (clippy::inherent_to_string)] |
75 | pub fn to_string(self) -> String |
76 | where |
77 | R: Records, |
78 | <R::Iter as IntoRecords>::Cell: AsRef<str>, |
79 | D: Dimension, |
80 | G: Borrow<CompactConfig>, |
81 | C: Colors, |
82 | { |
83 | let mut buf = String::new(); |
84 | self.build(&mut buf).expect("It's guaranteed to never happen otherwise it's considered an stdlib error or impl error" ); |
85 | buf |
86 | } |
87 | } |
88 | |
89 | impl<R, D, G, C> Display for CompactGrid<R, D, G, C> |
90 | where |
91 | for<'a> &'a R: Records, |
92 | for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef<str>, |
93 | D: Dimension, |
94 | G: Borrow<CompactConfig>, |
95 | C: Colors, |
96 | { |
97 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
98 | let records: &R = &self.records; |
99 | let config: &CompactConfig = self.config.borrow(); |
100 | |
101 | print_grid(f, records, cfg:config, &self.dimension, &self.colors) |
102 | } |
103 | } |
104 | |
105 | fn print_grid<F, R, D, C>( |
106 | f: &mut F, |
107 | records: R, |
108 | cfg: &CompactConfig, |
109 | dims: &D, |
110 | colors: &C, |
111 | ) -> fmt::Result |
112 | where |
113 | F: Write, |
114 | R: Records, |
115 | <R::Iter as IntoRecords>::Cell: AsRef<str>, |
116 | D: Dimension, |
117 | C: Colors, |
118 | { |
119 | let count_columns = records.count_columns(); |
120 | let count_rows = records.hint_count_rows(); |
121 | |
122 | if count_columns == 0 || matches!(count_rows, Some(0)) { |
123 | return Ok(()); |
124 | } |
125 | |
126 | let mut records = records.iter_rows().into_iter(); |
127 | let records_first = match records.next() { |
128 | Some(row) => row, |
129 | None => return Ok(()), |
130 | }; |
131 | |
132 | let wtotal = total_width(cfg, dims, count_columns); |
133 | |
134 | let borders_chars = cfg.get_borders(); |
135 | let borders_colors = cfg.get_borders_color(); |
136 | |
137 | let horizontal_borders = create_horizontal(borders_chars); |
138 | let horizontal_colors = create_horizontal_colors(borders_colors); |
139 | |
140 | let vertical_borders = create_vertical_borders(borders_chars, borders_colors); |
141 | |
142 | let margin = create_margin(cfg); |
143 | let padding = create_padding(cfg); |
144 | let alignment = cfg.get_alignment_horizontal(); |
145 | |
146 | let mut new_line = false; |
147 | |
148 | if margin.top.space.size > 0 { |
149 | let width_total = wtotal + margin.left.space.size + margin.right.space.size; |
150 | let indent = ColoredIndent::new(width_total, margin.top.space.fill, margin.top.color); |
151 | print_indent_lines(f, indent)?; |
152 | new_line = true; |
153 | } |
154 | |
155 | if borders_chars.has_top() { |
156 | if new_line { |
157 | f.write_char(' \n' )? |
158 | } |
159 | |
160 | let borders = create_horizontal_top(borders_chars); |
161 | let borders_colors = create_horizontal_top_colors(borders_colors); |
162 | print_horizontal_line(f, dims, &borders, &borders_colors, &margin, count_columns)?; |
163 | |
164 | new_line = true; |
165 | } |
166 | |
167 | if borders_chars.has_horizontal() { |
168 | if new_line { |
169 | f.write_char(' \n' )?; |
170 | } |
171 | |
172 | let cells = records_first.into_iter(); |
173 | print_grid_row( |
174 | f, |
175 | cells, |
176 | count_columns, |
177 | dims, |
178 | colors, |
179 | &margin, |
180 | &padding, |
181 | &vertical_borders, |
182 | alignment, |
183 | 0, |
184 | )?; |
185 | |
186 | for (row, cells) in records.enumerate() { |
187 | f.write_char(' \n' )?; |
188 | |
189 | print_horizontal_line( |
190 | f, |
191 | dims, |
192 | &horizontal_borders, |
193 | &horizontal_colors, |
194 | &margin, |
195 | count_columns, |
196 | )?; |
197 | |
198 | f.write_char(' \n' )?; |
199 | |
200 | let cells = cells.into_iter(); |
201 | print_grid_row( |
202 | f, |
203 | cells, |
204 | count_columns, |
205 | dims, |
206 | colors, |
207 | &margin, |
208 | &padding, |
209 | &vertical_borders, |
210 | alignment, |
211 | row + 1, |
212 | )?; |
213 | } |
214 | } else { |
215 | if new_line { |
216 | f.write_char(' \n' )?; |
217 | } |
218 | |
219 | print_grid_row( |
220 | f, |
221 | records_first.into_iter(), |
222 | count_columns, |
223 | dims, |
224 | colors, |
225 | &margin, |
226 | &padding, |
227 | &vertical_borders, |
228 | alignment, |
229 | 0, |
230 | )?; |
231 | |
232 | for (row, cells) in records.enumerate() { |
233 | f.write_char(' \n' )?; |
234 | |
235 | print_grid_row( |
236 | f, |
237 | cells.into_iter(), |
238 | count_columns, |
239 | dims, |
240 | colors, |
241 | &margin, |
242 | &padding, |
243 | &vertical_borders, |
244 | alignment, |
245 | row + 1, |
246 | )?; |
247 | } |
248 | } |
249 | |
250 | if borders_chars.has_bottom() { |
251 | f.write_char(' \n' )?; |
252 | |
253 | let borders = create_horizontal_bottom(borders_chars); |
254 | let colors = create_horizontal_bottom_colors(borders_colors); |
255 | print_horizontal_line(f, dims, &borders, &colors, &margin, count_columns)?; |
256 | } |
257 | |
258 | if cfg.get_margin().bottom.size > 0 { |
259 | f.write_char(' \n' )?; |
260 | |
261 | let width_total = wtotal + margin.left.space.size + margin.right.space.size; |
262 | let indent = ColoredIndent::new(width_total, margin.bottom.space.fill, margin.bottom.color); |
263 | print_indent_lines(f, indent)?; |
264 | } |
265 | |
266 | Ok(()) |
267 | } |
268 | |
269 | fn create_margin(cfg: &CompactConfig) -> Sides<ColoredIndent> { |
270 | let margin: &Sides = cfg.get_margin(); |
271 | let margin_color: &Sides> = cfg.get_margin_color(); |
272 | Sides::new( |
273 | left:ColoredIndent::from_indent(margin.left, margin_color.left), |
274 | right:ColoredIndent::from_indent(margin.right, margin_color.right), |
275 | top:ColoredIndent::from_indent(margin.top, margin_color.top), |
276 | bottom:ColoredIndent::from_indent(indent:margin.bottom, color:margin_color.bottom), |
277 | ) |
278 | } |
279 | |
280 | fn create_vertical_borders( |
281 | borders: &Borders<char>, |
282 | colors: &Borders<ANSIStr<'static>>, |
283 | ) -> HorizontalLine<ColoredIndent> { |
284 | let intersect: Option = bordersOption |
285 | .vertical |
286 | .map(|c: char| ColoredIndent::new(width:0, c, color:colors.vertical)); |
287 | let left: Option = borders.left.map(|c: char| ColoredIndent::new(width:0, c, color:colors.left)); |
288 | let right: Option = bordersOption |
289 | .right |
290 | .map(|c: char| ColoredIndent::new(width:0, c, color:colors.right)); |
291 | |
292 | HorizontalLine::new(main:None, intersection:intersect, left, right) |
293 | } |
294 | |
295 | fn print_horizontal_line<F, D>( |
296 | f: &mut F, |
297 | dims: &D, |
298 | borders: &HorizontalLine<char>, |
299 | borders_colors: &HorizontalLine<ANSIStr<'static>>, |
300 | margin: &Sides<ColoredIndent>, |
301 | count_columns: usize, |
302 | ) -> fmt::Result |
303 | where |
304 | F: fmt::Write, |
305 | D: Dimension, |
306 | { |
307 | let is_not_colored: bool = borders_colors.is_empty(); |
308 | |
309 | print_indent(f, indent:margin.left)?; |
310 | |
311 | if is_not_colored { |
312 | print_split_line(f, dims, chars:borders, count_columns)?; |
313 | } else { |
314 | print_split_line_colored(f, dimension:dims, borders, borders_colors, count_columns)?; |
315 | } |
316 | |
317 | print_indent(f, indent:margin.right)?; |
318 | |
319 | Ok(()) |
320 | } |
321 | |
322 | #[allow (clippy::too_many_arguments)] |
323 | fn print_grid_row<F, I, T, C, D>( |
324 | f: &mut F, |
325 | data: I, |
326 | size: usize, |
327 | dims: &D, |
328 | colors: &C, |
329 | margin: &Sides<ColoredIndent>, |
330 | padding: &Sides<ColoredIndent>, |
331 | borders: &HorizontalLine<ColoredIndent>, |
332 | alignemnt: AlignmentHorizontal, |
333 | row: usize, |
334 | ) -> fmt::Result |
335 | where |
336 | F: Write, |
337 | I: Iterator<Item = T>, |
338 | T: AsRef<str>, |
339 | C: Colors, |
340 | D: Dimension, |
341 | { |
342 | for _ in 0..padding.top.space.size { |
343 | print_indent(f, indent:margin.left)?; |
344 | print_columns_empty_colored(f, dims, borders, padding.top.color, count_columns:size)?; |
345 | print_indent(f, indent:margin.right)?; |
346 | |
347 | f.write_char(' \n' )?; |
348 | } |
349 | |
350 | print_indent(f, indent:margin.left)?; |
351 | print_row_columns_one_line(f, data, dims, colors, borders, padding, alignement:alignemnt, row)?; |
352 | print_indent(f, indent:margin.right)?; |
353 | |
354 | for _ in 0..padding.top.space.size { |
355 | f.write_char(' \n' )?; |
356 | |
357 | print_indent(f, indent:margin.left)?; |
358 | print_columns_empty_colored(f, dims, borders, padding.bottom.color, count_columns:size)?; |
359 | print_indent(f, indent:margin.right)?; |
360 | } |
361 | |
362 | Ok(()) |
363 | } |
364 | |
365 | fn create_padding(cfg: &CompactConfig) -> Sides<ColoredIndent> { |
366 | let pad: &Sides = cfg.get_padding(); |
367 | let colors: &Sides> = cfg.get_padding_color(); |
368 | Sides::new( |
369 | left:ColoredIndent::new(pad.left.size, pad.left.fill, create_color(colors.left)), |
370 | right:ColoredIndent::new(pad.right.size, pad.right.fill, create_color(colors.right)), |
371 | top:ColoredIndent::new(pad.top.size, pad.top.fill, create_color(colors.top)), |
372 | bottom:ColoredIndent::new( |
373 | width:pad.bottom.size, |
374 | c:pad.bottom.fill, |
375 | create_color(colors.bottom), |
376 | ), |
377 | ) |
378 | } |
379 | |
380 | fn create_horizontal(b: &Borders<char>) -> HorizontalLine<char> { |
381 | HorizontalLine::new(main:b.horizontal, b.intersection, b.left, b.right) |
382 | } |
383 | |
384 | fn create_horizontal_top(b: &Borders<char>) -> HorizontalLine<char> { |
385 | HorizontalLine::new(main:b.top, b.top_intersection, b.top_left, b.top_right) |
386 | } |
387 | |
388 | fn create_horizontal_bottom(b: &Borders<char>) -> HorizontalLine<char> { |
389 | HorizontalLine::new( |
390 | main:b.bottom, |
391 | b.bottom_intersection, |
392 | b.bottom_left, |
393 | b.bottom_right, |
394 | ) |
395 | } |
396 | |
397 | fn create_horizontal_colors(b: &Borders<ANSIStr<'static>>) -> HorizontalLine<ANSIStr<'static>> { |
398 | HorizontalLine::new(main:b.horizontal, b.intersection, b.left, b.right) |
399 | } |
400 | |
401 | fn create_horizontal_top_colors(b: &Borders<ANSIStr<'static>>) -> HorizontalLine<ANSIStr<'static>> { |
402 | HorizontalLine::new(main:b.top, b.top_intersection, b.top_left, b.top_right) |
403 | } |
404 | |
405 | fn create_horizontal_bottom_colors( |
406 | b: &Borders<ANSIStr<'static>>, |
407 | ) -> HorizontalLine<ANSIStr<'static>> { |
408 | HorizontalLine::new( |
409 | main:b.bottom, |
410 | b.bottom_intersection, |
411 | b.bottom_left, |
412 | b.bottom_right, |
413 | ) |
414 | } |
415 | |
416 | fn total_width<D>(cfg: &CompactConfig, dims: &D, count_columns: usize) -> usize |
417 | where |
418 | D: Dimension, |
419 | { |
420 | let content_width: usize = total_columns_width(count_columns, dims); |
421 | let count_verticals: usize = count_verticals(cfg, count_columns); |
422 | |
423 | content_width + count_verticals |
424 | } |
425 | |
426 | fn total_columns_width<D>(count_columns: usize, dims: &D) -> usize |
427 | where |
428 | D: Dimension, |
429 | { |
430 | (0..count_columns).map(|i: usize| dims.get_width(column:i)).sum::<usize>() |
431 | } |
432 | |
433 | fn count_verticals(cfg: &CompactConfig, count_columns: usize) -> usize { |
434 | assert!(count_columns > 0); |
435 | |
436 | let count_verticals: usize = count_columns - 1; |
437 | let borders: &Borders = cfg.get_borders(); |
438 | borders.has_vertical() as usize * count_verticals |
439 | + borders.has_left() as usize |
440 | + borders.has_right() as usize |
441 | } |
442 | |
443 | #[allow (clippy::too_many_arguments)] |
444 | fn print_row_columns_one_line<F, I, T, D, C>( |
445 | f: &mut F, |
446 | mut data: I, |
447 | dims: &D, |
448 | colors: &C, |
449 | borders: &HorizontalLine<ColoredIndent>, |
450 | padding: &Sides<ColoredIndent>, |
451 | alignement: AlignmentHorizontal, |
452 | row: usize, |
453 | ) -> fmt::Result |
454 | where |
455 | F: Write, |
456 | I: Iterator<Item = T>, |
457 | T: AsRef<str>, |
458 | D: Dimension, |
459 | C: Colors, |
460 | { |
461 | if let Some(indent) = borders.left { |
462 | print_char(f, indent.space.fill, indent.color)?; |
463 | } |
464 | |
465 | let text = data |
466 | .next() |
467 | .expect("we check in the beggining that size must be at least 1 column" ); |
468 | let width = dims.get_width(0); |
469 | let color = colors.get_color((row, 0)); |
470 | |
471 | let text = text.as_ref(); |
472 | let text = text.lines().next().unwrap_or("" ); |
473 | print_cell(f, text, color, padding, alignement, width)?; |
474 | |
475 | match borders.intersection { |
476 | Some(indent) => { |
477 | for (col, text) in data.enumerate() { |
478 | let col = col + 1; |
479 | |
480 | let width = dims.get_width(col); |
481 | let color = colors.get_color((row, col)); |
482 | let text = text.as_ref(); |
483 | let text = text.lines().next().unwrap_or("" ); |
484 | |
485 | print_char(f, indent.space.fill, indent.color)?; |
486 | print_cell(f, text, color, padding, alignement, width)?; |
487 | } |
488 | } |
489 | None => { |
490 | for (col, text) in data.enumerate() { |
491 | let col = col + 1; |
492 | |
493 | let width = dims.get_width(col); |
494 | let color = colors.get_color((row, col)); |
495 | let text = text.as_ref(); |
496 | let text = text.lines().next().unwrap_or("" ); |
497 | |
498 | print_cell(f, text, color, padding, alignement, width)?; |
499 | } |
500 | } |
501 | } |
502 | |
503 | if let Some(indent) = borders.right { |
504 | print_char(f, indent.space.fill, indent.color)?; |
505 | } |
506 | |
507 | Ok(()) |
508 | } |
509 | |
510 | fn print_columns_empty_colored<F, D>( |
511 | f: &mut F, |
512 | dims: &D, |
513 | borders: &HorizontalLine<ColoredIndent>, |
514 | color: Option<ANSIStr<'static>>, |
515 | count_columns: usize, |
516 | ) -> fmt::Result |
517 | where |
518 | F: Write, |
519 | D: Dimension, |
520 | { |
521 | if let Some(indent) = borders.left { |
522 | print_char(f, indent.space.fill, indent.color)?; |
523 | } |
524 | |
525 | let width = dims.get_width(0); |
526 | print_indent(f, ColoredIndent::new(width, ' ' , color))?; |
527 | |
528 | match borders.intersection { |
529 | Some(indent) => { |
530 | for column in 1..count_columns { |
531 | let width = dims.get_width(column); |
532 | |
533 | print_char(f, indent.space.fill, indent.color)?; |
534 | print_indent(f, ColoredIndent::new(width, ' ' , color))?; |
535 | } |
536 | } |
537 | None => { |
538 | for column in 1..count_columns { |
539 | let width = dims.get_width(column); |
540 | print_indent(f, ColoredIndent::new(width, ' ' , color))?; |
541 | } |
542 | } |
543 | } |
544 | |
545 | if let Some(indent) = borders.right { |
546 | print_char(f, indent.space.fill, indent.color)?; |
547 | } |
548 | |
549 | Ok(()) |
550 | } |
551 | |
552 | fn print_cell<F, C>( |
553 | f: &mut F, |
554 | text: &str, |
555 | color: Option<C>, |
556 | padding: &Sides<ColoredIndent>, |
557 | alignment: AlignmentHorizontal, |
558 | width: usize, |
559 | ) -> fmt::Result |
560 | where |
561 | F: Write, |
562 | C: ANSIFmt, |
563 | { |
564 | let available: usize = width - (padding.left.space.size + padding.right.space.size); |
565 | |
566 | let text_width: usize = string_width(text); |
567 | let (left: usize, right: usize) = if available > text_width { |
568 | calculate_indent(alignment, text_width, available) |
569 | } else { |
570 | (0, 0) |
571 | }; |
572 | |
573 | print_indent(f, indent:padding.left)?; |
574 | |
575 | repeat_char(f, c:' ' , n:left)?; |
576 | print_text(f, text, color)?; |
577 | repeat_char(f, c:' ' , n:right)?; |
578 | |
579 | print_indent(f, indent:padding.right)?; |
580 | |
581 | Ok(()) |
582 | } |
583 | |
584 | fn print_split_line_colored<F, D>( |
585 | f: &mut F, |
586 | dimension: &D, |
587 | borders: &HorizontalLine<char>, |
588 | borders_colors: &HorizontalLine<ANSIStr<'static>>, |
589 | count_columns: usize, |
590 | ) -> fmt::Result |
591 | where |
592 | F: Write, |
593 | D: Dimension, |
594 | { |
595 | let mut used_color = ANSIStr::default(); |
596 | let chars_main = borders.main.unwrap_or(' ' ); |
597 | |
598 | if let Some(c) = borders.left { |
599 | if let Some(color) = &borders_colors.right { |
600 | prepare_coloring(f, color, &mut used_color)?; |
601 | } |
602 | |
603 | f.write_char(c)?; |
604 | } |
605 | |
606 | let width = dimension.get_width(0); |
607 | if width > 0 { |
608 | if let Some(color) = borders_colors.main { |
609 | prepare_coloring(f, &color, &mut used_color)?; |
610 | } |
611 | |
612 | repeat_char(f, chars_main, width)?; |
613 | } |
614 | |
615 | for col in 1..count_columns { |
616 | if let Some(c) = borders.intersection { |
617 | if let Some(color) = borders_colors.intersection { |
618 | prepare_coloring(f, &color, &mut used_color)?; |
619 | } |
620 | |
621 | f.write_char(c)?; |
622 | } |
623 | |
624 | let width = dimension.get_width(col); |
625 | if width > 0 { |
626 | if let Some(color) = borders_colors.main { |
627 | prepare_coloring(f, &color, &mut used_color)?; |
628 | } |
629 | |
630 | repeat_char(f, chars_main, width)?; |
631 | } |
632 | } |
633 | |
634 | if let Some(c) = borders.right { |
635 | if let Some(color) = &borders_colors.right { |
636 | prepare_coloring(f, color, &mut used_color)?; |
637 | } |
638 | |
639 | f.write_char(c)?; |
640 | } |
641 | |
642 | used_color.fmt_ansi_suffix(f)?; |
643 | |
644 | Ok(()) |
645 | } |
646 | |
647 | fn print_split_line<F, D>( |
648 | f: &mut F, |
649 | dims: &D, |
650 | chars: &HorizontalLine<char>, |
651 | count_columns: usize, |
652 | ) -> fmt::Result |
653 | where |
654 | F: Write, |
655 | D: Dimension, |
656 | { |
657 | let chars_main = chars.main.unwrap_or(' ' ); |
658 | |
659 | if let Some(c) = chars.left { |
660 | f.write_char(c)?; |
661 | } |
662 | |
663 | let width = dims.get_width(0); |
664 | if width > 0 { |
665 | repeat_char(f, chars_main, width)?; |
666 | } |
667 | |
668 | for col in 1..count_columns { |
669 | if let Some(c) = chars.intersection { |
670 | f.write_char(c)?; |
671 | } |
672 | |
673 | let width = dims.get_width(col); |
674 | if width > 0 { |
675 | repeat_char(f, chars_main, width)?; |
676 | } |
677 | } |
678 | |
679 | if let Some(c) = chars.right { |
680 | f.write_char(c)?; |
681 | } |
682 | |
683 | Ok(()) |
684 | } |
685 | |
686 | fn print_text<F, C>(f: &mut F, text: &str, color: Option<C>) -> fmt::Result |
687 | where |
688 | F: Write, |
689 | C: ANSIFmt, |
690 | { |
691 | match color { |
692 | Some(color: C) => { |
693 | color.fmt_ansi_prefix(f)?; |
694 | f.write_str(text)?; |
695 | color.fmt_ansi_suffix(f)?; |
696 | } |
697 | None => { |
698 | f.write_str(text)?; |
699 | } |
700 | }; |
701 | |
702 | Ok(()) |
703 | } |
704 | |
705 | fn prepare_coloring<F>( |
706 | f: &mut F, |
707 | clr: &ANSIStr<'static>, |
708 | used: &mut ANSIStr<'static>, |
709 | ) -> fmt::Result |
710 | where |
711 | F: Write, |
712 | { |
713 | if *used != *clr { |
714 | used.fmt_ansi_suffix(f)?; |
715 | clr.fmt_ansi_prefix(f)?; |
716 | *used = *clr; |
717 | } |
718 | |
719 | Ok(()) |
720 | } |
721 | |
722 | fn calculate_indent( |
723 | alignment: AlignmentHorizontal, |
724 | text_width: usize, |
725 | available: usize, |
726 | ) -> (usize, usize) { |
727 | let diff: usize = available - text_width; |
728 | match alignment { |
729 | AlignmentHorizontal::Left => (0, diff), |
730 | AlignmentHorizontal::Right => (diff, 0), |
731 | AlignmentHorizontal::Center => { |
732 | let left: usize = diff / 2; |
733 | let rest: usize = diff - left; |
734 | (left, rest) |
735 | } |
736 | } |
737 | } |
738 | |
739 | fn repeat_char<F>(f: &mut F, c: char, n: usize) -> fmt::Result |
740 | where |
741 | F: Write, |
742 | { |
743 | for _ in 0..n { |
744 | f.write_char(c)?; |
745 | } |
746 | |
747 | Ok(()) |
748 | } |
749 | |
750 | // todo: replace Option<StaticColor> to StaticColor and check performance |
751 | fn print_char<F>(f: &mut F, c: char, color: Option<ANSIStr<'static>>) -> fmt::Result |
752 | where |
753 | F: Write, |
754 | { |
755 | match color { |
756 | Some(color: ANSIStr<'static>) => { |
757 | color.fmt_ansi_prefix(f)?; |
758 | f.write_char(c)?; |
759 | color.fmt_ansi_suffix(f) |
760 | } |
761 | None => f.write_char(c), |
762 | } |
763 | } |
764 | |
765 | fn print_indent_lines<F>(f: &mut F, indent: ColoredIndent) -> fmt::Result |
766 | where |
767 | F: Write, |
768 | { |
769 | print_indent(f, indent)?; |
770 | f.write_char(' \n' )?; |
771 | |
772 | for _ in 1..indent.space.size { |
773 | f.write_char(' \n' )?; |
774 | print_indent(f, indent)?; |
775 | } |
776 | |
777 | Ok(()) |
778 | } |
779 | |
780 | fn print_indent<F>(f: &mut F, indent: ColoredIndent) -> fmt::Result |
781 | where |
782 | F: Write, |
783 | { |
784 | match indent.color { |
785 | Some(color: ANSIStr<'static>) => { |
786 | color.fmt_ansi_prefix(f)?; |
787 | repeat_char(f, c:indent.space.fill, n:indent.space.size)?; |
788 | color.fmt_ansi_suffix(f)?; |
789 | } |
790 | None => { |
791 | repeat_char(f, c:indent.space.fill, n:indent.space.size)?; |
792 | } |
793 | } |
794 | |
795 | Ok(()) |
796 | } |
797 | |
798 | #[derive (Debug, Clone, Copy)] |
799 | struct ColoredIndent { |
800 | space: Indent, |
801 | color: Option<ANSIStr<'static>>, |
802 | } |
803 | |
804 | impl ColoredIndent { |
805 | fn new(width: usize, c: char, color: Option<ANSIStr<'static>>) -> Self { |
806 | Self { |
807 | space: Indent::new(size:width, fill:c), |
808 | color, |
809 | } |
810 | } |
811 | |
812 | fn from_indent(indent: Indent, color: ANSIStr<'static>) -> Self { |
813 | Self { |
814 | space: indent, |
815 | color: create_color(color), |
816 | } |
817 | } |
818 | } |
819 | |
820 | fn create_color(color: ANSIStr<'static>) -> Option<ANSIStr<'static>> { |
821 | if color.is_empty() { |
822 | None |
823 | } else { |
824 | Some(color) |
825 | } |
826 | } |
827 | |