nargo_parser/vutex/
frontmatter.rs1use nargo_types::{FrontMatter, NargoValue, Result};
5
6pub struct FrontMatterParser;
8
9impl FrontMatterParser {
10 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}