stdpython/stdlib/
string.rs

1//! Python string module implementation
2//! 
3//! This module provides string-related constants and template classes.
4//! Implementation matches Python's string module API.
5
6use crate::{PyException, python_function};
7
8// String constants
9pub 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    /// string.capwords - capitalize words in string
21    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/// Template - simple template substitution
63#[derive(Debug, Clone)]
64pub struct Template {
65    template: String,
66    delimiter: char,
67}
68
69impl Template {
70    /// Create a new template
71    pub fn new<S: AsRef<str>>(template: S) -> Self {
72        Self {
73            template: template.as_ref().to_string(),
74            delimiter: '$',
75        }
76    }
77    
78    /// Create template with custom delimiter
79    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    /// Substitute variables from mapping
87    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        // Find all variable references
96        for (key, value) in mapping {
97            let key_str = key.as_ref();
98            
99            // Handle both $var and ${var} forms
100            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        // Check for unsubstituted variables
115        if result.contains(self.delimiter) {
116            // Look for remaining variable references
117            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                            // Find closing brace
124                            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                            // Find end of identifier
138                            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    /// Safe substitute - leave unmatched variables as-is
159    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    /// Get template string
184    pub fn template_string(&self) -> &str {
185        &self.template
186    }
187    
188    /// Get delimiter
189    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/// Formatter - string formatting operations
201#[derive(Debug)]
202pub struct Formatter;
203
204impl Formatter {
205    /// Format string with positional arguments
206    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    /// Format string with named arguments
219    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    /// Validate format string
235    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(); // Skip escaped brace
244                    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(); // Skip escaped brace
268            }
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")]; // missing 'place'
308        
309        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")]; // missing 'place'
316        
317        assert_eq!(
318            tmpl.safe_substitute(&mapping),
319            "Hello Alice! Welcome to $place."
320        );
321    }
322}