[WIP] make 'waiting' status a 'virtual' status

This commit is contained in:
Dustin J. Mitchell
2021-08-08 02:08:43 +00:00
committed by Tomas Babej
parent 20041c120e
commit 901283c79f
7 changed files with 50 additions and 32 deletions

View File

@@ -1,5 +1,12 @@
2.6.0 () - 2.6.0 () -
- TW #2554 Waiting is now an entirely "virtual" concept, based on a task's
'wait' property and the current time. This is reflected in the +WAITING
tag, and in the now-deprecated `waiting` status. Please upgrade filters
and other automation to use `+WAITING` or `wait.after:now` instead of
`status:waiting`, as support will be dropped in a future version.
TaskWarrior no longer explicitly "unwaits" a task, so the "unwait' verbosity
token is no longer available.
- TW #1654 "Due" parsing behaviour seems inconsistent - TW #1654 "Due" parsing behaviour seems inconsistent
Thanks to Max Rossmannek. Thanks to Max Rossmannek.
- TW #1788 When deleting recurring task all tasks, including completed tasks, - TW #1788 When deleting recurring task all tasks, including completed tasks,

View File

@@ -299,11 +299,10 @@ control specific occasions when output is generated. This list may contain:
sync Feedback about sync sync Feedback about sync
filter Shows the filter used in the command filter Shows the filter used in the command
context Show the current context. Displayed in footnote. context Show the current context. Displayed in footnote.
unwait Notification when a task leaves the 'waiting' state
override Notification when configuration options are overridden override Notification when configuration options are overridden
recur Notification when a new recurring task instance is created recur Notification when a new recurring task instance is created
"affected", "new-id", "new-uuid", "project", "unwait", "override" and "recur" "affected", "new-id", "new-uuid", "project", "override" and "recur"
imply "footnote". imply "footnote".
Note that the "1" setting is equivalent to all the tokens being specified, Note that the "1" setting is equivalent to all the tokens being specified,
@@ -312,7 +311,7 @@ and the "nothing" setting is equivalent to none of the tokens being specified.
Here are the shortcut equivalents: Here are the shortcut equivalents:
verbose=on verbose=on
verbose=blank,header,footnote,label,new-id,affected,edit,special,project,sync,filter,unwait,override,recur verbose=blank,header,footnote,label,new-id,affected,edit,special,project,sync,filter,override,recur
verbose=0 verbose=0
verbose=blank,label,new-id,edit verbose=blank,label,new-id,edit

View File

