alef-codegen 0.15.20

Shared codegen utilities for the alef polyglot binding generator
Documentation
/// Builder for constructing Rust source files.
#[derive(Debug, Default)]
pub struct RustFileBuilder {
    doc_header: Option<String>,
    imports: Vec<String>,
    items: Vec<String>,
}

impl RustFileBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a "DO NOT EDIT" header.
    pub fn with_generated_header(mut self) -> Self {
        self.doc_header = Some(
            "// This file is auto-generated by alef. DO NOT EDIT.\n\
             // Re-generate with: alef generate\n"
                .to_string(),
        );
        self
    }

    /// Add a crate-level inner attribute (e.g., `#![allow(clippy::...)]`).
    /// These are placed at the very top of the file, before imports.
    pub fn add_inner_attribute(&mut self, attr: &str) {
        // Append to doc_header since it comes first
        let rendered = crate::template_env::render(
            "builders/inner_attribute.jinja",
            minijinja::context! {
                attr => attr,
            },
        );
        if let Some(ref mut header) = self.doc_header {
            header.push_str(&rendered);
        } else {
            self.doc_header = Some(rendered);
        }
    }

    /// Add a use import line.
    /// Single-component imports (e.g. `use serde_json;`) are skipped since they are
    /// redundant in Rust 2018+ where extern crates are automatically in scope.
    pub fn add_import(&mut self, import: &str) {
        // Skip single-component path imports — they trigger clippy::single_component_path_imports
        // and are redundant in edition 2018+. A single component has no `::`, `*`, or `{`.
        if !import.contains("::") && !import.contains('*') && !import.contains('{') {
            return;
        }
        if !self.imports.iter().any(|i| i == import) {
            self.imports.push(import.to_string());
        }
    }

    /// Add a raw code item (struct, impl, function, etc.).
    pub fn add_item(&mut self, item: &str) {
        self.items.push(item.to_string());
    }

    /// Build the final source string.
    pub fn build(&self) -> String {
        let mut capacity = 256; // base header + imports + newlines
        if let Some(header) = &self.doc_header {
            capacity += header.len() + 1;
        }
        capacity += self.imports.iter().map(|i| i.len() + 6).sum::<usize>(); // "use ...;\n"
        capacity += self.items.iter().map(|i| i.len() + 2).sum::<usize>(); // item + "\n\n"

        let mut out = String::with_capacity(capacity);

        if let Some(header) = &self.doc_header {
            out.push_str(header);
            out.push('\n');
        }

        if !self.imports.is_empty() {
            for import in &self.imports {
                out.push_str(&crate::template_env::render(
                    "builders/use_import.jinja",
                    minijinja::context! {
                        import => import,
                    },
                ));
            }
            out.push('\n');
        }

        for (i, item) in self.items.iter().enumerate() {
            out.push_str(item);
            if i < self.items.len() - 1 {
                out.push_str("\n\n");
            }
        }

        if !out.ends_with('\n') {
            out.push('\n');
        }

        out
    }
}

/// Helper to build a struct with attributes.
pub struct StructBuilder {
    attrs: Vec<String>,
    visibility: String,
    name: String,
    derives: Vec<String>,
    fields: Vec<(String, String, Vec<String>, String)>, // (name, type, field_attrs, doc)
}

impl StructBuilder {
    pub fn new(name: &str) -> Self {
        Self {
            attrs: vec![],
            visibility: String::from("pub"),
            name: name.to_string(),
            derives: vec![],
            fields: vec![],
        }
    }

    pub fn add_attr(&mut self, attr: &str) -> &mut Self {
        self.attrs.push(attr.to_string());
        self
    }

    pub fn add_derive(&mut self, derive: &str) -> &mut Self {
        if !self.derives.iter().any(|d| d == derive) {
            self.derives.push(derive.to_string());
        }
        self
    }

    pub fn add_field(&mut self, name: &str, ty: &str, attrs: Vec<String>) -> &mut Self {
        self.add_field_with_doc(name, ty, attrs, "")
    }

    pub fn add_field_with_doc(&mut self, name: &str, ty: &str, attrs: Vec<String>, doc: &str) -> &mut Self {
        self.fields
            .push((name.to_string(), ty.to_string(), attrs, doc.to_string()));
        self
    }

    pub fn build(&self) -> String {
        let mut capacity = 128; // structural overhead
        capacity += self.derives.iter().map(|d| d.len() + 2).sum::<usize>(); // derive + comma
        capacity += self.attrs.iter().map(|a| a.len() + 5).sum::<usize>(); // #[...]\n
        capacity += self.name.len() + self.visibility.len() + 16; // struct declaration
        capacity += self
            .fields
            .iter()
            .map(|(n, t, attrs, doc)| {
                n.len() + t.len() + 12 + doc.len() + 8 + attrs.iter().map(|a| a.len() + 5).sum::<usize>()
            })
            .sum::<usize>();

        let mut out = String::with_capacity(capacity);

        if !self.derives.is_empty() {
            let rendered = crate::template_env::render(
                "builders/derive_attr.jinja",
                minijinja::context! {
                    derives => self.derives.join(", "),
                },
            );
            out.push_str(&rendered);
            if !rendered.ends_with('\n') {
                out.push('\n');
            }
        }

        for attr in &self.attrs {
            let rendered = crate::template_env::render(
                "builders/generic_attr.jinja",
                minijinja::context! {
                    attr => attr,
                },
            );
            out.push_str(&rendered);
            if !rendered.ends_with('\n') {
                out.push('\n');
            }
        }

        let rendered_header = crate::template_env::render(
            "builders/struct_header.jinja",
            minijinja::context! {
                visibility => &self.visibility,
                name => &self.name,
            },
        );
        out.push_str(&rendered_header);
        if !rendered_header.ends_with('\n') {
            out.push('\n');
        }

        for (name, ty, attrs, doc) in &self.fields {
            if !doc.is_empty() {
                for line in doc.lines() {
                    let rendered = crate::template_env::render(
                        "builders/doc_line.jinja",
                        minijinja::context! {
                            line => line,
                        },
                    );
                    out.push_str(&rendered);
                    if !rendered.ends_with('\n') {
                        out.push('\n');
                    }
                }
            }
            for attr in attrs {
                let rendered = crate::template_env::render(
                    "builders/generic_attr.jinja",
                    minijinja::context! {
                        attr => attr,
                    },
                );
                out.push_str(&rendered);
                if !rendered.ends_with('\n') {
                    out.push('\n');
                }
            }
            let rendered_field = crate::template_env::render(
                "builders/struct_field.jinja",
                minijinja::context! {
                    name => name,
                    ty => ty,
                },
            );
            out.push_str(&rendered_field);
            if !rendered_field.ends_with('\n') {
                out.push('\n');
            }
        }

        out.push('}');
        out
    }
}

/// Helper to build an impl block.
pub struct ImplBuilder {
    target: String,
    attrs: Vec<String>,
    methods: Vec<String>,
}

impl ImplBuilder {
    pub fn new(target: &str) -> Self {
        Self {
            target: target.to_string(),
            attrs: vec![],
            methods: vec![],
        }
    }

    pub fn add_attr(&mut self, attr: &str) -> &mut Self {
        self.attrs.push(attr.to_string());
        self
    }

    pub fn add_method(&mut self, method: &str) -> &mut Self {
        self.methods.push(method.to_string());
        self
    }

    pub fn build(&self) -> String {
        let mut capacity = 128; // structural overhead
        capacity += self.target.len() + 10; // impl ... {}
        capacity += self.attrs.iter().map(|a| a.len() + 5).sum::<usize>(); // #[...]\n
        capacity += self.methods.iter().map(|m| m.len() + 4).sum::<usize>(); // indented + newlines

        let mut out = String::with_capacity(capacity);

        for attr in &self.attrs {
            let rendered = crate::template_env::render(
                "builders/generic_attr.jinja",
                minijinja::context! {
                    attr => attr,
                },
            );
            out.push_str(&rendered);
            if !rendered.ends_with('\n') {
                out.push('\n');
            }
        }

        let rendered_header = crate::template_env::render(
            "builders/impl_header.jinja",
            minijinja::context! {
                target => &self.target,
            },
        );
        out.push_str(&rendered_header);
        if !rendered_header.ends_with('\n') {
            out.push('\n');
        }

        for (i, method) in self.methods.iter().enumerate() {
            // Indent each line of the method
            for line in method.lines() {
                if line.is_empty() {
                    out.push('\n');
                } else {
                    let rendered = crate::template_env::render(
                        "builders/indented_line.jinja",
                        minijinja::context! {
                            line => line,
                        },
                    );
                    out.push_str(&rendered);
                    if !rendered.ends_with('\n') {
                        out.push('\n');
                    }
                }
            }
            if i < self.methods.len() - 1 {
                out.push('\n');
            }
        }

        out.push('}');
        out
    }
}