nautilus_core/
formatting.rs1fn separate_with(s: &str, sep: char) -> String {
19 let (neg, digits) = if let Some(rest) = s.strip_prefix('-') {
20 (true, rest)
21 } else {
22 (false, s)
23 };
24
25 let (int_part, dec_part) = match digits.find('.') {
26 Some(pos) => (&digits[..pos], Some(&digits[pos..])),
27 None => (digits, None),
28 };
29
30 let mut result = String::with_capacity(s.len() + int_part.len() / 3);
31
32 if neg {
33 result.push('-');
34 }
35
36 let chars: Vec<char> = int_part.chars().collect();
37 for (i, c) in chars.iter().enumerate() {
38 if i > 0 && (chars.len() - i).is_multiple_of(3) {
39 result.push(sep);
40 }
41 result.push(*c);
42 }
43
44 if let Some(dec) = dec_part {
45 result.push_str(dec);
46 }
47
48 result
49}
50
51pub trait Separable {
55 fn separate_with_commas(&self) -> String;
57
58 fn separate_with_underscores(&self) -> String;
60}
61
62macro_rules! impl_separable {
63 ($($t:ty),*) => {
64 $(
65 impl Separable for $t {
66 fn separate_with_commas(&self) -> String {
67 separate_with(&self.to_string(), ',')
68 }
69
70 fn separate_with_underscores(&self) -> String {
71 separate_with(&self.to_string(), '_')
72 }
73 }
74 )*
75 };
76}
77
78impl_separable!(
79 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
80);
81
82impl Separable for String {
83 fn separate_with_commas(&self) -> String {
84 separate_with(self, ',')
85 }
86
87 fn separate_with_underscores(&self) -> String {
88 separate_with(self, '_')
89 }
90}
91
92impl Separable for &str {
93 fn separate_with_commas(&self) -> String {
94 separate_with(self, ',')
95 }
96
97 fn separate_with_underscores(&self) -> String {
98 separate_with(self, '_')
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use rstest::rstest;
105
106 use super::*;
107
108 #[rstest]
109 #[case(0, "0")]
110 #[case(1, "1")]
111 #[case(12, "12")]
112 #[case(123, "123")]
113 #[case(1234, "1,234")]
114 #[case(12345, "12,345")]
115 #[case(123456, "123,456")]
116 #[case(1234567, "1,234,567")]
117 #[case(-1234, "-1,234")]
118 #[case(-1234567, "-1,234,567")]
119 fn test_separate_with_commas(#[case] input: i64, #[case] expected: &str) {
120 assert_eq!(input.separate_with_commas(), expected);
121 }
122
123 #[rstest]
124 #[case(1234, "1_234")]
125 #[case(1234567, "1_234_567")]
126 fn test_separate_with_underscores(#[case] input: i64, #[case] expected: &str) {
127 assert_eq!(input.separate_with_underscores(), expected);
128 }
129
130 #[rstest]
131 fn test_float_with_decimal() {
132 assert_eq!(1234.56_f64.separate_with_commas(), "1,234.56");
133 assert_eq!(1234567.89_f64.separate_with_underscores(), "1_234_567.89");
134 }
135
136 #[rstest]
137 fn test_string() {
138 assert_eq!("1234567".separate_with_commas(), "1,234,567");
139 assert_eq!("1234.5678".separate_with_underscores(), "1_234.5678");
140 }
141}