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}