feat: enable creating dependent sibling task
This commit is contained in:
parent
9f84fd7ef1
commit
85b923edc8
|
@ -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.
|
||||||
|
|
||||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
94
src/tasks.rs
94
src/tasks.rs
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue