Skip to main content

alef_codegen/
builder.rs

1use std::fmt::Write;
2
3/// Builder for constructing Rust source files.
4#[derive(Debug, Default)]
5pub struct RustFileBuilder {
6    doc_header: Option<String>,
7    imports: Vec<String>,
8    items: Vec<String>,
9}
10
11impl RustFileBuilder {
12    pub fn new() -> Self {
13        Self::default()
14    }
15
16    /// Add a "DO NOT EDIT" header.
17    pub fn with_generated_header(mut self) -> Self {
18        self.doc_header = Some(
19            "// This file is auto-generated by alef. DO NOT EDIT.\n\
20             // Re-generate with: alef generate\n"
21                .to_string(),
22        );
23        self
24    }
25
26    /// Add a crate-level inner attribute (e.g., `#![allow(clippy::...)]`).
27    /// These are placed at the very top of the file, before imports.
28    pub fn add_inner_attribute(&mut self, attr: &str) {
29        // Append to doc_header since it comes first
30        if let Some(ref mut header) = self.doc_header {
31            header.push_str(&format!("#![{attr}]\n"));
32        } else {
33            self.doc_header = Some(format!("#![{attr}]\n"));
34        }
35    }
36
37    /// Add a use import line.
38    /// Single-component imports (e.g. `use serde_json;`) are skipped since they are
39    /// redundant in Rust 2018+ where extern crates are automatically in scope.
40    pub fn add_import(&mut self, import: &str) {
41        // Skip single-component path imports — they trigger clippy::single_component_path_imports
42        // and are redundant in edition 2018+. A single component has no `::`, `*`, or `{`.
43        if !import.contains("::") && !import.contains('*') && !import.contains('{') {
44            return;
45        }
46        if !self.imports.iter().any(|i| i == import) {
47            self.imports.push(import.to_string());
48        }
49    }
50
51    /// Add a raw code item (struct, impl, function, etc.).
52    pub fn add_item(&mut self, item: &str) {
53        self.items.push(item.to_string());
54    }
55
56    /// Build the final source string.
57    pub fn build(&self) -> String {
58        let mut capacity = 256; // base header + imports + newlines
59        if let Some(header) = &self.doc_header {
60            capacity += header.len() + 1;
61        }
62        capacity += self.imports.iter().map(|i| i.len() + 6).sum::<usize>(); // "use ...;\n"
63        capacity += self.items.iter().map(|i| i.len() + 2).sum::<usize>(); // item + "\n\n"
64
65        let mut out = String::with_capacity(capacity);
66
67        if let Some(header) = &self.doc_header {
68            out.push_str(header);
69            out.push('\n');
70        }
71
72        if !self.imports.is_empty() {
73            for import in &self.imports {
74                writeln!(out, "use {import};").ok();
75            }
76            out.push('\n');
77        }
78
79        for (i, item) in self.items.iter().enumerate() {
80            out.push_str(item);
81            if i < self.items.len() - 1 {
82                out.push_str("\n\n");
83            }
84        }
85
86        if !out.ends_with('\n') {
87            out.push('\n');
88        }
89
90        out
91    }
92}
93
94/// Helper to build a struct with attributes.
95pub struct StructBuilder {
96    attrs: Vec<String>,
97    visibility: String,
98    name: String,
99    derives: Vec<String>,
100    fields: Vec<(String, String, Vec<String>, String)>, // (name, type, field_attrs, doc)
101}
102
103impl StructBuilder {
104    pub fn new(name: &str) -> Self {
105        Self {
106            attrs: vec![],
107            visibility: String::from("pub"),
108            name: name.to_string(),
109            derives: vec![],
110            fields: vec![],
111        }
112    }
113
114    pub fn add_attr(&mut self, attr: &str) -> &mut Self {
115        self.attrs.push(attr.to_string());
116        self
117    }
118
119    pub fn add_derive(&mut self, derive: &str) -> &mut Self {
120        if !self.derives.iter().any(|d| d == derive) {
121            self.derives.push(derive.to_string());
122        }
123        self
124    }
125
126    pub fn add_field(&mut self, name: &str, ty: &str, attrs: Vec<String>) -> &mut Self {
127        self.add_field_with_doc(name, ty, attrs, "")
128    }
129
130    pub fn add_field_with_doc(&mut self, name: &str, ty: &str, attrs: Vec<String>, doc: &str) -> &mut Self {
131        self.fields
132            .push((name.to_string(), ty.to_string(), attrs, doc.to_string()));
133        self
134    }
135
136    pub fn build(&self) -> String {
137        let mut capacity = 128; // structural overhead
138        capacity += self.derives.iter().map(|d| d.len() + 2).sum::<usize>(); // derive + comma
139        capacity += self.attrs.iter().map(|a| a.len() + 5).sum::<usize>(); // #[...]\n
140        capacity += self.name.len() + self.visibility.len() + 16; // struct declaration
141        capacity += self
142            .fields
143            .iter()
144            .map(|(n, t, attrs, doc)| {
145                n.len() + t.len() + 12 + doc.len() + 8 + attrs.iter().map(|a| a.len() + 5).sum::<usize>()
146            })
147            .sum::<usize>();
148
149        let mut out = String::with_capacity(capacity);
150
151        if !self.derives.is_empty() {
152            writeln!(out, "#[derive({})]", self.derives.join(", ")).ok();
153        }
154
155        for attr in &self.attrs {
156            writeln!(out, "#[{attr}]").ok();
157        }
158
159        writeln!(out, "{} struct {} {{", self.visibility, self.name).ok();
160
161        for (name, ty, attrs, doc) in &self.fields {
162            if !doc.is_empty() {
163                for line in doc.lines() {
164                    writeln!(out, "    /// {line}").ok();
165                }
166            }
167            for attr in attrs {
168                writeln!(out, "    #[{attr}]").ok();
169            }
170            writeln!(out, "    pub {name}: {ty},").ok();
171        }
172
173        write!(out, "}}").ok();
174        out
175    }
176}
177
178/// Helper to build an impl block.
179pub struct ImplBuilder {
180    target: String,
181    attrs: Vec<String>,
182    methods: Vec<String>,
183}
184
185impl ImplBuilder {
186    pub fn new(target: &str) -> Self {
187        Self {
188            target: target.to_string(),
189            attrs: vec![],
190            methods: vec![],
191        }
192    }
193
194    pub fn add_attr(&mut self, attr: &str) -> &mut Self {
195        self.attrs.push(attr.to_string());
196        self
197    }
198
199    pub fn add_method(&mut self, method: &str) -> &mut Self {
200        self.methods.push(method.to_string());
201        self
202    }
203
204    pub fn build(&self) -> String {
205        let mut capacity = 128; // structural overhead
206        capacity += self.target.len() + 10; // impl ... {}
207        capacity += self.attrs.iter().map(|a| a.len() + 5).sum::<usize>(); // #[...]\n
208        capacity += self.methods.iter().map(|m| m.len() + 4).sum::<usize>(); // indented + newlines
209
210        let mut out = String::with_capacity(capacity);
211
212        for attr in &self.attrs {
213            writeln!(out, "#[{attr}]").ok();
214        }
215
216        writeln!(out, "impl {} {{", self.target).ok();
217
218        for (i, method) in self.methods.iter().enumerate() {
219            // Indent each line of the method
220            for line in method.lines() {
221                if line.is_empty() {
222                    out.push('\n');
223                } else {
224                    writeln!(out, "    {line}").ok();
225                }
226            }
227            if i < self.methods.len() - 1 {
228                out.push('\n');
229            }
230        }
231
232        write!(out, "}}").ok();
233        out
234    }
235}