From 4f6c3afa3e785498ef82520866bde8697d399e86 Mon Sep 17 00:00:00 2001 From: Lukas Barth Date: Thu, 26 Jan 2017 19:37:48 +0100 Subject: [PATCH] Refactor history.monthly and history.annual --- src/commands/CmdHistory.cpp | 234 ++++++------------------------------ src/commands/CmdHistory.h | 82 ++++++++++++- 2 files changed, 112 insertions(+), 204 deletions(-) diff --git a/src/commands/CmdHistory.cpp b/src/commands/CmdHistory.cpp index 0e3044e04..c0f8fdf8c 100644 --- a/src/commands/CmdHistory.cpp +++ b/src/commands/CmdHistory.cpp @@ -39,11 +39,13 @@ extern Context context; //////////////////////////////////////////////////////////////////////////////// -CmdHistoryMonthly::CmdHistoryMonthly () +template +CmdHistoryBase::CmdHistoryBase () { - _keyword = "history.monthly"; - _usage = "task history.monthly"; - _description = STRING_CMD_HISTORY_USAGE_M; + _keyword = HistoryStrategy::keyword; + _usage = HistoryStrategy::usage; + _description = HistoryStrategy::description; + _read_only = true; _displays_id = false; _needs_gc = false; @@ -54,15 +56,16 @@ CmdHistoryMonthly::CmdHistoryMonthly () _category = Command::Category::graphs; } -//////////////////////////////////////////////////////////////////////////////// -int CmdHistoryMonthly::execute (std::string& output) +template +int CmdHistoryBase::execute (std::string& output) { int rc = 0; - std::map groups; // Represents any month with data - std::map addedGroup; // Additions by month - std::map completedGroup; // Completions by month - std::map deletedGroup; // Deletions by month + // TODO 'groups' should probably be std::set + std::map groups; // Represents any timeinterval with data + std::map addedGroup; // Additions by timeinterval + std::map completedGroup; // Completions by timeinterval + std::map deletedGroup; // Deletions by timeinterval // Apply filter. handleRecurrence (); @@ -78,7 +81,7 @@ int CmdHistoryMonthly::execute (std::string& output) if (task.has ("end")) end = Datetime (task.get_date ("end")); - time_t epoch = entry.startOfMonth ().toEpoch (); + time_t epoch = HistoryStrategy::getRelevantDate (entry).toEpoch (); groups[epoch] = 0; // Every task has an entry date, but exclude templates. @@ -88,7 +91,7 @@ int CmdHistoryMonthly::execute (std::string& output) // All deleted tasks have an end date. if (task.getStatus () == Task::deleted) { - epoch = end.startOfMonth ().toEpoch (); + epoch = HistoryStrategy::getRelevantDate (end).toEpoch (); groups[epoch] = 0; ++deletedGroup[epoch]; } @@ -96,7 +99,7 @@ int CmdHistoryMonthly::execute (std::string& output) // All completed tasks have an end date. else if (task.getStatus () == Task::completed) { - epoch = end.startOfMonth ().toEpoch (); + epoch = HistoryStrategy::getRelevantDate (end).toEpoch (); groups[epoch] = 0; ++completedGroup[epoch]; } @@ -107,8 +110,9 @@ int CmdHistoryMonthly::execute (std::string& output) if (context.config.getBoolean ("color")) view.forceColor (); view.width (context.getWidth ()); - view.add (STRING_CMD_HISTORY_YEAR); - view.add (STRING_CMD_HISTORY_MONTH); + + HistoryStrategy::setupTableDates (view); + view.add (STRING_CMD_HISTORY_ADDED, false); view.add (STRING_CMD_HISTORY_COMP, false); view.add (STRING_CMD_HISTORY_DEL, false); @@ -124,8 +128,8 @@ int CmdHistoryMonthly::execute (std::string& output) int totalCompleted = 0; int totalDeleted = 0; - int priorYear = 0; int row = 0; + time_t lastTime = 0; for (auto& i : groups) { row = view.addRow (); @@ -134,34 +138,26 @@ int CmdHistoryMonthly::execute (std::string& output) totalCompleted += completedGroup [i.first]; totalDeleted += deletedGroup [i.first]; - Datetime dt (i.first); - int m, d, y; - dt.toYMD (y, m, d); - - if (y != priorYear) - { - view.set (row, 0, y); - priorYear = y; - } - view.set (row, 1, Datetime::monthName(m)); + HistoryStrategy::insertRowDate (view, row, i.first, lastTime); + lastTime = i.first; int net = 0; if (addedGroup.find (i.first) != addedGroup.end ()) { - view.set (row, 2, addedGroup[i.first]); + view.set (row, HistoryStrategy::dateFieldCount + 0, addedGroup[i.first]); net +=addedGroup[i.first]; } if (completedGroup.find (i.first) != completedGroup.end ()) { - view.set (row, 3, completedGroup[i.first]); + view.set (row, HistoryStrategy::dateFieldCount + 1, completedGroup[i.first]); net -= completedGroup[i.first]; } if (deletedGroup.find (i.first) != deletedGroup.end ()) { - view.set (row, 4, deletedGroup[i.first]); + view.set (row, HistoryStrategy::dateFieldCount + 2, deletedGroup[i.first]); net -= deletedGroup[i.first]; } @@ -171,24 +167,24 @@ int CmdHistoryMonthly::execute (std::string& output) ? Color (Color::red) : Color (Color::green); - view.set (row, 5, net, net_color); + view.set (row, HistoryStrategy::dateFieldCount + 3, net, net_color); } if (view.rows ()) { - row = view.addRow (); - view.set (row, 0, ' '); + row = view.addRow(); + view.set (row, 1, " "); row = view.addRow (); Color row_color; if (context.color ()) row_color = Color (Color::nocolor, Color::nocolor, false, true, false); - view.set (row, 1, STRING_CMD_HISTORY_AVERAGE, row_color); - view.set (row, 2, totalAdded / (view.rows () - 2), row_color); - view.set (row, 3, totalCompleted / (view.rows () - 2), row_color); - view.set (row, 4, totalDeleted / (view.rows () - 2), row_color); - view.set (row, 5, (totalAdded - totalCompleted - totalDeleted) / (view.rows () - 2), row_color); + view.set (row, HistoryStrategy::dateFieldCount - 1, STRING_CMD_HISTORY_AVERAGE, row_color); + view.set (row, HistoryStrategy::dateFieldCount + 0, totalAdded / (view.rows () - 2), row_color); + view.set (row, HistoryStrategy::dateFieldCount + 1, totalCompleted / (view.rows () - 2), row_color); + view.set (row, HistoryStrategy::dateFieldCount + 2, totalDeleted / (view.rows () - 2), row_color); + view.set (row, HistoryStrategy::dateFieldCount + 3, (totalAdded - totalCompleted - totalDeleted) / (view.rows () - 2), row_color); } std::stringstream out; @@ -206,169 +202,11 @@ int CmdHistoryMonthly::execute (std::string& output) return rc; } -//////////////////////////////////////////////////////////////////////////////// -CmdHistoryAnnual::CmdHistoryAnnual () -{ - _keyword = "history.annual"; - _usage = "task history.annual"; - _description = STRING_CMD_HISTORY_USAGE_A; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = true; - _accepts_modifications = false; - _accepts_miscellaneous = false; - _category = Command::Category::graphs; -} +// Explicit instantiations, avoiding cpp-inclusion or implementation in header +template class CmdHistoryBase; +template class CmdHistoryBase; -//////////////////////////////////////////////////////////////////////////////// -int CmdHistoryAnnual::execute (std::string& output) -{ - int rc = 0; - std::map groups; // Represents any month with data - std::map addedGroup; // Additions by month - std::map completedGroup; // Completions by month - std::map deletedGroup; // Deletions by month - // Apply filter. - handleRecurrence (); - Filter filter; - std::vector filtered; - filter.subset (filtered); - - for (auto& task : filtered) - { - Datetime entry (task.get_date ("entry")); - - Datetime end; - if (task.has ("end")) - end = Datetime (task.get_date ("end")); - - time_t epoch = entry.startOfYear ().toEpoch (); - groups[epoch] = 0; - - // Every task has an entry date, but exclude templates. - if (task.getStatus () != Task::recurring) - ++addedGroup[epoch]; - - // All deleted tasks have an end date. - if (task.getStatus () == Task::deleted) - { - epoch = end.startOfYear ().toEpoch (); - groups[epoch] = 0; - ++deletedGroup[epoch]; - } - - // All completed tasks have an end date. - else if (task.getStatus () == Task::completed) - { - epoch = end.startOfYear ().toEpoch (); - groups[epoch] = 0; - ++completedGroup[epoch]; - } - } - - // Now build the view. - Table view; - if (context.config.getBoolean ("color")) - view.forceColor (); - view.width (context.getWidth ()); - view.add (STRING_CMD_HISTORY_YEAR); - view.add (STRING_CMD_HISTORY_ADDED, false); - view.add (STRING_CMD_HISTORY_COMP, false); - view.add (STRING_CMD_HISTORY_DEL, false); - view.add (STRING_CMD_HISTORY_NET, false); - - if (context.color ()) - { - Color label (context.config.get ("color.label")); - view.colorHeader (label); - } - - int totalAdded = 0; - int totalCompleted = 0; - int totalDeleted = 0; - - int priorYear = 0; - int row = 0; - for (auto& i : groups) - { - row = view.addRow (); - - totalAdded += addedGroup [i.first]; - totalCompleted += completedGroup [i.first]; - totalDeleted += deletedGroup [i.first]; - - Datetime dt (i.first); - int m, d, y; - dt.toYMD (y, m, d); - - if (y != priorYear) - { - view.set (row, 0, y); - priorYear = y; - } - - int net = 0; - - if (addedGroup.find (i.first) != addedGroup.end ()) - { - view.set (row, 1, addedGroup[i.first]); - net +=addedGroup[i.first]; - } - - if (completedGroup.find (i.first) != completedGroup.end ()) - { - view.set (row, 2, completedGroup[i.first]); - net -= completedGroup[i.first]; - } - - if (deletedGroup.find (i.first) != deletedGroup.end ()) - { - view.set (row, 3, deletedGroup[i.first]); - net -= deletedGroup[i.first]; - } - - Color net_color; - if (context.color () && net) - net_color = net > 0 - ? Color (Color::red) - : Color (Color::green); - - view.set (row, 4, net, net_color); - } - - if (view.rows ()) - { - view.addRow (); - row = view.addRow (); - - Color row_color; - if (context.color ()) - row_color = Color (Color::nocolor, Color::nocolor, false, true, false); - - view.set (row, 0, STRING_CMD_HISTORY_AVERAGE, row_color); - view.set (row, 1, totalAdded / (view.rows () - 2), row_color); - view.set (row, 2, totalCompleted / (view.rows () - 2), row_color); - view.set (row, 3, totalDeleted / (view.rows () - 2), row_color); - view.set (row, 4, (totalAdded - totalCompleted - totalDeleted) / (view.rows () - 2), row_color); - } - - std::stringstream out; - if (view.rows ()) - out << optionalBlankLine () - << view.render () - << '\n'; - else - { - context.footnote (STRING_FEEDBACK_NO_TASKS); - rc = 1; - } - - output = out.str (); - return rc; -} //////////////////////////////////////////////////////////////////////////////// CmdGHistoryMonthly::CmdGHistoryMonthly () diff --git a/src/commands/CmdHistory.h b/src/commands/CmdHistory.h index b40006d8f..d66f320c2 100644 --- a/src/commands/CmdHistory.h +++ b/src/commands/CmdHistory.h @@ -29,21 +29,91 @@ #include #include +#include +#include +#include -class CmdHistoryMonthly : public Command +template +class CmdHistoryBase : public Command { public: - CmdHistoryMonthly (); + CmdHistoryBase (); int execute (std::string&); }; -class CmdHistoryAnnual : public Command -{ +////////////////////////////////////////////////////////////////////////////i +class MonthlyHistoryStrategy { public: - CmdHistoryAnnual (); - int execute (std::string&); + static Datetime getRelevantDate (const Datetime & dt) { + return dt.startOfMonth (); + } + + static void setupTableDates (Table & view) { + view.add (STRING_CMD_HISTORY_YEAR); + view.add (STRING_CMD_HISTORY_MONTH); + } + + static void insertRowDate (Table & view, int row, time_t rowTime, time_t lastTime) { + Datetime dt (rowTime); + int m, d, y; + dt.toYMD (y, m, d); + + Datetime last_dt (lastTime); + int last_m, last_d, last_y; + last_dt.toYMD (last_y, last_m, last_d); + + if (y != last_y) + { + view.set (row, 0, y); + } + view.set (row, 1, Datetime::monthName(m)); + } + + static constexpr const char * keyword = "history.monthly"; + static constexpr const char * usage = "task history.monthly"; + static constexpr const char * description = STRING_CMD_HISTORY_USAGE_M; + static constexpr unsigned int dateFieldCount = 2; }; +typedef CmdHistoryBase CmdHistoryMonthly; +//////////////////////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////////////////////////i +class AnnualHistoryStrategy { +public: + static Datetime getRelevantDate (const Datetime & dt) { + return dt.startOfYear (); + } + + static void setupTableDates (Table & view) { + view.add (STRING_CMD_HISTORY_YEAR); + } + + static void insertRowDate (Table & view, int row, time_t rowTime, time_t lastTime) { + Datetime dt (rowTime); + int m, d, y; + dt.toYMD (y, m, d); + + Datetime last_dt (lastTime); + int last_m, last_d, last_y; + last_dt.toYMD (last_y, last_m, last_d); + + if (y != last_y) + { + view.set (row, 0, y); + } + } + + static constexpr const char * keyword = "history.annual"; + static constexpr const char * usage = "task history.annual"; + static constexpr const char * description = STRING_CMD_HISTORY_USAGE_A; + static constexpr unsigned int dateFieldCount = 1; +}; + +typedef CmdHistoryBase CmdHistoryAnnual; +//////////////////////////////////////////////////////////////////////////// + class CmdGHistoryMonthly : public Command { public: