commit 0bfe9f1ef2038afb550dae5cd394de6cb7a5a3ed Author: arcayr Date: Tue Aug 20 18:07:55 2024 +1000 initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81cf465 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/.vscode diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..270bfd5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,496 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cc" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "markdown-ast" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a3144cdf6273c7c25b0b2821a499ad980ff01f8902f85268d5ba35a0eabf9d" +dependencies = [ + "pulldown-cmark", + "pulldown-cmark-to-cmark", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "noteriety" +version = "0.1.0" +dependencies = [ + "chrono", + "ciborium", + "markdown-ast", + "serde", + "thiserror", + "uuid", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulldown-cmark" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4e75767fbc9d92b90e4d0c011f61358cde9513b31ef07ea3631b15ffc3b4fd" +dependencies = [ + "bitflags", + "getopts", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "15.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c77db841443d89a57ae94f22d29c022f6d9f41b00bddbf1f4024dbaf4bdce1" +dependencies = [ + "pulldown-cmark", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.208" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.208" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..21567f2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = [ "crates/*" ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae1ae7b --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# noteriety + +very simple outliner and notes app using gtk4. + + +## license + +this work is licensed under cc by-nc-sa 4.0. diff --git a/crates/noteriety/Cargo.toml b/crates/noteriety/Cargo.toml new file mode 100644 index 0000000..536c149 --- /dev/null +++ b/crates/noteriety/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "noteriety" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = { version = "0.4.38", features = ["serde"] } +ciborium = "0.2.2" +markdown-ast = "0.1.1" +serde = { version = "1.0.205", features = ["derive"] } +thiserror = "1.0.63" +uuid = { version = "1.10.0", features = ["serde", "v4"] } diff --git a/crates/noteriety/src/error.rs b/crates/noteriety/src/error.rs new file mode 100644 index 0000000..7d62d94 --- /dev/null +++ b/crates/noteriety/src/error.rs @@ -0,0 +1,25 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("could not create noteriety data directory")] + DataDirCreate, + #[error("could not read noteriety data directory")] + DataDirRead, + #[error("could not write noteriety data directory")] + DataDirWrite, + #[error("could not create notebook directory")] + NotebookDirCreate, + #[error("could not load notebook directory")] + NotebookDirRead, + #[error("could not write notebook directory")] + NotebookDirWrite, + #[error("could not create note file")] + NoteFileCreate, + #[error("could not read note file")] + NoteFileRead, + #[error("could not write note file")] + NoteFileWrite, + #[error("could not parse data dir from config file")] + ConfigDataDirParse, +} diff --git a/crates/noteriety/src/lib.rs b/crates/noteriety/src/lib.rs new file mode 100644 index 0000000..b0d7914 --- /dev/null +++ b/crates/noteriety/src/lib.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod notebook; +pub mod note; diff --git a/crates/noteriety/src/note.rs b/crates/noteriety/src/note.rs new file mode 100644 index 0000000..675ecce --- /dev/null +++ b/crates/noteriety/src/note.rs @@ -0,0 +1,147 @@ +use crate::error::Error; +use chrono::Utc; +use serde::{Deserialize, Serialize}; +use std::{ + fs::File, + io::{Read, Write}, + path::Path, +}; +use uuid::Uuid; + +/// note is a representation of an individually saved note. +/// for ordering comparison purposes, the `created_at` attribute is used. +/// for equality comparison purposes, the `id` attribute is used. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct Note { + /// a note id can be dynamic, or statically assigned. + /// in the context of noteriety, they are only used + /// to differentiate notes with identical titles. + pub id: uuid::Uuid, + pub title: String, + /// todo: replace body type with something more appropriate. + /// a rope-backed buffer, perhaps. + body: String, + created_at: chrono::DateTime, + /// if a note has never been updated since its creation, updated_at will be none. + updated_at: Option>, +} + +impl PartialEq for Note { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for Note {} + +impl PartialOrd for Note { + fn partial_cmp(&self, other: &Self) -> Option { + self.created_at.partial_cmp(&other.created_at) + } +} + +impl Note { + pub fn new(title: T) -> Self + where + T: AsRef, + { + Self { + id: Uuid::new_v4(), + title: title.as_ref().to_string(), + body: Default::default(), + created_at: chrono::Utc::now(), + updated_at: None, + } + } + + pub fn title(self, title: T) -> Self + where + T: AsRef, + { + Self { + id: self.id, + title: title.as_ref().to_string(), + body: self.body, + created_at: self.created_at, + updated_at: Some(Utc::now()) + } + } + + pub fn body(self, body: T) -> Self where T: AsRef { + Self { + id: self.id, + title: self.title, + body: body.as_ref().to_string(), + created_at: self.created_at, + updated_at: Some(Utc::now()), + } + } + + pub fn write(&self, write_dst: W) -> Result<(), Error> + where + W: Write, + { + ciborium::into_writer(self, write_dst).map_err(|_| Error::NoteFileWrite) + } + + pub fn write_path

(&self, path: P) -> Result<(), Error> + where + P: AsRef, + { + let file = File::open(path).map_err(|_| Error::NoteFileRead)?; + self.write(file) + } + + pub fn read(read_src: R) -> Result + where + R: Read, + { + ciborium::from_reader(read_src).map_err(|_| Error::NoteFileRead) + } + + pub fn read_path

(path: P) -> Result + where + P: AsRef, + { + let file = File::open(path).map_err(|_| Error::NoteFileRead)?; + Self::read(file) + } +} + +#[cfg(test)] +mod test { + use super::Note; + use chrono::DateTime; + + #[test] + fn same_titles_are_not_eq() { + let note1 = Note::new("a title"); + let note2 = Note::new("a title"); + let note3 = Note::new("another title"); + + assert!(note1.ne(¬e2)); + assert!(note1.ne(¬e3)); + } + + #[test] + fn are_date_ordered() { + let note1 = Note { + id: Default::default(), + title: Default::default(), + body: Default::default(), + created_at: DateTime::from_timestamp(2000000000, 0).unwrap(), + updated_at: Default::default(), + }; + + let note2 = Note { + id: Default::default(), + title: Default::default(), + body: Default::default(), + created_at: DateTime::from_timestamp(1000000000, 0).unwrap(), + updated_at: Default::default(), + }; + + assert!(note1.created_at.gt(¬e2.created_at)); + assert!(note1.gt(¬e2)); + } +} diff --git a/crates/noteriety/src/notebook.rs b/crates/noteriety/src/notebook.rs new file mode 100644 index 0000000..de2a740 --- /dev/null +++ b/crates/noteriety/src/notebook.rs @@ -0,0 +1,112 @@ +use std::{ + fs::{self, DirEntry}, + path::{Path, PathBuf}, + str::FromStr, +}; + +use crate::{error::Error, note::Note}; + +/// a notebook is a representation of a named collection of notes and child notebooks. +/// notebooks are ephemeral, and are represented on-disk by the directory tree. +#[derive(Default)] +pub struct Notebook { + pub root_path: PathBuf, + pub name: String, + pub child_notes: Vec, + pub child_notebooks: Vec, +} + +impl Notebook { + /// creates a new note with the provided title and root directory. + /// at this time, the presence of the directory tree is not verified. + pub fn new(root_path: PathBuf, name: String) -> Self { + Self { + root_path, + name, + child_notes: vec![], + child_notebooks: vec![], + } + } + + /// parses a notebook tree. + /// this function is recursive. + pub fn read

(root_path: P) -> Result + where + P: AsRef, + { + let direntries: Vec = fs::read_dir(&root_path) + .map_err(|_| Error::NotebookDirRead)? + .filter_map(Result::ok) + .collect(); + + let child_notebooks: Vec = direntries + .iter() + .filter(|de| de.metadata().is_ok_and(|de| de.is_file())) + .filter_map(|de| Result::ok(Notebook::read(de.path()))) + .collect(); + + let child_notes: Vec = direntries + .iter() + .filter(|de| de.metadata().is_ok_and(|de| de.is_file())) + .filter_map(|de| Result::ok(Note::read_path(de.path().to_string_lossy().into_owned()))) + .collect(); + + let name = root_path + .as_ref() + .file_stem() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or(String::from_str("unknown").unwrap()); + + Ok(Self { + root_path: root_path.as_ref().to_path_buf(), + child_notebooks, + child_notes, + name, + }) + } + + /// overwrites the contents of this notebook with the contents of the struct. + /// this function is recursive. + pub fn write(&self) -> Result<(), Error> { + fs::create_dir(&self.root_path).map_err(|_| Error::NotebookDirCreate)?; + + for notebook in &self.child_notebooks { + notebook.write()?; + } + + for note in &self.child_notes { + let path = PathBuf::new().join(¬e.title); + note.write_path(path)?; + } + + Ok(()) + } + + /// creates, but does not write, a notebook within this notebook. + pub fn create_notebook(mut self, notebook_name: N) -> Result<(), Error> + where + N: AsRef, + { + self.child_notebooks.push(Self { + root_path: self.root_path.join(notebook_name.as_ref()), + name: notebook_name.as_ref().to_string(), + child_notes: vec![], + child_notebooks: vec![], + }); + Ok(()) + } + + /// creates, but does not write, a new note within this notebook. + pub fn create_note(mut self, title: N, body: String) -> Result<(), Error> + where + N: AsRef, + { + self.child_notes.push(Note::new(title).body(body)); + Ok(()) + } + + /// writes a note to this notebook's root path. + pub fn write_note(&self, note: &Note) -> Result<(), Error> { + note.write_path(self.root_path.join(¬e.title)) + } +}