Skip to main content

archidoc_engine/
init.rs

1use std::path::Path;
2
3/// Supported comment styles for different languages.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum CommentStyle {
6    /// Rust doc comments: `//!`
7    Rust,
8    /// TypeScript/JavaScript JSDoc: `/** ... */`
9    TypeScript,
10}
11
12impl CommentStyle {
13    /// Auto-detect from project root by looking for Cargo.toml / package.json.
14    pub fn detect(root: &Path) -> Option<Self> {
15        if root.join("Cargo.toml").exists() {
16            Some(Self::Rust)
17        } else if root.join("package.json").exists() {
18            Some(Self::TypeScript)
19        } else {
20            None
21        }
22    }
23
24    pub fn from_lang(lang: &str) -> Option<Self> {
25        match lang.to_lowercase().as_str() {
26            "rust" | "rs" => Some(Self::Rust),
27            "typescript" | "ts" | "javascript" | "js" => Some(Self::TypeScript),
28            _ => None,
29        }
30    }
31}
32
33/// Generate a root-level annotation template for a project's entry file.
34///
35/// Outputs a doc comment block with recommended architectural sections,
36/// each with TODO placeholders. Designed to be pasted into `lib.rs`, `index.ts`, etc.
37pub fn generate_template(style: CommentStyle) -> String {
38    let sections = vec![
39        Section::heading("@c4 container"),
40        Section::heading("[Project Name]").h1(),
41        Section::blank(),
42        Section::line("[TODO: One-line description — what this system does and why it exists.]"),
43        Section::blank(),
44        Section::heading("C4 Context").h2(),
45        Section::blank(),
46        Section::code_block(
47            "mermaid",
48            &[
49                "C4Context",
50                "    title System Context Diagram",
51                "",
52                "    Person(user, \"TODO: User\", \"TODO: Primary user/actor\")",
53                "    System(system, \"TODO: System Name\", \"TODO: System purpose\")",
54                "    System_Ext(ext1, \"TODO: External System\", \"TODO: External dependency\")",
55                "",
56                "    Rel(user, system, \"Uses\")",
57                "    Rel(system, ext1, \"TODO: relationship\", \"TODO: protocol\")",
58                "",
59                "    UpdateLayoutConfig($c4ShapeInRow=\"3\", $c4BoundaryInRow=\"1\")",
60            ],
61        ),
62        Section::blank(),
63        Section::heading("Data Flow").h2(),
64        Section::blank(),
65        Section::line("1. TODO: Primary command/request flow (e.g., Frontend -> API -> Service -> DB)"),
66        Section::line("2. TODO: Primary data/response flow (e.g., DB -> Service -> Frontend)"),
67        Section::line("3. TODO: Secondary flows (settings, config, async jobs, etc.)"),
68        Section::blank(),
69        Section::heading("Concurrency & Data Patterns").h2(),
70        Section::blank(),
71        Section::line("- TODO: Key concurrency primitives (locks, channels, atomics, async, etc.)"),
72        Section::line("- TODO: Data access patterns (caching, buffering, connection pooling, etc.)"),
73        Section::blank(),
74        Section::heading("Deployment").h2(),
75        Section::blank(),
76        Section::line("- TODO: Where does this run? (local, cloud, hybrid, embedded)"),
77        Section::line("- TODO: Key infrastructure (Docker, K8s, serverless, etc.)"),
78        Section::blank(),
79        Section::heading("External Dependencies").h2(),
80        Section::blank(),
81        Section::line("- TODO: Third-party APIs and services"),
82        Section::line("- TODO: Databases and storage systems"),
83    ];
84
85    render(style, &sections)
86}
87
88// -- Internal rendering helpers --
89
90enum Section {
91    Line(String),
92    Blank,
93    Heading { text: String, level: u8 },
94    CodeBlock { lang: String, lines: Vec<String> },
95}
96
97impl Section {
98    fn heading(text: &str) -> Self {
99        Self::Heading {
100            text: text.to_string(),
101            level: 0,
102        }
103    }
104
105    fn h1(self) -> Self {
106        match self {
107            Self::Heading { text, .. } => Self::Heading { text, level: 1 },
108            other => other,
109        }
110    }
111
112    fn h2(self) -> Self {
113        match self {
114            Self::Heading { text, .. } => Self::Heading { text, level: 2 },
115            other => other,
116        }
117    }
118
119    fn line(text: &str) -> Self {
120        Self::Line(text.to_string())
121    }
122
123    fn blank() -> Self {
124        Self::Blank
125    }
126
127    fn code_block(lang: &str, lines: &[&str]) -> Self {
128        Self::CodeBlock {
129            lang: lang.to_string(),
130            lines: lines.iter().map(|s| s.to_string()).collect(),
131        }
132    }
133}
134
135fn render(style: CommentStyle, sections: &[Section]) -> String {
136    let mut out = String::new();
137
138    for section in sections {
139        match section {
140            Section::Blank => {
141                out.push_str(&comment_line(style, ""));
142                out.push('\n');
143            }
144            Section::Line(text) => {
145                out.push_str(&comment_line(style, text));
146                out.push('\n');
147            }
148            Section::Heading { text, level } => {
149                let prefix = match level {
150                    1 => "# ",
151                    2 => "## ",
152                    3 => "### ",
153                    _ => "",
154                };
155                out.push_str(&comment_line(style, &format!("{}{}", prefix, text)));
156                out.push('\n');
157            }
158            Section::CodeBlock { lang, lines } => {
159                out.push_str(&comment_line(style, &format!("```{}", lang)));
160                out.push('\n');
161                for line in lines {
162                    out.push_str(&comment_line(style, line));
163                    out.push('\n');
164                }
165                out.push_str(&comment_line(style, "```"));
166                out.push('\n');
167            }
168        }
169    }
170
171    out
172}
173
174fn comment_line(style: CommentStyle, text: &str) -> String {
175    match style {
176        CommentStyle::Rust => {
177            if text.is_empty() {
178                "//!".to_string()
179            } else {
180                format!("//! {}", text)
181            }
182        }
183        CommentStyle::TypeScript => {
184            if text.is_empty() {
185                " *".to_string()
186            } else {
187                format!(" * {}", text)
188            }
189        }
190    }
191}
192
193/// Wrap TypeScript output in JSDoc delimiters.
194pub fn wrap_jsdoc(content: &str) -> String {
195    format!("/**\n{} */\n", content)
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn rust_template_has_c4_marker() {
204        let out = generate_template(CommentStyle::Rust);
205        assert!(out.contains("//! @c4 container"));
206    }
207
208    #[test]
209    fn rust_template_has_all_sections() {
210        let out = generate_template(CommentStyle::Rust);
211        assert!(out.contains("# [Project Name]"));
212        assert!(out.contains("## C4 Context"));
213        assert!(out.contains("## Data Flow"));
214        assert!(out.contains("## Concurrency & Data Patterns"));
215        assert!(out.contains("## Deployment"));
216        assert!(out.contains("## External Dependencies"));
217    }
218
219    #[test]
220    fn rust_template_has_mermaid_block() {
221        let out = generate_template(CommentStyle::Rust);
222        assert!(out.contains("```mermaid"));
223        assert!(out.contains("C4Context"));
224        assert!(out.contains("```"));
225    }
226
227    #[test]
228    fn rust_template_has_todo_placeholders() {
229        let out = generate_template(CommentStyle::Rust);
230        assert!(out.contains("TODO:"));
231        assert!(out.contains("[TODO: One-line description"));
232    }
233
234    #[test]
235    fn typescript_template_uses_jsdoc_style() {
236        let out = generate_template(CommentStyle::TypeScript);
237        assert!(out.contains(" * @c4 container"));
238        assert!(out.contains(" * ## Data Flow"));
239    }
240
241    #[test]
242    fn detect_rust_from_cargo_toml() {
243        let tmp = tempfile::TempDir::new().unwrap();
244        std::fs::write(tmp.path().join("Cargo.toml"), "").unwrap();
245        assert_eq!(CommentStyle::detect(tmp.path()), Some(CommentStyle::Rust));
246    }
247
248    #[test]
249    fn detect_ts_from_package_json() {
250        let tmp = tempfile::TempDir::new().unwrap();
251        std::fs::write(tmp.path().join("package.json"), "").unwrap();
252        assert_eq!(
253            CommentStyle::detect(tmp.path()),
254            Some(CommentStyle::TypeScript)
255        );
256    }
257
258    #[test]
259    fn from_lang_parsing() {
260        assert_eq!(CommentStyle::from_lang("rust"), Some(CommentStyle::Rust));
261        assert_eq!(CommentStyle::from_lang("rs"), Some(CommentStyle::Rust));
262        assert_eq!(CommentStyle::from_lang("ts"), Some(CommentStyle::TypeScript));
263        assert_eq!(CommentStyle::from_lang("unknown"), None);
264    }
265
266    #[test]
267    fn blank_lines_are_comment_only() {
268        let out = generate_template(CommentStyle::Rust);
269        // Blank lines should be "//!" with no trailing space
270        assert!(out.contains("\n//!\n"));
271        assert!(!out.contains("//! \n"));
272    }
273}