@@ -87,7 +87,7 @@ std::string configurationDefaults =
"\n" "\n"
"# Miscellaneous\n" "# Miscellaneous\n"
"# # Comma-separated list. May contain any subset of:\n" "# # Comma-separated list. May contain any subset of:\n"
"verbose=blank,header,footnote,label,new-id,affected,edit,special,project,sync,filter,unwait,override,recur\n" "verbose=blank,header,footnote,label,new-id,affected,edit,special,project,sync,filter,override,recur\n"
"confirmation=1 # Confirmation on delete, big changes\n" "confirmation=1 # Confirmation on delete, big changes\n"
"recurrence=1 # Enable recurrence\n" "recurrence=1 # Enable recurrence\n"
"recurrence.confirmation=prompt # Confirmation for propagating changes among recurring tasks (yes/no/prompt)\n" "recurrence.confirmation=prompt # Confirmation for propagating changes among recurring tasks (yes/no/prompt)\n"
@@ -1029,7 +1029,6 @@ bool Context::verbose (const std::string& token)
v != "project" && // v != "project" && //
v != "sync" && // v != "sync" && //
v != "filter" && // v != "filter" && //
v != "unwait" && //
v != "override" && // v != "override" && //
v != "context" && // v != "context" && //
v != "recur") // v != "recur") //
@@ -1043,7 +1042,7 @@ bool Context::verbose (const std::string& token)
if (! verbosity.count ("footnote")) if (! verbosity.count ("footnote"))
{ {
// TODO: Some of these may not use footnotes yet. They should. // TODO: Some of these may not use footnotes yet. They should.
for (auto flag : {"affected", "new-id", "new-uuid", "project", "unwait", "override", "recur"}) for (auto flag : {"affected", "new-id", "new-uuid", "project", "override", "recur"})
{ {
if (verbosity.count (flag)) if (verbosity.count (flag))
{ {

View File

@@ -355,23 +355,6 @@ void TF2::load_gc (Task& task)
{ {
Context::getContext ().tdb2.pending._tasks.push_back (task); Context::getContext ().tdb2.pending._tasks.push_back (task);
} }
else if (status == "waiting")
{
Datetime wait (task.get_date ("wait"));
if (wait < now)
{
task.set ("status", "pending");
task.remove ("wait");
// Unwaiting pending tasks is the only case not caught by the size()
// checks in TDB2::gc(), so we need to signal it here.
Context::getContext ().tdb2.pending._dirty = true;
if (Context::getContext ().verbose ("unwait"))
Context::getContext ().footnote (format ("Un-waiting task {1} '{2}'", task.id, task.get ("description")));
}
Context::getContext ().tdb2.pending._tasks.push_back (task);
}
else else
{ {
Context::getContext ().tdb2.completed._tasks.push_back (task); Context::getContext ().tdb2.completed._tasks.push_back (task);
@@ -1249,7 +1232,6 @@ void TDB2::show_diff (
// Possible scenarios: // Possible scenarios:
// - task in pending that needs to be in completed // - task in pending that needs to be in completed
// - task in completed that needs to be in pending // - task in completed that needs to be in pending
// - waiting task in pending that needs to be un-waited
void TDB2::gc () void TDB2::gc ()
{ {
Timer timer; Timer timer;

View File

@@ -147,7 +147,9 @@ Task::status Task::textToStatus (const std::string& input)
else if (input[0] == 'c') return Task::completed; else if (input[0] == 'c') return Task::completed;
else if (input[0] == 'd') return Task::deleted; else if (input[0] == 'd') return Task::deleted;
else if (input[0] == 'r') return Task::recurring; else if (input[0] == 'r') return Task::recurring;
else if (input[0] == 'w') return Task::waiting; // for compatibility, parse `w` as pending; Task::getStatus will
// apply the virtual waiting status if appropriate
else if (input[0] == 'w') return Task::pending;
throw format ("The status '{1}' is not valid.", input); throw format ("The status '{1}' is not valid.", input);
} }
@@ -301,12 +303,25 @@ Task::status Task::getStatus () const
if (! has ("status")) if (! has ("status"))
return Task::pending; return Task::pending;
return textToStatus (get ("status")); auto status = textToStatus (get ("status"));
// implement the "virtual" Task::waiting status, which is not stored on-disk
// but is defined as a pending task with a `wait` attribute in the future.
if (status == Task::pending && is_waiting ()) {
return Task::waiting;
}
return status;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void Task::setStatus (Task::status status) void Task::setStatus (Task::status status)
{ {
// the 'waiting' status is a virtual version of 'pending', so translate
// that back to 'pending' here
if (status == Task::waiting)
status = Task::pending;
set ("status", statusToText (status)); set ("status", statusToText (status));
recalc_urgency = true; recalc_urgency = true;
@@ -559,6 +574,23 @@ bool Task::is_overdue () const
} }
#endif #endif
////////////////////////////////////////////////////////////////////////////////
bool Task::is_waiting () const
{
// note that is_waiting can return true for tasks in an actual status other
// than pending; in this case +WAITING will be set but the status will not be
// "waiting"
if (has ("wait"))
{
Datetime now;
Datetime wait (get_date ("wait"));
if (wait > now)
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Attempt an FF4 parse first, using Task::parse, and in the event of an error // Attempt an FF4 parse first, using Task::parse, and in the event of an error
// try a JSON parse, otherwise a legacy parse (currently no legacy formats are // try a JSON parse, otherwise a legacy parse (currently no legacy formats are
@@ -1311,7 +1343,7 @@ bool Task::hasTag (const std::string& tag) const
if (tag == "TAGGED") return getTagCount() > 0; if (tag == "TAGGED") return getTagCount() > 0;
if (tag == "PARENT") return has ("mask") || has ("last"); // 2017-01-07: Deprecated in 2.6.0 if (tag == "PARENT") return has ("mask") || has ("last"); // 2017-01-07: Deprecated in 2.6.0
if (tag == "TEMPLATE") return has ("last") || has ("mask"); if (tag == "TEMPLATE") return has ("last") || has ("mask");
if (tag == "WAITING") return get ("status") == "waiting"; if (tag == "WAITING") return is_waiting ();
if (tag == "PENDING") return get ("status") == "pending"; if (tag == "PENDING") return get ("status") == "pending";
if (tag == "COMPLETED") return get ("status") == "completed"; if (tag == "COMPLETED") return get ("status") == "completed";
if (tag == "DELETED") return get ("status") == "deleted"; if (tag == "DELETED") return get ("status") == "deleted";
@@ -2048,7 +2080,7 @@ float Task::urgency_scheduled () const
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
float Task::urgency_waiting () const float Task::urgency_waiting () const
{ {
if (get_ref ("status") == "waiting") if (is_waiting ())
return 1.0; return 1.0;
return 0.0; return 0.0;

View File

@@ -114,6 +114,7 @@ public:
bool is_udaPresent () const; bool is_udaPresent () const;
bool is_orphanPresent () const; bool is_orphanPresent () const;
#endif #endif
bool is_waiting () const;
status getStatus () const; status getStatus () const;
void setStatus (status); void setStatus (status);

View File

@@ -62,8 +62,6 @@ class TestWait(TestCase):
self.assertIn("visible", out) self.assertIn("visible", out)
self.assertIn("hidden", out) self.assertIn("hidden", out)
self.assertIn("Un-waiting task 2 'hidden'", err)
class TestBug434(TestCase): class TestBug434(TestCase):
# Bug #434: Task should not prevent users from marking as done tasks with # Bug #434: Task should not prevent users from marking as done tasks with
@@ -106,7 +104,7 @@ class TestFeature2322(TestCase):
self.t = Task() self.t = Task()
def test_done_unwait(self): def test_done_unwait(self):
"""2322: Done should un-wait a waiting task""" """2322: Done should remove the wait attribute"""
self.t("add foo wait:tomorrow") self.t("add foo wait:tomorrow")
code, out, err = self.t("export") code, out, err = self.t("export")
self.assertIn('"wait":', out) self.assertIn('"wait":', out)
@@ -116,7 +114,7 @@ class TestFeature2322(TestCase):
self.assertNotIn('"wait":', out) self.assertNotIn('"wait":', out)
def test_delete_unwait(self): def test_delete_unwait(self):
"""2322: Deleteion should un-wait a waiting task""" """2322: Delete should remove the wait attribute"""
self.t("add bar wait:tomorrow") self.t("add bar wait:tomorrow")
code, out, err = self.t("export") code, out, err = self.t("export")
self.assertIn('"wait":', out) self.assertIn('"wait":', out)