1#[derive(Debug, Clone, Copy)]
3pub struct Locale {
4 pub decimal_point: char,
6
7 pub thousands_sep: Option<char>,
11
12 pub grouping: [u8; 4],
18
19 pub group_repeat: bool,
22}
23
24impl Locale {
25 pub fn apply_grouping(&self, mut input: &str) -> String {
29 debug_assert!(input.bytes().all(|b| b.is_ascii_digit()));
30 let sep = self.thousands_sep.expect("no thousands separator");
31 let mut result = String::with_capacity(input.len() + self.separator_count(input.len()));
32 while !input.is_empty() {
33 let group_size = self.next_group_size(input.len());
34 let (group, rest) = input.split_at(group_size);
35 result.push_str(group);
36 if !rest.is_empty() {
37 result.push(sep);
38 }
39 input = rest;
40 }
41 result
42 }
43
44 fn next_group_size(&self, digits_left: usize) -> usize {
46 let mut accum: usize = 0;
47 for group in self.grouping {
48 if digits_left <= accum + group as usize {
49 return digits_left - accum;
50 }
51 accum += group as usize;
52 }
53 debug_assert!(digits_left >= accum);
56 let repeat_group = if self.group_repeat {
57 *self.grouping.last().unwrap()
58 } else {
59 0
60 };
61
62 if repeat_group == 0 {
63 digits_left - accum
65 } else {
66 let res = (digits_left - accum) % (repeat_group as usize);
69 if res > 0 {
70 res
71 } else {
72 repeat_group as usize
73 }
74 }
75 }
76
77 pub fn separator_count(&self, digits_count: usize) -> usize {
79 if self.thousands_sep.is_none() {
80 return 0;
81 }
82 let mut sep_count = 0;
83 let mut accum = 0;
84 for group in self.grouping {
85 if digits_count <= accum + group as usize {
86 return sep_count;
87 }
88 if group > 0 {
89 sep_count += 1;
90 }
91 accum += group as usize;
92 }
93 debug_assert!(digits_count >= accum);
94 let repeat_group = if self.group_repeat {
95 *self.grouping.last().unwrap()
96 } else {
97 0
98 };
99 if repeat_group > 0 && digits_count > accum {
102 sep_count += (digits_count - accum - 1) / repeat_group as usize;
103 }
104 sep_count
105 }
106}
107
108pub const C_LOCALE: Locale = Locale {
110 decimal_point: '.',
111 thousands_sep: None,
112 grouping: [0; 4],
113 group_repeat: false,
114};
115
116#[allow(dead_code)]
118pub const EN_US_LOCALE: Locale = Locale {
119 decimal_point: '.',
120 thousands_sep: Some(','),
121 grouping: [3, 3, 3, 3],
122 group_repeat: true,
123};
124
125#[test]
126fn test_apply_grouping() {
127 let input = "123456789";
128 let mut result: String;
129
130 assert_eq!(EN_US_LOCALE.thousands_sep, Some(','));
132 result = EN_US_LOCALE.apply_grouping(input);
133 assert_eq!(result, "123,456,789");
134
135 let input: &str = "1234567890123456";
137 let mut locale: Locale = C_LOCALE;
138 locale.thousands_sep = Some('!');
139
140 locale.grouping = [5, 3, 1, 0];
141 locale.group_repeat = false;
142 result = locale.apply_grouping(input);
143 assert_eq!(result, "1234567!8!901!23456");
144
145 locale.grouping = [5, 3, 1, 0];
147 locale.group_repeat = true;
148 result = locale.apply_grouping(input);
149 assert_eq!(result, "1234567!8!901!23456");
150
151 locale.grouping = [5, 3, 1, 2];
152 locale.group_repeat = false;
153 result = locale.apply_grouping(input);
154 assert_eq!(result, "12345!67!8!901!23456");
155
156 locale.grouping = [5, 3, 1, 2];
157 locale.group_repeat = true;
158 result = locale.apply_grouping(input);
159 assert_eq!(result, "1!23!45!67!8!901!23456");
160}
161
162#[test]
163#[should_panic]
164fn test_thousands_grouping_length_panics_if_no_sep() {
165 assert_eq!(C_LOCALE.thousands_sep, None);
167 C_LOCALE.apply_grouping("123");
168}
169
170#[test]
171fn test_thousands_grouping_length() {
172 fn validate_grouping_length_hint(locale: Locale, mut input: &str) {
173 loop {
174 let expected = locale.separator_count(input.len()) + input.len();
175 let actual = locale.apply_grouping(input).len();
176 assert_eq!(expected, actual);
177 if input.is_empty() {
178 break;
179 }
180 input = &input[1..];
181 }
182 }
183
184 validate_grouping_length_hint(EN_US_LOCALE, "123456789");
185
186 let input = "1234567890123456";
188 let mut locale: Locale = C_LOCALE;
189 locale.thousands_sep = Some('!');
190
191 locale.grouping = [5, 3, 1, 0];
192 locale.group_repeat = false;
193 validate_grouping_length_hint(locale, input);
194
195 locale.grouping = [5, 3, 1, 0];
197 locale.group_repeat = true;
198 validate_grouping_length_hint(locale, input);
199
200 locale.grouping = [5, 3, 1, 2];
201 locale.group_repeat = false;
202 validate_grouping_length_hint(locale, input);
203
204 locale.grouping = [5, 3, 1, 2];
205 locale.group_repeat = true;
206 validate_grouping_length_hint(locale, input);
207}