diff --git a/.tangled/workflows/check.yaml b/.tangled/workflows/check.yaml index d73c459..2d6c850 100644 --- a/.tangled/workflows/check.yaml +++ b/.tangled/workflows/check.yaml @@ -14,7 +14,7 @@ dependencies: steps: - name: clippy command: | - cargo clippy -- -D warnings + just check - name: build command: | just build diff --git a/crates/fil-core/src/due.rs b/crates/fil-core/src/due.rs index a20874b..04215f8 100644 --- a/crates/fil-core/src/due.rs +++ b/crates/fil-core/src/due.rs @@ -69,12 +69,13 @@ const DATE_FORMATS: &[&str] = &[ const YEARLESS_FORMATS: &[&str] = &["%m/%d", "%m-%d"]; -/// Due DateTime representation +/// Due `DateTime` representation #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Due(pub Option); impl Due { - /// Wheter there exist a date for this `Due` + /// Whether there exist a date for this `Due` + #[must_use] pub const fn has_date(&self) -> bool { self.0.is_some() } diff --git a/crates/fil-core/src/group.rs b/crates/fil-core/src/group.rs index d20d8b0..c712230 100644 --- a/crates/fil-core/src/group.rs +++ b/crates/fil-core/src/group.rs @@ -8,9 +8,6 @@ use crate::{Kasten, Priority, Zettel, frontmatter, zettel::Tag}; /// A `Group` which contains tasks! #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Group { - /// Should only be constructed from models. - _private: (), - /// The unique identifier for this group. pub id: NanoId, /// The group's human-readable name. @@ -33,7 +30,20 @@ pub struct Group { } impl Group { - /// Create a new `Group` + /// Creates a group with a dedicated tag and zettel. + /// + /// Persists all three records and adds the resulting group to `kt`'s + /// in-memory todo tree. + /// + /// # Errors + /// + /// Returns an error if a database operation, zettel creation, or index + /// update fails. + /// + /// # Panics + /// + /// Panics if the tag, zettel, or group cannot be reloaded immediately + /// after insertion, or if the in-memory todo tree rejects the group. pub async fn new( name: impl Into, parent_id: Option, @@ -88,6 +98,15 @@ impl Group { } /// Alter the name of this `Group` + /// + /// # Errors + /// + /// Returns an error if a database, tag, zettel, frontmatter, or index + /// update fails. + /// + /// # Panics + /// + /// Panics if `id` does not identify a fully loaded group. pub async fn alter_name( id: NanoId, new_name: impl Into, @@ -121,6 +140,14 @@ impl Group { } /// Alter the priority of this `Group` + /// + /// # Errors + /// + /// Returns an error if the database update fails. + /// + /// # Panics + /// + /// Panics if `id` does not identify an existing group. pub async fn alter_priority(id: NanoId, new_prio: Priority, kt: &Kasten) -> crate::Result<()> { GroupEntity::load() .filter_by_nano_id(id) @@ -136,17 +163,20 @@ impl Group { } /// Calculates the `p_score` of this `Group` + #[must_use] pub fn p_score(&self, parent_score: f64) -> f64 { self.priority.p_score() * parent_score } /// Returns when this group was created at, formatted as a string. + #[must_use] pub fn created_at(&self) -> String { self.created_at .format(frontmatter::DATE_FMT_STR) .to_string() } /// Returns when this group was modified at, formatted as a string. + #[must_use] pub fn modified_at(&self) -> String { self.modified_at .format(frontmatter::DATE_FMT_STR) @@ -157,7 +187,6 @@ impl Group { impl From for Group { fn from(value: GroupModelEx) -> Self { Self { - _private: (), id: value.nano_id, name: value.name, priority: value.priority.into(), diff --git a/crates/fil-core/src/kasten/index/mod.rs b/crates/fil-core/src/kasten/index/mod.rs index ee41df9..55df338 100644 --- a/crates/fil-core/src/kasten/index/mod.rs +++ b/crates/fil-core/src/kasten/index/mod.rs @@ -151,6 +151,11 @@ impl Index { } /// Updates the interal state of the `Index` for the provided `Zid`. + /// + /// # Errors + /// + /// Returns an error if the zettel file cannot be read or its frontmatter + /// cannot be parsed. pub fn process_zid(&mut self, zid: &ZettelId) -> crate::Result<()> { let zod = self.get_zod_mut(zid); @@ -164,6 +169,14 @@ impl Index { /// Sync's the curren title of the `Zettel` with the /// provided `zid` with the `DB` + /// + /// # Errors + /// + /// Returns an error if the database update fails. + /// + /// # Panics + /// + /// Panics if `zid` is missing from the index or database. pub async fn sync_zettel_title_with_db( &mut self, zid: &ZettelId, @@ -188,6 +201,15 @@ impl Index { /// Sync's `Tag`'s that are present in the frontmatter of this /// `Zettel` to the database. + /// + /// # Errors + /// + /// Returns an error if a database query or update fails. + /// + /// # Panics + /// + /// Panics if `zid` is missing from the index or database, or if an + /// expected zettel-tag relation is absent. pub async fn sync_tags_with_db( &mut self, zid: &ZettelId, @@ -252,6 +274,11 @@ impl Index { } /// Get the outgoing links for the provided `zid`. + /// + /// # Panics + /// + /// Panics if `zid` is not present in the outgoing-link index. + #[must_use] pub fn get_links(&self, zid: &ZettelId) -> &Vec { self.outgoing_links .get(zid) @@ -264,16 +291,26 @@ impl Index { } /// Get the `ZettelOnDisk` for the provided `zid`. + /// + /// # Panics + /// + /// Panics if `zid` is not present in the index. + #[must_use] pub fn get_zod(&self, zid: &ZettelId) -> &ZettelOnDisk { self.zods.get(zid).expect("Invariant broken. Any zid we lookup must exist in the index, otherwise the db is corrupt or not sync'd.") } /// Get the mutable `ZettelOnDisk` for the provided `zid`. + /// + /// # Panics + /// + /// Panics if `zid` is not present in the index. pub fn get_zod_mut(&mut self, zid: &ZettelId) -> &mut ZettelOnDisk { self.zods.get_mut(zid).expect("Invariant broken. Any zid we lookup must exist in the index, otherwise the db is corrupt or not sync'd.") } - /// All the `ZettelOnDisks` + /// Returns all indexed zettels. + #[must_use] pub const fn zods(&self) -> &HashMap { &self.zods } diff --git a/crates/fil-core/src/kasten/mod.rs b/crates/fil-core/src/kasten/mod.rs index acc4aac..e3bf184 100644 --- a/crates/fil-core/src/kasten/mod.rs +++ b/crates/fil-core/src/kasten/mod.rs @@ -24,10 +24,8 @@ pub use todo_tree::{TodoNode, TodoNodeKind, TodoTree}; /// /// Kasten means box in german. #[derive(Debug)] +#[non_exhaustive] pub struct Kasten { - /// Private field so it can only be instantiated from a `Path` - _private: (), - /// The root path to this `Kasten` pub root: PathBuf, @@ -49,6 +47,11 @@ impl Kasten { /// /// Note: this means that there should already exist a valid `Kasten` /// at that path. + /// + /// # Errors + /// + /// Returns an error if the workspace is invalid, or its database, index, + /// migrations, or todo tree cannot be initialized. pub async fn instansiate(root: impl Into) -> crate::Result { let root = root.into(); let db_conn_string = format!( @@ -72,7 +75,6 @@ impl Kasten { let todo_tree = TodoTree::construct(&conn).await?; Ok(Self { - _private: (), db: conn, root, index, @@ -81,6 +83,15 @@ impl Kasten { } /// Create a new `Kasten` at the provided `path`. + /// + /// # Errors + /// + /// Returns an error if the workspace directory or database file cannot be + /// created. + /// + /// # Panics + /// + /// Panics if the newly initialized workspace cannot be instantiated. pub async fn initialize(path: impl Into) -> crate::Result { let path = path.into(); @@ -102,6 +113,11 @@ impl Kasten { /// Process the `Zettel` for the provided `ZettelId`, /// meaning it updates the internal state of the `Kasten` /// with the changes in `Zettel`. + /// + /// # Errors + /// + /// Returns an error if the zettel cannot be read or parsed, or if the + /// index or database cannot be synchronized. pub async fn process_path(&mut self, path: impl AsRef) -> crate::Result<()> { let path = path.as_ref().canonicalize()?; let zid = ZettelId::try_from(path.as_path())?; diff --git a/crates/fil-core/src/kasten/todo_tree.rs b/crates/fil-core/src/kasten/todo_tree.rs index 6c1f8b5..befa156 100644 --- a/crates/fil-core/src/kasten/todo_tree.rs +++ b/crates/fil-core/src/kasten/todo_tree.rs @@ -32,6 +32,7 @@ pub struct TodoNode { impl TodoNode { /// Creates a todo node with its kind, nesting depth, and priority score. + #[must_use] pub const fn new(kind: TodoNodeKind, depth: usize, pscore: f64) -> Self { Self { depth, @@ -53,7 +54,12 @@ pub struct TodoTree { } impl TodoTree { - /// Construct a TodoTree from the database. + /// Constructs a `TodoTree` from the database. + /// + /// # Errors + /// + /// Returns an error if the database cannot be queried or the underlying + /// tree rejects an insertion. pub async fn construct(db: &DatabaseConnection) -> crate::Result { let mut tree = Tree::::new(); let root_id = tree.insert( @@ -306,6 +312,7 @@ impl TodoTree { /// Panics if `nano_id` is not indexed or its mapped node no longer exists /// in the tree. Both conditions indicate that the tree's lookup invariant /// has been broken. + #[must_use] pub fn get_node_by_nano_id(&self, nano_id: &NanoId) -> &Node { let node_id = self .nanoid_to_nodeid diff --git a/crates/fil-core/src/priority.rs b/crates/fil-core/src/priority.rs index c5f354e..a2ef423 100644 --- a/crates/fil-core/src/priority.rs +++ b/crates/fil-core/src/priority.rs @@ -8,7 +8,8 @@ use dto::PriorityDTO; pub struct Priority(PriorityDTO); impl Priority { - /// + /// Returns this priority's score on a scale from `0.25` to `1.0`. + #[must_use] pub const fn p_score(&self) -> f64 { match self.0 { PriorityDTO::Asap => 1.0, diff --git a/crates/fil-core/src/task.rs b/crates/fil-core/src/task.rs index 62e7bc0..20738a2 100644 --- a/crates/fil-core/src/task.rs +++ b/crates/fil-core/src/task.rs @@ -8,10 +8,8 @@ use crate::{Group, Kasten, Priority, Zettel, due::Due, frontmatter}; /// a `Task` that you have to complete! #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] pub struct Task { - /// Should only be constructed from models. - _private: (), - /// The task's unique identifier. pub id: NanoId, /// The task's human-readable name. @@ -44,6 +42,11 @@ impl Task { /// /// Returns [`crate::Error::GroupDoesNotExist`] when `parent_id` cannot be /// found, or propagates database and zettel creation errors. + /// + /// # Panics + /// + /// Panics if the parent's tag, the newly created zettel, group, or task + /// cannot be reloaded as required, or if the todo tree rejects the task. pub async fn new( name: impl Into, parent_id: NanoId, @@ -108,6 +111,11 @@ impl Task { /// Updates a task's name and the title of its related zettel. /// + /// # Errors + /// + /// Returns an error if a database, zettel, frontmatter, or index update + /// fails. + /// /// # Panics /// /// Panics if `id` does not identify an existing task. @@ -139,6 +147,10 @@ impl Task { /// Updates a task's priority in the database. /// + /// # Errors + /// + /// Returns an error if the database update fails. + /// /// # Panics /// /// Panics if `id` does not identify an existing task. @@ -158,6 +170,10 @@ impl Task { /// Updates a task's optional due date in the database. /// + /// # Errors + /// + /// Returns an error if the database update fails. + /// /// # Panics /// /// Panics if `id` does not identify an existing task. @@ -183,6 +199,10 @@ impl Task { /// Marks an incomplete task as complete using the current local time, or /// clears its completion time when it is already complete. /// + /// # Errors + /// + /// Returns an error if the database update fails. + /// /// # Panics /// /// Panics if `id` does not identify an existing task. @@ -213,6 +233,7 @@ impl Task { /// Calcualtes the `p_score` of this `Task` //NOTE: formula from claude #[expect(clippy::cast_precision_loss)] + #[must_use] pub fn p_score(&self, parent_score: f64) -> f64 { let priority_score = self.priority.p_score(); // [0.0, 1.0] @@ -229,12 +250,14 @@ impl Task { } /// Returns when this task was finished, as a string. + #[must_use] pub fn finished_at(&self) -> Option { self.finished_at .map(|finished_at| finished_at.format(frontmatter::DATE_FMT_STR).to_string()) } /// Returns when this task was created, as a string. + #[must_use] pub fn created_at(&self) -> String { self.created_at .format(frontmatter::DATE_FMT_STR) @@ -242,6 +265,7 @@ impl Task { } /// Returns when this task was last modified, as a string. + #[must_use] pub fn modified_at(&self) -> String { self.modified_at .format(frontmatter::DATE_FMT_STR) @@ -252,7 +276,6 @@ impl Task { impl From for Task { fn from(value: TaskModelEx) -> Self { Self { - _private: (), id: value.nano_id, name: value.name, priority: value.priority.into(), diff --git a/crates/fil-core/src/zettel/frontmatter.rs b/crates/fil-core/src/zettel/frontmatter.rs index c90fa8d..d58fdca 100644 --- a/crates/fil-core/src/zettel/frontmatter.rs +++ b/crates/fil-core/src/zettel/frontmatter.rs @@ -41,9 +41,7 @@ impl FrontMatter { } } - //TODO: this needs to be moved to the visualizer... - /// Apply the features of `FrontMatter` onto a - /// `Node` + // TODO: this needs to be moved to the visualizer. // pub fn apply_node_transform(&self, node: &mut Node) { // node.set_label(self.title.clone()); // let disp = node.display_mut(); @@ -59,6 +57,11 @@ impl FrontMatter { /// Tags: @Daily @barber /// --- /// ``` + /// + /// # Errors + /// + /// Returns an error if the file cannot be read or its frontmatter is + /// malformed. pub fn extract_from_file(path: impl AsRef) -> crate::Result<(Self, Body)> { let path = path.as_ref(); let string = std::fs::read_to_string(path)?; @@ -131,6 +134,11 @@ impl FrontMatter { } /// Flushes the `FrontMatter` to disk. + /// + /// # Errors + /// + /// Returns an error if the file cannot be read or rewritten, or if it + /// does not contain a closing frontmatter delimiter. pub fn flush_to_file(&self, path: impl AsRef) -> crate::Result<()> { let path = path.as_ref(); let string = std::fs::read_to_string(path)?; diff --git a/crates/fil-core/src/zettel/mod.rs b/crates/fil-core/src/zettel/mod.rs index b7a8b6a..9274386 100644 --- a/crates/fil-core/src/zettel/mod.rs +++ b/crates/fil-core/src/zettel/mod.rs @@ -26,9 +26,8 @@ pub use tag::*; /// categories. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] pub struct Zettel { - /// Should only be constructed from models. - _private: (), /// The zettel's unique identifier. pub id: ZettelId, /// The zettel's title. @@ -43,6 +42,10 @@ pub struct Zettel { impl Zettel { /// Fetches the `Zettel` with the provided `ZettelId`, returning `None` if not found. + /// + /// # Errors + /// + /// Returns an error if the database query fails. pub async fn fetch_from_db( zid: &ZettelId, db: &DatabaseConnection, @@ -60,6 +63,11 @@ impl Zettel { /// The zettel's frontmatter is rewritten with the updated title, then the /// corresponding entry in `kt.index` is refreshed. /// + /// # Errors + /// + /// Returns an error if the database, frontmatter file, or index cannot be + /// updated. + /// /// # Panics /// /// Panics if `id` does not identify an existing zettel after the database @@ -99,6 +107,11 @@ impl Zettel { /// /// Refreshes the corresponding entry in `kt.index` after writing the file. /// + /// # Errors + /// + /// Returns an error if the database query, frontmatter write, or index + /// update fails. + /// /// # Panics /// /// Panics if `id` does not identify an existing zettel. @@ -181,21 +194,25 @@ impl Zettel { /// Returns the most up-to-date `FrontMatter` for this /// `Zettel` + #[must_use] pub fn front_matter<'index>(&self, idx: &'index Index) -> &'index FrontMatter { &idx.get_zod(&self.id).fm } /// Returns the content of this `Zettel`, which is everything /// but the `FrontMatter` + #[must_use] pub fn content<'index>(&self, idx: &'index Index) -> &'index Body { &idx.get_zod(&self.id).body } /// Get the absolute path to this `Zettel` + #[must_use] pub fn absolute_path<'index>(&self, idx: &'index Index) -> &'index Path { &idx.get_zod(&self.id).path } /// Get the formatted creation datetime for this `Zettel` + #[must_use] pub fn created_at(&self) -> String { self.created_at .format(frontmatter::DATE_FMT_STR) @@ -203,6 +220,7 @@ impl Zettel { } /// Get the formatted modified datetime for this `Zettel` + #[must_use] pub fn modified_at(&self) -> String { self.modified_at .format(frontmatter::DATE_FMT_STR) @@ -219,7 +237,6 @@ impl From for Zettel { ); Self { - _private: (), id: value.nano_id.into(), title: value.title, created_at: value.created_at, diff --git a/crates/fil-core/src/zettel/tag.rs b/crates/fil-core/src/zettel/tag.rs index ebd4eaa..d687287 100644 --- a/crates/fil-core/src/zettel/tag.rs +++ b/crates/fil-core/src/zettel/tag.rs @@ -5,10 +5,8 @@ use crate::{Color, Kasten, Zettel}; /// Represents a `Tag` in a `ZettelKasten` note taking method. /// Easy way to link multiple notes under one simple word. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] pub struct Tag { - /// Should only be constructed from models. - _private: (), - /// A unique `NanoId` pub id: NanoId, /// Name of the tag @@ -71,6 +69,10 @@ impl Tag { /// Randomizes the color of this tag. /// + /// # Errors + /// + /// Returns an error if the database update fails. + /// /// # Panics /// /// Will panic if the tag doesn't exist which should be impossible. @@ -94,7 +96,6 @@ impl Tag { impl From for Tag { fn from(value: TagModel) -> Self { Self { - _private: (), id: value.nano_id, name: value.name, color: value.color.into(), @@ -105,7 +106,6 @@ impl From for Tag { impl From for Tag { fn from(value: TagModelEx) -> Self { Self { - _private: (), id: value.nano_id, name: value.name, color: value.color.into(), diff --git a/justfile b/justfile index e73a1ae..34f4b1e 100644 --- a/justfile +++ b/justfile @@ -19,6 +19,10 @@ run: # Run all tests test: cargo nextest r {{ cargo_flags }} + +check: + cargo clippy -- -D warnings + reset: rm -rf ./ZettelKasten rm -rf ./.data diff --git a/src/tui/components/zk/search.rs b/src/tui/components/zk/search.rs index 5008863..177bca0 100644 --- a/src/tui/components/zk/search.rs +++ b/src/tui/components/zk/search.rs @@ -89,8 +89,7 @@ impl Search<'_> { let documents = futures::future::join_all(read_tasks) .await .into_iter() - .filter_map(|result| result.ok()?.ok()) - .collect::>(); + .filter_map(|result| result.ok()?.ok()); let pattern = Pattern::parse(self.query(), CaseMatching::Ignore, Normalization::Smart);