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}