alef-codegen 0.3.4

Shared codegen utilities for the alef polyglot binding generator
Documentation
use std::fmt::Write;

/// 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
        if let Some(ref mut header) = self.doc_header {
            header.push_str(&format!("#![{attr}]\n"));
        } else {
            self.doc_header = Some(format!("#![{attr}]\n"));
        }
    }

    /// 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 {
                writeln!(out, "use {import};").ok();
            }
            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() {
            writeln!(out, "#[derive({})]", self.derives.join(", ")).ok();
        }

        for attr in &self.attrs {
            writeln!(out, "#[{attr}]").ok();
        }

        writeln!(out, "{} struct {} {{", self.visibility, self.name).ok();

        for (name, ty, attrs, doc) in &self.fields {
            if !doc.is_empty() {
                for line in doc.lines() {
                    writeln!(out, "    /// {line}").ok();
                }
            }
            for attr in attrs {
                writeln!(out, "    #[{attr}]").ok();
            }
            writeln!(out, "    pub {name}: {ty},").ok();
        }

        write!(out, "}}").ok();
        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 {
            writeln!(out, "#[{attr}]").ok();
        }

        writeln!(out, "impl {} {{", self.target).ok();

        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 {
                    writeln!(out, "    {line}").ok();
                }
            }
            if i < self.methods.len() - 1 {
                out.push('\n');
            }
        }

        write!(out, "}}").ok();
        out
    }
}