From 85b923edc82f50a04e7d09f289a1815561cc42fe Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Mon, 12 Aug 2024 23:02:50 +0300 Subject: [PATCH] feat: enable creating dependent sibling task --- README.md | 3 +- src/main.rs | 22 +++++++++--- src/task.rs | 2 +- src/tasks.rs | 96 ++++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 91 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index af32fba..04e51ec 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,8 @@ To stop time-tracking completely, simply move to the root of all tasks. + no match: create & activate task - `.2` - set view depth to `2`, which can be substituted for any number (how many subtask levels to show, default 1) - `/[TEXT]` - like `.`, but never creates a task -- `|[TASK]` - (un)mark current task as procedure or create and activate a new task procedure (where subtasks automatically depend on the previously created task) +- `||TASK` - create and activate a new task procedure (where subtasks automatically depend on the previously created task) +- `|[TASK]` - (un)mark current task as procedure or create a sibling task depending on the current one and move up Dots and slashes can be repeated to move to parent tasks. diff --git a/src/main.rs b/src/main.rs index 0652dd6..d78aa15 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use std::env::{args, var}; use std::fs; use std::fs::File; use std::io::{BufRead, BufReader, stdin, stdout, Write}; +use std::iter::once; use std::ops::Sub; use std::path::PathBuf; use std::str::FromStr; @@ -20,7 +21,7 @@ use xdg::BaseDirectories; use crate::helpers::*; use crate::kinds::{KINDS, PROPERTY_COLUMNS, TRACKING_KIND}; -use crate::task::State; +use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State}; use crate::tasks::{PropertyCollection, StateFilter, Tasks}; mod helpers; @@ -339,15 +340,26 @@ async fn main() { .map(|t| t.event.id) .collect() ); + info!("Filtering for procedures"); } Some(id) => { tasks.set_state_for(id, "", State::Procedure); } }, - Some(arg) => { - let id = tasks.make_task(arg); - tasks.set_state_for(id, "", State::Procedure); - tasks.move_to(Some(id)); + Some(arg) => 'arm: { + if arg.chars().next() != Some('|') { + if let Some(pos) = tasks.get_position() { + tasks.move_up(); + tasks.make_task_with( + arg, + once(tasks.make_event_tag_from_id(pos, MARKER_DEPENDS)) + .chain(tasks.parent_tag()), + true); + break 'arm; + } + } + let arg: String = arg.chars().skip_while(|c| c == &'|').collect(); + tasks.make_task_and_enter(&arg, State::Procedure); } } diff --git a/src/task.rs b/src/task.rs index d152640..a95dbbb 100644 --- a/src/task.rs +++ b/src/task.rs @@ -63,7 +63,7 @@ impl Task { &self.event.id } - fn find_refs<'a>(&'a self, marker: &'a str) -> impl Iterator { + pub(crate) fn find_refs<'a>(&'a self, marker: &'a str) -> impl Iterator { self.refs.iter().filter_map(move |(str, id)| Some(id).filter(|_| str == marker)) } diff --git a/src/tasks.rs b/src/tasks.rs index 0ef0eea..5a668c3 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -19,7 +19,7 @@ use TagStandard::Hashtag; use crate::{Events, EventSender}; use crate::helpers::some_non_empty; use crate::kinds::*; -use crate::task::{State, Task, TaskState}; +use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State, Task, TaskState}; type TaskMap = HashMap; #[derive(Debug, Clone)] @@ -613,7 +613,6 @@ impl Tasks { /// Expects sanitized input pub(crate) fn parse_task(&self, input: &str) -> EventBuilder { let mut tags: Vec = self.tags.iter().cloned().collect(); - self.position.inspect(|p| tags.push(Tag::event(*p))); match input.split_once(": ") { None => build_task(input, tags), Some(s) => { @@ -629,34 +628,65 @@ impl Tasks { } } - /// Creates a task following the current state - /// Sanitizes input - pub(crate) fn make_task(&mut self, input: &str) -> EventId { - let tag: Option = self.get_current_task() - .and_then(|t| { + pub(crate) fn make_event_tag_from_id(&self, id: EventId, marker: &str) -> Tag { + Tag::from(TagStandard::Event { + event_id: id, + relay_url: self.sender.url.as_ref().map(|url| UncheckedUrl::new(url.as_str())), + marker: Some(Marker::Custom(marker.to_string())), + public_key: self.get_by_id(&id).map(|e| e.event.pubkey), + }) + } + + pub(crate) fn make_event_tag(&self, event: &Event, marker: &str) -> Tag { + Tag::from(TagStandard::Event { + event_id: event.id, + relay_url: self.sender.url.as_ref().map(|url| UncheckedUrl::new(url.as_str())), + marker: Some(Marker::Custom(marker.to_string())), + public_key: Some(event.pubkey), + }) + } + + pub(crate) fn parent_tag(&self) -> Option { + self.position.map(|p| self.make_event_tag_from_id(p, MARKER_PARENT)) + } + + pub(crate) fn position_tags(&self) -> Vec { + let mut tags = Vec::with_capacity(2); + self.parent_tag().map(|t| tags.push(t)); + self.get_current_task() + .map(|t| { if t.pure_state() == State::Procedure { t.children.iter() .filter_map(|id| self.get_by_id(id)) .max() - .map(|t| { - Tag::from( - TagStandard::Event { - event_id: t.event.id, - relay_url: self.sender.url.as_ref().map(|url| UncheckedUrl::new(url.as_str())), - marker: Some(Marker::Custom("depends".to_string())), - public_key: Some(t.event.pubkey), - } - ) - }) - } else { - None + .map(|t| tags.push(self.make_event_tag(&t.event, MARKER_DEPENDS))); } }); + tags + } + + /// Creates a task following the current state + /// Sanitizes input + pub(crate) fn make_task(&mut self, input: &str) -> EventId { + self.make_task_with(input, self.position_tags(), true) + } + + pub(crate) fn make_task_and_enter(&mut self, input: &str, state: State) { + let id = self.make_task_with(input, self.position_tags(), false); + self.set_state_for(id, "", state); + self.move_to(Some(id)); + } + + /// Creates a task + /// Sanitizes input + pub(crate) fn make_task_with(&mut self, input: &str, tags: impl IntoIterator, set_state: bool) -> EventId { let id = self.submit( self.parse_task(input.trim()) - .add_tags(tag.into_iter()) + .add_tags(tags.into_iter()) ); - self.state.as_option().inspect(|s| self.set_state_for_with(id, s)); + if set_state { + self.state.as_option().inspect(|s| self.set_state_for_with(id, s)); + } id } @@ -730,13 +760,15 @@ impl Tasks { } pub(crate) fn add_task(&mut self, event: Event) { - self.referenced_tasks(&event, |t| { - t.children.insert(event.id); - }); if self.tasks.contains_key(&event.id) { debug!("Did not insert duplicate event {}", event.id); // TODO warn in next sdk version } else { - self.tasks.insert(event.id, Task::new(event)); + let id = event.id; + let task = Task::new(event); + task.find_refs(MARKER_PARENT).for_each(|parent| { + self.tasks.get_mut(parent).map(|t| { t.children.insert(id); }); + }); + self.tasks.insert(id, task); } } @@ -968,6 +1000,8 @@ impl<'a> Iterator for ParentIterator<'a> { #[cfg(test)] mod tasks_test { + use std::collections::HashSet; + use super::*; fn stub_tasks() -> Tasks { @@ -983,6 +1017,18 @@ mod tasks_test { }) } + #[test] + fn test_procedures() { + let mut tasks = stub_tasks(); + tasks.make_task_and_enter("proc: tags", State::Procedure); + let side = tasks.submit(build_task("side", vec![tasks.make_event_tag(&tasks.get_current_task().unwrap().event, MARKER_DEPENDS)])); + assert_eq!(tasks.get_current_task().unwrap().children, HashSet::::new()); + let subid = tasks.make_task("sub"); + assert_eq!(tasks.get_current_task().unwrap().children, HashSet::from([subid])); + let sub = tasks.get_by_id(&subid).unwrap(); + assert_eq!(sub.get_dependendees(), Vec::<&EventId>::new()); + } + #[test] fn test_tracking() { let mut tasks = stub_tasks();