diff --git a/src/Context.cpp b/src/Context.cpp index df313d553..cc9b0d8dd 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -124,7 +124,7 @@ void Context::initialize (int argc, char** argv) // Hook system init, plus post-start event occurring at the first possible // moment after hook initialization. hooks.initialize (); - hooks.trigger ("post-start"); + hooks.trigger ("on-launch"); } //////////////////////////////////////////////////////////////////////////////// @@ -189,41 +189,33 @@ int Context::run () } // Dump all debug messages. - hooks.trigger ("pre-debug"); if (config.getBoolean ("debug")) foreach (d, debugMessages) if (config.getBoolean ("color") || config.getBoolean ("_forcecolor")) std::cout << colorizeDebug (*d) << "\n"; else std::cout << *d << "\n"; - hooks.trigger ("post-debug"); // Dump all headers. - hooks.trigger ("pre-header"); if (config.getBoolean ("verbose")) foreach (h, headers) if (config.getBoolean ("color") || config.getBoolean ("_forcecolor")) std::cout << colorizeHeader (*h) << "\n"; else std::cout << *h << "\n"; - hooks.trigger ("post-header"); // Dump the report output. - hooks.trigger ("pre-output"); std::cout << output; - hooks.trigger ("post-output"); // Dump all footnotes. - hooks.trigger ("pre-footnote"); if (config.getBoolean ("verbose")) foreach (f, footnotes) if (config.getBoolean ("color") || config.getBoolean ("_forcecolor")) std::cout << colorizeFootnote (*f) << "\n"; else std::cout << *f << "\n"; - hooks.trigger ("post-footnote"); - hooks.trigger ("pre-exit"); + hooks.trigger ("on-exit"); return rc; } @@ -234,8 +226,6 @@ int Context::dispatch (std::string &out) Timer t ("Context::dispatch"); - hooks.trigger ("pre-dispatch"); - // For read-only commands, optionally update the xterm window title. // Why just the read-only commands? Because this capability is to answer the // question of 'what did I just do to generate this outout?'. @@ -310,15 +300,12 @@ int Context::dispatch (std::string &out) rc = handleCustomReport (cmd.command, out); } // If the command is not recognized, display usage. - else { hooks.trigger ("pre-usage-command"); - rc = shortUsage (out); - hooks.trigger ("post-usage-command"); } + else { rc = shortUsage (out); } // Only update the shadow file if such an update was not suppressed (shadow), if (cmd.isWriteCommand () && !inShadow) shadow (); - hooks.trigger ("post-dispatch"); return rc; } diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 5a0e8f21b..9af042267 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -73,208 +73,19 @@ Hook& Hook::operator= (const Hook& other) //////////////////////////////////////////////////////////////////////////////// Hooks::Hooks () { -/* // New 2.x hooks. - validTaskEvents.push_back ("on-task-add"); - validTaskEvents.push_back ("on-task-modify"); - validTaskEvents.push_back ("on-task-complete"); - validTaskEvents.push_back ("on-task-delete"); + validTaskEvents.push_back ("on-task-add"); // Unimplemented + validTaskEvents.push_back ("on-task-modify"); // Unimplemented + validTaskEvents.push_back ("on-task-complete"); // Unimplemented + validTaskEvents.push_back ("on-task-delete"); // Unimplemented validProgramEvents.push_back ("on-launch"); validProgramEvents.push_back ("on-exit"); - validProgramEvents.push_back ("on-file-read"); - validProgramEvents.push_back ("on-file-write"); - validProgramEvents.push_back ("on-synch"); - validProgramEvents.push_back ("on-gc"); -*/ - - // Obsolete 1.x hooks. - validProgramEvents.push_back ("post-start"); - validProgramEvents.push_back ("pre-exit"); - validProgramEvents.push_back ("pre-file-read"); - validProgramEvents.push_back ("post-file-read"); - validProgramEvents.push_back ("pre-file-write"); - validProgramEvents.push_back ("post-file-write"); - validProgramEvents.push_back ("pre-gc"); - validProgramEvents.push_back ("post-gc"); - - - - - - - validProgramEvents.push_back ("post-commit"); - validProgramEvents.push_back ("pre-fatal-error"); - validProgramEvents.push_back ("pre-command-line"); - validProgramEvents.push_back ("post-command-line"); - validProgramEvents.push_back ("pre-command-line-override"); - validProgramEvents.push_back ("post-command-line-override"); - validProgramEvents.push_back ("pre-config-create"); - validProgramEvents.push_back ("post-config-create"); - validProgramEvents.push_back ("pre-config-read"); - validProgramEvents.push_back ("post-config-read"); - validProgramEvents.push_back ("pre-config-value-read"); - validProgramEvents.push_back ("post-config-value-read"); - validProgramEvents.push_back ("pre-config-value-write"); - validProgramEvents.push_back ("post-config-value-write"); - validProgramEvents.push_back ("pre-file-lock"); - validProgramEvents.push_back ("post-file-lock"); - validProgramEvents.push_back ("pre-file-unlock"); - validProgramEvents.push_back ("post-file-unlock"); - validProgramEvents.push_back ("pre-output"); - validProgramEvents.push_back ("post-output"); - validProgramEvents.push_back ("pre-debug"); - validProgramEvents.push_back ("post-debug"); - validProgramEvents.push_back ("pre-header"); - validProgramEvents.push_back ("post-header"); - validProgramEvents.push_back ("pre-footnote"); - validProgramEvents.push_back ("post-footnote"); - validProgramEvents.push_back ("pre-dispatch"); - validProgramEvents.push_back ("post-dispatch"); - validProgramEvents.push_back ("pre-archive"); - validProgramEvents.push_back ("post-archive"); - validProgramEvents.push_back ("pre-purge"); - validProgramEvents.push_back ("post-purge"); - validProgramEvents.push_back ("pre-recurrence"); - validProgramEvents.push_back ("post-recurrence"); - validProgramEvents.push_back ("pre-interactive"); - validProgramEvents.push_back ("post-interactive"); - validProgramEvents.push_back ("pre-undo"); - validProgramEvents.push_back ("post-undo"); - validProgramEvents.push_back ("pre-confirm"); - validProgramEvents.push_back ("post-confirm"); - validProgramEvents.push_back ("pre-shell-prompt"); - validProgramEvents.push_back ("post-shell-prompt"); - validProgramEvents.push_back ("pre-add-command"); - validProgramEvents.push_back ("post-add-command"); - validProgramEvents.push_back ("pre-annotate-command"); - validProgramEvents.push_back ("post-annotate-command"); - validProgramEvents.push_back ("pre-denotate-command"); - validProgramEvents.push_back ("post-denotate-command"); - validProgramEvents.push_back ("pre-append-command"); - validProgramEvents.push_back ("post-append-command"); - validProgramEvents.push_back ("pre-calendar-command"); - validProgramEvents.push_back ("post-calendar-command"); - validProgramEvents.push_back ("pre-color-command"); - validProgramEvents.push_back ("post-color-command"); - validProgramEvents.push_back ("pre-config-command"); - validProgramEvents.push_back ("post-config-command"); - validProgramEvents.push_back ("pre-custom-report-command"); - validProgramEvents.push_back ("post-custom-report-command"); - validProgramEvents.push_back ("pre-default-command"); - validProgramEvents.push_back ("post-default-command"); - validProgramEvents.push_back ("pre-delete-command"); - validProgramEvents.push_back ("post-delete-command"); - validProgramEvents.push_back ("pre-done-command"); - validProgramEvents.push_back ("post-done-command"); - validProgramEvents.push_back ("pre-duplicate-command"); - validProgramEvents.push_back ("post-duplicate-command"); - validProgramEvents.push_back ("pre-count-command"); - validProgramEvents.push_back ("post-count-command"); - validProgramEvents.push_back ("pre-edit-command"); - validProgramEvents.push_back ("post-edit-command"); - validProgramEvents.push_back ("pre-export-command"); - validProgramEvents.push_back ("post-export-command"); - validProgramEvents.push_back ("pre-ghistory-command"); - validProgramEvents.push_back ("post-ghistory-command"); - validProgramEvents.push_back ("pre-history-command"); - validProgramEvents.push_back ("post-history-command"); - validProgramEvents.push_back ("pre-burndown-command"); - validProgramEvents.push_back ("post-burndown-command"); - validProgramEvents.push_back ("pre-import-command"); - validProgramEvents.push_back ("post-import-command"); - validProgramEvents.push_back ("pre-info-command"); - validProgramEvents.push_back ("post-info-command"); - validProgramEvents.push_back ("pre-merge-command"); - validProgramEvents.push_back ("post-merge-command"); - validProgramEvents.push_back ("pre-modify-command"); - validProgramEvents.push_back ("post-modify-command"); - validProgramEvents.push_back ("pre-prepend-command"); - validProgramEvents.push_back ("post-prepend-command"); - validProgramEvents.push_back ("pre-projects-command"); - validProgramEvents.push_back ("post-projects-command"); - validProgramEvents.push_back ("pre-pull-command"); - validProgramEvents.push_back ("post-pull-command"); - validProgramEvents.push_back ("pre-push-command"); - validProgramEvents.push_back ("post-push-command"); - validProgramEvents.push_back ("pre-shell-command"); - validProgramEvents.push_back ("post-shell-command"); - validProgramEvents.push_back ("pre-start-command"); - validProgramEvents.push_back ("post-start-command"); - validProgramEvents.push_back ("pre-stats-command"); - validProgramEvents.push_back ("post-stats-command"); - validProgramEvents.push_back ("pre-stop-command"); - validProgramEvents.push_back ("post-stop-command"); - validProgramEvents.push_back ("pre-summary-command"); - validProgramEvents.push_back ("post-summary-command"); - validProgramEvents.push_back ("pre-tags-command"); - validProgramEvents.push_back ("post-tags-command"); - validProgramEvents.push_back ("pre-timesheet-command"); - validProgramEvents.push_back ("post-timesheet-command"); - validProgramEvents.push_back ("pre-undo-command"); - validProgramEvents.push_back ("post-undo-command"); - validProgramEvents.push_back ("pre-usage-command"); - validProgramEvents.push_back ("post-usage-command"); - validProgramEvents.push_back ("pre-version-command"); - validProgramEvents.push_back ("post-version-command"); - - validTaskEvents.push_back ("pre-display"); - validTaskEvents.push_back ("pre-modification"); - validTaskEvents.push_back ("post-modification"); - validTaskEvents.push_back ("pre-delete"); - validTaskEvents.push_back ("post-delete"); - validTaskEvents.push_back ("pre-add"); - validTaskEvents.push_back ("post-add"); - validTaskEvents.push_back ("pre-undo"); - validTaskEvents.push_back ("post-undo"); - validTaskEvents.push_back ("pre-wait"); - validTaskEvents.push_back ("post-wait"); - validTaskEvents.push_back ("pre-unwait"); - validTaskEvents.push_back ("post-unwait"); - validTaskEvents.push_back ("pre-completed"); - validTaskEvents.push_back ("post-completed"); - validTaskEvents.push_back ("pre-priority-change"); - validTaskEvents.push_back ("post-priority-change"); - validTaskEvents.push_back ("pre-project-change"); - validTaskEvents.push_back ("post-project-change"); - validTaskEvents.push_back ("pre-substitution"); - validTaskEvents.push_back ("post-substitution"); - validTaskEvents.push_back ("pre-annotation"); - validTaskEvents.push_back ("post-annotation"); - validTaskEvents.push_back ("pre-tag"); - validTaskEvents.push_back ("post-tag"); - validTaskEvents.push_back ("pre-detag"); - validTaskEvents.push_back ("post-detag"); - validTaskEvents.push_back ("pre-colorization"); - validTaskEvents.push_back ("post-colorization"); - - validFieldEvents.push_back ("format-number"); - validFieldEvents.push_back ("format-date"); - validFieldEvents.push_back ("format-duration"); - validFieldEvents.push_back ("format-text"); - validFieldEvents.push_back ("format-id"); - validFieldEvents.push_back ("format-uuid"); - validFieldEvents.push_back ("format-project"); - validFieldEvents.push_back ("format-priority"); - validFieldEvents.push_back ("format-priority_long"); - validFieldEvents.push_back ("format-entry"); - validFieldEvents.push_back ("format-start"); - validFieldEvents.push_back ("format-end"); - validFieldEvents.push_back ("format-due"); - validFieldEvents.push_back ("format-countdown"); - validFieldEvents.push_back ("format-countdown_compact"); - validFieldEvents.push_back ("format-age"); - validFieldEvents.push_back ("format-age_compact"); - validFieldEvents.push_back ("format-active"); - validFieldEvents.push_back ("format-tags"); - validFieldEvents.push_back ("format-recur"); - validFieldEvents.push_back ("format-recurrence_indicator"); - validFieldEvents.push_back ("format-tag_indicator"); - validFieldEvents.push_back ("format-description_only"); - validFieldEvents.push_back ("format-description"); - validFieldEvents.push_back ("format-wait"); - validFieldEvents.push_back ("format-prompt"); - validFieldEvents.push_back ("format-depends"); + validProgramEvents.push_back ("on-file-read"); // Unimplemented + validProgramEvents.push_back ("on-file-write"); // Unimplemented + validProgramEvents.push_back ("on-synch"); // Unimplemented + validProgramEvents.push_back ("on-merge"); // Unimplemented + validProgramEvents.push_back ("on-gc"); // Unimplemented } //////////////////////////////////////////////////////////////////////////////// @@ -395,36 +206,6 @@ bool Hooks::trigger (const std::string& event, Task& task) return true; } -//////////////////////////////////////////////////////////////////////////////// -// Field hooks. -bool Hooks::trigger ( - const std::string& event, - const std::string& name, - std::string& value) -{ -#ifdef HAVE_LIBLUA - std::vector ::iterator it; - for (it = all.begin (); it != all.end (); ++it) - { - if (it->event == event) - { - Timer timer (std::string ("Hooks::trigger ") + event); - - if (validFieldEvent (event)) - { - context.debug (std::string ("Event ") + event + " triggered"); - if (! api.callFieldHook (it->file, it->function, name, value)) - return false; - } - else - throw std::string ("Unrecognized hook event '") + event + "'."; - } - } -#endif - - return true; -} - //////////////////////////////////////////////////////////////////////////////// bool Hooks::validProgramEvent (const std::string& event) { @@ -442,12 +223,4 @@ bool Hooks::validTaskEvent (const std::string& event) return false; } -bool Hooks::validFieldEvent (const std::string& event) -{ - if (std::find (validFieldEvents.begin (), validFieldEvents.end (), event) != validFieldEvents.end ()) - return true; - - return false; -} - //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Hooks.h b/src/Hooks.h index bb153ada1..e1e5c7725 100644 --- a/src/Hooks.h +++ b/src/Hooks.h @@ -60,12 +60,10 @@ public: bool trigger (const std::string&); // Program bool trigger (const std::string&, Task&); // Task - bool trigger (const std::string&, const std::string&, std::string&); // Field private: bool validProgramEvent (const std::string&); bool validTaskEvent (const std::string&); - bool validFieldEvent (const std::string&); private: #ifdef HAVE_LIBLUA @@ -75,7 +73,6 @@ private: std::vector validProgramEvents; std::vector validTaskEvents; - std::vector validFieldEvents; }; #endif diff --git a/src/TDB.cpp b/src/TDB.cpp index 748b3e7bd..a0a8a1809 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -191,25 +191,21 @@ void TDB::location (const std::string& path) //////////////////////////////////////////////////////////////////////////////// void TDB::lock (bool lockFile /* = true */) { - if (context.hooks.trigger ("pre-file-lock")) + mLock = lockFile; + + mPending.clear (); + mNew.clear (); + mCompleted.clear (); + mModified.clear (); + + foreach (location, mLocations) { - mLock = lockFile; - - mPending.clear (); - mNew.clear (); - mCompleted.clear (); - mModified.clear (); - - foreach (location, mLocations) - { - location->pending = openAndLock (location->path + "/pending.data"); - location->completed = openAndLock (location->path + "/completed.data"); - location->undo = openAndLock (location->path + "/undo.data"); - } - - mAllOpenAndLocked = true; - context.hooks.trigger ("post-file-lock"); + location->pending = openAndLock (location->path + "/pending.data"); + location->completed = openAndLock (location->path + "/completed.data"); + location->undo = openAndLock (location->path + "/undo.data"); } + + mAllOpenAndLocked = true; } //////////////////////////////////////////////////////////////////////////////// @@ -492,7 +488,6 @@ void TDB::update (const Task& task) int TDB::commit () { Timer t ("TDB::commit"); - context.hooks.trigger ("pre-commit"); int quantity = mNew.size () + mModified.size (); @@ -512,7 +507,6 @@ int TDB::commit () writeUndo (*task, mLocations[0].undo); mNew.clear (); - context.hooks.trigger ("post-commit"); return quantity; } @@ -579,7 +573,6 @@ int TDB::commit () mNew.clear (); } - context.hooks.trigger ("post-commit"); return quantity; } @@ -713,7 +706,6 @@ int TDB::nextId () //////////////////////////////////////////////////////////////////////////////// void TDB::undo () { - context.hooks.trigger ("pre-undo"); Directory location (context.config.get ("data.location")); std::string undoFile = location.data + "/undo.data"; @@ -997,7 +989,6 @@ void TDB::undo () !confirm ("The undo command is not reversible. Are you sure you want to revert to the previous state?")) { std::cout << "No changes made.\n"; - context.hooks.trigger ("post-undo"); return; } @@ -1035,7 +1026,6 @@ void TDB::undo () // Rewrite files. File::write (pendingFile, p); File::write (undoFile, u); - context.hooks.trigger ("post-undo"); return; } } @@ -1074,7 +1064,6 @@ void TDB::undo () } std::cout << "Undo complete.\n"; - context.hooks.trigger ("post-undo"); return; } } diff --git a/src/TDB2.cpp b/src/TDB2.cpp index d7edf7327..fbcdd1b6c 100644 --- a/src/TDB2.cpp +++ b/src/TDB2.cpp @@ -341,25 +341,21 @@ void TDB::location (const std::string& path) //////////////////////////////////////////////////////////////////////////////// void TDB::lock (bool lockFile /* = true */) { - if (context.hooks.trigger ("pre-file-lock")) + mLock = lockFile; + + mPending.clear (); + mNew.clear (); + mCompleted.clear (); + mModified.clear (); + + foreach (location, mLocations) { - mLock = lockFile; - - mPending.clear (); - mNew.clear (); - mCompleted.clear (); - mModified.clear (); - - foreach (location, mLocations) - { - location->pending = openAndLock (location->path + "/pending.data"); - location->completed = openAndLock (location->path + "/completed.data"); - location->undo = openAndLock (location->path + "/undo.data"); - } - - mAllOpenAndLocked = true; - context.hooks.trigger ("post-file-lock"); + location->pending = openAndLock (location->path + "/pending.data"); + location->completed = openAndLock (location->path + "/completed.data"); + location->undo = openAndLock (location->path + "/undo.data"); } + + mAllOpenAndLocked = true; } //////////////////////////////////////////////////////////////////////////////// @@ -642,7 +638,6 @@ void TDB::update (const Task& task) int TDB::commit () { Timer t ("TDB::commit"); - context.hooks.trigger ("pre-commit"); int quantity = mNew.size () + mModified.size (); @@ -662,7 +657,6 @@ int TDB::commit () writeUndo (*task, mLocations[0].undo); mNew.clear (); - context.hooks.trigger ("post-commit"); return quantity; } @@ -729,7 +723,6 @@ int TDB::commit () mNew.clear (); } - context.hooks.trigger ("post-commit"); return quantity; } @@ -863,7 +856,6 @@ int TDB::nextId () //////////////////////////////////////////////////////////////////////////////// void TDB::undo () { - context.hooks.trigger ("pre-undo"); Directory location (context.config.get ("data.location")); std::string undoFile = location.data + "/undo.data"; @@ -1147,7 +1139,6 @@ void TDB::undo () !confirm ("The undo command is not reversible. Are you sure you want to revert to the previous state?")) { std::cout << "No changes made.\n"; - context.hooks.trigger ("post-undo"); return; } @@ -1185,7 +1176,6 @@ void TDB::undo () // Rewrite files. File::write (pendingFile, p); File::write (undoFile, u); - context.hooks.trigger ("post-undo"); return; } } @@ -1224,7 +1214,6 @@ void TDB::undo () } std::cout << "Undo complete.\n"; - context.hooks.trigger ("post-undo"); return; } } diff --git a/src/burndown.cpp b/src/burndown.cpp index 0f572a3e1..7670848fb 100644 --- a/src/burndown.cpp +++ b/src/burndown.cpp @@ -974,47 +974,41 @@ int handleReportBurndownDaily (std::string& outs) { int rc = 0; - if (context.hooks.trigger ("pre-burndown-command")) + // Scan the pending tasks, applying any filter. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + // Create a chart, scan the tasks, then render. + Chart chart ('D'); + + // Use any filter as a title. + if (context.filter.size ()) { - // Scan the pending tasks, applying any filter. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); + std::string combined = "("; - // Create a chart, scan the tasks, then render. - Chart chart ('D'); - - // Use any filter as a title. - if (context.filter.size ()) + for (unsigned int i = 0; i < context.filter.size (); ++i) { - std::string combined = "("; + if (i) + combined += " "; - for (unsigned int i = 0; i < context.filter.size (); ++i) - { - if (i) - combined += " "; + combined += context.filter[i].name (); - combined += context.filter[i].name (); + if (context.filter[i].mod ().length ()) + combined += "." + context.filter[i].mod (); - if (context.filter[i].mod ().length ()) - combined += "." + context.filter[i].mod (); - - combined += ":" + context.filter[i].value (); - } - - combined += ")"; - chart.description (combined); + combined += ":" + context.filter[i].value (); } - chart.scan (tasks); - outs = chart.render (); - - context.hooks.trigger ("post-burndown-command"); + combined += ")"; + chart.description (combined); } + chart.scan (tasks); + outs = chart.render (); return rc; } @@ -1023,47 +1017,41 @@ int handleReportBurndownWeekly (std::string& outs) { int rc = 0; - if (context.hooks.trigger ("pre-burndown-command")) + // Scan the pending tasks, applying any filter. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + // Create a chart, scan the tasks, then render. + Chart chart ('W'); + + // Use any filter as a title. + if (context.filter.size ()) { - // Scan the pending tasks, applying any filter. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); + std::string combined = "("; - // Create a chart, scan the tasks, then render. - Chart chart ('W'); - - // Use any filter as a title. - if (context.filter.size ()) + for (unsigned int i = 0; i < context.filter.size (); ++i) { - std::string combined = "("; + if (i) + combined += " "; - for (unsigned int i = 0; i < context.filter.size (); ++i) - { - if (i) - combined += " "; + combined += context.filter[i].name (); - combined += context.filter[i].name (); + if (context.filter[i].mod ().length ()) + combined += "." + context.filter[i].mod (); - if (context.filter[i].mod ().length ()) - combined += "." + context.filter[i].mod (); - - combined += ":" + context.filter[i].value (); - } - - combined += ")"; - chart.description (combined); + combined += ":" + context.filter[i].value (); } - chart.scan (tasks); - outs = chart.render (); - - context.hooks.trigger ("post-burndown-command"); + combined += ")"; + chart.description (combined); } + chart.scan (tasks); + outs = chart.render (); return rc; } @@ -1072,47 +1060,41 @@ int handleReportBurndownMonthly (std::string& outs) { int rc = 0; - if (context.hooks.trigger ("pre-burndown-command")) + // Scan the pending tasks, applying any filter. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + // Create a chart, scan the tasks, then render. + Chart chart ('M'); + + // Use any filter as a title. + if (context.filter.size ()) { - // Scan the pending tasks, applying any filter. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); + std::string combined = "("; - // Create a chart, scan the tasks, then render. - Chart chart ('M'); - - // Use any filter as a title. - if (context.filter.size ()) + for (unsigned int i = 0; i < context.filter.size (); ++i) { - std::string combined = "("; + if (i) + combined += " "; - for (unsigned int i = 0; i < context.filter.size (); ++i) - { - if (i) - combined += " "; + combined += context.filter[i].name (); - combined += context.filter[i].name (); + if (context.filter[i].mod ().length ()) + combined += "." + context.filter[i].mod (); - if (context.filter[i].mod ().length ()) - combined += "." + context.filter[i].mod (); - - combined += ":" + context.filter[i].value (); - } - - combined += ")"; - chart.description (combined); + combined += ":" + context.filter[i].value (); } - chart.scan (tasks); - outs = chart.render (); - - context.hooks.trigger ("post-burndown-command"); + combined += ")"; + chart.description (combined); } + chart.scan (tasks); + outs = chart.render (); return rc; } diff --git a/src/command.cpp b/src/command.cpp index f68cce711..14c6a20e7 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -2457,7 +2457,7 @@ void handleShell () std::string prompt = context.config.get ("shell.prompt"); if (context.hooks.trigger ("pre-shell-prompt")) { - context.hooks.trigger ("format-prompt", "prompt", prompt); + //context.hooks.trigger ("format-prompt", "prompt", prompt); std::cout << prompt << " "; } context.hooks.trigger ("post-shell-prompt"); diff --git a/src/custom.cpp b/src/custom.cpp index a3c6f3d22..6509ebb2c 100644 --- a/src/custom.cpp +++ b/src/custom.cpp @@ -53,646 +53,616 @@ int handleCustomReport (const std::string& report, std::string& outs) { int rc = 0; - if (context.hooks.trigger ("pre-custom-report-command") && - context.hooks.trigger (std::string ("pre-") + report + "-command")) + // Load report configuration. + std::string reportColumns = context.config.get ("report." + report + ".columns"); + std::string reportLabels = context.config.get ("report." + report + ".labels"); + std::string reportSort = context.config.get ("report." + report + ".sort"); + std::string reportFilter = context.config.get ("report." + report + ".filter"); + + std::vector columns; + split (columns, reportColumns, ','); + validReportColumns (columns); + + std::vector labels; + split (labels, reportLabels, ','); + + if (columns.size () != labels.size () && labels.size () != 0) + throw std::string ("There are a different number of columns than labels ") + + "for report '" + report + "'."; + + std::map columnLabels; + if (labels.size ()) + for (unsigned int i = 0; i < columns.size (); ++i) + columnLabels[columns[i]] = labels[i]; + + std::vector sortOrder; + split (sortOrder, reportSort, ','); + validSortColumns (columns, sortOrder); + + // Apply rc overrides. + std::vector filterArgs; + std::vector filteredArgs; + split (filterArgs, reportFilter, ' '); + context.applyOverrides (filterArgs, filteredArgs); + { - // Load report configuration. - std::string reportColumns = context.config.get ("report." + report + ".columns"); - std::string reportLabels = context.config.get ("report." + report + ".labels"); - std::string reportSort = context.config.get ("report." + report + ".sort"); - std::string reportFilter = context.config.get ("report." + report + ".filter"); + Cmd cmd (report); + Task task; + Sequence sequence; + Subst subst; + Filter filter; + context.parse (filteredArgs, cmd, task, sequence, subst, filter); - std::vector columns; - split (columns, reportColumns, ','); - validReportColumns (columns); + context.sequence.combine (sequence); - std::vector labels; - split (labels, reportLabels, ','); + // Special case: Allow limit to be overridden by the command line. + if (!context.task.has ("limit") && task.has ("limit")) + context.task.set ("limit", task.get ("limit")); - if (columns.size () != labels.size () && labels.size () != 0) - throw std::string ("There are a different number of columns than labels ") + - "for report '" + report + "'."; - - std::map columnLabels; - if (labels.size ()) - for (unsigned int i = 0; i < columns.size (); ++i) - columnLabels[columns[i]] = labels[i]; - - std::vector sortOrder; - split (sortOrder, reportSort, ','); - validSortColumns (columns, sortOrder); - - // Apply rc overrides. - std::vector filterArgs; - std::vector filteredArgs; - split (filterArgs, reportFilter, ' '); - context.applyOverrides (filterArgs, filteredArgs); - - { - Cmd cmd (report); - Task task; - Sequence sequence; - Subst subst; - Filter filter; - context.parse (filteredArgs, cmd, task, sequence, subst, filter); - - context.sequence.combine (sequence); - - // Special case: Allow limit to be overridden by the command line. - if (!context.task.has ("limit") && task.has ("limit")) - context.task.set ("limit", task.get ("limit")); - - foreach (att, filter) - context.filter.push_back (*att); - } - - // Get all the tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); - - // Filter sequence. - if (context.sequence.size ()) - context.filter.applySequence (tasks, context.sequence); - - // Determine the output date format, which uses a hierarchy of definitions. - std::string dateformat = context.config.get ("report." + report + ".dateformat"); - if (dateformat == "") - dateformat = context.config.get ("dateformat.report"); - if (dateformat == "") - dateformat = context.config.get ("dateformat"); - - Table table; - table.setTableWidth (context.getWidth ()); - table.setDateFormat (dateformat); - table.setReportName (report); - - foreach (task, tasks) - { - table.addRow (); - context.hooks.trigger ("pre-display", *task); - } - - int columnCount = 0; - int dueColumn = -1; - foreach (col, columns) - { - // Add each column individually. - if (*col == "id") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "ID"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - std::string value; - int row = 0; - foreach (task, tasks) - { - if (task->id != 0) - value = format (task->id); - else - value = "-"; - - context.hooks.trigger ("format-id", "id", value); - table.addCell (row++, columnCount, value); - } - } - - else if (*col == "uuid") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "UUID"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::left); - - std::string value; - int row = 0; - foreach (task, tasks) - { - value = task->get ("uuid"); - context.hooks.trigger ("format-uuid", "uuid", value); - table.addCell (row++, columnCount, value); - } - } - - else if (*col == "project") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Project"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::left); - - std::string value; - int row = 0; - foreach (task, tasks) - { - value = task->get ("project"); - context.hooks.trigger ("format-project", "project", value); - table.addCell (row++, columnCount, value); - } - } - - else if (*col == "priority") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Pri"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::left); - - int row = 0; - foreach (task, tasks) - { - std::string value = task->get ("priority"); - context.hooks.trigger ("format-priority", "priority", value); - table.addCell (row++, columnCount, value); - } - } - - else if (*col == "priority_long") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Pri"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::left); - - int row = 0; - std::string pri; - foreach (task, tasks) - { - pri = task->get ("priority"); - - if (pri == "H") pri = "High"; // TODO i18n - else if (pri == "M") pri = "Medium"; // TODO i18n - else if (pri == "L") pri = "Low"; // TODO i18n - - context.hooks.trigger ("format-priority_long", "priority", pri); - table.addCell (row++, columnCount, pri); - } - } - - else if (*col == "entry" || *col == "entry_time") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Added"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - std::string entered; - for (unsigned int row = 0; row < tasks.size(); ++row) - { - entered = tasks[row].get ("entry"); - if (entered.length ()) - { - Date dt (::atoi (entered.c_str ())); - entered = dt.toString (dateformat); - context.hooks.trigger ("format-entry", "entry", entered); - table.addCell (row, columnCount, entered); - } - } - } - - else if (*col == "start" || *col == "start_time") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Started"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - std::string started; - for (unsigned int row = 0; row < tasks.size(); ++row) - { - started = tasks[row].get ("start"); - if (started.length ()) - { - Date dt (::atoi (started.c_str ())); - started = dt.toString (dateformat); - context.hooks.trigger ("format-start", "start", started); - table.addCell (row, columnCount, started); - } - } - } - - else if (*col == "end" || *col == "end_time") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Completed"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - std::string ended; - for (unsigned int row = 0; row < tasks.size(); ++row) - { - ended = tasks[row].get ("end"); - if (ended.length ()) - { - Date dt (::atoi (ended.c_str ())); - ended = dt.toString (dateformat); - context.hooks.trigger ("format-end", "end", ended); - table.addCell (row, columnCount, ended); - } - } - } - - else if (*col == "due") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Due"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::left); - - int row = 0; - std::string due; - foreach (task, tasks) - { - std::string value = getDueDate (*task, dateformat); - context.hooks.trigger ("format-due", "due", value); - table.addCell (row++, columnCount, value); - } - - dueColumn = columnCount; - } - - else if (*col == "countdown") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Countdown"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - std::string due; - std::string countdown; - Date now; - for (unsigned int row = 0; row < tasks.size(); ++row) - { - due = tasks[row].get ("due"); - if (due.length ()) - { - Date dt (::atoi (due.c_str ())); - countdown = Duration (now - dt).format (); - context.hooks.trigger ("format-countdown", "countdown", countdown); - table.addCell (row, columnCount, countdown); - } - } - } - - else if (*col == "countdown_compact") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Countdown"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - std::string due; - std::string countdown; - Date now; - for (unsigned int row = 0; row < tasks.size(); ++row) - { - due = tasks[row].get ("due"); - if (due.length ()) - { - Date dt (::atoi (due.c_str ())); - countdown = Duration (now - dt).formatCompact (); - context.hooks.trigger ("format-countdown_compact", "countdown_compact", countdown); - table.addCell (row, columnCount, countdown); - } - } - } - - else if (*col == "age") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Age"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - std::string created; - std::string age; - Date now; - for (unsigned int row = 0; row < tasks.size(); ++row) - { - created = tasks[row].get ("entry"); - if (created.length ()) - { - Date dt (::atoi (created.c_str ())); - age = Duration (now - dt).format (); - context.hooks.trigger ("format-age", "age", age); - table.addCell (row, columnCount, age); - } - } - } - - else if (*col == "age_compact") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Age"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - std::string created; - std::string age; - Date now; - for (unsigned int row = 0; row < tasks.size(); ++row) - { - created = tasks[row].get ("entry"); - if (created.length ()) - { - Date dt (::atoi (created.c_str ())); - age = Duration (now - dt).formatCompact (); - context.hooks.trigger ("format-age_compact", "age_compact", age); - table.addCell (row, columnCount, age); - } - } - } - - else if (*col == "active") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Active"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::left); - - for (unsigned int row = 0; row < tasks.size(); ++row) - if (tasks[row].has ("start")) - table.addCell (row, columnCount, context.config.get ("active.indicator")); - } - - else if (*col == "tags") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Tags"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::left); - - int row = 0; - std::vector all; - std::string tags; - foreach (task, tasks) - { - task->getTags (all); - join (tags, " ", all); - context.hooks.trigger ("format-tags", "tags", tags); - table.addCell (row++, columnCount, tags); - } - } - - else if (*col == "description_only") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Description"); - table.setColumnWidth (columnCount, Table::flexible); - table.setColumnJustification (columnCount, Table::left); - - std::string desc; - int row = 0; - foreach (task, tasks) - { - desc = task->get ("description"); - context.hooks.trigger ("format-description_only", "description_only", desc); - table.addCell (row++, columnCount, desc); - } - } - - else if (*col == "description") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Description"); - table.setColumnWidth (columnCount, Table::flexible); - table.setColumnJustification (columnCount, Table::left); - - std::string desc; - int row = 0; - foreach (task, tasks) - { - desc = getFullDescription (*task, report); - context.hooks.trigger ("format-description", "description", desc); - table.addCell (row++, columnCount, desc); - } - } - - else if (*col == "recur") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Recur"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - for (unsigned int row = 0; row < tasks.size(); ++row) - { - std::string recur = tasks[row].get ("recur"); - if (recur != "") - { - context.hooks.trigger ("format-recur", "recur", recur); - table.addCell (row, columnCount, recur); - } - } - } - - else if (*col == "recurrence_indicator") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "R"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - for (unsigned int row = 0; row < tasks.size(); ++row) - if (tasks[row].has ("recur")) - table.addCell (row, columnCount, context.config.get ("recurrence.indicator")); - } - - else if (*col == "tag_indicator") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "T"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - for (unsigned int row = 0; row < tasks.size(); ++row) - if (tasks[row].getTagCount ()) - table.addCell (row, columnCount, context.config.get ("tag.indicator")); - } - - else if (*col == "wait") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Wait"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - int row = 0; - std::string wait; - foreach (task, tasks) - { - wait = task->get ("wait"); - if (wait != "") - { - Date dt (::atoi (wait.c_str ())); - wait = dt.toString (dateformat); - context.hooks.trigger ("format-wait", "wait", wait); - table.addCell (row++, columnCount, wait); - } - } - } - - else if (*col == "depends") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Deps"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - int row = 0; - std::vector blocked; - std::vector blocked_ids; - std::string deps; - foreach (task, tasks) - { - dependencyGetBlocking (*task, blocked); - foreach (b, blocked) - blocked_ids.push_back (b->id); - - join (deps, ",", blocked_ids); - blocked_ids.clear (); - blocked.clear (); - - context.hooks.trigger ("format-depends", "depends", deps); - table.addCell (row++, columnCount, deps); - } - } - - else if (*col == "urgency") - { - table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Urgency"); - table.setColumnWidth (columnCount, Table::minimum); - table.setColumnJustification (columnCount, Table::right); - - int row = 0; - foreach (task, tasks) - { - std::string value = format (task->urgency (), 4, 3); - context.hooks.trigger ("format-urgency", "urgency", value); - table.addCell (row++, columnCount, value); - } - } - - // Common to all columns. - // Add underline. - if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && - context.config.getBoolean ("fontunderline")) - table.setColumnUnderline (columnCount); - else - table.setTableDashedUnderline (); - - ++columnCount; - } - - // Dynamically add sort criteria. - // Build a map of column names -> index. - std::map columnIndex; - for (unsigned int c = 0; c < columns.size (); ++c) - columnIndex[columns[c]] = c; - - foreach (sortColumn, sortOrder) - { - // Separate column and direction. - std::string column = sortColumn->substr (0, sortColumn->length () - 1); - char direction = (*sortColumn)[sortColumn->length () - 1]; - - // TODO This code should really be using Att::type. - if (column == "id" || column == "urgency") - table.sortOn (columnIndex[column], - (direction == '+' ? - Table::ascendingNumeric : - Table::descendingNumeric)); - - else if (column == "priority") - table.sortOn (columnIndex[column], - (direction == '+' ? - Table::ascendingPriority : - Table::descendingPriority)); - - else if (column == "entry" || column == "start" || column == "wait" || - column == "until" || column == "end" || column == "entry_time" || - column == "start_time" || column == "end_time") - table.sortOn (columnIndex[column], - (direction == '+' ? - Table::ascendingDate : - Table::descendingDate)); - - else if (column == "due") - table.sortOn (columnIndex[column], - (direction == '+' ? - Table::ascendingDueDate : - Table::descendingDueDate)); - - else if (column == "recur" || column == "age" || column == "age_compact") - table.sortOn (columnIndex[column], - (direction == '+' ? - Table::ascendingPeriod : - Table::descendingPeriod)); - - else if (column == "countdown" || column == "countdown_compact") - table.sortOn (columnIndex[column], - (direction == '+' ? - Table::descendingPeriod : // Yes, these are flipped. - Table::ascendingPeriod)); // Yes, these are flipped. - - else - table.sortOn (columnIndex[column], - (direction == '+' ? - Table::ascendingCharacter : - Table::descendingCharacter)); - } - - // Now auto colorize all rows. - if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) - { - for (unsigned int row = 0; row < tasks.size (); ++row) - { - Color c (tasks[row].get ("fg") + " " + tasks[row].get ("bg")); - autoColorize (tasks[row], c); - table.setRowColor (row, c); - } - } - - // If an alternating row color is specified, notify the table. - if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) - { - Color alternate (context.config.get ("color.alternate")); - if (alternate.nontrivial ()) - table.setTableAlternateColor (alternate); - } - - // How many lines taken up by table header? - int table_header; - if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && - context.config.getBoolean ("fontunderline")) - table_header = 1; // Underlining doesn't use extra line. - else - table_header = 2; // Dashes use an extra line. - - // Report output can be limited by rows or lines. - int maxrows = 0; - int maxlines = 0; - getLimits (report, maxrows, maxlines); - - // Adjust for fluff in the output. - if (maxlines) - maxlines -= (context.config.getBoolean ("blanklines") ? 1 : 0) - + table_header - + context.headers.size () - + context.footnotes.size (); - - std::stringstream out; - if (table.rowCount ()) - { - out << optionalBlankLine () - << table.render (maxrows, maxlines) - << optionalBlankLine () - << table.rowCount () - << (table.rowCount () == 1 ? " task" : " tasks"); - - if (maxrows && maxrows < table.rowCount ()) - out << ", " << maxrows << " shown"; - - if (maxlines && maxlines < table.rowCount ()) - out << ", truncated to " << maxlines - table_header << " lines"; - - out << std::endl; - } - else - { - out << "No matches." - << std::endl; - rc = 1; - } - - outs = out.str (); - context.hooks.trigger (std::string ("post-") + report + "-command"); - context.hooks.trigger ("post-custom-report-command"); + foreach (att, filter) + context.filter.push_back (*att); } + // Get all the tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + // Filter sequence. + if (context.sequence.size ()) + context.filter.applySequence (tasks, context.sequence); + + // Determine the output date format, which uses a hierarchy of definitions. + std::string dateformat = context.config.get ("report." + report + ".dateformat"); + if (dateformat == "") + dateformat = context.config.get ("dateformat.report"); + if (dateformat == "") + dateformat = context.config.get ("dateformat"); + + Table table; + table.setTableWidth (context.getWidth ()); + table.setDateFormat (dateformat); + table.setReportName (report); + + foreach (task, tasks) + table.addRow (); + + int columnCount = 0; + int dueColumn = -1; + foreach (col, columns) + { + // Add each column individually. + if (*col == "id") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "ID"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + std::string value; + int row = 0; + foreach (task, tasks) + { + if (task->id != 0) + value = format (task->id); + else + value = "-"; + + table.addCell (row++, columnCount, value); + } + } + + else if (*col == "uuid") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "UUID"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + std::string value; + int row = 0; + foreach (task, tasks) + { + value = task->get ("uuid"); + table.addCell (row++, columnCount, value); + } + } + + else if (*col == "project") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Project"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + std::string value; + int row = 0; + foreach (task, tasks) + { + value = task->get ("project"); + table.addCell (row++, columnCount, value); + } + } + + else if (*col == "priority") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Pri"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + int row = 0; + foreach (task, tasks) + { + std::string value = task->get ("priority"); + table.addCell (row++, columnCount, value); + } + } + + else if (*col == "priority_long") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Pri"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + int row = 0; + std::string pri; + foreach (task, tasks) + { + pri = task->get ("priority"); + + if (pri == "H") pri = "High"; // TODO i18n + else if (pri == "M") pri = "Medium"; // TODO i18n + else if (pri == "L") pri = "Low"; // TODO i18n + + table.addCell (row++, columnCount, pri); + } + } + + else if (*col == "entry" || *col == "entry_time") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Added"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + std::string entered; + for (unsigned int row = 0; row < tasks.size(); ++row) + { + entered = tasks[row].get ("entry"); + if (entered.length ()) + { + Date dt (::atoi (entered.c_str ())); + entered = dt.toString (dateformat); + table.addCell (row, columnCount, entered); + } + } + } + + else if (*col == "start" || *col == "start_time") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Started"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + std::string started; + for (unsigned int row = 0; row < tasks.size(); ++row) + { + started = tasks[row].get ("start"); + if (started.length ()) + { + Date dt (::atoi (started.c_str ())); + started = dt.toString (dateformat); + table.addCell (row, columnCount, started); + } + } + } + + else if (*col == "end" || *col == "end_time") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Completed"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + std::string ended; + for (unsigned int row = 0; row < tasks.size(); ++row) + { + ended = tasks[row].get ("end"); + if (ended.length ()) + { + Date dt (::atoi (ended.c_str ())); + ended = dt.toString (dateformat); + table.addCell (row, columnCount, ended); + } + } + } + + else if (*col == "due") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Due"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + int row = 0; + std::string due; + foreach (task, tasks) + { + std::string value = getDueDate (*task, dateformat); + table.addCell (row++, columnCount, value); + } + + dueColumn = columnCount; + } + + else if (*col == "countdown") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Countdown"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + std::string due; + std::string countdown; + Date now; + for (unsigned int row = 0; row < tasks.size(); ++row) + { + due = tasks[row].get ("due"); + if (due.length ()) + { + Date dt (::atoi (due.c_str ())); + countdown = Duration (now - dt).format (); + table.addCell (row, columnCount, countdown); + } + } + } + + else if (*col == "countdown_compact") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Countdown"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + std::string due; + std::string countdown; + Date now; + for (unsigned int row = 0; row < tasks.size(); ++row) + { + due = tasks[row].get ("due"); + if (due.length ()) + { + Date dt (::atoi (due.c_str ())); + countdown = Duration (now - dt).formatCompact (); + table.addCell (row, columnCount, countdown); + } + } + } + + else if (*col == "age") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Age"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + std::string created; + std::string age; + Date now; + for (unsigned int row = 0; row < tasks.size(); ++row) + { + created = tasks[row].get ("entry"); + if (created.length ()) + { + Date dt (::atoi (created.c_str ())); + age = Duration (now - dt).format (); + table.addCell (row, columnCount, age); + } + } + } + + else if (*col == "age_compact") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Age"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + std::string created; + std::string age; + Date now; + for (unsigned int row = 0; row < tasks.size(); ++row) + { + created = tasks[row].get ("entry"); + if (created.length ()) + { + Date dt (::atoi (created.c_str ())); + age = Duration (now - dt).formatCompact (); + table.addCell (row, columnCount, age); + } + } + } + + else if (*col == "active") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Active"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + for (unsigned int row = 0; row < tasks.size(); ++row) + if (tasks[row].has ("start")) + table.addCell (row, columnCount, context.config.get ("active.indicator")); + } + + else if (*col == "tags") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Tags"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + int row = 0; + std::vector all; + std::string tags; + foreach (task, tasks) + { + task->getTags (all); + join (tags, " ", all); + table.addCell (row++, columnCount, tags); + } + } + + else if (*col == "description_only") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Description"); + table.setColumnWidth (columnCount, Table::flexible); + table.setColumnJustification (columnCount, Table::left); + + std::string desc; + int row = 0; + foreach (task, tasks) + { + desc = task->get ("description"); + table.addCell (row++, columnCount, desc); + } + } + + else if (*col == "description") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Description"); + table.setColumnWidth (columnCount, Table::flexible); + table.setColumnJustification (columnCount, Table::left); + + std::string desc; + int row = 0; + foreach (task, tasks) + { + desc = getFullDescription (*task, report); + table.addCell (row++, columnCount, desc); + } + } + + else if (*col == "recur") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Recur"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + for (unsigned int row = 0; row < tasks.size(); ++row) + { + std::string recur = tasks[row].get ("recur"); + if (recur != "") + { + table.addCell (row, columnCount, recur); + } + } + } + + else if (*col == "recurrence_indicator") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "R"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + for (unsigned int row = 0; row < tasks.size(); ++row) + if (tasks[row].has ("recur")) + table.addCell (row, columnCount, context.config.get ("recurrence.indicator")); + } + + else if (*col == "tag_indicator") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "T"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + for (unsigned int row = 0; row < tasks.size(); ++row) + if (tasks[row].getTagCount ()) + table.addCell (row, columnCount, context.config.get ("tag.indicator")); + } + + else if (*col == "wait") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Wait"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + int row = 0; + std::string wait; + foreach (task, tasks) + { + wait = task->get ("wait"); + if (wait != "") + { + Date dt (::atoi (wait.c_str ())); + wait = dt.toString (dateformat); + table.addCell (row++, columnCount, wait); + } + } + } + + else if (*col == "depends") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Deps"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + int row = 0; + std::vector blocked; + std::vector blocked_ids; + std::string deps; + foreach (task, tasks) + { + dependencyGetBlocking (*task, blocked); + foreach (b, blocked) + blocked_ids.push_back (b->id); + + join (deps, ",", blocked_ids); + blocked_ids.clear (); + blocked.clear (); + + table.addCell (row++, columnCount, deps); + } + } + + else if (*col == "urgency") + { + table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Urgency"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + int row = 0; + foreach (task, tasks) + { + std::string value = format (task->urgency (), 4, 3); + table.addCell (row++, columnCount, value); + } + } + + // Common to all columns. + // Add underline. + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) + table.setColumnUnderline (columnCount); + else + table.setTableDashedUnderline (); + + ++columnCount; + } + + // Dynamically add sort criteria. + // Build a map of column names -> index. + std::map columnIndex; + for (unsigned int c = 0; c < columns.size (); ++c) + columnIndex[columns[c]] = c; + + foreach (sortColumn, sortOrder) + { + // Separate column and direction. + std::string column = sortColumn->substr (0, sortColumn->length () - 1); + char direction = (*sortColumn)[sortColumn->length () - 1]; + + // TODO This code should really be using Att::type. + if (column == "id" || column == "urgency") + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::ascendingNumeric : + Table::descendingNumeric)); + + else if (column == "priority") + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::ascendingPriority : + Table::descendingPriority)); + + else if (column == "entry" || column == "start" || column == "wait" || + column == "until" || column == "end" || column == "entry_time" || + column == "start_time" || column == "end_time") + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::ascendingDate : + Table::descendingDate)); + + else if (column == "due") + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::ascendingDueDate : + Table::descendingDueDate)); + + else if (column == "recur" || column == "age" || column == "age_compact") + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::ascendingPeriod : + Table::descendingPeriod)); + + else if (column == "countdown" || column == "countdown_compact") + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::descendingPeriod : // Yes, these are flipped. + Table::ascendingPeriod)); // Yes, these are flipped. + + else + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::ascendingCharacter : + Table::descendingCharacter)); + } + + // Now auto colorize all rows. + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + { + for (unsigned int row = 0; row < tasks.size (); ++row) + { + Color c (tasks[row].get ("fg") + " " + tasks[row].get ("bg")); + autoColorize (tasks[row], c); + table.setRowColor (row, c); + } + } + + // If an alternating row color is specified, notify the table. + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + { + Color alternate (context.config.get ("color.alternate")); + if (alternate.nontrivial ()) + table.setTableAlternateColor (alternate); + } + + // How many lines taken up by table header? + int table_header; + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) + table_header = 1; // Underlining doesn't use extra line. + else + table_header = 2; // Dashes use an extra line. + + // Report output can be limited by rows or lines. + int maxrows = 0; + int maxlines = 0; + getLimits (report, maxrows, maxlines); + + // Adjust for fluff in the output. + if (maxlines) + maxlines -= (context.config.getBoolean ("blanklines") ? 1 : 0) + + table_header + + context.headers.size () + + context.footnotes.size (); + + std::stringstream out; + if (table.rowCount ()) + { + out << optionalBlankLine () + << table.render (maxrows, maxlines) + << optionalBlankLine () + << table.rowCount () + << (table.rowCount () == 1 ? " task" : " tasks"); + + if (maxrows && maxrows < table.rowCount ()) + out << ", " << maxrows << " shown"; + + if (maxlines && maxlines < table.rowCount ()) + out << ", truncated to " << maxlines - table_header << " lines"; + + out << std::endl; + } + else + { + out << "No matches." + << std::endl; + rc = 1; + } + + outs = out.str (); return rc; } diff --git a/src/edit.cpp b/src/edit.cpp index 86dc1ff28..8d265728a 100644 --- a/src/edit.cpp +++ b/src/edit.cpp @@ -652,32 +652,27 @@ int handleEdit (std::string& outs) { int rc = 0; - if (context.hooks.trigger ("pre-edit-command")) - { - std::stringstream out; + std::stringstream out; - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - Filter filter; - context.tdb.loadPending (tasks, filter); + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + Filter filter; + context.tdb.loadPending (tasks, filter); - // Filter sequence. - std::vector all = tasks; - context.filter.applySequence (tasks, context.sequence); + // Filter sequence. + std::vector all = tasks; + context.filter.applySequence (tasks, context.sequence); - std::vector ::iterator task; - for (task = tasks.begin (); task != tasks.end (); ++task) - if (editFile (*task)) - context.tdb.update (*task); + std::vector ::iterator task; + for (task = tasks.begin (); task != tasks.end (); ++task) + if (editFile (*task)) + context.tdb.update (*task); - context.tdb.commit (); - context.tdb.unlock (); - - outs = out.str (); - context.hooks.trigger ("post-edit-command"); - } + context.tdb.commit (); + context.tdb.unlock (); + outs = out.str (); return rc; } diff --git a/src/export.cpp b/src/export.cpp index d0dc5b45e..cd3c052b8 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -41,50 +41,40 @@ extern Context context; int handleExportCSV (std::string& outs) { int rc = 0; + std::stringstream out; - if (context.hooks.trigger ("pre-export-command")) - { - std::stringstream out; + // Deliberately no 'id'. + out << "'uuid'," + << "'status'," + << "'tags'," + << "'entry'," + << "'start'," + << "'due'," + << "'recur'," + << "'end'," + << "'project'," + << "'priority'," + << "'fg'," + << "'bg'," + << "'description'" + << "\n"; - // Deliberately no 'id'. - out << "'uuid'," - << "'status'," - << "'tags'," - << "'entry'," - << "'start'," - << "'due'," - << "'recur'," - << "'end'," - << "'project'," - << "'priority'," - << "'fg'," - << "'bg'," - << "'description'" - << "\n"; + // Get all the tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); - // Get all the tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); + foreach (task, tasks) + if (task->getStatus () != Task::recurring) + out << task->composeCSV ().c_str (); - foreach (task, tasks) - { - context.hooks.trigger ("pre-display", *task); - - if (task->getStatus () != Task::recurring) - out << task->composeCSV ().c_str (); - } - - outs = out.str (); - context.hooks.trigger ("post-export-command"); - - // Prevent messages from cluttering the export output. - context.headers.clear (); - } + outs = out.str (); + // Prevent messages from cluttering the export output. + context.headers.clear (); return rc; } @@ -95,133 +85,125 @@ int handleExportCSV (std::string& outs) int handleExportiCal (std::string& outs) { int rc = 0; + std::stringstream out; - if (context.hooks.trigger ("pre-export-command")) + out << "BEGIN:VCALENDAR\n" + << "VERSION:2.0\n" + << "PRODID:-//GBF//" << PACKAGE_STRING << "//EN\n"; + + // Get all the tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + foreach (task, tasks) { - std::stringstream out; - - out << "BEGIN:VCALENDAR\n" - << "VERSION:2.0\n" - << "PRODID:-//GBF//" << PACKAGE_STRING << "//EN\n"; - - // Get all the tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); - - foreach (task, tasks) + if (task->getStatus () != Task::recurring) { - context.hooks.trigger ("pre-display", *task); + out << "BEGIN:VTODO\n"; - if (task->getStatus () != Task::recurring) + // Required UID:20070313T123432Z-456553@example.com + out << "UID:" << task->get ("uuid") << "\n"; + + // Required DTSTAMP:20070313T123432Z + Date entry (atoi (task->get ("entry").c_str ())); + out << "DTSTAMP:" << entry.toISO () << "\n"; + + // Optional DTSTART:20070514T110000Z + if (task->has ("start")) { - out << "BEGIN:VTODO\n"; - - // Required UID:20070313T123432Z-456553@example.com - out << "UID:" << task->get ("uuid") << "\n"; - - // Required DTSTAMP:20070313T123432Z - Date entry (atoi (task->get ("entry").c_str ())); - out << "DTSTAMP:" << entry.toISO () << "\n"; - - // Optional DTSTART:20070514T110000Z - if (task->has ("start")) - { - Date start (atoi (task->get ("start").c_str ())); - out << "DTSTART:" << start.toISO () << "\n"; - } - - // Optional DUE:20070709T130000Z - if (task->has ("due")) - { - Date due (atoi (task->get ("due").c_str ())); - out << "DUE:" << due.toISO () << "\n"; - } - - // Optional COMPLETED:20070707T100000Z - if (task->has ("end") && task->getStatus () == Task::completed) - { - Date end (atoi (task->get ("end").c_str ())); - out << "COMPLETED:" << end.toISO () << "\n"; - } - - out << "SUMMARY:" << task->get ("description") << "\n"; - - // Optional CLASS:PUBLIC/PRIVATE/CONFIDENTIAL - std::string classification = context.config.get ("export.ical.class"); - if (classification == "") - classification = "PRIVATE"; - out << "CLASS:" << classification << "\n"; - - // Optional multiple CATEGORIES:FAMILY,FINANCE - if (task->getTagCount () > 0) - { - std::vector tags; - task->getTags (tags); - std::string all; - join (all, ",", tags); - out << "CATEGORIES:" << all << "\n"; - } - - // Optional PRIORITY: - // 1-4 H - // 5 M - // 6-9 L - if (task->has ("priority")) - { - out << "PRIORITY:"; - std::string priority = task->get ("priority"); - - if (priority == "H") out << "1"; - else if (priority == "M") out << "5"; - else out << "9"; - - out << "\n"; - } - - // Optional STATUS:NEEDS-ACTION/IN-PROCESS/COMPLETED/CANCELLED - out << "STATUS:"; - Task::status stat = task->getStatus (); - if (stat == Task::pending || stat == Task::waiting) - { - if (task->has ("start")) - out << "IN-PROCESS"; - else - out << "NEEDS-ACTION"; - } - else if (stat == Task::completed) - { - out << "COMPLETED"; - } - else if (stat == Task::deleted) - { - out << "CANCELLED"; - } - out << "\n"; - - // Optional COMMENT:annotation1 - // Optional COMMENT:annotation2 - std::vector annotations; - task->getAnnotations (annotations); - foreach (anno, annotations) - out << "COMMENT:" << anno->value () << "\n"; - - out << "END:VTODO\n"; + Date start (atoi (task->get ("start").c_str ())); + out << "DTSTART:" << start.toISO () << "\n"; } + + // Optional DUE:20070709T130000Z + if (task->has ("due")) + { + Date due (atoi (task->get ("due").c_str ())); + out << "DUE:" << due.toISO () << "\n"; + } + + // Optional COMPLETED:20070707T100000Z + if (task->has ("end") && task->getStatus () == Task::completed) + { + Date end (atoi (task->get ("end").c_str ())); + out << "COMPLETED:" << end.toISO () << "\n"; + } + + out << "SUMMARY:" << task->get ("description") << "\n"; + + // Optional CLASS:PUBLIC/PRIVATE/CONFIDENTIAL + std::string classification = context.config.get ("export.ical.class"); + if (classification == "") + classification = "PRIVATE"; + out << "CLASS:" << classification << "\n"; + + // Optional multiple CATEGORIES:FAMILY,FINANCE + if (task->getTagCount () > 0) + { + std::vector tags; + task->getTags (tags); + std::string all; + join (all, ",", tags); + out << "CATEGORIES:" << all << "\n"; + } + + // Optional PRIORITY: + // 1-4 H + // 5 M + // 6-9 L + if (task->has ("priority")) + { + out << "PRIORITY:"; + std::string priority = task->get ("priority"); + + if (priority == "H") out << "1"; + else if (priority == "M") out << "5"; + else out << "9"; + + out << "\n"; + } + + // Optional STATUS:NEEDS-ACTION/IN-PROCESS/COMPLETED/CANCELLED + out << "STATUS:"; + Task::status stat = task->getStatus (); + if (stat == Task::pending || stat == Task::waiting) + { + if (task->has ("start")) + out << "IN-PROCESS"; + else + out << "NEEDS-ACTION"; + } + else if (stat == Task::completed) + { + out << "COMPLETED"; + } + else if (stat == Task::deleted) + { + out << "CANCELLED"; + } + out << "\n"; + + // Optional COMMENT:annotation1 + // Optional COMMENT:annotation2 + std::vector annotations; + task->getAnnotations (annotations); + foreach (anno, annotations) + out << "COMMENT:" << anno->value () << "\n"; + + out << "END:VTODO\n"; } - - out << "END:VCALENDAR\n"; - - outs = out.str (); - context.hooks.trigger ("post-export-command"); - - // Prevent messages from cluttering the export output. - context.headers.clear (); } + out << "END:VCALENDAR\n"; + + outs = out.str (); + + // Prevent messages from cluttering the export output. + context.headers.clear (); return rc; } @@ -230,36 +212,27 @@ int handleExportYAML (std::string& outs) { int rc = 0; - if (context.hooks.trigger ("pre-export-command")) - { - // YAML header. - std::stringstream out; - out << "%YAML 1.1\n" - << "---\n"; + // YAML header. + std::stringstream out; + out << "%YAML 1.1\n" + << "---\n"; - // Get all the tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); + // Get all the tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); - foreach (task, tasks) - { - context.hooks.trigger ("pre-display", *task); - out << task->composeYAML ().c_str (); - } + foreach (task, tasks) + out << task->composeYAML ().c_str (); - out << "...\n"; - - outs = out.str (); - context.hooks.trigger ("post-export-command"); - - // Prevent messages from cluttering the export output. - context.headers.clear (); - } + out << "...\n"; + outs = out.str (); + // Prevent messages from cluttering the export output. + context.headers.clear (); return rc; } diff --git a/src/history.cpp b/src/history.cpp index 580978e24..cf466aa90 100644 --- a/src/history.cpp +++ b/src/history.cpp @@ -39,80 +39,397 @@ extern Context context; int handleReportHistoryMonthly (std::string& outs) { 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 - if (context.hooks.trigger ("pre-history-command")) + // Scan the pending tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + foreach (task, tasks) { - 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 + Date entry (task->get ("entry")); - // Scan the pending tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); + Date end; + if (task->has ("end")) + end = Date (task->get ("end")); - foreach (task, tasks) + time_t epoch = entry.startOfMonth ().toEpoch (); + groups[epoch] = 0; + + // Every task has an entry date. + ++addedGroup[epoch]; + + // All deleted tasks have an end date. + if (task->getStatus () == Task::deleted) { - Date entry (task->get ("entry")); - - Date end; - if (task->has ("end")) - end = Date (task->get ("end")); - - time_t epoch = entry.startOfMonth ().toEpoch (); + epoch = end.startOfMonth ().toEpoch (); groups[epoch] = 0; - - // Every task has an entry date. - ++addedGroup[epoch]; - - // All deleted tasks have an end date. - if (task->getStatus () == Task::deleted) - { - epoch = end.startOfMonth ().toEpoch (); - groups[epoch] = 0; - ++deletedGroup[epoch]; - } - - // All completed tasks have an end date. - else if (task->getStatus () == Task::completed) - { - epoch = end.startOfMonth ().toEpoch (); - groups[epoch] = 0; - ++completedGroup[epoch]; - } + ++deletedGroup[epoch]; } - // Now build the table. - Table table; - table.setDateFormat (context.config.get ("dateformat")); - table.addColumn ("Year"); - table.addColumn ("Month"); - 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")) + // All completed tasks have an end date. + else if (task->getStatus () == Task::completed) { - table.setColumnUnderline (0); - table.setColumnUnderline (1); - table.setColumnUnderline (2); - table.setColumnUnderline (3); - table.setColumnUnderline (4); - table.setColumnUnderline (5); + epoch = end.startOfMonth ().toEpoch (); + groups[epoch] = 0; + ++completedGroup[epoch]; } - else - table.setTableDashedUnderline (); + } - table.setColumnJustification (2, Table::right); - table.setColumnJustification (3, Table::right); - table.setColumnJustification (4, Table::right); - table.setColumnJustification (5, Table::right); + // Now build the table. + Table table; + table.setDateFormat (context.config.get ("dateformat")); + table.addColumn ("Year"); + table.addColumn ("Month"); + 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); + table.setColumnUnderline (5); + } + else + table.setTableDashedUnderline (); + + table.setColumnJustification (2, Table::right); + table.setColumnJustification (3, Table::right); + table.setColumnJustification (4, Table::right); + table.setColumnJustification (5, 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; + } + table.addCell (row, 1, Date::monthName(m)); + + int net = 0; + + if (addedGroup.find (i->first) != addedGroup.end ()) + { + table.addCell (row, 2, addedGroup[i->first]); + net +=addedGroup[i->first]; + } + + if (completedGroup.find (i->first) != completedGroup.end ()) + { + table.addCell (row, 3, completedGroup[i->first]); + net -= completedGroup[i->first]; + } + + if (deletedGroup.find (i->first) != deletedGroup.end ()) + { + table.addCell (row, 4, deletedGroup[i->first]); + net -= deletedGroup[i->first]; + } + + table.addCell (row, 5, net); + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && net) + table.setCellColor (row, 5, net > 0 ? Color (Color::red) : + Color (Color::green)); + } + + if (table.rowCount ()) + { + table.addRow (); + row = table.addRow (); + + table.addCell (row, 1, "Average"); + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + table.setRowColor (row, Color (Color::nocolor, Color::nocolor, false, true, false)); + table.addCell (row, 2, totalAdded / (table.rowCount () - 2)); + table.addCell (row, 3, totalCompleted / (table.rowCount () - 2)); + table.addCell (row, 4, totalDeleted / (table.rowCount () - 2)); + table.addCell (row, 5, (totalAdded - totalCompleted - totalDeleted) / (table.rowCount () - 2)); + } + + std::stringstream out; + if (table.rowCount ()) + out << optionalBlankLine () + << table.render () + << "\n"; + else + { + out << "No tasks.\n"; + rc = 1; + } + + outs = out.str (); + return rc; +} + +//////////////////////////////////////////////////////////////////////////////// +int handleReportHistoryAnnual (std::string& outs) +{ + 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 + + // Scan the pending tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + foreach (task, tasks) + { + Date entry (task->get ("entry")); + + Date end; + if (task->has ("end")) + end = Date (task->get ("end")); + + time_t epoch = entry.startOfYear ().toEpoch (); + groups[epoch] = 0; + + // Every task has an entry date. + ++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 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 () + << "\n"; + else + { + out << "No tasks.\n"; + rc = 1; + } + + outs = out.str (); + return rc; +} + +//////////////////////////////////////////////////////////////////////////////// +int handleReportGHistoryMonthly (std::string& outs) +{ + 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 + + // Scan the pending tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + foreach (task, tasks) + { + Date entry (task->get ("entry")); + + Date end; + if (task->has ("end")) + end = Date (task->get ("end")); + + time_t epoch = entry.startOfMonth ().toEpoch (); + groups[epoch] = 0; + + // Every task has an entry date. + ++addedGroup[epoch]; + + // All deleted tasks have an end date. + if (task->getStatus () == Task::deleted) + { + epoch = end.startOfMonth ().toEpoch (); + groups[epoch] = 0; + ++deletedGroup[epoch]; + } + + // All completed tasks have an end date. + else if (task->getStatus () == Task::completed) + { + epoch = end.startOfMonth ().toEpoch (); + groups[epoch] = 0; + ++completedGroup[epoch]; + } + } + + 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 ("Month"); + table.addColumn ("Number Added/Completed/Deleted"); + + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) + { + table.setColumnUnderline (0); + table.setColumnUnderline (1); + } + else + table.setTableDashedUnderline (); + + Color color_add (context.config.get ("color.history.add")); + Color color_done (context.config.get ("color.history.done")); + Color color_delete (context.config.get ("color.history.delete")); + + // 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; @@ -124,9 +441,9 @@ int handleReportHistoryMonthly (std::string& outs) { row = table.addRow (); - totalAdded += addedGroup [i->first]; - totalCompleted += completedGroup [i->first]; - totalDeleted += deletedGroup [i->first]; + totalAdded += addedGroup[i->first]; + totalCompleted += completedGroup[i->first]; + totalDeleted += deletedGroup[i->first]; Date dt (i->first); int m, d, y; @@ -139,140 +456,170 @@ int handleReportHistoryMonthly (std::string& outs) } table.addCell (row, 1, Date::monthName(m)); - int net = 0; + unsigned int addedBar = (widthOfBar * addedGroup[i->first]) / maxLine; + unsigned int completedBar = (widthOfBar * completedGroup[i->first]) / maxLine; + unsigned int deletedBar = (widthOfBar * deletedGroup[i->first]) / maxLine; - if (addedGroup.find (i->first) != addedGroup.end ()) - { - table.addCell (row, 2, addedGroup[i->first]); - net +=addedGroup[i->first]; - } - - if (completedGroup.find (i->first) != completedGroup.end ()) - { - table.addCell (row, 3, completedGroup[i->first]); - net -= completedGroup[i->first]; - } - - if (deletedGroup.find (i->first) != deletedGroup.end ()) - { - table.addCell (row, 4, deletedGroup[i->first]); - net -= deletedGroup[i->first]; - } - - table.addCell (row, 5, net); - if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && net) - table.setCellColor (row, 5, net > 0 ? Color (Color::red) : - Color (Color::green)); - } - - if (table.rowCount ()) - { - table.addRow (); - row = table.addRow (); - - table.addCell (row, 1, "Average"); + std::string bar = ""; if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) - table.setRowColor (row, Color (Color::nocolor, Color::nocolor, false, true, false)); - table.addCell (row, 2, totalAdded / (table.rowCount () - 2)); - table.addCell (row, 3, totalCompleted / (table.rowCount () - 2)); - table.addCell (row, 4, totalDeleted / (table.rowCount () - 2)); - table.addCell (row, 5, (totalAdded - totalCompleted - totalDeleted) / (table.rowCount () - 2)); - } + { + std::string aBar = ""; + if (addedGroup[i->first]) + { + aBar = format (addedGroup[i->first]); + while (aBar.length () < addedBar) + aBar = " " + aBar; + } - std::stringstream out; - if (table.rowCount ()) - out << optionalBlankLine () - << table.render () - << "\n"; - else - { - out << "No tasks.\n"; - rc = 1; - } + std::string cBar = ""; + if (completedGroup[i->first]) + { + cBar = format (completedGroup[i->first]); + while (cBar.length () < completedBar) + cBar = " " + cBar; + } - outs = out.str (); - context.hooks.trigger ("post-history-command"); + std::string dBar = ""; + if (deletedGroup[i->first]) + { + dBar = format (deletedGroup[i->first]); + while (dBar.length () < deletedBar) + dBar = " " + dBar; + } + + bar += std::string (leftOffset - aBar.length (), ' '); + + bar += color_add.colorize (aBar); + bar += color_done.colorize (cBar); + bar += color_delete.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 += "-"; + + bar += std::string (leftOffset - aBar.length (), ' '); + bar += aBar + cBar + dBar; + } + + table.addCell (row, 2, bar); + } } + std::stringstream out; + if (table.rowCount ()) + { + out << optionalBlankLine () + << table.render () + << "\n"; + + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + out << "Legend: " + << color_add.colorize ("added") + << ", " + << color_done.colorize ("completed") + << ", " + << color_delete.colorize ("deleted") + << optionalBlankLine () + << "\n"; + else + out << "Legend: + added, X completed, - deleted\n"; + } + else + { + out << "No tasks.\n"; + rc = 1; + } + + outs = out.str (); return rc; } //////////////////////////////////////////////////////////////////////////////// -int handleReportHistoryAnnual (std::string& outs) +int handleReportGHistoryAnnual (std::string& outs) { 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 - if (context.hooks.trigger ("pre-history-command")) + // Scan the pending tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + foreach (task, tasks) { - 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 + Date entry (task->get ("entry")); - // Scan the pending tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); + Date end; + if (task->has ("end")) + end = Date (task->get ("end")); - foreach (task, tasks) + time_t epoch = entry.startOfYear ().toEpoch (); + groups[epoch] = 0; + + // Every task has an entry date. + ++addedGroup[epoch]; + + // All deleted tasks have an end date. + if (task->getStatus () == Task::deleted) { - Date entry (task->get ("entry")); - - Date end; - if (task->has ("end")) - end = Date (task->get ("end")); - - time_t epoch = entry.startOfYear ().toEpoch (); + epoch = end.startOfYear ().toEpoch (); groups[epoch] = 0; - - // Every task has an entry date. - ++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]; - } + ++deletedGroup[epoch]; } - // 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")) + // All completed tasks have an end date. + else if (task->getStatus () == Task::completed) { - table.setColumnUnderline (0); - table.setColumnUnderline (1); - table.setColumnUnderline (2); - table.setColumnUnderline (3); - table.setColumnUnderline (4); + epoch = end.startOfYear ().toEpoch (); + groups[epoch] = 0; + ++completedGroup[epoch]; } - else - table.setTableDashedUnderline (); + } - table.setColumnJustification (1, Table::right); - table.setColumnJustification (2, Table::right); - table.setColumnJustification (3, Table::right); - table.setColumnJustification (4, Table::right); + int widthOfBar = context.getWidth () - 5; // 5 == strlen ("2008 ") + + // 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_add (context.config.get ("color.history.add")); + Color color_done (context.config.get ("color.history.done")); + Color color_delete (context.config.get ("color.history.delete")); + + // 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; @@ -284,9 +631,9 @@ int handleReportHistoryAnnual (std::string& outs) { row = table.addRow (); - totalAdded += addedGroup [i->first]; - totalCompleted += completedGroup [i->first]; - totalDeleted += deletedGroup [i->first]; + totalAdded += addedGroup[i->first]; + totalCompleted += completedGroup[i->first]; + totalDeleted += deletedGroup[i->first]; Date dt (i->first); int m, d, y; @@ -298,453 +645,82 @@ int handleReportHistoryAnnual (std::string& outs) priorYear = y; } - int net = 0; + unsigned int addedBar = (widthOfBar * addedGroup[i->first]) / maxLine; + unsigned int completedBar = (widthOfBar * completedGroup[i->first]) / maxLine; + unsigned int deletedBar = (widthOfBar * deletedGroup[i->first]) / maxLine; - 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"); + std::string bar = ""; 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 () - << "\n"; - else - { - out << "No tasks.\n"; - rc = 1; - } - - outs = out.str (); - context.hooks.trigger ("post-history-command"); - } - - return rc; -} - -//////////////////////////////////////////////////////////////////////////////// -int handleReportGHistoryMonthly (std::string& outs) -{ - int rc = 0; - - if (context.hooks.trigger ("pre-ghistory-command")) - { - 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 - - // Scan the pending tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); - - foreach (task, tasks) - { - Date entry (task->get ("entry")); - - Date end; - if (task->has ("end")) - end = Date (task->get ("end")); - - time_t epoch = entry.startOfMonth ().toEpoch (); - groups[epoch] = 0; - - // Every task has an entry date. - ++addedGroup[epoch]; - - // All deleted tasks have an end date. - if (task->getStatus () == Task::deleted) { - epoch = end.startOfMonth ().toEpoch (); - groups[epoch] = 0; - ++deletedGroup[epoch]; - } - - // All completed tasks have an end date. - else if (task->getStatus () == Task::completed) - { - epoch = end.startOfMonth ().toEpoch (); - groups[epoch] = 0; - ++completedGroup[epoch]; - } - } - - 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 ("Month"); - table.addColumn ("Number Added/Completed/Deleted"); - - if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && - context.config.getBoolean ("fontunderline")) - { - table.setColumnUnderline (0); - table.setColumnUnderline (1); - } - else - table.setTableDashedUnderline (); - - Color color_add (context.config.get ("color.history.add")); - Color color_done (context.config.get ("color.history.done")); - Color color_delete (context.config.get ("color.history.delete")); - - // 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) + std::string aBar = ""; + if (addedGroup[i->first]) { - 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 (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) - { - std::string aBar = ""; - if (addedGroup[i->first]) - { - aBar = format (addedGroup[i->first]); - while (aBar.length () < addedBar) - aBar = " " + aBar; - } - - std::string cBar = ""; - if (completedGroup[i->first]) - { - cBar = format (completedGroup[i->first]); - while (cBar.length () < completedBar) - cBar = " " + cBar; - } - - std::string dBar = ""; - if (deletedGroup[i->first]) - { - dBar = format (deletedGroup[i->first]); - while (dBar.length () < deletedBar) - dBar = " " + dBar; - } - - bar += std::string (leftOffset - aBar.length (), ' '); - - bar += color_add.colorize (aBar); - bar += color_done.colorize (cBar); - bar += color_delete.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 += "-"; - - bar += std::string (leftOffset - aBar.length (), ' '); - bar += aBar + cBar + dBar; + aBar = format (addedGroup[i->first]); + while (aBar.length () < addedBar) + aBar = " " + aBar; } - table.addCell (row, 2, bar); + std::string cBar = ""; + if (completedGroup[i->first]) + { + cBar = format (completedGroup[i->first]); + while (cBar.length () < completedBar) + cBar = " " + cBar; + } + + std::string dBar = ""; + if (deletedGroup[i->first]) + { + dBar = format (deletedGroup[i->first]); + while (dBar.length () < deletedBar) + dBar = " " + dBar; + } + + bar += std::string (leftOffset - aBar.length (), ' '); + bar += color_add.colorize (aBar); + bar += color_done.colorize (cBar); + bar += color_delete.colorize (dBar); } - } - - std::stringstream out; - if (table.rowCount ()) - { - out << optionalBlankLine () - << table.render () - << "\n"; - - if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) - out << "Legend: " - << color_add.colorize ("added") - << ", " - << color_done.colorize ("completed") - << ", " - << color_delete.colorize ("deleted") - << optionalBlankLine () - << "\n"; else - out << "Legend: + added, X completed, - deleted\n"; - } - else - { - out << "No tasks.\n"; - rc = 1; - } + { + 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 += "-"; - outs = out.str (); - context.hooks.trigger ("post-ghistory-command"); + bar += std::string (leftOffset - aBar.length (), ' '); + bar += aBar + cBar + dBar; + } + + table.addCell (row, 1, bar); + } } - return rc; -} - -//////////////////////////////////////////////////////////////////////////////// -int handleReportGHistoryAnnual (std::string& outs) -{ - int rc = 0; - - if (context.hooks.trigger ("pre-ghistory-command")) + std::stringstream out; + if (table.rowCount ()) { - 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 + out << optionalBlankLine () + << table.render () + << "\n"; - // Scan the pending tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); - - foreach (task, tasks) - { - Date entry (task->get ("entry")); - - Date end; - if (task->has ("end")) - end = Date (task->get ("end")); - - time_t epoch = entry.startOfYear ().toEpoch (); - groups[epoch] = 0; - - // Every task has an entry date. - ++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]; - } - } - - int widthOfBar = context.getWidth () - 5; // 5 == strlen ("2008 ") - - // 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_add (context.config.get ("color.history.add")); - Color color_done (context.config.get ("color.history.done")); - Color color_delete (context.config.get ("color.history.delete")); - - // 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")) - { - std::string aBar = ""; - if (addedGroup[i->first]) - { - aBar = format (addedGroup[i->first]); - while (aBar.length () < addedBar) - aBar = " " + aBar; - } - - std::string cBar = ""; - if (completedGroup[i->first]) - { - cBar = format (completedGroup[i->first]); - while (cBar.length () < completedBar) - cBar = " " + cBar; - } - - std::string dBar = ""; - if (deletedGroup[i->first]) - { - dBar = format (deletedGroup[i->first]); - while (dBar.length () < deletedBar) - dBar = " " + dBar; - } - - bar += std::string (leftOffset - aBar.length (), ' '); - bar += color_add.colorize (aBar); - bar += color_done.colorize (cBar); - bar += color_delete.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 += "-"; - - bar += std::string (leftOffset - aBar.length (), ' '); - bar += aBar + cBar + dBar; - } - - table.addCell (row, 1, bar); - } - } - - std::stringstream out; - if (table.rowCount ()) - { - out << optionalBlankLine () - << table.render () + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + out << "Legend: " + << color_add.colorize ("added") + << ", " + << color_done.colorize ("completed") + << ", " + << color_delete.colorize ("deleted") + << optionalBlankLine () << "\n"; - - if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) - out << "Legend: " - << color_add.colorize ("added") - << ", " - << color_done.colorize ("completed") - << ", " - << color_delete.colorize ("deleted") - << optionalBlankLine () - << "\n"; - else - out << "Legend: + added, X completed, - deleted\n"; - } else - { - out << "No tasks.\n"; - rc = 1; - } - - outs = out.str (); - context.hooks.trigger ("post-ghistory-command"); + out << "Legend: + added, X completed, - deleted\n"; + } + else + { + out << "No tasks.\n"; + rc = 1; } + outs = out.str (); return rc; } diff --git a/src/import.cpp b/src/import.cpp index 35bd6904f..29284f53b 100644 --- a/src/import.cpp +++ b/src/import.cpp @@ -1264,94 +1264,88 @@ static std::string importYAML (const std::vector & lines) int handleImport (std::string& outs) { int rc = 0; + std::stringstream out; - if (context.hooks.trigger ("pre-import-command")) + // Use the description as a file name. + std::string file = trim (context.task.get ("description")); + + std::string tmpfile = ""; + Uri uri (file); + uri.parse (); + + Transport* transport; + if ((transport = Transport::getTransport (uri)) != NULL ) { - std::stringstream out; + std::string location (context.config.get ("data.location")); + tmpfile = location + "/import.data"; + transport->recv (tmpfile); + delete transport; - // Use the description as a file name. - std::string file = trim (context.task.get ("description")); - - std::string tmpfile = ""; - Uri uri (file); - uri.parse (); - - Transport* transport; - if ((transport = Transport::getTransport (uri)) != NULL ) - { - std::string location (context.config.get ("data.location")); - tmpfile = location + "/import.data"; - transport->recv (tmpfile); - delete transport; - - file = tmpfile; - } - - if (file.length () > 0) - { - // Load the file. - std::vector all; - File::read (file, all); - - std::vector lines; - std::vector ::iterator it; - for (it = all.begin (); it != all.end (); ++it) - { - std::string line = *it; - trim (line); - - // Skip blank lines - if (line.length () > 0) - lines.push_back (line); - } - - // Take a guess at the file type. - fileType type = determineFileType (lines); - std::string identifier; - switch (type) - { - case task_1_4_3: identifier = "This looks like an older taskwarrior export file."; break; - case task_1_5_0: identifier = "This looks like a recent taskwarrior export file."; break; - case task_1_6_0: identifier = "This looks like a current taskwarrior export file."; break; - case task_cmd_line: identifier = "This looks like taskwarrior command line arguments."; break; - case todo_sh_2_0: identifier = "This looks like a todo.sh 2.x file."; break; - case csv: identifier = "This looks like a CSV file, but not a taskwarrior export file."; break; - case yaml: identifier = "This looks like a YAML file."; break; - case text: identifier = "This looks like a text file with one task per line."; break; - case not_a_clue: - throw std::string ("Taskwarrior cannot determine which type of file " - "this is, and cannot proceed."); - } - - // For tty users, confirm the import, as it is destructive. - if (isatty (fileno (stdout))) - if (! confirm (identifier + " Okay to proceed?")) - throw std::string ("Taskwarrior will not import any data."); - - // Determine which type it might be, then attempt an import. - switch (type) - { - case task_1_4_3: out << importTask_1_4_3 (lines); break; - case task_1_5_0: out << importTask_1_5_0 (lines); break; - case task_1_6_0: out << importTask_1_6_0 (lines); break; - case task_cmd_line: out << importTaskCmdLine (lines); break; - case todo_sh_2_0: out << importTodoSh_2_0 (lines); break; - case csv: out << importCSV (lines); break; - case yaml: out << importYAML (lines); break; - case text: out << importText (lines); break; - case not_a_clue: /* to stop the compiler from complaining. */ break; - } - - if (tmpfile != "") - remove (tmpfile.c_str ()); - } - else - throw std::string ("You must specify a file to import."); - - outs = out.str (); - context.hooks.trigger ("post-import-command"); + file = tmpfile; } + if (file.length () > 0) + { + // Load the file. + std::vector all; + File::read (file, all); + + std::vector lines; + std::vector ::iterator it; + for (it = all.begin (); it != all.end (); ++it) + { + std::string line = *it; + trim (line); + + // Skip blank lines + if (line.length () > 0) + lines.push_back (line); + } + + // Take a guess at the file type. + fileType type = determineFileType (lines); + std::string identifier; + switch (type) + { + case task_1_4_3: identifier = "This looks like an older taskwarrior export file."; break; + case task_1_5_0: identifier = "This looks like a recent taskwarrior export file."; break; + case task_1_6_0: identifier = "This looks like a current taskwarrior export file."; break; + case task_cmd_line: identifier = "This looks like taskwarrior command line arguments."; break; + case todo_sh_2_0: identifier = "This looks like a todo.sh 2.x file."; break; + case csv: identifier = "This looks like a CSV file, but not a taskwarrior export file."; break; + case yaml: identifier = "This looks like a YAML file."; break; + case text: identifier = "This looks like a text file with one task per line."; break; + case not_a_clue: + throw std::string ("Taskwarrior cannot determine which type of file " + "this is, and cannot proceed."); + } + + // For tty users, confirm the import, as it is destructive. + if (isatty (fileno (stdout))) + if (! confirm (identifier + " Okay to proceed?")) + throw std::string ("Taskwarrior will not import any data."); + + // Determine which type it might be, then attempt an import. + switch (type) + { + case task_1_4_3: out << importTask_1_4_3 (lines); break; + case task_1_5_0: out << importTask_1_5_0 (lines); break; + case task_1_6_0: out << importTask_1_6_0 (lines); break; + case task_cmd_line: out << importTaskCmdLine (lines); break; + case todo_sh_2_0: out << importTodoSh_2_0 (lines); break; + case csv: out << importCSV (lines); break; + case yaml: out << importYAML (lines); break; + case text: out << importText (lines); break; + case not_a_clue: /* to stop the compiler from complaining. */ break; + } + + if (tmpfile != "") + remove (tmpfile.c_str ()); + } + else + throw std::string ("You must specify a file to import."); + + outs = out.str (); return rc; } diff --git a/src/report.cpp b/src/report.cpp index 76f9847e9..4ce693fc7 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -298,83 +298,77 @@ int shortUsage (std::string& outs) int longUsage (std::string& outs) { int rc = 0; + std::string shortUsageString; + std::stringstream out; - if (context.hooks.trigger ("pre-usage-command")) - { - std::string shortUsageString; - std::stringstream out; + (void)shortUsage(shortUsageString); - (void)shortUsage(shortUsageString); - - out << shortUsageString - << "ID is the numeric identifier displayed by the 'task list' command. " - << "You can specify multiple IDs for task commands, and multiple tasks " - << "will be affected. To specify multiple IDs make sure you use one " - << "of these forms:" << "\n" - << " task delete 1,2,3" << "\n" - << " task info 1-3" << "\n" - << " task pri:H 1,2-5,19" << "\n" - << "\n" - << "Tags are arbitrary words, any quantity:" << "\n" - << " +tag The + means add the tag" << "\n" - << " -tag The - means remove the tag" << "\n" - << "\n" - << "Attributes are:" << "\n" - << " project: Project name" << "\n" - << " priority: Priority" << "\n" - << " due: Due date" << "\n" - << " recur: Recurrence frequency" << "\n" - << " until: Recurrence end date" << "\n" - << " fg: Foreground color" << "\n" - << " bg: Background color" << "\n" - << " limit: Desired number of rows in report, or 'page'" << "\n" - << " wait: Date until task becomes pending" << "\n" - << "\n" - << "Attribute modifiers improve filters. Supported modifiers are:" << "\n" - << " before (synonyms under, below)" << "\n" - << " after (synonyms over, above)" << "\n" - << " none" << "\n" - << " any" << "\n" - << " is (synonym equals)" << "\n" - << " isnt (synonym not)" << "\n" - << " has (synonym contains)" << "\n" - << " hasnt" << "\n" - << " startswith (synonym left)" << "\n" - << " endswith (synonym right)" << "\n" - << " word" << "\n" - << " noword" << "\n" - << "\n" - << " For example:" << "\n" - << " task list due.before:eom priority.not:L" << "\n" - << "\n" - << " Modifiers can be inverted with the ~ character:" << "\n" - << " project.~is is equivalent to project.isnt" << "\n" - << "\n" - << "The default .taskrc file can be overridden with:" << "\n" - << " task rc: ..." << "\n" - << "\n" - << "The values in .taskrc (or alternate) can be overridden with:" << "\n" - << " task ... rc.:" << "\n" - << "\n" - << "Any command or attribute name may be abbreviated if still unique:" << "\n" - << " task list project:Home" << "\n" - << " task li pro:Home" << "\n" - << "\n" - << "Some task descriptions need to be escaped because of the shell:" << "\n" - << " task add \"quoted ' quote\"" << "\n" - << " task add escaped \\' quote" << "\n" - << "\n" - << "The argument -- tells taskwarrior to treat all other args as description." << "\n" - << " task add -- project:Home needs scheduling" << "\n" - << "\n" - << "Many characters have special meaning to the shell, including:" << "\n" - << " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~" << "\n" - << "\n"; - - outs = out.str(); - context.hooks.trigger ("post-usage-command"); - } + out << shortUsageString + << "ID is the numeric identifier displayed by the 'task list' command. " + << "You can specify multiple IDs for task commands, and multiple tasks " + << "will be affected. To specify multiple IDs make sure you use one " + << "of these forms:" << "\n" + << " task delete 1,2,3" << "\n" + << " task info 1-3" << "\n" + << " task pri:H 1,2-5,19" << "\n" + << "\n" + << "Tags are arbitrary words, any quantity:" << "\n" + << " +tag The + means add the tag" << "\n" + << " -tag The - means remove the tag" << "\n" + << "\n" + << "Attributes are:" << "\n" + << " project: Project name" << "\n" + << " priority: Priority" << "\n" + << " due: Due date" << "\n" + << " recur: Recurrence frequency" << "\n" + << " until: Recurrence end date" << "\n" + << " fg: Foreground color" << "\n" + << " bg: Background color" << "\n" + << " limit: Desired number of rows in report, or 'page'" << "\n" + << " wait: Date until task becomes pending" << "\n" + << "\n" + << "Attribute modifiers improve filters. Supported modifiers are:" << "\n" + << " before (synonyms under, below)" << "\n" + << " after (synonyms over, above)" << "\n" + << " none" << "\n" + << " any" << "\n" + << " is (synonym equals)" << "\n" + << " isnt (synonym not)" << "\n" + << " has (synonym contains)" << "\n" + << " hasnt" << "\n" + << " startswith (synonym left)" << "\n" + << " endswith (synonym right)" << "\n" + << " word" << "\n" + << " noword" << "\n" + << "\n" + << " For example:" << "\n" + << " task list due.before:eom priority.not:L" << "\n" + << "\n" + << " Modifiers can be inverted with the ~ character:" << "\n" + << " project.~is is equivalent to project.isnt" << "\n" + << "\n" + << "The default .taskrc file can be overridden with:" << "\n" + << " task rc: ..." << "\n" + << "\n" + << "The values in .taskrc (or alternate) can be overridden with:" << "\n" + << " task ... rc.:" << "\n" + << "\n" + << "Any command or attribute name may be abbreviated if still unique:" << "\n" + << " task list project:Home" << "\n" + << " task li pro:Home" << "\n" + << "\n" + << "Some task descriptions need to be escaped because of the shell:" << "\n" + << " task add \"quoted ' quote\"" << "\n" + << " task add escaped \\' quote" << "\n" + << "\n" + << "The argument -- tells taskwarrior to treat all other args as description." << "\n" + << " task add -- project:Home needs scheduling" << "\n" + << "\n" + << "Many characters have special meaning to the shell, including:" << "\n" + << " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~" << "\n" + << "\n"; + outs = out.str(); return rc; } @@ -384,407 +378,383 @@ int handleInfo (std::string& outs) { int rc = 0; - if (context.hooks.trigger ("pre-info-command")) + // Get all the tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.loadPending (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + // Filter sequence. + context.filter.applySequence (tasks, context.sequence); + + // Read the undo file. + std::vector undo; + if (context.config.getBoolean ("journal.info")) { - // Get all the tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.loadPending (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); + Directory location (context.config.get ("data.location")); + std::string undoFile = location.data + "/undo.data"; + File::read (undoFile, undo); + } - // Filter sequence. - context.filter.applySequence (tasks, context.sequence); + // Find the task. + std::stringstream out; + foreach (task, tasks) + { + Table table; + table.setTableWidth (context.getWidth ()); + table.setDateFormat (context.config.get ("dateformat")); - // Read the undo file. - std::vector undo; - if (context.config.getBoolean ("journal.info")) + table.addColumn ("Name"); + table.addColumn ("Value"); + + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) { - Directory location (context.config.get ("data.location")); - std::string undoFile = location.data + "/undo.data"; - File::read (undoFile, undo); + table.setColumnUnderline (0); + table.setColumnUnderline (1); + } + else + table.setTableDashedUnderline (); + + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::flexible); + + table.setColumnJustification (0, Table::left); + table.setColumnJustification (1, Table::left); + Date now; + + // id + int row = table.addRow (); + table.addCell (row, 0, "ID"); + table.addCell (row, 1, format (task->id)); + + std::string status = ucFirst (Task::statusToText (task->getStatus ())); + + // description + row = table.addRow (); + table.addCell (row, 0, "Description"); + table.addCell (row, 1, getFullDescription (*task, "info")); + + Color c; + autoColorize (*task, c); + table.setCellColor (row, 1, c); + + // status + row = table.addRow (); + table.addCell (row, 0, "Status"); + table.addCell (row, 1, status); + + // project + if (task->has ("project")) + { + row = table.addRow (); + table.addCell (row, 0, "Project"); + table.addCell (row, 1, task->get ("project")); } - // Find the task. - std::stringstream out; - foreach (task, tasks) + // priority + if (task->has ("priority")) { - Table table; - table.setTableWidth (context.getWidth ()); - table.setDateFormat (context.config.get ("dateformat")); - - table.addColumn ("Name"); - table.addColumn ("Value"); - - if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && - context.config.getBoolean ("fontunderline")) - { - table.setColumnUnderline (0); - table.setColumnUnderline (1); - } - else - table.setTableDashedUnderline (); - - table.setColumnWidth (0, Table::minimum); - table.setColumnWidth (1, Table::flexible); - - table.setColumnJustification (0, Table::left); - table.setColumnJustification (1, Table::left); - Date now; - - context.hooks.trigger ("pre-display", *task); - - // id - int row = table.addRow (); - table.addCell (row, 0, "ID"); - std::string value = format (task->id); - context.hooks.trigger ("format-id", "id", value); - table.addCell (row, 1, value); - - std::string status = ucFirst (Task::statusToText (task->getStatus ())); - - // description row = table.addRow (); - table.addCell (row, 0, "Description"); - table.addCell (row, 1, getFullDescription (*task, "info")); + table.addCell (row, 0, "Priority"); + table.addCell (row, 1, task->get ("priority")); + } - Color c; - autoColorize (*task, c); - table.setCellColor (row, 1, c); + // dependencies: blocked + { + std::vector blocked; + dependencyGetBlocking (*task, blocked); + if (blocked.size ()) + { + std::stringstream message; + std::vector ::const_iterator it; + for (it = blocked.begin (); it != blocked.end (); ++it) + message << it->id << " " << it->get ("description") << "\n"; - // status + row = table.addRow (); + table.addCell (row, 0, "This task blocked by"); + table.addCell (row, 1, message.str ()); + } + } + + // dependencies: blocking + { + std::vector blocking; + dependencyGetBlocked (*task, blocking); + if (blocking.size ()) + { + std::stringstream message; + std::vector ::const_iterator it; + for (it = blocking.begin (); it != blocking.end (); ++it) + message << it->id << " " << it->get ("description") << "\n"; + + row = table.addRow (); + table.addCell (row, 0, "This task is blocking"); + table.addCell (row, 1, message.str ()); + } + } + + // recur + if (task->has ("recur")) + { row = table.addRow (); - table.addCell (row, 0, "Status"); - table.addCell (row, 1, status); + table.addCell (row, 0, "Recurrence"); + table.addCell (row, 1, task->get ("recur")); + } - // project - if (task->has ("project")) - { - row = table.addRow (); - table.addCell (row, 0, "Project"); - table.addCell (row, 1, task->get ("project")); - } - - // priority - if (task->has ("priority")) - { - row = table.addRow (); - table.addCell (row, 0, "Priority"); - value = task->get ("priority"); - context.hooks.trigger ("format-priority", "priority", value); - table.addCell (row, 1, value); - } - - // dependencies: blocked - { - std::vector blocked; - dependencyGetBlocking (*task, blocked); - if (blocked.size ()) - { - std::stringstream message; - std::vector ::const_iterator it; - for (it = blocked.begin (); it != blocked.end (); ++it) - message << it->id << " " << it->get ("description") << "\n"; - - row = table.addRow (); - table.addCell (row, 0, "This task blocked by"); - table.addCell (row, 1, message.str ()); - } - } - - // dependencies: blocking - { - std::vector blocking; - dependencyGetBlocked (*task, blocking); - if (blocking.size ()) - { - std::stringstream message; - std::vector ::const_iterator it; - for (it = blocking.begin (); it != blocking.end (); ++it) - message << it->id << " " << it->get ("description") << "\n"; - - row = table.addRow (); - table.addCell (row, 0, "This task is blocking"); - table.addCell (row, 1, message.str ()); - } - } - - // recur - if (task->has ("recur")) - { - row = table.addRow (); - table.addCell (row, 0, "Recurrence"); - value = task->get ("recur"); - context.hooks.trigger ("format-recur", "recur", value); - table.addCell (row, 1, value); - } - - // until - if (task->has ("until")) - { - row = table.addRow (); - table.addCell (row, 0, "Recur until"); - - Date dt (atoi (task->get ("until").c_str ())); - std::string format = context.config.get ("reportdateformat"); - if (format == "") - format = context.config.get ("dateformat"); - - std::string until = getDueDate (*task, format); - table.addCell (row, 1, until); - } - - // mask - if (task->getStatus () == Task::recurring) - { - row = table.addRow (); - table.addCell (row, 0, "Mask"); - table.addCell (row, 1, task->get ("mask")); - } - - if (task->has ("parent")) - { - // parent - row = table.addRow (); - table.addCell (row, 0, "Parent task"); - table.addCell (row, 1, task->get ("parent")); - - // imask - row = table.addRow (); - table.addCell (row, 0, "Mask Index"); - table.addCell (row, 1, task->get ("imask")); - } - - // due (colored) - if (task->has ("due")) - { - row = table.addRow (); - table.addCell (row, 0, "Due"); - - std::string format = context.config.get ("reportdateformat"); - if (format == "") - format = context.config.get ("dateformat"); - - value = getDueDate (*task, format); - context.hooks.trigger ("format-due", "due", value); - table.addCell (row, 1, value); - } - - // wait - if (task->has ("wait")) - { - row = table.addRow (); - table.addCell (row, 0, "Waiting until"); - Date dt (atoi (task->get ("wait").c_str ())); - value = dt.toString (context.config.get ("dateformat")); - context.hooks.trigger ("format-wait", "wait", value); - table.addCell (row, 1, value); - } - - // start - if (task->has ("start")) - { - row = table.addRow (); - table.addCell (row, 0, "Start"); - Date dt (atoi (task->get ("start").c_str ())); - - value = dt.toString (context.config.get ("dateformat")); - context.hooks.trigger ("format-due", "due", value); - table.addCell (row, 1, value); - } - - // end - if (task->has ("end")) - { - row = table.addRow (); - table.addCell (row, 0, "End"); - Date dt (atoi (task->get ("end").c_str ())); - value = dt.toString (context.config.get ("dateformat")); - context.hooks.trigger ("format-end", "end", value); - table.addCell (row, 1, value); - } - - // tags ... - std::vector tags; - task->getTags (tags); - if (tags.size ()) - { - std::string allTags; - join (allTags, " ", tags); - - row = table.addRow (); - table.addCell (row, 0, "Tags"); - table.addCell (row, 1, allTags); - } - - // uuid + // until + if (task->has ("until")) + { row = table.addRow (); - table.addCell (row, 0, "UUID"); - std::string uuid = value = task->get ("uuid"); - context.hooks.trigger ("format-uuid", "uuid", value); - table.addCell (row, 1, value); + table.addCell (row, 0, "Recur until"); - // entry + Date dt (atoi (task->get ("until").c_str ())); + std::string format = context.config.get ("reportdateformat"); + if (format == "") + format = context.config.get ("dateformat"); + + std::string until = getDueDate (*task, format); + table.addCell (row, 1, until); + } + + // mask + if (task->getStatus () == Task::recurring) + { row = table.addRow (); - table.addCell (row, 0, "Entered"); - Date dt (atoi (task->get ("entry").c_str ())); - std::string entry = dt.toString (context.config.get ("dateformat")); + table.addCell (row, 0, "Mask"); + table.addCell (row, 1, task->get ("mask")); + } - std::string age; - std::string created = task->get ("entry"); - if (created.length ()) + if (task->has ("parent")) + { + // parent + row = table.addRow (); + table.addCell (row, 0, "Parent task"); + table.addCell (row, 1, task->get ("parent")); + + // imask + row = table.addRow (); + table.addCell (row, 0, "Mask Index"); + table.addCell (row, 1, task->get ("imask")); + } + + // due (colored) + if (task->has ("due")) + { + row = table.addRow (); + table.addCell (row, 0, "Due"); + + std::string format = context.config.get ("reportdateformat"); + if (format == "") + format = context.config.get ("dateformat"); + + table.addCell (row, 1, getDueDate (*task, format)); + } + + // wait + if (task->has ("wait")) + { + row = table.addRow (); + table.addCell (row, 0, "Waiting until"); + Date dt (atoi (task->get ("wait").c_str ())); + table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); + } + + // start + if (task->has ("start")) + { + row = table.addRow (); + table.addCell (row, 0, "Start"); + Date dt (atoi (task->get ("start").c_str ())); + table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); + } + + // end + if (task->has ("end")) + { + row = table.addRow (); + table.addCell (row, 0, "End"); + Date dt (atoi (task->get ("end").c_str ())); + table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); + } + + // tags ... + std::vector tags; + task->getTags (tags); + if (tags.size ()) + { + std::string allTags; + join (allTags, " ", tags); + + row = table.addRow (); + table.addCell (row, 0, "Tags"); + table.addCell (row, 1, allTags); + } + + // uuid + row = table.addRow (); + table.addCell (row, 0, "UUID"); + std::string uuid = task->get ("uuid"); + table.addCell (row, 1, uuid); + + // entry + row = table.addRow (); + table.addCell (row, 0, "Entered"); + Date dt (atoi (task->get ("entry").c_str ())); + std::string entry = dt.toString (context.config.get ("dateformat")); + + std::string age; + std::string created = task->get ("entry"); + if (created.length ()) + { + Date dt (atoi (created.c_str ())); + age = Duration (now - dt).format (); + } + + table.addCell (row, 1, entry + " (" + age + ")"); + + // fg + std::string color = task->get ("fg"); + if (color != "") + { + row = table.addRow (); + table.addCell (row, 0, "Foreground color"); + table.addCell (row, 1, color); + } + + // bg + color = task->get ("bg"); + if (color != "") + { + row = table.addRow (); + table.addCell (row, 0, "Background color"); + table.addCell (row, 1, color); + } + + // Task::urgency + // TODO Enable this later. This was for testing. + //row = table.addRow (); + //table.addCell (row, 0, "Urgency"); + //table.addCell (row, 1, task->urgency ()); + + // Create a second table, containing undo log change details. + Table journal; + + // If an alternating row color is specified, notify the table. + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + { + Color alternate (context.config.get ("color.alternate")); + if (alternate.nontrivial ()) { - Date dt (atoi (created.c_str ())); - age = Duration (now - dt).format (); + table.setTableAlternateColor (alternate); + journal.setTableAlternateColor (alternate); } + } - context.hooks.trigger ("format-entry", "entry", entry); - table.addCell (row, 1, entry + " (" + age + ")"); + journal.setTableWidth (context.getWidth ()); + journal.setDateFormat (context.config.get ("dateformat")); - // fg - std::string color = task->get ("fg"); - if (color != "") + journal.addColumn ("Date"); + journal.addColumn ("Modification"); + + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) + { + journal.setColumnUnderline (0); + journal.setColumnUnderline (1); + } + else + journal.setTableDashedUnderline (); + + journal.setColumnWidth (0, Table::minimum); + journal.setColumnWidth (1, Table::flexible); + + journal.setColumnJustification (0, Table::left); + journal.setColumnJustification (1, Table::left); + + if (context.config.getBoolean ("journal.info") && + undo.size () > 3) + { + // Scan the undo data for entries matching this task. + std::string when; + std::string previous; + std::string current; + unsigned int i = 0; + long total_time = 0; + while (i < undo.size ()) { - row = table.addRow (); - table.addCell (row, 0, "Foreground color"); - table.addCell (row, 1, color); - } + when = undo[i++]; + previous = ""; + if (undo[i].substr (0, 3) == "old") + previous = undo[i++]; - // bg - color = task->get ("bg"); - if (color != "") - { - row = table.addRow (); - table.addCell (row, 0, "Background color"); - table.addCell (row, 1, color); - } + current = undo[i++]; + i++; // Separator - // Task::urgency - // TODO Enable this later. This was for testing. - //row = table.addRow (); - //table.addCell (row, 0, "Urgency"); - //table.addCell (row, 1, task->urgency ()); - - // Create a second table, containing undo log change details. - Table journal; - - // If an alternating row color is specified, notify the table. - if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) - { - Color alternate (context.config.get ("color.alternate")); - if (alternate.nontrivial ()) + if (current.find ("uuid:\"" + uuid) != std::string::npos) { - table.setTableAlternateColor (alternate); - journal.setTableAlternateColor (alternate); - } - } - - journal.setTableWidth (context.getWidth ()); - journal.setDateFormat (context.config.get ("dateformat")); - - journal.addColumn ("Date"); - journal.addColumn ("Modification"); - - if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && - context.config.getBoolean ("fontunderline")) - { - journal.setColumnUnderline (0); - journal.setColumnUnderline (1); - } - else - journal.setTableDashedUnderline (); - - journal.setColumnWidth (0, Table::minimum); - journal.setColumnWidth (1, Table::flexible); - - journal.setColumnJustification (0, Table::left); - journal.setColumnJustification (1, Table::left); - - if (context.config.getBoolean ("journal.info") && - undo.size () > 3) - { - // Scan the undo data for entries matching this task. - std::string when; - std::string previous; - std::string current; - unsigned int i = 0; - long total_time = 0; - while (i < undo.size ()) - { - when = undo[i++]; - previous = ""; - if (undo[i].substr (0, 3) == "old") - previous = undo[i++]; - - current = undo[i++]; - i++; // Separator - - if (current.find ("uuid:\"" + uuid) != std::string::npos) + if (previous != "") { - if (previous != "") + int row = journal.addRow (); + + Date timestamp (atoi (when.substr (5).c_str ())); + journal.addCell (row, 0, timestamp.toString (context.config.get ("dateformat"))); + + Task before (previous.substr (4)); + Task after (current.substr (4)); + journal.addCell (row, 1, taskInfoDifferences (before, after)); + + // calculate the total active time + if (before.get ("start") == "" + && after.get ("start") != "") { - int row = journal.addRow (); - - Date timestamp (atoi (when.substr (5).c_str ())); - journal.addCell (row, 0, timestamp.toString (context.config.get ("dateformat"))); - - Task before (previous.substr (4)); - Task after (current.substr (4)); - journal.addCell (row, 1, taskInfoDifferences (before, after)); - - // calculate the total active time - if (before.get ("start") == "" - && after.get ("start") != "") - { - // task started - total_time -= timestamp.toEpoch (); - } - else if (before.get ("start") != "" - && after.get ("start") == "") - { - // task stopped - total_time += timestamp.toEpoch (); - } + // task started + total_time -= timestamp.toEpoch (); + } + else if (before.get ("start") != "" + && after.get ("start") == "") + { + // task stopped + total_time += timestamp.toEpoch (); } } } - - // add now() if task is still active - if (total_time < 0) - total_time += Date ().toEpoch (); - - // print total active time - if (total_time > 0) - { - row = journal.addRow (); - journal.addCell (row, 0, "Total active time"); - journal.addCell (row, 1, Duration (total_time).format ()); - - if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) - journal.setCellColor (row, 1, Color ("bold")); - } } - out << optionalBlankLine () - << table.render () + // add now() if task is still active + if (total_time < 0) + total_time += Date ().toEpoch (); + + // print total active time + if (total_time > 0) + { + row = journal.addRow (); + journal.addCell (row, 0, "Total active time"); + journal.addCell (row, 1, Duration (total_time).format ()); + + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + journal.setCellColor (row, 1, Color ("bold")); + } + } + + out << optionalBlankLine () + << table.render () + << "\n"; + + if (journal.rowCount () > 0) + out << journal.render () << "\n"; - - if (journal.rowCount () > 0) - out << journal.render () - << "\n"; - } - - if (! tasks.size ()) - { - out << "No matches.\n"; - rc = 1; - } - - outs = out.str (); - context.hooks.trigger ("post-info-command"); } + if (! tasks.size ()) + { + out << "No matches.\n"; + rc = 1; + } + + outs = out.str (); return rc; } @@ -796,146 +766,141 @@ int handleReportSummary (std::string& outs) { int rc = 0; - if (context.hooks.trigger ("pre-summary-command")) + // Scan the pending tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + // Generate unique list of project names from all pending tasks. + std::map allProjects; + foreach (task, tasks) + if (task->getStatus () == Task::pending) + allProjects[task->get ("project")] = false; + + // Initialize counts, sum. + std::map countPending; + std::map countCompleted; + std::map sumEntry; + std::map counter; + time_t now = time (NULL); + + // Initialize counters. + foreach (project, allProjects) { - // Scan the pending tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); - - // Generate unique list of project names from all pending tasks. - std::map allProjects; - foreach (task, tasks) - if (task->getStatus () == Task::pending) - allProjects[task->get ("project")] = false; - - // Initialize counts, sum. - std::map countPending; - std::map countCompleted; - std::map sumEntry; - std::map counter; - time_t now = time (NULL); - - // Initialize counters. - foreach (project, allProjects) - { - countPending [project->first] = 0; - countCompleted [project->first] = 0; - sumEntry [project->first] = 0.0; - counter [project->first] = 0; - } - - // Count the various tasks. - foreach (task, tasks) - { - std::string project = task->get ("project"); - ++counter[project]; - - if (task->getStatus () == Task::pending || - task->getStatus () == Task::waiting) - { - ++countPending[project]; - - time_t entry = atoi (task->get ("entry").c_str ()); - if (entry) - sumEntry[project] = sumEntry[project] + (double) (now - entry); - } - - else if (task->getStatus () == Task::completed) - { - ++countCompleted[project]; - - time_t entry = atoi (task->get ("entry").c_str ()); - time_t end = atoi (task->get ("end").c_str ()); - if (entry && end) - sumEntry[project] = sumEntry[project] + (double) (end - entry); - } - } - - // Create a table for output. - Table table; - table.addColumn ("Project"); - table.addColumn ("Remaining"); - table.addColumn ("Avg age"); - table.addColumn ("Complete"); - table.addColumn ("0% 100%"); - - 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); - } - else - table.setTableDashedUnderline (); - - table.setColumnJustification (1, Table::right); - table.setColumnJustification (2, Table::right); - table.setColumnJustification (3, Table::right); - - table.sortOn (0, Table::ascendingCharacter); - table.setDateFormat (context.config.get ("dateformat")); - - Color bar_color (context.config.get ("color.summary.bar")); - Color bg_color (context.config.get ("color.summary.background")); - - int barWidth = 30; - foreach (i, allProjects) - { - if (countPending[i->first] > 0) - { - int row = table.addRow (); - table.addCell (row, 0, (i->first == "" ? "(none)" : i->first)); - table.addCell (row, 1, countPending[i->first]); - if (counter[i->first]) - table.addCell (row, 2, Duration ((int) (sumEntry[i->first] / (double)counter[i->first])).format ()); - - int c = countCompleted[i->first]; - int p = countPending[i->first]; - int completedBar = (c * barWidth) / (c + p); - - std::string bar; - std::string subbar; - if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) - { - bar += bar_color.colorize (std::string ( completedBar, ' ')); - bar += bg_color.colorize (std::string (barWidth - completedBar, ' ')); - } - else - { - bar += std::string ( completedBar, '=') - + std::string (barWidth - completedBar, ' '); - } - table.addCell (row, 4, bar); - - char percent[12]; - sprintf (percent, "%d%%", 100 * c / (c + p)); - table.addCell (row, 3, percent); - } - } - - std::stringstream out; - if (table.rowCount ()) - out << optionalBlankLine () - << table.render () - << optionalBlankLine () - << table.rowCount () - << (table.rowCount () == 1 ? " project" : " projects") - << "\n"; - else { - out << "No projects.\n"; - rc = 1; - } - - outs = out.str (); - context.hooks.trigger ("post-summary-command"); + countPending [project->first] = 0; + countCompleted [project->first] = 0; + sumEntry [project->first] = 0.0; + counter [project->first] = 0; } + // Count the various tasks. + foreach (task, tasks) + { + std::string project = task->get ("project"); + ++counter[project]; + + if (task->getStatus () == Task::pending || + task->getStatus () == Task::waiting) + { + ++countPending[project]; + + time_t entry = atoi (task->get ("entry").c_str ()); + if (entry) + sumEntry[project] = sumEntry[project] + (double) (now - entry); + } + + else if (task->getStatus () == Task::completed) + { + ++countCompleted[project]; + + time_t entry = atoi (task->get ("entry").c_str ()); + time_t end = atoi (task->get ("end").c_str ()); + if (entry && end) + sumEntry[project] = sumEntry[project] + (double) (end - entry); + } + } + + // Create a table for output. + Table table; + table.addColumn ("Project"); + table.addColumn ("Remaining"); + table.addColumn ("Avg age"); + table.addColumn ("Complete"); + table.addColumn ("0% 100%"); + + 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); + } + else + table.setTableDashedUnderline (); + + table.setColumnJustification (1, Table::right); + table.setColumnJustification (2, Table::right); + table.setColumnJustification (3, Table::right); + + table.sortOn (0, Table::ascendingCharacter); + table.setDateFormat (context.config.get ("dateformat")); + + Color bar_color (context.config.get ("color.summary.bar")); + Color bg_color (context.config.get ("color.summary.background")); + + int barWidth = 30; + foreach (i, allProjects) + { + if (countPending[i->first] > 0) + { + int row = table.addRow (); + table.addCell (row, 0, (i->first == "" ? "(none)" : i->first)); + table.addCell (row, 1, countPending[i->first]); + if (counter[i->first]) + table.addCell (row, 2, Duration ((int) (sumEntry[i->first] / (double)counter[i->first])).format ()); + + int c = countCompleted[i->first]; + int p = countPending[i->first]; + int completedBar = (c * barWidth) / (c + p); + + std::string bar; + std::string subbar; + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + { + bar += bar_color.colorize (std::string ( completedBar, ' ')); + bar += bg_color.colorize (std::string (barWidth - completedBar, ' ')); + } + else + { + bar += std::string ( completedBar, '=') + + std::string (barWidth - completedBar, ' '); + } + table.addCell (row, 4, bar); + + char percent[12]; + sprintf (percent, "%d%%", 100 * c / (c + p)); + table.addCell (row, 3, percent); + } + } + + std::stringstream out; + if (table.rowCount ()) + out << optionalBlankLine () + << table.render () + << optionalBlankLine () + << table.rowCount () + << (table.rowCount () == 1 ? " project" : " projects") + << "\n"; + else { + out << "No projects.\n"; + rc = 1; + } + + outs = out.str (); return rc; } @@ -944,187 +909,178 @@ int handleReportTimesheet (std::string& outs) { int rc = 0; - if (context.hooks.trigger ("pre-timesheet-command")) + // Scan the pending tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + // Just do this once. + int width = context.getWidth (); + + // What day of the week does the user consider the first? + int weekStart = Date::dayOfWeek (context.config.get ("weekstart")); + if (weekStart != 0 && weekStart != 1) + throw std::string ("The 'weekstart' configuration variable may " + "only contain 'Sunday' or 'Monday'."); + + // Determine the date of the first day of the most recent report. + Date today; + Date start; + start -= (((today.dayOfWeek () - weekStart) + 7) % 7) * 86400; + + // Roll back to midnight. + start = Date (start.month (), start.day (), start.year ()); + Date end = start + (7 * 86400); + + // Determine how many reports to run. + int quantity = 1; + if (context.sequence.size () == 1) + quantity = context.sequence[0]; + + bool color = context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor"); + + std::stringstream out; + for (int week = 0; week < quantity; ++week) { - // Scan the pending tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); + Date endString (end); + endString -= 86400; - // Just do this once. - int width = context.getWidth (); + std::string title = start.toString (context.config.get ("dateformat")) + + " - " + + endString.toString (context.config.get ("dateformat")); - // What day of the week does the user consider the first? - int weekStart = Date::dayOfWeek (context.config.get ("weekstart")); - if (weekStart != 0 && weekStart != 1) - throw std::string ("The 'weekstart' configuration variable may " - "only contain 'Sunday' or 'Monday'."); + Color bold (Color::nocolor, Color::nocolor, false, true, false); + out << "\n" + << (color ? bold.colorize (title) : title) + << "\n"; - // Determine the date of the first day of the most recent report. - Date today; - Date start; - start -= (((today.dayOfWeek () - weekStart) + 7) % 7) * 86400; + // Render the completed table. + Table completed; + completed.setTableWidth (width); + completed.addColumn (" "); + completed.addColumn ("Project"); + completed.addColumn ("Due"); + completed.addColumn ("Description"); - // Roll back to midnight. - start = Date (start.month (), start.day (), start.year ()); - Date end = start + (7 * 86400); - - // Determine how many reports to run. - int quantity = 1; - if (context.sequence.size () == 1) - quantity = context.sequence[0]; - - bool color = context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor"); - - std::stringstream out; - for (int week = 0; week < quantity; ++week) + if (color && context.config.getBoolean ("fontunderline")) { - Date endString (end); - endString -= 86400; + completed.setColumnUnderline (1); + completed.setColumnUnderline (2); + completed.setColumnUnderline (3); + } + else + completed.setTableDashedUnderline (); - std::string title = start.toString (context.config.get ("dateformat")) - + " - " - + endString.toString (context.config.get ("dateformat")); + completed.setColumnWidth (0, Table::minimum); + completed.setColumnWidth (1, Table::minimum); + completed.setColumnWidth (2, Table::minimum); + completed.setColumnWidth (3, Table::flexible); - Color bold (Color::nocolor, Color::nocolor, false, true, false); - out << "\n" - << (color ? bold.colorize (title) : title) - << "\n"; + completed.setColumnJustification (0, Table::left); + completed.setColumnJustification (1, Table::left); + completed.setColumnJustification (2, Table::right); + completed.setColumnJustification (3, Table::left); - // Render the completed table. - Table completed; - completed.setTableWidth (width); - completed.addColumn (" "); - completed.addColumn ("Project"); - completed.addColumn ("Due"); - completed.addColumn ("Description"); - - if (color && context.config.getBoolean ("fontunderline")) + foreach (task, tasks) + { + // If task completed within range. + if (task->getStatus () == Task::completed) { - completed.setColumnUnderline (1); - completed.setColumnUnderline (2); - completed.setColumnUnderline (3); - } - else - completed.setTableDashedUnderline (); - - completed.setColumnWidth (0, Table::minimum); - completed.setColumnWidth (1, Table::minimum); - completed.setColumnWidth (2, Table::minimum); - completed.setColumnWidth (3, Table::flexible); - - completed.setColumnJustification (0, Table::left); - completed.setColumnJustification (1, Table::left); - completed.setColumnJustification (2, Table::right); - completed.setColumnJustification (3, Table::left); - - foreach (task, tasks) - { - // If task completed within range. - if (task->getStatus () == Task::completed) + Date compDate (atoi (task->get ("end").c_str ())); + if (compDate >= start && compDate < end) { - context.hooks.trigger ("pre-display", *task); + int row = completed.addRow (); + std::string format = context.config.get ("dateformat.report"); + if (format == "") + format = context.config.get ("dateformat"); + completed.addCell (row, 1, task->get ("project")); + completed.addCell (row, 2, getDueDate (*task, format)); + completed.addCell (row, 3, getFullDescription (*task, "timesheet")); - Date compDate (atoi (task->get ("end").c_str ())); - if (compDate >= start && compDate < end) + if (color) { - int row = completed.addRow (); - std::string format = context.config.get ("dateformat.report"); - if (format == "") - format = context.config.get ("dateformat"); - completed.addCell (row, 1, task->get ("project")); - completed.addCell (row, 2, getDueDate (*task, format)); - completed.addCell (row, 3, getFullDescription (*task, "timesheet")); - - if (color) - { - Color c (task->get ("fg") + " " + task->get ("bg")); - autoColorize (*task, c); - completed.setRowColor (row, c); - } + Color c (task->get ("fg") + " " + task->get ("bg")); + autoColorize (*task, c); + completed.setRowColor (row, c); } } } - - out << " Completed (" << completed.rowCount () << " tasks)\n"; - - if (completed.rowCount ()) - out << completed.render () - << "\n"; - - // Now render the started table. - Table started; - started.setTableWidth (width); - started.addColumn (" "); - started.addColumn ("Project"); - started.addColumn ("Due"); - started.addColumn ("Description"); - - if (color && context.config.getBoolean ("fontunderline")) - { - started.setColumnUnderline (1); - started.setColumnUnderline (2); - started.setColumnUnderline (3); - } - else - started.setTableDashedUnderline (); - - started.setColumnWidth (0, Table::minimum); - started.setColumnWidth (1, Table::minimum); - started.setColumnWidth (2, Table::minimum); - started.setColumnWidth (3, Table::flexible); - - started.setColumnJustification (0, Table::left); - started.setColumnJustification (1, Table::left); - started.setColumnJustification (2, Table::right); - started.setColumnJustification (3, Table::left); - foreach (task, tasks) - { - // If task started within range, but not completed withing range. - if (task->getStatus () == Task::pending && - task->has ("start")) - { - context.hooks.trigger ("pre-display", *task); - - Date startDate (atoi (task->get ("start").c_str ())); - if (startDate >= start && startDate < end) - { - int row = started.addRow (); - std::string format = context.config.get ("dateformat.report"); - if (format == "") - format = context.config.get ("dateformat"); - started.addCell (row, 1, task->get ("project")); - started.addCell (row, 2, getDueDate (*task, format)); - started.addCell (row, 3, getFullDescription (*task, "timesheet")); - - if (color) - { - Color c (task->get ("fg") + " " + task->get ("bg")); - autoColorize (*task, c); - started.setRowColor (row, c); - } - } - } - } - - out << " Started (" << started.rowCount () << " tasks)\n"; - - if (started.rowCount ()) - out << started.render () - << "\n\n"; - - // Prior week. - start -= 7 * 86400; - end -= 7 * 86400; } - outs = out.str (); - context.hooks.trigger ("post-timesheet-command"); + out << " Completed (" << completed.rowCount () << " tasks)\n"; + + if (completed.rowCount ()) + out << completed.render () + << "\n"; + + // Now render the started table. + Table started; + started.setTableWidth (width); + started.addColumn (" "); + started.addColumn ("Project"); + started.addColumn ("Due"); + started.addColumn ("Description"); + + if (color && context.config.getBoolean ("fontunderline")) + { + started.setColumnUnderline (1); + started.setColumnUnderline (2); + started.setColumnUnderline (3); + } + else + started.setTableDashedUnderline (); + + started.setColumnWidth (0, Table::minimum); + started.setColumnWidth (1, Table::minimum); + started.setColumnWidth (2, Table::minimum); + started.setColumnWidth (3, Table::flexible); + + started.setColumnJustification (0, Table::left); + started.setColumnJustification (1, Table::left); + started.setColumnJustification (2, Table::right); + started.setColumnJustification (3, Table::left); + foreach (task, tasks) + { + // If task started within range, but not completed withing range. + if (task->getStatus () == Task::pending && + task->has ("start")) + { + Date startDate (atoi (task->get ("start").c_str ())); + if (startDate >= start && startDate < end) + { + int row = started.addRow (); + std::string format = context.config.get ("dateformat.report"); + if (format == "") + format = context.config.get ("dateformat"); + started.addCell (row, 1, task->get ("project")); + started.addCell (row, 2, getDueDate (*task, format)); + started.addCell (row, 3, getFullDescription (*task, "timesheet")); + + if (color) + { + Color c (task->get ("fg") + " " + task->get ("bg")); + autoColorize (*task, c); + started.setRowColor (row, c); + } + } + } + } + + out << " Started (" << started.rowCount () << " tasks)\n"; + + if (started.rowCount ()) + out << started.render () + << "\n\n"; + + // Prior week. + start -= 7 * 86400; + end -= 7 * 86400; } + outs = out.str (); return rc; } @@ -1359,373 +1315,368 @@ int handleReportCalendar (std::string& outs) { int rc = 0; - if (context.hooks.trigger ("pre-calendar-command")) + // Each month requires 28 text columns width. See how many will actually + // fit. But if a preference is specified, and it fits, use it. + int width = context.getWidth (); + int preferredMonthsPerLine = (context.config.getInteger ("monthsperline")); + int monthsThatFit = width / 26; + + int monthsPerLine = monthsThatFit; + if (preferredMonthsPerLine != 0 && preferredMonthsPerLine < monthsThatFit) + monthsPerLine = preferredMonthsPerLine; + + // Get all the tasks. + std::vector tasks; + Filter filter; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.loadPending (tasks, filter); + context.tdb.commit (); + context.tdb.unlock (); + + Date today; + bool getpendingdate = false; + int monthsToDisplay = 1; + int mFrom = today.month (); + int yFrom = today.year (); + int mTo = mFrom; + int yTo = yFrom; + + // Defaults. + monthsToDisplay = monthsPerLine; + mFrom = today.month (); + yFrom = today.year (); + + // Set up a vector of commands (1), for autoComplete. + std::vector commandNames; + commandNames.push_back ("calendar"); + + // Set up a vector of keywords, for autoComplete. + std::vector keywordNames; + keywordNames.push_back ("due"); + + // Set up a vector of months, for autoComplete. + std::vector monthNames; + monthNames.push_back ("january"); + monthNames.push_back ("february"); + monthNames.push_back ("march"); + monthNames.push_back ("april"); + monthNames.push_back ("may"); + monthNames.push_back ("june"); + monthNames.push_back ("july"); + monthNames.push_back ("august"); + monthNames.push_back ("september"); + monthNames.push_back ("october"); + monthNames.push_back ("november"); + monthNames.push_back ("december"); + + // For autoComplete results. + std::vector matches; + + // Look at all args, regardless of sequence. + int argMonth = 0; + int argYear = 0; + bool argWholeYear = false; + foreach (arg, context.args) { - // Each month requires 28 text columns width. See how many will actually - // fit. But if a preference is specified, and it fits, use it. - int width = context.getWidth (); - int preferredMonthsPerLine = (context.config.getInteger ("monthsperline")); - int monthsThatFit = width / 26; + // Some version of "calendar". + if (autoComplete (lowerCase (*arg), commandNames, matches) == 1) + continue; - int monthsPerLine = monthsThatFit; - if (preferredMonthsPerLine != 0 && preferredMonthsPerLine < monthsThatFit) - monthsPerLine = preferredMonthsPerLine; + // "due". + else if (autoComplete (lowerCase (*arg), keywordNames, matches) == 1) + getpendingdate = true; - // Get all the tasks. - std::vector tasks; - Filter filter; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.loadPending (tasks, filter); - context.tdb.commit (); - context.tdb.unlock (); + // "y". + else if (lowerCase (*arg) == "y") + argWholeYear = true; - Date today; - bool getpendingdate = false; - int monthsToDisplay = 1; - int mFrom = today.month (); - int yFrom = today.year (); - int mTo = mFrom; - int yTo = yFrom; + // YYYY. + else if (digitsOnly (*arg) && arg->length () == 4) + argYear = atoi (arg->c_str ()); - // Defaults. - monthsToDisplay = monthsPerLine; - mFrom = today.month (); - yFrom = today.year (); - - // Set up a vector of commands (1), for autoComplete. - std::vector commandNames; - commandNames.push_back ("calendar"); - - // Set up a vector of keywords, for autoComplete. - std::vector keywordNames; - keywordNames.push_back ("due"); - - // Set up a vector of months, for autoComplete. - std::vector monthNames; - monthNames.push_back ("january"); - monthNames.push_back ("february"); - monthNames.push_back ("march"); - monthNames.push_back ("april"); - monthNames.push_back ("may"); - monthNames.push_back ("june"); - monthNames.push_back ("july"); - monthNames.push_back ("august"); - monthNames.push_back ("september"); - monthNames.push_back ("october"); - monthNames.push_back ("november"); - monthNames.push_back ("december"); - - // For autoComplete results. - std::vector matches; - - // Look at all args, regardless of sequence. - int argMonth = 0; - int argYear = 0; - bool argWholeYear = false; - foreach (arg, context.args) + // MM. + else if (digitsOnly (*arg) && arg->length () <= 2) { - // Some version of "calendar". - if (autoComplete (lowerCase (*arg), commandNames, matches) == 1) - continue; - - // "due". - else if (autoComplete (lowerCase (*arg), keywordNames, matches) == 1) - getpendingdate = true; - - // "y". - else if (lowerCase (*arg) == "y") - argWholeYear = true; - - // YYYY. - else if (digitsOnly (*arg) && arg->length () == 4) - argYear = atoi (arg->c_str ()); - - // MM. - else if (digitsOnly (*arg) && arg->length () <= 2) - { - argMonth = atoi (arg->c_str ()); - if (argMonth < 1 || argMonth > 12) - throw std::string ("Argument '") + *arg + "' is not a valid month."; - } - - // "January" etc. - else if (autoComplete (lowerCase (*arg), monthNames, matches) == 1) - { - argMonth = Date::monthOfYear (matches[0]); - if (argMonth == -1) - throw std::string ("Argument '") + *arg + "' is not a valid month."; - } - - else - throw std::string ("Could not recognize argument '") + *arg + "'."; + argMonth = atoi (arg->c_str ()); + if (argMonth < 1 || argMonth > 12) + throw std::string ("Argument '") + *arg + "' is not a valid month."; } - // Supported combinations: - // - // Command line monthsToDisplay mFrom yFrom getpendingdate - // ------------ --------------- ----- ----- -------------- - // cal monthsPerLine today today false - // cal y 12 today today false - // cal due monthsPerLine today today true - // cal YYYY 12 1 arg false - // cal due y 12 today today true - // cal MM YYYY monthsPerLine arg arg false - // cal MM YYYY y 12 arg arg false - - if (argWholeYear || (argYear && !argMonth && !argWholeYear)) - monthsToDisplay = 12; - - if (!argMonth && argYear) - mFrom = 1; - else if (argMonth && argYear) - mFrom = argMonth; - - if (argYear) - yFrom = argYear; - - // Now begin the data subset and rendering. - int countDueDates = 0; - if (getpendingdate == true) { - // Find the oldest pending due date. - Date oldest (12,31,2037); - foreach (task, tasks) - { - if (task->getStatus () == Task::pending) - { - if (task->has ("due") && - !task->hasTag ("nocal")) - { - ++countDueDates; - Date d (atoi (task->get ("due").c_str ())); - if (d < oldest) oldest = d; - } - } - } - mFrom = oldest.month(); - yFrom = oldest.year(); - } - - if (context.config.getBoolean ("calendar.offset")) + // "January" etc. + else if (autoComplete (lowerCase (*arg), monthNames, matches) == 1) { - int moffset = context.config.getInteger ("calendar.offset.value") % 12; - int yoffset = context.config.getInteger ("calendar.offset.value") / 12; - mFrom += moffset; - yFrom += yoffset; - if (mFrom < 1) - { - mFrom += 12; - yFrom--; - } - else if (mFrom > 12) - { - mFrom -= 12; - yFrom++; - } + argMonth = Date::monthOfYear (matches[0]); + if (argMonth == -1) + throw std::string ("Argument '") + *arg + "' is not a valid month."; } - mTo = mFrom + monthsToDisplay - 1; - yTo = yFrom; - if (mTo > 12) - { - mTo -= 12; - yTo++; - } - - int details_yFrom = yFrom; - int details_mFrom = mFrom; - - std::stringstream out; - out << "\n"; - - while (yFrom < yTo || (yFrom == yTo && mFrom <= mTo)) - { - int nextM = mFrom; - int nextY = yFrom; - - // Print month headers (cheating on the width settings, yes) - for (int i = 0 ; i < monthsPerLine ; i++) - { - std::string month = Date::monthName (nextM); - - // 12345678901234567890123456 = 26 chars wide - // ^^ = center - // <-------> = 13 - (month.length / 2) + 1 - // <------> = 26 - above - // +--------------------------+ - // | July 2009 | - // | Mo Tu We Th Fr Sa Su | - // | 27 1 2 3 4 5 | - // | 28 6 7 8 9 10 11 12 | - // | 29 13 14 15 16 17 18 19 | - // | 30 20 21 22 23 24 25 26 | - // | 31 27 28 29 30 31 | - // +--------------------------+ - - int totalWidth = 26; - int labelWidth = month.length () + 5; // 5 = " 2009" - int leftGap = (totalWidth / 2) - (labelWidth / 2); - int rightGap = totalWidth - leftGap - labelWidth; - - out << std::setw (leftGap) << ' ' - << month - << ' ' - << nextY - << std::setw (rightGap) << ' '; - - if (++nextM > 12) - { - nextM = 1; - nextY++; - } - } - - out << "\n" - << optionalBlankLine () - << renderMonths (mFrom, yFrom, today, tasks, monthsPerLine) - << "\n"; - - mFrom += monthsPerLine; - if (mFrom > 12) - { - mFrom -= 12; - ++yFrom; - } - } - - Color color_today (context.config.get ("color.calendar.today")); - Color color_due (context.config.get ("color.calendar.due")); - Color color_duetoday (context.config.get ("color.calendar.due.today")); - Color color_overdue (context.config.get ("color.calendar.overdue")); - Color color_weekend (context.config.get ("color.calendar.weekend")); - Color color_holiday (context.config.get ("color.calendar.holiday")); - Color color_weeknumber (context.config.get ("color.calendar.weeknumber")); - - if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && - context.config.getBoolean ("calendar.legend")) - out << "Legend: " - << color_today.colorize ("today") - << ", " - << color_due.colorize ("due") - << ", " - << color_duetoday.colorize ("due-today") - << ", " - << color_overdue.colorize ("overdue") - << ", " - << color_weekend.colorize ("weekend") - << ", " - << color_holiday.colorize ("holiday") - << ", " - << color_weeknumber.colorize ("weeknumber") - << "." - << optionalBlankLine () - << "\n"; - - if (context.config.get ("calendar.details") == "full" || context.config.get ("calendar.holidays") == "full") - { - --details_mFrom; - if (details_mFrom == 0) - { - details_mFrom = 12; - --details_yFrom; - } - int details_dFrom = Date::daysInMonth (details_mFrom, details_yFrom); - - ++mTo; - if (mTo == 13) - { - mTo = 1; - ++yTo; - } - - Date date_after (details_mFrom, details_dFrom, details_yFrom); - std::string after = date_after.toString (context.config.get ("dateformat")); - - Date date_before (mTo, 1, yTo); - std::string before = date_before.toString (context.config.get ("dateformat")); - - // Table with due date information - if (context.config.get ("calendar.details") == "full") - { - std::string report = context.config.get ("calendar.details.report"); - std::string report_filter = context.config.get ("report." + report + ".filter"); - - report_filter += " due.after:" + after + " due.before:" + before; - context.config.set ("report." + report + ".filter", report_filter); - - // Display all due task in the report colorized not only the imminet ones - context.config.set ("due", 0); - - context.args.clear (); - context.filter.clear (); - context.sequence.clear (); - - std::string output; - handleCustomReport (report, output); - out << output; - } - - // Table with holiday information - if (context.config.get ("calendar.holidays") == "full") - { - std::vector holidays; - context.config.all (holidays); - - Table holTable; - holTable.setTableWidth (context.getWidth ()); - holTable.addColumn ("Date"); - holTable.addColumn ("Holiday"); - holTable.sortOn (0, Table::ascendingDueDate); - - if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && - context.config.getBoolean ("fontunderline")) - { - holTable.setColumnUnderline (0); - holTable.setColumnUnderline (1); - } - else - holTable.setTableDashedUnderline (); - - holTable.setColumnWidth (0, Table::minimum); - holTable.setColumnWidth (1, Table::flexible); - - holTable.setColumnJustification (0, Table::left); - holTable.setColumnJustification (1, Table::left); - - foreach (hol, holidays) - if (hol->substr (0, 8) == "holiday.") - if (hol->substr (hol->size () - 4) == "name") - { - std::string holName = context.config.get ("holiday." + hol->substr (8, hol->size () - 13) + ".name"); - std::string holDate = context.config.get ("holiday." + hol->substr (8, hol->size () - 13) + ".date"); - Date hDate (holDate.c_str (), context.config.get ("dateformat.holiday")); - - if (date_after < hDate && hDate < date_before) - { - std::string format = context.config.get ("report." + - context.config.get ("calendar.details.report") + - ".dateformat"); - if (format == "") - format = context.config.get ("dateformat.report"); - if (format == "") - format = context.config.get ("dateformat"); - - int row = holTable.addRow (); - holTable.addCell (row, 0, hDate.toString (format)); - holTable.addCell (row, 1, holName); - } - } - - out << optionalBlankLine () - << holTable.render () - << "\n"; - } - } - - outs = out.str (); - context.hooks.trigger ("post-calendar-command"); + else + throw std::string ("Could not recognize argument '") + *arg + "'."; } + // Supported combinations: + // + // Command line monthsToDisplay mFrom yFrom getpendingdate + // ------------ --------------- ----- ----- -------------- + // cal monthsPerLine today today false + // cal y 12 today today false + // cal due monthsPerLine today today true + // cal YYYY 12 1 arg false + // cal due y 12 today today true + // cal MM YYYY monthsPerLine arg arg false + // cal MM YYYY y 12 arg arg false + + if (argWholeYear || (argYear && !argMonth && !argWholeYear)) + monthsToDisplay = 12; + + if (!argMonth && argYear) + mFrom = 1; + else if (argMonth && argYear) + mFrom = argMonth; + + if (argYear) + yFrom = argYear; + + // Now begin the data subset and rendering. + int countDueDates = 0; + if (getpendingdate == true) { + // Find the oldest pending due date. + Date oldest (12,31,2037); + foreach (task, tasks) + { + if (task->getStatus () == Task::pending) + { + if (task->has ("due") && + !task->hasTag ("nocal")) + { + ++countDueDates; + Date d (atoi (task->get ("due").c_str ())); + if (d < oldest) oldest = d; + } + } + } + mFrom = oldest.month(); + yFrom = oldest.year(); + } + + if (context.config.getBoolean ("calendar.offset")) + { + int moffset = context.config.getInteger ("calendar.offset.value") % 12; + int yoffset = context.config.getInteger ("calendar.offset.value") / 12; + mFrom += moffset; + yFrom += yoffset; + if (mFrom < 1) + { + mFrom += 12; + yFrom--; + } + else if (mFrom > 12) + { + mFrom -= 12; + yFrom++; + } + } + + mTo = mFrom + monthsToDisplay - 1; + yTo = yFrom; + if (mTo > 12) + { + mTo -= 12; + yTo++; + } + + int details_yFrom = yFrom; + int details_mFrom = mFrom; + + std::stringstream out; + out << "\n"; + + while (yFrom < yTo || (yFrom == yTo && mFrom <= mTo)) + { + int nextM = mFrom; + int nextY = yFrom; + + // Print month headers (cheating on the width settings, yes) + for (int i = 0 ; i < monthsPerLine ; i++) + { + std::string month = Date::monthName (nextM); + + // 12345678901234567890123456 = 26 chars wide + // ^^ = center + // <-------> = 13 - (month.length / 2) + 1 + // <------> = 26 - above + // +--------------------------+ + // | July 2009 | + // | Mo Tu We Th Fr Sa Su | + // | 27 1 2 3 4 5 | + // | 28 6 7 8 9 10 11 12 | + // | 29 13 14 15 16 17 18 19 | + // | 30 20 21 22 23 24 25 26 | + // | 31 27 28 29 30 31 | + // +--------------------------+ + + int totalWidth = 26; + int labelWidth = month.length () + 5; // 5 = " 2009" + int leftGap = (totalWidth / 2) - (labelWidth / 2); + int rightGap = totalWidth - leftGap - labelWidth; + + out << std::setw (leftGap) << ' ' + << month + << ' ' + << nextY + << std::setw (rightGap) << ' '; + + if (++nextM > 12) + { + nextM = 1; + nextY++; + } + } + + out << "\n" + << optionalBlankLine () + << renderMonths (mFrom, yFrom, today, tasks, monthsPerLine) + << "\n"; + + mFrom += monthsPerLine; + if (mFrom > 12) + { + mFrom -= 12; + ++yFrom; + } + } + + Color color_today (context.config.get ("color.calendar.today")); + Color color_due (context.config.get ("color.calendar.due")); + Color color_duetoday (context.config.get ("color.calendar.due.today")); + Color color_overdue (context.config.get ("color.calendar.overdue")); + Color color_weekend (context.config.get ("color.calendar.weekend")); + Color color_holiday (context.config.get ("color.calendar.holiday")); + Color color_weeknumber (context.config.get ("color.calendar.weeknumber")); + + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("calendar.legend")) + out << "Legend: " + << color_today.colorize ("today") + << ", " + << color_due.colorize ("due") + << ", " + << color_duetoday.colorize ("due-today") + << ", " + << color_overdue.colorize ("overdue") + << ", " + << color_weekend.colorize ("weekend") + << ", " + << color_holiday.colorize ("holiday") + << ", " + << color_weeknumber.colorize ("weeknumber") + << "." + << optionalBlankLine () + << "\n"; + + if (context.config.get ("calendar.details") == "full" || context.config.get ("calendar.holidays") == "full") + { + --details_mFrom; + if (details_mFrom == 0) + { + details_mFrom = 12; + --details_yFrom; + } + int details_dFrom = Date::daysInMonth (details_mFrom, details_yFrom); + + ++mTo; + if (mTo == 13) + { + mTo = 1; + ++yTo; + } + + Date date_after (details_mFrom, details_dFrom, details_yFrom); + std::string after = date_after.toString (context.config.get ("dateformat")); + + Date date_before (mTo, 1, yTo); + std::string before = date_before.toString (context.config.get ("dateformat")); + + // Table with due date information + if (context.config.get ("calendar.details") == "full") + { + std::string report = context.config.get ("calendar.details.report"); + std::string report_filter = context.config.get ("report." + report + ".filter"); + + report_filter += " due.after:" + after + " due.before:" + before; + context.config.set ("report." + report + ".filter", report_filter); + + // Display all due task in the report colorized not only the imminet ones + context.config.set ("due", 0); + + context.args.clear (); + context.filter.clear (); + context.sequence.clear (); + + std::string output; + handleCustomReport (report, output); + out << output; + } + + // Table with holiday information + if (context.config.get ("calendar.holidays") == "full") + { + std::vector holidays; + context.config.all (holidays); + + Table holTable; + holTable.setTableWidth (context.getWidth ()); + holTable.addColumn ("Date"); + holTable.addColumn ("Holiday"); + holTable.sortOn (0, Table::ascendingDueDate); + + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) + { + holTable.setColumnUnderline (0); + holTable.setColumnUnderline (1); + } + else + holTable.setTableDashedUnderline (); + + holTable.setColumnWidth (0, Table::minimum); + holTable.setColumnWidth (1, Table::flexible); + + holTable.setColumnJustification (0, Table::left); + holTable.setColumnJustification (1, Table::left); + + foreach (hol, holidays) + if (hol->substr (0, 8) == "holiday.") + if (hol->substr (hol->size () - 4) == "name") + { + std::string holName = context.config.get ("holiday." + hol->substr (8, hol->size () - 13) + ".name"); + std::string holDate = context.config.get ("holiday." + hol->substr (8, hol->size () - 13) + ".date"); + Date hDate (holDate.c_str (), context.config.get ("dateformat.holiday")); + + if (date_after < hDate && hDate < date_before) + { + std::string format = context.config.get ("report." + + context.config.get ("calendar.details.report") + + ".dateformat"); + if (format == "") + format = context.config.get ("dateformat.report"); + if (format == "") + format = context.config.get ("dateformat"); + + int row = holTable.addRow (); + holTable.addCell (row, 0, hDate.toString (format)); + holTable.addCell (row, 1, holName); + } + } + + out << optionalBlankLine () + << holTable.render () + << "\n"; + } + } + + outs = out.str (); return rc; } @@ -1733,243 +1684,237 @@ int handleReportCalendar (std::string& outs) int handleReportStats (std::string& outs) { int rc = 0; + std::stringstream out; - if (context.hooks.trigger ("pre-stats-command")) + // Go get the file sizes. + size_t dataSize = 0; + + Directory location (context.config.get ("data.location")); + File pending (location.data + "/pending.data"); + dataSize += pending.size (); + + File completed (location.data + "/completed.data"); + dataSize += completed.size (); + + File undo (location.data + "/undo.data"); + dataSize += undo.size (); + + std::vector undoTxns; + File::read (undo, undoTxns); + int undoCount = 0; + foreach (tx, undoTxns) + if (tx->substr (0, 3) == "---") + ++undoCount; + + // Get all the tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.load (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + Date now; + time_t earliest = time (NULL); + time_t latest = 1; + int totalT = 0; + int deletedT = 0; + int pendingT = 0; + int completedT = 0; + int waitingT = 0; + int taggedT = 0; + int annotationsT = 0; + int recurringT = 0; + float daysPending = 0.0; + int descLength = 0; + std::map allTags; + std::map allProjects; + + std::vector ::iterator it; + for (it = tasks.begin (); it != tasks.end (); ++it) { - std::stringstream out; + ++totalT; + if (it->getStatus () == Task::deleted) ++deletedT; + if (it->getStatus () == Task::pending) ++pendingT; + if (it->getStatus () == Task::completed) ++completedT; + if (it->getStatus () == Task::recurring) ++recurringT; + if (it->getStatus () == Task::waiting) ++waitingT; - // Go get the file sizes. - size_t dataSize = 0; + time_t entry = atoi (it->get ("entry").c_str ()); + if (entry < earliest) earliest = entry; + if (entry > latest) latest = entry; - Directory location (context.config.get ("data.location")); - File pending (location.data + "/pending.data"); - dataSize += pending.size (); - - File completed (location.data + "/completed.data"); - dataSize += completed.size (); - - File undo (location.data + "/undo.data"); - dataSize += undo.size (); - - std::vector undoTxns; - File::read (undo, undoTxns); - int undoCount = 0; - foreach (tx, undoTxns) - if (tx->substr (0, 3) == "---") - ++undoCount; - - // Get all the tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.load (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); - - Date now; - time_t earliest = time (NULL); - time_t latest = 1; - int totalT = 0; - int deletedT = 0; - int pendingT = 0; - int completedT = 0; - int waitingT = 0; - int taggedT = 0; - int annotationsT = 0; - int recurringT = 0; - float daysPending = 0.0; - int descLength = 0; - std::map allTags; - std::map allProjects; - - std::vector ::iterator it; - for (it = tasks.begin (); it != tasks.end (); ++it) + if (it->getStatus () == Task::completed) { - ++totalT; - if (it->getStatus () == Task::deleted) ++deletedT; - if (it->getStatus () == Task::pending) ++pendingT; - if (it->getStatus () == Task::completed) ++completedT; - if (it->getStatus () == Task::recurring) ++recurringT; - if (it->getStatus () == Task::waiting) ++waitingT; - - time_t entry = atoi (it->get ("entry").c_str ()); - if (entry < earliest) earliest = entry; - if (entry > latest) latest = entry; - - if (it->getStatus () == Task::completed) - { - time_t end = atoi (it->get ("end").c_str ()); - daysPending += (end - entry) / 86400.0; - } - - if (it->getStatus () == Task::pending) - daysPending += (now.toEpoch () - entry) / 86400.0; - - descLength += it->get ("description").length (); - - std::vector annotations; - it->getAnnotations (annotations); - annotationsT += annotations.size (); - - std::vector tags; - it->getTags (tags); - if (tags.size ()) ++taggedT; - - foreach (t, tags) - allTags[*t] = 0; - - std::string project = it->get ("project"); - if (project != "") - allProjects[project] = 0; + time_t end = atoi (it->get ("end").c_str ()); + daysPending += (end - entry) / 86400.0; } - // Create a table for output. - Table table; - table.setTableWidth (context.getWidth ()); - table.setTableIntraPadding (2); - table.setDateFormat (context.config.get ("dateformat")); - table.addColumn ("Category"); - table.addColumn ("Data"); + if (it->getStatus () == Task::pending) + daysPending += (now.toEpoch () - entry) / 86400.0; - if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && - context.config.getBoolean ("fontunderline")) - { - table.setColumnUnderline (0); - table.setColumnUnderline (1); - } - else - table.setTableDashedUnderline (); + descLength += it->get ("description").length (); - table.setColumnWidth (0, Table::minimum); - table.setColumnWidth (1, Table::flexible); + std::vector annotations; + it->getAnnotations (annotations); + annotationsT += annotations.size (); - table.setColumnJustification (0, Table::left); - table.setColumnJustification (1, Table::left); + std::vector tags; + it->getTags (tags); + if (tags.size ()) ++taggedT; - int row = table.addRow (); - table.addCell (row, 0, "Pending"); - table.addCell (row, 1, pendingT); + foreach (t, tags) + allTags[*t] = 0; - row = table.addRow (); - table.addCell (row, 0, "Waiting"); - table.addCell (row, 1, waitingT); - - row = table.addRow (); - table.addCell (row, 0, "Recurring"); - table.addCell (row, 1, recurringT); - - row = table.addRow (); - table.addCell (row, 0, "Completed"); - table.addCell (row, 1, completedT); - - row = table.addRow (); - table.addCell (row, 0, "Deleted"); - table.addCell (row, 1, deletedT); - - row = table.addRow (); - table.addCell (row, 0, "Total"); - table.addCell (row, 1, totalT); - - row = table.addRow (); - table.addCell (row, 0, "Annotations"); - table.addCell (row, 1, annotationsT); - - row = table.addRow (); - table.addCell (row, 0, "Unique tags"); - table.addCell (row, 1, (int)allTags.size ()); - - row = table.addRow (); - table.addCell (row, 0, "Projects"); - table.addCell (row, 1, (int)allProjects.size ()); - - row = table.addRow (); - table.addCell (row, 0, "Data size"); - table.addCell (row, 1, formatBytes (dataSize)); - - row = table.addRow (); - table.addCell (row, 0, "Undo transactions"); - table.addCell (row, 1, undoCount); - - if (totalT) - { - row = table.addRow (); - table.addCell (row, 0, "Tasks tagged"); - - std::stringstream value; - value << std::setprecision (3) << (100.0 * taggedT / totalT) << "%"; - table.addCell (row, 1, value.str ()); - } - - if (tasks.size ()) - { - Date e (earliest); - row = table.addRow (); - table.addCell (row, 0, "Oldest task"); - table.addCell (row, 1, e.toString (context.config.get ("dateformat"))); - - Date l (latest); - row = table.addRow (); - table.addCell (row, 0, "Newest task"); - table.addCell (row, 1, l.toString (context.config.get ("dateformat"))); - - row = table.addRow (); - table.addCell (row, 0, "Task used for"); - table.addCell (row, 1, Duration (latest - earliest).format ()); - } - - if (totalT) - { - row = table.addRow (); - table.addCell (row, 0, "Task added every"); - table.addCell (row, 1, Duration (((latest - earliest) / totalT)).format ()); - } - - if (completedT) - { - row = table.addRow (); - table.addCell (row, 0, "Task completed every"); - table.addCell (row, 1, Duration ((latest - earliest) / completedT).format ()); - } - - if (deletedT) - { - row = table.addRow (); - table.addCell (row, 0, "Task deleted every"); - table.addCell (row, 1, Duration ((latest - earliest) / deletedT).format ()); - } - - if (pendingT || completedT) - { - row = table.addRow (); - table.addCell (row, 0, "Average time pending"); - table.addCell (row, 1, Duration ((int) ((daysPending / (pendingT + completedT)) * 86400)).format ()); - } - - if (totalT) - { - row = table.addRow (); - table.addCell (row, 0, "Average desc length"); - std::stringstream value; - value << (int) (descLength / totalT) << " characters"; - table.addCell (row, 1, value.str ()); - } - - // If an alternating row color is specified, notify the table. - if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) - { - Color alternate (context.config.get ("color.alternate")); - if (alternate.nontrivial ()) - table.setTableAlternateColor (alternate); - } - - out << optionalBlankLine () - << table.render () - << optionalBlankLine (); - - outs = out.str (); - context.hooks.trigger ("post-stats-command"); + std::string project = it->get ("project"); + if (project != "") + allProjects[project] = 0; } + // Create a table for output. + Table table; + table.setTableWidth (context.getWidth ()); + table.setTableIntraPadding (2); + table.setDateFormat (context.config.get ("dateformat")); + table.addColumn ("Category"); + table.addColumn ("Data"); + + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) + { + table.setColumnUnderline (0); + table.setColumnUnderline (1); + } + else + table.setTableDashedUnderline (); + + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::flexible); + + table.setColumnJustification (0, Table::left); + table.setColumnJustification (1, Table::left); + + int row = table.addRow (); + table.addCell (row, 0, "Pending"); + table.addCell (row, 1, pendingT); + + row = table.addRow (); + table.addCell (row, 0, "Waiting"); + table.addCell (row, 1, waitingT); + + row = table.addRow (); + table.addCell (row, 0, "Recurring"); + table.addCell (row, 1, recurringT); + + row = table.addRow (); + table.addCell (row, 0, "Completed"); + table.addCell (row, 1, completedT); + + row = table.addRow (); + table.addCell (row, 0, "Deleted"); + table.addCell (row, 1, deletedT); + + row = table.addRow (); + table.addCell (row, 0, "Total"); + table.addCell (row, 1, totalT); + + row = table.addRow (); + table.addCell (row, 0, "Annotations"); + table.addCell (row, 1, annotationsT); + + row = table.addRow (); + table.addCell (row, 0, "Unique tags"); + table.addCell (row, 1, (int)allTags.size ()); + + row = table.addRow (); + table.addCell (row, 0, "Projects"); + table.addCell (row, 1, (int)allProjects.size ()); + + row = table.addRow (); + table.addCell (row, 0, "Data size"); + table.addCell (row, 1, formatBytes (dataSize)); + + row = table.addRow (); + table.addCell (row, 0, "Undo transactions"); + table.addCell (row, 1, undoCount); + + if (totalT) + { + row = table.addRow (); + table.addCell (row, 0, "Tasks tagged"); + + std::stringstream value; + value << std::setprecision (3) << (100.0 * taggedT / totalT) << "%"; + table.addCell (row, 1, value.str ()); + } + + if (tasks.size ()) + { + Date e (earliest); + row = table.addRow (); + table.addCell (row, 0, "Oldest task"); + table.addCell (row, 1, e.toString (context.config.get ("dateformat"))); + + Date l (latest); + row = table.addRow (); + table.addCell (row, 0, "Newest task"); + table.addCell (row, 1, l.toString (context.config.get ("dateformat"))); + + row = table.addRow (); + table.addCell (row, 0, "Task used for"); + table.addCell (row, 1, Duration (latest - earliest).format ()); + } + + if (totalT) + { + row = table.addRow (); + table.addCell (row, 0, "Task added every"); + table.addCell (row, 1, Duration (((latest - earliest) / totalT)).format ()); + } + + if (completedT) + { + row = table.addRow (); + table.addCell (row, 0, "Task completed every"); + table.addCell (row, 1, Duration ((latest - earliest) / completedT).format ()); + } + + if (deletedT) + { + row = table.addRow (); + table.addCell (row, 0, "Task deleted every"); + table.addCell (row, 1, Duration ((latest - earliest) / deletedT).format ()); + } + + if (pendingT || completedT) + { + row = table.addRow (); + table.addCell (row, 0, "Average time pending"); + table.addCell (row, 1, Duration ((int) ((daysPending / (pendingT + completedT)) * 86400)).format ()); + } + + if (totalT) + { + row = table.addRow (); + table.addCell (row, 0, "Average desc length"); + std::stringstream value; + value << (int) (descLength / totalT) << " characters"; + table.addCell (row, 1, value.str ()); + } + + // If an alternating row color is specified, notify the table. + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + { + Color alternate (context.config.get ("color.alternate")); + if (alternate.nontrivial ()) + table.setTableAlternateColor (alternate); + } + + out << optionalBlankLine () + << table.render () + << optionalBlankLine (); + + outs = out.str (); return rc; } diff --git a/test/hook.pre-exit.t b/test/hook.on-exit.t similarity index 97% rename from test/hook.pre-exit.t rename to test/hook.on-exit.t index 67796e89b..0fd82326a 100755 --- a/test/hook.pre-exit.t +++ b/test/hook.on-exit.t @@ -35,7 +35,7 @@ if (open my $fh, '>', 'hook.rc') { print $fh "data.location=.\n", "hooks=on\n", - "hook.pre-exit=" . $ENV{'PWD'} . "/hook:test\n"; + "hook.on-exit=" . $ENV{'PWD'} . "/hook:test\n"; close $fh; ok (-r 'hook.rc', 'Created hook.rc'); } diff --git a/test/hook.post-start.t b/test/hook.on-launch.t similarity index 97% rename from test/hook.post-start.t rename to test/hook.on-launch.t index d425e3158..08f81b067 100755 --- a/test/hook.post-start.t +++ b/test/hook.on-launch.t @@ -35,7 +35,7 @@ if (open my $fh, '>', 'hook.rc') { print $fh "data.location=.\n", "hooks=on\n", - "hook.post-start=" . $ENV{'PWD'} . "/hook:test\n"; + "hook.on-launch=" . $ENV{'PWD'} . "/hook:test\n"; close $fh; ok (-r 'hook.rc', 'Created hook.rc'); }