mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
- Recurring tasks!
This commit is contained in:
parent
46ded4c026
commit
6f7b9b7d42
13 changed files with 395 additions and 188 deletions
2
AUTHORS
2
AUTHORS
|
@ -13,4 +13,6 @@ With thanks to:
|
|||
Nishiishii
|
||||
galvanizd
|
||||
H. İbrahim Güngör
|
||||
Stas Antons
|
||||
Andy Lester
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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@
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
2
src/T.h
2
src/T.h
|
@ -32,7 +32,7 @@
|
|||
#include <map>
|
||||
|
||||
// Length of longest line.
|
||||
#define T_LINE_MAX 8192
|
||||
#define T_LINE_MAX 32768
|
||||
|
||||
class T
|
||||
{
|
||||
|
|
31
src/TDB.cpp
31
src/TDB.cpp
|
@ -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++;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
18
src/TDB.h
18
src/TDB.h
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
334
src/task.cpp
334
src/task.cpp
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
52
src/task.h
52
src/task.h
|
@ -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&);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue