forked from janek/mostr
refactor: refactor into tasks struct
This commit is contained in:
parent
6362b62a86
commit
0b1c6fa45c
157
src/main.rs
157
src/main.rs
|
@ -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))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue