refactor: modularize task

This commit is contained in:
xeruf 2024-12-06 12:42:39 +01:00
parent 03fd79ad95
commit 6f2a7951d5
5 changed files with 255 additions and 228 deletions

View File

@ -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))
});

View File

@ -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<BTreeSet<Tag>>,
/// 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<Item=&Event> {
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<Item=PublicKey> + '_ {
@ -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<Item=&'a EventId> {
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<Item=&'a EventId> {
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<Item=&Event> + '_ {
self.props.iter().filter(|event| event.kind == Kind::TextNote)
}
@ -139,9 +154,9 @@ impl Task {
})
}
fn states(&self) -> impl DoubleEndedIterator<Item=TaskState> + '_ {
fn states(&self) -> impl DoubleEndedIterator<Item=StateChange> + '_ {
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<TaskState> {
pub fn state_at(&self, time: Timestamp) -> Option<StateChange> {
// 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<TaskState> {
pub fn state(&self) -> Option<StateChange> {
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<String> {
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<String>,
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<Self, Self::Error> {
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<Kind> for State {
type Error = ();
fn try_from(value: Kind) -> Result<Self, Self::Error> {
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<State> 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,
}));
}
}

128
src/task/state.rs Normal file
View File

@ -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<String>,
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<Option<StateChange>> for State {
fn from(value: Option<StateChange>) -> 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<Self, Self::Error> {
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<Kind> for State {
type Error = ();
fn try_from(value: Kind) -> Result<Self, Self::Error> {
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<State> 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)
}
}

40
src/task/tests.rs Normal file
View File

@ -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,
}));
}

View File

@ -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<EventId, Task>;
trait TaskMapMethods {
fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator<Item=&Task> + 'a;
fn children_for<'a>(&'a self, id: Option<EventId>) -> impl Iterator<Item=&Task> + 'a;
fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator<Item=&EventId> + 'a;
fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator<Item=EventId> + 'a;
}
impl TaskMapMethods for TaskMap {
fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator<Item=&Task> + 'a {
self.children_for(Some(task.event.id))
self.children_for(Some(task.get_id().clone()))
}
fn children_for<'a>(&'a self, id: Option<EventId>) -> impl Iterator<Item=&Task> + 'a {
self.values().filter(move |t| t.parent_id() == id.as_ref())
}
fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator<Item=&EventId> + 'a {
fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator<Item=EventId> + '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<f32> {
self.get_by_id(id).and_then(|task| match task.pure_state() {
fn total_progress(&self, id: EventId) -> Option<f32> {
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<Item=&EventId> {
fn quick_access_raw(&self) -> impl Iterator<Item=EventId> + '_ {
// 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<Item=&Task> {
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<EventId> = 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<Timestamp> = 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<T> {
pub(super) trait PropertyCollection<T> {
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<EventId>>(),
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);
}