diff --git a/src/helpers.rs b/src/helpers.rs index 98ded9d..9438d24 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -36,7 +36,7 @@ impl ToTimestamp for DateTime { /// Parses the hour from a plain number in the String, /// with max of max_future hours into the future. -/// TODO parse HHMM as well +// TODO parse HHMM as well pub fn parse_hour(str: &str, max_future: i64) -> Option> { str.parse::().ok().and_then(|hour| { let now = Local::now(); diff --git a/src/kinds.rs b/src/kinds.rs index d61fc47..8179cbe 100644 --- a/src/kinds.rs +++ b/src/kinds.rs @@ -118,8 +118,8 @@ pub(crate) fn extract_tags(input: &str, users: &NostrUsers) -> (String, Vec return false; } if let Ok(num) = s[1..].parse::() { - tags.push(to_prio_tag(num * (if s.len() > 2 { 1 } else { 10 }))); - return false + tags.push(to_prio_tag(num * (if s.len() > 2 { 1 } else { 10 }))); + return false; } } true diff --git a/src/main.rs b/src/main.rs index 3566520..5103c16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use std::str::FromStr; use std::time::Duration; 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}; @@ -28,7 +29,6 @@ use rustyline::DefaultEditor; use tokio::sync::mpsc; use tokio::time::error::Elapsed; use tokio::time::timeout; -use crate::hashtag::Hashtag; mod helpers; mod task; @@ -404,7 +404,7 @@ async fn main() -> Result<()> { Some(et) => Some(et).take_if(|et| et.marker.as_ref().is_some_and(|m| m != MARKER_PROPERTY)) .map(|et| format!("{}: {}", et.marker.as_ref().unwrap(), tasks.get_relative_path(et.id))), - None => + None => Some(format_tag_basic(t)), } }).join(", ") @@ -492,7 +492,8 @@ async fn main() -> Result<()> { tasks.set_key_filter(key) } else { if parse_hour(arg, 1) - .or_else(|| parse_date(arg).map(|utc| utc.with_timezone(&Local))) + .or_else(|| parse_date(arg) + .map(|utc| utc.with_timezone(&Local))) .map(|time| { info!("Filtering for tasks from {}", format_datetime_relative(time)); tasks.set_filter_since(time.to_timestamp()) diff --git a/src/task.rs b/src/task.rs index 5d7c92f..bc02280 100644 --- a/src/task.rs +++ b/src/task.rs @@ -7,15 +7,16 @@ use std::iter::once; use std::str::FromStr; use std::string::ToString; +use crate::hashtag::{is_hashtag, Hashtag}; +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; + use colored::{ColoredString, Colorize}; use itertools::Either::{Left, Right}; use itertools::Itertools; use log::{debug, error, info, trace, warn}; use nostr_sdk::{Alphabet, Event, EventId, Kind, PublicKey, SingleLetterTag, Tag, TagKind, Timestamp}; -use crate::hashtag::{is_hashtag, Hashtag}; -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 static MARKER_PARENT: &str = "parent"; pub static MARKER_DEPENDS: &str = "depends"; @@ -151,7 +152,7 @@ impl Task { pub fn last_state_update(&self) -> Timestamp { self.state().map(|s| s.time).unwrap_or(self.event.created_at) } - + pub fn state_at(&self, time: Timestamp) -> Option { // TODO do not iterate constructed state objects let state = self.states().take_while_inclusive(|ts| ts.time > time); @@ -201,7 +202,7 @@ impl Task { fn tags(&self) -> impl Iterator { self.props.iter() .flat_map(|e| e.tags.iter() - .filter(|t| t.single_letter_tag().is_none_or(|s| s.character != Alphabet::E))) + .filter(|t| t.single_letter_tag().is_none_or(|s| s.character != Alphabet::E))) .chain(self.tags.iter().flatten()) } @@ -376,7 +377,7 @@ mod tasks_test { .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(), "") diff --git a/src/tasks.rs b/src/tasks.rs index 91558a9..f76ec1d 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -9,14 +9,20 @@ use std::time::Duration; use crate::event_sender::{EventSender, MostrMessage}; use crate::hashtag::Hashtag; -use crate::helpers::{format_timestamp_local, format_timestamp_relative, format_timestamp_relative_to, parse_tracking_stamp, some_non_empty, to_string_or_default, CHARACTER_THRESHOLD}; +use crate::helpers::{ + format_timestamp_local, format_timestamp_relative, format_timestamp_relative_to, + 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::tasks::nostr_users::NostrUsers; use colored::Colorize; use itertools::Itertools; use log::{debug, error, info, trace, warn}; -use nostr_sdk::{Alphabet, Event, EventBuilder, EventId, JsonUtil, Keys, Kind, Metadata, PublicKey, SingleLetterTag, Tag, TagKind, Timestamp, Url}; +use nostr_sdk::{ + Alphabet, Event, EventBuilder, EventId, JsonUtil, Keys, Kind, Metadata, PublicKey, + SingleLetterTag, Tag, TagKind, Timestamp, Url, +}; use regex::bytes::Regex; use tokio::sync::mpsc::Sender; @@ -64,7 +70,7 @@ pub(crate) struct TasksRelay { /// The task properties currently visible properties: Vec, /// The task properties sorted by - sorting: VecDeque, // TODO track boolean for reversal? + sorting: VecDeque, // TODO prefix +/- for asc/desc, no prefix for default /// A filtered view of the current tasks. /// Would like this to be Task references @@ -270,7 +276,9 @@ impl TasksRelay { hist.values().filter_map(move |event| { let new = some_non_empty(&event.tags.iter() .filter_map(|t| t.content()) - .map(|str| EventId::from_str(str).ok().map_or(str.to_string(), |id| self.get_task_path(Some(id)))) + .map(|str| + EventId::from_str(str).ok() + .map_or(str.to_string(), |id| self.get_task_path(Some(id)))) .join(" ")); if new != last { // TODO omit intervals <2min - but I think I need threeway variable tracking for that @@ -310,7 +318,7 @@ impl TasksRelay { match self.get_position() { None => self.times_tracked_for(key), Some(id) => { - // TODO show current recursive with pubkey + // TODO show current recursive if there is a pubkey let ids = [id]; let mut history = self.history.iter().flat_map(|(key, set)| { @@ -335,8 +343,7 @@ impl TasksRelay { )) }); vec - }) - .collect_vec(); + }).collect_vec(); // TODO sorting depends on timestamp format - needed to interleave different people history.sort_unstable(); ( @@ -404,13 +411,14 @@ impl TasksRelay { pub(crate) fn pubkey_str(&self) -> Option { match self.pubkey { - None => { Some("ALL".to_string()) } - Some(key) => + None => Some("ALL".to_string()), + Some(key) => { if key != self.sender.pubkey() { Some(self.users.get_username(&key)) } else { None - }, + } + } } } @@ -425,8 +433,8 @@ impl TasksRelay { prompt.push_str(&format!(" -#{}", tag)); } prompt.push_str(&self.state.indicator()); - self.priority.map(|p| - prompt.push_str(&format!(" *{:02}", p))); + self.priority + .map(|p| prompt.push_str(&format!(" *{:02}", p))); prompt } @@ -453,7 +461,6 @@ impl TasksRelay { } } - // Helpers fn resolve_tasks_rec<'a>( @@ -495,8 +502,7 @@ impl TasksRelay { let mut found = false; for tag in event.tags.iter() { if let Some(event_tag) = match_event_tag(tag) { - if event_tag.marker - .as_ref() + if event_tag.marker.as_ref() .is_none_or(|m| m.to_string() == MARKER_PROPERTY) { self.tasks.get_mut(&event_tag.id).map(|t| { @@ -529,11 +535,7 @@ impl TasksRelay { } // TODO sparse is deprecated and only left for tests - pub(crate) fn filtered_tasks( - &self, - position: Option, - sparse: bool, - ) -> Vec<&Task> { + pub(crate) fn filtered_tasks(&self, position: Option, sparse: bool) -> Vec<&Task> { let roots = self.tasks.children_for(position); let mut current = self.resolve_tasks_rec(roots, sparse, self.search_depth + self.view_depth); @@ -712,7 +714,7 @@ impl TasksRelay { }; self.sender.submit( EventBuilder::new(Kind::Bookmarks, "mostr pins") - .tags(self.bookmarks.iter().map(|id| Tag::event(*id))) + .tags(self.bookmarks.iter().map(|id| Tag::event(*id))), )?; Ok(added) } @@ -955,7 +957,7 @@ impl TasksRelay { self.track_at(time, target); return; } - + self.view.clear(); let pos = self.get_position(); if target == pos { @@ -989,20 +991,26 @@ impl TasksRelay { // Updates pub(crate) fn make_event_tag_from_id(&self, id: EventId, marker: &str) -> Tag { - Tag::custom(TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::E)), [ - id.to_string(), - to_string_or_default(self.sender.url.as_ref()), - marker.to_string(), - ]) + Tag::custom( + TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::E)), + [ + id.to_string(), + to_string_or_default(self.sender.url.as_ref()), + marker.to_string(), + ], + ) } pub(crate) fn make_event_tag(&self, event: &Event, marker: &str) -> Tag { - Tag::custom(TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::E)), [ - event.id.to_string(), - to_string_or_default(self.sender.url.as_ref()), - marker.to_string(), - event.pubkey.to_string(), - ]) + Tag::custom( + TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::E)), + [ + event.id.to_string(), + to_string_or_default(self.sender.url.as_ref()), + marker.to_string(), + event.pubkey.to_string(), + ], + ) } pub(crate) fn parent_tag(&self) -> Option { @@ -1069,17 +1077,29 @@ impl TasksRelay { /// Creates a task including current tag filters /// /// Sanitizes input - pub(crate) fn make_task_with(&mut self, input: &str, tags: impl IntoIterator, set_state: bool) -> EventId { + pub(crate) fn make_task_with( + &mut self, + input: &str, + tags: impl IntoIterator, + set_state: bool, + ) -> EventId { let (input, input_tags) = extract_tags(input.trim(), &self.users); let prio = - if input_tags.iter().any(|t| t.kind().to_string() == PRIO) { None } else { self.priority.map(|p| to_prio_tag(p)) }; - info!("Created task \"{input}\" with tags [{}]", join_tags(&input_tags)); + if input_tags.iter().any(|t| t.kind().to_string() == PRIO) { + None + } else { + self.priority.map(|p| to_prio_tag(p)) + }; + info!( + "Created task \"{input}\" with tags [{}]", + join_tags(&input_tags) + ); let id = self.submit( EventBuilder::new(TASK_KIND, &input) .tags(input_tags) .tags(self.context_hashtags()) .tags(tags) - .tags(prio) + .tags(prio), ); if set_state { self.state @@ -1224,9 +1244,7 @@ impl TasksRelay { } fn get_own_events_history(&self) -> impl DoubleEndedIterator + '_ { - self.get_own_history() - .into_iter() - .flat_map(|t| t.values()) + self.get_own_history().into_iter().flat_map(|t| t.values()) } pub(super) fn history_before_now(&self) -> impl Iterator { @@ -1310,7 +1328,8 @@ impl TasksRelay { self.get_by_id(&id) .and_then(|task| task.state_at(self.custom_time.unwrap_or_default())) .map(|ts| format!(" from {}", ts)) - .unwrap_or_default()); + .unwrap_or_default() + ); self.submit(prop) } @@ -1338,7 +1357,8 @@ impl TasksRelay { info!("Created {} {format}", if marker == MARKER_PROPERTY { "note" } else { "activity" } ); self.submit( prop.tags( - self.get_position().map(|pos| self.make_event_tag_from_id(pos, marker)))) + self.get_position() + .map(|pos| self.make_event_tag_from_id(pos, marker)))) } // Properties @@ -1381,9 +1401,7 @@ impl Display for TasksRelay { let state = t.state_or_default(); let now = &now(); let mut tracking_stamp: Option = None; - for elem in - timestamps(self.get_own_events_history(), &[t.event.id]) - .map(|(e, _)| e) { + for elem in timestamps(self.get_own_events_history(), &[t.event.id]).map(|(e, _)| e) { if tracking_stamp.is_some() && elem > now { break; } @@ -1437,11 +1455,10 @@ impl Display for TasksRelay { let count = visible.len(); let mut total_time = 0; for task in visible { - writeln!( - lock, - "{}", self.properties.iter() - .map(|p| self.get_property(task, p.as_str())) - .join(" \t") + writeln!(lock, "{}", + self.properties.iter() + .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 } @@ -1499,11 +1516,12 @@ where fn display_time(format: &str, secs: u64) -> String { Some(secs / 60) .filter(|t| t > &0) - .map_or(String::new(), |mins| format - .replace("MMM", &format!("{:3}", mins)) - .replace("HH", &format!("{:02}", mins.div(60))) - .replace("MM", &format!("{:02}", mins.rem(60))), - ) + .map_or(String::new(), |mins| { + format + .replace("MMM", &format!("{:3}", mins)) + .replace("HH", &format!("{:02}", mins.div(60))) + .replace("MM", &format!("{:02}", mins.rem(60))) + }) } /// Joins the tasks of this upwards iterator. @@ -1521,7 +1539,8 @@ pub(crate) fn join_tasks<'a>( .take_if(|_| include_last_id) .and_then(|t| t.parent_id()) .map(|id| id.to_string()) - .into_iter()) + .into_iter(), + ) .fold(None, |acc, val| { Some(acc.map_or_else( || val.clone(), @@ -1793,7 +1812,8 @@ mod tasks_test { ($left:expr, $right:expr $(,)?) => { let tasks = $left.visible_tasks(); assert_tasks!($left, tasks, $right, - "\nQuick Access: {:?}", $left.quick_access_raw().map(|id| $left.get_relative_path(*id)).collect_vec()); + "\nQuick Access: {:?}", + $left.quick_access_raw().map(|id| $left.get_relative_path(*id)).collect_vec()); }; } @@ -1828,14 +1848,20 @@ mod tasks_test { let parent = tasks.make_task("parent #tag1"); tasks.move_to(Some(parent)); let sub = tasks.make_task("sub #oi # tag2"); - assert_eq!(tasks.all_hashtags(), ["oi", "tag1", "tag2"].into_iter().map(Hashtag::from).collect()); + assert_eq!( + tasks.all_hashtags(), + ["oi", "tag1", "tag2"].into_iter().map(Hashtag::from).collect() + ); tasks.make_note("note with #tag3 # yeah"); let all_tags = ["oi", "tag1", "tag2", "tag3", "yeah"].into_iter().map(Hashtag::from).collect(); assert_eq!(tasks.all_hashtags(), all_tags); tasks.custom_time = Some(Timestamp::now()); tasks.update_state("Finished #YeaH # oi", State::Done); - assert_eq!(tasks.get_by_id(&parent).unwrap().list_hashtags().collect_vec(), ["YeaH", "oi", "tag3", "yeah", "tag1"].map(Hashtag::from)); + assert_eq!( + tasks.get_by_id(&parent).unwrap().list_hashtags().collect_vec(), + ["YeaH", "oi", "tag3", "yeah", "tag1"].map(Hashtag::from) + ); assert_eq!(tasks.all_hashtags(), all_tags); tasks.custom_time = Some(now()); @@ -1911,7 +1937,7 @@ mod tasks_test { let parent = tasks.make_task("parent"); let sub = tasks.submit( EventBuilder::new(TASK_KIND, "sub") - .tags([tasks.make_event_tag_from_id(parent, MARKER_PARENT)]) + .tags([tasks.make_event_tag_from_id(parent, MARKER_PARENT)]), ); assert_tasks_view!(tasks, [parent]); tasks.track_at(Timestamp::now(), Some(sub)); diff --git a/src/tasks/nostr_users.rs b/src/tasks/nostr_users.rs index 90596a6..156dc02 100644 --- a/src/tasks/nostr_users.rs +++ b/src/tasks/nostr_users.rs @@ -1,10 +1,10 @@ +use nostr_sdk::{Keys, Metadata, PublicKey, Tag}; use std::collections::HashMap; use std::str::FromStr; -use nostr_sdk::{Keys, Metadata, PublicKey, Tag}; #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct NostrUsers { - users: HashMap + users: HashMap, } impl NostrUsers { @@ -18,7 +18,7 @@ impl NostrUsers { let lowered = term.trim().to_ascii_lowercase(); let term = lowered.as_str(); if term.is_empty() { - return None + return None; } if let Ok(key) = PublicKey::from_str(term) { return self.users.get_key_value(&key);