diff --git a/src/main.rs b/src/main.rs index 756aee2..eaf159c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use crate::event_sender::MostrMessage; use crate::hashtag::Hashtag; use crate::helpers::*; use crate::kinds::{format_tag_basic, match_event_tag, Prio, BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS}; -use crate::task::{State, Task, TaskState, MARKER_PROPERTY}; +use crate::task::{State, StateChange, Task, MARKER_PROPERTY}; use crate::tasks::{referenced_event, PropertyCollection, StateFilter, TasksRelay}; use chrono::Local; use colored::Colorize; @@ -387,7 +387,7 @@ async fn main() -> Result<()> { None => { if let Some(task) = tasks.get_current_task() { println!("Change History for {}:", task.get_id()); - for e in once(&task.event).chain(task.props.iter().rev()) { + for e in task.all_events() { println!("{} {} [{}]", format_timestamp_full(&e.created_at), match State::try_from(e.kind) { @@ -567,7 +567,7 @@ async fn main() -> Result<()> { match tasks.get_position() { None => { warn!("First select a task to set its state!"); - info!("Usage: ![(Open|Procedure|Pending|Done|Closed): ][Statename]"); + info!("Usage: ![(Open|Procedure|Pending|Done|Closed): ][Statename] OR Time: Reason"); } Some(id) => { 'block: { @@ -584,8 +584,8 @@ async fn main() -> Result<()> { tasks.set_state_for(id, right, State::Pending); tasks.custom_time = Some(stamp); tasks.set_state_for(id, - &state.as_ref().map(TaskState::get_label).unwrap_or_default(), - state.map(|ts| ts.state).unwrap_or(State::Open)); + &state.as_ref().map(StateChange::get_label).unwrap_or_default(), + State::from(state)); break 'block; } } @@ -728,7 +728,7 @@ async fn main() -> Result<()> { let filtered = tasks.get_filtered(pos, |t| { - transform(&t.event.content).contains(&remaining) || + transform(&t.get_title()).contains(&remaining) || t.list_hashtags().any( |tag| tag.contains(&remaining)) }); diff --git a/src/task.rs b/src/task.rs index bc02280..a751ba4 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,9 +1,14 @@ +mod state; +#[cfg(test)] +mod tests; + use fmt::Display; use std::cmp::Ordering; +use std::collections::btree_set::Iter; use std::collections::BTreeSet; use std::fmt; use std::hash::{Hash, Hasher}; -use std::iter::once; +use std::iter::{once, Chain, Once}; use std::str::FromStr; use std::string::ToString; @@ -12,6 +17,9 @@ use crate::helpers::{format_timestamp_local, some_non_empty}; use crate::kinds::{match_event_tag, Prio, PRIO, PROCEDURE_KIND, PROCEDURE_KIND_ID, TASK_KIND}; use crate::tasks::now; +pub use crate::task::state::State; +pub use crate::task::state::StateChange; + use colored::{ColoredString, Colorize}; use itertools::Either::{Left, Right}; use itertools::Itertools; @@ -25,7 +33,7 @@ pub static MARKER_PROPERTY: &str = "property"; #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct Task { /// Event that defines this task - pub(crate) event: Event, + pub(super) event: Event, // TODO make private /// Cached sorted tags of the event with references removed tags: Option>, /// Task references derived from the event tags @@ -68,8 +76,13 @@ impl Task { } } - pub(crate) fn get_id(&self) -> &EventId { - &self.event.id + /// All Events including the task and its props in chronological order + pub(crate) fn all_events(&self) -> impl DoubleEndedIterator { + once(&self.event).chain(self.props.iter().rev()) + } + + pub(crate) fn get_id(&self) -> EventId { + self.event.id } pub(crate) fn get_participants(&self) -> impl Iterator + '_ { @@ -84,28 +97,30 @@ impl Task { .unwrap_or_else(|| self.event.pubkey) } - pub(crate) fn find_refs<'a>(&'a self, marker: &'a str) -> impl Iterator { - self.refs.iter().filter_map(move |(str, id)| Some(id).filter(|_| str == marker)) - } - - pub(crate) fn parent_id(&self) -> Option<&EventId> { - self.find_refs(MARKER_PARENT).next() - } - - pub(crate) fn get_dependendees(&self) -> Vec<&EventId> { - self.find_refs(MARKER_DEPENDS).collect() - } - /// Trimmed event content or stringified id pub(crate) fn get_title(&self) -> String { some_non_empty(self.event.content.trim()) .unwrap_or_else(|| self.get_id().to_string()) } + /// Title with leading hashtags removed pub(crate) fn get_filter_title(&self) -> String { self.event.content.trim().trim_start_matches('#').to_string() } + pub(crate) fn find_refs<'a>(&'a self, marker: &'a str) -> impl Iterator { + self.refs.iter().filter_map(move |(str, id)| + Some(id).filter(|_| str == marker)) + } + + pub(crate) fn parent_id(&self) -> Option<&EventId> { + self.find_refs(MARKER_PARENT).next() + } + + pub(crate) fn find_dependents(&self) -> Vec<&EventId> { + self.find_refs(MARKER_DEPENDS).collect() + } + fn description_events(&self) -> impl DoubleEndedIterator + '_ { self.props.iter().filter(|event| event.kind == Kind::TextNote) } @@ -139,9 +154,9 @@ impl Task { }) } - fn states(&self) -> impl DoubleEndedIterator + '_ { + fn states(&self) -> impl DoubleEndedIterator + '_ { self.props.iter().filter_map(|event| { - event.kind.try_into().ok().map(|s| TaskState { + event.kind.try_into().ok().map(|s| StateChange { name: some_non_empty(&event.content), state: s, time: event.created_at, @@ -153,7 +168,7 @@ impl Task { self.state().map(|s| s.time).unwrap_or(self.event.created_at) } - pub fn state_at(&self, time: Timestamp) -> Option { + pub fn state_at(&self, time: Timestamp) -> Option { // TODO do not iterate constructed state objects let state = self.states().take_while_inclusive(|ts| ts.time > time); state.last().map(|ts| { @@ -166,16 +181,16 @@ impl Task { } /// Returns the current state if this is a task rather than an activity - pub fn state(&self) -> Option { + pub fn state(&self) -> Option { let now = now(); self.state_at(now) } pub(crate) fn pure_state(&self) -> State { - self.state().map_or(State::Open, |s| s.state) + State::from(self.state()) } - pub(crate) fn state_or_default(&self) -> TaskState { + pub(crate) fn state_or_default(&self) -> StateChange { self.state().unwrap_or_else(|| self.default_state()) } @@ -186,8 +201,8 @@ impl Task { .map(|state| state.get_colored_label()) } - fn default_state(&self) -> TaskState { - TaskState { + fn default_state(&self) -> StateChange { + StateChange { name: None, state: State::Open, time: self.event.created_at, @@ -221,7 +236,7 @@ impl Task { pub(crate) fn get(&self, property: &str) -> Option { match property { // Static - "id" => Some(self.event.id.to_string()), + "id" => Some(self.get_id().to_string()), "parentid" => self.parent_id().map(|i| i.to_string()), "name" => Some(self.event.content.clone()), "key" | "pubkey" => Some(self.event.pubkey.to_string()), @@ -251,159 +266,3 @@ impl Task { } } } - -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub(crate) struct TaskState { - pub(crate) state: State, - name: Option, - pub(crate) time: Timestamp, -} -impl TaskState { - pub(crate) fn get_label_for(state: &State, comment: &str) -> String { - some_non_empty(comment).unwrap_or_else(|| state.to_string()) - } - pub(crate) fn get_label(&self) -> String { - self.name.clone().unwrap_or_else(|| self.state.to_string()) - } - pub(crate) fn get_colored_label(&self) -> ColoredString { - self.state.colorize(&self.get_label()) - } - pub(crate) fn matches_label(&self, label: &str) -> bool { - self.name.as_ref().is_some_and(|n| n.eq_ignore_ascii_case(label)) - || self.state.to_string().eq_ignore_ascii_case(label) - } -} -impl Display for TaskState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let state_str = self.state.to_string(); - write!( - f, - "{}", - self.name - .as_ref() - .map(|s| s.trim()) - .filter(|s| !s.eq_ignore_ascii_case(&state_str)) - .map_or(state_str, |s| format!("{}: {}", self.state, s)) - ) - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub(crate) enum State { - /// Actionable - Open = 1630, - /// Completed - Done, - /// Not Actionable (anymore) - Closed, - /// Temporarily not actionable - Pending, - /// Actionable ordered task list - Procedure = PROCEDURE_KIND_ID as isize, -} -impl TryFrom<&str> for State { - type Error = (); - - fn try_from(value: &str) -> Result { - match value.to_ascii_lowercase().as_str() { - "closed" => Ok(State::Closed), - "done" => Ok(State::Done), - "pending" => Ok(State::Pending), - "proc" | "procedure" | "list" => Ok(State::Procedure), - "open" => Ok(State::Open), - _ => Err(()), - } - } -} -impl TryFrom for State { - type Error = (); - - fn try_from(value: Kind) -> Result { - match value { - Kind::GitStatusOpen => Ok(State::Open), - Kind::GitStatusApplied => Ok(State::Done), - Kind::GitStatusClosed => Ok(State::Closed), - Kind::GitStatusDraft => Ok(State::Pending), - _ => { - if value == PROCEDURE_KIND { - Ok(State::Procedure) - } else { - Err(()) - } - } - } - } -} -impl State { - pub(crate) fn is_open(&self) -> bool { - matches!(self, State::Open | State::Pending | State::Procedure) - } - - pub(crate) fn kind(self) -> u16 { - self as u16 - } - - pub(crate) fn colorize(&self, str: &str) -> ColoredString { - match self { - State::Open => str.green(), - State::Done => str.bright_black(), - State::Closed => str.magenta(), - State::Pending => str.yellow(), - State::Procedure => str.blue(), - } - } -} -impl From for Kind { - fn from(value: State) -> Self { - Kind::from(value.kind()) - } -} -impl Display for State { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -#[cfg(test)] -mod tasks_test { - use super::*; - use nostr_sdk::{EventBuilder, Keys}; - - #[test] - fn test_state() { - let keys = Keys::generate(); - let mut task = Task::new( - EventBuilder::new(TASK_KIND, "task").tags([Tag::hashtag("tag1")]) - .sign_with_keys(&keys).unwrap()); - assert_eq!(task.pure_state(), State::Open); - assert_eq!(task.list_hashtags().count(), 1); - - let now = Timestamp::now(); - task.props.insert( - EventBuilder::new(State::Done.into(), "") - .custom_created_at(now) - .sign_with_keys(&keys).unwrap()); - assert_eq!(task.pure_state(), State::Done); - task.props.insert( - EventBuilder::new(State::Open.into(), "Ready").tags([Tag::hashtag("tag2")]) - .custom_created_at(now - 2) - .sign_with_keys(&keys).unwrap()); - assert_eq!(task.pure_state(), State::Done); - assert_eq!(task.list_hashtags().count(), 2); - task.props.insert( - EventBuilder::new(State::Closed.into(), "") - .custom_created_at(now + 9) - .sign_with_keys(&keys).unwrap()); - assert_eq!(task.pure_state(), State::Closed); - assert_eq!(task.state_at(now), Some(TaskState { - state: State::Done, - name: None, - time: now, - })); - assert_eq!(task.state_at(now - 1), Some(TaskState { - state: State::Open, - name: Some("Ready".to_string()), - time: now - 2, - })); - } -} diff --git a/src/task/state.rs b/src/task/state.rs new file mode 100644 index 0000000..bf030c2 --- /dev/null +++ b/src/task/state.rs @@ -0,0 +1,128 @@ +use crate::helpers::some_non_empty; +use crate::kinds::{PROCEDURE_KIND, PROCEDURE_KIND_ID}; + +use colored::{ColoredString, Colorize}; +use nostr_sdk::{Kind, Timestamp}; +use std::fmt; +use std::fmt::Display; + +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct StateChange { + pub(super) state: State, + pub(super) name: Option, + pub(super) time: Timestamp, +} +impl StateChange { + pub fn get_label_for(state: &State, comment: &str) -> String { + some_non_empty(comment).unwrap_or_else(|| state.to_string()) + } + pub fn get_label(&self) -> String { + self.name.clone().unwrap_or_else(|| self.state.to_string()) + } + pub fn get_colored_label(&self) -> ColoredString { + self.state.colorize(&self.get_label()) + } + pub fn matches_label(&self, label: &str) -> bool { + self.name.as_ref().is_some_and(|n| n.eq_ignore_ascii_case(label)) + || self.state.to_string().eq_ignore_ascii_case(label) + } + pub fn get_timestamp(&self) -> Timestamp { + self.time + } +} +impl Display for StateChange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let state_str = self.state.to_string(); + write!( + f, + "{}", + self.name + .as_ref() + .map(|s| s.trim()) + .filter(|s| !s.eq_ignore_ascii_case(&state_str)) + .map_or(state_str, |s| format!("{}: {}", self.state, s)) + ) + } +} +impl From> for State { + fn from(value: Option) -> Self { + value.map_or(State::Open, |s| s.state) + } +} + + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum State { + /// Actionable + Open = 1630, + /// Completed + Done, + /// Not Actionable (anymore) + Closed, + /// Temporarily not actionable + Pending, + /// Ordered task list + Procedure = PROCEDURE_KIND_ID as isize, +} +impl TryFrom<&str> for State { + type Error = (); + + fn try_from(value: &str) -> Result { + match value.to_ascii_lowercase().as_str() { + "closed" => Ok(State::Closed), + "done" => Ok(State::Done), + "pending" => Ok(State::Pending), + "proc" | "procedure" | "list" => Ok(State::Procedure), + "open" => Ok(State::Open), + _ => Err(()), + } + } +} +impl TryFrom for State { + type Error = (); + + fn try_from(value: Kind) -> Result { + match value { + Kind::GitStatusOpen => Ok(State::Open), + Kind::GitStatusApplied => Ok(State::Done), + Kind::GitStatusClosed => Ok(State::Closed), + Kind::GitStatusDraft => Ok(State::Pending), + _ => { + if value == PROCEDURE_KIND { + Ok(State::Procedure) + } else { + Err(()) + } + } + } + } +} +impl State { + pub(crate) fn is_open(&self) -> bool { + matches!(self, State::Open | State::Pending | State::Procedure) + } + + pub(crate) fn kind(self) -> u16 { + self as u16 + } + + pub(crate) fn colorize(&self, str: &str) -> ColoredString { + match self { + State::Open => str.green(), + State::Done => str.bright_black(), + State::Closed => str.magenta(), + State::Pending => str.yellow(), + State::Procedure => str.blue(), + } + } +} +impl From for Kind { + fn from(value: State) -> Self { + Kind::from(value.kind()) + } +} +impl Display for State { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} \ No newline at end of file diff --git a/src/task/tests.rs b/src/task/tests.rs new file mode 100644 index 0000000..9798868 --- /dev/null +++ b/src/task/tests.rs @@ -0,0 +1,40 @@ +use super::*; +use nostr_sdk::{EventBuilder, Keys, Tag, Timestamp}; + +#[test] +fn test_state() { + let keys = Keys::generate(); + let mut task = Task::new( + EventBuilder::new(Kind::GitIssue, "task").tags([Tag::hashtag("tag1")]) + .sign_with_keys(&keys).unwrap()); + assert_eq!(task.pure_state(), State::Open); + assert_eq!(task.list_hashtags().count(), 1); + + let now = Timestamp::now(); + task.props.insert( + EventBuilder::new(State::Done.into(), "") + .custom_created_at(now) + .sign_with_keys(&keys).unwrap()); + assert_eq!(task.pure_state(), State::Done); + task.props.insert( + EventBuilder::new(State::Open.into(), "Ready").tags([Tag::hashtag("tag2")]) + .custom_created_at(now - 2) + .sign_with_keys(&keys).unwrap()); + assert_eq!(task.pure_state(), State::Done); + assert_eq!(task.list_hashtags().count(), 2); + task.props.insert( + EventBuilder::new(State::Closed.into(), "") + .custom_created_at(now + 9) + .sign_with_keys(&keys).unwrap()); + assert_eq!(task.pure_state(), State::Closed); + assert_eq!(task.state_at(now), Some(StateChange { + state: State::Done, + name: None, + time: now, + })); + assert_eq!(task.state_at(now - 1), Some(StateChange { + state: State::Open, + name: Some("Ready".to_string()), + time: now - 2, + })); +} diff --git a/src/tasks.rs b/src/tasks.rs index d8d9904..4fb1cb6 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -14,7 +14,7 @@ use crate::helpers::{ parse_tracking_stamp, some_non_empty, to_string_or_default, CHARACTER_THRESHOLD, }; use crate::kinds::*; -use crate::task::{State, Task, TaskState, MARKER_DEPENDS, MARKER_PARENT, MARKER_PROPERTY}; +use crate::task::{State, StateChange, Task, MARKER_DEPENDS, MARKER_PARENT, MARKER_PROPERTY}; use crate::tasks::nostr_users::NostrUsers; use colored::Colorize; use itertools::Itertools; @@ -40,18 +40,18 @@ type TaskMap = HashMap; trait TaskMapMethods { fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator + 'a; fn children_for<'a>(&'a self, id: Option) -> impl Iterator + 'a; - fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator + 'a; + fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator + 'a; } impl TaskMapMethods for TaskMap { fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator + 'a { - self.children_for(Some(task.event.id)) + self.children_for(Some(task.get_id().clone())) } fn children_for<'a>(&'a self, id: Option) -> impl Iterator + 'a { self.values().filter(move |t| t.parent_id() == id.as_ref()) } - fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator + 'a { + fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator + 'a { self.children_for(Some(id)).map(|t| t.get_id()) } } @@ -376,15 +376,15 @@ impl TasksRelay { total } - fn total_progress(&self, id: &EventId) -> Option { - self.get_by_id(id).and_then(|task| match task.pure_state() { + fn total_progress(&self, id: EventId) -> Option { + self.get_by_id(&id).and_then(|task| match task.pure_state() { State::Closed => None, State::Done => Some(1.0), _ => { let mut sum = 0f32; let mut count = 0; for prog in self.tasks - .children_ids_for(task.event.id) + .children_ids_for(task.get_id()) .filter_map(|e| self.total_progress(e)) { sum += prog; @@ -450,7 +450,7 @@ impl TasksRelay { pub(crate) fn get_relative_path(&self, id: EventId) -> String { join_tasks( self.traverse_up_from(Some(id)) - .take_while(|t| Some(t.event.id) != self.get_position()), + .take_while(|t| Some(t.get_id()) != self.get_position()), false, ).unwrap_or(id.to_string()) } @@ -598,9 +598,9 @@ impl TasksRelay { } } - fn quick_access_raw(&self) -> impl Iterator { + fn quick_access_raw(&self) -> impl Iterator + '_ { // TODO add recent tasks (most time tracked + recently created) - self.bookmarks.iter() + self.bookmarks.iter().cloned() .chain( // Latest self.tasks.values() @@ -618,13 +618,13 @@ impl TasksRelay { fn bookmarked_tasks_deduped(&self, visible: &[&Task]) -> impl Iterator { let tree = visible.iter() - .flat_map(|task| self.traverse_up_from(Some(task.event.id))) + .flat_map(|task| self.traverse_up_from(Some(task.get_id()))) .unique(); let pos = self.get_position(); - let ids: HashSet<&EventId> = tree.map(|t| t.get_id()).chain(pos.as_ref()).collect(); + let ids: HashSet = tree.map(|t| t.get_id()).chain(pos).collect(); self.quick_access_raw() .filter(|id| !ids.contains(id)) - .filter_map(|id| self.get_by_id(id)) + .filter_map(|id| self.get_by_id(&id)) .filter(|t| self.filter(t)) .sorted_by_cached_key(|t| self.sorting_key(t)) .dedup() @@ -655,7 +655,7 @@ impl TasksRelay { } "state" => { if let Some(task) = task - .get_dependendees() + .find_dependents() .iter() .filter_map(|id| self.get_by_id(id)) .find(|t| t.pure_state().is_open()) @@ -676,7 +676,7 @@ impl TasksRelay { "owner" => format!("{:.6}", self.users.get_username(&task.get_owner())), "author" | "creator" => format!("{:.6}", self.users.get_username(&task.event.pubkey)), // FIXME temporary until proper column alignment "prio" => self - .traverse_up_from(Some(task.event.id)) + .traverse_up_from(Some(task.get_id())) .find_map(Task::priority_raw) .map(|p| p.to_string()) .unwrap_or_else(|| { @@ -686,11 +686,11 @@ impl TasksRelay { "".to_string() } }), - "path" => self.get_task_path(Some(task.event.id)), - "rpath" => self.get_relative_path(task.event.id), + "path" => self.get_task_path(Some(task.get_id())), + "rpath" => self.get_relative_path(task.get_id()), // TODO format strings configurable - "time" => display_time("MMMm", self.time_tracked(*task.get_id())), - "rtime" => display_time("HH:MM", self.total_time_tracked(*task.get_id())), + "time" => display_time("MMMm", self.time_tracked(task.get_id())), + "rtime" => display_time("HH:MM", self.total_time_tracked(task.get_id())), prop => task.get(prop).unwrap_or_default(), } } @@ -749,7 +749,7 @@ impl TasksRelay { self.filtered_tasks(position, false) .into_iter() .filter(predicate) - .map(|t| t.event.id) + .map(|t| t.get_id()) .collect() } @@ -881,22 +881,22 @@ impl TasksRelay { let content = task.get_filter_title(); let lowercase = content.to_ascii_lowercase(); if lowercase == lowercase_arg { - return vec![task.event.id]; + return vec![task.get_id()]; } else if content.starts_with(arg) { - filtered.push(task.event.id) + filtered.push(task.get_id()) } else if regex.as_ref() .map(|r| r.is_match(lowercase.as_bytes())) .unwrap_or_else(|_| lowercase.starts_with(&lowercase_arg)) { - filtered_fuzzy.push(task.event.id) + filtered_fuzzy.push(task.get_id()) } } // Find global exact match for task in self.tasks.values() { if task.get_filter_title().to_ascii_lowercase() == lowercase_arg && // exclude closed tasks and their subtasks - !self.traverse_up_from(Some(*task.get_id())).any(|t| !t.pure_state().is_open()) + !self.traverse_up_from(Some(task.get_id())).any(|t| !t.pure_state().is_open()) { - return vec![task.event.id]; + return vec![task.get_id()]; } } @@ -1286,7 +1286,7 @@ impl TasksRelay { .find(|e| { referenced_event(e) .and_then(|id| self.get_by_id(&id)) - .is_some_and(|t| t.event.content.to_ascii_lowercase().contains(&lower)) + .is_some_and(|t| t.get_title().to_ascii_lowercase().contains(&lower)) }); if let Some(event) = found { self.move_to(referenced_event(event)); @@ -1343,7 +1343,7 @@ impl TasksRelay { .tags(tags); info!( "Task status {} set for \"{}\"{}{}", - TaskState::get_label_for(&state, comment), + StateChange::get_label_for(&state, comment), self.get_task_title(&id), self.custom_time .map(|ts| format!(" at {}", format_timestamp_relative(&ts))) @@ -1424,7 +1424,7 @@ impl Display for TasksRelay { let state = t.state_or_default(); let now = &now(); let mut tracking_stamp: Option = None; - for elem in timestamps(self.get_own_events_history(), &[t.event.id]).map(|(e, _)| e) { + for elem in timestamps(self.get_own_events_history(), &[t.get_id()]).map(|(e, _)| e) { if tracking_stamp.is_some() && elem > now { break; } @@ -1434,9 +1434,9 @@ impl Display for TasksRelay { lock, "Active from {} (total tracked time {}m) - {} since {}", tracking_stamp.map_or("?".to_string(), |t| format_timestamp_relative(&t)), - self.time_tracked(*t.get_id()) / 60, + self.time_tracked(t.get_id()) / 60, state, - format_timestamp_relative(&state.time) + format_timestamp_relative(&state.get_timestamp()) )?; for d in t.descriptions().rev() { writeln!(lock, "{}", d)?; } writeln!(lock)?; @@ -1483,7 +1483,7 @@ impl Display for TasksRelay { .map(|p| self.get_property(task, p.as_str())) .join(" \t") )?; - total_time += self.total_time_tracked(task.event.id) // TODO include parent if it matches + total_time += self.total_time_tracked(task.get_id()) // TODO include parent if it matches } writeln!(lock, @@ -1494,7 +1494,7 @@ impl Display for TasksRelay { } } -pub trait PropertyCollection { +pub(super) trait PropertyCollection { fn remove_at(&mut self, index: usize); fn add_or_remove(&mut self, value: T); fn add_or_remove_at(&mut self, value: T, index: usize); @@ -1675,7 +1675,7 @@ impl<'a> ChildIterator<'a> { &mut tasks .values() .filter(move |t| t.parent_id() == id) - .map(|t| t.event.id) + .map(|t| t.get_id()) .collect_vec() ); Self::with_queue(tasks, queue) @@ -1765,7 +1765,7 @@ impl<'a> ChildIterator<'a> { } fn queue_children_of(&mut self, task: &'a Task) { - self.queue.extend(self.tasks.children_ids_for(task.event.id)); + self.queue.extend(self.tasks.children_ids_for(task.get_id())); } } impl FusedIterator for ChildIterator<'_> {} @@ -1779,7 +1779,7 @@ impl<'a> Iterator for ChildIterator<'a> { // Unknown task, might still find children, just slower for task in self.tasks.values() { if task.parent_id().is_some_and(|i| i == id) { - self.queue.push(task.event.id); + self.queue.push(task.get_id()); } } } @@ -1835,7 +1835,7 @@ mod tasks_test { ($tasks:expr, $expected:expr $(,)?) => { assert_tasks!($tasks, $tasks.visible_tasks(), $expected, "\nQuick Access: {:?}", - $tasks.quick_access_raw().map(|id| $tasks.get_relative_path(*id)).collect_vec()); + $tasks.quick_access_raw().map(|id| $tasks.get_relative_path(id)).collect_vec()); }; } @@ -1850,11 +1850,11 @@ mod tasks_test { assert_eq!( $tasklist .iter() - .map(|t| t.event.id) + .map(|t| t.get_id()) .collect::>(), HashSet::from_iter($expected.clone()), "Tasks Visible: {:?}\nExpected: {:?}{}", - $tasklist.iter().map(|t| t.event.id).map(|id| $tasks.get_relative_path(id)).collect_vec(), + $tasklist.iter().map(|t| t.get_id()).map(|id| $tasks.get_relative_path(id)).collect_vec(), $expected.into_iter().map(|id| $tasks.get_relative_path(id)).collect_vec(), format!($($($arg)*)?) ); @@ -1923,7 +1923,7 @@ mod tasks_test { let task2 = tasks.get_current_task().unwrap(); assert_eq!(task2.descriptions().next(), None); assert_eq!(task2.priority(), Some(30)); - let anid = task2.event.id; + let anid = task2.get_id(); tasks.custom_time = Some(Timestamp::now() + 1); let s1 = tasks.make_task_unwrapped("sub1"); @@ -2039,7 +2039,7 @@ mod tasks_test { assert_tasks_view!(tasks, [sub_id]); assert_eq!(tasks.len(), 3); let sub = tasks.get_by_id(&sub_id).unwrap(); - assert_eq!(sub.get_dependendees(), Vec::<&EventId>::new()); + assert_eq!(sub.find_dependents(), Vec::<&EventId>::new()); } #[test] @@ -2218,7 +2218,7 @@ mod tasks_test { let empty = tasks.make_task_unchecked("", vec![]); let empty_task = tasks.get_by_id(&empty).unwrap(); - let empty_id = empty_task.event.id.to_string(); + let empty_id = empty_task.get_id().to_string(); assert_eq!(empty_task.get_title(), empty_id); assert_eq!(tasks.get_task_path(Some(empty)), empty_id); }