baobao_codegen/
file_builder.rs

1//! Composable file builder
2
3use crate::{CodeBuilder, ImportCollector, Indent};
4
5/// Composable file builder that combines import collection with code generation.
6///
7/// This struct provides a language-agnostic way to build source files that need
8/// both imports and code. Language-specific crates can add rendering methods.
9///
10/// # Example
11///
12/// ```
13/// use baobao_codegen::{FileBuilder, Indent};
14///
15/// let mut builder = FileBuilder::new(Indent::RUST);
16/// builder.imports.add("std::io", "Read");
17/// builder.code = builder.code.line("fn main() {}");
18///
19/// // Language-specific rendering would be added by extension
20/// ```
21#[derive(Debug, Clone)]
22pub struct FileBuilder {
23    /// Import collector for tracking dependencies
24    pub imports: ImportCollector,
25    /// Code builder for generating the file body
26    pub code: CodeBuilder,
27}
28
29impl FileBuilder {
30    /// Create a new FileBuilder with the specified indentation.
31    pub fn new(indent: Indent) -> Self {
32        Self {
33            imports: ImportCollector::new(),
34            code: CodeBuilder::new(indent),
35        }
36    }
37
38    /// Create a new FileBuilder with Rust-style indentation (4 spaces).
39    pub fn rust() -> Self {
40        Self::new(Indent::RUST)
41    }
42
43    /// Create a new FileBuilder with TypeScript-style indentation (2 spaces).
44    pub fn typescript() -> Self {
45        Self::new(Indent::TYPESCRIPT)
46    }
47
48    /// Create a new FileBuilder with Go-style indentation (tabs).
49    pub fn go() -> Self {
50        Self::new(Indent::GO)
51    }
52
53    /// Add an import.
54    pub fn add_import(mut self, module: &str, symbol: &str) -> Self {
55        self.imports.add(module, symbol);
56        self
57    }
58
59    /// Add a module import without specific symbols.
60    pub fn add_module(mut self, module: &str) -> Self {
61        self.imports.add_module(module);
62        self
63    }
64
65    /// Apply a function to the code builder.
66    pub fn with_code<F>(mut self, f: F) -> Self
67    where
68        F: FnOnce(CodeBuilder) -> CodeBuilder,
69    {
70        self.code = f(self.code);
71        self
72    }
73
74    /// Check if imports are empty.
75    pub fn has_imports(&self) -> bool {
76        !self.imports.is_empty()
77    }
78
79    /// Consume and return the individual components.
80    pub fn into_parts(self) -> (ImportCollector, CodeBuilder) {
81        (self.imports, self.code)
82    }
83}
84
85impl Default for FileBuilder {
86    fn default() -> Self {
87        Self::rust()
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_file_builder_basic() {
97        let builder = FileBuilder::rust()
98            .add_import("std::io", "Read")
99            .add_import("std::io", "Write")
100            .with_code(|c| c.line("fn main() {}"));
101
102        assert!(builder.imports.has_symbol("std::io", "Read"));
103        assert!(builder.imports.has_symbol("std::io", "Write"));
104        assert!(builder.code.as_str().contains("fn main()"));
105    }
106
107    #[test]
108    fn test_file_builder_fluent() {
109        let builder = FileBuilder::rust()
110            .add_import("clap", "Parser")
111            .add_import("clap", "Subcommand")
112            .with_code(|c| c.line("#[derive(Parser)]").line("struct Cli {}"));
113
114        assert!(builder.imports.has_module("clap"));
115        assert!(builder.code.as_str().contains("#[derive(Parser)]"));
116    }
117}