text_grid/
grid_builder.rs

1use self::HorizontalAlignment::*;
2use crate::cell::*;
3use crate::Cells;
4use crate::CellsFormatter;
5use crate::CellsSchema;
6use crate::CellsWrite;
7use crate::DefaultCellsSchema;
8use derive_ex::derive_ex;
9use std::borrow::Borrow;
10use std::cmp::*;
11use std::collections::HashMap;
12use std::fmt::*;
13use std::ops::Deref;
14use unicode_width::UnicodeWidthStr;
15
16struct GridLayout {
17    depth: usize,
18    depth_max: usize,
19    styles: Vec<ColumnStyle>,
20}
21impl GridLayout {
22    pub fn from_schema<T: ?Sized>(schema: &dyn CellsSchema<Source = T>) -> Self {
23        let mut this = GridLayout::new();
24        schema.fmt(&mut CellsFormatter::new(&mut this, None));
25        this.styles.pop();
26        this
27    }
28    fn new() -> Self {
29        Self {
30            depth: 0,
31            depth_max: 0,
32            styles: Vec::new(),
33        }
34    }
35    fn set_column_end_style(&mut self) {
36        if let Some(last) = self.styles.last_mut() {
37            last.column_end = true;
38        }
39    }
40}
41impl CellsWrite for GridLayout {
42    fn content(&mut self, _cell: Option<&dyn RawCell>, stretch: bool) {
43        self.styles.push(ColumnStyle {
44            column_end: false,
45            stretch,
46        });
47    }
48    fn content_start(&mut self, _cell: &dyn RawCell) {}
49    fn content_end(&mut self, _cell: &dyn RawCell) {}
50    fn column_start(&mut self, _header: &dyn RawCell) {
51        self.set_column_end_style();
52        self.depth += 1;
53        self.depth_max = max(self.depth_max, self.depth);
54    }
55
56    fn column_end(&mut self, _header: &dyn RawCell) {
57        self.depth -= 1;
58        self.set_column_end_style()
59    }
60}
61
62struct HeaderWriter<'a, 'b> {
63    b: &'a mut RowBuilder<'b>,
64    depth: usize,
65    target: usize,
66    column: usize,
67    column_last: usize,
68}
69impl<'a, 'b> HeaderWriter<'a, 'b> {
70    fn new(b: &'a mut RowBuilder<'b>, target: usize) -> Self {
71        Self {
72            b,
73            depth: 0,
74            target,
75            column: 0,
76            column_last: 0,
77        }
78    }
79
80    fn push_cell(&mut self, cell: impl RawCell) {
81        let colspan = self.column - self.column_last;
82        self.b.push_with_colspan(cell, colspan);
83        self.column_last = self.column;
84    }
85}
86impl CellsWrite for HeaderWriter<'_, '_> {
87    fn content(&mut self, _cell: Option<&dyn RawCell>, _stretch: bool) {
88        self.column += 1;
89    }
90    fn content_start(&mut self, _cell: &dyn RawCell) {}
91    fn content_end(&mut self, _cell: &dyn RawCell) {}
92    fn column_start(&mut self, _header: &dyn RawCell) {
93        if self.depth <= self.target {
94            self.push_cell(Cell::empty());
95        }
96        self.depth += 1;
97    }
98    fn column_end(&mut self, header: &dyn RawCell) {
99        self.depth -= 1;
100        if self.depth == self.target {
101            let style = CellStyle {
102                align_h: Some(HorizontalAlignment::Center),
103            };
104            let header = Cell::new(header).with_base_style(style);
105            self.push_cell(header);
106        }
107    }
108}
109impl Drop for HeaderWriter<'_, '_> {
110    fn drop(&mut self) {
111        self.push_cell("");
112    }
113}
114struct BodyWriter<'a, 'b> {
115    b: &'a mut RowBuilder<'b>,
116    colspan: Option<usize>,
117}
118
119impl<'a, 'b> BodyWriter<'a, 'b> {
120    fn new(b: &'a mut RowBuilder<'b>) -> Self {
121        Self { b, colspan: None }
122    }
123}
124
125impl CellsWrite for BodyWriter<'_, '_> {
126    fn content(&mut self, cell: Option<&dyn RawCell>, _stretch: bool) {
127        if let Some(colspan) = &mut self.colspan {
128            *colspan += 1;
129        } else {
130            self.b.push(cell);
131        }
132    }
133    fn content_start(&mut self, _cell: &dyn RawCell) {
134        assert!(self.colspan.is_none());
135        self.colspan = Some(0);
136    }
137    fn content_end(&mut self, cell: &dyn RawCell) {
138        let colspan = self.colspan.take().unwrap();
139        self.b.push_with_colspan(cell, colspan);
140    }
141
142    fn column_start(&mut self, _header: &dyn RawCell) {}
143    fn column_end(&mut self, _header: &dyn RawCell) {}
144}
145
146/// A builder used to create plain-text table.
147///
148/// # Examples
149/// ```rust
150/// use text_grid::*;
151/// let mut g = GridBuilder::new();
152/// g.push(|b| {
153///     b.push(cell("name").right());
154///     b.push("type");
155///     b.push("value");
156/// });
157/// g.push_separator();
158/// g.push(|b| {
159///     b.push(cell(String::from("X")).right());
160///     b.push("A");
161///     b.push(10);
162/// });
163/// g.push(|b| {
164///     b.push(cell("Y").right());
165///     b.push_with_colspan(cell("BBB").center(), 2);
166/// });
167/// assert_eq!(format!("\n{g}"), r#"
168///  name | type | value |
169/// ------|------|-------|
170///     X | A    |    10 |
171///     Y |     BBB      |
172/// "#);
173/// ```
174#[derive_ex(Default)]
175#[default(Self::new())]
176pub struct GridBuilder {
177    s: String,
178    cells: Vec<CellEntry>,
179    rows: Vec<RowEntry>,
180    columns: usize,
181    pub column_styles: Vec<ColumnStyle>,
182}
183
184struct CellEntry {
185    s_idx: usize,
186    width: usize,
187    colspan: usize,
188    style: CellStyle,
189}
190struct RowEntry {
191    cells_idx: usize,
192    has_separator: bool,
193}
194
195impl GridBuilder {
196    /// Create a new `GridBuilder`.
197    pub fn new() -> Self {
198        GridBuilder {
199            s: String::new(),
200            cells: Vec::new(),
201            rows: Vec::new(),
202            columns: 0,
203            column_styles: Vec::new(),
204        }
205    }
206
207    pub fn from_iter_with_schema<T>(
208        source: impl IntoIterator<Item = impl Borrow<T>>,
209        schema: impl CellsSchema<Source = T>,
210    ) -> Self {
211        let mut this = Self::new();
212        this.extend_header_with_schema(&schema);
213        this.extend_body_with_schema(source, &schema);
214        this
215    }
216
217    /// Append a row to the bottom of the grid.
218    pub fn push(&mut self, f: impl FnOnce(&mut RowBuilder)) {
219        let cells_idx = self.cells.len();
220        f(&mut RowBuilder {
221            grid: self,
222            cells_idx,
223        })
224    }
225
226    /// Append a row separator to the bottom of the grid.
227    pub fn push_separator(&mut self) {
228        if let Some(row) = self.rows.last_mut() {
229            row.has_separator = true;
230        }
231    }
232
233    pub fn extend_header<T: ?Sized + Cells>(&mut self) {
234        self.extend_header_with_schema::<T>(&DefaultCellsSchema::default());
235    }
236
237    pub fn extend_header_with_schema<T: ?Sized>(&mut self, schema: impl CellsSchema<Source = T>) {
238        let layout = GridLayout::from_schema(&schema);
239        self.column_styles = layout.styles;
240        for target in 0..layout.depth_max {
241            self.push(|b| {
242                schema.fmt(&mut CellsFormatter::new(
243                    &mut HeaderWriter::new(b, target),
244                    None,
245                ))
246            });
247            self.push_separator();
248        }
249    }
250
251    pub fn push_body(&mut self, source: &impl Cells) {
252        self.push_body_with_schema(source, &DefaultCellsSchema::default());
253    }
254    pub fn push_body_with_schema<T: ?Sized>(
255        &mut self,
256        source: &T,
257        schema: impl CellsSchema<Source = T>,
258    ) {
259        self.push(|b| {
260            b.extend_with_schema(source, &schema);
261        });
262    }
263    pub fn extend_body(&mut self, source: impl IntoIterator<Item = impl Cells>) {
264        self.extend_body_with_schema(source, &DefaultCellsSchema::default());
265    }
266    pub fn extend_body_with_schema<T>(
267        &mut self,
268        source: impl IntoIterator<Item = impl Borrow<T>>,
269        schema: impl CellsSchema<Source = T>,
270    ) {
271        for source in source {
272            self.push(|b| {
273                b.extend_with_schema(source.borrow(), &schema);
274            });
275        }
276    }
277
278    fn push_cell<S: RawCell>(&mut self, cell: S, colspan: usize) {
279        let s_idx = self.s.len();
280        cell.fmt(&mut self.s);
281        self.cells.push(CellEntry {
282            s_idx,
283            width: self.s[s_idx..].width(),
284            colspan,
285            style: cell.style().or(cell.style_for_body()),
286        });
287    }
288    fn get_width(&self, widths: &[usize], column: usize, colspan: usize) -> usize {
289        assert!(colspan >= 1);
290        let mut result = widths[column];
291        for i in 1..colspan {
292            if self.has_border(column + i) {
293                result += 3;
294            }
295            result += widths[column + i];
296        }
297        result
298    }
299    fn has_border(&self, n: usize) -> bool {
300        if n == 0 {
301            false
302        } else if n >= self.columns {
303            true
304        } else {
305            self.column_style(n - 1).column_end
306        }
307    }
308    fn has_left_padding(&self, n: usize) -> bool {
309        if n == 0 {
310            true
311        } else {
312            self.has_border(n)
313        }
314    }
315    fn has_right_padding(&self, n: usize) -> bool {
316        if n == self.columns {
317            true
318        } else {
319            self.has_border(n + 1)
320        }
321    }
322
323    fn column_style(&self, column: usize) -> &ColumnStyle {
324        self.column_styles
325            .get(column)
326            .unwrap_or(&ColumnStyle::DEFAULT)
327    }
328    fn stretch_count(&self, column: usize, colspan: usize) -> usize {
329        let mut count = 0;
330        for i in 0..colspan {
331            if self.column_style(column + i).stretch {
332                count += 1;
333            }
334        }
335        count
336    }
337
338    fn get_widths(&self) -> Vec<usize> {
339        #[derive(PartialEq, Eq, Hash)]
340        struct ColRange {
341            colspan: usize,
342            column: usize,
343        }
344
345        #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
346        struct Block {
347            stretch: usize,
348            colspan: usize,
349            column: usize,
350            width: usize,
351        }
352
353        let mut widths = vec![0; self.columns];
354        let mut blocks = HashMap::new();
355        for row in self.rows() {
356            for c in row {
357                let e = if c.colspan == 1 {
358                    &mut widths[c.column]
359                } else {
360                    let key = ColRange {
361                        colspan: c.colspan,
362                        column: c.column,
363                    };
364                    blocks.entry(key).or_insert(0)
365                };
366                *e = max(*e, c.width);
367            }
368        }
369        let mut blocks: Vec<_> = blocks
370            .into_iter()
371            .map(|c| Block {
372                stretch: self.stretch_count(c.0.column, c.0.colspan),
373                colspan: c.0.colspan,
374                column: c.0.column,
375                width: c.1,
376            })
377            .collect();
378        blocks.sort();
379
380        let mut expand_cols = Vec::new();
381        for b in blocks {
382            let mut width_sum = self.get_width(&widths, b.column, b.colspan);
383            let start = if b.stretch == 0 {
384                b.column
385            } else {
386                (b.column..b.column + b.colspan)
387                    .find(|&column| self.column_style(column).stretch)
388                    .unwrap()
389            };
390
391            while width_sum < b.width {
392                expand_cols.clear();
393                expand_cols.push(start);
394                let mut min_width = widths[start];
395                let mut next_width = usize::max_value();
396                #[allow(clippy::needless_range_loop)]
397                for column in start + 1..b.column + b.colspan {
398                    if b.stretch == 0 || self.column_style(column).stretch {
399                        let width = widths[column];
400                        if width < min_width {
401                            expand_cols.clear();
402                            next_width = min_width;
403                            min_width = width;
404                        }
405                        if width == min_width {
406                            expand_cols.push(column);
407                        }
408                    }
409                }
410                for i in 0..expand_cols.len() {
411                    let count = expand_cols.len() - i;
412                    let expand_width_all = b.width - width_sum;
413                    let expand_width = (expand_width_all + count - 1) / count;
414                    let expand_width = min(expand_width, next_width - min_width);
415                    width_sum += expand_width;
416                    widths[expand_cols[i]] += expand_width;
417                }
418            }
419        }
420        widths
421    }
422    fn row(&self, row: usize) -> Option<Cursor> {
423        if row < self.rows.len() {
424            Some(Cursor {
425                grid: self,
426                column: 0,
427                idx: self.cells_idx(row),
428                end: self.cells_idx(row + 1),
429            })
430        } else {
431            None
432        }
433    }
434    fn rows(&self) -> impl Iterator<Item = Cursor> {
435        (0..self.rows.len()).map(|row| self.row(row).unwrap())
436    }
437
438    fn cells_idx(&self, row: usize) -> usize {
439        if let Some(row) = self.rows.get(row) {
440            row.cells_idx
441        } else {
442            self.cells.len()
443        }
444    }
445    fn s_idx(&self, cells_idx: usize) -> usize {
446        if let Some(cell) = self.cells.get(cells_idx) {
447            cell.s_idx
448        } else {
449            self.s.len()
450        }
451    }
452}
453
454impl Display for GridBuilder {
455    fn fmt(&self, f: &mut Formatter) -> Result {
456        let widths = self.get_widths();
457        for row in 0..self.rows.len() {
458            if self.has_border(0) {
459                write!(f, "|")?;
460            }
461            for c in self.row(row).unwrap() {
462                let width = self.get_width(&widths, c.column, c.colspan);
463                if self.has_left_padding(c.column) {
464                    write!(f, " ")?;
465                }
466                let p = width - c.width;
467                match c.style.align_h.unwrap_or(Left) {
468                    Left => write!(f, "{0}{1:<p$}", c.s, "", p = p),
469                    Right => write!(f, "{1:<p$}{0}", c.s, "", p = p),
470                    Center => {
471                        let lp = p / 2;
472                        let rp = p - lp;
473                        write!(f, "{1:<lp$}{0}{1:<rp$}", c.s, "", lp = lp, rp = rp)
474                    }
475                }?;
476                if self.has_right_padding(c.column + c.colspan - 1) {
477                    write!(f, " ")?;
478                }
479                if self.has_border(c.column + c.colspan) {
480                    write!(f, "|")?;
481                }
482            }
483            writeln!(f)?;
484            if self.rows[row].has_separator {
485                let mut cs = [self.row(row), self.row(row + 1)];
486                for (column, _) in widths.iter().enumerate() {
487                    if self.has_left_padding(column) {
488                        write!(f, "-")?;
489                    }
490                    write!(f, "{:-<f$}", "", f = widths[column])?;
491                    if self.has_right_padding(column) {
492                        write!(f, "-")?;
493                    }
494                    for c in cs.iter_mut().flatten() {
495                        while c.column <= column && c.next().is_some() {}
496                    }
497                    if self.has_border(column + 1) {
498                        if cs.iter().flatten().all(|x| x.column == column + 1) {
499                            write!(f, "|")?;
500                        } else {
501                            write!(f, "-")?;
502                        }
503                    }
504                }
505                writeln!(f)?;
506            }
507        }
508        Ok(())
509    }
510}
511impl Debug for GridBuilder {
512    fn fmt(&self, f: &mut Formatter) -> Result {
513        Display::fmt(self, f)
514    }
515}
516impl<T: Cells> FromIterator<T> for GridBuilder {
517    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
518        let mut b = Self::new();
519        b.extend_header::<T>();
520        b.extend_body(iter);
521        b
522    }
523}
524
525/// A builder used to create row of [`GridBuilder`].
526///
527/// This structure is created by [`GridBuilder::push`].
528pub struct RowBuilder<'a> {
529    grid: &'a mut GridBuilder,
530    cells_idx: usize,
531}
532
533impl RowBuilder<'_> {
534    /// Append a cell to the right of row.
535    pub fn push(&mut self, cell: impl RawCell) {
536        self.grid.push_cell(cell, 1);
537    }
538
539    /// Append a multi-column cell to the right of row.
540    ///
541    /// - `cell` : Contents of cell to be appended.
542    /// - `colspan` : Number of columns used by the cell to be appended.
543    ///
544    /// if `colspan == 0`, this method will do nothing.
545    pub fn push_with_colspan(&mut self, cell: impl RawCell, colspan: usize) {
546        if colspan != 0 {
547            self.grid.push_cell(cell, colspan);
548        }
549    }
550
551    pub fn extend<T: ?Sized + Cells>(&mut self, source: &T) {
552        T::fmt(&mut CellsFormatter::new(
553            &mut BodyWriter::new(self),
554            Some(source),
555        ))
556    }
557    pub fn extend_with_schema<T: ?Sized>(
558        &mut self,
559        source: &T,
560        schema: impl CellsSchema<Source = T>,
561    ) {
562        schema.fmt(&mut CellsFormatter::new(
563            &mut BodyWriter::new(self),
564            Some(source),
565        ))
566    }
567}
568impl Drop for RowBuilder<'_> {
569    fn drop(&mut self) {
570        let mut columns = 0;
571        for cell in &self.grid.cells[self.cells_idx..] {
572            columns += cell.colspan;
573        }
574        self.grid.columns = max(self.grid.columns, columns);
575        self.grid.rows.push(RowEntry {
576            cells_idx: self.cells_idx,
577            has_separator: false,
578        });
579    }
580}
581
582struct Cursor<'a> {
583    grid: &'a GridBuilder,
584    column: usize,
585    idx: usize,
586    end: usize,
587}
588impl<'a> Iterator for Cursor<'a> {
589    type Item = CellRef<'a>;
590
591    fn next(&mut self) -> Option<Self::Item> {
592        if self.idx == self.end {
593            None
594        } else {
595            let g = self.grid;
596            let r = CellRef {
597                cell: &g.cells[self.idx],
598                s: &g.s[g.s_idx(self.idx)..g.s_idx(self.idx + 1)],
599                column: self.column,
600            };
601            self.column += r.colspan;
602            self.idx += 1;
603            Some(r)
604        }
605    }
606}
607
608struct CellRef<'a> {
609    cell: &'a CellEntry,
610    s: &'a str,
611    column: usize,
612}
613impl<'a> Deref for CellRef<'a> {
614    type Target = &'a CellEntry;
615    fn deref(&self) -> &Self::Target {
616        &self.cell
617    }
618}
619
620/// Column's style.
621#[derive(Debug, Clone, Eq, PartialEq)]
622#[derive_ex(Default)]
623#[default(Self::DEFAULT)]
624pub struct ColumnStyle {
625    /// If true, display a separator on the right side of this column.
626    ///
627    /// This setting is ignored for the rightmost column, and the border is always displayed.
628    ///
629    /// The default for this is `true`.
630    ///
631    /// ```
632    /// use text_grid::*;
633    /// let mut g = GridBuilder::new();
634    /// g.push(|b| {
635    ///     b.push("A");
636    ///     b.push("B");
637    ///     b.push("C");
638    /// });
639    /// assert_eq!(format!("\n{g}"), E0);
640    ///
641    /// g.column_styles = vec![ColumnStyle::default(); 2];
642    /// g.column_styles[0].column_end = false;
643    ///
644    /// assert_eq!(format!("\n{g}"), E1);
645    ///
646    /// const E0: &str = r"
647    ///  A | B | C |
648    /// ";
649    ///
650    /// const E1: &str = r"
651    ///  AB | C |
652    /// ";
653    /// ```
654    pub column_end: bool,
655
656    /// If true, prioritize this column width expansion over others.
657    ///
658    /// When stretching a multi-column layout,
659    /// if any column has `stretch` set to true, only those columns will be stretched,
660    /// while columns with `stretch` set to false will not be stretched.
661    ///
662    /// The default for this is `false`.
663    ///
664    /// ```
665    /// use text_grid::*;
666    /// let mut g = GridBuilder::new();
667    /// g.push(|b| {
668    ///     b.push_with_colspan("............", 2);
669    /// });
670    /// g.push(|b| {
671    ///     b.push("A");
672    ///     b.push("B");
673    /// });
674    /// assert_eq!(format!("\n{g}"), E0);
675    ///
676    /// g.column_styles = vec![ColumnStyle::default(); 2];
677    /// g.column_styles[0].stretch = true;
678    ///
679    /// assert_eq!(format!("\n{g}"), E1);
680    ///
681    /// const E0: &str = r"
682    ///  ............ |
683    ///  A     | B    |
684    /// ";
685    ///
686    /// const E1: &str = r"
687    ///  ............ |
688    ///  A        | B |
689    /// ";
690    /// ```
691    pub stretch: bool,
692}
693impl ColumnStyle {
694    const DEFAULT: Self = Self {
695        column_end: true,
696        stretch: false,
697    };
698}