Recurring Tasks - upgrades and bug fixes

- Improved (fixed) logical consistency checks that prevent the
  addition of a recurrence frequency without a due date.
- Improved (fixed) logical consistency checks that prevent the
  addition of an until date with a recurrence frequency.
- When a recurring task is modified, all sibling instances, as well
  as the parent task now get modified.
- When a recurring task is appended, all sibling instances, as well
  as the parent task now get modified.
- Updated documentation.
- It is now possible to upgrade a regular task to a recurring task,
  which is triggered by the "recur" attribute.
This commit is contained in:
Paul Beckingham 2009-03-25 01:08:13 -04:00
parent 5ec0d569a9
commit 1a656f0f60
7 changed files with 92 additions and 37 deletions

View file

@ -9,13 +9,17 @@
+ UTF8 text is now supported in task project names, tags and descriptions. + UTF8 text is now supported in task project names, tags and descriptions.
+ Fixed bug that caused the y/n confirmation on task deletion to ignore the + Fixed bug that caused the y/n confirmation on task deletion to ignore the
Enter key and fail to re-prompt (thanks to Bruce Dillahunty). Enter key and fail to re-prompt (thanks to Bruce Dillahunty).
+ Added support for the "echo.command" configuration variable that displays + When the "echo.command" configuration variable is set to "yes", it causes
the task affected by the start, stop, do, undo, delete and undelete commands that modify tasks to display which task was affected (thanks to
commands (thanks to Bruce Dillahunty). Bruce Dillahunty).
+ Added support for task annotations, with each annotation comprising a + A task can now be annotated with the command "task <id> annotate ...", and
timestamp and a description. a timestamped annotation will appear in reports.
+ Added support for a 'description_only' column that can be used in custom + A 'description_only' column is now available for use in custom reports,
reports which excludes annotations. and it excludes annotations.
+ A task can now be upgraded to a recurring task by adding a recurrence
frequency, a due date, and an optional until date.
+ When a recurring task is modified, all other instances of the recurring
task are also modified.
------ old releases ------------------------------ ------ old releases ------------------------------

View file

@ -101,6 +101,13 @@ Permanently delete task? (y/n) y
This is a recurring task. Do you want to delete all pending This is a recurring task. Do you want to delete all pending
recurrences of this same task? (y/n) y</code></pre> recurrences of this same task? (y/n) y</code></pre>
<h4>Modification</h4>
<p>
When a recurring task is modified, all the other recurring task instances will
be modified. For example, if you raise the priority of one of the recurring
task instances, all will be modified.
</p>
<h4>Recurrence Periods</h4> <h4>Recurrence Periods</h4>
<p> <p>
In the above examples, the recurrence period was specified as "monthly" and In the above examples, the recurrence period was specified as "monthly" and

View file

@ -105,13 +105,17 @@
<li>UTF8 text is now supported in task project names, tags and descriptions. <li>UTF8 text is now supported in task project names, tags and descriptions.
<li>Fixed bug that caused the y/n confirmation on task deletion to ignore the <li>Fixed bug that caused the y/n confirmation on task deletion to ignore the
Enter key and fail to re-prompt (thanks to Bruce Dillahunty). Enter key and fail to re-prompt (thanks to Bruce Dillahunty).
<li>Added support for the "echo.command" configuration variable that displays <li>When the "echo.command" configuration variable is set to "yes", it causes
the task affected by the start, stop, do, undo, delete and undelete commands that modify tasks to display which task was affected (thanks to
commands (thanks to Bruce Dillahunty). Bruce Dillahunty).
<li>Added support for task annotations, with each annotation comprising a <li>A task can now be annotated with the command "task <id> annotate ...", and
timestamp and a description. a timestamped annotation will appear in reports.
<li>Added support for a 'description_only' column that can be used in custom <li>A 'description_only' column is now available for use in custom reports,
reports which excludes annotations. and it excludes annotations.
<li>A task can now be upgraded to a recurring task by adding a recurrence
frequency, a due date, and an optional until date.
<li>When a recurring task is modified, all other instances of the recurring
task are also modified.
</ul> </ul>
<p> <p>

View file

