gather_all_code_from_crates/
reconstruct.rs

1crate::ix!();
2
3pub fn reconstruct_code_from_filtered_items(items: &[ItemInfo], omit_bodies: bool) -> String {
4
5    //println!("items: {:#?}", items);
6
7    let mut output = String::new();
8
9    for item in items {
10        match item {
11            ItemInfo::Function(f) => {
12                reconstruct_function(f, omit_bodies, &mut output, 0);
13            }
14            ItemInfo::Struct { attributes, is_public, signature, .. } => {
15                for attr in attributes {
16                    output.push_str(attr);
17                    output.push('\n');
18                }
19                if *is_public && !signature.starts_with("pub ") {
20                    //output.push_str("pub ");
21                }
22                output.push_str(signature);
23                output.push('\n');
24                output.push('\n');
25            }
26            ItemInfo::Enum { attributes, is_public, signature, .. } => {
27                for attr in attributes {
28                    output.push_str(attr);
29                    output.push('\n');
30                }
31                if *is_public && !signature.starts_with("pub ") {
32                    //output.push_str("pub ");
33                }
34                output.push_str(signature);
35                output.push('\n');
36                output.push('\n');
37            }
38            ItemInfo::TypeAlias { attributes, is_public, signature, .. } => {
39                for attr in attributes {
40                    output.push_str(attr);
41                    output.push('\n');
42                }
43                if *is_public && !signature.starts_with("pub ") {
44                    //output.push_str("pub ");
45                }
46                output.push_str(signature);
47                output.push('\n');
48                output.push('\n');
49            }
50            ItemInfo::ImplBlock { attributes, signature, methods, .. } => {
51                for attr in attributes {
52                    output.push_str(attr);
53                    output.push('\n');
54                }
55                // impl blocks do not have a pub keyword. Just print the cleaned signature.
56                output.push_str(signature);
57                output.push_str(" {\n");
58                for m in methods {
59                    reconstruct_function(m, omit_bodies, &mut output,4);
60                }
61                output.push_str("}\n\n");
62            }
63        }
64    }
65
66    output
67}
68
69
70pub fn reconstruct_function(f: &FunctionInfo, omit_bodies: bool, output: &mut String, indent_level: usize) {
71    let indent = " ".repeat(indent_level);
72
73    // Add attributes
74    for attr in f.attributes() {
75        output.push_str(&format!("{}{}\n", indent, attr.trim()));
76    }
77
78    // Add the function signature
79    let mut sig_line = f.signature().trim().to_string();
80
81    if omit_bodies {
82        if !sig_line.ends_with(';') {
83            sig_line.push(';');
84        }
85        output.push_str(&format!("{}{}\n", indent, sig_line.trim()));
86    } else {
87        if let Some(body) = f.body() {
88            let sig_line = sig_line.trim_end().trim_end_matches(';').trim_end();
89            output.push_str(&format!("{}{} ", indent, sig_line));
90
91            // Normalize and reindent the body
92            let normalized_body = normalize_and_reindent_body(body, indent_level + 4);
93            output.push_str("{\n");
94            output.push_str(&normalized_body);
95            output.push_str(&format!("\n{}}}\n", indent)); // Only one closing brace
96        } else {
97            if !sig_line.ends_with(';') {
98                sig_line.push(';');
99            }
100            output.push_str(&format!("{}{}\n", indent, sig_line.trim()));
101        }
102    }
103
104    // Ensure a blank line after each function
105    output.push('\n');
106}
107
108
109fn normalize_and_reindent_body(body: &str, target_indent: usize) -> String {
110    let lines: Vec<&str> = body.lines().collect();
111
112    // If the first line is just "{", and the last line is just "}", skip them.
113    let (start, end) = if !lines.is_empty()
114        && lines.first().map(|l| l.trim()) == Some("{")
115        && lines.last().map(|l| l.trim()) == Some("}")
116    {
117        (1, lines.len() - 1)
118    } else {
119        (0, lines.len())
120    };
121
122    let relevant_lines = &lines[start..end];
123
124    // Determine min indentation of non-empty lines
125    let min_indent = relevant_lines.iter()
126        .filter(|line| !line.trim().is_empty())
127        .map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
128        .min()
129        .unwrap_or(0);
130
131    let mut final_body = String::new();
132
133    for line in relevant_lines {
134        let trimmed_line = if line.trim().is_empty() {
135            "".to_string()  // preserve empty lines as empty
136        } else {
137            let start_idx = min_indent.min(line.len());
138            line[start_idx..].to_string()
139        };
140
141        let indented_line = format!("{}{}", " ".repeat(target_indent), trimmed_line);
142        final_body.push_str(&indented_line);
143        final_body.push('\n');
144    }
145
146    final_body
147}
148
149
150#[cfg(test)]
151mod reconstruct_code_from_filtered_fns_tests {
152    use super::*;
153
154    #[test]
155    fn test_reconstruct_code_from_filtered_fns_with_bodies() {
156
157        let fns = vec![
158            ItemInfo::Function(FunctionInfoBuilder::default()
159                .name("foo".to_string())
160                .is_public(false)
161                .is_test(false)
162                .attributes(vec!["#[inline]".to_string()])
163                .signature("fn foo(x: i32) -> i32".to_string())
164                .body(Some("{ x + 1 }".to_string()))
165                .build()
166                .unwrap()),
167                ItemInfo::Function(FunctionInfoBuilder::default()
168                    .name("bar".to_string())
169                    .is_public(true)
170                    .is_test(true)
171                    .attributes(vec!["#[test]".to_string()])
172                    .signature("pub fn bar()".to_string())
173                    .body(Some("{ assert_eq!(2,2); }".to_string()))
174                    .build()
175                    .unwrap()),
176        ];
177
178        let code = reconstruct_code_from_filtered_items(&fns, false);
179        assert!(code.contains("#[inline]\nfn foo(x: i32) -> i32 { x + 1 }"));
180        assert!(code.contains("#[test]\npub fn bar() { assert_eq!(2,2); }"));
181    }
182
183    #[test]
184    fn test_reconstruct_code_from_filtered_fns_omit_bodies() {
185
186        let fns = vec![
187            ItemInfo::Function(FunctionInfoBuilder::default()
188                .name("foo".to_string())
189                .is_public(false)
190                .is_test(false)
191                .attributes(vec!["#[inline]".to_string()])
192                .signature("fn foo(x: i32) -> i32".to_string())
193                .body(Some("{ x + 1 }".to_string()))
194                .build()
195                .unwrap()),
196        ];
197
198        let code = reconstruct_code_from_filtered_items(&fns, true);
199        // Body should be omitted and replaced with a semicolon
200        assert!(code.contains("#[inline]\nfn foo(x: i32) -> i32;"));
201        assert!(!code.contains("{ x + 1 }"));
202    }
203}