1use core::{cmp::max, fmt};
4
5use crate::{
6 grid::{
7 colors::NoColors,
8 config::{AlignmentHorizontal, CompactConfig, Indent, Sides},
9 dimension::{ConstDimension, ConstSize, Dimension},
10 records::{
11 into_records::{LimitColumns, LimitRows},
12 IntoRecords, IterRecords,
13 },
14 util::string::get_line_width,
15 CompactGrid,
16 },
17 settings::{style::Style, TableOption},
18};
19
20#[cfg_attr(feature = "std", doc = "```")]
31#[cfg_attr(not(feature = "std"), doc = "```ignore")]
32#[cfg_attr(feature = "std", doc = "```")]
57#[cfg_attr(not(feature = "std"), doc = "```ignore")]
58#[derive(Debug, Clone)]
88pub struct CompactTable<I, D> {
89 records: I,
90 cfg: CompactConfig,
91 dims: D,
92 count_columns: usize,
93 count_rows: Option<usize>,
94}
95
96impl<I> CompactTable<I, ConstDimension<0, 0>> {
97 pub const fn new(iter: I) -> Self
99 where
100 I: IntoRecords,
101 {
102 Self {
103 records: iter,
104 cfg: create_config(),
105 count_columns: 0,
106 count_rows: None,
107 dims: ConstDimension::new(ConstSize::Value(2), ConstSize::Value(1)),
108 }
109 }
110}
111
112impl<I, const ROWS: usize, const COLS: usize> CompactTable<I, ConstDimension<COLS, ROWS>> {
113 pub fn height<S: Into<ConstSize<COUNT_ROWS>>, const COUNT_ROWS: usize>(
115 self,
116 size: S,
117 ) -> CompactTable<I, ConstDimension<COLS, COUNT_ROWS>> {
118 let (width, _) = self.dims.into();
119 CompactTable {
120 dims: ConstDimension::new(width, size.into()),
121 records: self.records,
122 cfg: self.cfg,
123 count_columns: self.count_columns,
124 count_rows: self.count_rows,
125 }
126 }
127
128 pub fn width<S: Into<ConstSize<COUNT_COLUMNS>>, const COUNT_COLUMNS: usize>(
130 self,
131 size: S,
132 ) -> CompactTable<I, ConstDimension<COUNT_COLUMNS, ROWS>> {
133 let (_, height) = self.dims.into();
134 CompactTable {
135 dims: ConstDimension::new(size.into(), height),
136 records: self.records,
137 cfg: self.cfg,
138 count_columns: self.count_columns,
139 count_rows: self.count_rows,
140 }
141 }
142}
143
144impl<I, D> CompactTable<I, D> {
145 pub fn with_dimension(iter: I, dimension: D) -> Self
151 where
152 I: IntoRecords,
153 {
154 Self {
155 records: iter,
156 dims: dimension,
157 cfg: create_config(),
158 count_columns: 0,
159 count_rows: None,
160 }
161 }
162
163 pub fn with<O>(mut self, option: O) -> Self
165 where
166 for<'a> O: TableOption<IterRecords<&'a I>, CompactConfig, D>,
167 {
168 let mut records = IterRecords::new(&self.records, self.count_columns, self.count_rows);
169 option.change(&mut records, &mut self.cfg, &mut self.dims);
170
171 self
172 }
173
174 pub const fn rows(mut self, count_rows: usize) -> Self {
176 self.count_rows = Some(count_rows);
177 self
178 }
179
180 pub const fn columns(mut self, count: usize) -> Self {
182 self.count_columns = count;
183 self
184 }
185
186 pub fn get_config(&self) -> &CompactConfig {
188 &self.cfg
189 }
190
191 pub fn get_config_mut(&mut self) -> &mut CompactConfig {
193 &mut self.cfg
194 }
195
196 pub fn fmt<W>(self, writer: W) -> fmt::Result
198 where
199 I: IntoRecords,
200 I::Cell: AsRef<str>,
201 D: Dimension,
202 W: fmt::Write,
203 {
204 build_grid(
205 writer,
206 self.records,
207 self.dims,
208 self.cfg,
209 self.count_columns,
210 self.count_rows,
211 )
212 }
213
214 #[cfg(feature = "std")]
216 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
217 pub fn build<W>(self, writer: W) -> std::io::Result<()>
218 where
219 I: IntoRecords,
220 I::Cell: AsRef<str>,
221 D: Dimension,
222 W: std::io::Write,
223 {
224 let writer = crate::util::utf8_writer::UTF8Writer::new(writer);
225 self.fmt(writer).map_err(std::io::Error::other)
226 }
227
228 #[allow(clippy::inherent_to_string)]
232 #[cfg(feature = "std")]
233 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
234 pub fn to_string(self) -> String
235 where
236 I: IntoRecords,
237 I::Cell: AsRef<str>,
238 D: Dimension,
239 {
240 let mut buf = String::new();
241 self.fmt(&mut buf)
242 .expect("it's expected to be ok according to doc");
243
244 buf
245 }
246}
247
248impl<T, const ROWS: usize, const COLS: usize> From<[[T; COLS]; ROWS]>
249 for CompactTable<[[T; COLS]; ROWS], ConstDimension<COLS, ROWS>>
250where
251 T: AsRef<str>,
252{
253 fn from(mat: [[T; COLS]; ROWS]) -> Self {
254 let mut width = [0; COLS];
255 for row in mat.iter() {
256 for (col, text) in row.iter().enumerate() {
257 let text = text.as_ref();
258 let text_width = get_line_width(text);
259 width[col] = max(width[col], text_width);
260 }
261 }
262
263 for w in &mut width {
265 *w += 2;
266 }
267
268 let dims = ConstDimension::new(ConstSize::List(width), ConstSize::Value(1));
269 Self::with_dimension(mat, dims).columns(COLS).rows(ROWS)
270 }
271}
272
273fn build_grid<W, I, D>(
274 writer: W,
275 records: I,
276 dims: D,
277 config: CompactConfig,
278 cols: usize,
279 rows: Option<usize>,
280) -> fmt::Result
281where
282 W: fmt::Write,
283 I: IntoRecords,
284 I::Cell: AsRef<str>,
285 D: Dimension,
286{
287 match rows {
288 Some(limit) => {
289 let records = LimitRows::new(records, limit);
290 let records = LimitColumns::new(records, cols);
291 let records = IterRecords::new(records, cols, rows);
292 CompactGrid::new(records, config, dims, NoColors).build(writer)
293 }
294 None => {
295 let records = LimitColumns::new(records, cols);
296 let records = IterRecords::new(records, cols, rows);
297 CompactGrid::new(records, config, dims, NoColors).build(writer)
298 }
299 }
300}
301
302const fn create_config() -> CompactConfig {
303 CompactConfig::new()
304 .set_padding(Sides::new(
305 Indent::spaced(1),
306 Indent::spaced(1),
307 Indent::zero(),
308 Indent::zero(),
309 ))
310 .set_alignment_horizontal(AlignmentHorizontal::Left)
311 .set_borders(Style::ascii().get_borders())
312}
313
314impl<R, D> TableOption<R, CompactConfig, D> for CompactConfig {
315 fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) {
316 *cfg = self;
317 }
318}