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",
|
"anyhow",
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"atty",
|
"atty",
|
||||||
|
"chrono",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
"env_logger 0.8.3",
|
"env_logger 0.8.3",
|
||||||
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"mdbook",
|
"mdbook",
|
||||||
"nom",
|
"nom",
|
||||||
|
|
|
@ -22,6 +22,8 @@ termcolor = "^1.1.2"
|
||||||
atty = "^0.2.14"
|
atty = "^0.2.14"
|
||||||
toml = "^0.5.8"
|
toml = "^0.5.8"
|
||||||
toml_edit = "^0.2.0"
|
toml_edit = "^0.2.0"
|
||||||
|
chrono = "*"
|
||||||
|
lazy_static = "1"
|
||||||
|
|
||||||
# only needed for usage-docs
|
# only needed for usage-docs
|
||||||
mdbook = { version = "0.4", optional = true }
|
mdbook = { version = "0.4", optional = true }
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
//! Parsers for argument lists -- arrays of strings
|
//! Parsers for argument lists -- arrays of strings
|
||||||
use super::ArgList;
|
use super::ArgList;
|
||||||
|
use super::NOW;
|
||||||
|
use chrono::prelude::*;
|
||||||
use nom::bytes::complete::tag as nomtag;
|
use nom::bytes::complete::tag as nomtag;
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::*,
|
branch::*,
|
||||||
|
@ -67,6 +69,30 @@ pub(super) fn status_colon(input: &str) -> IResult<&str, Status> {
|
||||||
map_res(colon_prefixed("status"), to_status)(input)
|
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
|
/// Recognizes a comma-separated list of TaskIds
|
||||||
pub(super) fn id_list(input: &str) -> IResult<&str, Vec<TaskId>> {
|
pub(super) fn id_list(input: &str) -> IResult<&str, Vec<TaskId>> {
|
||||||
fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> {
|
fn hex_n(n: usize) -> impl Fn(&str) -> IResult<&str, &str> {
|
||||||
|
@ -237,6 +263,17 @@ mod test {
|
||||||
assert!(minus_tag("-1abc").is_err());
|
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]
|
#[test]
|
||||||
fn test_literal() {
|
fn test_literal() {
|
||||||
assert_eq!(literal("list")("list").unwrap().1, "list");
|
assert_eq!(literal("list")("list").unwrap().1, "list");
|
||||||
|
|
|
@ -31,9 +31,16 @@ pub(crate) use modification::{DescriptionMod, Modification};
|
||||||
pub(crate) use subcommand::Subcommand;
|
pub(crate) use subcommand::Subcommand;
|
||||||
|
|
||||||
use crate::usage::Usage;
|
use crate::usage::Usage;
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
type ArgList<'a> = &'a [&'a str];
|
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) {
|
pub(crate) fn get_usage(usage: &mut Usage) {
|
||||||
Subcommand::get_usage(usage);
|
Subcommand::get_usage(usage);
|
||||||
Filter::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 super::ArgList;
|
||||||
use crate::usage;
|
use crate::usage;
|
||||||
|
use chrono::prelude::*;
|
||||||
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
|
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use taskchampion::Status;
|
use taskchampion::Status;
|
||||||
|
@ -36,6 +37,9 @@ pub struct Modification {
|
||||||
/// Set the status
|
/// Set the status
|
||||||
pub status: Option<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.
|
/// Set the "active" state, that is, start (true) or stop (false) the task.
|
||||||
pub active: Option<bool>,
|
pub active: Option<bool>,
|
||||||
|
|
||||||
|
@ -51,6 +55,7 @@ enum ModArg<'a> {
|
||||||
Description(&'a str),
|
Description(&'a str),
|
||||||
PlusTag(&'a str),
|
PlusTag(&'a str),
|
||||||
MinusTag(&'a str),
|
MinusTag(&'a str),
|
||||||
|
Wait(Option<DateTime<Utc>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Modification {
|
impl Modification {
|
||||||
|
@ -71,6 +76,9 @@ impl Modification {
|
||||||
ModArg::MinusTag(tag) => {
|
ModArg::MinusTag(tag) => {
|
||||||
acc.remove_tags.insert(tag.to_owned());
|
acc.remove_tags.insert(tag.to_owned());
|
||||||
}
|
}
|
||||||
|
ModArg::Wait(wait) => {
|
||||||
|
acc.wait = Some(wait);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
acc
|
acc
|
||||||
}
|
}
|
||||||
|
@ -78,6 +86,7 @@ impl Modification {
|
||||||
alt((
|
alt((
|
||||||
Self::plus_tag,
|
Self::plus_tag,
|
||||||
Self::minus_tag,
|
Self::minus_tag,
|
||||||
|
Self::wait,
|
||||||
// this must come last
|
// this must come last
|
||||||
Self::description,
|
Self::description,
|
||||||
)),
|
)),
|
||||||
|
@ -109,6 +118,13 @@ impl Modification {
|
||||||
map_res(arg_matching(minus_tag), to_modarg)(input)
|
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) {
|
pub(super) fn get_usage(u: &mut usage::Usage) {
|
||||||
u.modifications.push(usage::Modification {
|
u.modifications.push(usage::Modification {
|
||||||
syntax: "DESCRIPTION",
|
syntax: "DESCRIPTION",
|
||||||
|
@ -122,14 +138,25 @@ impl Modification {
|
||||||
u.modifications.push(usage::Modification {
|
u.modifications.push(usage::Modification {
|
||||||
syntax: "+TAG",
|
syntax: "+TAG",
|
||||||
summary: "Tag task",
|
summary: "Tag task",
|
||||||
description: "
|
description: "Add the given tag to the task.",
|
||||||
Add the given tag to the task.",
|
|
||||||
});
|
});
|
||||||
u.modifications.push(usage::Modification {
|
u.modifications.push(usage::Modification {
|
||||||
syntax: "-TAG",
|
syntax: "-TAG",
|
||||||
summary: "Un-tag task",
|
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: "
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::argparse::NOW;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_empty() {
|
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]
|
#[test]
|
||||||
fn test_multi_arg_description() {
|
fn test_multi_arg_description() {
|
||||||
let (input, modification) = Modification::parse(argv!["new", "desc", "fun"]).unwrap();
|
let (input, modification) = Modification::parse(argv!["new", "desc", "fun"]).unwrap();
|
||||||
|
|
|
@ -40,5 +40,9 @@ pub(super) fn apply_modification(
|
||||||
task.remove_tag(&tag)?;
|
task.remove_tag(&tag)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(wait) = modification.wait {
|
||||||
|
task.set_wait(wait)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ The following keys, and key formats, are defined:
|
||||||
* `modified` - the time of the last modification of this task
|
* `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)
|
* `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)
|
* `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:
|
The following are not yet implemented:
|
||||||
|
|
||||||
|
|
|
@ -211,6 +211,20 @@ impl Task {
|
||||||
.unwrap_or("")
|
.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
|
/// Determine whether this task is active -- that is, that it has been started
|
||||||
/// and not stopped.
|
/// and not stopped.
|
||||||
pub fn is_active(&self) -> bool {
|
pub fn is_active(&self) -> bool {
|
||||||
|
@ -275,6 +289,10 @@ impl<'r> TaskMut<'r> {
|
||||||
self.set_string("description", Some(description))
|
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<()> {
|
pub fn set_modified(&mut self, modified: DateTime<Utc>) -> anyhow::Result<()> {
|
||||||
self.set_timestamp("modified", Some(modified))
|
self.set_timestamp("modified", Some(modified))
|
||||||
}
|
}
|
||||||
|
@ -452,6 +470,43 @@ mod test {
|
||||||
assert!(!task.is_active());
|
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]
|
#[test]
|
||||||
fn test_has_tag() {
|
fn test_has_tag() {
|
||||||
let task = Task::new(
|
let task = Task::new(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue