obsidian_parser/note/
note_in_memory.rs1use super::{DefaultProperties, Note, NoteFromReader, NoteFromString};
4use crate::note::parser::{self, ResultParse, parse_note};
5use serde::de::DeserializeOwned;
6use std::{
7 borrow::Cow,
8 fs::File,
9 path::{Path, PathBuf},
10};
11use thiserror::Error;
12
13#[derive(Debug, Default, PartialEq, Eq, Clone)]
29pub struct NoteInMemory<T = DefaultProperties>
30where
31 T: Clone,
32{
33 content: String,
35
36 path: Option<PathBuf>,
38
39 properties: Option<T>,
41}
42
43#[derive(Debug, Error)]
45pub enum Error {
46 #[error("IO error: {0}")]
48 IO(#[from] std::io::Error),
49
50 #[error("Invalid frontmatter format")]
64 InvalidFormat(#[from] parser::Error),
65
66 #[error("YAML parsing error: {0}")]
76 Yaml(#[from] serde_yml::Error),
77}
78
79impl<T> Note for NoteInMemory<T>
80where
81 T: Clone,
82{
83 type Properties = T;
84 type Error = self::Error;
85
86 #[inline]
88 fn properties(&self) -> Result<Option<Cow<'_, T>>, Self::Error> {
89 Ok(self.properties.as_ref().map(|p| Cow::Borrowed(p)))
90 }
91
92 #[inline]
94 fn content(&self) -> Result<Cow<'_, str>, Self::Error> {
95 Ok(Cow::Borrowed(&self.content))
96 }
97
98 #[inline]
100 fn path(&self) -> Option<Cow<'_, Path>> {
101 self.path.as_ref().map(|p| Cow::Borrowed(p.as_path()))
102 }
103}
104
105impl<T> NoteInMemory<T>
106where
107 T: Clone,
108{
109 #[inline]
111 pub fn set_path(&mut self, path: Option<PathBuf>) {
112 self.path = path;
113 }
114}
115
116impl<T> NoteFromString for NoteInMemory<T>
117where
118 T: DeserializeOwned + Clone,
119{
120 #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
157 fn from_string(raw_text: impl AsRef<str>) -> Result<Self, Self::Error> {
158 let raw_text = raw_text.as_ref();
159
160 #[cfg(feature = "tracing")]
161 tracing::trace!("Parsing in-memory note");
162
163 match parse_note(raw_text)? {
164 ResultParse::WithProperties {
165 content,
166 properties,
167 } => {
168 #[cfg(feature = "tracing")]
169 tracing::trace!("Frontmatter detected, parsing properties");
170
171 Ok(Self {
172 content: content.to_string(),
173 properties: Some(serde_yml::from_str(properties)?),
174 path: None,
175 })
176 }
177 ResultParse::WithoutProperties => {
178 #[cfg(feature = "tracing")]
179 tracing::trace!("No frontmatter found, storing raw content");
180
181 Ok(Self {
182 content: raw_text.to_string(),
183 path: None,
184 properties: None,
185 })
186 }
187 }
188 }
189}
190
191#[cfg(not(target_family = "wasm"))]
192impl<T> crate::prelude::NoteFromFile for NoteInMemory<T>
193where
194 T: DeserializeOwned + Clone,
195{
196 #[cfg_attr(feature = "tracing", tracing::instrument(skip_all, fields(path = %path.as_ref().display())))]
197 fn from_file(path: impl AsRef<Path>) -> Result<Self, Self::Error> {
198 let path_buf = path.as_ref().to_path_buf();
199
200 #[cfg(feature = "tracing")]
201 tracing::trace!("Parse obsidian file from file");
202
203 let mut file = File::open(&path_buf)?;
204 let mut note = Self::from_reader(&mut file)?;
205 note.set_path(Some(path_buf));
206
207 Ok(note)
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::note::{
215 note_aliases::tests::impl_all_tests_aliases,
216 note_is_todo::tests::impl_all_tests_is_todo,
217 note_read::tests::{
218 impl_all_tests_from_file, impl_all_tests_from_reader, impl_all_tests_from_string,
219 },
220 note_write::tests::impl_all_tests_flush,
221 };
222
223 impl_all_tests_from_reader!(NoteInMemory);
224 impl_all_tests_from_string!(NoteInMemory);
225 impl_all_tests_from_file!(NoteInMemory);
226 impl_all_tests_flush!(NoteInMemory);
227 impl_all_tests_is_todo!(NoteInMemory);
228 impl_all_tests_aliases!(NoteInMemory);
229}