refactor: modularize task
This commit is contained in:
parent
03fd79ad95
commit
6f2a7951d5
12
src/main.rs
12
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))
|
||||
});
|
||||
|
|
221
src/task.rs
221
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<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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}));
|
||||
}
|
82
src/tasks.rs
82
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<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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue