pyo3_stub_gen/generate/docstring.rs
1use std::fmt;
2
3/// Normalize a docstring by trimming outer whitespace and dedenting.
4///
5/// Implements Python's inspect.cleandoc() behavior:
6/// 1. Trim leading/trailing whitespace from the entire string
7/// 2. Find minimum indentation of non-empty lines (skip first line)
8/// 3. Remove that indentation from all lines
9///
10/// # Examples
11/// ```
12/// # use pyo3_stub_gen::generate::normalize_docstring;
13/// let doc = r#"
14/// First line
15/// Second line
16/// Indented line
17/// "#;
18/// let normalized = normalize_docstring(doc);
19/// assert_eq!(normalized, "First line\nSecond line\n Indented line");
20/// ```
21pub fn normalize_docstring(doc: &str) -> String {
22 let doc = doc.trim();
23 if doc.is_empty() {
24 return String::new();
25 }
26
27 let lines: Vec<&str> = doc.lines().collect();
28
29 // Find minimum indentation of non-empty lines (skip first line)
30 let min_indent = lines
31 .iter()
32 .skip(1)
33 .filter(|line| !line.trim().is_empty())
34 .map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
35 .min()
36 .unwrap_or(0);
37
38 // Build normalized lines with dedenting applied
39 let normalized_lines: Vec<String> = lines
40 .iter()
41 .enumerate()
42 .map(|(i, line)| {
43 if i == 0 {
44 // First line: use as-is (already trimmed by outer trim())
45 line.to_string()
46 } else if line.trim().is_empty() {
47 // Empty line: keep it but remove whitespace
48 String::new()
49 } else {
50 // Other lines: remove common indentation
51 if line.len() >= min_indent {
52 line[min_indent..].to_string()
53 } else {
54 line.trim_start().to_string()
55 }
56 }
57 })
58 .collect();
59
60 normalized_lines.join("\n")
61}
62
63pub fn write_docstring(f: &mut fmt::Formatter, doc: &str, indent: &str) -> fmt::Result {
64 // Docstrings should already be normalized, but trim again for safety
65 let doc = doc.trim();
66 if !doc.is_empty() {
67 writeln!(f, r#"{indent}r""""#)?;
68 for line in doc.lines() {
69 writeln!(f, "{indent}{line}")?;
70 }
71 writeln!(f, r#"{indent}""""#)?;
72 }
73 Ok(())
74}