1use std::cmp;
2
3use crate::{
4 grid::{
5 config::{AlignmentHorizontal, AlignmentVertical, ColoredConfig, Position},
6 dimension::{CompleteDimensionVecRecords, Dimension, Estimate},
7 records::{
8 vec_records::{CellInfo, VecRecords},
9 ExactRecords, PeekableRecords, Records, Resizable,
10 },
11 util::string::string_width,
12 },
13 settings::{
14 object::{Column, Row},
15 style::{LineText, Offset},
16 Alignment, Color, TableOption,
17 },
18};
19
20/// [`ColumnNames`] sets strings on horizontal lines for the columns.
21///
22/// Notice that using a [`Default`] would reuse a names from the first row.
23///
24/// # Examples
25///
26/// ```
27/// use std::iter::FromIterator;
28/// use tabled::{
29/// Table,
30/// settings::{themes::ColumnNames, Alignment},
31/// };
32///
33/// let data = vec![
34/// vec!["Hello", "World"],
35/// vec!["Hello", "World"],
36/// ];
37///
38/// let mut table = Table::from_iter(data);
39/// table.with(
40/// ColumnNames::new(["head1", "head2"])
41/// .line(2)
42/// .alignment(Alignment::right())
43/// );
44///
45/// assert_eq!(
46/// table.to_string(),
47/// "+-------+-------+\n\
48/// | Hello | World |\n\
49/// +-------+-------+\n\
50/// | Hello | World |\n\
51/// +--head1+--head2+"
52/// );
53/// ```
54///
55/// [`Default`] usage.
56///
57/// ```
58/// use std::iter::FromIterator;
59/// use tabled::{Table, settings::themes::ColumnNames};
60///
61/// let data = vec![
62/// vec!["Hello", "World"],
63/// vec!["Hello", "World"],
64/// ];
65///
66/// let mut table = Table::from_iter(data);
67/// table.with(ColumnNames::default());
68///
69/// assert_eq!(
70/// table.to_string(),
71/// "+Hello--+World--+\n\
72/// | Hello | World |\n\
73/// +-------+-------+"
74/// );
75/// ```
76#[derive(Debug, Clone)]
77pub struct ColumnNames {
78 names: Option<Vec<String>>,
79 colors: Option<ListValue<Color>>,
80 alignments: ListValue<Alignment>,
81 line: usize,
82}
83
84impl Default for ColumnNames {
85 fn default() -> Self {
86 Self {
87 names: Default::default(),
88 colors: Default::default(),
89 line: Default::default(),
90 alignments: ListValue::Static(Alignment::left()),
91 }
92 }
93}
94
95impl ColumnNames {
96 /// Creates a [`ColumnNames`] with a given names.
97 ///
98 /// Using a [`Default`] would reuse a names from the first row.
99 ///
100 /// # Example
101 ///
102 /// ```
103 /// use std::iter::FromIterator;
104 /// use tabled::{Table, settings::themes::ColumnNames};
105 ///
106 /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
107 /// table.with(ColumnNames::new(["head1", "head2"]));
108 ///
109 /// assert_eq!(
110 /// table.to_string(),
111 /// "+head1--+head2--+\n\
112 /// | Hello | World |\n\
113 /// +-------+-------+"
114 /// );
115 /// ```
116 pub fn new<I>(names: I) -> Self
117 where
118 I: IntoIterator,
119 I::Item: Into<String>,
120 {
121 let names = names.into_iter().map(Into::into).collect::<Vec<_>>();
122 Self {
123 names: Some(names),
124 ..Default::default()
125 }
126 }
127
128 /// Set color for the column names.
129 ///
130 /// By default there's no colors.
131 ///
132 /// # Example
133 ///
134 /// ```
135 /// use std::iter::FromIterator;
136 /// use tabled::Table;
137 /// use tabled::settings::{Color, themes::ColumnNames};
138 ///
139 /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
140 /// table.with(ColumnNames::new(["head1", "head2"]).color(vec![Color::FG_RED]));
141 ///
142 /// assert_eq!(
143 /// table.to_string(),
144 /// "+\u{1b}[31mh\u{1b}[39m\u{1b}[31me\u{1b}[39m\u{1b}[31ma\u{1b}[39m\u{1b}[31md\u{1b}[39m\u{1b}[31m1\u{1b}[39m--+head2--+\n\
145 /// | Hello | World |\n\
146 /// +-------+-------+"
147 /// );
148 /// ```
149 pub fn color<T>(self, color: T) -> Self
150 where
151 T: Into<ListValue<Color>>,
152 {
153 Self {
154 names: self.names,
155 line: self.line,
156 alignments: self.alignments,
157 colors: Some(color.into()),
158 }
159 }
160
161 /// Set a horizontal line the names will be applied to.
162 ///
163 /// The default value is 0 (the top horizontal line).
164 ///
165 /// # Example
166 ///
167 /// ```
168 /// use std::iter::FromIterator;
169 /// use tabled::{Table, settings::themes::ColumnNames};
170 ///
171 /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
172 /// table.with(ColumnNames::new(["head1", "head2"]).line(1));
173 ///
174 /// assert_eq!(
175 /// table.to_string(),
176 /// "+-------+-------+\n\
177 /// | Hello | World |\n\
178 /// +head1--+head2--+"
179 /// );
180 /// ```
181 pub fn line(self, i: usize) -> Self {
182 Self {
183 names: self.names,
184 line: i,
185 alignments: self.alignments,
186 colors: self.colors,
187 }
188 }
189
190 /// Set an alignment for the names.
191 ///
192 /// By default it's left aligned.
193 ///
194 /// # Example
195 ///
196 /// ```
197 /// use std::iter::FromIterator;
198 /// use tabled::{
199 /// Table,
200 /// settings::{themes::ColumnNames, Alignment},
201 /// };
202 ///
203 /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
204 /// table.with(ColumnNames::new(["head1", "head2"]).alignment(Alignment::right()));
205 ///
206 /// assert_eq!(
207 /// table.to_string(),
208 /// "+--head1+--head2+\n\
209 /// | Hello | World |\n\
210 /// +-------+-------+"
211 /// );
212 /// ```
213 pub fn alignment<T>(self, alignment: T) -> Self
214 where
215 T: Into<ListValue<Alignment>>,
216 {
217 Self {
218 names: self.names,
219 line: self.line,
220 alignments: alignment.into(),
221 colors: self.colors,
222 }
223 }
224}
225
226impl TableOption<VecRecords<CellInfo<String>>, ColoredConfig, CompleteDimensionVecRecords<'_>>
227 for ColumnNames
228{
229 fn change(
230 self,
231 records: &mut VecRecords<CellInfo<String>>,
232 cfg: &mut ColoredConfig,
233 dims: &mut CompleteDimensionVecRecords<'_>,
234 ) {
235 let count_rows = records.count_rows();
236 let count_columns = records.count_columns();
237
238 if count_columns == 0 || count_rows == 0 || self.line > count_rows {
239 return;
240 }
241
242 let alignemnt_horizontal = convert_alignment_value(self.alignments.clone());
243 let alignemnt_vertical = convert_alignment_value(self.alignments.clone());
244
245 if let Some(alignment) = alignemnt_horizontal {
246 let names = get_column_names(records, self.names);
247 let names = vec_set_size(names, records.count_columns());
248 set_column_text(names, self.line, alignment, self.colors, records, dims, cfg);
249 return;
250 }
251
252 if let Some(alignment) = alignemnt_vertical {
253 let names = get_column_names(records, self.names);
254 let names = vec_set_size(names, records.count_rows());
255 set_row_text(names, self.line, alignment, self.colors, records, dims, cfg);
256 return;
257 }
258
259 let names = get_column_names(records, self.names);
260 let names = vec_set_size(names, records.count_columns());
261 let alignment = ListValue::Static(AlignmentHorizontal::Left);
262 set_column_text(names, self.line, alignment, self.colors, records, dims, cfg);
263 }
264}
265
266fn set_column_text(
267 names: Vec<String>,
268 target_line: usize,
269 alignments: ListValue<AlignmentHorizontal>,
270 colors: Option<ListValue<Color>>,
271 records: &mut VecRecords<CellInfo<String>>,
272 dims: &mut CompleteDimensionVecRecords<'_>,
273 cfg: &mut ColoredConfig,
274) {
275 dims.estimate(&*records, cfg);
276
277 let count_columns = names.len();
278 let widths = names
279 .iter()
280 .enumerate()
281 .map(|(col, name)| (cmp::max(string_width(name), dims.get_width(col))))
282 .collect::<Vec<_>>();
283
284 dims.set_widths(widths.clone());
285
286 let mut total_width = 0;
287 for (column, (width, name)) in widths.into_iter().zip(names).enumerate() {
288 let color = get_color(&colors, column);
289 let alignment = alignments.get(column).unwrap_or(AlignmentHorizontal::Left);
290 let left_vertical = get_vertical_width(cfg, (target_line, column), count_columns);
291 let grid_offset =
292 total_width + left_vertical + get_horizontal_indent(&name, alignment, width);
293 let line = Row::from(target_line);
294
295 let linetext = create_line_text(&name, grid_offset, color, line);
296 linetext.change(records, cfg, dims);
297
298 total_width += width + left_vertical;
299 }
300}
301
302fn set_row_text(
303 names: Vec<String>,
304 target_line: usize,
305 alignments: ListValue<AlignmentVertical>,
306 colors: Option<ListValue<Color>>,
307 records: &mut VecRecords<CellInfo<String>>,
308 dims: &mut CompleteDimensionVecRecords<'_>,
309 cfg: &mut ColoredConfig,
310) {
311 dims.estimate(&*records, cfg);
312
313 let count_rows = names.len();
314 let heights = names
315 .iter()
316 .enumerate()
317 .map(|(row, name)| (cmp::max(string_width(name), dims.get_height(row))))
318 .collect::<Vec<_>>();
319
320 dims.set_heights(heights.clone());
321
322 let mut total_height = 0;
323 for (row, (row_height, name)) in heights.into_iter().zip(names).enumerate() {
324 let color = get_color(&colors, row);
325 let alignment = alignments.get(row).unwrap_or(AlignmentVertical::Top);
326 let top_horizontal = get_horizontal_width(cfg, (row, target_line), count_rows);
327 let cell_indent = get_vertical_indent(&name, alignment, row_height);
328 let grid_offset = total_height + top_horizontal + cell_indent;
329 let line = Column::from(target_line);
330
331 let linetext = create_line_text(&name, grid_offset, color, line);
332 linetext.change(records, cfg, dims);
333
334 total_height += row_height + top_horizontal;
335 }
336}
337
338fn get_column_names(
339 records: &mut VecRecords<CellInfo<String>>,
340 opt: Option<Vec<String>>,
341) -> Vec<String> {
342 match opt {
343 Some(names: Vec) => namesimpl Iterator
344 .into_iter()
345 .map(|name: String| name.lines().next().unwrap_or(default:"").to_string())
346 .collect::<Vec<_>>(),
347 None => collect_head(records),
348 }
349}
350
351fn vec_set_size(mut data: Vec<String>, size: usize) -> Vec<String> {
352 match data.len().cmp(&size) {
353 cmp::Ordering::Equal => {}
354 cmp::Ordering::Less => {
355 let additional_size: usize = size - data.len();
356 data.extend(iter:std::iter::repeat(elt:String::new()).take(additional_size));
357 }
358 cmp::Ordering::Greater => {
359 data.truncate(len:size);
360 }
361 }
362
363 data
364}
365
366fn collect_head(records: &mut VecRecords<CellInfo<String>>) -> Vec<String> {
367 if records.count_rows() == 0 || records.count_columns() == 0 {
368 return Vec::new();
369 }
370
371 let names: Vec = (0..records.count_columns())
372 .map(|column: usize| records.get_line((0, column), line:0))
373 .map(ToString::to_string)
374 .collect();
375
376 records.remove_row(0);
377
378 names
379}
380
381fn create_line_text<T>(text: &str, offset: usize, color: Option<&Color>, line: T) -> LineText<T> {
382 let offset: Offset = Offset::Begin(offset);
383 let mut btext: LineText = LineText::new(text, line).offset(offset);
384 if let Some(color: &Color) = color {
385 btext = btext.color(color.clone());
386 }
387
388 btext
389}
390
391fn get_color(colors: &Option<ListValue<Color>>, i: usize) -> Option<&Color> {
392 match colors {
393 Some(ListValue::List(list: &Vec)) => list.get(index:i),
394 Some(ListValue::Static(color: &Color)) => Some(color),
395 None => None,
396 }
397}
398
399fn get_horizontal_indent(text: &str, align: AlignmentHorizontal, available: usize) -> usize {
400 match align {
401 AlignmentHorizontal::Left => 0,
402 AlignmentHorizontal::Right => available - string_width(text),
403 AlignmentHorizontal::Center => (available - string_width(text)) / 2,
404 }
405}
406
407fn get_vertical_indent(text: &str, align: AlignmentVertical, available: usize) -> usize {
408 match align {
409 AlignmentVertical::Top => 0,
410 AlignmentVertical::Bottom => available - string_width(text),
411 AlignmentVertical::Center => (available - string_width(text)) / 2,
412 }
413}
414
415fn get_vertical_width(cfg: &mut ColoredConfig, pos: Position, count_columns: usize) -> usize {
416 cfg.get_vertical(pos, count_columns)
417 .and_then(unicode_width::UnicodeWidthChar::width)
418 .unwrap_or(default:0)
419}
420
421fn get_horizontal_width(cfg: &mut ColoredConfig, pos: Position, count_rows: usize) -> usize {
422 cfg.get_horizontal(pos, count_rows)
423 .and_then(unicode_width::UnicodeWidthChar::width)
424 .unwrap_or(default:0)
425}
426
427fn convert_alignment_value<T>(value: ListValue<Alignment>) -> Option<ListValue<T>>
428where
429 Option<T>: From<Alignment>,
430{
431 match value {
432 ListValue::List(list: Vec) => {
433 let new: Vec = listimpl Iterator
434 .iter()
435 .flat_map(|value: &Alignment| Option::from(*value))
436 .collect::<Vec<_>>();
437 if new.len() == list.len() {
438 Some(ListValue::List(new))
439 } else {
440 None
441 }
442 }
443 ListValue::Static(value: Alignment) => Option::from(value).map(ListValue::Static),
444 }
445}
446
447#[derive(Debug, Clone)]
448pub enum ListValue<T> {
449 List(Vec<T>),
450 Static(T),
451}
452
453impl<T> ListValue<T> {
454 fn get(&self, i: usize) -> Option<T>
455 where
456 T: Copy,
457 {
458 match self {
459 ListValue::List(list: &Vec) => list.get(index:i).copied(),
460 ListValue::Static(alignment: &T) => Some(*alignment),
461 }
462 }
463}
464
465impl<T> From<T> for ListValue<T> {
466 fn from(value: T) -> Self {
467 Self::Static(value)
468 }
469}
470
471impl<T> From<Vec<T>> for ListValue<T> {
472 fn from(value: Vec<T>) -> Self {
473 Self::List(value)
474 }
475}
476
477impl<T> Default for ListValue<T>
478where
479 T: Default,
480{
481 fn default() -> Self {
482 Self::Static(T::default())
483 }
484}
485