use super::{DefaultProperties, Note, NoteFromReader, NoteFromString};
use crate::note::parser::{self, ResultParse, parse_note};
use serde::de::DeserializeOwned;
use std::{
borrow::Cow,
fs::File,
path::{Path, PathBuf},
};
use thiserror::Error;
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct NoteInMemory<T = DefaultProperties>
where
T: Clone,
{
content: String,
path: Option<PathBuf>,
properties: Option<T>,
}
#[derive(Debug, Error)]
pub enum Error {
#[error("IO error: {0}")]
IO(#[from] std::io::Error),
#[error("Invalid frontmatter format")]
InvalidFormat(#[from] parser::Error),
#[error("YAML parsing error: {0}")]
Yaml(#[from] serde_yml::Error),
}
impl<T> Note for NoteInMemory<T>
where
T: Clone,
{
type Properties = T;
type Error = self::Error;
#[inline]
fn properties(&self) -> Result<Option<Cow<'_, T>>, Self::Error> {
Ok(self.properties.as_ref().map(|p| Cow::Borrowed(p)))
}
#[inline]
fn content(&self) -> Result<Cow<'_, str>, Self::Error> {
Ok(Cow::Borrowed(&self.content))
}
#[inline]
fn path(&self) -> Option<Cow<'_, Path>> {
self.path.as_ref().map(|p| Cow::Borrowed(p.as_path()))
}
}
impl<T> NoteInMemory<T>
where
T: Clone,
{
#[inline]
pub fn set_path(&mut self, path: Option<PathBuf>) {
self.path = path;
}
}
impl<T> NoteFromString for NoteInMemory<T>
where
T: DeserializeOwned + Clone,
{
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
fn from_string(raw_text: impl AsRef<str>) -> Result<Self, Self::Error> {
let raw_text = raw_text.as_ref();
#[cfg(feature = "tracing")]
tracing::trace!("Parsing in-memory note");
match parse_note(raw_text)? {
ResultParse::WithProperties {
content,
properties,
} => {
#[cfg(feature = "tracing")]
tracing::trace!("Frontmatter detected, parsing properties");
Ok(Self {
content: content.to_string(),
properties: Some(serde_yml::from_str(properties)?),
path: None,
})
}
ResultParse::WithoutProperties => {
#[cfg(feature = "tracing")]
tracing::trace!("No frontmatter found, storing raw content");
Ok(Self {
content: raw_text.to_string(),
path: None,
properties: None,
})
}
}
}
}
#[cfg(not(target_family = "wasm"))]
impl<T> crate::prelude::NoteFromFile for NoteInMemory<T>
where
T: DeserializeOwned + Clone,
{
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all, fields(path = %path.as_ref().display())))]
fn from_file(path: impl AsRef<Path>) -> Result<Self, Self::Error> {
let path_buf = path.as_ref().to_path_buf();
#[cfg(feature = "tracing")]
tracing::trace!("Parse obsidian file from file");
let mut file = File::open(&path_buf)?;
let mut note = Self::from_reader(&mut file)?;
note.set_path(Some(path_buf));
Ok(note)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::note::{
note_aliases::tests::impl_all_tests_aliases,
note_is_todo::tests::impl_all_tests_is_todo,
note_read::tests::{
impl_all_tests_from_file, impl_all_tests_from_reader, impl_all_tests_from_string,
},
note_tags::tests::impl_all_tests_tags,
note_write::tests::impl_all_tests_flush,
};
impl_all_tests_tags!(NoteInMemory);
impl_all_tests_from_reader!(NoteInMemory);
impl_all_tests_from_string!(NoteInMemory);
impl_all_tests_from_file!(NoteInMemory);
impl_all_tests_flush!(NoteInMemory);
impl_all_tests_is_todo!(NoteInMemory);
impl_all_tests_aliases!(NoteInMemory);
}