mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-20 22:33:08 +02:00
Merge pull request #330 from djmitche/issue327
Support an 'end' key in task maps
This commit is contained in:
commit
8576e7ffa7
3 changed files with 93 additions and 4 deletions
|
@ -17,6 +17,12 @@ The result of this reconciliation will be `oldtag,newtag2`, while the user almos
|
||||||
|
|
||||||
The key names given below avoid this issue, allowing user updates such as adding a tag or deleting a dependency to be represented in a single `Update` operation.
|
The key names given below avoid this issue, allowing user updates such as adding a tag or deleting a dependency to be represented in a single `Update` operation.
|
||||||
|
|
||||||
|
## Validity
|
||||||
|
|
||||||
|
_Any_ key/value map is a valid task.
|
||||||
|
Consumers of task data must make a best effort to interpret any map, even if it contains apparently contradictory information.
|
||||||
|
For example, a task with status "completed" but no "end" key present should be interpreted as completed at an unknown time.
|
||||||
|
|
||||||
## Representations
|
## Representations
|
||||||
|
|
||||||
Integers are stored in decimal notation.
|
Integers are stored in decimal notation.
|
||||||
|
@ -31,6 +37,7 @@ The following keys, and key formats, are defined:
|
||||||
* `description` - the one-line summary of the task
|
* `description` - the one-line summary of the task
|
||||||
* `modified` - the time of the last modification of this task
|
* `modified` - the time of the last modification of this task
|
||||||
* `start` - the most recent time at which this task was started (a task with no `start` key is not active)
|
* `start` - the most recent time at which this task was started (a task with no `start` key is not active)
|
||||||
|
* `end` - if present, the time at which this task was completed or deleted (note that this key may not agree with `status`: it may be present for a pending task, or absent for a deleted or completed task)
|
||||||
* `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
|
* `wait` - indicates the time before which this task should be hidden, as it is not actionable
|
||||||
* `entry` - the time at which the task was created
|
* `entry` - the time at which the task was created
|
||||||
|
|
|
@ -241,7 +241,9 @@ mod tests {
|
||||||
..
|
..
|
||||||
} = op
|
} = op
|
||||||
{
|
{
|
||||||
if property == "modified" || property == "entry" {
|
// rewrite automatically-created dates to "just-now" for ease
|
||||||
|
// of testing
|
||||||
|
if property == "modified" || property == "end" || property == "entry" {
|
||||||
if value.is_some() {
|
if value.is_some() {
|
||||||
value = Some("just-now".into());
|
value = Some("just-now".into());
|
||||||
}
|
}
|
||||||
|
@ -311,6 +313,13 @@ mod tests {
|
||||||
value: Some("past tense".into()),
|
value: Some("past tense".into()),
|
||||||
timestamp: now,
|
timestamp: now,
|
||||||
},
|
},
|
||||||
|
ReplicaOp::Update {
|
||||||
|
uuid: t.get_uuid(),
|
||||||
|
property: "end".into(),
|
||||||
|
old_value: None,
|
||||||
|
value: Some("just-now".into()),
|
||||||
|
timestamp: now,
|
||||||
|
},
|
||||||
ReplicaOp::Update {
|
ReplicaOp::Update {
|
||||||
uuid: t.get_uuid(),
|
uuid: t.get_uuid(),
|
||||||
property: "status".into(),
|
property: "status".into(),
|
||||||
|
|
|
@ -57,6 +57,7 @@ enum Prop {
|
||||||
Start,
|
Start,
|
||||||
Status,
|
Status,
|
||||||
Wait,
|
Wait,
|
||||||
|
End,
|
||||||
Entry,
|
Entry,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,9 +272,22 @@ impl<'r> TaskMut<'r> {
|
||||||
/// Set the task's status. This also adds the task to the working set if the
|
/// Set the task's status. This also adds the task to the working set if the
|
||||||
/// new status puts it in that set.
|
/// new status puts it in that set.
|
||||||
pub fn set_status(&mut self, status: Status) -> anyhow::Result<()> {
|
pub fn set_status(&mut self, status: Status) -> anyhow::Result<()> {
|
||||||
if status == Status::Pending {
|
match status {
|
||||||
let uuid = self.uuid;
|
Status::Pending => {
|
||||||
self.replica.add_to_working_set(uuid)?;
|
// clear "end" when a task becomes "pending"
|
||||||
|
if self.taskmap.contains_key(Prop::End.as_ref()) {
|
||||||
|
self.set_timestamp(Prop::End.as_ref(), None)?;
|
||||||
|
}
|
||||||
|
let uuid = self.uuid;
|
||||||
|
self.replica.add_to_working_set(uuid)?;
|
||||||
|
}
|
||||||
|
Status::Completed | Status::Deleted => {
|
||||||
|
// set "end" when a task is deleted or completed
|
||||||
|
if !self.taskmap.contains_key(Prop::End.as_ref()) {
|
||||||
|
self.set_timestamp(Prop::End.as_ref(), Some(Utc::now()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
self.set_string(
|
self.set_string(
|
||||||
Prop::Status.as_ref(),
|
Prop::Status.as_ref(),
|
||||||
|
@ -316,6 +330,14 @@ impl<'r> TaskMut<'r> {
|
||||||
self.set_status(Status::Completed)
|
self.set_status(Status::Completed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mark this task as deleted.
|
||||||
|
///
|
||||||
|
/// Note that this does not delete the task. It merely marks the task as
|
||||||
|
/// deleted.
|
||||||
|
pub fn delete(&mut self) -> anyhow::Result<()> {
|
||||||
|
self.set_status(Status::Deleted)
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a tag to this task. Does nothing if the tag is already present.
|
/// Add a tag to this task. Does nothing if the tag is already present.
|
||||||
pub fn add_tag(&mut self, tag: &Tag) -> anyhow::Result<()> {
|
pub fn add_tag(&mut self, tag: &Tag) -> anyhow::Result<()> {
|
||||||
if tag.is_synthetic() {
|
if tag.is_synthetic() {
|
||||||
|
@ -688,6 +710,41 @@ mod test {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_status_pending() {
|
||||||
|
with_mut_task(|mut task| {
|
||||||
|
task.done().unwrap();
|
||||||
|
|
||||||
|
task.set_status(Status::Pending).unwrap();
|
||||||
|
assert_eq!(task.get_status(), Status::Pending);
|
||||||
|
assert!(!task.taskmap.contains_key("end"));
|
||||||
|
assert!(task.has_tag(&stag(SyntheticTag::Pending)));
|
||||||
|
assert!(!task.has_tag(&stag(SyntheticTag::Completed)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_status_completed() {
|
||||||
|
with_mut_task(|mut task| {
|
||||||
|
task.set_status(Status::Completed).unwrap();
|
||||||
|
assert_eq!(task.get_status(), Status::Completed);
|
||||||
|
assert!(task.taskmap.contains_key("end"));
|
||||||
|
assert!(!task.has_tag(&stag(SyntheticTag::Pending)));
|
||||||
|
assert!(task.has_tag(&stag(SyntheticTag::Completed)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_status_deleted() {
|
||||||
|
with_mut_task(|mut task| {
|
||||||
|
task.set_status(Status::Deleted).unwrap();
|
||||||
|
assert_eq!(task.get_status(), Status::Deleted);
|
||||||
|
assert!(task.taskmap.contains_key("end"));
|
||||||
|
assert!(!task.has_tag(&stag(SyntheticTag::Pending)));
|
||||||
|
assert!(!task.has_tag(&stag(SyntheticTag::Completed)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_start() {
|
fn test_start() {
|
||||||
with_mut_task(|mut task| {
|
with_mut_task(|mut task| {
|
||||||
|
@ -730,6 +787,7 @@ mod test {
|
||||||
with_mut_task(|mut task| {
|
with_mut_task(|mut task| {
|
||||||
task.done().unwrap();
|
task.done().unwrap();
|
||||||
assert_eq!(task.get_status(), Status::Completed);
|
assert_eq!(task.get_status(), Status::Completed);
|
||||||
|
assert!(task.taskmap.contains_key("end"));
|
||||||
assert!(task.has_tag(&stag(SyntheticTag::Completed)));
|
assert!(task.has_tag(&stag(SyntheticTag::Completed)));
|
||||||
|
|
||||||
// redundant call does nothing..
|
// redundant call does nothing..
|
||||||
|
@ -739,6 +797,21 @@ mod test {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete() {
|
||||||
|
with_mut_task(|mut task| {
|
||||||
|
task.delete().unwrap();
|
||||||
|
assert_eq!(task.get_status(), Status::Deleted);
|
||||||
|
assert!(task.taskmap.contains_key("end"));
|
||||||
|
assert!(!task.has_tag(&stag(SyntheticTag::Completed)));
|
||||||
|
|
||||||
|
// redundant call does nothing..
|
||||||
|
task.delete().unwrap();
|
||||||
|
assert_eq!(task.get_status(), Status::Deleted);
|
||||||
|
assert!(!task.has_tag(&stag(SyntheticTag::Completed)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_tags() {
|
fn test_add_tags() {
|
||||||
with_mut_task(|mut task| {
|
with_mut_task(|mut task| {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue