obsidian_parser/note/mod.rs
1//! Represents an Obsidian note file with frontmatter properties and content
2
3pub mod note_aliases;
4pub mod note_default;
5pub mod note_in_memory;
6pub mod note_is_todo;
7pub mod note_on_disk;
8pub mod note_once_cell;
9pub mod note_once_lock;
10pub mod note_read;
11pub mod note_tags;
12pub mod parser;
13
14#[cfg(not(target_family = "wasm"))]
15pub mod note_write;
16
17use std::{borrow::Cow, collections::HashMap, fs::OpenOptions, path::Path};
18
19pub use note_default::NoteDefault;
20pub use note_read::{NoteFromReader, NoteFromString};
21
22#[cfg(not(target_family = "wasm"))]
23pub use note_read::NoteFromFile;
24
25#[cfg(not(target_family = "wasm"))]
26pub use note_write::NoteWrite;
27
28pub(crate) type DefaultProperties = HashMap<String, serde_yml::Value>;
29
30/// Represents an Obsidian note file with frontmatter properties and content
31///
32/// This trait provides a standardized interface for working with Obsidian markdown files,
33/// handling frontmatter parsing, content extraction, and file operations.
34///
35/// # Example
36/// ```no_run
37/// use obsidian_parser::prelude::*;
38/// use serde::Deserialize;
39///
40/// #[derive(Deserialize, Clone)]
41/// struct NoteProperties {
42/// topic: String,
43/// created: String,
44/// }
45///
46/// let note: NoteInMemory<NoteProperties> = NoteFromFile::from_file("note.md").unwrap();
47/// let properties = note.properties().unwrap().unwrap();
48/// println!("Note topic: {}", properties.topic);
49/// ```
50///
51/// # Other
52/// * To open and read [`Note`] to a file, see [`note_read`] module.
53/// * To write and modify [`Note`] to a file, use the [`NoteWrite`] trait.
54pub trait Note: Sized {
55 /// Frontmatter properties type
56 type Properties: Clone;
57
58 /// Error type
59 type Error: std::error::Error;
60
61 /// Returns the parsed properties of frontmatter
62 ///
63 /// Returns [`None`] if the note has no properties
64 fn properties(&self) -> Result<Option<Cow<'_, Self::Properties>>, Self::Error>;
65
66 /// Returns the main content body of the note (excluding frontmatter)
67 ///
68 /// # Implementation Notes
69 /// - Strips YAML frontmatter if present
70 /// - Preserves original formatting and whitespace
71 fn content(&self) -> Result<Cow<'_, str>, Self::Error>;
72
73 /// Returns the source file path if available
74 ///
75 /// Returns [`None`] for in-memory notes without physical storage
76 fn path(&self) -> Option<Cow<'_, Path>>;
77
78 /// Get note name
79 fn note_name(&self) -> Option<String> {
80 self.path().as_ref().map(|path| {
81 path.file_stem()
82 .expect("Path is not file")
83 .to_string_lossy()
84 .to_string()
85 })
86 }
87
88 /// Get count words from content
89 ///
90 /// # Example
91 ///
92 /// ```
93 /// use obsidian_parser::prelude::*;
94 ///
95 /// let data = "---\ntags:\n- my_tag\n---\n My super note";
96 /// let note = NoteInMemory::from_string_default(data).unwrap();
97 ///
98 /// assert_eq!(note.count_words_from_content().unwrap(), 3);
99 /// ```
100 fn count_words_from_content(&self) -> Result<usize, Self::Error> {
101 let content = self.content()?;
102 Ok(content.split_whitespace().count())
103 }
104
105 /// Get count symbols from content
106 ///
107 /// # Example
108 ///
109 /// ```
110 /// use obsidian_parser::prelude::*;
111 ///
112 /// let data = "---\ntags:\n- my_tag\n---\n My super note";
113 /// let content = "My super note";
114 ///
115 /// let note = NoteInMemory::from_string_default(data).unwrap();
116 ///
117 /// assert_eq!(note.count_symbols_from_content().unwrap(), content.len());
118 /// ``````
119 fn count_symbols_from_content(&self) -> Result<usize, Self::Error> {
120 let content = self.content()?;
121 Ok(content.len())
122 }
123}
124
125#[cfg(test)]
126pub(crate) mod impl_tests {
127 macro_rules! impl_test_for_note {
128 ($name_test:ident, $fn_test:ident, $impl_note:path) => {
129 #[cfg_attr(feature = "tracing", tracing_test::traced_test)]
130 #[test]
131 fn $name_test() {
132 $fn_test::<$impl_note>().unwrap();
133 }
134 };
135 }
136
137 pub(crate) use impl_test_for_note;
138}