1mod ast;
4mod template;
5mod tokenizer;
6
7pub use ast::{OrbitAst, TemplateNode};
8
9use std::fs;
10use std::path::Path;
11
12#[derive(Default)]
14pub struct OrbitParser;
15
16impl OrbitParser {
17 pub fn parse(content: &str) -> Result<OrbitAst, String> {
19 let sections = Self::split_sections(content)?;
21
22 let template_node = template::TemplateParser::new(§ions.template).parse()?;
24
25 let style_node = ast::StyleNode {
27 rules: Vec::new(),
28 scoped: false,
29 };
30
31 let script_node = ast::ScriptNode {
33 imports: Vec::new(),
34 component_name: String::new(),
35 props: Vec::new(),
36 state: Vec::new(),
37 methods: Vec::new(),
38 lifecycle: Vec::new(),
39 };
40
41 Ok(OrbitAst::new(template_node, style_node, script_node))
42 }
43
44 pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<OrbitAst, String> {
46 let content = fs::read_to_string(path).map_err(|e| format!("Failed to read file: {e}"))?;
47 Self::parse(&content)
48 }
49
50 fn split_sections(content: &str) -> Result<Sections, String> {
52 let mut template = String::new();
53 let mut style = String::new();
54 let mut script = String::new();
55
56 let mut current_section = None;
57 let mut current_content = String::new();
58
59 for line in content.lines() {
60 match line.trim() {
61 "<template>" => {
62 current_section = Some(Section::Template);
63 continue;
64 }
65 "</template>" => {
66 template = current_content.clone();
67 current_content.clear();
68 current_section = None;
69 continue;
70 }
71 "<style>" => {
72 current_section = Some(Section::Style);
73 continue;
74 }
75 "</style>" => {
76 style = current_content.clone();
77 current_content.clear();
78 current_section = None;
79 continue;
80 }
81 "<script>" | "<code>" | "<code lang=\"rust\">" => {
82 current_section = Some(Section::Script);
83 continue;
84 }
85 "</script>" | "</code>" => {
86 script = current_content.clone();
87 current_content.clear();
88 current_section = None;
89 continue;
90 }
91 _ => {}
92 }
93
94 if let Some(_section) = current_section {
95 current_content.push_str(line);
96 current_content.push('\n');
97 }
98 }
99
100 if template.is_empty() {
101 return Err("Missing <template> section".to_string());
102 }
103
104 Ok(Sections {
105 template,
106 style,
107 script,
108 })
109 }
110}
111
112#[derive(Debug, Clone, Copy)]
113enum Section {
114 Template,
115 Style,
116 Script,
117}
118
119#[derive(Debug)]
120#[allow(dead_code)]
121struct Sections {
122 template: String,
123 style: String,
124 script: String,
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_parse_simple_component() {
133 let content = r#"
134<template>
135 <div class="greeting">
136 <h1>Hello, {{ name }}!</h1>
137 <button @click="increment">Count: {{ count }}</button>
138 </div>
139</template>
140
141<style>
142.greeting {
143 font-family: Arial, sans-serif;
144 padding: 20px;
145}
146</style>
147
148<script>
149use orbit::prelude::*;
150
151pub struct Greeting {
152 name: String,
153 count: i32,
154}
155
156impl Component for Greeting {
157 fn new() -> Self {
158 Self {
159 name: String::from("World"),
160 count: 0,
161 }
162 }
163}
164</script>
165"#;
166
167 let ast = OrbitParser::parse(content).unwrap();
168
169 match ast.template {
171 TemplateNode::Element {
172 tag,
173 attributes,
174 events: _,
175 children,
176 } => {
177 assert_eq!(tag, "div");
178 assert!(attributes.contains_key("class"));
179 assert_eq!(children.len(), 2);
180 }
181 _ => panic!("Expected element node"),
182 }
183 }
184}