tabled/features/format.rs
1//! This module contains a list of primitives to help to modify a [`Table`].
2//!
3//! [`Table`]: crate::Table
4
5use papergrid::{
6 records::{Records, RecordsMut},
7 width::CfgWidthFunction,
8 Entity,
9};
10
11use crate::{CellOption, Table};
12
13/// A formatting function of particular cells on a [`Table`].
14///
15/// [`Table`]: crate::Table
16#[derive(Debug)]
17pub struct Format<F> {
18 f: F,
19}
20
21impl Format<()> {
22 /// This function creates a new [`Format`] instance, so
23 /// it can be used as a grid setting.
24 ///
25 /// # Example
26 ///
27 /// ```
28 /// use tabled::{Table, format::Format, object::Rows, Modify};
29 ///
30 /// let data = vec![
31 /// (0, "Grodno", true),
32 /// (1, "Minsk", true),
33 /// (2, "Hamburg", false),
34 /// (3, "Brest", true),
35 /// ];
36 ///
37 /// let table = Table::new(&data)
38 /// .with(Modify::new(Rows::new(1..)).with(Format::new(|s| format!(": {} :", s))))
39 /// .to_string();
40 ///
41 /// assert_eq!(table, "+-------+-------------+-----------+\n\
42 /// | i32 | &str | bool |\n\
43 /// +-------+-------------+-----------+\n\
44 /// | : 0 : | : Grodno : | : true : |\n\
45 /// +-------+-------------+-----------+\n\
46 /// | : 1 : | : Minsk : | : true : |\n\
47 /// +-------+-------------+-----------+\n\
48 /// | : 2 : | : Hamburg : | : false : |\n\
49 /// +-------+-------------+-----------+\n\
50 /// | : 3 : | : Brest : | : true : |\n\
51 /// +-------+-------------+-----------+");
52 /// ```
53 ///
54 pub fn new<F>(f: F) -> Format<F>
55 where
56 F: FnMut(&str) -> String,
57 {
58 Format { f }
59 }
60
61 /// This function creates a new [`FormatWithIndex`], so
62 /// it can be used as a grid setting.
63 ///
64 /// It's different from [`Format::new`] as it also provides a row and column index.
65 ///
66 /// # Example
67 ///
68 /// ```
69 /// use tabled::{Table, format::Format, object::Rows, Modify};
70 ///
71 /// let data = vec![
72 /// (0, "Grodno", true),
73 /// (1, "Minsk", true),
74 /// (2, "Hamburg", false),
75 /// (3, "Brest", true),
76 /// ];
77 ///
78 /// let table = Table::new(&data)
79 /// .with(Modify::new(Rows::single(0)).with(Format::with_index(|_, (_, column)| column.to_string())))
80 /// .to_string();
81 ///
82 /// assert_eq!(table, "+---+---------+-------+\n\
83 /// | 0 | 1 | 2 |\n\
84 /// +---+---------+-------+\n\
85 /// | 0 | Grodno | true |\n\
86 /// +---+---------+-------+\n\
87 /// | 1 | Minsk | true |\n\
88 /// +---+---------+-------+\n\
89 /// | 2 | Hamburg | false |\n\
90 /// +---+---------+-------+\n\
91 /// | 3 | Brest | true |\n\
92 /// +---+---------+-------+");
93 /// ```
94 pub fn with_index<F>(f: F) -> FormatWithIndex<F>
95 where
96 F: FnMut(&str, (usize, usize)) -> String,
97 {
98 FormatWithIndex::new(f)
99 }
100
101 /// Multiline a helper function for changing multiline content of cell.
102 /// Using this formatting applied for all rows not to a string as a whole.
103 ///
104 /// ```rust,no_run
105 /// use tabled::{Table, format::Format, object::Segment, Modify};
106 ///
107 /// let data: Vec<&'static str> = Vec::new();
108 /// let table = Table::new(&data)
109 /// .with(Modify::new(Segment::all()).with(Format::multiline(|s| format!("{}", s))))
110 /// .to_string();
111 /// ```
112 pub fn multiline<F>(f: F) -> Format<impl Fn(&str) -> String>
113 where
114 F: Fn(&str) -> String,
115 {
116 let closure = move |s: &str| {
117 let mut v = Vec::new();
118 for line in s.lines() {
119 v.push(f(line));
120 }
121
122 v.join("\n")
123 };
124
125 Format::new(closure)
126 }
127}
128
129impl<F, R> CellOption<R> for Format<F>
130where
131 F: FnMut(&str) -> String,
132 R: Records + RecordsMut<String>,
133{
134 fn change_cell(&mut self, table: &mut Table<R>, entity: Entity) {
135 let width_fn = CfgWidthFunction::from_cfg(table.get_config());
136 let (count_rows, count_cols) = table.shape();
137 for pos in entity.iter(count_rows, count_cols) {
138 let records = table.get_records();
139 let content = records.get_text(pos);
140 let content = (self.f)(content);
141 table.get_records_mut().set(pos, content, &width_fn);
142 }
143
144 table.destroy_width_cache();
145 table.destroy_height_cache();
146 }
147}
148
149/// [`FormatWithIndex`] is like a [`Format`] an abstraction over a function you can use against a cell.
150///
151/// It differerent from [`Format`] that it provides a row and column index.
152#[derive(Debug)]
153pub struct FormatWithIndex<F> {
154 f: F,
155}
156
157impl<F> FormatWithIndex<F>
158where
159 F: FnMut(&str, (usize, usize)) -> String,
160{
161 fn new(f: F) -> Self {
162 Self { f }
163 }
164}
165
166impl<F, R> CellOption<R> for FormatWithIndex<F>
167where
168 F: FnMut(&str, (usize, usize)) -> String,
169 R: Records + RecordsMut<String>,
170{
171 fn change_cell(&mut self, table: &mut Table<R>, entity: Entity) {
172 let width_fn = CfgWidthFunction::from_cfg(table.get_config());
173 let (count_rows, count_cols) = table.shape();
174 for pos in entity.iter(count_rows, count_cols) {
175 let records = table.get_records();
176 let content = records.get_text(pos);
177 let content = (self.f)(content, pos);
178 table.get_records_mut().set(pos, content, &width_fn);
179 }
180
181 table.destroy_width_cache();
182 table.destroy_height_cache();
183 }
184}
185
186impl<F, R> CellOption<R> for F
187where
188 F: FnMut(&str) -> String,
189 R: Records + RecordsMut<String>,
190{
191 fn change_cell(&mut self, table: &mut Table<R>, entity: Entity) {
192 Format::new(self).change_cell(table, entity);
193 }
194}
195
196impl<R> CellOption<R> for String
197where
198 R: Records + RecordsMut<String>,
199{
200 fn change_cell(&mut self, table: &mut Table<R>, entity: Entity) {
201 let width_fn = CfgWidthFunction::from_cfg(table.get_config());
202 let (count_rows, count_cols) = table.shape();
203 for pos in entity.iter(count_rows, count_cols) {
204 let text = self.clone();
205 table.get_records_mut().set(pos, text, &width_fn);
206 }
207
208 table.destroy_width_cache();
209 table.destroy_height_cache();
210 }
211}