codebank/parser/formatter/
python.rs

1use crate::{BankStrategy, FileUnit, FunctionUnit, ModuleUnit, Result, StructUnit};
2
3pub trait PythonFormatter {
4    fn format_python(&self, strategy: BankStrategy) -> Result<String>;
5}
6
7impl PythonFormatter for FunctionUnit {
8    fn format_python(&self, strategy: BankStrategy) -> Result<String> {
9        let mut output = String::new();
10
11        match strategy {
12            BankStrategy::Default => {
13                if let Some(source) = &self.source {
14                    output.push_str(source);
15                }
16            }
17            BankStrategy::NoTests => {
18                // Skip test functions (marked with pytest decorators)
19                if self.attributes.iter().any(|attr| attr.contains("test")) {
20                    return Ok(String::new());
21                }
22                if let Some(source) = &self.source {
23                    output.push_str(source);
24                }
25            }
26            BankStrategy::Summary => {
27                // Skip private functions
28                if self.visibility == crate::Visibility::Private {
29                    return Ok(String::new());
30                }
31                // Add function signature only (no body)
32                if let Some(sig) = &self.signature {
33                    output.push_str(sig);
34                    output.push_str(" ...");
35                } else if let Some(source) = &self.source {
36                    // Try to extract just the signature from the source
37                    if let Some(idx) = source.find(':') {
38                        output.push_str(&source[0..=idx]);
39                        output.push_str(" ...");
40                    } else {
41                        // Fallback: use the whole source
42                        output.push_str(source);
43                    }
44                }
45            }
46        }
47
48        Ok(output)
49    }
50}
51
52impl PythonFormatter for StructUnit {
53    fn format_python(&self, strategy: BankStrategy) -> Result<String> {
54        let mut output = String::new();
55
56        match strategy {
57            BankStrategy::Default => {
58                if let Some(source) = &self.source {
59                    output.push_str(source);
60                }
61                // Format methods
62                for method in &self.methods {
63                    output.push_str("\n    ");
64                    output.push_str(&method.format_python(strategy)?);
65                }
66            }
67            BankStrategy::NoTests => {
68                if let Some(source) = &self.source {
69                    output.push_str(source);
70                }
71                // Include all non-test methods
72                for method in &self.methods {
73                    if !method.attributes.iter().any(|attr| attr.contains("test")) {
74                        output.push_str("\n    ");
75                        output.push_str(&method.format_python(strategy)?);
76                    }
77                }
78            }
79            BankStrategy::Summary => {
80                // Skip private classes
81                if self.visibility == crate::Visibility::Private {
82                    return Ok(String::new());
83                }
84                if let Some(source) = &self.source {
85                    // Extract class definition
86                    if let Some(idx) = source.find(':') {
87                        output.push_str(&source[0..=idx]);
88                        output.push('\n');
89                    }
90                }
91                // Format public methods only
92                for method in &self.methods {
93                    if method.visibility == crate::Visibility::Public {
94                        output.push_str("    ");
95                        output.push_str(&method.format_python(strategy)?);
96                        output.push('\n');
97                    }
98                }
99            }
100        }
101
102        Ok(output)
103    }
104}
105
106impl PythonFormatter for ModuleUnit {
107    fn format_python(&self, strategy: BankStrategy) -> Result<String> {
108        let mut output = String::new();
109
110        match strategy {
111            BankStrategy::Default => {
112                if let Some(source) = &self.source {
113                    output.push_str(source);
114                    output.push_str("\n\n");
115                }
116            }
117            BankStrategy::NoTests => {
118                // Skip test modules (prefixed with test_)
119                if self.attributes.iter().any(|attr| attr.contains("test_"))
120                    || self.name.starts_with("test_")
121                {
122                    return Ok(String::new());
123                }
124                // Include all declarations
125                for decl in &self.declares {
126                    output.push_str(&decl.source);
127                    output.push('\n');
128                }
129                // Format all non-test functions and classes
130                for function in &self.functions {
131                    let formatted = function.format_python(strategy)?;
132                    if !formatted.is_empty() {
133                        output.push_str(&formatted);
134                        output.push_str("\n\n");
135                    }
136                }
137                for class in &self.structs {
138                    let formatted = class.format_python(strategy)?;
139                    if !formatted.is_empty() {
140                        output.push_str(&formatted);
141                        output.push_str("\n\n");
142                    }
143                }
144            }
145            BankStrategy::Summary => {
146                // Skip private modules
147                if self.visibility == crate::Visibility::Private {
148                    return Ok(String::new());
149                }
150                // Format public functions and classes only
151                for function in &self.functions {
152                    if function.visibility == crate::Visibility::Public {
153                        let formatted = function.format_python(strategy)?;
154                        if !formatted.is_empty() {
155                            output.push_str(&formatted);
156                            output.push('\n');
157                        }
158                    }
159                }
160                for class in &self.structs {
161                    if class.visibility == crate::Visibility::Public {
162                        let formatted = class.format_python(strategy)?;
163                        if !formatted.is_empty() {
164                            output.push_str(&formatted);
165                            output.push('\n');
166                        }
167                    }
168                }
169            }
170        }
171
172        Ok(output)
173    }
174}
175
176impl PythonFormatter for FileUnit {
177    fn format_python(&self, strategy: BankStrategy) -> Result<String> {
178        let mut output = String::new();
179
180        match strategy {
181            BankStrategy::Default => {
182                if let Some(source) = &self.source {
183                    output.push_str(source);
184                }
185            }
186            BankStrategy::NoTests | BankStrategy::Summary => {
187                // Add declarations first
188                for decl in &self.declares {
189                    output.push_str(&decl.source);
190                    output.push('\n');
191                }
192
193                // Add modules
194                for module in &self.modules {
195                    let formatted = module.format_python(strategy)?;
196                    if !formatted.is_empty() {
197                        output.push_str(&formatted);
198                        output.push_str("\n\n");
199                    }
200                }
201
202                // Add functions
203                for function in &self.functions {
204                    let formatted = function.format_python(strategy)?;
205                    if !formatted.is_empty() {
206                        output.push_str(&formatted);
207                        output.push_str("\n\n");
208                    }
209                }
210
211                // Add classes
212                for class in &self.structs {
213                    let formatted = class.format_python(strategy)?;
214                    if !formatted.is_empty() {
215                        output.push_str(&formatted);
216                        output.push_str("\n\n");
217                    }
218                }
219            }
220        }
221
222        Ok(output)
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use crate::{parser::FieldUnit, *};
229
230    // Helper to create a test function
231    fn create_test_function(name: &str, is_public: bool, has_test_attr: bool) -> FunctionUnit {
232        let mut attrs = Vec::new();
233        if has_test_attr {
234            attrs.push("@pytest.mark.test".to_string());
235        }
236
237        FunctionUnit {
238            name: name.to_string(),
239            attributes: attrs,
240            visibility: if is_public {
241                Visibility::Public
242            } else {
243                Visibility::Private
244            },
245            doc: Some(format!("Documentation for {}", name)),
246            signature: Some(format!("def {}():", name)),
247            body: Some("    pass".to_string()),
248            source: Some(format!("def {}():\n    pass", name)),
249        }
250    }
251
252    // Helper to create a test class
253    fn create_test_class(name: &str, is_public: bool) -> StructUnit {
254        let mut methods = Vec::new();
255        methods.push(create_test_function(
256            &format!("{}_method", name.to_lowercase()),
257            true,
258            false,
259        ));
260        // Add a private method as well
261        methods.push(create_test_function(
262            &format!("_{}_private_method", name.to_lowercase()),
263            false,
264            false,
265        ));
266
267        StructUnit {
268            name: name.to_string(),
269            head: format!("class {}", name),
270            attributes: Vec::new(),
271            visibility: if is_public {
272                Visibility::Public
273            } else {
274                Visibility::Private
275            },
276            doc: Some(format!("Documentation for {}", name)),
277            methods,
278            source: Some(format!("class {}:\n    pass", name)),
279            fields: Vec::new(),
280        }
281    }
282
283    // Helper to create a test module
284    fn create_test_module(name: &str, is_public: bool, is_test: bool) -> ModuleUnit {
285        let functions = vec![
286            create_test_function("module_function", true, false),
287            // Add a private function
288            create_test_function("_module_private_function", false, false),
289        ];
290
291        let structs = vec![create_test_class("ModuleClass", true)];
292
293        let mut attributes = Vec::new();
294        if is_test {
295            attributes.push("test_".to_string());
296        }
297
298        // Add declarations
299        let mut declares = Vec::new();
300        declares.push(DeclareStatements {
301            source: "from typing import List, Dict".to_string(),
302            kind: DeclareKind::Import,
303        });
304
305        ModuleUnit {
306            name: name.to_string(),
307            attributes,
308            doc: Some(format!("Documentation for module {}", name)),
309            visibility: if is_public {
310                Visibility::Public
311            } else {
312                Visibility::Private
313            },
314            functions,
315            structs,
316            traits: Vec::new(),
317            impls: Vec::new(),
318            submodules: Vec::new(),
319            declares,
320            source: Some(format!("# Module {}", name)),
321        }
322    }
323
324    #[test]
325    fn test_function_formatter_default() {
326        let function = create_test_function("test_function", true, false);
327        let formatted = function
328            .format(&BankStrategy::Default, LanguageType::Python)
329            .unwrap();
330        assert!(formatted.contains("def test_function():"));
331        assert!(formatted.contains("pass"));
332    }
333
334    #[test]
335    fn test_function_formatter_no_tests() {
336        // Regular function
337        let function = create_test_function("regular_function", true, false);
338        let formatted = function
339            .format(&BankStrategy::NoTests, LanguageType::Python)
340            .unwrap();
341        assert!(formatted.contains("def regular_function():"));
342        assert!(formatted.contains("pass"));
343
344        // Test function
345        let test_function = create_test_function("test_function", true, true);
346        let formatted = test_function
347            .format(&BankStrategy::NoTests, LanguageType::Python)
348            .unwrap();
349        assert!(formatted.is_empty());
350    }
351
352    #[test]
353    fn test_function_formatter_summary() {
354        // Public function
355        let public_function = create_test_function("public_function", true, false);
356        let formatted = public_function
357            .format(&BankStrategy::Summary, LanguageType::Python)
358            .unwrap();
359        assert!(formatted.contains("def public_function():"));
360        assert!(formatted.contains("..."));
361        assert!(!formatted.contains("pass"));
362
363        // Private function
364        let private_function = create_test_function("_private_function", false, false);
365        let formatted = private_function
366            .format(&BankStrategy::Summary, LanguageType::Python)
367            .unwrap();
368        assert!(formatted.is_empty());
369    }
370
371    #[test]
372    fn test_class_formatter_default() {
373        let class_unit = create_test_class("TestClass", true);
374        let formatted = class_unit
375            .format(&BankStrategy::Default, LanguageType::Python)
376            .unwrap();
377        assert!(formatted.contains("class TestClass:"));
378        assert!(formatted.contains("pass"));
379    }
380
381    #[test]
382    fn test_class_formatter_summary() {
383        // Public class
384        let mut public_class = create_test_class("PublicClass", true);
385
386        // Add a field to the class
387        let field = FieldUnit {
388            name: "field".to_string(),
389            doc: Some("Field documentation".to_string()),
390            attributes: vec![],
391            source: Some("field = None".to_string()),
392        };
393        public_class.fields.push(field);
394
395        let formatted = public_class
396            .format(&BankStrategy::Summary, LanguageType::Python)
397            .unwrap();
398
399        assert!(
400            formatted.contains("class PublicClass:"),
401            "Should include class definition"
402        );
403        assert!(formatted.contains("field = None"), "Should include fields");
404        assert!(
405            formatted.contains("def publicclass_method"),
406            "Should include public methods"
407        );
408        assert!(
409            !formatted.contains("def _publicclass_private_method"),
410            "Should not include private methods"
411        );
412
413        // Private class
414        let private_class = create_test_class("_PrivateClass", false);
415        let formatted = private_class
416            .format(&BankStrategy::Summary, LanguageType::Python)
417            .unwrap();
418        assert!(formatted.is_empty());
419    }
420
421    #[test]
422    fn test_module_formatter_default() {
423        let module = create_test_module("test_module", true, false);
424        let formatted = module
425            .format(&BankStrategy::Default, LanguageType::Python)
426            .unwrap();
427        assert!(formatted.contains("# Module test_module"));
428    }
429
430    #[test]
431    fn test_module_formatter_no_tests() {
432        // Regular module
433        let module = create_test_module("regular_module", true, false);
434        let formatted = module
435            .format(&BankStrategy::NoTests, LanguageType::Python)
436            .unwrap();
437        // Check for essential elements
438        assert!(formatted.contains("def module_function"));
439        assert!(formatted.contains("class ModuleClass"));
440        assert!(formatted.contains("from typing import List, Dict"));
441        assert!(formatted.contains("def _module_private_function")); // Check private function included
442
443        // Test module - should also be processed by NoTests, skipping inner tests if any
444        let test_module = create_test_module("test_module", true, true);
445        let formatted_test = test_module
446            .format(&BankStrategy::NoTests, LanguageType::Python)
447            .unwrap();
448        assert!(!formatted_test.is_empty()); // Should not be empty
449        assert!(formatted_test.contains("def module_function")); // Check content is present
450        assert!(formatted_test.contains("class ModuleClass"));
451    }
452
453    #[test]
454    fn test_module_formatter_summary() {
455        // Public module
456        let public_module = create_test_module("public_module", true, false);
457        let formatted = public_module
458            .format(&BankStrategy::Summary, LanguageType::Python)
459            .unwrap();
460        assert!(formatted.contains("def module_function():"));
461        assert!(formatted.contains("..."));
462        assert!(!formatted.contains("pass"));
463
464        // Private module
465        let private_module = create_test_module("_private_module", false, false);
466        let formatted = private_module
467            .format(&BankStrategy::Summary, LanguageType::Python)
468            .unwrap();
469        assert!(formatted.is_empty());
470    }
471}