cli_grid/
row.rs

1use crate::{
2    cell::{Cell, HAlign, VAlign, DEFAULT_BLANK_CHAR, DEFAULT_H_ALIGN, DEFAULT_V_ALIGN},
3    options::Options,
4};
5
6use std::borrow::Cow;
7
8/// Data type for creating a [`Row`] for the grid.
9///
10/// [`Row`]: struct.Row.html
11pub struct Row {
12    /// These options will be used if the equivalent is not provided
13    /// by the underlying [`Cell`] type.
14    ///
15    /// [`Cell`]: struct.Cell.html
16    pub default_options: Options,
17
18    /// Width in chars for each column of the [`Row`].
19    ///
20    /// [`Row`]: struct.Row.html
21    pub column_width: Option<usize>,
22
23    /// Number of char spaces for each padding space between row columns.
24    pub padding_size: Option<usize>,
25
26    /// Collection of cells that this [`Row`] contains.
27    ///
28    /// [`Row`]: struct.Row.html
29    pub cells: Vec<Cell>,
30}
31
32impl Row {
33    /// Creates a new [`Row`] by its cells.
34    ///
35    /// [`Row`]: struct.Row.html
36    pub fn new(cells: Vec<Cell>) -> Self {
37        Self {
38            default_options: Options {
39                col_span: None,
40                h_align: None,
41                v_align: None,
42                blank_char: None,
43            },
44            column_width: None,
45            padding_size: None,
46            cells,
47        }
48    }
49
50    /// Create a new empty row with a specific column span.
51    pub fn new_empty(col_span: usize) -> Self {
52        Row::new(vec![Cell::new_empty(col_span)])
53    }
54
55    /// Create a new row with a specified column span filled by the repeated content.
56    pub fn new_fill(content: String, col_span: usize) -> Self {
57        Row::new(vec![Cell::new_fill(content, col_span)])
58    }
59
60    /// Creates a [`RowBuilder`] initiated with cells.
61    /// To build the final [`RowBuilder`] call the [`build`] method.
62    ///
63    /// [`RowBuilder`]: struct.RowBuilder.html
64    /// [`build`]: struct.RowBuilder.html#method.build
65    pub fn builder(cells: Vec<Cell>) -> RowBuilder {
66        RowBuilder {
67            inner: Self::new(cells),
68        }
69    }
70
71    /// Formats the [`Row`] into a string.
72    ///
73    /// [`Row`]: struct.Row.html
74    pub fn render(
75        &self,
76        f: &mut std::fmt::Formatter<'_>,
77        default_options: &Options,
78        column_width: Option<usize>,
79        padding_size: Option<usize>,
80    ) -> std::fmt::Result {
81        let column_width = column_width.or(self.column_width).unwrap_or(1);
82        let padding_size = padding_size.or(self.padding_size).unwrap_or(1);
83        let mut cols_lines = self
84            .cells
85            .iter()
86            .map(|c| {
87                let mut lines = c.content.lines().map(|l| l.to_owned()).collect::<Vec<_>>();
88                if lines.is_empty() {
89                    lines.push(String::new());
90                }
91                lines
92            })
93            .collect::<Vec<_>>();
94        let max_lines = cols_lines
95            .iter()
96            .map(|col_lines| col_lines.len())
97            .max()
98            .unwrap_or(0);
99        for line_index in 0..max_lines {
100            for (col_index, col) in self.cells.iter().enumerate() {
101                let col_lines = &mut cols_lines[col_index];
102                let col_span = col
103                    .col_span
104                    .or(self.default_options.col_span)
105                    .or(default_options.col_span)
106                    .unwrap_or(1);
107                let col_width = col_span * column_width + padding_size * (col_span - 1);
108                let h_align = col
109                    .h_align
110                    .or(self.default_options.h_align)
111                    .or(default_options.h_align)
112                    .unwrap_or(DEFAULT_H_ALIGN);
113                let v_align = col
114                    .v_align
115                    .or(self.default_options.v_align)
116                    .or(default_options.v_align)
117                    .unwrap_or(DEFAULT_V_ALIGN);
118                let blank_char = col
119                    .blank_char
120                    .or(self.default_options.blank_char)
121                    .or(default_options.blank_char)
122                    .unwrap_or(DEFAULT_BLANK_CHAR);
123                if col_index != 0 {
124                    write!(f, "{s:<0$}", padding_size, s = "")?;
125                }
126                write!(
127                    f,
128                    "{}",
129                    col_line(
130                        h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
131                    )
132                )?;
133            }
134            writeln!(f)?;
135        }
136        Ok(())
137    }
138}
139
140impl std::fmt::Display for Row {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        self.render(
143            f,
144            &self.default_options,
145            self.column_width,
146            self.padding_size,
147        )
148    }
149}
150
151fn col_line(
152    h_align: HAlign,
153    v_align: VAlign,
154    col_width: usize,
155    col_lines: &mut [String],
156    max_lines: usize,
157    line_index: usize,
158    blank_char: char,
159) -> Cow<str> {
160    let index = match v_align {
161        VAlign::Top => {
162            let start_line_index = 0;
163            let end_line_index = col_lines.len() - 1;
164            if line_index >= start_line_index && line_index <= end_line_index {
165                Some(line_index)
166            } else {
167                None
168            }
169        }
170        VAlign::Bottom => {
171            let start_line_index = max_lines - col_lines.len();
172            let end_line_index = max_lines - 1;
173            if line_index >= start_line_index && line_index <= end_line_index {
174                Some(line_index - start_line_index)
175            } else {
176                None
177            }
178        }
179        VAlign::Middle => {
180            let start_line_index = (max_lines - col_lines.len()) / 2;
181            let end_line_index = start_line_index + col_lines.len() - 1;
182            if line_index >= start_line_index && line_index <= end_line_index {
183                Some(line_index - start_line_index)
184            } else {
185                None
186            }
187        }
188    };
189    if let Some(i) = index {
190        return pad(h_align, &mut col_lines[i], col_width, blank_char);
191    }
192    Cow::Owned(blank_char.to_string().repeat(col_width))
193}
194
195fn pad(h_align: HAlign, s: &mut String, width: usize, blank_char: char) -> Cow<str> {
196    let s_chars_len = s.chars().count();
197    if s_chars_len >= width {
198        let bytes_index = byte_index(s, width);
199        return s[..bytes_index].into();
200    }
201    let blanks = width - s_chars_len;
202    match h_align {
203        HAlign::Left => {
204            s.extend(std::iter::repeat(blank_char).take(blanks));
205            s.as_str().into()
206        }
207        HAlign::Right => {
208            let mut new_str = std::iter::repeat(blank_char)
209                .take(blanks)
210                .collect::<String>();
211            new_str.push_str(s);
212            new_str.into()
213        }
214        HAlign::Center => {
215            let left_blanks = blanks / 2;
216            let right_blanks = blanks - left_blanks;
217            let mut new_str = std::iter::repeat(blank_char)
218                .take(left_blanks)
219                .collect::<String>();
220            new_str.push_str(s);
221            new_str.extend(std::iter::repeat(blank_char).take(right_blanks));
222            new_str.into()
223        }
224        HAlign::Fill => {
225            let repeats = width / s_chars_len + 1;
226            let s = s.repeat(repeats);
227            let bytes_index = byte_index(&s, width);
228            s[..bytes_index].to_owned().into()
229        }
230    }
231}
232
233fn byte_index(s: &str, char_index: usize) -> usize {
234    s.char_indices()
235        .take(char_index)
236        .last()
237        .map_or(0, |(i, ch)| i + ch.len_utf8())
238}
239
240/// Builder for the [`Row`] type.
241///
242/// [`Row`]: struct.Row.html
243pub struct RowBuilder {
244    inner: Row,
245}
246
247impl RowBuilder {
248    /// Builds a [`Row`] from a [`RowBuilder`].
249    ///
250    /// [`Row`]: struct.Row.html
251    /// [`RowBuilder`]: struct.RowBuilder.html
252    pub fn build(self) -> Row {
253        self.inner
254    }
255
256    /// Sets the default column span for all the cells of the grid. If a cell specifies
257    /// a column span it will be used instead of the grids default value.
258    pub fn default_colspan(mut self, default_col_span: usize) -> Self {
259        self.inner.default_options.col_span = Some(default_col_span);
260        self
261    }
262
263    /// Sets the default horizontal alignment for all the cells of the grid. If a cell specifies
264    /// a horizontal alignment it will be used instead of the grids default value.
265    pub fn default_h_align(mut self, default_h_align: HAlign) -> Self {
266        self.inner.default_options.h_align = Some(default_h_align);
267        self
268    }
269
270    /// Sets the default vertical alignment for all the cells of the grid. If a cell specifies
271    /// a vertical alignment it will be used instead of the grids default value.
272    pub fn default_v_align(mut self, default_v_align: VAlign) -> Self {
273        self.inner.default_options.v_align = Some(default_v_align);
274        self
275    }
276
277    /// Sets the default blank char for all the cells of the grid. If a cell specifies
278    /// a blank char it will be used instead of the grids default value.
279    pub fn default_blank_char(mut self, default_blank_char: char) -> Self {
280        self.inner.default_options.blank_char = Some(default_blank_char);
281        self
282    }
283
284    /// Sets the width of each column in the [`Row`].
285    ///
286    /// [`Row`]: struct.Row.html
287    pub fn column_width(mut self, column_width: usize) -> Self {
288        self.inner.column_width = Some(column_width);
289        self
290    }
291
292    /// Sets the padding size between each column in the [`Row`].
293    ///
294    /// [`Row`]: struct.Row.html
295    pub fn padding_size(mut self, padding_size: usize) -> Self {
296        self.inner.padding_size = Some(padding_size);
297        self
298    }
299
300    /// Sets the cells collection of the [`Row`].
301    ///
302    /// [`Row`]: struct.Row.html
303    pub fn cells(mut self, cells: Vec<Cell>) -> Self {
304        self.inner.cells = cells;
305        self
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[test]
314    fn test_byte_index_ascii() {
315        let s = "abc";
316        let result = byte_index(s, 2);
317        let expected = 2;
318        assert_eq!(result, expected);
319    }
320
321    #[test]
322    fn test_byte_index_unicode1() {
323        let s = "aµc";
324        let result = byte_index(s, 2);
325        let expected = 3;
326        assert_eq!(result, expected);
327    }
328
329    #[test]
330    fn test_byte_index_unicode2() {
331        let s = "µ∆c";
332        let result = byte_index(s, 2);
333        let expected = 5;
334        assert_eq!(result, expected);
335    }
336
337    #[test]
338    fn test_pad_left_empty() {
339        let s = &mut "".into();
340        let result = pad(HAlign::Right, s, 3, '.');
341        let expected = String::from("...");
342        assert_eq!(result, expected);
343    }
344
345    #[test]
346    fn test_pad_right_empty() {
347        let s = &mut "".into();
348        let result = pad(HAlign::Left, s, 3, '.');
349        let expected = String::from("...");
350        assert_eq!(result, expected);
351    }
352
353    #[test]
354    fn test_pad_left() {
355        let s = &mut "a".into();
356        let result = pad(HAlign::Right, s, 3, '.');
357        let expected = String::from("..a");
358        assert_eq!(result, expected);
359    }
360
361    #[test]
362    fn test_pad_right() {
363        let s = &mut "a".into();
364        let result = pad(HAlign::Left, s, 3, '.');
365        let expected = String::from("a..");
366        assert_eq!(result, expected);
367    }
368
369    #[test]
370    fn test_pad_center() {
371        let s = &mut "a".into();
372        let result = pad(HAlign::Center, s, 3, '.');
373        let expected = String::from(".a.");
374        assert_eq!(result, expected);
375    }
376
377    #[test]
378    fn test_pad_fill1() {
379        let s = &mut "a".into();
380        let result = pad(HAlign::Fill, s, 3, '.');
381        let expected = String::from("aaa");
382        assert_eq!(result, expected);
383    }
384
385    #[test]
386    fn test_pad_fill2() {
387        let s = &mut "ab".into();
388        let result = pad(HAlign::Fill, s, 3, '.');
389        let expected = String::from("aba");
390        assert_eq!(result, expected);
391    }
392
393    #[test]
394    fn test_pad_left_unicode() {
395        let s = &mut "∆".into();
396        let result = pad(HAlign::Right, s, 3, '.');
397        let expected = String::from("..∆");
398        assert_eq!(result, expected);
399    }
400
401    #[test]
402    fn test_pad_right_unicode() {
403        let s = &mut "∆".into();
404        let result = pad(HAlign::Left, s, 3, '.');
405        let expected = String::from("∆..");
406        assert_eq!(result, expected);
407    }
408
409    #[test]
410    fn test_pad_center_unicode() {
411        let s = &mut "∆".into();
412        let result = pad(HAlign::Center, s, 3, '.');
413        let expected = String::from(".∆.");
414        assert_eq!(result, expected);
415    }
416
417    #[test]
418    fn test_pad_fill_unicode() {
419        let s = &mut "∆".into();
420        let result = pad(HAlign::Fill, s, 3, '.');
421        let expected = String::from("∆∆∆");
422        assert_eq!(result, expected);
423    }
424
425    #[test]
426    fn test_col_line_for_a_content_with_1_row_in_a_3_lines_column_left_top() {
427        let h_align = HAlign::Left;
428        let v_align = VAlign::Top;
429        let col_width = 3;
430        let col_lines = &mut [String::from("a")];
431        let max_lines = 3;
432        let blank_char = '.';
433
434        let line_index = 0;
435        let result = col_line(
436            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
437        );
438        let expected = "a..";
439        assert_eq!(result, expected);
440
441        let line_index = 1;
442        let result = col_line(
443            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
444        );
445        let expected = "...";
446        assert_eq!(result, expected);
447
448        let line_index = 2;
449        let result = col_line(
450            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
451        );
452        let expected = "...";
453        assert_eq!(result, expected);
454    }
455
456    #[test]
457    fn test_col_line_for_a_content_with_1_row_in_a_3_lines_column_left_middle() {
458        let h_align = HAlign::Left;
459        let v_align = VAlign::Middle;
460        let col_width = 3;
461        let col_lines = &mut [String::from("a")];
462        let max_lines = 3;
463        let blank_char = '.';
464
465        let line_index = 0;
466        let result = col_line(
467            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
468        );
469        let expected = "...";
470        assert_eq!(result, expected);
471
472        let line_index = 1;
473        let result = col_line(
474            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
475        );
476        let expected = "a..";
477        assert_eq!(result, expected);
478
479        let line_index = 2;
480        let result = col_line(
481            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
482        );
483        let expected = "...";
484        assert_eq!(result, expected);
485    }
486
487    #[test]
488    fn test_col_line_for_a_content_with_1_row_in_a_3_lines_column_left_bottom() {
489        let h_align = HAlign::Left;
490        let v_align = VAlign::Bottom;
491        let col_width = 3;
492        let col_lines = &mut [String::from("a")];
493        let max_lines = 3;
494        let blank_char = '.';
495
496        let line_index = 0;
497        let result = col_line(
498            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
499        );
500        let expected = "...";
501        assert_eq!(result, expected);
502
503        let line_index = 1;
504        let result = col_line(
505            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
506        );
507        let expected = "...";
508        assert_eq!(result, expected);
509
510        let line_index = 2;
511        let result = col_line(
512            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
513        );
514        let expected = "a..";
515        assert_eq!(result, expected);
516    }
517
518    #[test]
519    fn test_col_line_for_a_content_with_2_row_in_a_4_lines_column_left_top() {
520        let h_align = HAlign::Left;
521        let v_align = VAlign::Top;
522        let col_width = 3;
523        let col_lines = &mut [String::from("a"), String::from("b")];
524        let max_lines = 4;
525        let blank_char = '.';
526
527        let line_index = 0;
528        let result = col_line(
529            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
530        );
531        let expected = "a..";
532        assert_eq!(result, expected);
533
534        let line_index = 1;
535        let result = col_line(
536            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
537        );
538        let expected = "b..";
539        assert_eq!(result, expected);
540
541        let line_index = 2;
542        let result = col_line(
543            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
544        );
545        let expected = "...";
546        assert_eq!(result, expected);
547
548        let line_index = 3;
549        let result = col_line(
550            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
551        );
552        let expected = "...";
553        assert_eq!(result, expected);
554    }
555
556    #[test]
557    fn test_col_line_for_a_content_with_2_row_in_a_4_lines_column_left_middle() {
558        let h_align = HAlign::Left;
559        let v_align = VAlign::Middle;
560        let col_width = 3;
561        let col_lines = &mut [String::from("a"), String::from("b")];
562        let max_lines = 4;
563        let blank_char = '.';
564
565        let line_index = 0;
566        let result = col_line(
567            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
568        );
569        let expected = "...";
570        assert_eq!(result, expected);
571
572        let line_index = 1;
573        let result = col_line(
574            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
575        );
576        let expected = "a..";
577        assert_eq!(result, expected);
578
579        let line_index = 2;
580        let result = col_line(
581            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
582        );
583        let expected = "b..";
584        assert_eq!(result, expected);
585
586        let line_index = 3;
587        let result = col_line(
588            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
589        );
590        let expected = "...";
591        assert_eq!(result, expected);
592    }
593
594    #[test]
595    fn test_col_line_for_a_content_with_2_row_in_a_4_lines_column_left_bottom() {
596        let h_align = HAlign::Left;
597        let v_align = VAlign::Bottom;
598        let col_width = 3;
599        let col_lines = &mut [String::from("a"), String::from("b")];
600        let max_lines = 4;
601        let blank_char = '.';
602
603        let line_index = 0;
604        let result = col_line(
605            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
606        );
607        let expected = "...";
608        assert_eq!(result, expected);
609
610        let line_index = 1;
611        let result = col_line(
612            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
613        );
614        let expected = "...";
615        assert_eq!(result, expected);
616
617        let line_index = 2;
618        let result = col_line(
619            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
620        );
621        let expected = "a..";
622        assert_eq!(result, expected);
623
624        let line_index = 3;
625        let result = col_line(
626            h_align, v_align, col_width, col_lines, max_lines, line_index, blank_char,
627        );
628        let expected = "b..";
629        assert_eq!(result, expected);
630    }
631}