etop_format/table_formats/
column_format.rs

1use crate::{
2    BinaryFormat, BoolFormat, CellFormat, CellFormatShorthand, FormatError, NumberFormat,
3    StringFormat, UnknownFormat,
4};
5use polars::prelude::*;
6use unicode_truncate::{Alignment, UnicodeTruncateStr};
7
8/// column format shorthand
9#[derive(Debug, Clone)]
10pub struct ColumnFormatShorthand {
11    /// name
12    pub name: String,
13    /// display name
14    pub display_name: String,
15    /// format
16    pub format: CellFormatShorthand,
17    /// alignment
18    pub align: ColumnAlign,
19}
20
21impl ColumnFormatShorthand {
22    /// finalize shorthand into format format
23    pub fn finalize(self, dtype: &DataType) -> Result<ColumnFormat, FormatError> {
24        Ok(ColumnFormat {
25            name: self.name,
26            display_name: self.display_name,
27            format: self.format.finalize(dtype)?,
28            align: self.align,
29        })
30    }
31}
32
33impl Default for ColumnFormatShorthand {
34    fn default() -> ColumnFormatShorthand {
35        let format = UnknownFormat { min_width: None, max_width: None };
36        ColumnFormatShorthand {
37            name: "".to_string(),
38            display_name: "".to_string(),
39            format: CellFormatShorthand::Unknown(format),
40            align: ColumnAlign::Right,
41        }
42    }
43}
44
45/// column format
46#[derive(Debug, Clone)]
47pub struct ColumnFormat {
48    /// name
49    pub name: String,
50    /// display name
51    pub display_name: String,
52    /// format
53    pub format: CellFormat,
54    /// alignment
55    pub align: ColumnAlign,
56}
57
58/// column alignment
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum ColumnAlign {
61    /// left
62    Left,
63    /// right
64    Right,
65}
66
67impl ColumnFormat {
68    /// get header width
69    pub fn header_width(&self) -> usize {
70        self.display_name
71            .split('\n')
72            // .map(unicode_width::UnicodeWidthStr::width)
73            // .map(unicode_width::UnicodeWidthStr::width_cjk)
74            .map(|s| s.chars().count())
75            .max()
76            .unwrap_or(0)
77    }
78
79    /// get min width
80    pub fn get_min_width(&self) -> usize {
81        self.format.get_min_width().unwrap_or(0)
82    }
83
84    /// get max width
85    pub fn get_max_width(&self) -> usize {
86        self.format.get_max_width().unwrap_or(usize::MAX)
87    }
88
89    /// format series
90    pub fn format(&self, series: &Series) -> Result<Vec<String>, FormatError> {
91        let formatted: Result<Vec<String>, FormatError> = match series.dtype() {
92            DataType::Binary => {
93                let fmt: BinaryFormat = self.format.clone().try_into()?;
94                series.binary()?.into_iter().map(|v| fmt.format_option(v, "")).collect()
95            }
96            DataType::Utf8 => {
97                let fmt: StringFormat = self.format.clone().try_into()?;
98                series.utf8()?.into_iter().map(|v| fmt.format_option(v, "")).collect()
99            }
100            dtype if dtype.is_numeric() => {
101                let fmt: NumberFormat = self.format.clone().try_into()?;
102                series.to_float()?.f64()?.into_iter().map(|v| fmt.format_option(v, "")).collect()
103            }
104            DataType::Boolean => {
105                let fmt: BoolFormat = self.format.clone().try_into()?;
106                series.bool()?.into_iter().map(|v| fmt.format_option(v, "")).collect()
107            }
108            dtype => {
109                let message = format!("column {} has type {}", series.name(), dtype);
110                return Err(FormatError::UnsupportedDatatype(message));
111            }
112        };
113        let formatted = formatted?;
114
115        let max_width = formatted
116            .iter()
117            // .map(|s| unicode_width::UnicodeWidthStr::width(s.as_str()))
118            // .map(|s| unicode_width::UnicodeWidthStr::width_cjk(s.as_str()))
119            .map(|s| s.chars().count())
120            .max()
121            .unwrap_or(0);
122
123        let formatted = if self.align == ColumnAlign::Right {
124            formatted
125                .into_iter()
126                .map(|s| s.unicode_pad(max_width, Alignment::Right, true).to_string())
127                .collect()
128        } else {
129            formatted
130                .into_iter()
131                .map(|s| s.unicode_pad(max_width, Alignment::Left, true).to_string())
132                .collect()
133        };
134
135        Ok(formatted)
136    }
137}
138
139// builder
140impl ColumnFormat {
141    /// set name
142    pub fn name<T: AsRef<str>>(mut self, name: T) -> ColumnFormat {
143        let name = name.as_ref().to_string();
144        self.name = name.clone();
145        if self.display_name.is_empty() {
146            self.display_name = name
147        };
148        self
149    }
150
151    /// set display name
152    pub fn display_name<T: AsRef<str>>(mut self, display_name: T) -> ColumnFormat {
153        self.display_name = display_name.as_ref().to_string();
154        self
155    }
156
157    /// set newline underscores
158    pub fn newline_underscores(mut self) -> ColumnFormat {
159        self.display_name = self.display_name.replace('_', "\n");
160        self
161    }
162
163    /// set width
164    pub fn width(self, width: usize) -> ColumnFormat {
165        self.min_width(width).max_width(width)
166    }
167
168    /// set min width
169    pub fn min_width(mut self, width: usize) -> ColumnFormat {
170        self.format = self.format.min_width(width);
171        self
172    }
173
174    /// set max width
175    pub fn max_width(mut self, width: usize) -> ColumnFormat {
176        self.format = self.format.max_width(width);
177        self
178    }
179}
180
181// builder
182impl ColumnFormatShorthand {
183    /// new
184    pub fn new() -> ColumnFormatShorthand {
185        ColumnFormatShorthand::default()
186    }
187
188    /// set name
189    pub fn name<T: AsRef<str>>(mut self, name: T) -> ColumnFormatShorthand {
190        let name = name.as_ref().to_string();
191        self.name = name.clone();
192        if self.display_name.is_empty() {
193            self.display_name = name
194        };
195        self
196    }
197
198    /// set display name
199    pub fn display_name<T: AsRef<str>>(mut self, display_name: T) -> ColumnFormatShorthand {
200        self.display_name = display_name.as_ref().to_string();
201        self
202    }
203
204    /// set newline underscores
205    pub fn newline_underscores(mut self) -> ColumnFormatShorthand {
206        self.display_name = self.display_name.replace('_', "\n");
207        self
208    }
209
210    /// set width
211    pub fn width(self, width: usize) -> ColumnFormatShorthand {
212        self.min_width(width).max_width(width)
213    }
214
215    /// set min width
216    pub fn min_width(mut self, width: usize) -> ColumnFormatShorthand {
217        self.format = self.format.min_width(width);
218        self
219    }
220
221    /// set max width
222    pub fn max_width(mut self, width: usize) -> ColumnFormatShorthand {
223        self.format = self.format.max_width(width);
224        self
225    }
226
227    /// set format
228    pub fn set_format<T: Into<CellFormatShorthand>>(mut self, format: T) -> ColumnFormatShorthand {
229        self.format = format.into();
230        self
231    }
232}