tabled/settings/merge/
mod.rs

1//! The module contains a set of methods to merge cells together via [`Span`]s.
2//!
3//! [`Span`]: crate::settings::span::Span
4
5use crate::{
6    grid::config::ColoredConfig,
7    grid::records::{ExactRecords, PeekableRecords, Records},
8    settings::TableOption,
9};
10
11/// Merge to combine duplicates together, using [`Span`].
12///
13/// [`Span`]: crate::settings::span::Span
14#[derive(Debug)]
15pub struct Merge;
16
17impl Merge {
18    /// Vertical merge.
19    pub fn vertical() -> MergeDuplicatesVertical {
20        MergeDuplicatesVertical
21    }
22
23    /// Horizontal merge.
24    pub fn horizontal() -> MergeDuplicatesHorizontal {
25        MergeDuplicatesHorizontal
26    }
27}
28
29/// A modificator for [`Table`] which looks up for duplicates in columns and
30/// in case of duplicate merges the cells together using [`Span`].
31///
32/// [`Table`]: crate::Table
33/// [`Span`]: crate::settings::span::Span
34#[derive(Debug)]
35pub struct MergeDuplicatesVertical;
36
37impl<R, D> TableOption<R, ColoredConfig, D> for MergeDuplicatesVertical
38where
39    R: Records + PeekableRecords + ExactRecords,
40{
41    #[allow(clippy::assigning_clones)]
42    // NOTE: Temporarily disabled due to a issue with `assigning_clones` not respecting MSRV in clippy 1.78.0.
43    //       See https://github.com/rust-lang/rust-clippy/issues/12502
44    fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
45        let count_rows = records.count_rows();
46        let count_cols = records.count_columns();
47
48        if count_rows == 0 || count_cols == 0 {
49            return;
50        }
51
52        for column in 0..count_cols {
53            let mut repeat_length = 0;
54            let mut repeat_value = String::new();
55            let mut repeat_is_set = false;
56            let mut last_is_row_span = false;
57            for row in (0..count_rows).rev() {
58                if last_is_row_span {
59                    last_is_row_span = false;
60                    continue;
61                }
62
63                // we need to mitigate messing existing spans
64                let is_cell_visible = cfg.is_cell_visible((row, column).into());
65                let is_row_span_cell = cfg.get_column_span((row, column).into()).is_some();
66
67                if !repeat_is_set {
68                    if !is_cell_visible {
69                        continue;
70                    }
71
72                    if is_row_span_cell {
73                        continue;
74                    }
75
76                    repeat_length = 1;
77                    repeat_value = records.get_text((row, column).into()).to_owned();
78                    repeat_is_set = true;
79                    continue;
80                }
81
82                if is_row_span_cell {
83                    repeat_is_set = false;
84                    last_is_row_span = true;
85                    continue;
86                }
87
88                if !is_cell_visible {
89                    repeat_is_set = false;
90                    continue;
91                }
92
93                let text = records.get_text((row, column).into());
94                let is_duplicate = text == repeat_value;
95
96                if is_duplicate {
97                    repeat_length += 1;
98                    continue;
99                }
100
101                if repeat_length > 1 {
102                    cfg.set_row_span((row + 1, column).into(), repeat_length);
103                }
104
105                repeat_length = 1;
106                repeat_value = records.get_text((row, column).into()).to_owned();
107            }
108
109            if repeat_length > 1 {
110                cfg.set_row_span((0, column).into(), repeat_length);
111            }
112        }
113    }
114}
115
116/// A modificator for [`Table`] which looks up for duplicates in rows and
117/// in case of duplicate merges the cells together using [`Span`].
118///
119/// [`Table`]: crate::Table
120/// [`Span`]: crate::settings::span::Span
121#[derive(Debug)]
122pub struct MergeDuplicatesHorizontal;
123
124impl<R, D> TableOption<R, ColoredConfig, D> for MergeDuplicatesHorizontal
125where
126    R: Records + PeekableRecords + ExactRecords,
127{
128    #[allow(clippy::assigning_clones)]
129    // NOTE: Temporarily disabled due to a issue with `assigning_clones` not respecting MSRV in clippy 1.78.0.
130    //       See https://github.com/rust-lang/rust-clippy/issues/12502
131    fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
132        let count_rows = records.count_rows();
133        let count_cols = records.count_columns();
134
135        if count_rows == 0 || count_cols == 0 {
136            return;
137        }
138
139        for row in 0..count_rows {
140            let mut repeat_length = 0;
141            let mut repeat_value = String::new();
142            let mut repeat_is_set = false;
143            let mut last_is_col_span = false;
144
145            for column in (0..count_cols).rev() {
146                if last_is_col_span {
147                    last_is_col_span = false;
148                    continue;
149                }
150
151                // we need to mitigate messing existing spans
152                let is_cell_visible = cfg.is_cell_visible((row, column).into());
153                let is_col_span_cell = cfg.get_row_span((row, column).into()).is_some();
154
155                if !repeat_is_set {
156                    if !is_cell_visible {
157                        continue;
158                    }
159
160                    if is_col_span_cell {
161                        continue;
162                    }
163
164                    repeat_length = 1;
165                    repeat_value = records.get_text((row, column).into()).to_owned();
166                    repeat_is_set = true;
167                    continue;
168                }
169
170                if is_col_span_cell {
171                    repeat_is_set = false;
172                    last_is_col_span = true;
173                    continue;
174                }
175
176                if !is_cell_visible {
177                    repeat_is_set = false;
178                    continue;
179                }
180
181                let text = records.get_text((row, column).into());
182                let is_duplicate = text == repeat_value;
183
184                if is_duplicate {
185                    repeat_length += 1;
186                    continue;
187                }
188
189                if repeat_length > 1 {
190                    cfg.set_column_span((row, column + 1).into(), repeat_length);
191                }
192
193                repeat_length = 1;
194                repeat_value = records.get_text((row, column).into()).to_owned();
195            }
196
197            if repeat_length > 1 {
198                cfg.set_column_span((row, 0).into(), repeat_length);
199            }
200        }
201    }
202}