mdbook_journal/
journal.rs1use crate::prelude::*;
2
3mod entry;
4mod loader;
5mod persistence;
6mod topic;
7
8pub use entry::*;
9pub use loader::*;
10pub use persistence::*;
11pub use topic::*;
12
13pub struct Journal<LOADER>
14where
15 LOADER: JournalLoaderTrait,
16{
17 source_root: PathBuf,
19 topics: TopicMap,
21 persistence: LOADER::DataDriver,
23}
24
25impl<LOADER> Journal<LOADER>
26where
27 LOADER: JournalLoaderTrait,
28{
29 pub fn install(config: LOADER::ConfigSource) -> Result<()> {
30 LOADER::install(config)
31 }
32
33 pub fn load(config: LOADER::ConfigSource) -> Result<Self> {
34 let (persistence, topics, source_root) = LOADER::load(config)?;
35
36 Ok(Self {
37 source_root,
38 persistence,
39 topics,
40 })
41 }
42
43 pub fn with_topic<T>(&self, topic: &T) -> Result<&Topic>
44 where
45 T: AsRef<str>,
46 {
47 self.topics
48 .find(topic)
49 .with_context(|| format!("Topic Not Found [{}]", topic.as_ref()))
50 }
51
52 pub fn each_topic(&self) -> impl Iterator<Item = &Topic> {
53 self.topics.iter()
54 }
55
56 pub fn persist_entry(&self, entry: &Entry) -> Result<PathBuf> {
57 let topic = self.with_topic(&entry.topic_name())?;
58 let file_location = self.source_root.join(topic.source_path(entry)?);
59 let data = &self.persistence.serialize(entry)?;
60 self.persistence.persist(&file_location, data)?;
61 Ok(file_location)
62 }
63
64 pub fn fetch_entry(&self, path: &Path) -> Result<Entry> {
65 self.persistence.fetch(path)
66 }
67
68 pub fn entries_for_topic<T>(&self, topic: &T) -> Result<Vec<Entry>>
69 where
70 T: AsRef<str>,
71 {
72 let mut entries = self
73 .persistence
74 .query(&Query::ForTopic(self.with_topic(topic)?))?;
75 self.hydrate_virtual_paths(&mut entries)?;
76 Ok(entries)
77 }
78
79 pub fn all_entries(&self) -> Result<Vec<Entry>> {
80 let mut entries = self.persistence.query(&Query::AllEntries)?;
81 self.hydrate_virtual_paths(&mut entries)?;
82 Ok(entries)
83 }
84
85 fn hydrate_virtual_paths(&self, entries: &mut [Entry]) -> Result<()> {
88 for entry in entries {
89 let path = self.with_topic(&entry.topic_name())?.virtual_path(entry)?;
90 entry.virtual_path = Some(path);
91 }
92
93 Ok(())
94 }
95}
96
97#[cfg(test)]
98mod test {
99 use crate::prelude::*;
100 use crate::support::prelude::*;
101 use pretty_assertions::assert_eq;
102
103 #[rstest]
104 fn full_generation() -> Result<()> {
105 let journal: Journal<MockJournalLoaderTrait> = Journal {
106 persistence: FilePersistence::new("/tmp/mdbook-journal-test"),
107 source_root: "/tmp/mdbook-journal-test".into(),
108 topics: TopicMap::default().insert(
109 Topic::builder("code-blog")
110 .add_variable(Variable::new("title").required())
111 .build(),
112 )?,
113 };
114
115 let topic = journal.with_topic(&"code-blog")?;
116 assert_eq!("code-blog", topic.name());
117
118 let mut adapter = MockEntryGenerationTrait::new();
119
120 adapter
121 .expect_created_at()
122 .returning(|| Ok(Utc.with_ymd_and_hms(2024, 10, 19, 16, 20, 0).unwrap()));
123
124 adapter
125 .expect_collect_value()
126 .withf(|var| var.key() == "title")
127 .returning(|_| Ok(Some(MetaValue::String("Test Entry".to_owned()))));
128
129 let entry = topic.generate_entry(&adapter)?;
130
131 assert_eq!(entry.topic_name(), "code-blog");
132 assert_eq!(entry.created_at().year(), 2024);
133 assert_eq!(entry.created_at().month(), 10);
134 assert_eq!(
135 entry.meta_value(&"title").unwrap(),
136 &MetaValue::String("Test Entry".to_owned())
137 );
138 assert_eq!(entry.content(), "");
139
140 let file_location = journal.persist_entry(&entry)?;
141 let reloaded = journal.fetch_entry(&file_location)?;
142
143 assert_eq!(entry.topic_name(), reloaded.topic_name());
144 assert_eq!(entry.created_at(), reloaded.created_at());
145 assert_eq!(entry.content(), reloaded.content());
146 assert_eq!(entry.meta_value(&"title"), reloaded.meta_value(&"title"));
147 assert_eq!(&file_location, reloaded.file_location().unwrap());
148
149 let entries = journal.entries_for_topic(&"code-blog")?;
150 assert_eq!(entry.meta_value(&"title"), entries[0].meta_value(&"title"));
151
152 let entries = journal.all_entries()?;
153 assert_eq!(entry.meta_value(&"title"), entries[0].meta_value(&"title"));
154 Ok(())
155 }
156}