turbovault_parser/
parsers.rs1use turbovault_core::{FileMetadata, Frontmatter, Result, SourcePosition, VaultFile};
4use std::path::{Path, PathBuf};
5
6mod callouts;
7mod embeds;
8mod frontmatter_parser;
9mod headings;
10mod tags;
11mod tasks;
12mod wikilinks;
13
14pub use self::frontmatter_parser::extract_frontmatter;
15
16#[allow(dead_code)]
18pub struct Parser {
19 vault_root: PathBuf,
20}
21
22impl Parser {
23 pub fn new(vault_root: PathBuf) -> Self {
25 Self { vault_root }
26 }
27
28 pub fn parse_file(&self, path: &Path, content: &str) -> Result<VaultFile> {
30 let metadata = self.extract_metadata(path, content)?;
31 let mut vault_file = VaultFile::new(path.to_path_buf(), content.to_string(), metadata);
32
33 if path.extension().is_some_and(|ext| ext == "md") {
35 self.parse_content(&mut vault_file)?;
36 vault_file.is_parsed = true;
37 vault_file.last_parsed = Some(
38 std::time::SystemTime::now()
39 .duration_since(std::time::UNIX_EPOCH)
40 .unwrap_or_default()
41 .as_secs_f64(),
42 );
43 }
44
45 Ok(vault_file)
46 }
47
48 fn extract_metadata(&self, path: &Path, content: &str) -> Result<FileMetadata> {
49 use std::collections::hash_map::DefaultHasher;
50 use std::hash::{Hash, Hasher};
51
52 let size = content.len() as u64;
53 let mut hasher = DefaultHasher::new();
54 content.hash(&mut hasher);
55 let checksum = format!("{:x}", hasher.finish());
56
57 Ok(FileMetadata {
58 path: path.to_path_buf(),
59 size,
60 created_at: 0.0,
61 modified_at: 0.0,
62 checksum,
63 is_attachment: !matches!(
64 path.extension().map(|e| e.to_str()),
65 Some(Some("md" | "txt"))
66 ),
67 })
68 }
69
70 fn parse_content(&self, vault_file: &mut VaultFile) -> Result<()> {
72 let content = &vault_file.content;
73
74 if let Ok((fm_str, content_without_fm)) = extract_frontmatter(content) {
76 if let Some(fm_str) = fm_str {
77 vault_file.frontmatter = self.parse_frontmatter(&fm_str)?;
78 }
79 vault_file.content = content_without_fm;
80 }
81
82 let content = &vault_file.content;
83
84 vault_file
86 .links
87 .extend(wikilinks::parse_wikilinks(content, &vault_file.path));
88 vault_file
89 .links
90 .extend(embeds::parse_embeds(content, &vault_file.path));
91
92 vault_file.tags.extend(tags::parse_tags(content));
94
95 vault_file.tasks.extend(tasks::parse_tasks(content));
97
98 vault_file
100 .callouts
101 .extend(callouts::parse_callouts(content));
102
103 vault_file
105 .headings
106 .extend(headings::parse_headings(content));
107
108 Ok(())
109 }
110
111 fn parse_frontmatter(&self, fm_str: &str) -> Result<Option<Frontmatter>> {
112 match serde_yaml::from_str::<serde_json::Value>(fm_str) {
113 Ok(serde_json::Value::Object(map)) => {
114 let data = map.into_iter().collect();
115 Ok(Some(Frontmatter {
116 data,
117 position: SourcePosition::start(),
118 }))
119 }
120 Ok(_) => Ok(None),
121 Err(_) => Ok(None),
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn test_parser_creation() {
132 let parser = Parser::new(PathBuf::from("/vault"));
133 assert_eq!(parser.vault_root, PathBuf::from("/vault"));
134 }
135}