mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Implement modifying tasks' "wait" value
This commit is contained in:
parent
d7d703f135
commit
e977fb294c
8 changed files with 166 additions and 4 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2809,8 +2809,10 @@ dependencies = [
|
|||
"anyhow",
|
||||
"assert_cmd",
|
||||
"atty",
|
||||
"chrono",
|
||||
"dirs-next",
|
||||
"env_logger 0.8.3",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"mdbook",
|
||||
"nom",
|
||||
|
|
|
@ -22,6 +22,8 @@ termcolor = "^1.1.2"
|
|||
atty = "^0.2.14"
|
||||
toml = "^0.5.8"
|
||||
toml_edit = "^0.2.0"
|
||||
chrono = "*"
|
||||
lazy_static = "1"
|
||||
|
||||
# only needed for usage-docs
|
||||
mdbook = { version = "0.4", optional = true }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Parsers for argument lists -- arrays of strings
|
||||
use super::ArgList;
|
||||
use super::NOW;
|
||||
use chrono::prelude::*;
|
||||
use nom::bytes::complete::tag as nomtag;
|
||||
use nom::{
|
||||
branch::*,
|
||||
|
@ -67,6 +69,30 @@ pub(super) fn status_colon(input: &str) -> IResult<&str, Status> {
|
|||
map_res(colon_prefixed("status"), to_status)(input)
|
||||
}
|
||||
|
||||
/// Recognizes timestamps
|
||||
pub(super) fn timestamp(input: &str) -> IResult<&str, DateTime<Utc>> {
|
||||
// TODO: full relative date language supported by TW
|
||||
fn nn_d_to_timestamp(input: &str) -> Result<DateTime<Utc>, ()> {
|
||||
// TODO: don't unwrap
|
||||
Ok(*NOW + chrono::Duration::days(input.parse().unwrap()))
|
||||
}
|
||||
map_res(terminated(digit1, char('d')), nn_d_to_timestamp)(input)
|
||||
}
|
||||
|
||||
/// Recognizes `wait:` to None and `wait:<ts>` to `Some(ts)`
|
||||
pub(super) fn wait_colon(input: &str) -> IResult<&str, Option<DateTime<Utc>>> {
|
||||
fn to_wait(input: DateTime<Utc>) -> Result<Option<DateTime<Utc>>, ()> {
|
||||
Ok(Some(input))
|
||||
}
|
||||
fn to_none(_: &str) -> Result<Option<DateTime<Utc>>, ()> {
|
||||
Ok(None)
|
||||
}
|
||||
preceded(
|
||||
nomtag("wait:"),
|
||||
alt((map_res(timestamp, to_wait), map_res(nomtag(""), to_none))),
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// Recognizes a comma-separated list of TaskIds
|
||||
pub(super) fn id_list(input: &str) -> IResult<&str, Vec<TaskId>> {
|
||||
fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> {
|
||||
|
@ -237,6 +263,17 @@ mod test {
|
|||
assert!(minus_tag("-1abc").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wait() {
|
||||
assert_eq!(wait_colon("wait:").unwrap(), ("", None));
|
||||
|
||||
let one_day = *NOW + chrono::Duration::days(1);
|
||||
assert_eq!(wait_colon("wait:1d").unwrap(), ("", Some(one_day)));
|
||||
|
||||
let one_day = *NOW + chrono::Duration::days(1);
|
||||
assert_eq!(wait_colon("wait:1d2").unwrap(), ("2", Some(one_day)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_literal() {
|
||||
assert_eq!(literal("list")("list").unwrap().1, "list");
|
||||
|
|
|
@ -31,9 +31,16 @@ pub(crate) use modification::{DescriptionMod, Modification};
|
|||
pub(crate) use subcommand::Subcommand;
|
||||
|
||||
use crate::usage::Usage;
|
||||
use chrono::prelude::*;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
type ArgList<'a> = &'a [&'a str];
|
||||
|
||||
lazy_static! {
|
||||
// A static value of NOW to make tests easier
|
||||
pub(super) static ref NOW: DateTime<Utc> = Utc::now();
|
||||
}
|
||||
|
||||
pub(crate) fn get_usage(usage: &mut Usage) {
|
||||
Subcommand::get_usage(usage);
|
||||
Filter::get_usage(usage);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::args::{any, arg_matching, minus_tag, plus_tag};
|
||||
use super::args::{any, arg_matching, minus_tag, plus_tag, wait_colon};
|
||||
use super::ArgList;
|
||||
use crate::usage;
|
||||
use chrono::prelude::*;
|
||||
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
|
||||
use std::collections::HashSet;
|
||||
use taskchampion::Status;
|
||||
|
@ -36,6 +37,9 @@ pub struct Modification {
|
|||
/// Set the status
|
||||
pub status: Option<Status>,
|
||||
|
||||
/// Set (or, with `Some(None)`, clear) the wait timestamp
|
||||
pub wait: Option<Option<DateTime<Utc>>>,
|
||||
|
||||
/// Set the "active" state, that is, start (true) or stop (false) the task.
|
||||
pub active: Option<bool>,
|
||||
|
||||
|
@ -51,6 +55,7 @@ enum ModArg<'a> {
|
|||
Description(&'a str),
|
||||
PlusTag(&'a str),
|
||||
MinusTag(&'a str),
|
||||
Wait(Option<DateTime<Utc>>),
|
||||
}
|
||||
|
||||
impl Modification {
|
||||
|
@ -71,6 +76,9 @@ impl Modification {
|
|||
ModArg::MinusTag(tag) => {
|
||||
acc.remove_tags.insert(tag.to_owned());
|
||||
}
|
||||
ModArg::Wait(wait) => {
|
||||
acc.wait = Some(wait);
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
@ -78,6 +86,7 @@ impl Modification {
|
|||
alt((
|
||||
Self::plus_tag,
|
||||
Self::minus_tag,
|
||||
Self::wait,
|
||||
// this must come last
|
||||
Self::description,
|
||||
)),
|
||||
|
@ -109,6 +118,13 @@ impl Modification {
|
|||
map_res(arg_matching(minus_tag), to_modarg)(input)
|
||||
}
|
||||
|
||||
fn wait(input: ArgList) -> IResult<ArgList, ModArg> {
|
||||
fn to_modarg(input: Option<DateTime<Utc>>) -> Result<ModArg<'static>, ()> {
|
||||
Ok(ModArg::Wait(input))
|
||||
}
|
||||
map_res(arg_matching(wait_colon), to_modarg)(input)
|
||||
}
|
||||
|
||||
pub(super) fn get_usage(u: &mut usage::Usage) {
|
||||
u.modifications.push(usage::Modification {
|
||||
syntax: "DESCRIPTION",
|
||||
|
@ -122,14 +138,25 @@ impl Modification {
|
|||
u.modifications.push(usage::Modification {
|
||||
syntax: "+TAG",
|
||||
summary: "Tag task",
|
||||
description: "
|
||||
Add the given tag to the task.",
|
||||
description: "Add the given tag to the task.",
|
||||
});
|
||||
u.modifications.push(usage::Modification {
|
||||
syntax: "-TAG",
|
||||
summary: "Un-tag task",
|
||||
description: "Remove the given tag from the task.",
|
||||
});
|
||||
u.modifications.push(usage::Modification {
|
||||
syntax: "status:{pending,completed,deleted}",
|
||||
summary: "Set the task's status",
|
||||
description: "Set the status of the task explicitly.",
|
||||
});
|
||||
u.modifications.push(usage::Modification {
|
||||
syntax: "wait:<timestamp>",
|
||||
summary: "Set or unset the task's wait time",
|
||||
description: "
|
||||
Remove the given tag from the task.",
|
||||
Set the time before which the task is not actionable and
|
||||
should not be shown in reports. With `wait:`, the time
|
||||
is un-set.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +164,7 @@ impl Modification {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::argparse::NOW;
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
|
@ -176,6 +204,32 @@ mod test {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_wait() {
|
||||
let (input, modification) = Modification::parse(argv!["wait:2d"]).unwrap();
|
||||
assert_eq!(input.len(), 0);
|
||||
assert_eq!(
|
||||
modification,
|
||||
Modification {
|
||||
wait: Some(Some(*NOW + chrono::Duration::days(2))),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unset_wait() {
|
||||
let (input, modification) = Modification::parse(argv!["wait:"]).unwrap();
|
||||
assert_eq!(input.len(), 0);
|
||||
assert_eq!(
|
||||
modification,
|
||||
Modification {
|
||||
wait: Some(None),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_arg_description() {
|
||||
let (input, modification) = Modification::parse(argv!["new", "desc", "fun"]).unwrap();
|
||||
|
|
|
@ -40,5 +40,9 @@ pub(super) fn apply_modification(
|
|||
task.remove_tag(&tag)?;
|
||||
}
|
||||
|
||||
if let Some(wait) = modification.wait {
|
||||
task.set_wait(wait)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ The following keys, and key formats, are defined:
|
|||
* `modified` - the time of the last modification of this task
|
||||
* `start.<timestamp>` - either an empty string (representing work on the task to the task that has not been stopped) or a timestamp (representing the time that work stopped)
|
||||
* `tag.<tag>` - indicates this task has tag `<tag>` (value is an empty string)
|
||||
* `wait` - indicates the time before which this task should be hidden, as it is not actionable
|
||||
|
||||
The following are not yet implemented:
|
||||
|
||||
|
|
|
@ -211,6 +211,20 @@ impl Task {
|
|||
.unwrap_or("")
|
||||
}
|
||||
|
||||
/// Get the wait time. If this value is set, it will be returned, even
|
||||
/// if it is in the past.
|
||||
pub fn get_wait(&self) -> Option<DateTime<Utc>> {
|
||||
self.get_timestamp("wait")
|
||||
}
|
||||
|
||||
/// Determine whether this task is waiting now.
|
||||
pub fn is_waiting(&self) -> bool {
|
||||
if let Some(ts) = self.get_wait() {
|
||||
return ts > Utc::now();
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Determine whether this task is active -- that is, that it has been started
|
||||
/// and not stopped.
|
||||
pub fn is_active(&self) -> bool {
|
||||
|
@ -275,6 +289,10 @@ impl<'r> TaskMut<'r> {
|
|||
self.set_string("description", Some(description))
|
||||
}
|
||||
|
||||
pub fn set_wait(&mut self, wait: Option<DateTime<Utc>>) -> anyhow::Result<()> {
|
||||
self.set_timestamp("wait", wait)
|
||||
}
|
||||
|
||||
pub fn set_modified(&mut self, modified: DateTime<Utc>) -> anyhow::Result<()> {
|
||||
self.set_timestamp("modified", Some(modified))
|
||||
}
|
||||
|
@ -452,6 +470,43 @@ mod test {
|
|||
assert!(!task.is_active());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wait_not_set() {
|
||||
let task = Task::new(Uuid::new_v4(), TaskMap::new());
|
||||
|
||||
assert!(!task.is_waiting());
|
||||
assert_eq!(task.get_wait(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wait_in_past() {
|
||||
let ts = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0);
|
||||
let task = Task::new(
|
||||
Uuid::new_v4(),
|
||||
vec![(String::from("wait"), format!("{}", ts.timestamp()))]
|
||||
.drain(..)
|
||||
.collect(),
|
||||
);
|
||||
dbg!(&task);
|
||||
|
||||
assert!(!task.is_waiting());
|
||||
assert_eq!(task.get_wait(), Some(ts));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wait_in_future() {
|
||||
let ts = Utc.ymd(3000, 1, 1).and_hms(0, 0, 0);
|
||||
let task = Task::new(
|
||||
Uuid::new_v4(),
|
||||
vec![(String::from("wait"), format!("{}", ts.timestamp()))]
|
||||
.drain(..)
|
||||
.collect(),
|
||||
);
|
||||
|
||||
assert!(task.is_waiting());
|
||||
assert_eq!(task.get_wait(), Some(ts));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_tag() {
|
||||
let task = Task::new(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue