refactor: refactor into tasks struct

This commit is contained in:
xeruf 2024-07-19 01:15:11 +03:00
parent 6362b62a86
commit 0b1c6fa45c
2 changed files with 227 additions and 103 deletions

View File

@ -1,9 +1,11 @@
mod tasks;
use crate::tasks::Tasks;
use crate::State::*; use crate::State::*;
use nostr_sdk::async_utility::futures_util::TryFutureExt; use nostr_sdk::async_utility::futures_util::TryFutureExt;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap;
use std::env::args; use std::env::args;
use std::fmt; use std::fmt;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
@ -102,43 +104,17 @@ fn make_event(kind: Kind, text: &str, tags: &[Tag]) -> Event {
.unwrap() .unwrap()
} }
type TaskMap = HashMap<EventId, Task>;
fn add_task(tasks: &mut TaskMap, event: Event) -> Option<Task> {
tasks.insert(event.id, Task::new(event))
}
async fn repl() { async fn repl() {
let mut tasks: TaskMap = HashMap::new(); let mut tasks: Tasks = Default::default();
for argument in args().skip(1) { for argument in args().skip(1) {
add_task( tasks.add_task(make_task(&argument, &[Tag::Hashtag("arg".to_string())]));
&mut tasks,
make_task(&argument, &[Tag::Hashtag("arg".to_string())]),
);
} }
let mut properties: Vec<String> = vec!["id".into(), "name".into(), "state".into()];
let mut position: Option<EventId> = None;
let print_tasks = |tasks: Vec<&Task>, properties: &Vec<String>| {
println!("{}", properties.join(" "));
for task in tasks {
println!("{}", properties.iter().map(|p| task.get(p).unwrap_or(String::new())).collect::<Vec<String>>().join(" "));
}
println!(); println!();
}; tasks.print_current_tasks();
println!();
print_tasks(tasks.values().collect(), &properties);
loop { loop {
let mut prompt = String::with_capacity(64); print!(" {}> ", tasks.taskpath(tasks.get_position()));
let mut pos = position;
while pos.is_some() {
let id = pos.unwrap();
let task = tasks.get(&id);
prompt = task.map_or(id.to_string(), |t| t.event.content.clone()) + " " + &prompt;
pos = task.and_then(|t| t.parent_id());
}
print!(" {}> ", prompt);
stdout().flush().unwrap(); stdout().flush().unwrap();
match stdin().lines().next() { match stdin().lines().next() {
Some(Ok(input)) => { Some(Ok(input)) => {
@ -149,99 +125,56 @@ async fn repl() {
Some(':') => match input[1..2].parse::<usize>() { Some(':') => match input[1..2].parse::<usize>() {
Ok(index) => { Ok(index) => {
properties.insert(index, input[2..].to_string()); tasks.properties.insert(index, input[2..].to_string());
} }
Err(_) => { Err(_) => {
let prop = &input[1..]; let prop = &input[1..];
let pos = properties.iter().position(|s| s == &prop); let pos = tasks.properties.iter().position(|s| s == &prop);
match pos { match pos {
None => { None => {
properties.push(prop.to_string()); tasks.properties.push(prop.to_string());
} }
Some(i) => { Some(i) => {
properties.remove(i); tasks.properties.remove(i);
} }
} }
} }
}, },
Some('>') | Some('<') => { Some('>') | Some('<') => {
position.inspect(|e| { tasks.update_state(&input[1..], |_| {
tasks.get_mut(e).map(|t| t.props.push(make_event( Some(if op.unwrap() == '<' { Closed } else { Done })
(if op.unwrap() == '<' { Closed } else { Done }).kind(), &input[1..], &[Tag::event(e.clone())])));
}); });
position = position tasks.move_up()
.and_then(|id| tasks.get_mut(&id))
.and_then(|t| t.parent_id())
} }
Some('.') => { Some('.') => {
if input.len() > 1 {
position.and_then(|p| tasks.get_mut(&p))
.map(|t| {
if t.state().map(|s| s.state) == Some(Active) {
t.update_state(Open, "");
}
});
}
let mut dots = 1; let mut dots = 1;
let mut pos = tasks.get_position();
for _ in iter.take_while(|c| c == &'.') { for _ in iter.take_while(|c| c == &'.') {
dots += 1; dots += 1;
position = position pos = tasks.parent(pos);
.and_then(|id| tasks.get(&id))
.and_then(|t| t.parent_id());
} }
let slice = &input[dots..]; let slice = &input[dots..];
if !slice.is_empty() { if !slice.is_empty() {
position = EventId::parse(slice).ok().or_else(|| { pos = EventId::parse(slice).ok().or_else(|| {
let task = make_task(slice, &[]); tasks.move_to(pos);
let task = tasks.make_task(slice);
let ret = Some(task.id); let ret = Some(task.id);
add_task(&mut tasks, task); tasks.add_task(task);
ret ret
}).inspect(|id| { });
tasks.get_mut(id).map(|t| tasks.move_to(pos);
if t.state().map_or(Open, |s| s.state) == Open {
t.update_state(Active, "")
}
);
})
} }
tasks.move_to(pos);
} }
_ => { _ => {
let mut tags: Vec<Tag> = Vec::new(); tasks.add_task(tasks.make_task(&input));
position.inspect(|p| tags.push(Tag::event(*p)));
let event = match input.split_once(": ") {
None => make_task(&input, &tags),
Some(s) => {
tags.append(
&mut s.1.split(" ")
.map(|t| Tag::Hashtag(t.to_string()))
.collect());
make_task(s.0, &tags)
}
};
for tag in event.tags.iter() {
match tag {
Tag::Event { event_id, .. } => {
tasks
.get_mut(event_id)
.map(|t| t.children.push(event.id));
}
_ => {}
}
}
let _ = add_task(&mut tasks, event);
} }
} }
let tasks: Vec<&Task> = tasks.print_current_tasks();
position.map_or(tasks.values().collect(),
|p| {
tasks.get(&p)
.map_or(Vec::new(), |t| t.children.iter().filter_map(|id| tasks.get(id)).collect())
});
print_tasks(tasks, &properties);
} }
Some(Err(e)) => eprintln!("{}", e), Some(Err(e)) => eprintln!("{}", e),
None => break, None => break,
@ -252,11 +185,15 @@ async fn repl() {
println!("Submitting created events"); println!("Submitting created events");
let _ = CLIENT let _ = CLIENT
.batch_event( .batch_event(
tasks.into_values().flat_map(|mut t| { tasks
.tasks
.into_values()
.flat_map(|t| {
let mut ev = t.props; let mut ev = t.props;
ev.push(t.event); ev.push(t.event);
ev ev
}).collect(), })
.collect(),
RelaySendOptions::new().skip_send_confirmation(true), RelaySendOptions::new().skip_send_confirmation(true),
) )
.await; .await;
@ -265,7 +202,7 @@ async fn repl() {
struct Task { struct Task {
event: Event, event: Event,
children: Vec<EventId>, children: Vec<EventId>,
props: Vec<Event> props: Vec<Event>,
} }
impl Task { impl Task {
fn new(event: Event) -> Task { fn new(event: Event) -> Task {
@ -304,8 +241,13 @@ impl Task {
1632 => Some(Closed), 1632 => Some(Closed),
1633 => Some(Active), 1633 => Some(Active),
_ => None, _ => None,
}.map(|s| TaskState { }
name: if event.content.is_empty() { None } else { Some(event.content.clone()) }, .map(|s| TaskState {
name: if event.content.is_empty() {
None
} else {
Some(event.content.clone())
},
state: s, state: s,
time: event.created_at.clone(), time: event.created_at.clone(),
}) })
@ -316,6 +258,10 @@ impl Task {
self.states().max_by_key(|t| t.time) self.states().max_by_key(|t| t.time)
} }
fn pure_state(&self) -> State {
self.state().map_or(Open, |s| s.state)
}
fn default_state(&self) -> TaskState { fn default_state(&self) -> TaskState {
TaskState { TaskState {
name: None, name: None,
@ -325,7 +271,11 @@ impl Task {
} }
fn update_state(&mut self, state: State, comment: &str) { fn update_state(&mut self, state: State, comment: &str) {
self.props.push(make_event(state.kind(), comment, &[Tag::event(self.event.id)])) self.props.push(make_event(
state.kind(),
comment,
&[Tag::event(self.event.id)],
))
} }
fn get(&self, property: &str) -> Option<String> { fn get(&self, property: &str) -> Option<String> {
@ -343,7 +293,7 @@ impl Task {
_ => { _ => {
eprintln!("Unknown column {}", property); eprintln!("Unknown column {}", property);
None None
}, }
} }
} }
} }
@ -355,7 +305,14 @@ struct TaskState {
} }
impl Display for TaskState { impl Display for TaskState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.state, self.name.as_ref().map_or(String::new(), |s| format!(": {}", s))) write!(
f,
"{}{}",
self.state,
self.name
.as_ref()
.map_or(String::new(), |s| format!(": {}", s))
)
} }
} }

167
src/tasks.rs Normal file
View File

@ -0,0 +1,167 @@
use crate::{make_event, make_task, State, Task};
use nostr_sdk::{Event, EventId, Tag};
use std::collections::HashMap;
type TaskMap = HashMap<EventId, Task>;
pub(crate) struct Tasks {
pub(crate) tasks: TaskMap,
pub(crate) properties: Vec<String>,
position: Option<EventId>,
}
impl Default for Tasks {
fn default() -> Self {
Tasks {
tasks: Default::default(),
properties: vec!["id".into(), "name".into(), "state".into()],
position: None,
}
}
}
impl Tasks {
pub(crate) fn get_position(&self) -> Option<EventId> {
self.position
}
pub(crate) fn make_task(&self, input: &str) -> Event {
let mut tags: Vec<Tag> = Vec::new();
self.position.inspect(|p| tags.push(Tag::event(*p)));
return match input.split_once(": ") {
None => make_task(&input, &tags),
Some(s) => {
tags.append(
&mut s
.1
.split(" ")
.map(|t| Tag::Hashtag(t.to_string()))
.collect(),
);
make_task(s.0, &tags)
}
};
}
pub(crate) fn add_task(&mut self, event: Event) {
for tag in event.tags.iter() {
match tag {
Tag::Event { event_id, .. } => {
self.tasks
.get_mut(event_id)
.map(|t| t.children.push(event.id));
}
_ => {}
}
}
self.tasks.insert(event.id, Task::new(event));
}
pub(crate) fn current_tasks(&self) -> Vec<&Task> {
self.position.map_or(self.tasks.values().collect(), |p| {
self.tasks.get(&p).map_or(Vec::new(), |t| {
t.children
.iter()
.filter_map(|id| self.tasks.get(id))
.collect()
})
})
}
pub(crate) fn print_current_tasks(&self) {
println!("{}", self.properties.join(" "));
for task in self.current_tasks() {
println!(
"{}",
self.properties
.iter()
.map(|p| task.get(p).unwrap_or(String::new()))
.collect::<Vec<String>>()
.join(" ")
);
}
println!();
}
pub(crate) fn move_up(&mut self) {
self.move_to(
self.position
.and_then(|id| self.tasks.get(&id))
.and_then(|t| t.parent_id()),
)
}
pub(crate) fn move_to(&mut self, id: Option<EventId>) {
if id == self.position {
return;
}
self.update_state("", |s| {
if s.pure_state() == State::Active {
Some(State::Open)
} else {
None
}
});
self.position = id;
self.update_state("", |s| {
if s.pure_state() == State::Open {
Some(State::Active)
} else {
None
}
});
}
pub(crate) fn parent(&self, id: Option<EventId>) -> Option<EventId> {
id.and_then(|id| self.tasks.get(&id))
.and_then(|t| t.parent_id())
}
pub(crate) fn taskpath(&self, id: Option<EventId>) -> String {
self.traverse_up_from(id)
.map(|t| t.event.content.clone())
.fold(String::new(), |acc, val| format!("{} {}", val, acc))
}
pub(crate) fn traverse_up_from(&self, id: Option<EventId>) -> ParentIterator {
ParentIterator {
tasks: &self.tasks,
current: id,
}
}
pub(crate) fn update_state_for<F>(&mut self, id: &EventId, comment: &str, f: F)
where
F: FnOnce(&Task) -> Option<State>,
{
self.tasks.get_mut(id).map(|t| {
f(t).map(|s| {
t.props
.push(make_event(s.kind(), comment, &[Tag::event(id.clone())]))
})
});
}
pub(crate) fn update_state<F>(&mut self, comment: &str, f: F)
where
F: FnOnce(&Task) -> Option<State>,
{
self.position.inspect(|id| {
self.update_state_for(id, comment, f);
});
}
}
struct ParentIterator<'a> {
tasks: &'a TaskMap,
current: Option<EventId>,
}
impl<'a> Iterator for ParentIterator<'a> {
type Item = &'a Task;
fn next(&mut self) -> Option<Self::Item> {
self.current.and_then(|id| self.tasks.get(&id)).map(|t| {
self.current = t.parent_id();
t
})
}
}