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::hashtag::Hashtag;
|
||||||
use crate::helpers::*;
|
use crate::helpers::*;
|
||||||
use crate::kinds::{format_tag_basic, match_event_tag, Prio, BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS};
|
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 crate::tasks::{referenced_event, PropertyCollection, StateFilter, TasksRelay};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
@ -387,7 +387,7 @@ async fn main() -> Result<()> {
|
||||||
None => {
|
None => {
|
||||||
if let Some(task) = tasks.get_current_task() {
|
if let Some(task) = tasks.get_current_task() {
|
||||||
println!("Change History for {}:", task.get_id());
|
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!("{} {} [{}]",
|
println!("{} {} [{}]",
|
||||||
format_timestamp_full(&e.created_at),
|
format_timestamp_full(&e.created_at),
|
||||||
match State::try_from(e.kind) {
|
match State::try_from(e.kind) {
|
||||||
|
@ -567,7 +567,7 @@ async fn main() -> Result<()> {
|
||||||
match tasks.get_position() {
|
match tasks.get_position() {
|
||||||
None => {
|
None => {
|
||||||
warn!("First select a task to set its state!");
|
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) => {
|
Some(id) => {
|
||||||
'block: {
|
'block: {
|
||||||
|
@ -584,8 +584,8 @@ async fn main() -> Result<()> {
|
||||||
tasks.set_state_for(id, right, State::Pending);
|
tasks.set_state_for(id, right, State::Pending);
|
||||||
tasks.custom_time = Some(stamp);
|
tasks.custom_time = Some(stamp);
|
||||||
tasks.set_state_for(id,
|
tasks.set_state_for(id,
|
||||||
&state.as_ref().map(TaskState::get_label).unwrap_or_default(),
|
&state.as_ref().map(StateChange::get_label).unwrap_or_default(),
|
||||||
state.map(|ts| ts.state).unwrap_or(State::Open));
|
State::from(state));
|
||||||
break 'block;
|
break 'block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -728,7 +728,7 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let filtered =
|
let filtered =
|
||||||
tasks.get_filtered(pos, |t| {
|
tasks.get_filtered(pos, |t| {
|
||||||
transform(&t.event.content).contains(&remaining) ||
|
transform(&t.get_title()).contains(&remaining) ||
|
||||||
t.list_hashtags().any(
|
t.list_hashtags().any(
|
||||||
|tag| tag.contains(&remaining))
|
|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 fmt::Display;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::btree_set::Iter;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter::once;
|
use std::iter::{once, Chain, Once};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::string::ToString;
|
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::kinds::{match_event_tag, Prio, PRIO, PROCEDURE_KIND, PROCEDURE_KIND_ID, TASK_KIND};
|
||||||
use crate::tasks::now;
|
use crate::tasks::now;
|
||||||
|
|
||||||
|
pub use crate::task::state::State;
|
||||||
|
pub use crate::task::state::StateChange;
|
||||||
|
|
||||||
use colored::{ColoredString, Colorize};
|
use colored::{ColoredString, Colorize};
|
||||||
use itertools::Either::{Left, Right};
|
use itertools::Either::{Left, Right};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -25,7 +33,7 @@ pub static MARKER_PROPERTY: &str = "property";
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub(crate) struct Task {
|
pub(crate) struct Task {
|
||||||
/// Event that defines this 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
|
/// Cached sorted tags of the event with references removed
|
||||||
tags: Option<BTreeSet<Tag>>,
|
tags: Option<BTreeSet<Tag>>,
|
||||||
/// Task references derived from the event tags
|
/// Task references derived from the event tags
|
||||||
|
@ -68,8 +76,13 @@ impl Task {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_id(&self) -> &EventId {
|
/// All Events including the task and its props in chronological order
|
||||||
&self.event.id
|
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> + '_ {
|
pub(crate) fn get_participants(&self) -> impl Iterator<Item=PublicKey> + '_ {
|
||||||
|
@ -84,28 +97,30 @@ impl Task {
|
||||||
.unwrap_or_else(|| self.event.pubkey)
|
.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
|
/// Trimmed event content or stringified id
|
||||||
pub(crate) fn get_title(&self) -> String {
|
pub(crate) fn get_title(&self) -> String {
|
||||||
some_non_empty(self.event.content.trim())
|
some_non_empty(self.event.content.trim())
|
||||||
.unwrap_or_else(|| self.get_id().to_string())
|
.unwrap_or_else(|| self.get_id().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Title with leading hashtags removed
|
||||||
pub(crate) fn get_filter_title(&self) -> String {
|
pub(crate) fn get_filter_title(&self) -> String {
|
||||||
self.event.content.trim().trim_start_matches('#').to_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> + '_ {
|
fn description_events(&self) -> impl DoubleEndedIterator<Item=&Event> + '_ {
|
||||||
self.props.iter().filter(|event| event.kind == Kind::TextNote)
|
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| {
|
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),
|
name: some_non_empty(&event.content),
|
||||||
state: s,
|
state: s,
|
||||||
time: event.created_at,
|
time: event.created_at,
|
||||||
|
@ -153,7 +168,7 @@ impl Task {
|
||||||
self.state().map(|s| s.time).unwrap_or(self.event.created_at)
|
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
|
// TODO do not iterate constructed state objects
|
||||||
let state = self.states().take_while_inclusive(|ts| ts.time > time);
|
let state = self.states().take_while_inclusive(|ts| ts.time > time);
|
||||||
state.last().map(|ts| {
|
state.last().map(|ts| {
|
||||||
|
@ -166,16 +181,16 @@ impl Task {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current state if this is a task rather than an activity
|
/// 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();
|
let now = now();
|
||||||
self.state_at(now)
|
self.state_at(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn pure_state(&self) -> State {
|
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())
|
self.state().unwrap_or_else(|| self.default_state())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,8 +201,8 @@ impl Task {
|
||||||
.map(|state| state.get_colored_label())
|
.map(|state| state.get_colored_label())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_state(&self) -> TaskState {
|
fn default_state(&self) -> StateChange {
|
||||||
TaskState {
|
StateChange {
|
||||||
name: None,
|
name: None,
|
||||||
state: State::Open,
|
state: State::Open,
|
||||||
time: self.event.created_at,
|
time: self.event.created_at,
|
||||||
|
@ -221,7 +236,7 @@ impl Task {
|
||||||
pub(crate) fn get(&self, property: &str) -> Option<String> {
|
pub(crate) fn get(&self, property: &str) -> Option<String> {
|
||||||
match property {
|
match property {
|
||||||
// Static
|
// Static
|
||||||
"id" => Some(self.event.id.to_string()),
|
"id" => Some(self.get_id().to_string()),
|
||||||
"parentid" => self.parent_id().map(|i| i.to_string()),
|
"parentid" => self.parent_id().map(|i| i.to_string()),
|
||||||
"name" => Some(self.event.content.clone()),
|
"name" => Some(self.event.content.clone()),
|
||||||
"key" | "pubkey" => Some(self.event.pubkey.to_string()),
|
"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,
|
parse_tracking_stamp, some_non_empty, to_string_or_default, CHARACTER_THRESHOLD,
|
||||||
};
|
};
|
||||||
use crate::kinds::*;
|
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 crate::tasks::nostr_users::NostrUsers;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -40,18 +40,18 @@ type TaskMap = HashMap<EventId, Task>;
|
||||||
trait TaskMapMethods {
|
trait TaskMapMethods {
|
||||||
fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator<Item=&Task> + 'a;
|
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_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 {
|
impl TaskMapMethods for TaskMap {
|
||||||
fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator<Item=&Task> + 'a {
|
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 {
|
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())
|
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())
|
self.children_for(Some(id)).map(|t| t.get_id())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,15 +376,15 @@ impl TasksRelay {
|
||||||
total
|
total
|
||||||
}
|
}
|
||||||
|
|
||||||
fn total_progress(&self, id: &EventId) -> Option<f32> {
|
fn total_progress(&self, id: EventId) -> Option<f32> {
|
||||||
self.get_by_id(id).and_then(|task| match task.pure_state() {
|
self.get_by_id(&id).and_then(|task| match task.pure_state() {
|
||||||
State::Closed => None,
|
State::Closed => None,
|
||||||
State::Done => Some(1.0),
|
State::Done => Some(1.0),
|
||||||
_ => {
|
_ => {
|
||||||
let mut sum = 0f32;
|
let mut sum = 0f32;
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
for prog in self.tasks
|
for prog in self.tasks
|
||||||
.children_ids_for(task.event.id)
|
.children_ids_for(task.get_id())
|
||||||
.filter_map(|e| self.total_progress(e))
|
.filter_map(|e| self.total_progress(e))
|
||||||
{
|
{
|
||||||
sum += prog;
|
sum += prog;
|
||||||
|
@ -450,7 +450,7 @@ impl TasksRelay {
|
||||||
pub(crate) fn get_relative_path(&self, id: EventId) -> String {
|
pub(crate) fn get_relative_path(&self, id: EventId) -> String {
|
||||||
join_tasks(
|
join_tasks(
|
||||||
self.traverse_up_from(Some(id))
|
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,
|
false,
|
||||||
).unwrap_or(id.to_string())
|
).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)
|
// TODO add recent tasks (most time tracked + recently created)
|
||||||
self.bookmarks.iter()
|
self.bookmarks.iter().cloned()
|
||||||
.chain(
|
.chain(
|
||||||
// Latest
|
// Latest
|
||||||
self.tasks.values()
|
self.tasks.values()
|
||||||
|
@ -618,13 +618,13 @@ impl TasksRelay {
|
||||||
|
|
||||||
fn bookmarked_tasks_deduped(&self, visible: &[&Task]) -> impl Iterator<Item=&Task> {
|
fn bookmarked_tasks_deduped(&self, visible: &[&Task]) -> impl Iterator<Item=&Task> {
|
||||||
let tree = visible.iter()
|
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();
|
.unique();
|
||||||
let pos = self.get_position();
|
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()
|
self.quick_access_raw()
|
||||||
.filter(|id| !ids.contains(id))
|
.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))
|
.filter(|t| self.filter(t))
|
||||||
.sorted_by_cached_key(|t| self.sorting_key(t))
|
.sorted_by_cached_key(|t| self.sorting_key(t))
|
||||||
.dedup()
|
.dedup()
|
||||||
|
@ -655,7 +655,7 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
"state" => {
|
"state" => {
|
||||||
if let Some(task) = task
|
if let Some(task) = task
|
||||||
.get_dependendees()
|
.find_dependents()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|id| self.get_by_id(id))
|
.filter_map(|id| self.get_by_id(id))
|
||||||
.find(|t| t.pure_state().is_open())
|
.find(|t| t.pure_state().is_open())
|
||||||
|
@ -676,7 +676,7 @@ impl TasksRelay {
|
||||||
"owner" => format!("{:.6}", self.users.get_username(&task.get_owner())),
|
"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
|
"author" | "creator" => format!("{:.6}", self.users.get_username(&task.event.pubkey)), // FIXME temporary until proper column alignment
|
||||||
"prio" => self
|
"prio" => self
|
||||||
.traverse_up_from(Some(task.event.id))
|
.traverse_up_from(Some(task.get_id()))
|
||||||
.find_map(Task::priority_raw)
|
.find_map(Task::priority_raw)
|
||||||
.map(|p| p.to_string())
|
.map(|p| p.to_string())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
|
@ -686,11 +686,11 @@ impl TasksRelay {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
"path" => self.get_task_path(Some(task.event.id)),
|
"path" => self.get_task_path(Some(task.get_id())),
|
||||||
"rpath" => self.get_relative_path(task.event.id),
|
"rpath" => self.get_relative_path(task.get_id()),
|
||||||
// TODO format strings configurable
|
// TODO format strings configurable
|
||||||
"time" => display_time("MMMm", self.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())),
|
"rtime" => display_time("HH:MM", self.total_time_tracked(task.get_id())),
|
||||||
prop => task.get(prop).unwrap_or_default(),
|
prop => task.get(prop).unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -749,7 +749,7 @@ impl TasksRelay {
|
||||||
self.filtered_tasks(position, false)
|
self.filtered_tasks(position, false)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(predicate)
|
.filter(predicate)
|
||||||
.map(|t| t.event.id)
|
.map(|t| t.get_id())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -881,22 +881,22 @@ impl TasksRelay {
|
||||||
let content = task.get_filter_title();
|
let content = task.get_filter_title();
|
||||||
let lowercase = content.to_ascii_lowercase();
|
let lowercase = content.to_ascii_lowercase();
|
||||||
if lowercase == lowercase_arg {
|
if lowercase == lowercase_arg {
|
||||||
return vec![task.event.id];
|
return vec![task.get_id()];
|
||||||
} else if content.starts_with(arg) {
|
} else if content.starts_with(arg) {
|
||||||
filtered.push(task.event.id)
|
filtered.push(task.get_id())
|
||||||
} else if regex.as_ref()
|
} else if regex.as_ref()
|
||||||
.map(|r| r.is_match(lowercase.as_bytes()))
|
.map(|r| r.is_match(lowercase.as_bytes()))
|
||||||
.unwrap_or_else(|_| lowercase.starts_with(&lowercase_arg)) {
|
.unwrap_or_else(|_| lowercase.starts_with(&lowercase_arg)) {
|
||||||
filtered_fuzzy.push(task.event.id)
|
filtered_fuzzy.push(task.get_id())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Find global exact match
|
// Find global exact match
|
||||||
for task in self.tasks.values() {
|
for task in self.tasks.values() {
|
||||||
if task.get_filter_title().to_ascii_lowercase() == lowercase_arg &&
|
if task.get_filter_title().to_ascii_lowercase() == lowercase_arg &&
|
||||||
// exclude closed tasks and their subtasks
|
// 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| {
|
.find(|e| {
|
||||||
referenced_event(e)
|
referenced_event(e)
|
||||||
.and_then(|id| self.get_by_id(&id))
|
.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 {
|
if let Some(event) = found {
|
||||||
self.move_to(referenced_event(event));
|
self.move_to(referenced_event(event));
|
||||||
|
@ -1343,7 +1343,7 @@ impl TasksRelay {
|
||||||
.tags(tags);
|
.tags(tags);
|
||||||
info!(
|
info!(
|
||||||
"Task status {} set for \"{}\"{}{}",
|
"Task status {} set for \"{}\"{}{}",
|
||||||
TaskState::get_label_for(&state, comment),
|
StateChange::get_label_for(&state, comment),
|
||||||
self.get_task_title(&id),
|
self.get_task_title(&id),
|
||||||
self.custom_time
|
self.custom_time
|
||||||
.map(|ts| format!(" at {}", format_timestamp_relative(&ts)))
|
.map(|ts| format!(" at {}", format_timestamp_relative(&ts)))
|
||||||
|
@ -1424,7 +1424,7 @@ impl Display for TasksRelay {
|
||||||
let state = t.state_or_default();
|
let state = t.state_or_default();
|
||||||
let now = &now();
|
let now = &now();
|
||||||
let mut tracking_stamp: Option<Timestamp> = None;
|
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 {
|
if tracking_stamp.is_some() && elem > now {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1434,9 +1434,9 @@ impl Display for TasksRelay {
|
||||||
lock,
|
lock,
|
||||||
"Active from {} (total tracked time {}m) - {} since {}",
|
"Active from {} (total tracked time {}m) - {} since {}",
|
||||||
tracking_stamp.map_or("?".to_string(), |t| format_timestamp_relative(&t)),
|
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,
|
state,
|
||||||
format_timestamp_relative(&state.time)
|
format_timestamp_relative(&state.get_timestamp())
|
||||||
)?;
|
)?;
|
||||||
for d in t.descriptions().rev() { writeln!(lock, "{}", d)?; }
|
for d in t.descriptions().rev() { writeln!(lock, "{}", d)?; }
|
||||||
writeln!(lock)?;
|
writeln!(lock)?;
|
||||||
|
@ -1483,7 +1483,7 @@ impl Display for TasksRelay {
|
||||||
.map(|p| self.get_property(task, p.as_str()))
|
.map(|p| self.get_property(task, p.as_str()))
|
||||||
.join(" \t")
|
.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,
|
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 remove_at(&mut self, index: usize);
|
||||||
fn add_or_remove(&mut self, value: T);
|
fn add_or_remove(&mut self, value: T);
|
||||||
fn add_or_remove_at(&mut self, value: T, index: usize);
|
fn add_or_remove_at(&mut self, value: T, index: usize);
|
||||||
|
@ -1675,7 +1675,7 @@ impl<'a> ChildIterator<'a> {
|
||||||
&mut tasks
|
&mut tasks
|
||||||
.values()
|
.values()
|
||||||
.filter(move |t| t.parent_id() == id)
|
.filter(move |t| t.parent_id() == id)
|
||||||
.map(|t| t.event.id)
|
.map(|t| t.get_id())
|
||||||
.collect_vec()
|
.collect_vec()
|
||||||
);
|
);
|
||||||
Self::with_queue(tasks, queue)
|
Self::with_queue(tasks, queue)
|
||||||
|
@ -1765,7 +1765,7 @@ impl<'a> ChildIterator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_children_of(&mut self, task: &'a Task) {
|
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<'_> {}
|
impl FusedIterator for ChildIterator<'_> {}
|
||||||
|
@ -1779,7 +1779,7 @@ impl<'a> Iterator for ChildIterator<'a> {
|
||||||
// Unknown task, might still find children, just slower
|
// Unknown task, might still find children, just slower
|
||||||
for task in self.tasks.values() {
|
for task in self.tasks.values() {
|
||||||
if task.parent_id().is_some_and(|i| i == id) {
|
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 $(,)?) => {
|
($tasks:expr, $expected:expr $(,)?) => {
|
||||||
assert_tasks!($tasks, $tasks.visible_tasks(), $expected,
|
assert_tasks!($tasks, $tasks.visible_tasks(), $expected,
|
||||||
"\nQuick Access: {:?}",
|
"\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!(
|
assert_eq!(
|
||||||
$tasklist
|
$tasklist
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| t.event.id)
|
.map(|t| t.get_id())
|
||||||
.collect::<HashSet<EventId>>(),
|
.collect::<HashSet<EventId>>(),
|
||||||
HashSet::from_iter($expected.clone()),
|
HashSet::from_iter($expected.clone()),
|
||||||
"Tasks Visible: {:?}\nExpected: {:?}{}",
|
"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(),
|
$expected.into_iter().map(|id| $tasks.get_relative_path(id)).collect_vec(),
|
||||||
format!($($($arg)*)?)
|
format!($($($arg)*)?)
|
||||||
);
|
);
|
||||||
|
@ -1923,7 +1923,7 @@ mod tasks_test {
|
||||||
let task2 = tasks.get_current_task().unwrap();
|
let task2 = tasks.get_current_task().unwrap();
|
||||||
assert_eq!(task2.descriptions().next(), None);
|
assert_eq!(task2.descriptions().next(), None);
|
||||||
assert_eq!(task2.priority(), Some(30));
|
assert_eq!(task2.priority(), Some(30));
|
||||||
let anid = task2.event.id;
|
let anid = task2.get_id();
|
||||||
|
|
||||||
tasks.custom_time = Some(Timestamp::now() + 1);
|
tasks.custom_time = Some(Timestamp::now() + 1);
|
||||||
let s1 = tasks.make_task_unwrapped("sub1");
|
let s1 = tasks.make_task_unwrapped("sub1");
|
||||||
|
@ -2039,7 +2039,7 @@ mod tasks_test {
|
||||||
assert_tasks_view!(tasks, [sub_id]);
|
assert_tasks_view!(tasks, [sub_id]);
|
||||||
assert_eq!(tasks.len(), 3);
|
assert_eq!(tasks.len(), 3);
|
||||||
let sub = tasks.get_by_id(&sub_id).unwrap();
|
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]
|
#[test]
|
||||||
|
@ -2218,7 +2218,7 @@ mod tasks_test {
|
||||||
|
|
||||||
let empty = tasks.make_task_unchecked("", vec![]);
|
let empty = tasks.make_task_unchecked("", vec![]);
|
||||||
let empty_task = tasks.get_by_id(&empty).unwrap();
|
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!(empty_task.get_title(), empty_id);
|
||||||
assert_eq!(tasks.get_task_path(Some(empty)), empty_id);
|
assert_eq!(tasks.get_task_path(Some(empty)), empty_id);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue