1use lazy_static::lazy_static;
2use regex::Regex;
3use std::collections::HashMap;
4
5lazy_static! {
6 static ref STANDARD_FRONT_MATTER_START: Regex = Regex::new(r"^---\s*$").unwrap();
8 static ref STANDARD_FRONT_MATTER_END: Regex = Regex::new(r"^---\s*$").unwrap();
9
10 static ref TOML_FRONT_MATTER_START: Regex = Regex::new(r"^\+\+\+\s*$").unwrap();
12 static ref TOML_FRONT_MATTER_END: Regex = Regex::new(r"^\+\+\+\s*$").unwrap();
13
14 static ref JSON_FRONT_MATTER_START: Regex = Regex::new(r"^\{\s*$").unwrap();
16 static ref JSON_FRONT_MATTER_END: Regex = Regex::new(r"^\}\s*$").unwrap();
17
18 static ref MALFORMED_FRONT_MATTER_START1: Regex = Regex::new(r"^- --\s*$").unwrap();
20 static ref MALFORMED_FRONT_MATTER_END1: Regex = Regex::new(r"^- --\s*$").unwrap();
21
22 static ref MALFORMED_FRONT_MATTER_START2: Regex = Regex::new(r"^-- -\s*$").unwrap();
24 static ref MALFORMED_FRONT_MATTER_END2: Regex = Regex::new(r"^-- -\s*$").unwrap();
25
26 static ref FRONT_MATTER_FIELD: Regex = Regex::new(r"^([^:]+):\s*(.*)$").unwrap();
28
29 static ref TOML_FIELD_PATTERN: Regex = Regex::new(r#"^([^=]+)\s*=\s*"?([^"]*)"?$"#).unwrap();
31}
32
33#[derive(Debug, PartialEq, Eq, Clone, Copy)]
35pub enum FrontMatterType {
36 Yaml,
38 Toml,
40 Json,
42 Malformed,
44 None,
46}
47
48pub struct FrontMatterUtils;
50
51impl FrontMatterUtils {
52 pub fn is_in_front_matter(content: &str, line_num: usize) -> bool {
54 let lines: Vec<&str> = content.lines().collect();
55 if line_num >= lines.len() {
56 return false;
57 }
58
59 let mut in_standard_front_matter = false;
60 let mut in_toml_front_matter = false;
61 let mut in_json_front_matter = false;
62 let mut in_malformed_front_matter1 = false;
63 let mut in_malformed_front_matter2 = false;
64
65 for (i, line) in lines.iter().enumerate() {
66 if i > line_num {
67 break;
68 }
69
70 if i == line_num
72 && i > 0
73 && ((in_standard_front_matter && STANDARD_FRONT_MATTER_END.is_match(line))
74 || (in_toml_front_matter && TOML_FRONT_MATTER_END.is_match(line))
75 || (in_json_front_matter && JSON_FRONT_MATTER_END.is_match(line))
76 || (in_malformed_front_matter1 && MALFORMED_FRONT_MATTER_END1.is_match(line))
77 || (in_malformed_front_matter2 && MALFORMED_FRONT_MATTER_END2.is_match(line)))
78 {
79 return false; }
81
82 if i == 0 && STANDARD_FRONT_MATTER_START.is_match(line) {
84 in_standard_front_matter = true;
85 } else if STANDARD_FRONT_MATTER_END.is_match(line) && in_standard_front_matter && i > 0 {
86 in_standard_front_matter = false;
87 }
88 else if i == 0 && TOML_FRONT_MATTER_START.is_match(line) {
90 in_toml_front_matter = true;
91 } else if TOML_FRONT_MATTER_END.is_match(line) && in_toml_front_matter && i > 0 {
92 in_toml_front_matter = false;
93 }
94 else if i == 0 && JSON_FRONT_MATTER_START.is_match(line) {
96 in_json_front_matter = true;
97 } else if JSON_FRONT_MATTER_END.is_match(line) && in_json_front_matter && i > 0 {
98 in_json_front_matter = false;
99 }
100 else if i == 0 && MALFORMED_FRONT_MATTER_START1.is_match(line) {
102 in_malformed_front_matter1 = true;
103 } else if MALFORMED_FRONT_MATTER_END1.is_match(line) && in_malformed_front_matter1 && i > 0 {
104 in_malformed_front_matter1 = false;
105 }
106 else if i == 0 && MALFORMED_FRONT_MATTER_START2.is_match(line) {
108 in_malformed_front_matter2 = true;
109 } else if MALFORMED_FRONT_MATTER_END2.is_match(line) && in_malformed_front_matter2 && i > 0 {
110 in_malformed_front_matter2 = false;
111 }
112 }
113
114 in_standard_front_matter
116 || in_toml_front_matter
117 || in_json_front_matter
118 || in_malformed_front_matter1
119 || in_malformed_front_matter2
120 }
121
122 pub fn has_front_matter_field(content: &str, field_prefix: &str) -> bool {
124 let lines: Vec<&str> = content.lines().collect();
125 if lines.len() < 3 {
126 return false;
127 }
128
129 let front_matter_type = Self::detect_front_matter_type(content);
130 if front_matter_type == FrontMatterType::None {
131 return false;
132 }
133
134 let front_matter = Self::extract_front_matter(content);
135 for line in front_matter {
136 if line.trim().starts_with(field_prefix) {
137 return true;
138 }
139 }
140
141 false
142 }
143
144 pub fn get_front_matter_field_value<'a>(content: &'a str, field_name: &str) -> Option<&'a str> {
146 let lines: Vec<&'a str> = content.lines().collect();
147 if lines.len() < 3 {
148 return None;
149 }
150
151 let front_matter_type = Self::detect_front_matter_type(content);
152 if front_matter_type == FrontMatterType::None {
153 return None;
154 }
155
156 let front_matter = Self::extract_front_matter(content);
157 for line in front_matter {
158 let line = line.trim();
159 match front_matter_type {
160 FrontMatterType::Toml => {
161 if let Some(captures) = TOML_FIELD_PATTERN.captures(line) {
163 let key = captures.get(1).unwrap().as_str().trim();
164 if key == field_name {
165 let value = captures.get(2).unwrap().as_str();
166 return Some(value);
167 }
168 }
169 }
170 _ => {
171 if let Some(captures) = FRONT_MATTER_FIELD.captures(line) {
173 let mut key = captures.get(1).unwrap().as_str().trim();
174
175 if key.starts_with('"') && key.ends_with('"') && key.len() >= 2 {
177 key = &key[1..key.len() - 1];
178 }
179
180 if key == field_name {
181 let value = captures.get(2).unwrap().as_str().trim();
182 if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
184 return Some(&value[1..value.len() - 1]);
185 }
186 return Some(value);
187 }
188 }
189 }
190 }
191 }
192
193 None
194 }
195
196 pub fn extract_front_matter_fields(content: &str) -> HashMap<String, String> {
198 let mut fields = HashMap::new();
199
200 let front_matter_type = Self::detect_front_matter_type(content);
201 if front_matter_type == FrontMatterType::None {
202 return fields;
203 }
204
205 let front_matter = Self::extract_front_matter(content);
206 let mut current_prefix = String::new();
207 let mut indent_level = 0;
208
209 for line in front_matter {
210 let line_indent = line.chars().take_while(|c| c.is_whitespace()).count();
211 let line = line.trim();
212
213 match line_indent.cmp(&indent_level) {
215 std::cmp::Ordering::Greater => {
216 indent_level = line_indent;
218 }
219 std::cmp::Ordering::Less => {
220 indent_level = line_indent;
222 if let Some(last_dot) = current_prefix.rfind('.') {
224 current_prefix.truncate(last_dot);
225 } else {
226 current_prefix.clear();
227 }
228 }
229 std::cmp::Ordering::Equal => {}
230 }
231
232 match front_matter_type {
233 FrontMatterType::Toml => {
234 if let Some(captures) = TOML_FIELD_PATTERN.captures(line) {
236 let key = captures.get(1).unwrap().as_str().trim();
237 let value = captures.get(2).unwrap().as_str();
238 let full_key = if current_prefix.is_empty() {
239 key.to_string()
240 } else {
241 format!("{current_prefix}.{key}")
242 };
243 fields.insert(full_key, value.to_string());
244 }
245 }
246 _ => {
247 if let Some(captures) = FRONT_MATTER_FIELD.captures(line) {
249 let mut key = captures.get(1).unwrap().as_str().trim();
250 let value = captures.get(2).unwrap().as_str().trim();
251
252 if key.starts_with('"') && key.ends_with('"') && key.len() >= 2 {
254 key = &key[1..key.len() - 1];
255 }
256
257 if let Some(stripped) = key.strip_suffix(':') {
258 if current_prefix.is_empty() {
260 current_prefix = stripped.to_string();
261 } else {
262 current_prefix = format!("{current_prefix}.{stripped}");
263 }
264 } else {
265 let full_key = if current_prefix.is_empty() {
267 key.to_string()
268 } else {
269 format!("{current_prefix}.{key}")
270 };
271 let value = value
273 .strip_prefix('"')
274 .and_then(|v| v.strip_suffix('"'))
275 .unwrap_or(value);
276 fields.insert(full_key, value.to_string());
277 }
278 }
279 }
280 }
281 }
282
283 fields
284 }
285
286 pub fn extract_front_matter<'a>(content: &'a str) -> Vec<&'a str> {
288 let lines: Vec<&'a str> = content.lines().collect();
289 if lines.len() < 3 {
290 return Vec::new();
291 }
292
293 let front_matter_type = Self::detect_front_matter_type(content);
294 if front_matter_type == FrontMatterType::None {
295 return Vec::new();
296 }
297
298 let mut front_matter = Vec::new();
299 let mut in_front_matter = false;
300
301 for (i, line) in lines.iter().enumerate() {
302 match front_matter_type {
303 FrontMatterType::Yaml => {
304 if i == 0 && STANDARD_FRONT_MATTER_START.is_match(line) {
305 in_front_matter = true;
306 continue;
307 } else if STANDARD_FRONT_MATTER_END.is_match(line) && in_front_matter && i > 0 {
308 break;
309 }
310 }
311 FrontMatterType::Toml => {
312 if i == 0 && TOML_FRONT_MATTER_START.is_match(line) {
313 in_front_matter = true;
314 continue;
315 } else if TOML_FRONT_MATTER_END.is_match(line) && in_front_matter && i > 0 {
316 break;
317 }
318 }
319 FrontMatterType::Json => {
320 if i == 0 && JSON_FRONT_MATTER_START.is_match(line) {
321 in_front_matter = true;
322 continue;
323 } else if JSON_FRONT_MATTER_END.is_match(line) && in_front_matter && i > 0 {
324 break;
325 }
326 }
327 FrontMatterType::Malformed => {
328 if i == 0
329 && (MALFORMED_FRONT_MATTER_START1.is_match(line)
330 || MALFORMED_FRONT_MATTER_START2.is_match(line))
331 {
332 in_front_matter = true;
333 continue;
334 } else if (MALFORMED_FRONT_MATTER_END1.is_match(line) || MALFORMED_FRONT_MATTER_END2.is_match(line))
335 && in_front_matter
336 && i > 0
337 {
338 break;
339 }
340 }
341 FrontMatterType::None => break,
342 }
343
344 if in_front_matter {
345 front_matter.push(*line);
346 }
347 }
348
349 front_matter
350 }
351
352 pub fn detect_front_matter_type(content: &str) -> FrontMatterType {
354 let lines: Vec<&str> = content.lines().collect();
355 if lines.is_empty() {
356 return FrontMatterType::None;
357 }
358
359 let first_line = lines[0];
360
361 if STANDARD_FRONT_MATTER_START.is_match(first_line) {
362 for line in lines.iter().skip(1) {
364 if STANDARD_FRONT_MATTER_END.is_match(line) {
365 return FrontMatterType::Yaml;
366 }
367 }
368 } else if TOML_FRONT_MATTER_START.is_match(first_line) {
369 for line in lines.iter().skip(1) {
371 if TOML_FRONT_MATTER_END.is_match(line) {
372 return FrontMatterType::Toml;
373 }
374 }
375 } else if JSON_FRONT_MATTER_START.is_match(first_line) {
376 for line in lines.iter().skip(1) {
378 if JSON_FRONT_MATTER_END.is_match(line) {
379 return FrontMatterType::Json;
380 }
381 }
382 } else if MALFORMED_FRONT_MATTER_START1.is_match(first_line)
383 || MALFORMED_FRONT_MATTER_START2.is_match(first_line)
384 {
385 for line in lines.iter().skip(1) {
387 if MALFORMED_FRONT_MATTER_END1.is_match(line) || MALFORMED_FRONT_MATTER_END2.is_match(line) {
388 return FrontMatterType::Malformed;
389 }
390 }
391 }
392
393 FrontMatterType::None
394 }
395
396 pub fn get_front_matter_end_line(content: &str) -> usize {
398 let lines: Vec<&str> = content.lines().collect();
399 if lines.len() < 3 {
400 return 0;
401 }
402
403 let front_matter_type = Self::detect_front_matter_type(content);
404 if front_matter_type == FrontMatterType::None {
405 return 0;
406 }
407
408 let mut in_front_matter = false;
409
410 for (i, line) in lines.iter().enumerate() {
411 match front_matter_type {
412 FrontMatterType::Yaml => {
413 if i == 0 && STANDARD_FRONT_MATTER_START.is_match(line) {
414 in_front_matter = true;
415 } else if STANDARD_FRONT_MATTER_END.is_match(line) && in_front_matter && i > 0 {
416 return i + 1;
417 }
418 }
419 FrontMatterType::Toml => {
420 if i == 0 && TOML_FRONT_MATTER_START.is_match(line) {
421 in_front_matter = true;
422 } else if TOML_FRONT_MATTER_END.is_match(line) && in_front_matter && i > 0 {
423 return i + 1;
424 }
425 }
426 FrontMatterType::Json => {
427 if i == 0 && JSON_FRONT_MATTER_START.is_match(line) {
428 in_front_matter = true;
429 } else if JSON_FRONT_MATTER_END.is_match(line) && in_front_matter && i > 0 {
430 return i + 1;
431 }
432 }
433 FrontMatterType::Malformed => {
434 if i == 0
435 && (MALFORMED_FRONT_MATTER_START1.is_match(line)
436 || MALFORMED_FRONT_MATTER_START2.is_match(line))
437 {
438 in_front_matter = true;
439 } else if (MALFORMED_FRONT_MATTER_END1.is_match(line) || MALFORMED_FRONT_MATTER_END2.is_match(line))
440 && in_front_matter
441 && i > 0
442 {
443 return i + 1;
444 }
445 }
446 FrontMatterType::None => return 0,
447 }
448 }
449
450 0
451 }
452
453 pub fn fix_malformed_front_matter(content: &str) -> String {
455 let lines: Vec<&str> = content.lines().collect();
456 if lines.len() < 3 {
457 return content.to_string();
458 }
459
460 let mut result = Vec::new();
461 let mut in_front_matter = false;
462 let mut is_malformed = false;
463
464 for (i, line) in lines.iter().enumerate() {
465 if i == 0 {
467 if STANDARD_FRONT_MATTER_START.is_match(line) {
468 in_front_matter = true;
470 result.push(line.to_string());
471 } else if MALFORMED_FRONT_MATTER_START1.is_match(line) || MALFORMED_FRONT_MATTER_START2.is_match(line) {
472 in_front_matter = true;
474 is_malformed = true;
475 result.push("---".to_string());
476 } else {
477 result.push(line.to_string());
479 }
480 continue;
481 }
482
483 if in_front_matter {
485 if STANDARD_FRONT_MATTER_END.is_match(line) {
486 in_front_matter = false;
488 result.push(line.to_string());
489 } else if (MALFORMED_FRONT_MATTER_END1.is_match(line) || MALFORMED_FRONT_MATTER_END2.is_match(line))
490 && is_malformed
491 {
492 in_front_matter = false;
494 result.push("---".to_string());
495 } else {
496 result.push(line.to_string());
498 }
499 continue;
500 }
501
502 result.push(line.to_string());
504 }
505
506 result.join("\n")
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513
514 #[test]
515 fn test_front_matter_type_enum() {
516 assert_eq!(FrontMatterType::Yaml, FrontMatterType::Yaml);
517 assert_eq!(FrontMatterType::Toml, FrontMatterType::Toml);
518 assert_eq!(FrontMatterType::Json, FrontMatterType::Json);
519 assert_eq!(FrontMatterType::Malformed, FrontMatterType::Malformed);
520 assert_eq!(FrontMatterType::None, FrontMatterType::None);
521 assert_ne!(FrontMatterType::Yaml, FrontMatterType::Toml);
522 }
523
524 #[test]
525 fn test_detect_front_matter_type() {
526 let yaml_content = "---\ntitle: Test\n---\nContent";
528 assert_eq!(
529 FrontMatterUtils::detect_front_matter_type(yaml_content),
530 FrontMatterType::Yaml
531 );
532
533 let toml_content = "+++\ntitle = \"Test\"\n+++\nContent";
535 assert_eq!(
536 FrontMatterUtils::detect_front_matter_type(toml_content),
537 FrontMatterType::Toml
538 );
539
540 let json_content = "{\n\"title\": \"Test\"\n}\nContent";
542 assert_eq!(
543 FrontMatterUtils::detect_front_matter_type(json_content),
544 FrontMatterType::Json
545 );
546
547 let malformed1 = "- --\ntitle: Test\n- --\nContent";
549 assert_eq!(
550 FrontMatterUtils::detect_front_matter_type(malformed1),
551 FrontMatterType::Malformed
552 );
553
554 let malformed2 = "-- -\ntitle: Test\n-- -\nContent";
555 assert_eq!(
556 FrontMatterUtils::detect_front_matter_type(malformed2),
557 FrontMatterType::Malformed
558 );
559
560 assert_eq!(
562 FrontMatterUtils::detect_front_matter_type("# Regular content"),
563 FrontMatterType::None
564 );
565 assert_eq!(FrontMatterUtils::detect_front_matter_type(""), FrontMatterType::None);
566
567 assert_eq!(
569 FrontMatterUtils::detect_front_matter_type("---\ntitle: Test"),
570 FrontMatterType::None
571 );
572 }
573
574 #[test]
575 fn test_is_in_front_matter() {
576 let content = "---\ntitle: Test\nauthor: Me\n---\nContent here";
577
578 assert!(FrontMatterUtils::is_in_front_matter(content, 0)); assert!(FrontMatterUtils::is_in_front_matter(content, 1)); assert!(FrontMatterUtils::is_in_front_matter(content, 2)); assert!(!FrontMatterUtils::is_in_front_matter(content, 3)); assert!(!FrontMatterUtils::is_in_front_matter(content, 4)); assert!(!FrontMatterUtils::is_in_front_matter(content, 100));
588 }
589
590 #[test]
591 fn test_extract_front_matter() {
592 let content = "---\ntitle: Test\nauthor: Me\n---\nContent";
593 let front_matter = FrontMatterUtils::extract_front_matter(content);
594
595 assert_eq!(front_matter.len(), 2);
596 assert_eq!(front_matter[0], "title: Test");
597 assert_eq!(front_matter[1], "author: Me");
598
599 let no_fm = FrontMatterUtils::extract_front_matter("Regular content");
601 assert!(no_fm.is_empty());
602
603 let short = FrontMatterUtils::extract_front_matter("---\n---");
605 assert!(short.is_empty());
606 }
607
608 #[test]
609 fn test_has_front_matter_field() {
610 let content = "---\ntitle: Test\nauthor: Me\n---\nContent";
611
612 assert!(FrontMatterUtils::has_front_matter_field(content, "title"));
613 assert!(FrontMatterUtils::has_front_matter_field(content, "author"));
614 assert!(!FrontMatterUtils::has_front_matter_field(content, "date"));
615
616 assert!(!FrontMatterUtils::has_front_matter_field("Regular content", "title"));
618
619 assert!(!FrontMatterUtils::has_front_matter_field("--", "title"));
621 }
622
623 #[test]
624 fn test_get_front_matter_field_value() {
625 let yaml_content = "---\ntitle: Test Title\nauthor: \"John Doe\"\n---\nContent";
627 assert_eq!(
628 FrontMatterUtils::get_front_matter_field_value(yaml_content, "title"),
629 Some("Test Title")
630 );
631 assert_eq!(
632 FrontMatterUtils::get_front_matter_field_value(yaml_content, "author"),
633 Some("John Doe")
634 );
635 assert_eq!(
636 FrontMatterUtils::get_front_matter_field_value(yaml_content, "nonexistent"),
637 None
638 );
639
640 let toml_content = "+++\ntitle = \"Test Title\"\nauthor = \"John Doe\"\n+++\nContent";
642 assert_eq!(
643 FrontMatterUtils::get_front_matter_field_value(toml_content, "title"),
644 Some("Test Title")
645 );
646 assert_eq!(
647 FrontMatterUtils::get_front_matter_field_value(toml_content, "author"),
648 Some("John Doe")
649 );
650
651 let json_style_yaml = "---\n\"title\": \"Test Title\"\n---\nContent";
653 assert_eq!(
654 FrontMatterUtils::get_front_matter_field_value(json_style_yaml, "title"),
655 Some("Test Title")
656 );
657
658 let json_fm = "{\n\"title\": \"Test Title\"\n}\nContent";
660 assert_eq!(
661 FrontMatterUtils::get_front_matter_field_value(json_fm, "title"),
662 Some("Test Title")
663 );
664
665 assert_eq!(
667 FrontMatterUtils::get_front_matter_field_value("Regular content", "title"),
668 None
669 );
670
671 assert_eq!(FrontMatterUtils::get_front_matter_field_value("--", "title"), None);
673 }
674
675 #[test]
676 fn test_extract_front_matter_fields() {
677 let yaml_content = "---\ntitle: Test\nauthor: Me\n---\nContent";
679 let fields = FrontMatterUtils::extract_front_matter_fields(yaml_content);
680
681 assert_eq!(fields.get("title"), Some(&"Test".to_string()));
682 assert_eq!(fields.get("author"), Some(&"Me".to_string()));
683
684 let toml_content = "+++\ntitle = \"Test\"\nauthor = \"Me\"\n+++\nContent";
686 let toml_fields = FrontMatterUtils::extract_front_matter_fields(toml_content);
687
688 assert_eq!(toml_fields.get("title"), Some(&"Test".to_string()));
689 assert_eq!(toml_fields.get("author"), Some(&"Me".to_string()));
690
691 let no_fields = FrontMatterUtils::extract_front_matter_fields("Regular content");
693 assert!(no_fields.is_empty());
694 }
695
696 #[test]
697 fn test_get_front_matter_end_line() {
698 let content = "---\ntitle: Test\n---\nContent";
699 assert_eq!(FrontMatterUtils::get_front_matter_end_line(content), 3);
700
701 let toml_content = "+++\ntitle = \"Test\"\n+++\nContent";
703 assert_eq!(FrontMatterUtils::get_front_matter_end_line(toml_content), 3);
704
705 assert_eq!(FrontMatterUtils::get_front_matter_end_line("Regular content"), 0);
707
708 assert_eq!(FrontMatterUtils::get_front_matter_end_line("--"), 0);
710 }
711
712 #[test]
713 fn test_fix_malformed_front_matter() {
714 let malformed1 = "- --\ntitle: Test\n- --\nContent";
716 let fixed1 = FrontMatterUtils::fix_malformed_front_matter(malformed1);
717 assert!(fixed1.starts_with("---\ntitle: Test\n---"));
718
719 let malformed2 = "-- -\ntitle: Test\n-- -\nContent";
721 let fixed2 = FrontMatterUtils::fix_malformed_front_matter(malformed2);
722 assert!(fixed2.starts_with("---\ntitle: Test\n---"));
723
724 let valid = "---\ntitle: Test\n---\nContent";
726 let unchanged = FrontMatterUtils::fix_malformed_front_matter(valid);
727 assert_eq!(unchanged, valid);
728
729 let no_fm = "# Regular content";
731 assert_eq!(FrontMatterUtils::fix_malformed_front_matter(no_fm), no_fm);
732
733 let short = "--";
735 assert_eq!(FrontMatterUtils::fix_malformed_front_matter(short), short);
736 }
737
738 #[test]
739 fn test_nested_yaml_fields() {
740 let content = "---
741title: Test
742author:
743 name: John Doe
744 email: john@example.com
745---
746Content";
747
748 let fields = FrontMatterUtils::extract_front_matter_fields(content);
749
750 assert!(fields.contains_key("title"));
753 }
755
756 #[test]
757 fn test_edge_cases() {
758 assert_eq!(FrontMatterUtils::detect_front_matter_type(""), FrontMatterType::None);
760 assert!(FrontMatterUtils::extract_front_matter("").is_empty());
761 assert_eq!(FrontMatterUtils::get_front_matter_end_line(""), 0);
762
763 let only_delim = "---\n---";
765 assert!(FrontMatterUtils::extract_front_matter(only_delim).is_empty());
766
767 let multiple = "---\ntitle: First\n---\n---\ntitle: Second\n---";
769 let fm_type = FrontMatterUtils::detect_front_matter_type(multiple);
770 assert_eq!(fm_type, FrontMatterType::Yaml);
771 let fields = FrontMatterUtils::extract_front_matter_fields(multiple);
772 assert_eq!(fields.get("title"), Some(&"First".to_string()));
773 }
774
775 #[test]
776 fn test_unicode_content() {
777 let content = "---\ntitle: 你好世界\nauthor: José\n---\nContent";
778
779 assert_eq!(
780 FrontMatterUtils::detect_front_matter_type(content),
781 FrontMatterType::Yaml
782 );
783 assert_eq!(
784 FrontMatterUtils::get_front_matter_field_value(content, "title"),
785 Some("你好世界")
786 );
787 assert_eq!(
788 FrontMatterUtils::get_front_matter_field_value(content, "author"),
789 Some("José")
790 );
791 }
792}