Enhancement - #36, #37

- Added features #36 and #37, providing annual versions of the 'history'
  and 'ghistory' command as 'history.annual' and 'ghistory.annual'.
- Uses new canonical names history.monthly, history.annual, ghistory.monthly
  and ghistory.annual, with aliases providing original history and ghistory
  commands.
- Updated man pages.
This commit is contained in:
Paul Beckingham
2010-05-30 17:01:38 -04:00
parent fcbc8a2ee2
commit d92e80e289
11 changed files with 542 additions and 108 deletions

View File

@@ -6,6 +6,8 @@
implicit "task info 123" command (thanks to John Florian). implicit "task info 123" command (thanks to John Florian).
+ Added feature #326, allowing tasks to be added in the completed state, + Added feature #326, allowing tasks to be added in the completed state,
by using the 'log' command in place of 'add' (thanks to Cory Donnelly). by using the 'log' command in place of 'add' (thanks to Cory Donnelly).
+ Added features #36 and #37, providing annual versions of the 'history'
and 'ghistory' command as 'history.annual' and 'ghistory.annual'.
------ old releases ------------------------------ ------ old releases ------------------------------

1
NEWS
View File

@@ -16,6 +16,7 @@ New Features in task 1.9
- Ability to do case-sensitive or case-insensitive search for keywords, and - Ability to do case-sensitive or case-insensitive search for keywords, and
substitutions in the description and annotations. substitutions in the description and annotations.
- New 'log' command to add tasks that are already completed. - New 'log' command to add tasks that are already completed.
- New annual history and ghistory command variations.
- Task is now part of Debian - Task is now part of Debian
Please refer to the ChangeLog file for full details. There are too many to Please refer to the ChangeLog file for full details. There are too many to

View File

@@ -106,11 +106,19 @@ Shows a weekly report of tasks completed and started.
.TP .TP
.B history .B history
Shows a report of task history by month. Shows a report of task history by month. Alias to history.monthly.
.TP
.B history.annual
Shows a report of task history by year.
.TP .TP
.B ghistory .B ghistory
Shows a graphical report of task status by month. Shows a graphical report of task status by month. Alias to ghistory.monthly.
.TP
.B ghistory.annual
Shows a graphical report of task status by year.
.TP .TP
.B calendar [ y | due [y] | month year [y] | year ] .B calendar [ y | due [y] | month year [y] | year ]

View File

@@ -42,8 +42,8 @@
210 edit 210 edit
211 export 211 export
212 help 212 help
213 history 213 history.monthly
214 ghistory 214 ghistory.monthly
215 import 215 import
216 info 216 info
217 prepend 217 prepend
@@ -59,6 +59,9 @@
227 undo 227 undo
228 version 228 version
229 shell 229 shell
230 config
231 history.annual
232 ghistory.annual
# 3xx Attributes - must be sequential # 3xx Attributes - must be sequential
300 project 300 project

View File

