- Recurring tasks!

This commit is contained in:
Paul Beckingham 2008-07-09 03:26:44 -04:00
parent 46ded4c026
commit 6f7b9b7d42
13 changed files with 395 additions and 188 deletions

View file

@ -13,4 +13,6 @@ With thanks to:
Nishiishii
galvanizd
H. İbrahim Güngör
Stas Antons
Andy Lester

View file

@ -13,6 +13,7 @@ represents a feature release, and the Z represents a patch.
1.4.0 ()
+ New recurring tasks feature
+ "task undelete" can now undelete erroneously deleted tasks, provided no
reports have been run (and therefore TDB::gc run)
+ Added averages to the "task history" report

View file

@ -48,6 +48,7 @@
</p>
<ul>
<li>Added new recurring tasks feature
<li>Added "task undelete" feature to restore a (very) recently deleted
task
<li>Added averages to the "task history" report

View file

@ -45,8 +45,8 @@ binPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
PROGRAMS = $(bin_PROGRAMS)
am_task_OBJECTS = Config.$(OBJEXT) Date.$(OBJEXT) T.$(OBJEXT) \
TDB.$(OBJEXT) Table.$(OBJEXT) Grid.$(OBJEXT) color.$(OBJEXT) \
parse.$(OBJEXT) task.$(OBJEXT) util.$(OBJEXT) text.$(OBJEXT) \
rules.$(OBJEXT)
parse.$(OBJEXT) task.$(OBJEXT) command.$(OBJEXT) \
report.$(OBJEXT) util.$(OBJEXT) text.$(OBJEXT) rules.$(OBJEXT)
task_OBJECTS = $(am_task_OBJECTS)
task_LDADD = $(LDADD)
DEFAULT_INCLUDES = -I. -I$(top_builddir)@am__isrc@
@ -154,7 +154,7 @@ sysconfdir = @sysconfdir@
target_alias = @target_alias@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp color.cpp parse.cpp task.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h color.h task.h
task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h color.h task.h
AM_CPPFLAGS = -Wall -pedantic -ggdb3 -fno-rtti
all: all-am
@ -229,7 +229,9 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TDB.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Table.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/report.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rules.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/task.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/text.Po@am__quote@

View file

@ -450,7 +450,7 @@ void T::parse (const std::string& line)
{
std::vector <std::string> pair;
split (pair, pairs[i], ':');
if (pair[1] != "")
if (pair.size () == 2)
mAttributes[pair[0]] = pair[1];
}

View file

@ -32,7 +32,7 @@
#include <map>
// Length of longest line.
#define T_LINE_MAX 8192
#define T_LINE_MAX 32768
class T
{

View file

@ -38,6 +38,7 @@ TDB::TDB ()
: mPendingFile ("")
, mCompletedFile ("")
, mLogFile ("")
, mId (1)
{
}
@ -67,7 +68,7 @@ void TDB::dataDirectory (const std::string& directory)
////////////////////////////////////////////////////////////////////////////////
// Combine allPendingT with allCompletedT.
// Note: this method is O(N1) + O(N2), where N2 is not bounded.
bool TDB::allT (std::vector <T>& all) const
bool TDB::allT (std::vector <T>& all)
{
all.clear ();
@ -95,20 +96,20 @@ bool TDB::allT (std::vector <T>& all) const
////////////////////////////////////////////////////////////////////////////////
// Only accesses to the pending file result in Tasks that have assigned ids.
bool TDB::pendingT (std::vector <T>& all) const
bool TDB::pendingT (std::vector <T>& all)
{
all.clear ();
std::vector <std::string> lines;
if (readLockedFile (mPendingFile, lines))
{
int id = 1;
mId = 1;
std::vector <std::string>::iterator it;
for (it = lines.begin (); it != lines.end (); ++it)
{
T t (*it);
t.setId (id++);
t.setId (mId++);
if (t.getStatus () == T::pending)
all.push_back (t);
}
@ -121,20 +122,20 @@ bool TDB::pendingT (std::vector <T>& all) const
////////////////////////////////////////////////////////////////////////////////
// Only accesses to the pending file result in Tasks that have assigned ids.
bool TDB::allPendingT (std::vector <T>& all) const
bool TDB::allPendingT (std::vector <T>& all)
{
all.clear ();
std::vector <std::string> lines;
if (readLockedFile (mPendingFile, lines))
{
int id = 1;
mId = 1;
std::vector <std::string>::iterator it;
for (it = lines.begin (); it != lines.end (); ++it)
{
T t (*it);
t.setId (id++);
t.setId (mId++);
all.push_back (t);
}
@ -188,7 +189,7 @@ bool TDB::allCompletedT (std::vector <T>& all) const
}
////////////////////////////////////////////////////////////////////////////////
bool TDB::deleteT (const T& t) const
bool TDB::deleteT (const T& t)
{
T task (t);
@ -212,7 +213,7 @@ bool TDB::deleteT (const T& t) const
}
////////////////////////////////////////////////////////////////////////////////
bool TDB::completeT (const T& t) const
bool TDB::completeT (const T& t)
{
T task (t);
@ -261,7 +262,7 @@ bool TDB::addT (const T& t) const
}
////////////////////////////////////////////////////////////////////////////////
bool TDB::modifyT (const T& t) const
bool TDB::modifyT (const T& t)
{
T modified (t);
@ -348,7 +349,7 @@ bool TDB::lock (FILE* file) const
}
////////////////////////////////////////////////////////////////////////////////
bool TDB::overwritePending (std::vector <T>& all) const
bool TDB::overwritePending (std::vector <T>& all)
{
// Write a single task to the pending file
FILE* out;
@ -453,7 +454,7 @@ bool TDB::readLockedFile (
}
////////////////////////////////////////////////////////////////////////////////
int TDB::gc () const
int TDB::gc ()
{
int count = 0;
@ -486,4 +487,10 @@ int TDB::gc () const
}
////////////////////////////////////////////////////////////////////////////////
int TDB::nextId ()
{
return mId++;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -38,22 +38,23 @@ public:
~TDB ();
void dataDirectory (const std::string&);
bool allT (std::vector <T>&) const;
bool pendingT (std::vector <T>&) const;
bool allPendingT (std::vector <T>&) const;
bool allT (std::vector <T>&);
bool pendingT (std::vector <T>&);
bool allPendingT (std::vector <T>&);
bool completedT (std::vector <T>&) const;
bool allCompletedT (std::vector <T>&) const;
bool deleteT (const T&) const;
bool completeT (const T&) const;
bool deleteT (const T&);
bool completeT (const T&);
bool addT (const T&) const;
bool modifyT (const T&) const;
bool modifyT (const T&);
bool logRead (std::vector <std::string>&) const;
bool logCommand (int, char**) const;
int gc () const;
int gc ();
int nextId ();
private:
bool lock (FILE*) const;
bool overwritePending (std::vector <T>&) const;
bool overwritePending (std::vector <T>&);
bool writePending (const T&) const;
bool writeCompleted (const T&) const;
bool readLockedFile (const std::string&, std::vector <std::string>&) const;
@ -62,6 +63,7 @@ private:
std::string mPendingFile;
std::string mCompletedFile;
std::string mLogFile;
int mId;
};
#endif

View file

@ -55,10 +55,8 @@ void handleAdd (const TDB& tdb, T& task, Config& conf)
std::map <std::string, std::string> atts;
task.getAttributes (atts);
foreach (i, atts)
{
if (i->second == "")
task.removeAttribute (i->first);
}
// Recurring tasks get a special status.
if (task.getAttribute ("due") != "" &&
@ -76,7 +74,7 @@ void handleAdd (const TDB& tdb, T& task, Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
void handleProjects (const TDB& tdb, T& task, Config& conf)
void handleProjects (TDB& tdb, T& task, Config& conf)
{
// Get all the tasks, including deleted ones.
std::vector <T> tasks;
@ -127,7 +125,7 @@ void handleProjects (const TDB& tdb, T& task, Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
void handleTags (const TDB& tdb, T& task, Config& conf)
void handleTags (TDB& tdb, T& task, Config& conf)
{
// Get all the tasks.
std::vector <T> tasks;
@ -165,7 +163,7 @@ void handleTags (const TDB& tdb, T& task, Config& conf)
////////////////////////////////////////////////////////////////////////////////
// If a task is deleted, but is still in the pending file, then it may be
// undeleted simply by changing it's status.
void handleUndelete (const TDB& tdb, T& task, Config& conf)
void handleUndelete (TDB& tdb, T& task, Config& conf)
{
std::vector <T> all;
tdb.allPendingT (all);
@ -289,44 +287,54 @@ void handleVersion (Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
void handleDelete (const TDB& tdb, T& task, Config& conf)
void handleDelete (TDB& tdb, T& task, Config& conf)
{
if (conf.get ("confirmation") != "yes" || confirm ("Permanently delete task?"))
{
// Check for the more complex case of a recurring task. If this is a
// recurring task, get confirmation to delete them all.
std::string parent = task.getAttribute ("parent");
if (parent != "")
std::vector <T> all;
tdb.allPendingT (all);
foreach (t, all)
{
if (confirm ("This is a recurring task. Do you want to delete all pending recurrences of this same task?"))
if (t->getId () == task.getId ())
{
// Scan all pending tasks for siblings of this task, and the parent
// itself, and delete them.
std::vector <T> all;
tdb.allPendingT (all);
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
if (it->getAttribute ("parent") == parent ||
it->getUUID () == parent)
tdb.deleteT (*it);
// Check for the more complex case of a recurring task. If this is a
// recurring task, get confirmation to delete them all.
std::string parent = t->getAttribute ("parent");
if (parent != "")
{
if (confirm ("This is a recurring task. Do you want to delete all pending recurrences of this same task?"))
{
// Scan all pending tasks for siblings of this task, and the parent
// itself, and delete them.
foreach (sibling, all)
if (sibling->getAttribute ("parent") == parent ||
sibling->getUUID () == parent)
tdb.deleteT (*sibling);
return;
}
else
{
// TODO Update mask in parent.
return;
}
else
{
// Update mask in parent.
t->setStatus (T::deleted);
updateRecurrenceMask (tdb, all, *t);
tdb.deleteT (*t);
return;
}
}
else
tdb.deleteT (*t);
break; // No point continuing the loop.
}
}
// No confirmation, just delete the one.
tdb.deleteT (task);
}
else
std::cout << "Task not deleted." << std::endl;
}
////////////////////////////////////////////////////////////////////////////////
void handleStart (const TDB& tdb, T& task, Config& conf)
void handleStart (TDB& tdb, T& task, Config& conf)
{
std::vector <T> all;
tdb.pendingT (all);
@ -359,18 +367,29 @@ void handleStart (const TDB& tdb, T& task, Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
void handleDone (const TDB& tdb, T& task, Config& conf)
void handleDone (TDB& tdb, T& task, Config& conf)
{
if (!tdb.completeT (task))
throw std::string ("Could not mark task as completed.");
// TODO Now updates mask in parent.
// Now update mask in parent.
std::vector <T> all;
tdb.allPendingT (all);
foreach (t, all)
{
if (t->getId () == task.getId ())
{
t->setStatus (T::completed);
updateRecurrenceMask (tdb, all, *t);
break;
}
}
nag (tdb, task, conf);
}
////////////////////////////////////////////////////////////////////////////////
void handleExport (const TDB& tdb, T& task, Config& conf)
void handleExport (TDB& tdb, T& task, Config& conf)
{
// Use the description as a file name, then clobber the description so the
// file name isn't used for filtering.
@ -413,7 +432,7 @@ void handleExport (const TDB& tdb, T& task, Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
void handleModify (const TDB& tdb, T& task, Config& conf)
void handleModify (TDB& tdb, T& task, Config& conf)
{
std::vector <T> all;
tdb.pendingT (all);

View file

@ -108,7 +108,7 @@ void filter (std::vector<T>& all, T& task)
////////////////////////////////////////////////////////////////////////////////
// Successively apply filters based on the task object built from the command
// line. Tasks that match all the specified criteria are listed.
void handleList (const TDB& tdb, T& task, Config& conf)
void handleList (TDB& tdb, T& task, Config& conf)
{
// Determine window size, and set table accordingly.
int width = conf.get ("defaultwidth", 80);
@ -253,7 +253,7 @@ void handleList (const TDB& tdb, T& task, Config& conf)
// Successively apply filters based on the task object built from the command
// line. Tasks that match all the specified criteria are listed. Show a narrow
// list that works better on mobile devices.
void handleSmallList (const TDB& tdb, T& task, Config& conf)
void handleSmallList (TDB& tdb, T& task, Config& conf)
{
// Determine window size, and set table accordingly.
int width = conf.get ("defaultwidth", 80);
@ -379,7 +379,7 @@ void handleSmallList (const TDB& tdb, T& task, Config& conf)
////////////////////////////////////////////////////////////////////////////////
// Successively apply filters based on the task object built from the command
// line. Tasks that match all the specified criteria are listed.
void handleCompleted (const TDB& tdb, T& task, Config& conf)
void handleCompleted (TDB& tdb, T& task, Config& conf)
{
// Determine window size, and set table accordingly.
int width = conf.get ("defaultwidth", 80);
@ -464,7 +464,7 @@ void handleCompleted (const TDB& tdb, T& task, Config& conf)
////////////////////////////////////////////////////////////////////////////////
// Display all information for the given task.
void handleInfo (const TDB& tdb, T& task, Config& conf)
void handleInfo (TDB& tdb, T& task, Config& conf)
{
// Determine window size, and set table accordingly.
int width = conf.get ("defaultwidth", 80);
@ -548,6 +548,21 @@ void handleInfo (const TDB& tdb, T& task, Config& conf)
row = table.addRow ();
table.addCell (row, 0, "Recur until");
table.addCell (row, 1, refTask.getAttribute ("until"));
row = table.addRow ();
table.addCell (row, 0, "Mask");
table.addCell (row, 1, refTask.getAttribute ("mask"));
}
if (refTask.getAttribute ("parent") != "")
{
row = table.addRow ();
table.addCell (row, 0, "Parent task");
table.addCell (row, 1, refTask.getAttribute ("parent"));
row = table.addRow ();
table.addCell (row, 0, "Mask Index");
table.addCell (row, 1, refTask.getAttribute ("imask"));
}
// due (colored)
@ -645,7 +660,7 @@ void handleInfo (const TDB& tdb, T& task, Config& conf)
////////////////////////////////////////////////////////////////////////////////
// Successively apply filters based on the task object built from the command
// line. Tasks that match all the specified criteria are listed.
void handleLongList (const TDB& tdb, T& task, Config& conf)
void handleLongList (TDB& tdb, T& task, Config& conf)
{
// Determine window size, and set table accordingly.
int width = conf.get ("defaultwidth", 80);
@ -814,7 +829,7 @@ void handleLongList (const TDB& tdb, T& task, Config& conf)
// Project Tasks Avg Age Status
// A 12 13d XXXXXXXX------
// B 109 3d 12h XX------------
void handleReportSummary (const TDB& tdb, T& task, Config& conf)
void handleReportSummary (TDB& tdb, T& task, Config& conf)
{
// Generate unique list of project names.
tdb.gc ();
@ -984,7 +999,7 @@ void handleReportSummary (const TDB& tdb, T& task, Config& conf)
//
// Make the "three" tasks a configurable number
//
void handleReportNext (const TDB& tdb, T& task, Config& conf)
void handleReportNext (TDB& tdb, T& task, Config& conf)
{
// Load all pending.
tdb.gc ();
@ -1156,7 +1171,7 @@ time_t monthlyEpoch (const std::string& date)
return 0;
}
void handleReportHistory (const TDB& tdb, T& task, Config& conf)
void handleReportHistory (TDB& tdb, T& task, Config& conf)
{
std::map <time_t, int> groups;
std::map <time_t, int> addedGroup;
@ -1338,7 +1353,7 @@ void handleReportHistory (const TDB& tdb, T& task, Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
void handleReportGHistory (const TDB& tdb, T& task, Config& conf)
void handleReportGHistory (TDB& tdb, T& task, Config& conf)
{
// Determine window size, and set table accordingly.
int width = conf.get ("defaultwidth", 80);
@ -1755,7 +1770,7 @@ std::string renderMonths (
}
////////////////////////////////////////////////////////////////////////////////
void handleReportCalendar (const TDB& tdb, T& task, Config& conf)
void handleReportCalendar (TDB& tdb, T& task, Config& conf)
{
// Load all the pending tasks.
tdb.gc ();
@ -1841,7 +1856,7 @@ void handleReportCalendar (const TDB& tdb, T& task, Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
void handleReportActive (const TDB& tdb, T& task, Config& conf)
void handleReportActive (TDB& tdb, T& task, Config& conf)
{
// Determine window size, and set table accordingly.
int width = conf.get ("defaultwidth", 80);
@ -1957,7 +1972,7 @@ void handleReportActive (const TDB& tdb, T& task, Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
void handleReportOverdue (const TDB& tdb, T& task, Config& conf)
void handleReportOverdue (TDB& tdb, T& task, Config& conf)
{
// Determine window size, and set table accordingly.
int width = conf.get ("defaultwidth", 80);
@ -2064,7 +2079,7 @@ void handleReportOverdue (const TDB& tdb, T& task, Config& conf)
////////////////////////////////////////////////////////////////////////////////
// Successively apply filters based on the task object built from the command
// line. Tasks that match all the specified criteria are listed.
void handleReportOldest (const TDB& tdb, T& task, Config& conf)
void handleReportOldest (TDB& tdb, T& task, Config& conf)
{
// Determine window size, and set table accordingly.
int width = conf.get ("defaultwidth", 80);
@ -2207,7 +2222,7 @@ void handleReportOldest (const TDB& tdb, T& task, Config& conf)
////////////////////////////////////////////////////////////////////////////////
// Successively apply filters based on the task object built from the command
// line. Tasks that match all the specified criteria are listed.
void handleReportNewest (const TDB& tdb, T& task, Config& conf)
void handleReportNewest (TDB& tdb, T& task, Config& conf)
{
// Determine window size, and set table accordingly.
int width = conf.get ("defaultwidth", 80);
@ -2350,7 +2365,7 @@ void handleReportNewest (const TDB& tdb, T& task, Config& conf)
////////////////////////////////////////////////////////////////////////////////
void handleReportStats (const TDB& tdb, T& task, Config& conf)
void handleReportStats (TDB& tdb, T& task, Config& conf)
{
// Get all the tasks.
std::vector <T> tasks;

View file

@ -345,7 +345,7 @@ int main (int argc, char** argv)
}
////////////////////////////////////////////////////////////////////////////////
void nag (const TDB& tdb, T& task, Config& conf)
void nag (TDB& tdb, T& task, Config& conf)
{
std::string nagMessage = conf.get ("nag", std::string (""));
if (nagMessage != "")
@ -390,138 +390,296 @@ int getDueState (const std::string& due)
}
////////////////////////////////////////////////////////////////////////////////
// Scan for recurring tasks, and generate any necessary instances of those
// tasks.
void handleRecurrence (const TDB& tdb, std::vector <T>& tasks)
// Scans all tasks, and for any recurring tasks, determines whether any new
// child tasks need to be generated to fill gaps.
void handleRecurrence (TDB& tdb, std::vector <T>& tasks)
{
std::vector <T> modified;
Date now;
std::cout << "# handleRecurrence" << std::endl;
std::vector <T>::iterator it;
for (it = tasks.begin (); it != tasks.end (); ++it)
// Look at all tasks and find any recurring ones.
foreach (t, tasks)
{
if (it->getStatus () == T::recurring)
if (t->getStatus () == T::recurring)
{
std::cout << "# found recurring task " << it->getUUID () << std::endl;
// std::cout << "# found recurring task " << t->getUUID () << std::endl;
// This task is recurring. While it remains hidden from view, it spawns
// child tasks automatically, here, that are regular tasks, except they
// have a "parent" attribute that contains the UUID of the original.
// Generate a list of due dates for this recurring task, regardless of
// the mask.
std::vector <Date> due;
generateDueDates (*t, due);
// Generate a list of child tasks.
std::vector <T> children;
std::vector <T>::iterator them;
for (them = tasks.begin (); them != tasks.end (); ++them)
if (them->getAttribute ("parent") == it->getUUID ())
children.push_back (*them);
// Get the mask from the parent task.
std::string mask = t->getAttribute ("mask");
// std::cout << "# mask=" << mask << std::endl;
// Determine due date, recur period and until date.
Date due (atoi (it->getAttribute ("due").c_str ()));
std::cout << "# due=" << due.toString () << std::endl;
std::string recur = it->getAttribute ("recur");
std::cout << "# recur=" << recur << std::endl;
bool specificEnd = false;
Date until;
if (it->getAttribute ("until") != "")
// Iterate over the due dates, and check each against the mask.
bool changed = false;
unsigned int i = 0;
foreach (d, due)
{
until = Date (atoi (it->getAttribute ("until").c_str ()));
specificEnd = true;
}
// std::cout << "# need: " << d->toString () << std::endl;
std::cout << "# specficEnd=" << (specificEnd ? "true" : "false") << std::endl;
if (specificEnd)
std::cout << "# until=" << until.toString () << std::endl;
for (Date i = due; ; i = getNextRecurrence (i, recur))
{
std::cout << "# i=" << i.toString () << std::endl;
if (specificEnd && i > until)
break;
// Look to see if there is a gap at date "i" by scanning children.
bool foundChild = false;
std::vector <T>::iterator cit;
for (cit = children.begin (); cit != children.end (); ++cit)
if (mask.length () <= i)
{
if (atoi (cit->getAttribute ("due").c_str ()) == i.toEpoch ())
{
foundChild = true;
break;
}
}
mask += '-';
changed = true;
// TODO A gap may be filled by a completed task. Oh crap.
// There is a gap, so insert a task.
if (!foundChild)
{
std::cout << "# found a gap at i=" << i.toString () << std::endl;
T rec (*it); // Clone the parent.
T rec (*t); // Clone the parent.
rec.setId (tdb.nextId ()); // Assign a unique id.
rec.setUUID (uuid ()); // New UUID.
rec.setStatus (T::pending); // Shiny.
rec.setAttribute ("parent", t->getUUID ()); // Remember mom.
char dueDate[16];
sprintf (dueDate, "%u", (unsigned int) i.toEpoch ());
rec.setAttribute ("due", dueDate);
rec.setAttribute ("parent", it->getUUID ());
rec.setStatus (T::pending);
sprintf (dueDate, "%u", (unsigned int) d->toEpoch ());
rec.setAttribute ("due", dueDate); // Store generated due date.
std::cout << "# adding to modified" << std::endl;
char indexMask[12];
sprintf (indexMask, "%u", (unsigned int) i);
rec.setAttribute ("imask", indexMask); // Store index into mask.
// Add the new task to the vector, for immediate use.
// std::cout << "# adding to modified" << std::endl;
modified.push_back (rec);
std::cout << "# adding to pending" << std::endl;
// Add the new task to the DB.
// std::cout << "# adding to pending" << std::endl;
tdb.addT (rec);
}
if (i > now)
{
std::cout << "# already 1 instance into the future, stopping" << std::endl;
break;
}
++i;
}
// Only modify the parent if necessary.
if (changed)
{
// std::cout << "# modifying parent with mask=" << mask << std::endl;
t->setAttribute ("mask", mask);
tdb.modifyT (*t);
}
}
else
modified.push_back (*it);
modified.push_back (*t);
}
tasks = modified;
}
////////////////////////////////////////////////////////////////////////////////
// Determine a start date (due), an optional end date (until), and an increment
// period (recur). Then generate a set of corresponding dates.
void generateDueDates (T& parent, std::vector <Date>& allDue)
{
// Determine due date, recur period and until date.
Date due (atoi (parent.getAttribute ("due").c_str ()));
// std::cout << "# due=" << due.toString () << std::endl;
std::string recur = parent.getAttribute ("recur");
// std::cout << "# recur=" << recur << std::endl;
bool specificEnd = false;
Date until;
if (parent.getAttribute ("until") != "")
{
until = Date (atoi (parent.getAttribute ("until").c_str ()));
specificEnd = true;
}
// std::cout << "# specficEnd=" << (specificEnd ? "true" : "false") << std::endl;
// if (specificEnd)
// std::cout << "# until=" << until.toString () << std::endl;
Date now;
for (Date i = due; ; i = getNextRecurrence (i, recur))
{
allDue.push_back (i);
// std::cout << "# i=" << i.toString () << std::endl;
if (specificEnd && i > until)
break;
if (i > now)
// {
// std::cout << "# already 1 instance into the future, stopping" << std::endl;
break;
// }
}
}
////////////////////////////////////////////////////////////////////////////////
Date getNextRecurrence (Date& current, std::string& period)
{
int days = convertDuration (period);
int m = current.month ();
int d = current.day ();
int y = current.year ();
// Some periods are difficult, because they can be vague.
if (period == "monthly" ||
(isdigit (period[0]) && period[period.length () - 1] == 'm'))
if (period == "monthly")
{
int m = current.month ();
int d = current.day ();
int y = current.year ();
if (++m > 12)
{
m -= 12;
++y;
}
if (++m == 13) m = 1;
while (! Date::valid (m, d, y))
--d;
std::cout << "# next " << current.toString () << " + " << period << " = " << m << "/" << d << "/" << y << std::endl;
// std::cout << "# next " << current.toString () << " + " << period << " = " << m << "/" << d << "/" << y << std::endl;
return Date (m, d, y);
}
if (period == "bimonthly" ||
period == "semimonthly" ||
period == "quarterly" ||
period == "biannual" ||
period == "biyearly" ||
period == "semiannual" ||
(isdigit (period[0]) && (
period[period.length () - 1] == 'm' ||
period[period.length () - 1] == 'q')))
if (isdigit (period[0]) && period[period.length () - 1] == 'm')
{
// TODO lots of work here...
std::string numeric = period.substr (0, period.length () - 1);
int increment = atoi (numeric.c_str ());
m += increment;
while (m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
// std::cout << "# next " << current.toString () << " + " << period << " = " << m << "/" << d << "/" << y << std::endl;
return Date (m, d, y);
}
else if (period == "quarterly")
{
m += 3;
if (m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
// std::cout << "# next " << current.toString () << " + " << period << " = " << m << "/" << d << "/" << y << std::endl;
return Date (m, d, y);
}
else if (isdigit (period[0]) && period[period.length () - 1] == 'q')
{
std::string numeric = period.substr (0, period.length () - 1);
int increment = atoi (numeric.c_str ());
m += 3 * increment;
while (m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
// std::cout << "# next " << current.toString () << " + " << period << " = " << m << "/" << d << "/" << y << std::endl;
return Date (m, d, y);
}
else if (period == "semiannual")
{
m += 6;
if (m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
// std::cout << "# next " << current.toString () << " + " << period << " = " << m << "/" << d << "/" << y << std::endl;
return Date (m, d, y);
}
else if (period == "bimonthly")
{
m += 2;
if (m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
// std::cout << "# next " << current.toString () << " + " << period << " = " << m << "/" << d << "/" << y << std::endl;
return Date (m, d, y);
}
else if (period == "biannual" ||
period == "biyearly")
{
y += 2;
// std::cout << "# next " << current.toString () << " + " << period << " = " << m << "/" << d << "/" << y << std::endl;
return Date (m, d, y);
}
// If the period is an 'easy' one, add it to current, and we're done.
int days = convertDuration (period);
return current + (days * 86400);
}
////////////////////////////////////////////////////////////////////////////////
// When the status of a recurring child task changes, the parent task must
// update it's mask.
void updateRecurrenceMask (
TDB& tdb,
std::vector <T>& all,
T& task)
{
std::string parent = task.getAttribute ("parent");
// std::cout << "# updateRecurrenceMask of " << parent << std::endl;
if (parent != "")
{
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{
if (it->getUUID () == parent)
{
// std::cout << "# located parent task" << std::endl;
unsigned int index = atoi (task.getAttribute ("imask").c_str ());
// std::cout << "# child imask=" << index << std::endl;
std::string mask = it->getAttribute ("mask");
// std::cout << "# parent mask=" << mask << std::endl;
if (mask.length () > index)
{
mask[index] = (task.getStatus () == T::pending) ? '-'
: (task.getStatus () == T::completed) ? '+'
: (task.getStatus () == T::deleted) ? 'X'
: '?';
// std::cout << "# setting parent mask to=" << mask << std::endl;
it->setAttribute ("mask", mask);
// std::cout << "# tdb.modifyT (parent)" << std::endl;
tdb.modifyT (*it);
}
else
{
// std::cout << "# mask of insufficient length" << std::endl;
// std::cout << "# should never occur" << std::endl;
std::string mask;
for (unsigned int i = 0; i < index; ++i)
mask += "?";
mask += (task.getStatus () == T::pending) ? '-'
: (task.getStatus () == T::completed) ? '+'
: (task.getStatus () == T::deleted) ? 'X'
: '?';
}
return; // No point continuing the loop.
}
}
}
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -59,42 +59,44 @@ bool validDate (std::string&, Config&);
// task.cpp
void gatherNextTasks (const TDB&, T&, Config&, std::vector <T>&, std::vector <int>&);
void nag (const TDB&, T&, Config&);
void nag (TDB&, T&, Config&);
int getDueState (const std::string&);
void handleRecurrence (const TDB&, std::vector <T>&);
void handleRecurrence (TDB&, std::vector <T>&);
void generateDueDates (T&, std::vector <Date>&);
Date getNextRecurrence (Date&, std::string&);
void updateRecurrenceMask (TDB&, std::vector <T>&, T&);
// command.cpp
void handleAdd (const TDB&, T&, Config&);
void handleProjects (const TDB&, T&, Config&);
void handleTags (const TDB&, T&, Config&);
void handleUndelete (const TDB&, T&, Config&);
void handleProjects (TDB&, T&, Config&);
void handleTags (TDB&, T&, Config&);
void handleUndelete (TDB&, T&, Config&);
void handleVersion (Config&);
void handleExport (const TDB&, T&, Config&);
void handleDelete (const TDB&, T&, Config&);
void handleStart (const TDB&, T&, Config&);
void handleDone (const TDB&, T&, Config&);
void handleModify (const TDB&, T&, Config&);
void handleExport (TDB&, T&, Config&);
void handleDelete (TDB&, T&, Config&);
void handleStart (TDB&, T&, Config&);
void handleDone (TDB&, T&, Config&);
void handleModify (TDB&, T&, Config&);
void handleColor (Config&);
// report.cpp
void filter (std::vector<T>&, T&);
void handleList (const TDB&, T&, Config&);
void handleInfo (const TDB&, T&, Config&);
void handleLongList (const TDB&, T&, Config&);
void handleSmallList (const TDB&, T&, Config&);
void handleCompleted (const TDB&, T&, Config&);
void handleReportSummary (const TDB&, T&, Config&);
void handleReportNext (const TDB&, T&, Config&);
void handleReportHistory (const TDB&, T&, Config&);
void handleReportGHistory (const TDB&, T&, Config&);
void handleList (TDB&, T&, Config&);
void handleInfo (TDB&, T&, Config&);
void handleLongList (TDB&, T&, Config&);
void handleSmallList (TDB&, T&, Config&);
void handleCompleted (TDB&, T&, Config&);
void handleReportSummary (TDB&, T&, Config&);
void handleReportNext (TDB&, T&, Config&);
void handleReportHistory (TDB&, T&, Config&);
void handleReportGHistory (TDB&, T&, Config&);
void handleReportUsage (const TDB&, T&, Config&);
void handleReportCalendar (const TDB&, T&, Config&);
void handleReportActive (const TDB&, T&, Config&);
void handleReportOverdue (const TDB&, T&, Config&);
void handleReportStats (const TDB&, T&, Config&);
void handleReportOldest (const TDB&, T&, Config&);
void handleReportNewest (const TDB&, T&, Config&);
void handleReportCalendar (TDB&, T&, Config&);
void handleReportActive (TDB&, T&, Config&);
void handleReportOverdue (TDB&, T&, Config&);
void handleReportStats (TDB&, T&, Config&);
void handleReportOldest (TDB&, T&, Config&);
void handleReportNewest (TDB&, T&, Config&);
// util.cpp
bool confirm (const std::string&);

View file

@ -252,7 +252,6 @@ int convertDuration (std::string& input)
supported.push_back ("fortnight");
supported.push_back ("monthly");
supported.push_back ("bimonthly");
supported.push_back ("semimonthly");
supported.push_back ("quarterly");
supported.push_back ("biannual");
supported.push_back ("biyearly");
@ -268,7 +267,6 @@ int convertDuration (std::string& input)
if (found == "daily" || found == "day") return 1;
else if (found == "weekly" || found == "sennight") return 7;
else if (found == "biweekly" || found == "fortnight") return 14;
else if (found == "semimonthly") return 15;
else if (found == "monthly") return 30;
else if (found == "bimonthly") return 61;
else if (found == "quarterly") return 91;