1use crate::Cell;
5
6pub trait CellFormatter: Send + Sync {
11 fn format(&self, cell: &Cell) -> String;
13}
14
15pub struct DefaultFormatter;
19
20impl CellFormatter for DefaultFormatter {
21 fn format(&self, cell: &Cell) -> String {
22 cell.to_string()
23 }
24}
25
26pub struct NumberFormatter {
31 pub decimal_places: usize,
34 pub thousands_separator: bool,
36}
37
38impl NumberFormatter {
39 fn format_integer(n: i64, thousands: bool) -> String {
41 let s = n.unsigned_abs().to_string();
42 let with_sep = if thousands {
43 insert_thousands(s)
44 } else {
45 n.unsigned_abs().to_string()
46 };
47 if n < 0 {
48 format!("-{with_sep}")
49 } else {
50 with_sep
51 }
52 }
53
54 fn format_float(&self, v: f64) -> String {
57 let formatted = format!("{v:.prec$}", prec = self.decimal_places);
58 if !self.thousands_separator {
59 return formatted;
60 }
61 match formatted.split_once('.') {
63 Some((int_part, dec_part)) => {
64 let neg = int_part.starts_with('-');
65 let digits = if neg { &int_part[1..] } else { int_part };
66 let int_sep = insert_thousands(digits.to_owned());
67 let sign = if neg { "-" } else { "" };
68 format!("{sign}{int_sep}.{dec_part}")
69 }
70 None => insert_thousands(formatted),
71 }
72 }
73}
74
75fn insert_thousands(s: String) -> String {
77 let chars: Vec<char> = s.chars().collect();
78 let len = chars.len();
79 let mut out = String::with_capacity(len + len / 3);
80 for (i, c) in chars.iter().enumerate() {
81 if i > 0 && (len - i).is_multiple_of(3) {
82 out.push(',');
83 }
84 out.push(*c);
85 }
86 out
87}
88
89impl CellFormatter for NumberFormatter {
90 fn format(&self, cell: &Cell) -> String {
91 match cell {
92 Cell::Int(n) => {
93 if self.decimal_places == 0 {
94 Self::format_integer(*n, self.thousands_separator)
95 } else {
96 self.format_float(*n as f64)
97 }
98 }
99 Cell::Float(v) => self.format_float(*v),
100 other => DefaultFormatter.format(other),
101 }
102 }
103}
104
105pub struct DateFormatter {
117 pub fmt: String,
119}
120
121impl CellFormatter for DateFormatter {
122 fn format(&self, cell: &Cell) -> String {
123 cell.to_string()
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn default_formatter_strings() {
135 let f = DefaultFormatter;
136 assert_eq!(f.format(&Cell::Text("hi".into())), "hi");
137 assert_eq!(f.format(&Cell::Int(42)), "42");
138 assert_eq!(f.format(&Cell::Empty), "");
139 }
140
141 #[test]
142 fn number_formatter_decimal() {
143 let f = NumberFormatter {
144 decimal_places: 2,
145 thousands_separator: false,
146 };
147 assert_eq!(f.format(&Cell::Float(1.5)), "1.50");
148 assert_eq!(f.format(&Cell::Float(9.876_54)), "9.88");
149 }
150
151 #[test]
152 fn number_formatter_thousands() {
153 let f = NumberFormatter {
154 decimal_places: 0,
155 thousands_separator: true,
156 };
157 assert_eq!(f.format(&Cell::Int(1_000_000)), "1,000,000");
158 assert_eq!(f.format(&Cell::Int(1_234)), "1,234");
159 assert_eq!(f.format(&Cell::Int(999)), "999");
160 }
161
162 #[test]
163 fn number_formatter_negative() {
164 let f = NumberFormatter {
165 decimal_places: 0,
166 thousands_separator: true,
167 };
168 assert_eq!(f.format(&Cell::Int(-1_000)), "-1,000");
169 }
170
171 #[test]
172 fn number_formatter_float_thousands() {
173 let f = NumberFormatter {
174 decimal_places: 2,
175 thousands_separator: true,
176 };
177 assert_eq!(f.format(&Cell::Float(1234567.891)), "1,234,567.89");
178 }
179
180 #[test]
181 fn date_formatter_passthrough() {
182 let f = DateFormatter {
183 fmt: "%Y-%m-%d".into(),
184 };
185 assert_eq!(f.format(&Cell::Text("2026-05-29".into())), "2026-05-29");
186 }
187}