use crate::prelude::*;
mod entry;
mod loader;
mod persistence;
mod topic;
pub use entry::*;
pub use loader::*;
pub use persistence::*;
pub use topic::*;
pub struct Journal<LOADER>
where
LOADER: JournalLoaderTrait,
{
source_root: PathBuf,
topics: TopicMap,
persistence: LOADER::DataDriver,
}
impl<LOADER> Journal<LOADER>
where
LOADER: JournalLoaderTrait,
{
pub fn install(config: LOADER::ConfigSource) -> Result<()> {
LOADER::install(config)
}
pub fn load(config: LOADER::ConfigSource) -> Result<Self> {
let (persistence, topics, source_root) = LOADER::load(config)?;
Ok(Self {
source_root,
persistence,
topics,
})
}
pub fn with_topic<T>(&self, topic: &T) -> Result<&Topic>
where
T: AsRef<str>,
{
self.topics
.find(topic)
.with_context(|| format!("Topic Not Found [{}]", topic.as_ref()))
}
pub fn each_topic(&self) -> impl Iterator<Item = &Topic> {
self.topics.iter()
}
pub fn persist_entry(&self, entry: &Entry) -> Result<PathBuf> {
let topic = self.with_topic(&entry.topic_name())?;
let file_location = self.source_root.join(topic.source_path(entry)?);
let data = &self.persistence.serialize(entry)?;
self.persistence.persist(&file_location, data)?;
Ok(file_location)
}
pub fn fetch_entry(&self, path: &Path) -> Result<Entry> {
self.persistence.fetch(path)
}
pub fn entries_for_topic<T>(&self, topic: &T) -> Result<Vec<Entry>>
where
T: AsRef<str>,
{
self.persistence
.query(&Query::ForTopic(self.with_topic(topic)?))
}
pub fn all_entries(&self) -> Result<Vec<Entry>> {
self.persistence.query(&Query::AllEntries)
}
}
#[cfg(test)]
mod test {
use crate::prelude::*;
use crate::support::prelude::*;
use pretty_assertions::assert_eq;
#[rstest]
fn full_generation() -> Result<()> {
let journal: Journal<MockJournalLoaderTrait> = Journal {
persistence: FilePersistence::new("/tmp/mdbook-journal-test"),
source_root: "/tmp/mdbook-journal-test".into(),
topics: TopicMap::default().insert(
Topic::builder("code-blog")
.add_variable(Variable::new("title").required())
.build(),
)?,
};
let topic = journal.with_topic(&"code-blog")?;
assert_eq!("code-blog", topic.name());
let mut adapter = MockEntryGenerationTrait::new();
adapter
.expect_created_at()
.returning(|| Ok(Utc.with_ymd_and_hms(2024, 10, 19, 16, 20, 0).unwrap()));
adapter
.expect_collect_value()
.withf(|var| var.key() == "title")
.returning(|_| Ok(Some(MetaValue::String("Test Entry".to_owned()))));
let entry = topic.generate_entry(adapter)?;
assert_eq!(entry.topic_name(), "code-blog");
assert_eq!(entry.created_at().year(), 2024);
assert_eq!(entry.created_at().month(), 10);
assert_eq!(
entry.meta_value(&"title").unwrap(),
&MetaValue::String("Test Entry".to_owned())
);
assert_eq!(entry.content(), "");
let file_location = journal.persist_entry(&entry)?;
let reloaded = journal.fetch_entry(&file_location)?;
assert_eq!(entry.topic_name(), reloaded.topic_name());
assert_eq!(entry.created_at(), reloaded.created_at());
assert_eq!(entry.content(), reloaded.content());
assert_eq!(entry.meta_value(&"title"), reloaded.meta_value(&"title"));
assert_eq!(&file_location, reloaded.file_location().unwrap());
let entries = journal.entries_for_topic(&"code-blog")?;
assert_eq!(entry.meta_value(&"title"), entries[0].meta_value(&"title"));
let entries = journal.all_entries()?;
assert_eq!(entry.meta_value(&"title"), entries[0].meta_value(&"title"));
Ok(())
}
}