obsidian_parser/note/
note_write.rs1use super::{Note, OpenOptions};
4use crate::note::parser;
5use serde::Serialize;
6use std::io::Write;
7
8pub trait NoteWrite: Note
10where
11 Self::Properties: Serialize,
12 Self::Error: From<std::io::Error> + From<serde_yml::Error> + From<parser::Error>,
13{
14 fn flush_content(&self, open_option: &OpenOptions) -> Result<(), Self::Error> {
18 if let Some(path) = self.path() {
19 let text = std::fs::read_to_string(&path)?;
20 let parsed = parser::parse_note(&text)?;
21
22 let mut file = open_option.open(path)?;
23
24 match parsed {
25 parser::ResultParse::WithProperties {
26 content: _,
27 properties,
28 } => file.write_all(
29 format!("---\n{}\n---\n{}", properties, self.content()?).as_bytes(),
30 )?,
31 parser::ResultParse::WithoutProperties => {
32 file.write_all(self.content()?.as_bytes())?;
33 }
34 }
35 }
36
37 Ok(())
38 }
39
40 fn flush_properties(&self, open_option: &OpenOptions) -> Result<(), Self::Error> {
44 if let Some(path) = self.path() {
45 let text = std::fs::read_to_string(&path)?;
46 let parsed = parser::parse_note(&text)?;
47
48 let mut file = open_option.open(path)?;
49
50 match parsed {
51 parser::ResultParse::WithProperties {
52 content,
53 properties: _,
54 } => match self.properties()? {
55 Some(properties) => file.write_all(
56 format!(
57 "---\n{}\n---\n{}",
58 serde_yml::to_string(&properties)?,
59 content
60 )
61 .as_bytes(),
62 )?,
63 None => file.write_all(self.content()?.as_bytes())?,
64 },
65 parser::ResultParse::WithoutProperties => {
66 file.write_all(self.content()?.as_bytes())?;
67 }
68 }
69 }
70
71 Ok(())
72 }
73
74 fn flush(&self, open_option: &OpenOptions) -> Result<(), Self::Error> {
78 if let Some(path) = self.path() {
79 let mut file = open_option.open(path)?;
80
81 match self.properties()? {
82 Some(properties) => file.write_all(
83 format!(
84 "---\n{}\n---\n{}",
85 serde_yml::to_string(&properties)?,
86 self.content()?
87 )
88 .as_bytes(),
89 )?,
90 None => file.write_all(self.content()?.as_bytes())?,
91 }
92 }
93
94 Ok(())
95 }
96}
97
98impl<T: Note> NoteWrite for T
99where
100 T::Properties: Serialize,
101 Self::Error: From<std::io::Error> + From<serde_yml::Error> + From<super::parser::Error>,
102{
103}
104
105#[cfg(test)]
106pub(crate) mod tests {
107 use super::*;
108 use crate::note::{DefaultProperties, NoteFromFile};
109 use tempfile::NamedTempFile;
110
111 const TEST_DATA: &str = "---\n\
112topic: life\n\
113created: 2025-03-16\n\
114---\n\
115Test data\n\
116---\n\
117Two test data";
118
119 pub(crate) fn flush_properties<T>() -> Result<(), T::Error>
120 where
121 T: NoteFromFile<Properties = DefaultProperties> + NoteWrite,
122 T::Error: From<std::io::Error> + From<serde_yml::Error> + From<parser::Error>,
123 {
124 let mut test_file = NamedTempFile::new().unwrap();
125 test_file.write_all(TEST_DATA.as_bytes()).unwrap();
126
127 let file = T::from_file(test_file.path())?;
128 let open_options = OpenOptions::new().write(true).create(false).clone();
129 file.flush_properties(&open_options)?;
130 drop(file);
131
132 let file = T::from_file(test_file.path())?;
133
134 let properties = file.properties()?.unwrap();
135 assert_eq!(properties["topic"], "life");
136 assert_eq!(properties["created"], "2025-03-16");
137 assert_eq!(file.content().unwrap(), "Test data\n---\nTwo test data");
138
139 Ok(())
140 }
141
142 pub(crate) fn flush_content<T>() -> Result<(), T::Error>
143 where
144 T: NoteFromFile<Properties = DefaultProperties> + NoteWrite,
145 T::Error: From<std::io::Error> + From<serde_yml::Error> + From<parser::Error>,
146 {
147 let mut test_file = NamedTempFile::new().unwrap();
148 test_file.write_all(TEST_DATA.as_bytes()).unwrap();
149
150 let file = T::from_file(test_file.path())?;
151 let open_options = OpenOptions::new().write(true).create(false).clone();
152 file.flush_content(&open_options)?;
153 drop(file);
154
155 let file = T::from_file(test_file.path())?;
156 let properties = file.properties()?.unwrap();
157 assert_eq!(properties["topic"], "life");
158 assert_eq!(properties["created"], "2025-03-16");
159 assert_eq!(file.content().unwrap(), "Test data\n---\nTwo test data");
160
161 Ok(())
162 }
163
164 pub(crate) fn flush<T>() -> Result<(), T::Error>
165 where
166 T: NoteFromFile<Properties = DefaultProperties> + NoteWrite,
167 T::Error: From<std::io::Error> + From<serde_yml::Error> + From<parser::Error>,
168 {
169 let mut test_file = NamedTempFile::new().unwrap();
170 test_file.write_all(TEST_DATA.as_bytes()).unwrap();
171
172 let file = T::from_file(test_file.path())?;
173 let open_options = OpenOptions::new().write(true).create(false).clone();
174 file.flush(&open_options)?;
175 drop(file);
176
177 let file = T::from_file(test_file.path())?;
178 let properties = file.properties()?.unwrap();
179 assert_eq!(properties["topic"], "life");
180 assert_eq!(properties["created"], "2025-03-16");
181 assert_eq!(file.content().unwrap(), "Test data\n---\nTwo test data");
182
183 Ok(())
184 }
185
186 macro_rules! impl_all_tests_flush {
187 ($impl_note:path) => {
188 #[allow(unused_imports)]
189 use $crate::note::note_write::tests::*;
190
191 impl_test_for_note!(impl_flush, flush, $impl_note);
192 impl_test_for_note!(impl_flush_content, flush_content, $impl_note);
193 impl_test_for_note!(impl_flush_properties, flush_properties, $impl_note);
194 };
195 }
196
197 pub(crate) use impl_all_tests_flush;
198}