baobao_codegen/builder/
file_builder.rs

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