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        self.derives.push(derive.to_string());
121        self
122    }
123
124    pub fn add_field(&mut self, name: &str, ty: &str, attrs: Vec<String>) -> &mut Self {
125        self.add_field_with_doc(name, ty, attrs, "")
126    }
127
128    pub fn add_field_with_doc(&mut self, name: &str, ty: &str, attrs: Vec<String>, doc: &str) -> &mut Self {
129        self.fields
130            .push((name.to_string(), ty.to_string(), attrs, doc.to_string()));
131        self
132    }
133
134    pub fn build(&self) -> String {
135        let mut capacity = 128; // structural overhead
136        capacity += self.derives.iter().map(|d| d.len() + 2).sum::<usize>(); // derive + comma
137        capacity += self.attrs.iter().map(|a| a.len() + 5).sum::<usize>(); // #[...]\n
138        capacity += self.name.len() + self.visibility.len() + 16; // struct declaration
139        capacity += self
140            .fields
141            .iter()
142            .map(|(n, t, attrs, doc)| {
143                n.len() + t.len() + 12 + doc.len() + 8 + attrs.iter().map(|a| a.len() + 5).sum::<usize>()
144            })
145            .sum::<usize>();
146
147        let mut out = String::with_capacity(capacity);
148
149        if !self.derives.is_empty() {
150            writeln!(out, "#[derive({})]", self.derives.join(", ")).ok();
151        }
152
153        for attr in &self.attrs {
154            writeln!(out, "#[{attr}]").ok();
155        }
156
157        writeln!(out, "{} struct {} {{", self.visibility, self.name).ok();
158
159        for (name, ty, attrs, doc) in &self.fields {
160            if !doc.is_empty() {
161                for line in doc.lines() {
162                    writeln!(out, "    /// {line}").ok();
163                }
164            }
165            for attr in attrs {
166                writeln!(out, "    #[{attr}]").ok();
167            }
168            writeln!(out, "    pub {name}: {ty},").ok();
169        }
170
171        write!(out, "}}").ok();
172        out
173    }
174}
175
176/// Helper to build an impl block.
177pub struct ImplBuilder {
178    target: String,
179    attrs: Vec<String>,
180    methods: Vec<String>,
181}
182
183impl ImplBuilder {
184    pub fn new(target: &str) -> Self {
185        Self {
186            target: target.to_string(),
187            attrs: vec![],
188            methods: vec![],
189        }
190    }
191
192    pub fn add_attr(&mut self, attr: &str) -> &mut Self {
193        self.attrs.push(attr.to_string());
194        self
195    }
196
197    pub fn add_method(&mut self, method: &str) -> &mut Self {
198        self.methods.push(method.to_string());
199        self
200    }
201
202    pub fn build(&self) -> String {
203        let mut capacity = 128; // structural overhead
204        capacity += self.target.len() + 10; // impl ... {}
205        capacity += self.attrs.iter().map(|a| a.len() + 5).sum::<usize>(); // #[...]\n
206        capacity += self.methods.iter().map(|m| m.len() + 4).sum::<usize>(); // indented + newlines
207
208        let mut out = String::with_capacity(capacity);
209
210        for attr in &self.attrs {
211            writeln!(out, "#[{attr}]").ok();
212        }
213
214        writeln!(out, "impl {} {{", self.target).ok();
215
216        for (i, method) in self.methods.iter().enumerate() {
217            // Indent each line of the method
218            for line in method.lines() {
219                if line.is_empty() {
220                    out.push('\n');
221                } else {
222                    writeln!(out, "    {line}").ok();
223                }
224            }
225            if i < self.methods.len() - 1 {
226                out.push('\n');
227            }
228        }
229
230        write!(out, "}}").ok();
231        out
232    }
233}