1use std::path::{Path, PathBuf};
4
5pub fn is_path_within_root(path: &Path, root: &Path) -> bool {
7 let resolved = if path.exists() {
8 path.canonicalize().ok()
9 } else {
10 path.parent()
12 .map(|p| {
13 if p.as_os_str().is_empty() {
14 PathBuf::from(".")
15 } else {
16 p.to_path_buf()
17 }
18 })
19 .and_then(|p| p.canonicalize().ok())
20 .map(|p| p.join(path.file_name().unwrap_or_default()))
21 };
22
23 match resolved {
24 Some(abs_path) => abs_path.starts_with(root),
25 None => false,
26 }
27}
28
29pub fn resolve_path(path: &str, root: &Path) -> PathBuf {
31 let p = Path::new(path);
32 if p.is_absolute() {
33 p.to_path_buf()
34 } else {
35 root.join(p)
36 }
37}
38
39pub fn parse_frontmatter_content(raw: &str) -> (Option<serde_json::Value>, String) {
43 let trimmed = raw.trim_start();
44
45 if trimmed.starts_with("---")
47 && let Some(end_idx) = trimmed[3..].find("\n---")
48 {
49 let fm_str = &trimmed[3..3 + end_idx].trim();
50 let content_start = 3 + end_idx + 4; let content = trimmed[content_start..].trim_start().to_string();
52
53 match serde_yaml::from_str::<serde_json::Value>(fm_str) {
54 Ok(v) => return (Some(v), content),
55 Err(_) => return (None, raw.to_string()),
56 }
57 }
58
59 if trimmed.starts_with("+++")
61 && let Some(end_idx) = trimmed[3..].find("\n+++")
62 {
63 let fm_str = &trimmed[3..3 + end_idx].trim();
64 let content_start = 3 + end_idx + 4; let content = trimmed[content_start..].trim_start().to_string();
66
67 match toml::from_str::<toml::Value>(fm_str) {
68 Ok(v) => {
69 let json_str = serde_json::to_string(&v).unwrap_or_default();
71 match serde_json::from_str(&json_str) {
72 Ok(jv) => return (Some(jv), content),
73 Err(_) => return (None, raw.to_string()),
74 }
75 }
76 Err(_) => return (None, raw.to_string()),
77 }
78 }
79
80 (None, raw.to_string())
82}