forked from janek/mostr
feat: implement priority parsing from task string
This commit is contained in:
parent
dd78a2f460
commit
f33d890d7f
40
README.md
40
README.md
|
@ -82,6 +82,46 @@ 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 by default recursively summarizes
|
||||||
|
|
||||||
|
### Priorities
|
||||||
|
|
||||||
|
Task priorities can be set as any natural number,
|
||||||
|
with higher numbers denoting higher priorities.
|
||||||
|
The syntax here allows for very convenient incremental usage:
|
||||||
|
By default, using priorities between 1 and 9 is recommended,
|
||||||
|
with an exemplary interpretation like this:
|
||||||
|
|
||||||
|
* 1 Ideas / "Someday"
|
||||||
|
* 2 Later
|
||||||
|
* 3 Soon
|
||||||
|
* 4 Relevant
|
||||||
|
* 5 Important
|
||||||
|
* 9 DO NOW
|
||||||
|
|
||||||
|
Internally, when giving a single digit, a 0 is appended,
|
||||||
|
so that the default priorities increment in steps of 10.
|
||||||
|
So in case you need more than 10 priorities,
|
||||||
|
instead of stacking them on top,
|
||||||
|
you can granularly add them in between.
|
||||||
|
For example, `12` is in between `1` and `2`
|
||||||
|
which are equivalent to `10` and `20`,
|
||||||
|
not above `9` but above `09`!
|
||||||
|
|
||||||
|
By default, only tasks with priority `35` and upward are shown
|
||||||
|
so you can focus on what matters,
|
||||||
|
but you can temporarily override that using `**PRIO`.
|
||||||
|
|
||||||
|
### Quick Access
|
||||||
|
|
||||||
|
Paper-based lists are often popular because you can quickly put down a bunch of items.
|
||||||
|
Mostr offers three useful workflows depending on the use-case:
|
||||||
|
If you want to TBC...
|
||||||
|
|
||||||
|
- temporary task with subtasks (especially handy for progression)
|
||||||
|
- Filter by recently created
|
||||||
|
- Pin to bookmarks
|
||||||
|
- high priority
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
|
|
53
src/kinds.rs
53
src/kinds.rs
|
@ -1,10 +1,11 @@
|
||||||
|
use crate::task::{State, MARKER_PARENT};
|
||||||
|
use crate::tasks::HIGH_PRIO;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::info;
|
use log::info;
|
||||||
use nostr_sdk::TagStandard::Hashtag;
|
use nostr_sdk::TagStandard::Hashtag;
|
||||||
use nostr_sdk::{Alphabet, EventBuilder, EventId, Kind, Tag, TagStandard};
|
use nostr_sdk::{Alphabet, EventBuilder, EventId, Kind, Tag, TagKind, TagStandard};
|
||||||
use std::collections::HashSet;
|
use std::borrow::Cow;
|
||||||
|
use std::iter::once;
|
||||||
use crate::task::{State, MARKER_PARENT};
|
|
||||||
|
|
||||||
pub const TASK_KIND: Kind = Kind::GitIssue;
|
pub const TASK_KIND: Kind = Kind::GitIssue;
|
||||||
pub const PROCEDURE_KIND_ID: u16 = 1639;
|
pub const PROCEDURE_KIND_ID: u16 = 1639;
|
||||||
|
@ -25,6 +26,8 @@ pub const PROP_KINDS: [Kind; 6] = [
|
||||||
PROCEDURE_KIND,
|
PROCEDURE_KIND,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub const PRIO: &str = "priority";
|
||||||
|
|
||||||
// TODO: use formatting - bold / heading / italics - and generate from code
|
// TODO: use formatting - bold / heading / italics - and generate from code
|
||||||
/// Helper for available properties.
|
/// Helper for available properties.
|
||||||
pub const PROPERTY_COLUMNS: &str =
|
pub const PROPERTY_COLUMNS: &str =
|
||||||
|
@ -95,16 +98,31 @@ pub(crate) fn extract_hashtags(input: &str) -> impl Iterator<Item=Tag> + '_ {
|
||||||
/// as well as various embedded tags.
|
/// as well as various embedded tags.
|
||||||
///
|
///
|
||||||
/// Expects sanitized input.
|
/// Expects sanitized input.
|
||||||
pub(crate) fn extract_tags(input: &str) -> (&str, Vec<Tag>) {
|
pub(crate) fn extract_tags(input: &str) -> (String, Vec<Tag>) {
|
||||||
match input.split_once(" # ") {
|
let words = input.split_ascii_whitespace();
|
||||||
None => (input, extract_hashtags(input).collect_vec()),
|
let mut prio = None;
|
||||||
Some((name, tags)) => {
|
let result = words.filter(|s| {
|
||||||
let tags = extract_hashtags(name)
|
if s.starts_with('*') {
|
||||||
.chain(tags.split_ascii_whitespace().map(to_hashtag))
|
if s.len() == 1 {
|
||||||
.collect();
|
prio = Some(HIGH_PRIO);
|
||||||
(name, tags)
|
return false
|
||||||
|
}
|
||||||
|
return match s[1..].parse::<u16>() {
|
||||||
|
Ok(num) => {
|
||||||
|
prio = Some(num * (if s.len() > 2 { 1 } else { 10 }));
|
||||||
|
false
|
||||||
|
},
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
true
|
||||||
|
}).collect_vec();
|
||||||
|
let mut split = result.split(|e| { e == &"#" });
|
||||||
|
let main = split.next().unwrap().join(" ");
|
||||||
|
let tags = extract_hashtags(&main)
|
||||||
|
.chain(split.flatten().map(|s| to_hashtag(&s)))
|
||||||
|
.chain(prio.map(|p| to_prio_tag(&p.to_string()))).collect();
|
||||||
|
(main, tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_hashtag(tag: &str) -> Tag {
|
fn to_hashtag(tag: &str) -> Tag {
|
||||||
|
@ -137,9 +155,14 @@ pub(crate) fn is_hashtag(tag: &Tag) -> bool {
|
||||||
.is_some_and(|letter| letter.character == Alphabet::T)
|
.is_some_and(|letter| letter.character == Alphabet::T)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn to_prio_tag(value: &str) -> Tag {
|
||||||
|
Tag::custom(TagKind::Custom(Cow::from(PRIO)), [if value.len() < 2 { format!("{value}0") } else { value.to_string() }])
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_extract_tags() {
|
fn test_extract_tags() {
|
||||||
assert_eq!(extract_tags("Hello from #mars with #greetings # yeah done-it"),
|
assert_eq!(extract_tags("Hello from #mars with #greetings *4 # yeah done-it"),
|
||||||
("Hello from #mars with #greetings", ["mars", "greetings", "yeah", "done-it"].into_iter().map(to_hashtag).collect()))
|
("Hello from #mars with #greetings".to_string(),
|
||||||
|
["mars", "greetings", "yeah", "done-it"].into_iter().map(to_hashtag)
|
||||||
|
.chain(once(Tag::custom(TagKind::Custom(Cow::from(PRIO)), [40.to_string()]))).collect()))
|
||||||
}
|
}
|
|
@ -19,6 +19,10 @@ use regex::bytes::Regex;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use TagStandard::Hashtag;
|
use TagStandard::Hashtag;
|
||||||
|
|
||||||
|
const DEFAULT_PRIO: u16 = 25;
|
||||||
|
pub const HIGH_PRIO: u16 = 85;
|
||||||
|
|
||||||
|
/// Amount of seconds to treat as "now"
|
||||||
const MAX_OFFSET: u64 = 9;
|
const MAX_OFFSET: u64 = 9;
|
||||||
fn now() -> Timestamp {
|
fn now() -> Timestamp {
|
||||||
Timestamp::now() + MAX_OFFSET
|
Timestamp::now() + MAX_OFFSET
|
||||||
|
@ -856,7 +860,7 @@ impl TasksRelay {
|
||||||
pub(crate) fn make_task_with(&mut self, input: &str, tags: impl IntoIterator<Item=Tag>, set_state: bool) -> EventId {
|
pub(crate) fn make_task_with(&mut self, input: &str, tags: impl IntoIterator<Item=Tag>, set_state: bool) -> EventId {
|
||||||
let (input, input_tags) = extract_tags(input.trim());
|
let (input, input_tags) = extract_tags(input.trim());
|
||||||
let id = self.submit(
|
let id = self.submit(
|
||||||
build_task(input, input_tags, None)
|
build_task(&input, input_tags, None)
|
||||||
.add_tags(self.tags.iter().cloned())
|
.add_tags(self.tags.iter().cloned())
|
||||||
.add_tags(tags)
|
.add_tags(tags)
|
||||||
);
|
);
|
||||||
|
@ -1060,7 +1064,7 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
let (input, tags) = extract_tags(note.trim());
|
let (input, tags) = extract_tags(note.trim());
|
||||||
self.submit(
|
self.submit(
|
||||||
build_task(input, tags, Some(("activity", Kind::TextNote)))
|
build_task(&input, tags, Some(("activity", Kind::TextNote)))
|
||||||
.add_tags(self.parent_tag())
|
.add_tags(self.parent_tag())
|
||||||
.add_tags(self.tags.iter().cloned())
|
.add_tags(self.tags.iter().cloned())
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue