feat: enable creating dependent sibling task

This commit is contained in:
xeruf 2024-08-12 23:02:50 +03:00
parent 9f84fd7ef1
commit 85b923edc8
4 changed files with 91 additions and 32 deletions

View File

@ -100,7 +100,8 @@ To stop time-tracking completely, simply move to the root of all tasks.
+ no match: create & activate task + 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) - `.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 - `/[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. Dots and slashes can be repeated to move to parent tasks.

View File

@ -4,6 +4,7 @@ use std::env::{args, var};
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader, stdin, stdout, Write}; use std::io::{BufRead, BufReader, stdin, stdout, Write};
use std::iter::once;
use std::ops::Sub; use std::ops::Sub;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
@ -20,7 +21,7 @@ use xdg::BaseDirectories;
use crate::helpers::*; use crate::helpers::*;
use crate::kinds::{KINDS, PROPERTY_COLUMNS, TRACKING_KIND}; 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}; use crate::tasks::{PropertyCollection, StateFilter, Tasks};
mod helpers; mod helpers;
@ -339,15 +340,26 @@ async fn main() {
.map(|t| t.event.id) .map(|t| t.event.id)
.collect() .collect()
); );
info!("Filtering for procedures");
} }
Some(id) => { Some(id) => {
tasks.set_state_for(id, "", State::Procedure); tasks.set_state_for(id, "", State::Procedure);
} }
}, },
Some(arg) => { Some(arg) => 'arm: {
let id = tasks.make_task(arg); if arg.chars().next() != Some('|') {
tasks.set_state_for(id, "", State::Procedure); if let Some(pos) = tasks.get_position() {
tasks.move_to(Some(id)); 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);
} }
} }

View File

@ -63,7 +63,7 @@ impl Task {
&self.event.id &self.event.id
} }
fn find_refs<'a>(&'a self, marker: &'a str) -> impl Iterator<Item=&'a EventId> { 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)) self.refs.iter().filter_map(move |(str, id)| Some(id).filter(|_| str == marker))
} }

View File

@ -19,7 +19,7 @@ use TagStandard::Hashtag;
use crate::{Events, EventSender}; use crate::{Events, EventSender};
use crate::helpers::some_non_empty; use crate::helpers::some_non_empty;
use crate::kinds::*; use crate::kinds::*;
use crate::task::{State, Task, TaskState}; use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State, Task, TaskState};
type TaskMap = HashMap<EventId, Task>; type TaskMap = HashMap<EventId, Task>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -613,7 +613,6 @@ impl Tasks {
/// Expects sanitized input /// Expects sanitized input
pub(crate) fn parse_task(&self, input: &str) -> EventBuilder { pub(crate) fn parse_task(&self, input: &str) -> EventBuilder {
let mut tags: Vec<Tag> = self.tags.iter().cloned().collect(); let mut tags: Vec<Tag> = self.tags.iter().cloned().collect();
self.position.inspect(|p| tags.push(Tag::event(*p)));
match input.split_once(": ") { match input.split_once(": ") {
None => build_task(input, tags), None => build_task(input, tags),
Some(s) => { Some(s) => {
@ -629,34 +628,65 @@ impl Tasks {
} }
} }
/// Creates a task following the current state pub(crate) fn make_event_tag_from_id(&self, id: EventId, marker: &str) -> Tag {
/// Sanitizes input Tag::from(TagStandard::Event {
pub(crate) fn make_task(&mut self, input: &str) -> EventId { event_id: id,
let tag: Option<Tag> = self.get_current_task() relay_url: self.sender.url.as_ref().map(|url| UncheckedUrl::new(url.as_str())),
.and_then(|t| { 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<Tag> {
self.position.map(|p| self.make_event_tag_from_id(p, MARKER_PARENT))
}
pub(crate) fn position_tags(&self) -> Vec<Tag> {
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 { if t.pure_state() == State::Procedure {
t.children.iter() t.children.iter()
.filter_map(|id| self.get_by_id(id)) .filter_map(|id| self.get_by_id(id))
.max() .max()
.map(|t| { .map(|t| tags.push(self.make_event_tag(&t.event, MARKER_DEPENDS)));
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
} }
}); });
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<Item=Tag>, set_state: bool) -> EventId {
let id = self.submit( let id = self.submit(
self.parse_task(input.trim()) self.parse_task(input.trim())
.add_tags(tag.into_iter()) .add_tags(tags.into_iter())
); );
if set_state {
self.state.as_option().inspect(|s| self.set_state_for_with(id, s)); self.state.as_option().inspect(|s| self.set_state_for_with(id, s));
}
id id
} }
@ -730,13 +760,15 @@ impl Tasks {
} }
pub(crate) fn add_task(&mut self, event: Event) { 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) { if self.tasks.contains_key(&event.id) {
debug!("Did not insert duplicate event {}", event.id); // TODO warn in next sdk version debug!("Did not insert duplicate event {}", event.id); // TODO warn in next sdk version
} else { } 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)] #[cfg(test)]
mod tasks_test { mod tasks_test {
use std::collections::HashSet;
use super::*; use super::*;
fn stub_tasks() -> Tasks { 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::<EventId>::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] #[test]
fn test_tracking() { fn test_tracking() {
let mut tasks = stub_tasks(); let mut tasks = stub_tasks();