1use crate::{
2 grid::{
3 config::{AlignmentHorizontal, ColoredConfig},
4 dimension::{CompleteDimensionVecRecords, Dimension, Estimate},
5 records::{
6 vec_records::{CellInfo, VecRecords},
7 ExactRecords, PeekableRecords, Records, Resizable,
8 },
9 util::string::string_width,
10 },
11 settings::{
12 style::{BorderText, Offset},
13 Color, TableOption,
14 },
15};
16
17/// [`ColumnNames`] sets strings on horizontal lines for the columns.
18///
19/// Notice that using a [`Default`] would reuse a names from the first row.
20///
21/// # Examples
22///
23/// ```
24/// use std::iter::FromIterator;
25/// use tabled::{Table, settings::themes::ColumnNames};
26///
27/// let data = vec![
28/// vec!["Hello", "World"],
29/// vec!["Hello", "World"],
30/// ];
31///
32/// let mut table = Table::from_iter(data);
33/// table.with(ColumnNames::new(["head1", "head2"]).set_offset(3).set_line(2));
34///
35/// assert_eq!(
36/// table.to_string(),
37/// "+--------+--------+\n\
38/// | Hello | World |\n\
39/// +--------+--------+\n\
40/// | Hello | World |\n\
41/// +---head1+---head2+"
42/// );
43/// ```
44///
45/// [`Default`] usage.
46///
47/// ```
48/// use std::iter::FromIterator;
49/// use tabled::{Table, settings::themes::ColumnNames};
50///
51/// let data = vec![
52/// vec!["Hello", "World"],
53/// vec!["Hello", "World"],
54/// ];
55///
56/// let mut table = Table::from_iter(data);
57/// table.with(ColumnNames::default());
58///
59/// assert_eq!(
60/// table.to_string(),
61/// "+Hello--+World--+\n\
62/// | Hello | World |\n\
63/// +-------+-------+"
64/// );
65/// ```
66#[derive(Debug, Clone)]
67pub struct ColumnNames {
68 names: Option<Vec<String>>,
69 colors: Vec<Option<Color>>,
70 offset: usize,
71 line: usize,
72 alignment: AlignmentHorizontal,
73}
74
75impl Default for ColumnNames {
76 fn default() -> Self {
77 Self {
78 names: Default::default(),
79 colors: Default::default(),
80 offset: Default::default(),
81 line: Default::default(),
82 alignment: AlignmentHorizontal::Left,
83 }
84 }
85}
86
87impl ColumnNames {
88 /// Creates a [`ColumnNames`] with a given names.
89 ///
90 /// Using a [`Default`] would reuse a names from the first row.
91 ///
92 /// # Example
93 ///
94 /// ```
95 /// use std::iter::FromIterator;
96 /// use tabled::{Table, settings::themes::ColumnNames};
97 ///
98 /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
99 /// table.with(ColumnNames::new(["head1", "head2"]));
100 ///
101 /// assert_eq!(
102 /// table.to_string(),
103 /// "+head1--+head2--+\n\
104 /// | Hello | World |\n\
105 /// +-------+-------+"
106 /// );
107 /// ```
108 pub fn new<I>(names: I) -> Self
109 where
110 I: IntoIterator,
111 I::Item: Into<String>,
112 {
113 let names = names.into_iter().map(Into::into).collect::<Vec<_>>();
114 Self {
115 names: Some(names),
116 colors: Vec::new(),
117 offset: 0,
118 line: 0,
119 alignment: AlignmentHorizontal::Left,
120 }
121 }
122
123 /// Set color for the column names.
124 ///
125 /// By default there's no colors.
126 ///
127 /// # Example
128 ///
129 /// ```
130 /// use std::iter::FromIterator;
131 /// use tabled::Table;
132 /// use tabled::settings::{Color, themes::ColumnNames};
133 ///
134 /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
135 /// table.with(ColumnNames::new(["head1", "head2"]).set_colors([Color::FG_RED]));
136 ///
137 /// assert_eq!(
138 /// table.to_string(),
139 /// "+\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\
140 /// | Hello | World |\n\
141 /// +-------+-------+"
142 /// );
143 /// ```
144 pub fn set_colors<I>(self, colors: I) -> Self
145 where
146 I: IntoIterator,
147 I::Item: Into<Option<Color>>,
148 {
149 let colors = colors.into_iter().map(Into::into).collect::<Vec<_>>();
150 Self {
151 names: self.names,
152 offset: self.offset,
153 line: self.line,
154 alignment: self.alignment,
155 colors,
156 }
157 }
158
159 /// Set a left offset after which the names will be used.
160 ///
161 /// By default there's no offset.
162 ///
163 /// # Example
164 ///
165 /// ```
166 /// use std::iter::FromIterator;
167 /// use tabled::{Table, settings::themes::ColumnNames};
168 ///
169 /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
170 /// table.with(ColumnNames::new(["head1", "head2"]).set_offset(1));
171 ///
172 /// assert_eq!(
173 /// table.to_string(),
174 /// "+-head1-+-head2-+\n\
175 /// | Hello | World |\n\
176 /// +-------+-------+"
177 /// );
178 /// ```
179 pub fn set_offset(self, i: usize) -> Self {
180 Self {
181 names: self.names,
182 colors: self.colors,
183 line: self.line,
184 alignment: self.alignment,
185 offset: i,
186 }
187 }
188
189 /// Set a horizontal line the names will be applied to.
190 ///
191 /// The default value is 0 (the top horizontal line).
192 ///
193 /// # Example
194 ///
195 /// ```
196 /// use std::iter::FromIterator;
197 /// use tabled::{Table, settings::themes::ColumnNames};
198 ///
199 /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
200 /// table.with(ColumnNames::new(["head1", "head2"]).set_line(1));
201 ///
202 /// assert_eq!(
203 /// table.to_string(),
204 /// "+-------+-------+\n\
205 /// | Hello | World |\n\
206 /// +head1--+head2--+"
207 /// );
208 /// ```
209 pub fn set_line(self, i: usize) -> Self {
210 Self {
211 names: self.names,
212 colors: self.colors,
213 offset: self.offset,
214 alignment: self.alignment,
215 line: i,
216 }
217 }
218
219 /// Set an alignment for the names.
220 ///
221 /// By default it's left aligned.
222 ///
223 /// # Example
224 ///
225 /// ```
226 /// use std::iter::FromIterator;
227 /// use tabled::{
228 /// Table,
229 /// settings::themes::ColumnNames,
230 /// grid::config::AlignmentHorizontal,
231 /// };
232 ///
233 /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]);
234 /// table.with(ColumnNames::new(["head1", "head2"]).set_alignment(AlignmentHorizontal::Right));
235 ///
236 /// assert_eq!(
237 /// table.to_string(),
238 /// "+--head1+--head2+\n\
239 /// | Hello | World |\n\
240 /// +-------+-------+"
241 /// );
242 /// ```
243 pub fn set_alignment(self, alignment: AlignmentHorizontal) -> Self {
244 Self {
245 names: self.names,
246 colors: self.colors,
247 offset: self.offset,
248 line: self.line,
249 alignment,
250 }
251 }
252}
253
254impl TableOption<VecRecords<CellInfo<String>>, CompleteDimensionVecRecords<'static>, ColoredConfig>
255 for ColumnNames
256{
257 fn change(
258 self,
259 records: &mut VecRecords<CellInfo<String>>,
260 cfg: &mut ColoredConfig,
261 dims: &mut CompleteDimensionVecRecords<'static>,
262 ) {
263 let names = match self.names {
264 Some(names) => names,
265 None => {
266 if records.count_rows() == 0 || records.count_columns() == 0 {
267 return;
268 }
269
270 let names = (0..records.count_columns())
271 .map(|column| records.get_text((0, column)))
272 .map(ToString::to_string)
273 .collect::<Vec<_>>();
274
275 records.remove_row(0);
276
277 names
278 }
279 };
280
281 let names = names.iter().map(|name| name.lines().next().unwrap_or(""));
282
283 dims.estimate(&*records, cfg);
284
285 let mut widths = (0..records.count_columns())
286 .map(|column| dims.get_width(column))
287 .collect::<Vec<_>>();
288
289 let names = names.take(widths.len());
290
291 let offset = self.offset;
292 widths
293 .iter_mut()
294 .zip(names.clone())
295 .for_each(|(width, text)| {
296 let name_width = string_width(text) + offset;
297 *width = std::cmp::max(name_width, *width);
298 });
299
300 let _ = dims.set_widths(widths.clone());
301
302 let mut total_width = 0;
303 for (i, (width, name)) in widths.iter().zip(names).enumerate() {
304 let color = get_color(&self.colors, i);
305 let offset = total_width + 1;
306 let btext = get_border_text(
307 name,
308 offset,
309 *width,
310 self.alignment,
311 self.offset,
312 self.line,
313 color,
314 );
315 btext.change(records, cfg, dims);
316
317 total_width += width + 1;
318 }
319 }
320}
321
322fn get_border_text(
323 text: &str,
324 offset: usize,
325 available: usize,
326 alignment: AlignmentHorizontal,
327 alignment_offset: usize,
328 line: usize,
329 color: Option<&Color>,
330) -> BorderText<usize> {
331 let name: String = text.to_string();
332 let left_indent: usize = get_indent(text, align:alignment, alignment_offset, available);
333 let left_offset: Offset = Offset::Begin(offset + left_indent);
334 let mut btext: BorderText = BorderText::new(text:name).horizontal(line).offset(left_offset);
335 if let Some(color: &Color) = color {
336 btext = btext.color(color.clone());
337 }
338
339 btext
340}
341
342fn get_color(colors: &[Option<Color>], i: usize) -> Option<&Color> {
343 colors.get(index:i).and_then(|color: &Option| match color {
344 Some(color: &Color) => Some(color),
345 None => None,
346 })
347}
348
349fn get_indent(text: &str, align: AlignmentHorizontal, offset: usize, available: usize) -> usize {
350 match align {
351 AlignmentHorizontal::Left => offset,
352 AlignmentHorizontal::Right => available - string_width(text) - offset,
353 AlignmentHorizontal::Center => (available - string_width(text)) / 2,
354 }
355}
356