@@ -111,36 +111,38 @@ void Cmd::load ()
commands.push_back ("_ids"); commands.push_back ("_ids");
commands.push_back ("_config"); commands.push_back ("_config");
commands.push_back ("_version"); commands.push_back ("_version");
commands.push_back (context.stringtable.get (CMD_ADD, "add")); commands.push_back (context.stringtable.get (CMD_ADD, "add"));
commands.push_back (context.stringtable.get (CMD_APPEND, "append")); commands.push_back (context.stringtable.get (CMD_APPEND, "append"));
commands.push_back (context.stringtable.get (CMD_ANNOTATE, "annotate")); commands.push_back (context.stringtable.get (CMD_ANNOTATE, "annotate"));
commands.push_back (context.stringtable.get (CMD_CALENDAR, "calendar")); commands.push_back (context.stringtable.get (CMD_CALENDAR, "calendar"));
commands.push_back (context.stringtable.get (CMD_COLORS, "colors")); commands.push_back (context.stringtable.get (CMD_COLORS, "colors"));
commands.push_back (context.stringtable.get (CMD_CONFIG, "config")); commands.push_back (context.stringtable.get (CMD_CONFIG, "config"));
commands.push_back (context.stringtable.get (CMD_DELETE, "delete")); commands.push_back (context.stringtable.get (CMD_DELETE, "delete"));
commands.push_back (context.stringtable.get (CMD_DONE, "done")); commands.push_back (context.stringtable.get (CMD_DONE, "done"));
commands.push_back (context.stringtable.get (CMD_DUPLICATE, "duplicate")); commands.push_back (context.stringtable.get (CMD_DUPLICATE, "duplicate"));
commands.push_back (context.stringtable.get (CMD_EDIT, "edit")); commands.push_back (context.stringtable.get (CMD_EDIT, "edit"));
commands.push_back (context.stringtable.get (CMD_EXPORT, "export")); commands.push_back (context.stringtable.get (CMD_EXPORT, "export"));
commands.push_back (context.stringtable.get (CMD_HELP, "help")); commands.push_back (context.stringtable.get (CMD_HELP, "help"));
commands.push_back (context.stringtable.get (CMD_HISTORY, "history")); commands.push_back (context.stringtable.get (CMD_HISTORY_MONTHLY, "history.monthly"));
commands.push_back (context.stringtable.get (CMD_GHISTORY, "ghistory")); commands.push_back (context.stringtable.get (CMD_HISTORY_ANNUAL, "history.annual"));
commands.push_back (context.stringtable.get (CMD_IMPORT, "import")); commands.push_back (context.stringtable.get (CMD_GHISTORY_MONTHLY, "ghistory.monthly"));
commands.push_back (context.stringtable.get (CMD_INFO, "info")); commands.push_back (context.stringtable.get (CMD_GHISTORY_ANNUAL, "ghistory.annual"));
commands.push_back (context.stringtable.get (CMD_LOG, "log")); commands.push_back (context.stringtable.get (CMD_IMPORT, "import"));
commands.push_back (context.stringtable.get (CMD_PREPEND, "prepend")); commands.push_back (context.stringtable.get (CMD_INFO, "info"));
commands.push_back (context.stringtable.get (CMD_PROJECTS, "projects")); commands.push_back (context.stringtable.get (CMD_LOG, "log"));
commands.push_back (context.stringtable.get (CMD_PREPEND, "prepend"));
commands.push_back (context.stringtable.get (CMD_PROJECTS, "projects"));
#ifdef FEATURE_SHELL #ifdef FEATURE_SHELL
commands.push_back (context.stringtable.get (CMD_SHELL, "shell")); commands.push_back (context.stringtable.get (CMD_SHELL, "shell"));
#endif #endif
commands.push_back (context.stringtable.get (CMD_START, "start")); commands.push_back (context.stringtable.get (CMD_START, "start"));
commands.push_back (context.stringtable.get (CMD_STATS, "stats")); commands.push_back (context.stringtable.get (CMD_STATS, "stats"));
commands.push_back (context.stringtable.get (CMD_STOP, "stop")); commands.push_back (context.stringtable.get (CMD_STOP, "stop"));
commands.push_back (context.stringtable.get (CMD_SUMMARY, "summary")); commands.push_back (context.stringtable.get (CMD_SUMMARY, "summary"));
commands.push_back (context.stringtable.get (CMD_TAGS, "tags")); commands.push_back (context.stringtable.get (CMD_TAGS, "tags"));
commands.push_back (context.stringtable.get (CMD_TIMESHEET, "timesheet")); commands.push_back (context.stringtable.get (CMD_TIMESHEET, "timesheet"));
commands.push_back (context.stringtable.get (CMD_UNDO, "undo")); commands.push_back (context.stringtable.get (CMD_UNDO, "undo"));
commands.push_back (context.stringtable.get (CMD_VERSION, "version")); commands.push_back (context.stringtable.get (CMD_VERSION, "version"));
// Now load the custom reports. // Now load the custom reports.
std::vector <std::string> all; std::vector <std::string> all;
@@ -190,27 +192,29 @@ void Cmd::allCommands (std::vector <std::string>& all) const
// Commands that do not directly modify the data files. // Commands that do not directly modify the data files.
bool Cmd::isReadOnlyCommand () bool Cmd::isReadOnlyCommand ()
{ {
if (command == "_projects" || if (command == "_projects" ||
command == "_tags" || command == "_tags" ||
command == "_commands" || command == "_commands" ||
command == "_ids" || command == "_ids" ||
command == "_config" || command == "_config" ||
command == "_version" || command == "_version" ||
command == context.stringtable.get (CMD_CALENDAR, "calendar") || command == context.stringtable.get (CMD_CALENDAR, "calendar") ||
command == context.stringtable.get (CMD_COLORS, "colors") || command == context.stringtable.get (CMD_COLORS, "colors") ||
command == context.stringtable.get (CMD_CONFIG, "config") || command == context.stringtable.get (CMD_CONFIG, "config") ||
command == context.stringtable.get (CMD_EXPORT, "export") || command == context.stringtable.get (CMD_EXPORT, "export") ||
command == context.stringtable.get (CMD_HELP, "help") || command == context.stringtable.get (CMD_HELP, "help") ||
command == context.stringtable.get (CMD_HISTORY, "history") || command == context.stringtable.get (CMD_HISTORY_MONTHLY, "history.monthly") ||
command == context.stringtable.get (CMD_GHISTORY, "ghistory") || command == context.stringtable.get (CMD_HISTORY_ANNUAL, "history.annual") ||
command == context.stringtable.get (CMD_INFO, "info") || command == context.stringtable.get (CMD_GHISTORY_MONTHLY, "ghistory.monthly") ||
command == context.stringtable.get (CMD_PROJECTS, "projects") || command == context.stringtable.get (CMD_GHISTORY_ANNUAL, "ghistory.annual") ||
command == context.stringtable.get (CMD_SHELL, "shell") || command == context.stringtable.get (CMD_INFO, "info") ||
command == context.stringtable.get (CMD_STATS, "stats") || command == context.stringtable.get (CMD_PROJECTS, "projects") ||
command == context.stringtable.get (CMD_SUMMARY, "summary") || command == context.stringtable.get (CMD_SHELL, "shell") ||
command == context.stringtable.get (CMD_TAGS, "tags") || command == context.stringtable.get (CMD_STATS, "stats") ||
command == context.stringtable.get (CMD_TIMESHEET, "timesheet") || command == context.stringtable.get (CMD_SUMMARY, "summary") ||
command == context.stringtable.get (CMD_VERSION, "version") || command == context.stringtable.get (CMD_TAGS, "tags") ||
command == context.stringtable.get (CMD_TIMESHEET, "timesheet") ||
command == context.stringtable.get (CMD_VERSION, "version") ||
validCustom (command)) validCustom (command))
return true; return true;

View File

@@ -161,6 +161,8 @@ std::string Config::defaults =
"\n" "\n"
"# Aliases - alternate names for commands\n" "# Aliases - alternate names for commands\n"
"alias.rm=delete # Alias for the delete command\n" "alias.rm=delete # Alias for the delete command\n"
"alias.history=history.monthly # Prefer monthly history reports\n"
"alias.ghistory=ghistory.monthly # Prefer monthly graphical history reports\n"
"\n" "\n"
"# Fields: id,uuid,project,priority,priority_long,entry,entry_time,\n" // TODO "# Fields: id,uuid,project,priority,priority_long,entry,entry_time,\n" // TODO
"# start,entry_time,due,recur,recurrence_indicator,age,\n" // TODO "# start,entry_time,due,recur,recurrence_indicator,age,\n" // TODO

View File

@@ -205,53 +205,55 @@ int Context::dispatch (std::string &out)
hooks.trigger ("pre-dispatch"); hooks.trigger ("pre-dispatch");
// TODO Just look at this thing. It cries out for a dispatch table. // TODO Just look at this thing. It cries out for a dispatch table.
if (cmd.command == "projects") { rc = handleProjects (out); } if (cmd.command == "projects") { rc = handleProjects (out); }
else if (cmd.command == "tags") { rc = handleTags (out); } else if (cmd.command == "tags") { rc = handleTags (out); }
else if (cmd.command == "colors") { rc = handleColor (out); } else if (cmd.command == "colors") { rc = handleColor (out); }
else if (cmd.command == "version") { rc = handleVersion (out); } else if (cmd.command == "version") { rc = handleVersion (out); }
else if (cmd.command == "config") { rc = handleConfig (out); } else if (cmd.command == "config") { rc = handleConfig (out); }
else if (cmd.command == "help") { rc = longUsage (out); } else if (cmd.command == "help") { rc = longUsage (out); }
else if (cmd.command == "stats") { rc = handleReportStats (out); } else if (cmd.command == "stats") { rc = handleReportStats (out); }
else if (cmd.command == "info") { rc = handleInfo (out); } else if (cmd.command == "info") { rc = handleInfo (out); }
else if (cmd.command == "history") { rc = handleReportHistory (out); } else if (cmd.command == "history.monthly") { rc = handleReportHistoryMonthly (out); }
else if (cmd.command == "ghistory") { rc = handleReportGHistory (out); } else if (cmd.command == "history.annual") { rc = handleReportHistoryAnnual (out); }
else if (cmd.command == "summary") { rc = handleReportSummary (out); } else if (cmd.command == "ghistory.monthly") { rc = handleReportGHistoryMonthly (out); }
else if (cmd.command == "calendar") { rc = handleReportCalendar (out); } else if (cmd.command == "ghistory.annual") { rc = handleReportGHistoryAnnual (out); }
else if (cmd.command == "timesheet") { rc = handleReportTimesheet (out); } else if (cmd.command == "summary") { rc = handleReportSummary (out); }
else if (cmd.command == "add") { rc = handleAdd (out); } else if (cmd.command == "calendar") { rc = handleReportCalendar (out); }
else if (cmd.command == "log") { rc = handleLog (out); } else if (cmd.command == "timesheet") { rc = handleReportTimesheet (out); }
else if (cmd.command == "append") { rc = handleAppend (out); } else if (cmd.command == "add") { rc = handleAdd (out); }
else if (cmd.command == "prepend") { rc = handlePrepend (out); } else if (cmd.command == "log") { rc = handleLog (out); }
else if (cmd.command == "annotate") { rc = handleAnnotate (out); } else if (cmd.command == "append") { rc = handleAppend (out); }
else if (cmd.command == "done") { rc = handleDone (out); } else if (cmd.command == "prepend") { rc = handlePrepend (out); }
else if (cmd.command == "delete") { rc = handleDelete (out); } else if (cmd.command == "annotate") { rc = handleAnnotate (out); }
else if (cmd.command == "start") { rc = handleStart (out); } else if (cmd.command == "done") { rc = handleDone (out); }
else if (cmd.command == "stop") { rc = handleStop (out); } else if (cmd.command == "delete") { rc = handleDelete (out); }
else if (cmd.command == "export") { rc = handleExport (out); } else if (cmd.command == "start") { rc = handleStart (out); }
else if (cmd.command == "import") { rc = handleImport (out); } else if (cmd.command == "stop") { rc = handleStop (out); }
else if (cmd.command == "duplicate") { rc = handleDuplicate (out); } else if (cmd.command == "export") { rc = handleExport (out); }
else if (cmd.command == "edit") { rc = handleEdit (out); } else if (cmd.command == "import") { rc = handleImport (out); }
else if (cmd.command == "duplicate") { rc = handleDuplicate (out); }
else if (cmd.command == "edit") { rc = handleEdit (out); }
#ifdef FEATURE_SHELL #ifdef FEATURE_SHELL
else if (cmd.command == "shell") { handleShell ( ); } else if (cmd.command == "shell") { handleShell ( ); }
#endif #endif
else if (cmd.command == "undo") { handleUndo ( ); } else if (cmd.command == "undo") { handleUndo ( ); }
else if (cmd.command == "_projects") { rc = handleCompletionProjects (out); } else if (cmd.command == "_projects") { rc = handleCompletionProjects (out); }
else if (cmd.command == "_tags") { rc = handleCompletionTags (out); } else if (cmd.command == "_tags") { rc = handleCompletionTags (out); }
else if (cmd.command == "_commands") { rc = handleCompletionCommands (out); } else if (cmd.command == "_commands") { rc = handleCompletionCommands (out); }
else if (cmd.command == "_ids") { rc = handleCompletionIDs (out); } else if (cmd.command == "_ids") { rc = handleCompletionIDs (out); }
else if (cmd.command == "_config") { rc = handleCompletionConfig (out); } else if (cmd.command == "_config") { rc = handleCompletionConfig (out); }
else if (cmd.command == "_version") { rc = handleCompletionVersion (out); } else if (cmd.command == "_version") { rc = handleCompletionVersion (out); }
else if (cmd.command == "" && else if (cmd.command == "" &&
sequence.size ()) { rc = handleModify (out); } sequence.size ()) { rc = handleModify (out); }
// Command that display IDs and therefore need TDB::gc first. // Command that display IDs and therefore need TDB::gc first.
else if (cmd.command == "next") { if (!inShadow) tdb.gc (); rc = handleReportNext (out); } else if (cmd.command == "next") { if (!inShadow) tdb.gc (); rc = handleReportNext (out); }
else if (cmd.validCustom (cmd.command)) { if (!inShadow) tdb.gc (); rc = handleCustomReport (cmd.command, out); } else if (cmd.validCustom (cmd.command)) { if (!inShadow) tdb.gc (); rc = handleCustomReport (cmd.command, out); }
// If the command is not recognized, display usage. // If the command is not recognized, display usage.
else { hooks.trigger ("pre-usage-command"); else { hooks.trigger ("pre-usage-command");
rc = shortUsage (out); rc = shortUsage (out);
hooks.trigger ("post-usage-command"); } hooks.trigger ("post-usage-command"); }
// Only update the shadow file if such an update was not suppressed (shadow), // Only update the shadow file if such an update was not suppressed (shadow),
if (cmd.isWriteCommand () && !inShadow) if (cmd.isWriteCommand () && !inShadow)

View File

@@ -79,8 +79,8 @@
#define CMD_EDIT 210 #define CMD_EDIT 210
#define CMD_EXPORT 211 #define CMD_EXPORT 211
#define CMD_HELP 212 #define CMD_HELP 212
#define CMD_HISTORY 213 #define CMD_HISTORY_MONTHLY 213
#define CMD_GHISTORY 214 #define CMD_GHISTORY_MONTHLY 214
#define CMD_IMPORT 215 #define CMD_IMPORT 215
#define CMD_INFO 216 #define CMD_INFO 216
#define CMD_PREPEND 217 #define CMD_PREPEND 217
@@ -97,6 +97,8 @@
#define CMD_VERSION 228 #define CMD_VERSION 228
#define CMD_SHELL 229 #define CMD_SHELL 229
#define CMD_CONFIG 230 #define CMD_CONFIG 230
#define CMD_HISTORY_ANNUAL 231
#define CMD_GHISTORY_ANNUAL 232
// 3xx Attributes // 3xx Attributes
#define ATT_PROJECT 300 #define ATT_PROJECT 300

View File

@@ -98,8 +98,10 @@ int longUsage (std::string &);
int handleInfo (std::string &); int handleInfo (std::string &);
int handleReportSummary (std::string &); int handleReportSummary (std::string &);
int handleReportNext (std::string &); int handleReportNext (std::string &);
int handleReportHistory (std::string &); int handleReportHistoryMonthly (std::string &);
int handleReportGHistory (std::string &); int handleReportHistoryAnnual (std::string &);
int handleReportGHistoryMonthly (std::string &);
int handleReportGHistoryAnnual (std::string &);
int handleReportCalendar (std::string &); int handleReportCalendar (std::string &);
int handleReportStats (std::string &); int handleReportStats (std::string &);
int handleReportTimesheet (std::string &); int handleReportTimesheet (std::string &);

View File

@@ -164,11 +164,19 @@ int shortUsage (std::string &outs)
row = table.addRow (); row = table.addRow ();
table.addCell (row, 1, "task history"); table.addCell (row, 1, "task history");
table.addCell (row, 2, "Shows a report of task history, by month."); table.addCell (row, 2, "Shows a report of task history, by month. Alias to history.monthly.");
row = table.addRow ();
table.addCell (row, 1, "task history.annual");
table.addCell (row, 2, "Shows a report of task history, by year.");
row = table.addRow (); row = table.addRow ();
table.addCell (row, 1, "task ghistory"); table.addCell (row, 1, "task ghistory");
table.addCell (row, 2, "Shows a graphical report of task history, by month."); table.addCell (row, 2, "Shows a graphical report of task history, by month. Alias to ghistory.monthly.");
row = table.addRow ();
table.addCell (row, 1, "task ghistory.annual");
table.addCell (row, 2, "Shows a graphical report of task history, by year.");
row = table.addRow (); row = table.addRow ();
table.addCell (row, 1, "task calendar [due|month year|year]"); table.addCell (row, 1, "task calendar [due|month year|year]");
@@ -827,7 +835,25 @@ time_t monthlyEpoch (const std::string& date)
return 0; return 0;
} }
int handleReportHistory (std::string &outs) time_t yearlyEpoch (const std::string& date)
{
// Convert any date in epoch form to m/d/y, then convert back
// to epoch form for the date 1/1/y.
if (date.length ())
{
Date d1 (atoi (date.c_str ()));
int m, d, y;
d1.toMDY (m, d, y);
Date d2 (1, 1, y);
time_t epoch;
d2.toEpoch (epoch);
return epoch;
}
return 0;
}
int handleReportHistoryMonthly (std::string &outs)
{ {
int rc = 0; int rc = 0;
@@ -993,8 +1019,171 @@ int handleReportHistory (std::string &outs)
return rc; return rc;
} }
int handleReportHistoryAnnual (std::string &outs)
{
int rc = 0;
if (context.hooks.trigger ("pre-history-command"))
{
std::map <time_t, int> groups; // Represents any month with data
std::map <time_t, int> addedGroup; // Additions by month
std::map <time_t, int> completedGroup; // Completions by month
std::map <time_t, int> deletedGroup; // Deletions by month
// Scan the pending tasks.
std::vector <Task> tasks;
context.tdb.lock (context.config.getBoolean ("locking"));
handleRecurrence ();
context.tdb.load (tasks, context.filter);
context.tdb.commit ();
context.tdb.unlock ();
foreach (task, tasks)
{
time_t epoch = yearlyEpoch (task->get ("entry"));
groups[epoch] = 0;
// Every task has an entry date.
if (addedGroup.find (epoch) != addedGroup.end ())
addedGroup[epoch] = addedGroup[epoch] + 1;
else
addedGroup[epoch] = 1;
// All deleted tasks have an end date.
if (task->getStatus () == Task::deleted)
{
epoch = yearlyEpoch (task->get ("end"));
groups[epoch] = 0;
if (deletedGroup.find (epoch) != deletedGroup.end ())
deletedGroup[epoch] = deletedGroup[epoch] + 1;
else
deletedGroup[epoch] = 1;
}
// All completed tasks have an end date.
else if (task->getStatus () == Task::completed)
{
epoch = yearlyEpoch (task->get ("end"));
groups[epoch] = 0;
if (completedGroup.find (epoch) != completedGroup.end ())
completedGroup[epoch] = completedGroup[epoch] + 1;
else
completedGroup[epoch] = 1;
}
}
// Now build the table.
Table table;
table.setDateFormat (context.config.get ("dateformat"));
table.addColumn ("Year");
table.addColumn ("Added");
table.addColumn ("Completed");
table.addColumn ("Deleted");
table.addColumn ("Net");
if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) &&
context.config.getBoolean ("fontunderline"))
{
table.setColumnUnderline (0);
table.setColumnUnderline (1);
table.setColumnUnderline (2);
table.setColumnUnderline (3);
table.setColumnUnderline (4);
}
else
table.setTableDashedUnderline ();
table.setColumnJustification (1, Table::right);
table.setColumnJustification (2, Table::right);
table.setColumnJustification (3, Table::right);
table.setColumnJustification (4, Table::right);
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;
}
int net = 0;
if (addedGroup.find (i->first) != addedGroup.end ())
{
table.addCell (row, 1, addedGroup[i->first]);
net +=addedGroup[i->first];
}
if (completedGroup.find (i->first) != completedGroup.end ())
{
table.addCell (row, 2, completedGroup[i->first]);
net -= completedGroup[i->first];
}
if (deletedGroup.find (i->first) != deletedGroup.end ())
{
table.addCell (row, 3, deletedGroup[i->first]);
net -= deletedGroup[i->first];
}
table.addCell (row, 4, net);
if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && net)
table.setCellColor (row, 4, net > 0 ? Color (Color::red) :
Color (Color::green));
}
if (table.rowCount ())
{
table.addRow ();
row = table.addRow ();
table.addCell (row, 0, "Average");
if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor"))
table.setRowColor (row, Color (Color::nocolor, Color::nocolor, false, true, false));
table.addCell (row, 1, totalAdded / (table.rowCount () - 2));
table.addCell (row, 2, totalCompleted / (table.rowCount () - 2));
table.addCell (row, 3, totalDeleted / (table.rowCount () - 2));
table.addCell (row, 4, (totalAdded - totalCompleted - totalDeleted) / (table.rowCount () - 2));
}
std::stringstream out;
if (table.rowCount ())
out << optionalBlankLine ()
<< table.render ()
<< std::endl;
else
{
out << "No tasks." << std::endl;
rc = 1;
}
outs = out.str ();
context.hooks.trigger ("post-history-command");
}
return rc;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int handleReportGHistory (std::string &outs) int handleReportGHistoryMonthly (std::string &outs)
{ {
int rc = 0; int rc = 0;
@@ -1203,6 +1392,213 @@ int handleReportGHistory (std::string &outs)
return rc; return rc;
} }
////////////////////////////////////////////////////////////////////////////////
int handleReportGHistoryAnnual (std::string &outs)
{
int rc = 0;
if (context.hooks.trigger ("pre-ghistory-command"))
{
std::map <time_t, int> groups; // Represents any month with data
std::map <time_t, int> addedGroup; // Additions by month
std::map <time_t, int> completedGroup; // Completions by month
std::map <time_t, int> deletedGroup; // Deletions by month
// Scan the pending tasks.
std::vector <Task> tasks;
context.tdb.lock (context.config.getBoolean ("locking"));
handleRecurrence ();
context.tdb.load (tasks, context.filter);
context.tdb.commit ();
context.tdb.unlock ();
foreach (task, tasks)
{
time_t epoch = yearlyEpoch (task->get ("entry"));
groups[epoch] = 0;
// Every task has an entry date.
if (addedGroup.find (epoch) != addedGroup.end ())
addedGroup[epoch] = addedGroup[epoch] + 1;
else
addedGroup[epoch] = 1;
// All deleted tasks have an end date.
if (task->getStatus () == Task::deleted)
{
epoch = yearlyEpoch (task->get ("end"));
groups[epoch] = 0;
if (deletedGroup.find (epoch) != deletedGroup.end ())
deletedGroup[epoch] = deletedGroup[epoch] + 1;
else
deletedGroup[epoch] = 1;
}
// All completed tasks have an end date.
else if (task->getStatus () == Task::completed)
{
epoch = yearlyEpoch (task->get ("end"));
groups[epoch] = 0;
if (completedGroup.find (epoch) != completedGroup.end ())
completedGroup[epoch] = completedGroup[epoch] + 1;
else
completedGroup[epoch] = 1;
}
}
int widthOfBar = context.getWidth () - 15; // 15 == strlen ("2008 September ")
// Now build the table.
Table table;
table.setDateFormat (context.config.get ("dateformat"));
table.addColumn ("Year");
table.addColumn ("Number Added/Completed/Deleted");
if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) &&
context.config.getBoolean ("fontunderline"))
{
table.setColumnUnderline (0);
}
else
table.setTableDashedUnderline ();
Color color_added (Color::black, Color::red);
Color color_completed (Color::black, Color::green);
Color color_deleted (Color::black, Color::yellow);
// Determine the longest line, and the longest "added" line.
int maxAddedLine = 0;
int maxRemovedLine = 0;
foreach (i, groups)
{
if (completedGroup[i->first] + deletedGroup[i->first] > maxRemovedLine)
maxRemovedLine = completedGroup[i->first] + deletedGroup[i->first];
if (addedGroup[i->first] > maxAddedLine)
maxAddedLine = addedGroup[i->first];
}
int maxLine = maxAddedLine + maxRemovedLine;
if (maxLine > 0)
{
unsigned int leftOffset = (widthOfBar * maxAddedLine) / maxLine;
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;
}
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 (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor"))
{
char number[24];
std::string aBar = "";
if (addedGroup[i->first])
{
sprintf (number, "%d", addedGroup[i->first]);
aBar = number;
while (aBar.length () < addedBar)
aBar = " " + aBar;
}
std::string cBar = "";
if (completedGroup[i->first])
{
sprintf (number, "%d", completedGroup[i->first]);
cBar = number;
while (cBar.length () < completedBar)
cBar = " " + cBar;
}
std::string dBar = "";
if (deletedGroup[i->first])
{
sprintf (number, "%d", deletedGroup[i->first]);
dBar = number;
while (dBar.length () < deletedBar)
dBar = " " + dBar;
}
while (bar.length () < leftOffset - aBar.length ())
bar += " ";
bar += color_added.colorize (aBar);
bar += color_completed.colorize (cBar);
bar += color_deleted.colorize (dBar);
}
else
{
std::string aBar = ""; while (aBar.length () < addedBar) aBar += "+";
std::string cBar = ""; while (cBar.length () < completedBar) cBar += "X";
std::string dBar = ""; while (dBar.length () < deletedBar) dBar += "-";
while (bar.length () < leftOffset - aBar.length ())
bar += " ";
bar += aBar + cBar + dBar;
}
table.addCell (row, 1, bar);
}
}
std::stringstream out;
if (table.rowCount ())
{
out << optionalBlankLine ()
<< table.render ()
<< std::endl;
if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor"))
out << "Legend: "
<< color_added.colorize ("added")
<< ", "
<< color_completed.colorize ("completed")
<< ", "
<< color_deleted.colorize ("deleted")
<< optionalBlankLine ()
<< std::endl;
else
out << "Legend: + added, X completed, - deleted" << std::endl;
}
else
{
out << "No tasks." << std::endl;
rc = 1;
}
outs = out.str ();
context.hooks.trigger ("post-ghistory-command");
}
return rc;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int handleReportTimesheet (std::string &outs) int handleReportTimesheet (std::string &outs)
{ {

View File

@@ -33,7 +33,7 @@ Context context;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv) int main (int argc, char** argv)
{ {
UnitTest t (78); UnitTest t (84);
// Without Context::initialize, there is no set of defaults loaded into // Without Context::initialize, there is no set of defaults loaded into
// Context::Config. // Context::Config.
@@ -65,13 +65,21 @@ int main (int argc, char** argv)
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand help"); t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand help");
t.notok (cmd.isWriteCommand (), "not isWriteCommand help"); t.notok (cmd.isWriteCommand (), "not isWriteCommand help");
cmd.command = "history"; cmd.command = "history.monthly";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand history"); t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand history.monthly");
t.notok (cmd.isWriteCommand (), "not isWriteCommand history"); t.notok (cmd.isWriteCommand (), "not isWriteCommand history.monthly");
cmd.command = "ghistory"; cmd.command = "history.annual";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand ghistory"); t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand history.annual");
t.notok (cmd.isWriteCommand (), "not isWriteCommand ghistory"); t.notok (cmd.isWriteCommand (), "not isWriteCommand history.annual");
cmd.command = "ghistory.monthly";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand ghistory.monthly");
t.notok (cmd.isWriteCommand (), "not isWriteCommand ghistory.monthly");
cmd.command = "ghistory.annual";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand ghistory.annual");
t.notok (cmd.isWriteCommand (), "not isWriteCommand ghistory.annual");
cmd.command = "info"; cmd.command = "info";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand info"); t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand info");
@@ -121,6 +129,10 @@ int main (int argc, char** argv)
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand add"); t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand add");
t.ok (cmd.isWriteCommand (), "isWriteCommand add"); t.ok (cmd.isWriteCommand (), "isWriteCommand add");
cmd.command = "log";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand log");
t.ok (cmd.isWriteCommand (), "isWriteCommand log");
cmd.command = "append"; cmd.command = "append";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand append"); t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand append");
t.ok (cmd.isWriteCommand (), "isWriteCommand append"); t.ok (cmd.isWriteCommand (), "isWriteCommand append");