@ -674,12 +674,30 @@ std::string handleModify (TDB& tdb, T& task, Config& conf)
{ {
std::stringstream out; std::stringstream out;
std::vector <T> all; std::vector <T> all;
tdb.pendingT (all); tdb.allPendingT (all);
// Lookup the complete task.
T complete = findT (task.getId (), all);
// Perform some logical consistency checks.
if (task.getAttribute ("recur") != "" &&
task.getAttribute ("due") == "" &&
complete.getAttribute ("due") == "")
throw std::string ("You cannot specify a recurring task without a due date.");
if (task.getAttribute ("until") != "" &&
task.getAttribute ("recur") == "" &&
complete.getAttribute ("recur") == "")
throw std::string ("You cannot specify an until date for a non-recurring task.");
int count = 0;
std::vector <T>::iterator it; std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it) for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == task.getId ()) if (it->getId () == complete.getId () || // Self
(complete.getAttribute ("parent") != "" &&
it->getAttribute ("parent") == complete.getAttribute ("parent")) || // Sibling
it->getUUID () == complete.getAttribute ("parent")) // Parent
{ {
T original (*it); T original (*it);
@ -725,8 +743,14 @@ std::string handleModify (TDB& tdb, T& task, Config& conf)
if (i->second == "") if (i->second == "")
original.removeAttribute (i->first); original.removeAttribute (i->first);
else else
{
original.setAttribute (i->first, i->second); original.setAttribute (i->first, i->second);
// If a "recur" attribute is added, upgrade to a recurring task.
if (i->first == "recur")
original.setStatus (T::recurring);
}
++changes; ++changes;
} }
@ -748,16 +772,18 @@ std::string handleModify (TDB& tdb, T& task, Config& conf)
} }
if (changes) if (changes)
{
original.setId (task.getId ());
tdb.modifyT (original); tdb.modifyT (original);
}
return out.str (); ++count;
} }
} }
if (count == 0)
throw std::string ("Task not found."); throw std::string ("Task not found.");
if (conf.get ("echo.command", true))
out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl;
return out.str (); return out.str ();
} }
@ -766,12 +792,19 @@ std::string handleAppend (TDB& tdb, T& task, Config& conf)
{ {
std::stringstream out; std::stringstream out;
std::vector <T> all; std::vector <T> all;
tdb.pendingT (all); tdb.allPendingT (all);
// Lookup the complete task.
T complete = findT (task.getId (), all);
int count = 0;
std::vector <T>::iterator it; std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it) for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == task.getId ()) if (it->getId () == complete.getId () || // Self
(complete.getAttribute ("parent") != "" &&
it->getAttribute ("parent") == complete.getAttribute ("parent")) || // Sibling
it->getUUID () == complete.getAttribute ("parent")) // Parent
{ {
T original (*it); T original (*it);
@ -843,23 +876,26 @@ std::string handleAppend (TDB& tdb, T& task, Config& conf)
if (changes) if (changes)
{ {
original.setId (task.getId ());
tdb.modifyT (original); tdb.modifyT (original);
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Appended '" out << "Appended '"
<< task.getDescription () << task.getDescription ()
<< "' to task " << "' to task "
<< task.getId () << original.getId ()
<< std::endl; << std::endl;
} }
return out.str (); ++count;
} }
} }
if (count == 0)
throw std::string ("Task not found."); throw std::string ("Task not found.");
if (conf.get ("echo.command", true))
out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl;
return out.str (); return out.str ();
} }
@ -983,3 +1019,14 @@ std::string handleAnnotate (TDB& tdb, T& task, Config& conf)
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
T findT (int id, const std::vector <T>& all)
{
std::vector <T>::const_iterator it;
for (it = all.begin (); it != all.end (); ++it)
if (id == it->getId ())
return *it;
return T ();
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -475,14 +475,6 @@ void parse (
} }
} }
if (task.getAttribute ("recur") != "" &&
task.getAttribute ("due") == "")
throw std::string ("You cannot specify a recurring task without a due date.");
if (task.getAttribute ("until") != "" &&
task.getAttribute ("recur") == "")
throw std::string ("You cannot specify an until date for a non-recurring task.");
if (validDescription (descCandidate)) if (validDescription (descCandidate))
task.setDescription (descCandidate); task.setDescription (descCandidate);
} }

View file

@ -89,6 +89,7 @@ std::string handleStop (TDB&, T&, Config&);
std::string handleUndo (TDB&, T&, Config&); std::string handleUndo (TDB&, T&, Config&);
std::string handleColor (Config&); std::string handleColor (Config&);
std::string handleAnnotate (TDB&, T&, Config&); std::string handleAnnotate (TDB&, T&, Config&);
T findT (int, const std::vector <T>&);
// report.cpp // report.cpp
void filter (std::vector<T>&, T&); void filter (std::vector<T>&, T&);