Compare commits
8 commits
00bd7a997a
...
20fc8f9a3a
Author | SHA1 | Date | |
---|---|---|---|
|
20fc8f9a3a | ||
|
1f13c45831 | ||
|
e320523fc0 | ||
|
b87970d4e2 | ||
|
2ce5801925 | ||
|
ca50bdf3bb | ||
|
9eb6138852 | ||
|
88ecd68eb8 |
8 changed files with 125 additions and 74 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1488,7 +1488,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mostr"
|
name = "mostr"
|
||||||
version = "0.6.0"
|
version = "0.6.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-english",
|
"chrono-english",
|
||||||
|
|
|
@ -5,7 +5,7 @@ repository = "https://forge.ftt.gmbh/janek/mostr"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "GPL 3.0"
|
license = "GPL 3.0"
|
||||||
authors = ["melonion"]
|
authors = ["melonion"]
|
||||||
version = "0.6.0"
|
version = "0.6.1"
|
||||||
rust-version = "1.82"
|
rust-version = "1.82"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "mostr"
|
default-run = "mostr"
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly-2024-11-09"
|
channel = "1.82.0"
|
||||||
|
|
|
@ -146,6 +146,11 @@ pub fn format_timestamp_local(stamp: &Timestamp) -> String {
|
||||||
format_timestamp(stamp, "%y-%m-%d %a %H:%M")
|
format_timestamp(stamp, "%y-%m-%d %a %H:%M")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format nostr timestamp with seconds precision.
|
||||||
|
pub fn format_timestamp_full(stamp: &Timestamp) -> String {
|
||||||
|
format_timestamp(stamp, "%y-%m-%d %a %H:%M:%S")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn format_timestamp_relative_to(stamp: &Timestamp, reference: &Timestamp) -> String {
|
pub fn format_timestamp_relative_to(stamp: &Timestamp, reference: &Timestamp) -> String {
|
||||||
// Rough difference in days
|
// Rough difference in days
|
||||||
match (stamp.as_u64() as i64 - reference.as_u64() as i64) / 80_000 {
|
match (stamp.as_u64() as i64 - reference.as_u64() as i64) / 80_000 {
|
||||||
|
|
16
src/kinds.rs
16
src/kinds.rs
|
@ -41,10 +41,11 @@ Task:
|
||||||
- `hashtags` - list of hashtags set for the task
|
- `hashtags` - list of hashtags set for the task
|
||||||
- `tags` - values of all nostr tags associated with the event, except event tags
|
- `tags` - values of all nostr tags associated with the event, except event tags
|
||||||
- `desc` - last note on the task
|
- `desc` - last note on the task
|
||||||
- `description` - accumulated notes on the task
|
- `description` - all notes on the task
|
||||||
- `time` - time tracked on this task by you
|
- `time` - time tracked on this task by you
|
||||||
Utilities:
|
Utilities:
|
||||||
- `state` - indicator of current progress
|
- `state` - indicator of current progress
|
||||||
|
- `owner` - author or task assignee
|
||||||
- `rtime` - time tracked on this tasks and its subtree by everyone
|
- `rtime` - time tracked on this tasks and its subtree by everyone
|
||||||
- `progress` - recursive subtask completion in percent
|
- `progress` - recursive subtask completion in percent
|
||||||
- `subtasks` - how many direct subtasks are complete
|
- `subtasks` - how many direct subtasks are complete
|
||||||
|
@ -78,7 +79,8 @@ where
|
||||||
.tags(id.into_iter().map(Tag::event))
|
.tags(id.into_iter().map(Tag::event))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn join<'a, T>(tags: T) -> String
|
/// Formats and joins the tags with commata
|
||||||
|
pub fn join_tags<'a, T>(tags: T) -> String
|
||||||
where
|
where
|
||||||
T: IntoIterator<Item=&'a Tag>,
|
T: IntoIterator<Item=&'a Tag>,
|
||||||
{
|
{
|
||||||
|
@ -131,12 +133,16 @@ pub fn to_hashtag(tag: &str) -> Tag {
|
||||||
TagStandard::Hashtag(tag.to_string()).into()
|
TagStandard::Hashtag(tag.to_string()).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_tag(tag: &Tag) -> String {
|
pub 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);
|
||||||
}
|
}
|
||||||
|
format_tag_basic(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_tag_basic(tag: &Tag) -> String {
|
||||||
match tag.as_standardized() {
|
match tag.as_standardized() {
|
||||||
Some(TagStandard::PublicKey {
|
Some(TagStandard::PublicKey {
|
||||||
public_key,
|
public_key,
|
||||||
|
@ -149,12 +155,12 @@ fn format_tag(tag: &Tag) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_hashtag(tag: &Tag) -> bool {
|
pub fn is_hashtag(tag: &Tag) -> bool {
|
||||||
tag.single_letter_tag()
|
tag.single_letter_tag()
|
||||||
.is_some_and(|letter| letter.character == Alphabet::T)
|
.is_some_and(|letter| letter.character == Alphabet::T)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn to_prio_tag(value: Prio) -> Tag {
|
pub fn to_prio_tag(value: Prio) -> Tag {
|
||||||
Tag::custom(TagKind::Custom(Cow::from(PRIO)), [value.to_string()])
|
Tag::custom(TagKind::Custom(Cow::from(PRIO)), [value.to_string()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
52
src/main.rs
52
src/main.rs
|
@ -10,7 +10,7 @@ use std::time::Duration;
|
||||||
|
|
||||||
use crate::event_sender::MostrMessage;
|
use crate::event_sender::MostrMessage;
|
||||||
use crate::helpers::*;
|
use crate::helpers::*;
|
||||||
use crate::kinds::{join, match_event_tag, Prio, BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS};
|
use crate::kinds::{format_tag_basic, match_event_tag, Prio, BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS};
|
||||||
use crate::task::{State, Task, TaskState, MARKER_PROPERTY};
|
use crate::task::{State, Task, TaskState, MARKER_PROPERTY};
|
||||||
use crate::tasks::{PropertyCollection, StateFilter, TasksRelay};
|
use crate::tasks::{PropertyCollection, StateFilter, TasksRelay};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
|
@ -385,8 +385,11 @@ async fn main() -> Result<()> {
|
||||||
match arg {
|
match arg {
|
||||||
None => {
|
None => {
|
||||||
if let Some(task) = tasks.get_current_task() {
|
if let Some(task) = tasks.get_current_task() {
|
||||||
|
println!("Change History:");
|
||||||
for e in once(&task.event).chain(task.props.iter().rev()) {
|
for e in once(&task.event).chain(task.props.iter().rev()) {
|
||||||
let content = match State::try_from(e.kind) {
|
println!("{} {} [{}]",
|
||||||
|
format_timestamp_full(&e.created_at),
|
||||||
|
match State::try_from(e.kind) {
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
format!("State: {state}{}",
|
format!("State: {state}{}",
|
||||||
if e.content.is_empty() { String::new() } else { format!(" - {}", e.content) })
|
if e.content.is_empty() { String::new() } else { format!(" - {}", e.content) })
|
||||||
|
@ -394,11 +397,17 @@ async fn main() -> Result<()> {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
e.content.to_string()
|
e.content.to_string()
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
println!("{} {} [{}]",
|
e.tags.iter().filter_map(|t| {
|
||||||
format_timestamp_local(&e.created_at),
|
match match_event_tag(t) {
|
||||||
content,
|
Some(et) =>
|
||||||
join(e.tags.iter().filter(|t| match_event_tag(t).unwrap().marker.is_none_or(|m| m != MARKER_PROPERTY))));
|
Some(et).take_if(|et| et.marker.as_ref().is_some_and(|m| m != MARKER_PROPERTY))
|
||||||
|
.map(|et| format!("{}: {}", et.marker.as_ref().unwrap(), tasks.get_relative_path(et.id))),
|
||||||
|
None =>
|
||||||
|
Some(format_tag_basic(t)),
|
||||||
|
}
|
||||||
|
}).join(", ")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
continue 'repl;
|
continue 'repl;
|
||||||
} else {
|
} else {
|
||||||
|
@ -444,37 +453,36 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Some('@') => {
|
Some('@') => {
|
||||||
let success = match arg {
|
match arg {
|
||||||
None => {
|
None => {
|
||||||
let today = Timestamp::now() - 80_000;
|
let today = Timestamp::now() - 80_000;
|
||||||
info!("Filtering for tasks from the last 22 hours");
|
info!("Filtering for tasks from the last 22 hours");
|
||||||
tasks.set_filter_from(today)
|
if !tasks.set_filter_since(today) {
|
||||||
|
continue 'repl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(arg) => {
|
Some(arg) => {
|
||||||
if arg == "@" {
|
if arg == "@" {
|
||||||
info!("Filtering for own tasks");
|
tasks.reset_key_filter()
|
||||||
tasks.set_filter_author(keys.public_key())
|
|
||||||
} else if let Ok(key) = PublicKey::from_str(arg) {
|
} else if let Ok(key) = PublicKey::from_str(arg) {
|
||||||
let author = tasks.get_username(&key);
|
info!("Showing {}'s tasks", tasks.get_username(&key));
|
||||||
info!("Filtering for tasks by {author}");
|
tasks.set_key_filter(key)
|
||||||
tasks.set_filter_author(key)
|
|
||||||
} else if let Some((key, meta)) = tasks.find_user(arg) {
|
} else if let Some((key, meta)) = tasks.find_user(arg) {
|
||||||
info!("Filtering for tasks by {}", meta.display_name.as_ref().unwrap_or(meta.name.as_ref().unwrap_or(&key.to_string())));
|
info!("Showing {}'s tasks", meta.display_name.as_ref().unwrap_or(meta.name.as_ref().unwrap_or(&key.to_string())));
|
||||||
tasks.set_filter_author(key.clone())
|
tasks.set_key_filter(key.clone())
|
||||||
} else {
|
} else {
|
||||||
parse_hour(arg, 1)
|
if parse_hour(arg, 1)
|
||||||
.or_else(|| parse_date(arg).map(|utc| utc.with_timezone(&Local)))
|
.or_else(|| parse_date(arg).map(|utc| utc.with_timezone(&Local)))
|
||||||
.map(|time| {
|
.map(|time| {
|
||||||
info!("Filtering for tasks from {}", format_datetime_relative(time));
|
info!("Filtering for tasks from {}", format_datetime_relative(time));
|
||||||
tasks.set_filter_from(time.to_timestamp())
|
tasks.set_filter_since(time.to_timestamp())
|
||||||
})
|
})
|
||||||
.unwrap_or(false)
|
.is_none_or(|b| !b) {
|
||||||
|
continue 'repl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if !success {
|
|
||||||
continue 'repl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some('*') => {
|
Some('*') => {
|
||||||
|
|
|
@ -92,11 +92,12 @@ impl Task {
|
||||||
self.event.content.trim().trim_start_matches('#').to_string()
|
self.event.content.trim().trim_start_matches('#').to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn description_events(&self) -> impl Iterator<Item=&Event> + '_ {
|
fn description_events(&self) -> impl DoubleEndedIterator<Item=&Event> + '_ {
|
||||||
self.props.iter().filter(|event| event.kind == Kind::TextNote)
|
self.props.iter().filter(|event| event.kind == Kind::TextNote)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn descriptions(&self) -> impl Iterator<Item=&String> + '_ {
|
/// Description items, ordered newest to oldest
|
||||||
|
pub(crate) fn descriptions(&self) -> impl DoubleEndedIterator<Item=&String> + '_ {
|
||||||
self.description_events().map(|e| &e.content)
|
self.description_events().map(|e| &e.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,8 +209,8 @@ impl Task {
|
||||||
// Dynamic
|
// Dynamic
|
||||||
"priority" => self.priority_raw().map(|c| c.to_string()),
|
"priority" => self.priority_raw().map(|c| c.to_string()),
|
||||||
"status" => self.state_label().map(|c| c.to_string()),
|
"status" => self.state_label().map(|c| c.to_string()),
|
||||||
"desc" => self.descriptions().last().cloned(),
|
"desc" => self.descriptions().next().cloned(),
|
||||||
"description" => Some(self.descriptions().join(" ")),
|
"description" => Some(self.descriptions().rev().join(" ")),
|
||||||
"hashtags" => Some(self.join_tags(|tag| { is_hashtag(tag) })),
|
"hashtags" => Some(self.join_tags(|tag| { is_hashtag(tag) })),
|
||||||
"tags" => Some(self.join_tags(|_| true)), // TODO test these!
|
"tags" => Some(self.join_tags(|_| true)), // TODO test these!
|
||||||
"alltags" => Some(format!("{:?}", self.tags)),
|
"alltags" => Some(format!("{:?}", self.tags)),
|
||||||
|
|
95
src/tasks.rs
95
src/tasks.rs
|
@ -79,6 +79,7 @@ pub(crate) struct TasksRelay {
|
||||||
state: StateFilter,
|
state: StateFilter,
|
||||||
/// Current priority for filtering and new tasks
|
/// Current priority for filtering and new tasks
|
||||||
priority: Option<Prio>,
|
priority: Option<Prio>,
|
||||||
|
pubkey: Option<PublicKey>,
|
||||||
|
|
||||||
sender: EventSender,
|
sender: EventSender,
|
||||||
overflow: VecDeque<Event>,
|
overflow: VecDeque<Event>,
|
||||||
|
@ -173,6 +174,7 @@ impl TasksRelay {
|
||||||
tags_excluded: Default::default(),
|
tags_excluded: Default::default(),
|
||||||
state: Default::default(),
|
state: Default::default(),
|
||||||
priority: None,
|
priority: None,
|
||||||
|
pubkey: Some(sender.pubkey()),
|
||||||
|
|
||||||
search_depth: 4,
|
search_depth: 4,
|
||||||
view_depth: 0,
|
view_depth: 0,
|
||||||
|
@ -382,14 +384,28 @@ impl TasksRelay {
|
||||||
.and_then(|t| t.parent_id())
|
.and_then(|t| t.parent_id())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO test with context elements
|
||||||
|
/// Visual representation of current context
|
||||||
pub(crate) fn get_prompt_suffix(&self) -> String {
|
pub(crate) fn get_prompt_suffix(&self) -> String {
|
||||||
self.tags.iter()
|
let mut prompt = String::with_capacity(128);
|
||||||
.map(|t| format!(" #{}", t.content().unwrap()))
|
match self.pubkey {
|
||||||
.chain(self.tags_excluded.iter()
|
None => { prompt.push_str(" @ALL"); }
|
||||||
.map(|t| format!(" -#{}", t.content().unwrap())))
|
Some(key) =>
|
||||||
.chain(once(self.state.indicator()))
|
if key != self.sender.pubkey() {
|
||||||
.chain(self.priority.map(|p| format!(" *{:02}", p)))
|
prompt.push_str(" ");
|
||||||
.join("")
|
prompt.push_str(&self.get_username(&key))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for tag in self.tags.iter() {
|
||||||
|
prompt.push_str(&format!(" #{}", tag.content().unwrap()));
|
||||||
|
}
|
||||||
|
for tag in self.tags_excluded.iter() {
|
||||||
|
prompt.push_str(&format!(" -#{}", tag.content().unwrap()));
|
||||||
|
}
|
||||||
|
prompt.push_str(&self.state.indicator());
|
||||||
|
self.priority.map(|p|
|
||||||
|
prompt.push_str(&format!(" *{:02}", p)));
|
||||||
|
prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_task_path(&self, id: Option<EventId>) -> String {
|
pub(crate) fn get_task_path(&self, id: Option<EventId>) -> String {
|
||||||
|
@ -399,6 +415,14 @@ impl TasksRelay {
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_relative_path(&self, id: EventId) -> String {
|
||||||
|
join_tasks(
|
||||||
|
self.traverse_up_from(Some(id))
|
||||||
|
.take_while(|t| Some(t.event.id) != self.get_position()),
|
||||||
|
false,
|
||||||
|
).unwrap_or(id.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate over the task referenced by the given id and all its available parents.
|
/// Iterate over the task referenced by the given id and all its available parents.
|
||||||
fn traverse_up_from(&self, id: Option<EventId>) -> ParentIterator {
|
fn traverse_up_from(&self, id: Option<EventId>) -> ParentIterator {
|
||||||
ParentIterator {
|
ParentIterator {
|
||||||
|
@ -407,13 +431,6 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn relative_path(&self, id: EventId) -> String {
|
|
||||||
join_tasks(
|
|
||||||
self.traverse_up_from(Some(id))
|
|
||||||
.take_while(|t| Some(t.event.id) != self.get_position()),
|
|
||||||
false,
|
|
||||||
).unwrap_or(id.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
|
@ -477,6 +494,7 @@ impl TasksRelay {
|
||||||
|
|
||||||
fn filter(&self, task: &Task) -> bool {
|
fn filter(&self, task: &Task) -> bool {
|
||||||
self.state.matches(task) &&
|
self.state.matches(task) &&
|
||||||
|
self.pubkey.is_none_or(|p| p == task.event.pubkey) &&
|
||||||
self.priority.is_none_or(|prio| {
|
self.priority.is_none_or(|prio| {
|
||||||
task.priority().unwrap_or(DEFAULT_PRIO) >= prio
|
task.priority().unwrap_or(DEFAULT_PRIO) >= prio
|
||||||
}) &&
|
}) &&
|
||||||
|
@ -597,7 +615,7 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
"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.get_relative_path(task.event.id),
|
||||||
// TODO format strings configurable
|
// TODO format strings configurable
|
||||||
"time" => display_time("MMMm", self.time_tracked(*task.get_id())),
|
"time" => display_time("MMMm", self.time_tracked(*task.get_id())),
|
||||||
"rtime" => display_time("HH:MM", self.total_time_tracked(*task.get_id())),
|
"rtime" => display_time("HH:MM", self.total_time_tracked(*task.get_id())),
|
||||||
|
@ -640,11 +658,22 @@ impl TasksRelay {
|
||||||
Ok(added)
|
Ok(added)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_filter_author(&mut self, key: PublicKey) -> bool {
|
pub(crate) fn reset_key_filter(&mut self) {
|
||||||
self.set_filter(|t| t.event.pubkey == key)
|
let own = self.sender.pubkey();
|
||||||
|
if self.pubkey.is_some_and(|k| k == own) {
|
||||||
|
info!("Showing everybody's tasks");
|
||||||
|
self.pubkey = None
|
||||||
|
} else {
|
||||||
|
info!("Showing own tasks");
|
||||||
|
self.pubkey = Some(own)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_filter_from(&mut self, time: Timestamp) -> bool {
|
pub(crate) fn set_key_filter(&mut self, key: PublicKey) {
|
||||||
|
self.pubkey = Some(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_filter_since(&mut self, time: Timestamp) -> bool {
|
||||||
// TODO filter at both ends
|
// TODO filter at both ends
|
||||||
self.set_filter(|t| t.last_state_update() > time)
|
self.set_filter(|t| t.last_state_update() > time)
|
||||||
}
|
}
|
||||||
|
@ -685,10 +714,11 @@ impl TasksRelay {
|
||||||
|
|
||||||
pub(crate) fn clear_filters(&mut self) {
|
pub(crate) fn clear_filters(&mut self) {
|
||||||
self.state = StateFilter::Default;
|
self.state = StateFilter::Default;
|
||||||
|
self.pubkey = Some(self.sender.pubkey());
|
||||||
self.view.clear();
|
self.view.clear();
|
||||||
self.tags.clear();
|
self.tags.clear();
|
||||||
self.tags_excluded.clear();
|
self.tags_excluded.clear();
|
||||||
info!("Removed all filters");
|
info!("Reset all filters");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_tag_filter(&self) -> bool {
|
pub(crate) fn has_tag_filter(&self) -> bool {
|
||||||
|
@ -973,7 +1003,7 @@ impl TasksRelay {
|
||||||
let (input, input_tags) = extract_tags(input.trim());
|
let (input, input_tags) = extract_tags(input.trim());
|
||||||
let prio =
|
let prio =
|
||||||
if input_tags.iter().any(|t| t.kind().to_string() == PRIO) { None } else { self.priority.map(|p| to_prio_tag(p)) };
|
if input_tags.iter().any(|t| t.kind().to_string() == PRIO) { None } else { self.priority.map(|p| to_prio_tag(p)) };
|
||||||
info!("Created task \"{input}\" with tags [{}]", join(&input_tags));
|
info!("Created task \"{input}\" with tags [{}]", join_tags(&input_tags));
|
||||||
let id = self.submit(
|
let id = self.submit(
|
||||||
EventBuilder::new(TASK_KIND, &input)
|
EventBuilder::new(TASK_KIND, &input)
|
||||||
.tags(input_tags)
|
.tags(input_tags)
|
||||||
|
@ -1215,7 +1245,7 @@ impl TasksRelay {
|
||||||
/// Sanitizes Input.
|
/// Sanitizes Input.
|
||||||
pub(crate) fn make_note(&mut self, note: &str) -> EventId {
|
pub(crate) fn make_note(&mut self, note: &str) -> EventId {
|
||||||
let (name, tags) = extract_tags(note.trim());
|
let (name, tags) = extract_tags(note.trim());
|
||||||
let format = format!("\"{name}\" with tags [{}]", join(&tags));
|
let format = format!("\"{name}\" with tags [{}]", join_tags(&tags));
|
||||||
let mut prop =
|
let mut prop =
|
||||||
EventBuilder::new(Kind::TextNote, name).tags(tags);
|
EventBuilder::new(Kind::TextNote, name).tags(tags);
|
||||||
//.filter(|id| self.get_by_id(id).is_some_and(|t| t.is_task()))
|
//.filter(|id| self.get_by_id(id).is_some_and(|t| t.is_task()))
|
||||||
|
@ -1284,13 +1314,14 @@ impl Display for TasksRelay {
|
||||||
}
|
}
|
||||||
writeln!(
|
writeln!(
|
||||||
lock,
|
lock,
|
||||||
"Active from {} (total tracked time {}m) - {} since {}",
|
"Active from {} (total tracked time {}m) - State {} since {}",
|
||||||
tracking_stamp.map_or("?".to_string(), |t| format_timestamp_relative(&t)),
|
tracking_stamp.map_or("?".to_string(), |t| format_timestamp_relative(&t)),
|
||||||
self.time_tracked(*t.get_id()) / 60,
|
self.time_tracked(*t.get_id()) / 60,
|
||||||
state.get_label(),
|
state.get_label(),
|
||||||
format_timestamp_relative(&state.time)
|
format_timestamp_relative(&state.time)
|
||||||
)?;
|
)?;
|
||||||
writeln!(lock, "{}", t.descriptions().join("\n"))?;
|
for d in t.descriptions().rev() { writeln!(lock, "{}", d)?; }
|
||||||
|
writeln!(lock)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let position = self.get_position();
|
let position = self.get_position();
|
||||||
|
@ -1447,6 +1478,8 @@ fn display_time(format: &str, secs: u64) -> String {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Joins the tasks of this upwards iterator.
|
||||||
|
/// * `include_last_id` whether to add the id of an unknown parent at the top
|
||||||
pub(crate) fn join_tasks<'a>(
|
pub(crate) fn join_tasks<'a>(
|
||||||
iter: impl Iterator<Item=&'a Task>,
|
iter: impl Iterator<Item=&'a Task>,
|
||||||
include_last_id: bool,
|
include_last_id: bool,
|
||||||
|
@ -1455,14 +1488,12 @@ pub(crate) fn join_tasks<'a>(
|
||||||
tasks
|
tasks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| t.get_title())
|
.map(|t| t.get_title())
|
||||||
.chain(if include_last_id {
|
.chain(
|
||||||
tasks.last()
|
tasks.last()
|
||||||
|
.take_if(|_| include_last_id)
|
||||||
.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())
|
||||||
} else {
|
|
||||||
None.into_iter()
|
|
||||||
})
|
|
||||||
.fold(None, |acc, val| {
|
.fold(None, |acc, val| {
|
||||||
Some(acc.map_or_else(
|
Some(acc.map_or_else(
|
||||||
|| val.clone(),
|
|| val.clone(),
|
||||||
|
@ -1963,7 +1994,7 @@ mod tasks_test {
|
||||||
let t11 = tasks.make_task("t11 # tag");
|
let t11 = tasks.make_task("t11 # tag");
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||||
assert_eq!(tasks.get_task_path(Some(t11)), "t1>t11");
|
assert_eq!(tasks.get_task_path(Some(t11)), "t1>t11");
|
||||||
assert_eq!(tasks.relative_path(t11), "t11");
|
assert_eq!(tasks.get_relative_path(t11), "t11");
|
||||||
let t12 = tasks.make_task("t12");
|
let t12 = tasks.make_task("t12");
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||||
|
|
||||||
|
@ -1973,7 +2004,7 @@ mod tasks_test {
|
||||||
let t111 = tasks.make_task("t111");
|
let t111 = tasks.make_task("t111");
|
||||||
assert_tasks!(tasks, [t111]);
|
assert_tasks!(tasks, [t111]);
|
||||||
assert_eq!(tasks.get_task_path(Some(t111)), "t1>t11>t111");
|
assert_eq!(tasks.get_task_path(Some(t111)), "t1>t11>t111");
|
||||||
assert_eq!(tasks.relative_path(t111), "t111");
|
assert_eq!(tasks.get_relative_path(t111), "t111");
|
||||||
tasks.view_depth = 2;
|
tasks.view_depth = 2;
|
||||||
assert_tasks!(tasks, [t111]);
|
assert_tasks!(tasks, [t111]);
|
||||||
|
|
||||||
|
@ -1988,7 +2019,7 @@ mod tasks_test {
|
||||||
tasks.move_to(Some(t1));
|
tasks.move_to(Some(t1));
|
||||||
assert_position!(tasks, t1);
|
assert_position!(tasks, t1);
|
||||||
assert_eq!(tasks.get_own_events_history().count(), 3);
|
assert_eq!(tasks.get_own_events_history().count(), 3);
|
||||||
assert_eq!(tasks.relative_path(t111), "t11>t111");
|
assert_eq!(tasks.get_relative_path(t111), "t11>t111");
|
||||||
assert_eq!(tasks.view_depth, 2);
|
assert_eq!(tasks.view_depth, 2);
|
||||||
assert_tasks!(tasks, [t111, t12]);
|
assert_tasks!(tasks, [t111, t12]);
|
||||||
tasks.set_view(vec![t11]);
|
tasks.set_view(vec![t11]);
|
||||||
|
@ -2041,7 +2072,7 @@ mod tasks_test {
|
||||||
tasks.get_task_path(Some(dangling)),
|
tasks.get_task_path(Some(dangling)),
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000>test"
|
"0000000000000000000000000000000000000000000000000000000000000000>test"
|
||||||
);
|
);
|
||||||
assert_eq!(tasks.relative_path(dangling), "test");
|
assert_eq!(tasks.get_relative_path(dangling), "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)] // #[test]
|
#[allow(dead_code)] // #[test]
|
||||||
|
|
Loading…
Add table
Reference in a new issue