obsidian_parser/obfile/
obfile_in_memory.rs

1use crate::error::Error;
2use crate::obfile::{ObFile, ResultParse, parse_obfile};
3use serde::de::DeserializeOwned;
4use std::{collections::HashMap, path::PathBuf};
5
6/// In-memory representation of an Obsidian note file
7///
8/// This struct provides full access to parsed note content, properties, and path.
9/// It stores the entire file contents in memory, making it suitable for:
10/// - Frequent access to note content
11/// - Transformation or analysis workflows
12/// - Environments with fast storage (SSD/RAM disks)
13///
14/// # Performance Considerations
15/// - Uses ~2x memory of original file size (UTF-8 + deserialized properties)
16/// - Preferred for small-to-medium vaults (<10k notes)
17///
18/// For large vaults or read-heavy workflows, consider [`ObFileOnDisk`].
19///
20/// [`ObFileOnDisk`]: crate::obfile::obfile_on_disk::ObFileOnDisk
21#[derive(Debug, Default, PartialEq, Clone)]
22pub struct ObFileInMemory<T = HashMap<String, serde_yml::Value>>
23where
24    T: DeserializeOwned + Default + Clone + Send,
25{
26    /// Markdown content body (without frontmatter)
27    pub content: String,
28
29    /// Source file path (if loaded from disk)
30    pub path: Option<PathBuf>,
31
32    /// Parsed frontmatter properties
33    pub properties: T,
34}
35
36impl<T: DeserializeOwned + Default + Clone + Send> ObFile<T> for ObFileInMemory<T> {
37    fn content(&self) -> String {
38        self.content.clone()
39    }
40
41    fn path(&self) -> Option<PathBuf> {
42        self.path.clone()
43    }
44
45    fn properties(&self) -> T {
46        self.properties.clone()
47    }
48
49    /// Parses a string into an in-memory Obsidian note representation
50    ///
51    /// # Arguments
52    /// * `raw_text` - Full note text including optional frontmatter
53    /// * `path` - Optional source path for reference
54    ///
55    /// # Process
56    /// 1. Splits text into frontmatter/content sections
57    /// 2. Parses YAML frontmatter if present
58    /// 3. Stores content without frontmatter delimiters
59    ///
60    /// # Errors
61    /// - [`Error::InvalidFormat`] for malformed frontmatter
62    /// - [`Error::Yaml`] for invalid YAML syntax
63    ///
64    /// # Example
65    /// ```rust
66    /// use obsidian_parser::prelude::*;
67    /// use serde::Deserialize;
68    ///
69    /// #[derive(Deserialize, Clone, Default)]
70    /// struct NoteProperties {
71    ///     title: String
72    /// }
73    ///
74    /// let note = r#"---
75    /// title: Example
76    /// ---
77    /// Content"#;
78    ///
79    /// let file: ObFileInMemory<NoteProperties> = ObFileInMemory::from_string(note, None::<&str>).unwrap();
80    /// assert_eq!(file.properties().title, "Example");
81    /// assert_eq!(file.content(), "Content");
82    /// ```
83    fn from_string<P: AsRef<std::path::Path>>(
84        raw_text: &str,
85        path: Option<P>,
86    ) -> Result<Self, Error> {
87        let path_buf = path.map(|x| x.as_ref().to_path_buf());
88
89        #[cfg(feature = "logging")]
90        log::trace!(
91            "Parsing in-memory note{}",
92            path_buf
93                .as_ref()
94                .map(|p| format!(" from {}", p.display()))
95                .unwrap_or_default()
96        );
97
98        match parse_obfile(raw_text)? {
99            ResultParse::WithProperties {
100                content,
101                properties,
102            } => {
103                #[cfg(feature = "logging")]
104                log::trace!("Frontmatter detected, parsing properties");
105
106                Ok(Self {
107                    content: content.to_string(),
108                    properties: serde_yml::from_str(properties)?,
109                    path: path_buf,
110                })
111            }
112            ResultParse::WithoutProperties => {
113                #[cfg(feature = "logging")]
114                log::trace!("No frontmatter found, storing raw content");
115
116                Ok(Self {
117                    content: raw_text.to_string(),
118                    path: path_buf,
119                    properties: T::default(),
120                })
121            }
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use crate::obfile::impl_tests::{impl_all_tests_from_file, impl_all_tests_from_string};
130
131    impl_all_tests_from_string!(ObFileInMemory);
132    impl_all_tests_from_file!(ObFileInMemory);
133}