stdpython/stdlib/
string.rs1use crate::{PyException, python_function};
7
8pub const ascii_lowercase: &str = "abcdefghijklmnopqrstuvwxyz";
10pub const ascii_uppercase: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
11pub const ascii_letters: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
12pub const digits: &str = "0123456789";
13pub const hexdigits: &str = "0123456789abcdefABCDEF";
14pub const octdigits: &str = "01234567";
15pub const punctuation: &str = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
16pub const printable: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c";
17pub const whitespace: &str = " \t\n\r\x0b\x0c";
18
19python_function! {
20 pub fn capwords<S>(s: S, sep: Option<String>) -> String
22 where [S: AsRef<str>]
23 [signature: (s, sep=None)]
24 [concrete_types: (String, Option<String>) -> String]
25 {
26 let s = s.as_ref();
27 match sep {
28 Some(separator) => {
29 s.split(&separator)
30 .map(|word| {
31 let mut chars: Vec<char> = word.chars().collect();
32 if let Some(first_char) = chars.first_mut() {
33 *first_char = first_char.to_uppercase().next().unwrap_or(*first_char);
34 }
35 for ch in chars.iter_mut().skip(1) {
36 *ch = ch.to_lowercase().next().unwrap_or(*ch);
37 }
38 chars.into_iter().collect::<String>()
39 })
40 .collect::<Vec<String>>()
41 .join(&separator)
42 }
43 None => {
44 s.split_whitespace()
45 .map(|word| {
46 let mut chars: Vec<char> = word.chars().collect();
47 if let Some(first_char) = chars.first_mut() {
48 *first_char = first_char.to_uppercase().next().unwrap_or(*first_char);
49 }
50 for ch in chars.iter_mut().skip(1) {
51 *ch = ch.to_lowercase().next().unwrap_or(*ch);
52 }
53 chars.into_iter().collect::<String>()
54 })
55 .collect::<Vec<String>>()
56 .join(" ")
57 }
58 }
59 }
60}
61
62#[derive(Debug, Clone)]
64pub struct Template {
65 template: String,
66 delimiter: char,
67}
68
69impl Template {
70 pub fn new<S: AsRef<str>>(template: S) -> Self {
72 Self {
73 template: template.as_ref().to_string(),
74 delimiter: '$',
75 }
76 }
77
78 pub fn with_delimiter<S: AsRef<str>>(template: S, delimiter: char) -> Self {
80 Self {
81 template: template.as_ref().to_string(),
82 delimiter,
83 }
84 }
85
86 pub fn substitute<K, V>(&self, mapping: &[(K, V)]) -> Result<String, PyException>
88 where
89 K: AsRef<str>,
90 V: AsRef<str>,
91 {
92 let mut result = self.template.clone();
93 let mut substituted = std::collections::HashSet::new();
94
95 for (key, value) in mapping {
97 let key_str = key.as_ref();
98
99 let simple_var = format!("{}{}", self.delimiter, key_str);
101 let braced_var = format!("{}{{{}}}", self.delimiter, key_str);
102
103 if result.contains(&simple_var) {
104 result = result.replace(&simple_var, value.as_ref());
105 substituted.insert(key_str.to_string());
106 }
107
108 if result.contains(&braced_var) {
109 result = result.replace(&braced_var, value.as_ref());
110 substituted.insert(key_str.to_string());
111 }
112 }
113
114 if result.contains(self.delimiter) {
116 let chars: Vec<char> = result.chars().collect();
118 let mut i = 0;
119 while i < chars.len() {
120 if chars[i] == self.delimiter {
121 if i + 1 < chars.len() {
122 if chars[i + 1] == '{' {
123 let mut end = i + 2;
125 while end < chars.len() && chars[end] != '}' {
126 end += 1;
127 }
128 if end < chars.len() {
129 let var_name: String = chars[i+2..end].iter().collect();
130 if !substituted.contains(&var_name) {
131 return Err(crate::key_error(format!("'{}' variable not provided", var_name)));
132 }
133 i = end + 1;
134 continue;
135 }
136 } else if chars[i + 1].is_ascii_alphabetic() || chars[i + 1] == '_' {
137 let mut end = i + 2;
139 while end < chars.len() && (chars[end].is_ascii_alphanumeric() || chars[end] == '_') {
140 end += 1;
141 }
142 let var_name: String = chars[i+1..end].iter().collect();
143 if !substituted.contains(&var_name) {
144 return Err(crate::key_error(format!("'{}' variable not provided", var_name)));
145 }
146 i = end;
147 continue;
148 }
149 }
150 }
151 i += 1;
152 }
153 }
154
155 Ok(result)
156 }
157
158 pub fn safe_substitute<K, V>(&self, mapping: &[(K, V)]) -> String
160 where
161 K: AsRef<str>,
162 V: AsRef<str>,
163 {
164 let mut result = self.template.clone();
165
166 for (key, value) in mapping {
167 let key_str = key.as_ref();
168 let simple_var = format!("{}{}", self.delimiter, key_str);
169 let braced_var = format!("{}{{{}}}", self.delimiter, key_str);
170
171 if result.contains(&simple_var) {
172 result = result.replace(&simple_var, value.as_ref());
173 }
174
175 if result.contains(&braced_var) {
176 result = result.replace(&braced_var, value.as_ref());
177 }
178 }
179
180 result
181 }
182
183 pub fn template_string(&self) -> &str {
185 &self.template
186 }
187
188 pub fn delimiter(&self) -> char {
190 self.delimiter
191 }
192}
193
194impl std::fmt::Display for Template {
195 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
196 write!(f, "{}", self.template)
197 }
198}
199
200#[derive(Debug)]
202pub struct Formatter;
203
204impl Formatter {
205 pub fn format<S: AsRef<str>>(template: S, args: &[&dyn std::fmt::Display]) -> String {
207 let template = template.as_ref();
208 let mut result = template.to_string();
209
210 for (i, arg) in args.iter().enumerate() {
211 let placeholder = format!("{{{}}}", i);
212 result = result.replace(&placeholder, &format!("{}", arg));
213 }
214
215 result
216 }
217
218 pub fn format_map<S: AsRef<str>, K: AsRef<str>, V: std::fmt::Display>(
220 template: S,
221 kwargs: &[(K, V)]
222 ) -> String {
223 let template = template.as_ref();
224 let mut result = template.to_string();
225
226 for (key, value) in kwargs {
227 let placeholder = format!("{{{}}}", key.as_ref());
228 result = result.replace(&placeholder, &format!("{}", value));
229 }
230
231 result
232 }
233
234 pub fn vformat<S: AsRef<str>>(template: S) -> Result<Vec<String>, PyException> {
236 let template = template.as_ref();
237 let mut placeholders = Vec::new();
238 let mut chars = template.chars().peekable();
239
240 while let Some(ch) = chars.next() {
241 if ch == '{' {
242 if chars.peek() == Some(&'{') {
243 chars.next(); continue;
245 }
246
247 let mut placeholder = String::new();
248 let mut found_end = false;
249
250 while let Some(ch) = chars.next() {
251 if ch == '}' {
252 found_end = true;
253 break;
254 }
255 placeholder.push(ch);
256 }
257
258 if !found_end {
259 return Err(crate::value_error("Unmatched '{' in format string"));
260 }
261
262 placeholders.push(placeholder);
263 } else if ch == '}' {
264 if chars.peek() != Some(&'}') {
265 return Err(crate::value_error("Unmatched '}' in format string"));
266 }
267 chars.next(); }
269 }
270
271 Ok(placeholders)
272 }
273}
274
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn test_capwords() {
282 assert_eq!(capwords("hello world", None), "Hello World");
283 assert_eq!(capwords("hello-world", Some("-".to_string())), "Hello-World");
284 assert_eq!(capwords("HELLO WORLD", None), "Hello World");
285 }
286
287 #[test]
288 fn test_template() {
289 let tmpl = Template::new("Hello $name! Welcome to $place.");
290 let mapping = [("name", "Alice"), ("place", "Wonderland")];
291
292 assert_eq!(
293 tmpl.substitute(&mapping).unwrap(),
294 "Hello Alice! Welcome to Wonderland."
295 );
296
297 let tmpl2 = Template::new("Hello ${name}! Welcome to ${place}.");
298 assert_eq!(
299 tmpl2.substitute(&mapping).unwrap(),
300 "Hello Alice! Welcome to Wonderland."
301 );
302 }
303
304 #[test]
305 fn test_template_missing_var() {
306 let tmpl = Template::new("Hello $name! Welcome to $place.");
307 let mapping = [("name", "Alice")]; assert!(tmpl.substitute(&mapping).is_err());
310 }
311
312 #[test]
313 fn test_safe_substitute() {
314 let tmpl = Template::new("Hello $name! Welcome to $place.");
315 let mapping = [("name", "Alice")]; assert_eq!(
318 tmpl.safe_substitute(&mapping),
319 "Hello Alice! Welcome to $place."
320 );
321 }
322}