diff --git a/ChangeLog b/ChangeLog index bc73770bd..c91e2779c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,7 @@ represents a feature release, and the Z represents a patch. reports have been run (and therefore TDB::gc run) + Added averages to the "task history" report + Added ability to override ~/.taskrc with rc: + + Added bar chart history report "task ghistory" + Bug: Fixed where Esc[0m sequences were being emitted for no good reason + Bug: Fixed underlined table headers when color is turned off diff --git a/TUTORIAL b/TUTORIAL index e3f4448d1..991dc863e 100644 --- a/TUTORIAL +++ b/TUTORIAL @@ -415,10 +415,12 @@ with no arguments will generate a help message that lists all these commands. % task history - Year Month Added Completed Deleted Net - 2008 March 21 16 0 5 - April 13 11 1 1 - May 8 14 3 -9 + Year Month Added Completed Deleted Net + 2008 March 21 16 0 5 + April 13 11 1 1 + May 8 14 3 -9 + + Average 14 13 1 -1 This shows that for the three months that task has been used, March and April saw the total number of tasks increase, but in May the number decreased as @@ -426,6 +428,23 @@ with no arguments will generate a help message that lists all these commands. +% task ghistory +-------------- + + This report shows you an overview of how many tasks were added, completed and + deleted, by month, as does "task history", but as a bar chart. It looks like this: + + % task history + + Year Month Added/Completed/Deleted + 2008 March +++++++++++++++++++++XXXXXXXXXXXXXXXX + April +++++++++++++XXXXXXXXXXX- + May ++++++++XXXXXXXXXXXXXX--- + + With color enabled, the bars contain the size of each bar. + + + % task calendar --------------- diff --git a/html/task.html b/html/task.html index d77bf4102..fdac75d58 100644 --- a/html/task.html +++ b/html/task.html @@ -51,6 +51,7 @@
  • Added "task undelete" feature to restore a (very) recently deleted task
  • Added averages to the "task history" report +
  • Added bar chart history report "task ghistory"
  • Added support for rc:<file> to allow override of the default ~/.taskrc file
  • Fixed bug where Esc[0m sequences were being emitted for no good reason @@ -1090,6 +1091,7 @@ on_white on_bright_white task tags task summary task history + task ghistory task next task calendar task active diff --git a/src/parse.cpp b/src/parse.cpp index 207c950b4..36a972e3c 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -126,6 +126,7 @@ static const char* commands[] = "export", "help", "history", + "ghistory", "info", "list", "long", diff --git a/src/task.cpp b/src/task.cpp index 654247f2e..cb9092d67 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -140,6 +140,10 @@ static void shortUsage (Config& conf) table.addCell (row, 1, "task history"); table.addCell (row, 2, "Shows a report of task history, by month"); + row = table.addRow (); + table.addCell (row, 1, "task ghistory"); + table.addCell (row, 2, "Shows a graphical report of task history, by month"); + row = table.addRow (); table.addCell (row, 1, "task next"); table.addCell (row, 2, "Shows the most important tasks for each project"); @@ -304,6 +308,7 @@ int main (int argc, char** argv) else if (command == "summary") handleReportSummary (tdb, task, conf); else if (command == "next") handleReportNext (tdb, task, conf); else if (command == "history") handleReportHistory (tdb, task, conf); + else if (command == "ghistory") handleReportGHistory (tdb, task, conf); else if (command == "calendar") handleReportCalendar (tdb, task, conf); else if (command == "active") handleReportActive (tdb, task, conf); else if (command == "overdue") handleReportOverdue (tdb, task, conf); @@ -1719,6 +1724,211 @@ void handleReportHistory (const TDB& tdb, T& task, Config& conf) std::cout << "No tasks." << std::endl; } +//////////////////////////////////////////////////////////////////////////////// +void handleReportGHistory (const TDB& tdb, T& task, Config& conf) +{ + // Determine window size, and set table accordingly. + int width = conf.get ("defaultwidth", 80); +#ifdef HAVE_LIBNCURSES + if (conf.get ("curses", true)) + { + WINDOW* w = initscr (); + width = w->_maxx + 1; + endwin (); + } +#endif + int widthOfBar = width - 15; // strlen ("2008 September ") + + std::map groups; + std::map addedGroup; + std::map completedGroup; + std::map deletedGroup; + + // Scan the pending tasks. + std::vector pending; + tdb.allPendingT (pending); + for (unsigned int i = 0; i < pending.size (); ++i) + { + T task (pending[i]); + time_t epoch = monthlyEpoch (task.getAttribute ("entry")); + if (epoch) + { + groups[epoch] = 0; + + if (addedGroup.find (epoch) != addedGroup.end ()) + addedGroup[epoch] = addedGroup[epoch] + 1; + else + addedGroup[epoch] = 1; + + if (task.getStatus () == T::deleted) + { + epoch = monthlyEpoch (task.getAttribute ("end")); + + if (deletedGroup.find (epoch) != deletedGroup.end ()) + deletedGroup[epoch] = deletedGroup[epoch] + 1; + else + deletedGroup[epoch] = 1; + } + else if (task.getStatus () == T::completed) + { + epoch = monthlyEpoch (task.getAttribute ("end")); + + if (completedGroup.find (epoch) != completedGroup.end ()) + completedGroup[epoch] = completedGroup[epoch] + 1; + else + completedGroup[epoch] = 1; + } + } + } + + // Scan the completed tasks. + std::vector completed; + tdb.allCompletedT (completed); + for (unsigned int i = 0; i < completed.size (); ++i) + { + T task (completed[i]); + time_t epoch = monthlyEpoch (task.getAttribute ("entry")); + if (epoch) + { + groups[epoch] = 0; + + if (addedGroup.find (epoch) != addedGroup.end ()) + addedGroup[epoch] = addedGroup[epoch] + 1; + else + addedGroup[epoch] = 1; + + epoch = monthlyEpoch (task.getAttribute ("end")); + if (task.getStatus () == T::deleted) + { + epoch = monthlyEpoch (task.getAttribute ("end")); + + if (deletedGroup.find (epoch) != deletedGroup.end ()) + deletedGroup[epoch] = deletedGroup[epoch] + 1; + else + deletedGroup[epoch] = 1; + } + else if (task.getStatus () == T::completed) + { + epoch = monthlyEpoch (task.getAttribute ("end")); + if (completedGroup.find (epoch) != completedGroup.end ()) + completedGroup[epoch] = completedGroup[epoch] + 1; + else + completedGroup[epoch] = 1; + } + } + } + + // Now build the table. + Table table; + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); + table.addColumn ("Year"); + table.addColumn ("Month"); + table.addColumn ("Added/Completed/Deleted"); + + if (conf.get ("color", true)) + { + table.setColumnUnderline (0); + table.setColumnUnderline (1); + } + + // Determine the longest line. + int maxLine = 0; + foreach (i, groups) + { + int line = addedGroup[i->first] + completedGroup[i->first] + deletedGroup[i->first]; + + if (line > maxLine) + maxLine = line; + } + + if (maxLine > 0) + { + int totalAdded = 0; + int totalCompleted = 0; + int totalDeleted = 0; + + int priorYear = 0; + int row = 0; + foreach (i, groups) + { + row = table.addRow (); + + totalAdded += addedGroup[i->first]; + totalCompleted += completedGroup[i->first]; + totalDeleted += deletedGroup[i->first]; + + Date dt (i->first); + int m, d, y; + dt.toMDY (m, d, y); + + if (y != priorYear) + { + table.addCell (row, 0, y); + priorYear = y; + } + table.addCell (row, 1, Date::monthName(m)); + + unsigned int addedBar = (widthOfBar * addedGroup[i->first]) / maxLine; + unsigned int completedBar = (widthOfBar * completedGroup[i->first]) / maxLine; + unsigned int deletedBar = (widthOfBar * deletedGroup[i->first]) / maxLine; + + std::string bar; + if (conf.get ("color", true)) + { + char number[24]; + sprintf (number, "%d", addedGroup[i->first]); + std::string aBar = number; + while (aBar.length () < addedBar) + aBar = " " + aBar; + + sprintf (number, "%d", completedGroup[i->first]); + std::string cBar = number; + while (cBar.length () < completedBar) + cBar = " " + cBar; + + sprintf (number, "%d", deletedGroup[i->first]); + std::string dBar = number; + while (dBar.length () < deletedBar) + dBar = " " + dBar; + + bar = Text::colorize (Text::black, Text::on_green, aBar); + bar += Text::colorize (Text::black, Text::on_yellow, cBar); + bar += Text::colorize (Text::black, Text::on_red, dBar); + } + else + { + std::string aBar = ""; while (aBar.length () < addedBar) aBar += "+"; + std::string cBar = ""; while (cBar.length () < completedBar) cBar += "+"; + std::string dBar = ""; while (dBar.length () < deletedBar) dBar += "+"; + + bar = aBar + cBar + dBar; + } + + table.addCell (row, 2, bar); + } + } + + if (table.rowCount ()) + { + std::cout << optionalBlankLine (conf) + << table.render () + << std::endl; + + if (conf.get ("color", true)) + std::cout << "Legend: " + << Text::colorize (Text::black, Text::on_green, "added") + << ", " + << Text::colorize (Text::black, Text::on_yellow, "completed") + << ", " + << Text::colorize (Text::black, Text::on_red, "deleted") + << std::endl; + else + std::cout << "Legend: + added, X completed, - deleted" << std::endl; + } + else + std::cout << "No tasks." << std::endl; +} + //////////////////////////////////////////////////////////////////////////////// // A summary of the command usage. Not useful to users, but used to display // usage statistics for feedback. diff --git a/src/task.h b/src/task.h index a400e0ed7..549333330 100644 --- a/src/task.h +++ b/src/task.h @@ -69,6 +69,7 @@ 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 handleReportUsage (const TDB&, T&, Config&); void handleReportCalendar (const TDB&, T&, Config&); void handleReportActive (const TDB&, T&, Config&);