gen_parser/ast/
mod.rs

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/// Parse Strategy
30/// Convert ParseTarget To AST
31#[derive(Debug, Clone, Default)]
32pub enum Strategy {
33    /// an empty file
34    None,
35    /// only has template tag
36    SingleTemplate,
37    /// only has rust script
38    SingleScript,
39    /// only has style tag
40    SingleStyle,
41    /// no template, rust script, style
42    /// only comment (should with signatures)
43    SingleComment,
44    /// template with rust script
45    TemplateScript,
46    /// template with style
47    TemplateStyle,
48    /// template with comment
49    TemplateComment,
50    /// script with comment
51    ScriptComment,
52    /// style with comment
53    StyleComment,
54    TemplateScriptComment,
55    TemplateStyleComment,
56    /// has all means: TemplateScriptStyle
57    #[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    /// content of template tag
80    template: Option<String>,
81    /// content of script tag
82    script: Option<Script>,
83    /// content of style tag
84    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/// # Parse Target
175/// The target which will be parsed
176///
177/// Through this structure, you can obtain the page structure
178///  
179/// ## how to get
180/// use nom to split the gen file
181/// ## target check
182/// When calling to determine the existence of fields in the parsing target, the actual content will be determined to be empty or not
183/// > reject cheat syntax
184/// ## Example
185/// ```rust
186/// let input = r#" ... "#;
187/// let target = ParseTarget::try_from(input).unwrap();
188/// ```
189#[derive(Debug, Clone, PartialEq, Default)]
190pub struct ParseTarget {
191    core: ParseCore,
192    /// after parse the core 3 tag parser will consider the other remains are comment
193    /// try to use comment parser to parse the remains
194    /// if not have any allowed comment signatures --> panic!
195    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    /// judge whether has other comments
242    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    /// # handle Self to be better
257    /// Call in TryFrom trait
258    /// ## which need to handle
259    /// is empty but not none
260    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    /// Get ParseTarget Convert to AST Strategy
281    /// This strategy affects how many threads are used for conversion
282    ///
283    /// 1. no <template> tag and no <style> tag  -->  parse as rust script (1 thread)
284    /// 2. no <template> tag and no rust script has <style> tag  -->  parse as style (1 thread)
285    /// 3. no <style> tag and no rust script has <template> tag  -->  parse as template (1 thread)
286    /// 4. has <template> tag and rust script no <style> tag --> parse as template_script (2 thread)
287    /// 5. has 3 tag --> parse as whole gen (3 thread)
288    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
321/// parse whole gen file from `Vec<Targets>` to `ParseTarget`
322impl<'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
361/// parse whole gen file from `&str` to `ParseTarget`
362/// recommended to use this method to parse gen file directly
363impl 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            // Err(crate::error::Error::new("The current file has no content. It should be removed to ensure your program has clean file tree!"))
369            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                // parse res to ParseTarget
377                return ParseTarget::try_from(res);
378            } else {
379                // dbg!(remain);
380                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
462/// check whether the target is empty
463/// ## return
464/// `(bool, bool)` means:
465/// 1. bool: is empty?
466/// 2. bool: is Option::None?
467fn 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}