From fb5fe5f5b44c2d3e1ac26a03205a40f944322dfa Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 8 Jun 2009 00:54:49 -0400 Subject: [PATCH] Integration - "projects" report - "projects" report converted to 1.8.0. - Relocated code from task.cpp to recur.cpp to allow unit tests to link without includign task.cpp and therefore main. - Removed obsolete sandbox directory. - Fixed bug where Config::load deleted the pre-loaded custom reports. - Fixed bug where Cmd::valid failed to include custom reports properly. --- src/Cmd.cpp | 10 +- src/Config.cpp | 2 - src/Context.cpp | 28 +-- src/Makefile.am | 2 +- src/command.cpp | 19 +- src/recur.cpp | 459 +++++++++++++++++++++++++++++++++++++++++ src/sandbox/.gitignore | 1 - src/sandbox/Makefile | 26 --- src/sandbox/main.cpp | 38 ---- src/task.cpp | 404 ------------------------------------ src/task.h | 16 +- src/tests/Makefile | 5 +- 12 files changed, 494 insertions(+), 516 deletions(-) create mode 100644 src/recur.cpp delete mode 100644 src/sandbox/.gitignore delete mode 100644 src/sandbox/Makefile delete mode 100644 src/sandbox/main.cpp diff --git a/src/Cmd.cpp b/src/Cmd.cpp index 93b382c96..06cf3cfe9 100644 --- a/src/Cmd.cpp +++ b/src/Cmd.cpp @@ -25,7 +25,6 @@ // //////////////////////////////////////////////////////////////////////////////// -#include #include "Cmd.h" #include "Context.h" #include "util.h" @@ -59,14 +58,9 @@ bool Cmd::valid (const std::string& input) loadCommands (); loadCustomReports (); - std::string candidate = lowerCase (input); - std::vector matches; - autoComplete (candidate, commands, matches); - if (0 == matches.size ()) - return false; - - return true; + autoComplete (lowerCase (input), commands, matches); + return matches.size () == 1 ? true : false; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Config.cpp b/src/Config.cpp index 2614890fd..094f95caa 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -85,8 +85,6 @@ Config::Config (const std::string& file) // not tolerated, but blank lines and comments starting with # are allowed. bool Config::load (const std::string& file) { - this->clear (); - std::ifstream in; in.open (file.c_str (), std::ifstream::in); if (in.good ()) diff --git a/src/Context.cpp b/src/Context.cpp index f405245b9..2663fd197 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -92,17 +92,13 @@ void Context::initialize (int argc, char** argv) if (locale != "") stringtable.load (location + "/strings." + locale); - // TODO Handle "--version, -v" right here. + // TODO Handle "--version, -v" right here? // init TDB. std::vector all; split (all, location, ','); foreach (path, all) tdb.location (expandPath (*path)); - - // TODO Load pending.data. - // TODO Load completed.data. - // TODO Load deleted.data. } //////////////////////////////////////////////////////////////////////////////// @@ -112,9 +108,7 @@ int Context::run () try { parse (); // Parse command line. - // TODO tdb.load (Filter); dispatch (); // Dispatch to command handlers. - // TODO Auto gc. shadow (); // Auto shadow update. } @@ -157,20 +151,19 @@ void Context::dispatch () split (args, defaultCommand, ' '); std::cout << "[task " << defaultCommand << "]" << std::endl; } +*/ - loadCustomReports (); - - std::string command; - T task; - parse (args, command, task); - +/* bool gcMod = false; // Change occurred by way of gc. bool cmdMod = false; // Change occurred by way of command type. +*/ std::string out; - +/* // Read-only commands with no side effects. if (command == "export") { out = handleExport (tdb, task); } - else if (command == "projects") { out = handleProjects (tdb, task); } +*/ + if (cmd.command == "projects") { out = handleProjects (); } +/* else if (command == "tags") { out = handleTags (tdb, task); } else if (command == "info") { out = handleInfo (tdb, task); } else if (command == "stats") { out = handleReportStats (tdb, task); } @@ -212,10 +205,9 @@ void Context::dispatch () // and if an actual change occurred (gcMod || cmdMod). if (shadow && (gcMod || cmdMod)) updateShadowFile (tdb); - - return out; */ - throw std::string ("unimplemented Context::dispatch"); + + std::cout << out; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Makefile.am b/src/Makefile.am index 9e31ed01f..0a35472a6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,7 +4,7 @@ task_SOURCES = Config.cpp Date.cpp Record.cpp T.cpp T2.cpp TDB.cpp TDB2.cpp \ Duration.cpp StringTable.cpp Location.cpp Subst.cpp Keymap.cpp \ Nibbler.cpp Context.cpp Cmd.cpp color.cpp parse.cpp task.cpp \ edit.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp \ - import.cpp interactive.cpp \ + import.cpp interactive.cpp recur.cpp \ Config.h Date.h Record.h T.h TDB.h Att.h Filter.h Sequence.h \ Table.h Grid.h Timer.h Duration.h StringTable.h Location.h \ Subst.h Keymap.h Nibbler.h Context.h Cmd.h color.h task.h diff --git a/src/command.cpp b/src/command.cpp index 5a8ad517e..099e21199 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -92,22 +92,21 @@ std::string handleAdd (TDB& tdb, T& task) } //////////////////////////////////////////////////////////////////////////////// -std::string handleProjects (TDB& tdb, T& task) +std::string handleProjects () { std::stringstream out; - // Get all the tasks, including deleted ones. - std::vector tasks; - tdb.pendingT (tasks); + context.filter.push_back (Att ("status", "pending")); + + std::vector tasks; + context.tdb.lock (context.config.get ("locking", true)); + int quantity = context.tdb.load (tasks, context.filter); // Scan all the tasks for their project name, building a map using project // names as keys. std::map unique; - for (unsigned int i = 0; i < tasks.size (); ++i) - { - T task (tasks[i]); - unique[task.getAttribute ("project")] += 1; - } + foreach (t, tasks) + unique[t->get ("project")] += 1; if (unique.size ()) { @@ -138,12 +137,14 @@ std::string handleProjects (TDB& tdb, T& task) << optionalBlankLine () << unique.size () << (unique.size () == 1 ? " project" : " projects") + << " (" << quantity << (quantity == 1 ? " task" : " tasks") << ")" << std::endl; } else out << "No projects." << std::endl; + context.tdb.unlock (); return out.str (); } diff --git a/src/recur.cpp b/src/recur.cpp new file mode 100644 index 000000000..b39e3d7bf --- /dev/null +++ b/src/recur.cpp @@ -0,0 +1,459 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2009, Paul Beckingham. +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the +// +// Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, +// Boston, MA +// 02110-1301 +// USA +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Context.h" +#include "Date.h" +#include "Duration.h" +#include "Table.h" +#include "TDB.h" +#include "T.h" +#include "text.h" +#include "util.h" +#include "task.h" + +#ifdef HAVE_LIBNCURSES +#include +#endif + +// Global context for use by all. +extern Context context; + +//////////////////////////////////////////////////////////////////////////////// +// 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 & tasks) +{ + std::vector modified; + + // Look at all tasks and find any recurring ones. + foreach (t, tasks) + { + if (t->getStatus () == T::recurring) + { + // Generate a list of due dates for this recurring task, regardless of + // the mask. + std::vector due; + if (!generateDueDates (*t, due)) + { + std::cout << "Task " + << t->getUUID () + << " (" + << trim (t->getDescription ()) + << ") is past its 'until' date, and has be deleted" << std::endl; + + // Determine the end date. + char endTime[16]; + sprintf (endTime, "%u", (unsigned int) time (NULL)); + t->setAttribute ("end", endTime); + t->setStatus (T::deleted); + tdb.modifyT (*t); + continue; + } + + // Get the mask from the parent task. + std::string mask = t->getAttribute ("mask"); + + // Iterate over the due dates, and check each against the mask. + bool changed = false; + unsigned int i = 0; + foreach (d, due) + { + if (mask.length () <= i) + { + mask += '-'; + changed = true; + + 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) d->toEpoch ()); + rec.setAttribute ("due", dueDate); // Store generated due date. + + 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. + modified.push_back (rec); + + // Add the new task to the DB. + tdb.addT (rec); + } + + ++i; + } + + // Only modify the parent if necessary. + if (changed) + { + t->setAttribute ("mask", mask); + tdb.modifyT (*t); + } + } + else + 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. +// +// Returns false if the parent recurring task is depleted. +bool generateDueDates (T& parent, std::vector & allDue) +{ + // Determine due date, recur period and until date. + Date due (atoi (parent.getAttribute ("due").c_str ())); + std::string recur = parent.getAttribute ("recur"); + + bool specificEnd = false; + Date until; + if (parent.getAttribute ("until") != "") + { + until = Date (atoi (parent.getAttribute ("until").c_str ())); + specificEnd = true; + } + + Date now; + for (Date i = due; ; i = getNextRecurrence (i, recur)) + { + allDue.push_back (i); + + if (specificEnd && i > until) + { + // If i > until, it means there are no more tasks to generate, and if the + // parent mask contains all + or X, then there never will be another task + // to generate, and this parent task may be safely reaped. + std::string mask = parent.getAttribute ("mask"); + if (mask.length () == allDue.size () && + mask.find ('-') == std::string::npos) + return false; + + return true; + } + + if (i > now) + return true; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +Date getNextRecurrence (Date& current, std::string& 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") + { + if (++m > 12) + { + m -= 12; + ++y; + } + + while (! Date::valid (m, d, y)) + --d; + + return Date (m, d, y); + } + + if (period == "weekdays") + { + int dow = current.dayOfWeek (); + int days; + + if (dow == 5) days = 3; + else if (dow == 6) days = 2; + else days = 1; + + return current + (days * 86400); + } + + if (isdigit (period[0]) && period[period.length () - 1] == 'm') + { + 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; + + return Date (m, d, y); + } + + else if (period == "quarterly") + { + m += 3; + if (m > 12) + { + m -= 12; + ++y; + } + + while (! Date::valid (m, d, y)) + --d; + + 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; + + return Date (m, d, y); + } + + else if (period == "semiannual") + { + m += 6; + if (m > 12) + { + m -= 12; + ++y; + } + + while (! Date::valid (m, d, y)) + --d; + + return Date (m, d, y); + } + + else if (period == "bimonthly") + { + m += 2; + if (m > 12) + { + m -= 12; + ++y; + } + + while (! Date::valid (m, d, y)) + --d; + + return Date (m, d, y); + } + + else if (period == "biannual" || + period == "biyearly") + { + y += 2; + + return Date (m, d, y); + } + + else if (period == "annual" || + period == "yearly") + { + y += 1; + + // If the due data just happens to be 2/29 in a leap year, then simply + // incrementing y is going to create an invalid date. + if (m == 2 && d == 29) + d = 28; + + return Date (m, d, y); + } + + // If the period is an 'easy' one, add it to current, and we're done. + int days = 0; + try { Duration du (period); days = du; } + catch (...) { days = 0; } + + 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 & all, + T& task) +{ + std::string parent = task.getAttribute ("parent"); + if (parent != "") + { + std::vector ::iterator it; + for (it = all.begin (); it != all.end (); ++it) + { + if (it->getUUID () == parent) + { + unsigned int index = atoi (task.getAttribute ("imask").c_str ()); + std::string mask = it->getAttribute ("mask"); + if (mask.length () > index) + { + mask[index] = (task.getStatus () == T::pending) ? '-' + : (task.getStatus () == T::completed) ? '+' + : (task.getStatus () == T::deleted) ? 'X' + : '?'; + + it->setAttribute ("mask", mask); + tdb.modifyT (*it); + } + else + { + 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. + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Determines whether a task is overdue. Returns +// 0 = not due at all +// 1 = imminent +// 2 = overdue +int getDueState (const std::string& due) +{ + if (due.length ()) + { + Date dt (::atoi (due.c_str ())); + + // rightNow is the current date + time. + Date rightNow; + Date midnight (rightNow.month (), rightNow.day (), rightNow.year ()); + + if (dt < midnight) + return 2; + + Date nextweek = midnight + 7 * 86400; + if (dt < nextweek) + return 1; + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +void nag (TDB& tdb, T& task) +{ + std::string nagMessage = context.config.get ("nag", std::string ("")); + if (nagMessage != "") + { + // Load all pending tasks. + std::vector pending; + tdb.allPendingT (pending); + + // Counters. + int overdue = 0; + int high = 0; + int medium = 0; + int low = 0; + bool isOverdue = false; + char pri = ' '; + + // Scan all pending tasks. + foreach (t, pending) + { + if (t->getId () == task.getId ()) + { + if (getDueState (t->getAttribute ("due")) == 2) + isOverdue = true; + + std::string priority = t->getAttribute ("priority"); + if (priority.length ()) + pri = priority[0]; + } + else if (t->getStatus () == T::pending) + { + if (getDueState (t->getAttribute ("due")) == 2) + overdue++; + + std::string priority = t->getAttribute ("priority"); + if (priority.length ()) + { + switch (priority[0]) + { + case 'H': high++; break; + case 'M': medium++; break; + case 'L': low++; break; + } + } + } + } + + // General form is "if there are no more deserving tasks", suppress the nag. + if (isOverdue ) return; + if (pri == 'H' && !overdue ) return; + if (pri == 'M' && !overdue && !high ) return; + if (pri == 'L' && !overdue && !high && !medium ) return; + if (pri == ' ' && !overdue && !high && !medium && !low) return; + + // All the excuses are made, all that remains is to nag the user. + std::cout << nagMessage << std::endl; + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/sandbox/.gitignore b/src/sandbox/.gitignore deleted file mode 100644 index 625934097..000000000 --- a/src/sandbox/.gitignore +++ /dev/null @@ -1 +0,0 @@ -1.8 diff --git a/src/sandbox/Makefile b/src/sandbox/Makefile deleted file mode 100644 index 0ea2b37d7..000000000 --- a/src/sandbox/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -PROJECT = 1.8 -CFLAGS = -I. -I.. -Wall -pedantic -ggdb3 -fno-rtti -fno-stack-check -LFLAGS = -LIBS = -OBJECTS = main.o ../Context.o ../TDB2.o ../T2.o ../Sequence.o ../Filter.o \ - ../Att.o ../Keymap.o ../Record.o ../StringTable.o ../Location.o \ - ../util.o ../text.o ../Date.o ../Config.o ../Subst.o ../Nibbler.o \ - ../parse.o ../Duration.o ../T.o ../Cmd.o - -all: $(PROJECT) - -install: $(PROJECT) - @echo unimplemented - -test: $(PROJECT) - @echo unimplemented - -clean: - -rm *.o $(PROJECT) - -.cpp.o: $(INCLUDE) - g++ -c $(CFLAGS) $< - -$(PROJECT): $(OBJECTS) - g++ $(OBJECTS) $(LFLAGS) $(LIBS) -o $(PROJECT) - diff --git a/src/sandbox/main.cpp b/src/sandbox/main.cpp deleted file mode 100644 index 4ddb9d388..000000000 --- a/src/sandbox/main.cpp +++ /dev/null @@ -1,38 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -#include -#include -#include "Context.h" - -Context context; - -int main (int argc, char** argv) -{ - try - { - context.initialize (argc, argv); - context.tdb.lock (context.config.get ("locking", true)); - - context.filter.push_back (Att ("priority", "L")); - - std::vector tasks; - int quantity = context.tdb.load (tasks, context.filter); - std::cout << "# " << quantity << " <-- context.tdb.load" << std::endl; - - context.tdb.unlock (); - return 0; - } - - catch (std::string e) - { - std::cerr << e << std::endl; - } - - catch (...) - { - std::cerr << "task internal error." << std::endl; - } - - return -1; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/task.cpp b/src/task.cpp index dc1a56072..c67434893 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -352,409 +352,6 @@ int main (int argc, char** argv) return status; } -//////////////////////////////////////////////////////////////////////////////// -void nag (TDB& tdb, T& task) -{ - std::string nagMessage = context.config.get ("nag", std::string ("")); - if (nagMessage != "") - { - // Load all pending tasks. - std::vector pending; - tdb.allPendingT (pending); - - // Counters. - int overdue = 0; - int high = 0; - int medium = 0; - int low = 0; - bool isOverdue = false; - char pri = ' '; - - // Scan all pending tasks. - foreach (t, pending) - { - if (t->getId () == task.getId ()) - { - if (getDueState (t->getAttribute ("due")) == 2) - isOverdue = true; - - std::string priority = t->getAttribute ("priority"); - if (priority.length ()) - pri = priority[0]; - } - else if (t->getStatus () == T::pending) - { - if (getDueState (t->getAttribute ("due")) == 2) - overdue++; - - std::string priority = t->getAttribute ("priority"); - if (priority.length ()) - { - switch (priority[0]) - { - case 'H': high++; break; - case 'M': medium++; break; - case 'L': low++; break; - } - } - } - } - - // General form is "if there are no more deserving tasks", suppress the nag. - if (isOverdue ) return; - if (pri == 'H' && !overdue ) return; - if (pri == 'M' && !overdue && !high ) return; - if (pri == 'L' && !overdue && !high && !medium ) return; - if (pri == ' ' && !overdue && !high && !medium && !low) return; - - // All the excuses are made, all that remains is to nag the user. - std::cout << nagMessage << std::endl; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Determines whether a task is overdue. Returns -// 0 = not due at all -// 1 = imminent -// 2 = overdue -int getDueState (const std::string& due) -{ - if (due.length ()) - { - Date dt (::atoi (due.c_str ())); - - // rightNow is the current date + time. - Date rightNow; - Date midnight (rightNow.month (), rightNow.day (), rightNow.year ()); - - if (dt < midnight) - return 2; - - Date nextweek = midnight + 7 * 86400; - if (dt < nextweek) - return 1; - } - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// -// 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 & tasks) -{ - std::vector modified; - - // Look at all tasks and find any recurring ones. - foreach (t, tasks) - { - if (t->getStatus () == T::recurring) - { - // Generate a list of due dates for this recurring task, regardless of - // the mask. - std::vector due; - if (!generateDueDates (*t, due)) - { - std::cout << "Task " - << t->getUUID () - << " (" - << trim (t->getDescription ()) - << ") is past its 'until' date, and has be deleted" << std::endl; - - // Determine the end date. - char endTime[16]; - sprintf (endTime, "%u", (unsigned int) time (NULL)); - t->setAttribute ("end", endTime); - t->setStatus (T::deleted); - tdb.modifyT (*t); - continue; - } - - // Get the mask from the parent task. - std::string mask = t->getAttribute ("mask"); - - // Iterate over the due dates, and check each against the mask. - bool changed = false; - unsigned int i = 0; - foreach (d, due) - { - if (mask.length () <= i) - { - mask += '-'; - changed = true; - - 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) d->toEpoch ()); - rec.setAttribute ("due", dueDate); // Store generated due date. - - 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. - modified.push_back (rec); - - // Add the new task to the DB. - tdb.addT (rec); - } - - ++i; - } - - // Only modify the parent if necessary. - if (changed) - { - t->setAttribute ("mask", mask); - tdb.modifyT (*t); - } - } - else - 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. -// -// Returns false if the parent recurring task is depleted. -bool generateDueDates (T& parent, std::vector & allDue) -{ - // Determine due date, recur period and until date. - Date due (atoi (parent.getAttribute ("due").c_str ())); - std::string recur = parent.getAttribute ("recur"); - - bool specificEnd = false; - Date until; - if (parent.getAttribute ("until") != "") - { - until = Date (atoi (parent.getAttribute ("until").c_str ())); - specificEnd = true; - } - - Date now; - for (Date i = due; ; i = getNextRecurrence (i, recur)) - { - allDue.push_back (i); - - if (specificEnd && i > until) - { - // If i > until, it means there are no more tasks to generate, and if the - // parent mask contains all + or X, then there never will be another task - // to generate, and this parent task may be safely reaped. - std::string mask = parent.getAttribute ("mask"); - if (mask.length () == allDue.size () && - mask.find ('-') == std::string::npos) - return false; - - return true; - } - - if (i > now) - return true; - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -Date getNextRecurrence (Date& current, std::string& 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") - { - if (++m > 12) - { - m -= 12; - ++y; - } - - while (! Date::valid (m, d, y)) - --d; - - return Date (m, d, y); - } - - if (period == "weekdays") - { - int dow = current.dayOfWeek (); - int days; - - if (dow == 5) days = 3; - else if (dow == 6) days = 2; - else days = 1; - - return current + (days * 86400); - } - - if (isdigit (period[0]) && period[period.length () - 1] == 'm') - { - 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; - - return Date (m, d, y); - } - - else if (period == "quarterly") - { - m += 3; - if (m > 12) - { - m -= 12; - ++y; - } - - while (! Date::valid (m, d, y)) - --d; - - 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; - - return Date (m, d, y); - } - - else if (period == "semiannual") - { - m += 6; - if (m > 12) - { - m -= 12; - ++y; - } - - while (! Date::valid (m, d, y)) - --d; - - return Date (m, d, y); - } - - else if (period == "bimonthly") - { - m += 2; - if (m > 12) - { - m -= 12; - ++y; - } - - while (! Date::valid (m, d, y)) - --d; - - return Date (m, d, y); - } - - else if (period == "biannual" || - period == "biyearly") - { - y += 2; - - return Date (m, d, y); - } - - else if (period == "annual" || - period == "yearly") - { - y += 1; - - // If the due data just happens to be 2/29 in a leap year, then simply - // incrementing y is going to create an invalid date. - if (m == 2 && d == 29) - d = 28; - - return Date (m, d, y); - } - - // If the period is an 'easy' one, add it to current, and we're done. - int days = 0; - try { Duration du (period); days = du; } - catch (...) { days = 0; } - - 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 & all, - T& task) -{ - std::string parent = task.getAttribute ("parent"); - if (parent != "") - { - std::vector ::iterator it; - for (it = all.begin (); it != all.end (); ++it) - { - if (it->getUUID () == parent) - { - unsigned int index = atoi (task.getAttribute ("imask").c_str ()); - std::string mask = it->getAttribute ("mask"); - if (mask.length () > index) - { - mask[index] = (task.getStatus () == T::pending) ? '-' - : (task.getStatus () == T::completed) ? '+' - : (task.getStatus () == T::deleted) ? 'X' - : '?'; - - it->setAttribute ("mask", mask); - tdb.modifyT (*it); - } - else - { - 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. - } - } - } -} - //////////////////////////////////////////////////////////////////////////////// void updateShadowFile (TDB& tdb) { @@ -856,7 +453,6 @@ std::string runTaskCommand ( // Read-only commands with no side effects. if (command == "export") { out = handleExport (tdb, task); } - else if (command == "projects") { out = handleProjects (tdb, task); } else if (command == "tags") { out = handleTags (tdb, task); } else if (command == "info") { out = handleInfo (tdb, task); } else if (command == "stats") { out = handleReportStats (tdb, task); } diff --git a/src/task.h b/src/task.h index 8acd218bf..acca78aad 100644 --- a/src/task.h +++ b/src/task.h @@ -50,23 +50,25 @@ void allCustomReports (std::vector &); // task.cpp void gatherNextTasks (const TDB&, T&, std::vector &, std::vector &); -void nag (TDB&, T&); -int getDueState (const std::string&); -void handleRecurrence (TDB&, std::vector &); -bool generateDueDates (T&, std::vector &); -Date getNextRecurrence (Date&, std::string&); -void updateRecurrenceMask (TDB&, std::vector &, T&); void onChangeCallback (); std::string runTaskCommand (int, char**, TDB&, bool gc = true, bool shadow = true); std::string runTaskCommand (std::vector &, TDB&, bool gc = false, bool shadow = false); +// recur.cpp +void handleRecurrence (TDB&, std::vector &); +Date getNextRecurrence (Date&, std::string&); +bool generateDueDates (T&, std::vector &); +void updateRecurrenceMask (TDB&, std::vector &, T&); +int getDueState (const std::string&); +void nag (TDB&, T&); + // command.cpp std::string handleAdd (TDB&, T&); std::string handleAppend (TDB&, T&); std::string handleExport (TDB&, T&); std::string handleDone (TDB&, T&); std::string handleModify (TDB&, T&); -std::string handleProjects (TDB&, T&); +std::string handleProjects (); std::string handleTags (TDB&, T&); std::string handleUndelete (TDB&, T&); std::string handleVersion (); diff --git a/src/tests/Makefile b/src/tests/Makefile index dfc1f6f72..a814fde36 100644 --- a/src/tests/Makefile +++ b/src/tests/Makefile @@ -2,11 +2,12 @@ PROJECT = t.t t2.t tdb.t date.t duration.t t.benchmark.t text.t autocomplete.t \ parse.t seq.t att.t stringtable.t record.t nibbler.t subst.t filt.t \ cmd.t config.t CFLAGS = -I. -I.. -Wall -pedantic -ggdb3 -fno-rtti -LFLAGS = -L/usr/local/lib +LFLAGS = -L/usr/local/lib -lncurses OBJECTS = ../TDB.o ../TDB2.o ../T.o ../T2.o ../parse.o ../text.o ../Date.o \ ../Duration.o ../util.o ../Config.o ../Sequence.o ../Att.o \ ../Record.o ../StringTable.o ../Subst.o ../Nibbler.o ../Location.o \ - ../Filter.o ../Context.o ../Keymap.o ../Cmd.o + ../Filter.o ../Context.o ../Keymap.o ../Cmd.o ../command.o \ + ../report.o ../Table.o ../Grid.o ../color.o ../rules.o ../recur.o all: $(PROJECT)