forked from janek/mostr
1
0
Fork 0

refactor: reformat tasks file

This commit is contained in:
xeruf 2024-11-18 14:43:49 +01:00
parent 2400f7c45b
commit adcd35967f
2 changed files with 125 additions and 68 deletions

View File

@ -62,7 +62,7 @@ pub struct EventTag {
pub(crate) fn match_event_tag(tag: &Tag) -> Option<EventTag> { pub(crate) fn match_event_tag(tag: &Tag) -> Option<EventTag> {
let mut vec = tag.as_slice().into_iter(); let mut vec = tag.as_slice().into_iter();
if vec.next() == Some(&"e".to_string()) { if vec.next() == Some(&"e".to_string()) {
if let Some(id) = vec.next().and_then(|v| EventId::parse(v).ok()) { if let Some(id) = vec.next().and_then(|v| EventId::parse(v).ok()) {
vec.next(); vec.next();
return Some(EventTag { id, marker: vec.next().cloned() }); return Some(EventTag { id, marker: vec.next().cloned() });
} }
@ -107,15 +107,15 @@ pub(crate) fn extract_tags(input: &str) -> (String, Vec<Tag>) {
if s.starts_with('*') { if s.starts_with('*') {
if s.len() == 1 { if s.len() == 1 {
prio = Some(HIGH_PRIO); prio = Some(HIGH_PRIO);
return false return false;
} }
return match s[1..].parse::<Prio>() { return match s[1..].parse::<Prio>() {
Ok(num) => { Ok(num) => {
prio = Some(num * (if s.len() > 2 { 1 } else { 10 })); prio = Some(num * (if s.len() > 2 { 1 } else { 10 }));
false false
}, }
_ => true, _ => true,
} };
} }
true true
}).collect_vec(); }).collect_vec();
@ -136,9 +136,9 @@ pub fn to_hashtag(tag: &str) -> Tag {
fn format_tag(tag: &Tag) -> String { fn format_tag(tag: &Tag) -> String {
if let Some(et) = match_event_tag(tag) { if let Some(et) = match_event_tag(tag) {
return format!("{}: {:.8}", return format!("{}: {:.8}",
et.marker.as_ref().map(|m| m.to_string()).unwrap_or(MARKER_PARENT.to_string()), et.marker.as_ref().map(|m| m.to_string()).unwrap_or(MARKER_PARENT.to_string()),
et.id) et.id);
} }
match tag.as_standardized() { match tag.as_standardized() {
Some(TagStandard::PublicKey { Some(TagStandard::PublicKey {

View File

@ -197,7 +197,10 @@ impl TasksRelay {
} }
} }
if elements > 0 { if elements > 0 {
info!("Reprocessed {elements} updates with {issues} issues{}", self.sender.url.clone().map(|url| format!(" from {url}")).unwrap_or_default()); info!(
"Reprocessed {elements} updates with {issues} issues{}",
self.sender.url.as_ref().map(|url| format!(" from {url}")).unwrap_or_default()
);
} }
} }
@ -269,14 +272,24 @@ impl TasksRelay {
if new != last { if new != last {
// TODO omit intervals <2min - but I think I need threeway for that // TODO omit intervals <2min - but I think I need threeway for that
// TODO alternate color with grey between days // TODO alternate color with grey between days
full.push(format!("{} {}", format_timestamp_local(&event.created_at), new.as_ref().unwrap_or(&"---".to_string()))); full.push(format!(
"{} {}",
format_timestamp_local(&event.created_at),
new.as_ref().unwrap_or(&"---".to_string())
));
last = new; last = new;
} }
} }
// TODO show history for active tags // TODO show history for active tags
("Your Time-Tracking History:".to_string(), Box::from(full.into_iter())) (
"Your Time-Tracking History:".to_string(),
Box::from(full.into_iter()),
)
} else { } else {
("You have nothing time-tracked yet".to_string(), Box::from(empty())) (
"You have nothing time-tracked yet".to_string(),
Box::from(empty()),
)
} }
} }
Some(id) => { Some(id) => {
@ -289,15 +302,21 @@ impl TasksRelay {
while let Some(((start, _), (end, _))) = iter.next() { while let Some(((start, _), (end, _))) = iter.next() {
// Filter out intervals <2 mins // Filter out intervals <2 mins
if start.as_u64() + 120 < end.as_u64() { if start.as_u64() + 120 < end.as_u64() {
vec.push(format!("{} - {} by {}", vec.push(format!(
format_timestamp_local(start), "{} - {} by {}",
format_timestamp_relative_to(end, start), format_timestamp_local(start),
self.get_username(key))) format_timestamp_relative_to(end, start),
self.get_username(key)
))
} }
} }
iter.into_buffer() iter.into_buffer().for_each(|(stamp, _)| {
.for_each(|(stamp, _)| vec.push(format!(
vec.push(format!("{} started by {}", format_timestamp_local(stamp), self.get_username(key)))); "{} started by {}",
format_timestamp_local(stamp),
self.get_username(key)
))
});
vec vec
}).sorted_unstable(); // TODO sorting depends on timestamp format - needed to interleave different people }).sorted_unstable(); // TODO sorting depends on timestamp format - needed to interleave different people
( (
@ -321,7 +340,9 @@ impl TasksRelay {
let children = ChildIterator::from(&self, id).get_all(); let children = ChildIterator::from(&self, id).get_all();
for user in self.history.values() { for user in self.history.values() {
total += Durations::from(user.values(), &children).sum::<Duration>().as_secs(); total += Durations::from(user.values(), &children)
.sum::<Duration>()
.as_secs();
} }
total total
} }
@ -340,13 +361,7 @@ impl TasksRelay {
sum += prog; sum += prog;
count += 1; count += 1;
} }
Some( Some(if count > 0 { sum / (count as f32) } else { 0.0 })
if count > 0 {
sum / (count as f32)
} else {
0.0
}
)
} }
}) })
} }
@ -421,7 +436,8 @@ impl TasksRelay {
} }
} }
if new_depth > 0 { if new_depth > 0 {
let mut children = self.resolve_tasks_rec(self.tasks.children_of(&task), sparse, new_depth); let mut children =
self.resolve_tasks_rec(self.tasks.children_of(&task), sparse, new_depth);
if !children.is_empty() { if !children.is_empty() {
if !sparse { if !sparse {
children.push(task); children.push(task);
@ -430,7 +446,8 @@ impl TasksRelay {
} }
} }
return if self.filter(task) { vec![task] } else { vec![] }; return if self.filter(task) { vec![task] } else { vec![] };
}).collect_vec() })
.collect_vec()
} }
/// Executes the given function with each task referenced by this event with no or property marker. /// Executes the given function with each task referenced by this event with no or property marker.
@ -477,7 +494,8 @@ impl TasksRelay {
sparse: bool, sparse: bool,
) -> Vec<&Task> { ) -> Vec<&Task> {
let roots = self.tasks.children_for(position); let roots = self.tasks.children_for(position);
let mut current = self.resolve_tasks_rec(roots, sparse, self.search_depth + self.view_depth); let mut current =
self.resolve_tasks_rec(roots, sparse, self.search_depth + self.view_depth);
if current.is_empty() { if current.is_empty() {
if !self.tags.is_empty() { if !self.tags.is_empty() {
let mut children = self.tasks.children_for(position).peekable(); let mut children = self.tasks.children_for(position).peekable();
@ -512,6 +530,7 @@ impl TasksRelay {
} }
// TODO this is a relict for tests // TODO this is a relict for tests
#[deprecated]
fn visible_tasks(&self) -> Vec<&Task> { fn visible_tasks(&self) -> Vec<&Task> {
if self.search_depth == 0 { if self.search_depth == 0 {
return vec![]; return vec![];
@ -546,8 +565,15 @@ impl TasksRelay {
} }
} }
"state" => { "state" => {
if let Some(task) = task.get_dependendees().iter().filter_map(|id| self.get_by_id(id)).find(|t| t.pure_state().is_open()) { if let Some(task) = task
return format!("Blocked by \"{}\"", task.get_title()).bright_red().to_string(); .get_dependendees()
.iter()
.filter_map(|id| self.get_by_id(id))
.find(|t| t.pure_state().is_open())
{
return format!("Blocked by \"{}\"", task.get_title())
.bright_red()
.to_string();
} }
let state = task.pure_state(); let state = task.pure_state();
if state.is_open() && progress.is_some_and(|p| p > 0.1) { if state.is_open() && progress.is_some_and(|p| p > 0.1) {
@ -559,11 +585,16 @@ impl TasksRelay {
"progress" => prog_string.clone(), "progress" => prog_string.clone(),
"author" | "creator" => format!("{:.6}", self.get_username(&task.event.pubkey)), // FIXME temporary until proper column alignment "author" | "creator" => format!("{:.6}", self.get_username(&task.event.pubkey)), // FIXME temporary until proper column alignment
"prio" => self.traverse_up_from(Some(*task.get_id())).find_map(Task::priority_raw).map(|p| p.to_string()).unwrap_or_else(|| "prio" => self
if self.priority.is_some() { .traverse_up_from(Some(task.event.id))
DEFAULT_PRIO.to_string().dimmed().to_string() .find_map(Task::priority_raw)
} else { .map(|p| p.to_string())
"".to_string() .unwrap_or_else(|| {
if self.priority.is_some() {
DEFAULT_PRIO.to_string().dimmed().to_string()
} else {
"".to_string()
}
}), }),
"path" => self.get_task_path(Some(task.event.id)), "path" => self.get_task_path(Some(task.event.id)),
"rpath" => self.relative_path(task.event.id), "rpath" => self.relative_path(task.event.id),
@ -602,9 +633,11 @@ impl TasksRelay {
false false
} }
}; };
self.sender.submit( self.sender.submit(EventBuilder::new(
EventBuilder::new(Kind::Bookmarks, "mostr pins", Kind::Bookmarks,
self.bookmarks.iter().map(|id| Tag::event(*id))))?; "mostr pins",
self.bookmarks.iter().map(|id| Tag::event(*id)),
))?;
Ok(added) Ok(added)
} }
@ -664,7 +697,10 @@ impl TasksRelay {
} }
pub(crate) fn print_hashtags(&self) { pub(crate) fn print_hashtags(&self) {
println!("Hashtags of all known tasks:\n{}", self.all_hashtags().join(" ").italic()); println!(
"Hashtags of all known tasks:\n{}",
self.all_hashtags().join(" ").italic()
);
} }
/// Returns true if tags have been updated, false if it printed something /// Returns true if tags have been updated, false if it printed something
@ -698,7 +734,9 @@ impl TasksRelay {
pub(crate) fn remove_tag(&mut self, tag: &str) { pub(crate) fn remove_tag(&mut self, tag: &str) {
self.view.clear(); self.view.clear();
let len = self.tags.len(); let len = self.tags.len();
self.tags.retain(|t| !t.content().is_some_and(|value| value.to_string().starts_with(tag))); self.tags.retain(|t| {
!t.content().is_some_and(|value| value.to_string().starts_with(tag))
});
if self.tags.len() < len { if self.tags.len() < len {
info!("Removed tag filters starting with {tag}"); info!("Removed tag filters starting with {tag}");
} else { } else {
@ -762,7 +800,8 @@ impl TasksRelay {
for task in self.tasks.values() { for task in self.tasks.values() {
if task.get_filter_title().to_ascii_lowercase() == lowercase_arg && if task.get_filter_title().to_ascii_lowercase() == lowercase_arg &&
// exclude closed tasks and their subtasks // exclude closed tasks and their subtasks
!self.traverse_up_from(Some(*task.get_id())).any(|t| !t.pure_state().is_open()) { !self.traverse_up_from(Some(*task.get_id())).any(|t| !t.pure_state().is_open())
{
return vec![task.event.id]; return vec![task.event.id];
} }
} }
@ -815,9 +854,10 @@ impl TasksRelay {
/// Returns all recent events from history until the first event at or before the given timestamp. /// Returns all recent events from history until the first event at or before the given timestamp.
fn history_from(&self, stamp: Timestamp) -> impl Iterator<Item=&Event> { fn history_from(&self, stamp: Timestamp) -> impl Iterator<Item=&Event> {
self.history.get(&self.sender.pubkey()).map(|hist| { self.history.get(&self.sender.pubkey())
hist.values().rev().take_while_inclusive(move |e| e.created_at > stamp) .map(|hist| {
}).into_iter().flatten() hist.values().rev().take_while_inclusive(move |e| e.created_at > stamp)
}).into_iter().flatten()
} }
pub(crate) fn move_to(&mut self, target: Option<EventId>) { pub(crate) fn move_to(&mut self, target: Option<EventId>) {
@ -838,13 +878,16 @@ impl TasksRelay {
} }
let now = Timestamp::now(); let now = Timestamp::now();
let offset: u64 = self.history_from(now).skip_while(|e| e.created_at.as_u64() > now.as_u64() + MAX_OFFSET).count() as u64; let offset: u64 = self
.history_from(now)
.skip_while(|e| e.created_at.as_u64() > now.as_u64() + MAX_OFFSET)
.count() as u64;
if offset >= MAX_OFFSET { if offset >= MAX_OFFSET {
warn!("Whoa you are moving around quickly! Give me a few seconds to process.") warn!("Whoa you are moving around quickly! Give me a few seconds to process.")
} }
self.submit( self.submit(
build_tracking(target) build_tracking(target)
.custom_created_at(Timestamp::from(now.as_u64() + offset)) .custom_created_at(Timestamp::from(now.as_u64() + offset)),
); );
} }
@ -913,9 +956,12 @@ impl TasksRelay {
self.move_up(); self.move_up();
self.make_task_with( self.make_task_with(
input, input,
self.get_position().map(|par| self.make_event_tag_from_id(par, MARKER_PARENT)) self.get_position()
.into_iter().chain(once(self.make_event_tag_from_id(pos, MARKER_DEPENDS))), .map(|par| self.make_event_tag_from_id(par, MARKER_PARENT))
true); .into_iter()
.chain(once(self.make_event_tag_from_id(pos, MARKER_DEPENDS))),
true,
);
return true; return true;
} }
false false
@ -944,7 +990,7 @@ impl TasksRelay {
} }
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.get_by_id(id).map_or(id.to_string(), |t| t.get_title())
} }
/// Parse relative time string and track for current position /// Parse relative time string and track for current position
@ -956,7 +1002,11 @@ impl TasksRelay {
.is_some() .is_some()
} }
pub(crate) fn track_at(&mut self, mut time: Timestamp, target: Option<EventId>) -> Option<EventId> { pub(crate) fn track_at(
&mut self,
mut time: Timestamp,
target: Option<EventId>,
) -> Option<EventId> {
if target.is_none() { if target.is_none() {
// Prevent random overlap with tracking started in the same second // Prevent random overlap with tracking started in the same second
time = time - 1; time = time - 1;
@ -1012,11 +1062,10 @@ impl TasksRelay {
pub(crate) fn add(&mut self, event: Event) { pub(crate) fn add(&mut self, event: Event) {
match event.kind { match event.kind {
Kind::GitIssue => self.add_task(event), Kind::GitIssue => self.add_task(event),
Kind::Metadata => Kind::Metadata => match Metadata::from_json(event.content.as_str()) {
match Metadata::from_json(event.content.as_str()) { 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) },
}
Kind::Bookmarks => { Kind::Bookmarks => {
if event.pubkey == self.sender.pubkey() { if event.pubkey == self.sender.pubkey() {
self.bookmarks = referenced_events(&event).collect_vec() self.bookmarks = referenced_events(&event).collect_vec()
@ -1073,7 +1122,9 @@ impl TasksRelay {
} }
fn get_own_events_history(&self) -> impl DoubleEndedIterator<Item=&Event> + '_ { fn get_own_events_history(&self) -> impl DoubleEndedIterator<Item=&Event> + '_ {
self.history.get(&self.sender.pubkey()).into_iter().flat_map(|t| t.values()) self.history.get(&self.sender.pubkey())
.into_iter()
.flat_map(|t| t.values())
} }
fn history_before_now(&self) -> impl Iterator<Item=&Event> { fn history_before_now(&self) -> impl Iterator<Item=&Event> {
@ -1098,7 +1149,9 @@ impl TasksRelay {
} }
pub(crate) fn move_back_by(&mut self, steps: usize) { pub(crate) fn move_back_by(&mut self, steps: usize) {
let id = self.history_before_now().nth(steps) let id = self
.history_before_now()
.nth(steps)
.and_then(|e| referenced_event(e)); .and_then(|e| referenced_event(e));
self.move_to(id) self.move_to(id)
} }
@ -1336,7 +1389,10 @@ impl Display for TasksRelay {
total_time += self.total_time_tracked(task.event.id) // TODO include parent if it matches total_time += self.total_time_tracked(task.event.id) // TODO include parent if it matches
} }
writeln!(lock, "{} visible tasks{}", count, display_time(" tracked a total of HHhMMm", total_time))?; writeln!(lock,
"{count} visible tasks{}",
display_time(" tracked a total of HHhMMm", total_time)
)?;
Ok(()) Ok(())
} }
} }
@ -1389,7 +1445,7 @@ fn display_time(format: &str, secs: u64) -> String {
.map_or(String::new(), |mins| format .map_or(String::new(), |mins| format
.replace("MMM", &format!("{:3}", mins)) .replace("MMM", &format!("{:3}", mins))
.replace("HH", &format!("{:02}", mins.div(60))) .replace("HH", &format!("{:02}", mins.div(60)))
.replace("MM", &format!("{:02}", mins.rem(60))), .replace("MM", &format!("{:02}", mins.rem(60)))
) )
} }
@ -1402,8 +1458,7 @@ pub(crate) fn join_tasks<'a>(
.iter() .iter()
.map(|t| t.get_title()) .map(|t| t.get_title())
.chain(if include_last_id { .chain(if include_last_id {
tasks tasks.last()
.last()
.and_then(|t| t.parent_id()) .and_then(|t| t.parent_id())
.map(|id| id.to_string()) .map(|id| id.to_string())
.into_iter() .into_iter()
@ -1411,7 +1466,10 @@ pub(crate) fn join_tasks<'a>(
None.into_iter() None.into_iter()
}) })
.fold(None, |acc, val| { .fold(None, |acc, val| {
Some(acc.map_or_else(|| val.clone(), |cur| format!("{}{}{}", val, ">".dimmed(), cur))) Some(acc.map_or_else(
|| val.clone(),
|cur| format!("{}{}{}", val, ">".dimmed(), cur),
))
}) })
} }
@ -1477,7 +1535,8 @@ impl Iterator for Durations<'_> {
} }
} }
let now = self.threshold.unwrap_or(Timestamp::now()).as_u64(); let now = self.threshold.unwrap_or(Timestamp::now()).as_u64();
start.filter(|t| t < &now).map(|stamp| Duration::from_secs(now.saturating_sub(stamp))) start.filter(|t| t < &now)
.map(|stamp| Duration::from_secs(now.saturating_sub(stamp)))
} }
} }
@ -1635,7 +1694,6 @@ impl<'a> Iterator for ChildIterator<'a> {
} }
} }
struct ParentIterator<'a> { struct ParentIterator<'a> {
tasks: &'a TaskMap, tasks: &'a TaskMap,
current: Option<EventId>, current: Option<EventId>,
@ -1653,9 +1711,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::*;
use std::collections::HashSet;
fn stub_tasks() -> TasksRelay { fn stub_tasks() -> TasksRelay {
use nostr_sdk::Keys; use nostr_sdk::Keys;