1pub mod comment;
2mod nodes;
3mod property;
4mod result;
5mod script;
6mod style;
7mod tag;
8
9use comment::offline::OfflineComment;
10pub use nodes::ASTNodes;
11
12pub use property::*;
13pub use result::ParseResult;
14pub use script::Script;
15#[allow(unused_imports)]
16use std::{default, fmt::Display};
17pub use style::{Style, StyleType};
18pub use tag::{CloseType, Tag};
19
20use self::nodes::asts_to_string;
21use crate::{
22 ast::comment::position::OfflinePosition, common::parse_all,
23};
24use gen_utils::{
25 error::{Error, ParseError},
26 parser::trim,
27};
28
29#[derive(Debug, Clone, Default)]
32pub enum Strategy {
33 None,
35 SingleTemplate,
37 SingleScript,
39 SingleStyle,
41 SingleComment,
44 TemplateScript,
46 TemplateStyle,
48 TemplateComment,
50 ScriptComment,
52 StyleComment,
54 TemplateScriptComment,
55 TemplateStyleComment,
56 #[default]
58 All,
59 Error(String),
60}
61
62#[derive(Debug, Clone, PartialEq)]
63pub enum Targets<'a> {
64 Template(&'a str),
65 Script { content: &'a str, ast_node: Tag },
66 Style(&'a str),
67 Comment(OfflineComment),
68}
69
70#[allow(dead_code)]
71impl<'a> Targets<'a> {
72 pub fn is_template(&self) -> bool {
73 matches!(self, Targets::Template(_))
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Default)]
78pub struct ParseCore {
79 template: Option<String>,
81 script: Option<Script>,
83 style: Option<String>,
85}
86
87impl From<ParseTarget> for ParseCore {
88 fn from(value: ParseTarget) -> Self {
89 value.core
90 }
91}
92
93#[allow(dead_code)]
94impl ParseCore {
95 pub fn template(&self) -> Option<&String> {
96 self.template.as_ref()
97 }
98 pub fn script(&self) -> Option<&Script> {
99 self.script.as_ref()
100 }
101 pub fn style(&self) -> Option<&String> {
102 self.style.as_ref()
103 }
104 pub fn has_template(&self) -> (bool, bool) {
105 has_target(self.template())
106 }
107 pub fn has_script(&self) -> (bool, bool) {
108 match self.script.as_ref() {
109 Some(sc) => (!sc.is_empty(), false),
110 None => (false, true),
111 }
112 }
113 pub fn has_style(&self) -> (bool, bool) {
114 has_target(self.style())
115 }
116 pub fn set_template_directly(&mut self, template: String) {
117 let _ = self.template.replace(template);
118 }
119 pub fn set_script_directly(&mut self, script: &Script) {
120 let _ = self.script.replace(script.clone());
121 }
122 pub fn set_style_directly(&mut self, style: String) {
123 let _ = self.style.replace(style);
124 }
125 pub fn set_template(&mut self, template: &str) {
126 let _ = self.template.replace(template.to_owned());
127 }
128 pub fn set_script(&mut self, content: &str, lang: Option<String>) -> Result<(), Error> {
129 let _ = self.script.replace((content, lang).try_into()?);
130 Ok(())
131 }
132 pub fn set_style(&mut self, style: &str) {
133 let _ = self.style.replace(style.to_owned());
134 }
135 pub fn has(&self) -> (bool, bool, bool) {
136 (
137 self.has_template().0,
138 self.has_script().0,
139 self.has_style().0,
140 )
141 }
142 pub fn target_strategy(&self) -> Strategy {
143 match self.has() {
144 (true, true, true) => Strategy::All,
145 (true, true, false) => Strategy::TemplateScript,
146 (true, false, true) => Strategy::TemplateStyle,
147 (true, false, false) => Strategy::SingleTemplate,
148 (false, true, true) => Strategy::Error(String::from(
149 "Gen Parse Strategy Error: There is no such strategy `Script` + `Style`",
150 )),
151 (false, true, false) => Strategy::SingleScript,
152 (false, false, true) => Strategy::SingleStyle,
153 (false, false, false) => Strategy::None,
154 }
155 }
156}
157
158impl From<ParseResult> for ParseCore {
159 fn from(value: ParseResult) -> Self {
160 let mut result = ParseCore::default();
161 if let Some(t) = value.template() {
162 let _ = result.set_template_directly(asts_to_string(t));
163 }
164 if let Some(sc) = value.script() {
165 let _ = result.set_script_directly(sc);
166 }
167 if let Some(s) = value.style() {
168 let _ = result.set_style_directly(asts_to_string(s));
169 }
170 result
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Default)]
190pub struct ParseTarget {
191 core: ParseCore,
192 comment: Option<Vec<OfflineComment>>,
196}
197
198#[allow(dead_code)]
199impl ParseTarget {
200 pub fn set_template(&mut self, template: &str) {
201 let _ = self.core.template.replace(template.to_owned());
202 }
203 pub fn set_script(&mut self, content: &str, lang: Option<String>)-> Result<(), Error> {
204 self.core.set_script(content, lang)
205 }
206 pub fn set_style(&mut self, style: &str) {
207 let _ = self.core.style.replace(style.to_owned());
208 }
209 pub fn set_comment(&mut self, comment: Vec<OfflineComment>) {
210 let _ = self.comment.replace(comment);
211 }
212 pub fn push_comment(&mut self, comment: OfflineComment) {
213 match &mut self.comment {
214 Some(c) => c.push(comment),
215 None => {
216 let _ = self.comment.replace(vec![comment]);
217 }
218 }
219 }
220 pub fn template(&self) -> Option<&String> {
221 self.core.template.as_ref()
222 }
223 pub fn script(&self) -> Option<&Script> {
224 self.core.script()
225 }
226 pub fn style(&self) -> Option<&String> {
227 self.core.style.as_ref()
228 }
229 pub fn comment(&self) -> Option<&Vec<OfflineComment>> {
230 self.comment.as_ref()
231 }
232 pub fn has_template(&self) -> (bool, bool) {
233 has_target(self.template())
234 }
235 pub fn has_script(&self) -> (bool, bool) {
236 self.core.has_script()
237 }
238 pub fn has_style(&self) -> (bool, bool) {
239 has_target(self.style())
240 }
241 pub fn has_comment(&self) -> (bool, bool) {
243 match self.comment() {
244 Some(v) => (!v.is_empty(), false),
245 None => (false, true),
246 }
247 }
248 pub fn has(&self) -> (bool, bool, bool, bool) {
249 (
250 self.has_template().0,
251 self.has_script().0,
252 self.has_style().0,
253 self.has_comment().0,
254 )
255 }
256 pub fn handle_self(&mut self) {
261 match self.has_template() {
262 (false, false) => {
263 self.core.template = None;
264 }
265 _ => {}
266 }
267 match self.has_script() {
268 (false, false) => {
269 self.core.script = None;
270 }
271 _ => {}
272 }
273 match self.has_style() {
274 (false, false) => {
275 self.core.style = None;
276 }
277 _ => {}
278 }
279 }
280 pub fn target_strategy(&self) -> Strategy {
289 match self.has() {
290 (true, true, true, true) | (true, true, true, false) => Strategy::All,
291 (true, true, false, true) => Strategy::TemplateScriptComment,
292 (true, true, false, false) => Strategy::TemplateScript,
293 (true, false, true, true) => Strategy::TemplateStyleComment,
294 (true, false, true, false) => Strategy::TemplateStyle,
295 (true, false, false, true) => Strategy::TemplateComment,
296 (true, false, false, false) => Strategy::SingleTemplate,
297 (false, true, true, true) | (false, true, true, false) => {
298 Strategy::Error(String::from(
299 "Gen Parse Strategy Error: There is no such strategy `Script` + `Style`",
300 ))
301 }
302 (false, true, false, true) => Strategy::ScriptComment,
303 (false, true, false, false) => Strategy::SingleScript,
304 (false, false, true, true) => Strategy::StyleComment,
305 (false, false, true, false) => Strategy::SingleStyle,
306 (false, false, false, true) => Strategy::SingleComment,
307 (false, false, false, false) => Strategy::None,
308 }
309 }
310}
311
312impl From<ParseCore> for ParseTarget {
313 fn from(value: ParseCore) -> Self {
314 ParseTarget {
315 core: value,
316 comment: None,
317 }
318 }
319}
320
321impl<'a> TryFrom<Vec<Targets<'a>>> for ParseTarget {
323 type Error = Error;
324
325 fn try_from(value: Vec<Targets>) -> Result<Self, Self::Error> {
326 return if value.is_empty() {
327 Err(ParseError::template("The current file has no content. It should be removed to ensure your program has clean file tree!").into())
328 } else {
329 let mut parse_target = ParseTarget::default();
330 let mut template_count = 0_u32;
331 let mut script_count = 0_u32;
332 let mut style_count = 0_u32;
333 for target in value {
334 if is_multi_nodes(template_count, script_count, style_count) {
335 match target {
336 Targets::Template(t) => {
337 template_count += 1;
338 parse_target.set_template(t);
339 }
340 Targets::Script { content, ast_node } => {
341 script_count += 1;
342 let script_lang = ast_node.get_script_lang();
343 parse_target.set_script(content, script_lang)?;
344 }
345 Targets::Style(s) => {
346 style_count += 1;
347 parse_target.set_style(s);
348 }
349 Targets::Comment(c) => parse_target.push_comment(c),
350 }
351 } else {
352 return Err(ParseError::template("Abnormal number of nodes, there is more than one `template` | `script` | `style` node in the file!").into());
353 }
354 }
355 let _ = parse_target.handle_self();
356 Ok(parse_target)
357 };
358 }
359}
360
361impl TryFrom<&str> for ParseTarget {
364 type Error = Error;
365
366 fn try_from(value: &str) -> Result<Self, Self::Error> {
367 return if value.trim().is_empty() {
368 Ok(ParseTarget {
370 core: Default::default(),
371 comment: None,
372 })
373 } else {
374 let (remain, res) = trim(parse_all)(value).unwrap();
375 if remain.is_empty() {
376 return ParseTarget::try_from(res);
378 } else {
379 return Err(ParseError::template("Parsing file exception. The current file contains content that is not covered by processed tags. If it is a rust script, please wrap it in a `<script>` tag").into());
381 }
382 };
383 }
384}
385
386impl Display for ParseTarget {
387 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388 let has_comment = self.has_comment().0;
389 if has_comment {
390 let _ = f.write_fmt(format_args!(
391 "{}\n",
392 &self
393 .comment()
394 .unwrap()
395 .iter()
396 .filter(|item| item.position() == OfflinePosition::AboveTemplate)
397 .map(|item| item.to_string())
398 .collect::<Vec<String>>()
399 .join("\n")
400 ));
401 }
402 if self.has_template().0 {
403 let _ = f.write_fmt(format_args!(
404 "<template>\n{}</template>\n",
405 self.template().unwrap()
406 ));
407 }
408 if has_comment {
409 let _ = f.write_fmt(format_args!(
410 "\n{}",
411 &self
412 .comment()
413 .unwrap()
414 .iter()
415 .filter(|item| item.position() == OfflinePosition::AboveScript)
416 .map(|item| item.to_string())
417 .collect::<Vec<String>>()
418 .join("\n")
419 ));
420 }
421 if self.has_script().0 {
422 let _ = f.write_fmt(format_args!(
423 "\n<script>\n{}</script>\n",
424 self.script().unwrap()
425 ));
426 }
427 if has_comment {
428 let _ = f.write_fmt(format_args!(
429 "\n{}",
430 &self
431 .comment()
432 .unwrap()
433 .iter()
434 .filter(|item| item.position() == OfflinePosition::AboveStyle)
435 .map(|item| item.to_string())
436 .collect::<Vec<String>>()
437 .join("\n")
438 ));
439 }
440 if self.has_style().0 {
441 let _ = f.write_fmt(format_args!(
442 "\n<style>\n{}</style>\n",
443 self.style().unwrap()
444 ));
445 }
446 if has_comment {
447 let _ = f.write_str(
448 &self
449 .comment()
450 .unwrap()
451 .iter()
452 .filter(|item| item.position() == OfflinePosition::End)
453 .map(|item| item.to_string())
454 .collect::<Vec<String>>()
455 .join("\n"),
456 );
457 }
458 f.write_str("\n")
459 }
460}
461
462fn has_target(target: Option<&String>) -> (bool, bool) {
468 match target {
469 Some(v) => (!v.is_empty(), false),
470 None => (false, true),
471 }
472}
473
474fn is_multi_nodes(t: u32, sc: u32, s: u32) -> bool {
475 (t <= 1) && (sc <= 1) && (s <= 1)
476}