forked from janek/mostr
feat: list existing tracked times
This commit is contained in:
parent
79b42b8df0
commit
43c62bf742
|
@ -79,8 +79,6 @@ as you work.
|
||||||
|
|
||||||
The currently active task is automatically time-tracked.
|
The currently active task is automatically time-tracked.
|
||||||
To stop time-tracking completely, simply move to the root of all tasks.
|
To stop time-tracking completely, simply move to the root of all tasks.
|
||||||
Time-tracking is currently also stopped
|
|
||||||
when the application is terminated regularly.
|
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
|
@ -100,7 +98,7 @@ when the application is terminated regularly.
|
||||||
Dots can be repeated to move to parent tasks.
|
Dots can be repeated to move to parent tasks.
|
||||||
|
|
||||||
- `:[IND][COL]` - add property column COL at IND or end, if it already exists remove property column COL or IND (1-indexed)
|
- `:[IND][COL]` - add property column COL at IND or end, if it already exists remove property column COL or IND (1-indexed)
|
||||||
- `*[TIME]` - add timetracking with the specified offset
|
- `*[TIME]` - add timetracking with the specified offset (empty: list tracked times)
|
||||||
- `>[TEXT]` - complete active task and move to parent, with optional state description
|
- `>[TEXT]` - complete active task and move to parent, with optional state description
|
||||||
- `<[TEXT]` - close active task and move to parent, with optional state description
|
- `<[TEXT]` - close active task and move to parent, with optional state description
|
||||||
- `!TEXT` - set state for current task from text
|
- `!TEXT` - set state for current task from text
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::io::{stdin, stdout, Write};
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
pub fn some_non_empty(str: &str) -> Option<String> {
|
pub fn some_non_empty(str: &str) -> Option<String> {
|
||||||
if str.is_empty() { None } else { Some(str.to_owned()) }
|
if str.is_empty() { None } else { Some(str.to_string()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn or_print<T, U: Display>(result: Result<T, U>) -> Option<T> {
|
pub fn or_print<T, U: Display>(result: Result<T, U>) -> Option<T> {
|
||||||
|
|
|
@ -367,8 +367,8 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// TODO time tracked list
|
println!("{}", tasks.times_tracked());
|
||||||
// continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
74
src/tasks.rs
74
src/tasks.rs
|
@ -2,18 +2,21 @@ use std::collections::{BTreeSet, HashMap};
|
||||||
use std::io::{Error, stdout, Write};
|
use std::io::{Error, stdout, Write};
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
use std::ops::{Div, Rem};
|
use std::ops::{Div, Rem};
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use chrono::{DateTime, Local, TimeZone};
|
use chrono::{Local, TimeZone};
|
||||||
use chrono::LocalResult::Single;
|
use chrono::LocalResult::Single;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use nostr_sdk::{Event, EventBuilder, EventId, Keys, Kind, PublicKey, Tag, TagStandard, Timestamp, Url};
|
use nostr_sdk::{Event, EventBuilder, EventId, Keys, Kind, PublicKey, Tag, TagStandard, Timestamp, Url};
|
||||||
|
use nostr_sdk::base64::write::StrConsumer;
|
||||||
use TagStandard::Hashtag;
|
use TagStandard::Hashtag;
|
||||||
|
|
||||||
use crate::{Events, EventSender};
|
use crate::{Events, EventSender};
|
||||||
|
use crate::helpers::some_non_empty;
|
||||||
use crate::kinds::*;
|
use crate::kinds::*;
|
||||||
use crate::task::{State, Task, TaskState};
|
use crate::task::{State, Task, TaskState};
|
||||||
|
|
||||||
|
@ -119,6 +122,47 @@ impl Tasks {
|
||||||
TimesTracked::from(self.history.get(&self.sender.pubkey()).into_iter().flatten(), &vec![id]).sum::<Duration>().as_secs()
|
TimesTracked::from(self.history.get(&self.sender.pubkey()).into_iter().flatten(), &vec![id]).sum::<Duration>().as_secs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn times_tracked(&self) -> String {
|
||||||
|
match self.get_position() {
|
||||||
|
None => {
|
||||||
|
let hist = self.history.get(&self.sender.pubkey());
|
||||||
|
if let Some(set) = hist {
|
||||||
|
let mut full = String::with_capacity(set.len() * 40);
|
||||||
|
let mut last: Option<String> = None;
|
||||||
|
full.push_str("Your Time Tracking History:\n");
|
||||||
|
for event in set {
|
||||||
|
let new = some_non_empty(&event.tags.iter()
|
||||||
|
.filter_map(|t| t.content())
|
||||||
|
.map(|str| EventId::from_str(str).ok().map_or(str.to_string(), |id| self.get_task_title(&id)))
|
||||||
|
.join(" "));
|
||||||
|
if new != last {
|
||||||
|
full.push_str(&format!("{} {}\n", event.created_at.to_human_datetime(), new.as_ref().unwrap_or(&"---".to_string())));
|
||||||
|
last = new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
full
|
||||||
|
} else {
|
||||||
|
String::from("You have nothing tracked yet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(id) => {
|
||||||
|
let vec = vec![id];
|
||||||
|
let res =
|
||||||
|
once(format!("Times tracked on {}", self.get_task_title(&id))).chain(
|
||||||
|
self.history.iter().flat_map(|(key, set)|
|
||||||
|
timestamps(set.iter(), &vec)
|
||||||
|
.tuples::<(_, _)>()
|
||||||
|
.map(move |((start, _), (end, _))| {
|
||||||
|
format!("{} - {} by {}", start.to_human_datetime(), end.to_human_datetime(), key)
|
||||||
|
})
|
||||||
|
).sorted_unstable()
|
||||||
|
).join("\n");
|
||||||
|
drop(vec);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Total time in seconds tracked on this task and its subtasks by all users.
|
/// Total time in seconds tracked on this task and its subtasks by all users.
|
||||||
fn total_time_tracked(&self, id: EventId) -> u64 {
|
fn total_time_tracked(&self, id: EventId) -> u64 {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
|
@ -692,6 +736,18 @@ pub(crate) fn join_tasks<'a>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn matching_tag_id<'a>(event: &'a Event, ids: &'a Vec<EventId>) -> Option<&'a EventId> {
|
||||||
|
event.tags.iter().find_map(|tag| match tag.as_standardized() {
|
||||||
|
Some(TagStandard::Event { event_id, .. }) if ids.contains(event_id) => Some(event_id),
|
||||||
|
_ => None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamps<'a>(events: impl Iterator<Item=&'a Event>, ids: &'a Vec<EventId>) -> impl Iterator<Item=(&Timestamp, Option<&EventId>)> {
|
||||||
|
events.map(|event| (&event.created_at, matching_tag_id(event, ids)))
|
||||||
|
.dedup_by(|(_, e1), (_, e2)| e1 == e2)
|
||||||
|
.skip_while(|element| element.1 == None)
|
||||||
|
}
|
||||||
|
|
||||||
struct TimesTracked<'a> {
|
struct TimesTracked<'a> {
|
||||||
events: Box<dyn Iterator<Item=&'a Event> + 'a>,
|
events: Box<dyn Iterator<Item=&'a Event> + 'a>,
|
||||||
|
@ -712,19 +768,15 @@ impl Iterator for TimesTracked<'_> {
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let mut start: Option<u64> = None;
|
let mut start: Option<u64> = None;
|
||||||
while let Some(event) = self.events.next() {
|
while let Some(event) = self.events.next() {
|
||||||
match event.tags.first().and_then(|tag| tag.as_standardized()) {
|
if matching_tag_id(event, self.ids).is_some() {
|
||||||
Some(TagStandard::Event {
|
start = start.or(Some(event.created_at.as_u64()))
|
||||||
event_id,
|
} else {
|
||||||
..
|
if let Some(stamp) = start {
|
||||||
}) if self.ids.contains(event_id) => {
|
return Some(Duration::from_secs(event.created_at.as_u64() - stamp));
|
||||||
start = start.or(Some(event.created_at.as_u64()))
|
|
||||||
}
|
|
||||||
_ => if let Some(stamp) = start {
|
|
||||||
return Some(Duration::from_secs(event.created_at.as_u64() - stamp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return start.map(|stamp| Duration::from_secs(Timestamp::now().as_u64() - stamp))
|
return start.map(|stamp| Duration::from_secs(Timestamp::now().as_u64() - stamp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue