feat: add notes as stateless tasks
This commit is contained in:
parent
2255abc1b8
commit
eaeeebca7b
4 changed files with 79 additions and 57 deletions
37
src/kinds.rs
37
src/kinds.rs
|
@ -1,6 +1,7 @@
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::info;
|
use log::info;
|
||||||
use nostr_sdk::{Alphabet, EventBuilder, EventId, Kind, Tag, TagStandard};
|
use nostr_sdk::{Alphabet, EventBuilder, EventId, Kind, Tag, TagStandard};
|
||||||
|
use nostr_sdk::TagStandard::Hashtag;
|
||||||
|
|
||||||
pub const METADATA_KIND: u16 = 0;
|
pub const METADATA_KIND: u16 = 0;
|
||||||
pub const NOTE_KIND: u16 = 1;
|
pub const NOTE_KIND: u16 = 1;
|
||||||
|
@ -53,9 +54,39 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build_task(name: &str, tags: Vec<Tag>) -> EventBuilder {
|
/// Build a task with informational output and optional labeled kind
|
||||||
info!("Created task \"{name}\" with tags [{}]", tags.iter().map(|tag| format_tag(tag)).join(", "));
|
pub(crate) fn build_task(name: &str, tags: Vec<Tag>, kind: Option<(&str, Kind)>) -> EventBuilder {
|
||||||
EventBuilder::new(Kind::from(TASK_KIND), name, tags)
|
info!("Created {}task \"{name}\" with tags [{}]",
|
||||||
|
kind.map(|k| k.0).unwrap_or_default(),
|
||||||
|
tags.iter().map(|tag| format_tag(tag)).join(", "));
|
||||||
|
EventBuilder::new(kind.map(|k| k.1).unwrap_or(Kind::from(TASK_KIND)), name, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_prop(
|
||||||
|
kind: Kind,
|
||||||
|
comment: &str,
|
||||||
|
id: EventId,
|
||||||
|
) -> EventBuilder {
|
||||||
|
EventBuilder::new(
|
||||||
|
kind,
|
||||||
|
comment,
|
||||||
|
vec![Tag::event(id)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expects sanitized input
|
||||||
|
pub(crate) fn extract_tags(input: &str) -> (&str, Vec<Tag>) {
|
||||||
|
match input.split_once(": ") {
|
||||||
|
None => (input, vec![]),
|
||||||
|
Some(s) => {
|
||||||
|
let tags = s
|
||||||
|
.1
|
||||||
|
.split_ascii_whitespace()
|
||||||
|
.map(|t| Hashtag(t.to_string()).into())
|
||||||
|
.collect();
|
||||||
|
(s.0, tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_tag(tag: &Tag) -> String {
|
fn format_tag(tag: &Tag) -> String {
|
||||||
|
|
|
@ -405,8 +405,7 @@ async fn main() {
|
||||||
tasks.move_up();
|
tasks.move_up();
|
||||||
tasks.make_task_with(
|
tasks.make_task_with(
|
||||||
arg,
|
arg,
|
||||||
once(tasks.make_event_tag_from_id(pos, MARKER_DEPENDS))
|
once(tasks.make_event_tag_from_id(pos, MARKER_DEPENDS)),
|
||||||
.chain(tasks.parent_tag()),
|
|
||||||
true);
|
true);
|
||||||
break 'arm;
|
break 'arm;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use log::{debug, error, info, trace, warn};
|
||||||
use nostr_sdk::{Event, EventId, Kind, Tag, TagStandard, Timestamp};
|
use nostr_sdk::{Event, EventId, Kind, Tag, TagStandard, Timestamp};
|
||||||
|
|
||||||
use crate::helpers::{local_datetimestamp, some_non_empty};
|
use crate::helpers::{local_datetimestamp, some_non_empty};
|
||||||
use crate::kinds::{is_hashtag, PROCEDURE_KIND};
|
use crate::kinds::{is_hashtag, PROCEDURE_KIND, TASK_KIND};
|
||||||
|
|
||||||
pub static MARKER_PARENT: &str = "parent";
|
pub static MARKER_PARENT: &str = "parent";
|
||||||
pub static MARKER_DEPENDS: &str = "depends";
|
pub static MARKER_DEPENDS: &str = "depends";
|
||||||
|
@ -95,6 +95,11 @@ impl Task {
|
||||||
self.description_events().map(|e| &e.content)
|
self.description_events().map(|e| &e.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_task(&self) -> bool {
|
||||||
|
self.event.kind.as_u16() == TASK_KIND ||
|
||||||
|
self.states().next().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
fn states(&self) -> impl Iterator<Item=TaskState> + '_ {
|
fn states(&self) -> impl Iterator<Item=TaskState> + '_ {
|
||||||
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| TaskState {
|
||||||
|
|
89
src/tasks.rs
89
src/tasks.rs
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::{BTreeSet, HashMap, VecDeque};
|
use std::collections::{BTreeSet, HashMap, VecDeque};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::io::{Error, stdout, Write};
|
use std::io::{Error, stdout, Write};
|
||||||
use std::iter::once;
|
use std::iter::{empty, once};
|
||||||
use std::ops::{Div, Rem};
|
use std::ops::{Div, Rem};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
@ -333,16 +333,18 @@ impl Tasks {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes the given function with each task referenced by this event.
|
/// Executes the given function with each task referenced by this event without marker.
|
||||||
/// Returns true if any task was found.
|
/// Returns true if any task was found.
|
||||||
pub(crate) fn referenced_tasks<F: Fn(&mut Task)>(&mut self, event: &Event, f: F) -> bool {
|
pub(crate) fn referenced_tasks<F: Fn(&mut Task)>(&mut self, event: &Event, f: F) -> bool {
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for tag in event.tags.iter() {
|
for tag in event.tags.iter() {
|
||||||
if let Some(TagStandard::Event { event_id, .. }) = tag.as_standardized() {
|
if let Some(TagStandard::Event { event_id, marker, .. }) = tag.as_standardized() {
|
||||||
self.tasks.get_mut(event_id).map(|t| {
|
if marker.is_none() {
|
||||||
found = true;
|
self.tasks.get_mut(event_id).map(|t| {
|
||||||
f(t)
|
found = true;
|
||||||
});
|
f(t)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
found
|
found
|
||||||
|
@ -616,24 +618,6 @@ impl Tasks {
|
||||||
|
|
||||||
// Updates
|
// Updates
|
||||||
|
|
||||||
/// Expects sanitized input
|
|
||||||
pub(crate) fn parse_task(&self, input: &str) -> EventBuilder {
|
|
||||||
let mut tags: Vec<Tag> = self.tags.iter().cloned().collect();
|
|
||||||
match input.split_once(": ") {
|
|
||||||
None => build_task(input, tags),
|
|
||||||
Some(s) => {
|
|
||||||
tags.append(
|
|
||||||
&mut s
|
|
||||||
.1
|
|
||||||
.split_ascii_whitespace()
|
|
||||||
.map(|t| Hashtag(t.to_string()).into())
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
build_task(s.0, tags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn make_event_tag_from_id(&self, id: EventId, marker: &str) -> Tag {
|
pub(crate) fn make_event_tag_from_id(&self, id: EventId, marker: &str) -> Tag {
|
||||||
Tag::from(TagStandard::Event {
|
Tag::from(TagStandard::Event {
|
||||||
event_id: id,
|
event_id: id,
|
||||||
|
@ -674,20 +658,23 @@ impl Tasks {
|
||||||
/// Creates a task following the current state
|
/// Creates a task following the current state
|
||||||
/// Sanitizes input
|
/// Sanitizes input
|
||||||
pub(crate) fn make_task(&mut self, input: &str) -> EventId {
|
pub(crate) fn make_task(&mut self, input: &str) -> EventId {
|
||||||
self.make_task_with(input, self.position_tags(), true)
|
self.make_task_with(input, empty(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn make_task_and_enter(&mut self, input: &str, state: State) {
|
pub(crate) fn make_task_and_enter(&mut self, input: &str, state: State) {
|
||||||
let id = self.make_task_with(input, self.position_tags(), false);
|
let id = self.make_task_with(input, empty(), false);
|
||||||
self.set_state_for(id, "", state);
|
self.set_state_for(id, "", state);
|
||||||
self.move_to(Some(id));
|
self.move_to(Some(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a task
|
/// Creates a task with tags from filter and position
|
||||||
/// Sanitizes input
|
/// Sanitizes input
|
||||||
pub(crate) fn make_task_with(&mut self, input: &str, tags: impl IntoIterator<Item=Tag>, set_state: bool) -> EventId {
|
pub(crate) fn make_task_with(&mut self, input: &str, tags: impl IntoIterator<Item=Tag>, set_state: bool) -> EventId {
|
||||||
|
let (input, input_tags) = extract_tags(input.trim());
|
||||||
let id = self.submit(
|
let id = self.submit(
|
||||||
self.parse_task(input.trim())
|
build_task(input, input_tags, None)
|
||||||
|
.add_tags(self.tags.iter().cloned())
|
||||||
|
.add_tags(self.position_tags())
|
||||||
.add_tags(tags.into_iter())
|
.add_tags(tags.into_iter())
|
||||||
);
|
);
|
||||||
if set_state {
|
if set_state {
|
||||||
|
@ -696,19 +683,6 @@ impl Tasks {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build_prop(
|
|
||||||
&mut self,
|
|
||||||
kind: Kind,
|
|
||||||
comment: &str,
|
|
||||||
id: EventId,
|
|
||||||
) -> EventBuilder {
|
|
||||||
EventBuilder::new(
|
|
||||||
kind,
|
|
||||||
comment,
|
|
||||||
vec![Tag::event(id)],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_task_title(&self, id: &EventId) -> String {
|
pub(crate) fn get_task_title(&self, id: &EventId) -> String {
|
||||||
self.tasks.get(id).map_or(id.to_string(), |t| t.get_title())
|
self.tasks.get(id).map_or(id.to_string(), |t| t.get_title())
|
||||||
}
|
}
|
||||||
|
@ -784,7 +758,7 @@ impl Tasks {
|
||||||
Ok(metadata) => { self.users.insert(event.pubkey, metadata); }
|
Ok(metadata) => { self.users.insert(event.pubkey, metadata); }
|
||||||
Err(e) => warn!("Cannot parse metadata: {} from {:?}", e, event)
|
Err(e) => warn!("Cannot parse metadata: {} from {:?}", e, event)
|
||||||
}
|
}
|
||||||
_ => self.add_prop(&event),
|
_ => self.add_prop(event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -801,10 +775,17 @@ impl Tasks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_prop(&mut self, event: &Event) {
|
fn add_prop(&mut self, event: Event) {
|
||||||
self.referenced_tasks(&event, |t| {
|
let found = self.referenced_tasks(&event, |t| {
|
||||||
t.props.insert(event.clone());
|
t.props.insert(event.clone());
|
||||||
});
|
});
|
||||||
|
if !found {
|
||||||
|
if event.kind.as_u16() == NOTE_KIND {
|
||||||
|
self.add_task(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
warn!("Unknown event {:?}", event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_own_history(&mut self) -> Option<&mut BTreeSet<Event>> {
|
fn get_own_history(&mut self) -> Option<&mut BTreeSet<Event>> {
|
||||||
|
@ -836,7 +817,7 @@ impl Tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_state_for(&mut self, id: EventId, comment: &str, state: State) -> EventId {
|
pub(crate) fn set_state_for(&mut self, id: EventId, comment: &str, state: State) -> EventId {
|
||||||
let prop = self.build_prop(
|
let prop = build_prop(
|
||||||
state.into(),
|
state.into(),
|
||||||
comment,
|
comment,
|
||||||
id,
|
id,
|
||||||
|
@ -851,13 +832,19 @@ impl Tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn make_note(&mut self, note: &str) {
|
pub(crate) fn make_note(&mut self, note: &str) {
|
||||||
match self.position {
|
if let Some(id) = self.position {
|
||||||
None => warn!("Cannot add note \"{}\" without active task", note),
|
if self.get_by_id(&id).is_some_and(|t| t.is_task()) {
|
||||||
Some(id) => {
|
let prop = build_prop(Kind::TextNote, note.trim(), id);
|
||||||
let prop = self.build_prop(Kind::TextNote, note, id);
|
|
||||||
self.submit(prop);
|
self.submit(prop);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let (input, tags) = extract_tags(note.trim());
|
||||||
|
self.submit(
|
||||||
|
build_task(input, tags, Some(("stateless ", Kind::TextNote)))
|
||||||
|
.add_tags(self.parent_tag())
|
||||||
|
.add_tags(self.tags.iter().cloned())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
@ -1099,7 +1086,7 @@ mod tasks_test {
|
||||||
fn test_procedures() {
|
fn test_procedures() {
|
||||||
let mut tasks = stub_tasks();
|
let mut tasks = stub_tasks();
|
||||||
tasks.make_task_and_enter("proc: tags", State::Procedure);
|
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)]));
|
let side = tasks.submit(build_task("side", vec![tasks.make_event_tag(&tasks.get_current_task().unwrap().event, MARKER_DEPENDS)], None));
|
||||||
assert_eq!(tasks.get_current_task().unwrap().children, HashSet::<EventId>::new());
|
assert_eq!(tasks.get_current_task().unwrap().children, HashSet::<EventId>::new());
|
||||||
let sub_id = tasks.make_task("sub");
|
let sub_id = tasks.make_task("sub");
|
||||||
assert_eq!(tasks.get_current_task().unwrap().children, HashSet::from([sub_id]));
|
assert_eq!(tasks.get_current_task().unwrap().children, HashSet::from([sub_id]));
|
||||||
|
|
Loading…
Add table
Reference in a new issue