Skip to main content

nargo_parser/vutex/
frontmatter.rs

1//! Frontmatter 解析模块
2//! 解析文档开头的 YAML 或 TOML frontmatter
3
4use nargo_types::{FrontMatter, NargoValue, Result};
5
6/// Frontmatter 解析器
7pub struct FrontMatterParser;
8
9impl FrontMatterParser {
10    /// 解析 frontmatter
11    ///
12    /// # Arguments
13    ///
14    /// * `source` - 完整的文档内容
15    ///
16    /// # Returns
17    ///
18    /// 解析后的 frontmatter 和内容起始位置
19    pub fn parse(source: &str) -> Result<(FrontMatter, usize)> {
20        let source = source.trim_start();
21
22        if !source.starts_with("---") {
23            return Ok((FrontMatter::new(), 0));
24        }
25
26        let after_first = &source[3..];
27        let end_pos = after_first.find("---");
28
29        if let Some(end) = end_pos {
30            let frontmatter_content = after_first[..end].trim();
31            let content_start = source[3..].len() - after_first[end + 3..].len() + 3 + 3;
32
33            let frontmatter = Self::parse_toml(frontmatter_content)?;
34
35            Ok((frontmatter, content_start))
36        }
37        else {
38            Ok((FrontMatter::new(), 0))
39        }
40    }
41
42    fn parse_toml(content: &str) -> Result<FrontMatter> {
43        let mut frontmatter = FrontMatter::new();
44
45        for line in content.lines() {
46            let line = line.trim();
47            if line.is_empty() || line.starts_with('#') {
48                continue;
49            }
50
51            if let Some(colon_pos) = line.find(':') {
52                let key = line[..colon_pos].trim();
53                let value = line[colon_pos + 1..].trim();
54
55                match key {
56                    "title" => {
57                        frontmatter.title = Some(Self::parse_string(value));
58                    }
59                    "description" => {
60                        frontmatter.description = Some(Self::parse_string(value));
61                    }
62                    "layout" => {
63                        frontmatter.layout = Some(Self::parse_string(value));
64                    }
65                    "tags" => {
66                        frontmatter.tags = Self::parse_array(value);
67                    }
68                    "sidebar" => {
69                        frontmatter.sidebar = Self::parse_bool(value);
70                    }
71                    "sidebar_order" => {
72                        frontmatter.sidebar_order = Self::parse_i32(value);
73                    }
74                    _ => {
75                        let parsed_value = Self::parse_value(value);
76                        frontmatter.custom.insert(key.to_string(), parsed_value);
77                    }
78                }
79            }
80        }
81
82        Ok(frontmatter)
83    }
84
85    fn parse_string(value: &str) -> String {
86        let value = value.trim();
87        if (value.starts_with('"') && value.ends_with('"')) || (value.starts_with('\'') && value.ends_with('\'')) {
88            value[1..value.len() - 1].to_string()
89        }
90        else {
91            value.to_string()
92        }
93    }
94
95    fn parse_bool(value: &str) -> Option<bool> {
96        let value = value.trim().to_lowercase();
97        match value.as_str() {
98            "true" | "yes" | "on" => Some(true),
99            "false" | "no" | "off" => Some(false),
100            _ => None,
101        }
102    }
103
104    fn parse_i32(value: &str) -> Option<i32> {
105        value.trim().parse().ok()
106    }
107
108    fn parse_array(value: &str) -> Vec<String> {
109        let value = value.trim();
110        if value.starts_with('[') && value.ends_with(']') {
111            let content = &value[1..value.len() - 1];
112            content.split(',').map(|s| Self::parse_string(s.trim())).filter(|s| !s.is_empty()).collect()
113        }
114        else {
115            Vec::new()
116        }
117    }
118
119    fn parse_value(value: &str) -> NargoValue {
120        let value = value.trim();
121
122        if value.starts_with('"') || value.starts_with('\'') {
123            NargoValue::String(Self::parse_string(value))
124        }
125        else if let Ok(b) = value.parse::<bool>() {
126            NargoValue::Bool(b)
127        }
128        else if let Ok(i) = value.parse::<i64>() {
129            NargoValue::Number(i as f64)
130        }
131        else if let Ok(f) = value.parse::<f64>() {
132            NargoValue::Number(f)
133        }
134        else if value.starts_with('[') && value.ends_with(']') {
135            let arr = Self::parse_array(value);
136            NargoValue::Array(arr.into_iter().map(NargoValue::String).collect())
137        }
138        else {
139            NargoValue::String(value.to_string())
140        }
141    }
142}