//////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // // Copyright 2006 - 2008, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software // Foundation; either version 2 of the License, or (at your option) any later // version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more // details. // // You should have received a copy of the GNU General Public License along with // this program; if not, write to the // // Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, // Boston, MA // 02110-1301 // USA // //////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include "Config.h" #include "Date.h" #include "Table.h" #include "TDB.h" #include "T.h" #include "task.h" #ifdef HAVE_LIBNCURSES #include #endif //////////////////////////////////////////////////////////////////////////////// void filter (std::vector& all, T& task) { std::vector filtered; // Split any description specified into words. std::vector descWords; split (descWords, lowerCase (task.getDescription ()), ' '); // Get all the tags to match against. std::vector tagList; task.getTags (tagList); // Get all the attributes to match against. std::map attrList; task.getAttributes (attrList); // Iterate over each task, and apply selection criteria. for (unsigned int i = 0; i < all.size (); ++i) { T refTask (all[i]); // Apply description filter. std::string desc = lowerCase (refTask.getDescription ()); unsigned int matches = 0; for (unsigned int w = 0; w < descWords.size (); ++w) if (desc.find (descWords[w]) != std::string::npos) ++matches; if (matches == descWords.size ()) { // Apply attribute filter. matches = 0; foreach (a, attrList) if (a->first == "project") { if (a->second.length () <= refTask.getAttribute (a->first).length ()) if (a->second == refTask.getAttribute (a->first).substr (0, a->second.length ())) ++matches; } else if (a->second == refTask.getAttribute (a->first)) ++matches; if (matches == attrList.size ()) { // Apply tag filter. matches = 0; for (unsigned int t = 0; t < tagList.size (); ++t) if (refTask.hasTag (tagList[t])) ++matches; if (matches == tagList.size ()) filtered.push_back (refTask); } } } all = filtered; } //////////////////////////////////////////////////////////////////////////////// // Successively apply filters based on the task object built from the command // line. Tasks that match all the specified criteria are listed. void handleList (TDB& tdb, T& task, Config& conf) { // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif // Get the pending tasks. tdb.gc (); std::vector tasks; tdb.allPendingT (tasks); handleRecurrence (tdb, tasks); filter (tasks, task); initializeColorRules (conf); bool showAge = conf.get ("showage", true); // Create a table for output. Table table; table.setTableWidth (width); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); table.addColumn ("Due"); table.addColumn ("Active"); if (showAge) table.addColumn ("Age"); table.addColumn ("Description"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); table.setColumnUnderline (3); table.setColumnUnderline (4); table.setColumnUnderline (5); if (showAge) table.setColumnUnderline (6); } table.setColumnWidth (0, Table::minimum); table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::minimum); table.setColumnWidth (3, Table::minimum); table.setColumnWidth (4, Table::minimum); if (showAge) table.setColumnWidth (5, Table::minimum); table.setColumnWidth ((showAge ? 6 : 5), Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); if (showAge) table.setColumnJustification (5, Table::right); table.sortOn (3, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); table.sortOn (1, Table::ascendingCharacter); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); for (unsigned int i = 0; i < tasks.size (); ++i) { T refTask (tasks[i]); if (refTask.getStatus () != T::pending) continue; // Now format the matching task. bool imminent = false; bool overdue = false; std::string due = refTask.getAttribute ("due"); if (due.length ()) { switch (getDueState (due)) { case 2: overdue = true; break; case 1: imminent = true; break; case 0: default: break; } Date dt (::atoi (due.c_str ())); due = dt.toString (conf.get ("dateformat", "m/d/Y")); } std::string active; if (refTask.getAttribute ("start") != "") active = "*"; std::string age; std::string created = refTask.getAttribute ("entry"); if (created.length ()) { Date now; Date dt (::atoi (created.c_str ())); formatTimeDeltaDays (age, (time_t) (now - dt)); } // All criteria match, so add refTask to the output table. int row = table.addRow (); table.addCell (row, 0, refTask.getId ()); table.addCell (row, 1, refTask.getAttribute ("project")); table.addCell (row, 2, refTask.getAttribute ("priority")); table.addCell (row, 3, due); table.addCell (row, 4, active); if (showAge) table.addCell (row, 5, age); table.addCell (row, (showAge ? 6 : 5), refTask.getDescription ()); if (conf.get ("color", true)) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); autoColorize (refTask, fg, bg); table.setRowFg (row, fg); table.setRowBg (row, bg); if (fg == Text::nocolor) { if (overdue) table.setCellFg (row, 3, Text::red); else if (imminent) table.setCellFg (row, 3, Text::yellow); } } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") << std::endl; else std::cout << "No matches." << std::endl; } //////////////////////////////////////////////////////////////////////////////// // Successively apply filters based on the task object built from the command // line. Tasks that match all the specified criteria are listed. Show a narrow // list that works better on mobile devices. void handleSmallList (TDB& tdb, T& task, Config& conf) { // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif // Get the pending tasks. tdb.gc (); std::vector tasks; tdb.allPendingT (tasks); handleRecurrence (tdb, tasks); filter (tasks, task); initializeColorRules (conf); // Create a table for output. Table table; table.setTableWidth (width); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); table.addColumn ("Description"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); table.setColumnUnderline (3); } table.setColumnWidth (0, Table::minimum); table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::minimum); table.setColumnWidth (3, Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::left); table.sortOn (2, Table::descendingPriority); table.sortOn (1, Table::ascendingCharacter); // Iterate over each task, and apply selection criteria. for (unsigned int i = 0; i < tasks.size (); ++i) { T refTask (tasks[i]); // Now format the matching task. bool imminent = false; bool overdue = false; std::string due = refTask.getAttribute ("due"); if (due.length ()) { switch (getDueState (due)) { case 2: overdue = true; break; case 1: imminent = true; break; case 0: default: break; } Date dt (::atoi (due.c_str ())); due = dt.toString (conf.get ("dateformat", "m/d/Y")); } std::string active; if (refTask.getAttribute ("start") != "") active = "*"; std::string age; std::string created = refTask.getAttribute ("entry"); if (created.length ()) { Date now; Date dt (::atoi (created.c_str ())); formatTimeDeltaDays (age, (time_t) (now - dt)); } // All criteria match, so add refTask to the output table. int row = table.addRow (); table.addCell (row, 0, refTask.getId ()); table.addCell (row, 1, refTask.getAttribute ("project")); table.addCell (row, 2, refTask.getAttribute ("priority")); table.addCell (row, 3, refTask.getDescription ()); if (conf.get ("color", true)) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); autoColorize (refTask, fg, bg); table.setRowFg (row, fg); table.setRowBg (row, bg); if (fg == Text::nocolor) { if (overdue) table.setCellFg (row, 3, Text::red); else if (imminent) table.setCellFg (row, 3, Text::yellow); } } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") << std::endl; else std::cout << "No matches." << std::endl; } //////////////////////////////////////////////////////////////////////////////// // Successively apply filters based on the task object built from the command // line. Tasks that match all the specified criteria are listed. void handleCompleted (TDB& tdb, T& task, Config& conf) { // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif // Get the pending tasks. tdb.gc (); std::vector tasks; tdb.completedT (tasks); filter (tasks, task); initializeColorRules (conf); // Create a table for output. Table table; table.setTableWidth (width); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("Done"); table.addColumn ("Project"); table.addColumn ("Description"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); } table.setColumnWidth (0, Table::minimum); table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (1, Table::left); table.setColumnJustification (2, Table::left); table.sortOn (0, Table::ascendingDate); // Iterate over each task, and apply selection criteria. for (unsigned int i = 0; i < tasks.size (); ++i) { T refTask (tasks[i]); // Now format the matching task. Date end (::atoi (refTask.getAttribute ("end").c_str ())); // All criteria match, so add refTask to the output table. int row = table.addRow (); table.addCell (row, 0, end.toString (conf.get ("dateformat", "m/d/Y"))); table.addCell (row, 1, refTask.getAttribute ("project")); table.addCell (row, 2, refTask.getDescription ()); if (conf.get ("color", true)) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); autoColorize (refTask, fg, bg); table.setRowFg (row, fg); table.setRowBg (row, bg); } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") << std::endl; else std::cout << "No matches." << std::endl; } //////////////////////////////////////////////////////////////////////////////// // Display all information for the given task. void handleInfo (TDB& tdb, T& task, Config& conf) { // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif // Get all the tasks. std::vector tasks; tdb.allPendingT (tasks); Table table; table.setTableWidth (width); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("Name"); table.addColumn ("Value"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); } table.setColumnWidth (0, Table::minimum); table.setColumnWidth (1, Table::minimum); table.setColumnJustification (0, Table::left); table.setColumnJustification (1, Table::left); // Find the task. for (unsigned int i = 0; i < tasks.size (); ++i) { T refTask (tasks[i]); if (refTask.getId () == task.getId ()) { Date now; int row = table.addRow (); table.addCell (row, 0, "ID"); table.addCell (row, 1, refTask.getId ()); row = table.addRow (); table.addCell (row, 0, "Status"); table.addCell (row, 1, ( refTask.getStatus () == T::pending ? "Pending" : refTask.getStatus () == T::completed ? "Completed" : refTask.getStatus () == T::deleted ? "Deleted" : refTask.getStatus () == T::recurring ? "Recurring" : "")); row = table.addRow (); table.addCell (row, 0, "Description"); table.addCell (row, 1, refTask.getDescription ()); if (refTask.getAttribute ("project") != "") { row = table.addRow (); table.addCell (row, 0, "Project"); table.addCell (row, 1, refTask.getAttribute ("project")); } if (refTask.getAttribute ("priority") != "") { row = table.addRow (); table.addCell (row, 0, "Priority"); table.addCell (row, 1, refTask.getAttribute ("priority")); } if (refTask.getStatus () == T::recurring) { row = table.addRow (); table.addCell (row, 0, "Recurrence"); table.addCell (row, 1, refTask.getAttribute ("recur")); row = table.addRow (); table.addCell (row, 0, "Recur until"); table.addCell (row, 1, refTask.getAttribute ("until")); row = table.addRow (); table.addCell (row, 0, "Mask"); table.addCell (row, 1, refTask.getAttribute ("mask")); } if (refTask.getAttribute ("parent") != "") { row = table.addRow (); table.addCell (row, 0, "Parent task"); table.addCell (row, 1, refTask.getAttribute ("parent")); row = table.addRow (); table.addCell (row, 0, "Mask Index"); table.addCell (row, 1, refTask.getAttribute ("imask")); } // due (colored) bool imminent = false; bool overdue = false; std::string due = refTask.getAttribute ("due"); if (due != "") { row = table.addRow (); table.addCell (row, 0, "Due"); Date dt (::atoi (due.c_str ())); due = dt.toString (conf.get ("dateformat", "m/d/Y")); table.addCell (row, 1, due); if (due.length ()) { overdue = (dt < now) ? true : false; Date nextweek = now + 7 * 86400; imminent = dt < nextweek ? true : false; if (conf.get ("color", true)) { if (overdue) table.setCellFg (row, 1, Text::red); else if (imminent) table.setCellFg (row, 1, Text::yellow); } } } // start if (refTask.getAttribute ("start") != "") { row = table.addRow (); table.addCell (row, 0, "Start"); Date dt (::atoi (refTask.getAttribute ("start").c_str ())); table.addCell (row, 1, dt.toString (conf.get ("dateformat", "m/d/Y"))); } // end if (refTask.getAttribute ("end") != "") { row = table.addRow (); table.addCell (row, 0, "End"); Date dt (::atoi (refTask.getAttribute ("end").c_str ())); table.addCell (row, 1, dt.toString (conf.get ("dateformat", "m/d/Y"))); } // tags ... std::vector tags; refTask.getTags (tags); if (tags.size ()) { std::string allTags; join (allTags, " ", tags); row = table.addRow (); table.addCell (row, 0, "Tags"); table.addCell (row, 1, allTags); } row = table.addRow (); table.addCell (row, 0, "UUID"); table.addCell (row, 1, refTask.getUUID ()); row = table.addRow (); table.addCell (row, 0, "Entered"); Date dt (::atoi (refTask.getAttribute ("entry").c_str ())); std::string entry = dt.toString (conf.get ("dateformat", "m/d/Y")); std::string age; std::string created = refTask.getAttribute ("entry"); if (created.length ()) { Date dt (::atoi (created.c_str ())); formatTimeDeltaDays (age, (time_t) (now - dt)); } table.addCell (row, 1, entry + " (" + age + ")"); } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") << std::endl; else std::cout << "No matches." << std::endl; } //////////////////////////////////////////////////////////////////////////////// // Successively apply filters based on the task object built from the command // line. Tasks that match all the specified criteria are listed. void handleLongList (TDB& tdb, T& task, Config& conf) { // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif // Get all the tasks. tdb.gc (); std::vector tasks; tdb.allPendingT (tasks); handleRecurrence (tdb, tasks); filter (tasks, task); initializeColorRules (conf); bool showAge = conf.get ("showage", true); // Create a table for output. Table table; table.setTableWidth (width); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); table.addColumn ("Entry"); table.addColumn ("Start"); table.addColumn ("Due"); if (showAge) table.addColumn ("Age"); table.addColumn ("Tags"); table.addColumn ("Description"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); table.setColumnUnderline (3); table.setColumnUnderline (4); table.setColumnUnderline (5); table.setColumnUnderline (6); table.setColumnUnderline (7); if (showAge) table.setColumnUnderline (8); } table.setColumnWidth (0, Table::minimum); table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::minimum); table.setColumnWidth (3, Table::minimum); table.setColumnWidth (4, Table::minimum); table.setColumnWidth (5, Table::minimum); if (showAge) table.setColumnWidth (6, Table::minimum); table.setColumnWidth ((showAge ? 7 : 6), Table::minimum); table.setColumnWidth ((showAge ? 8 : 7), Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); table.setColumnJustification (4, Table::right); table.setColumnJustification (5, Table::right); if (showAge) table.setColumnJustification (6, Table::right); table.sortOn (5, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); table.sortOn (1, Table::ascendingCharacter); // Iterate over each task, and apply selection criteria. for (unsigned int i = 0; i < tasks.size (); ++i) { T refTask (tasks[i]); Date now; std::string started = refTask.getAttribute ("start"); if (started.length ()) { Date dt (::atoi (started.c_str ())); started = dt.toString (conf.get ("dateformat", "m/d/Y")); } std::string entered = refTask.getAttribute ("entry"); if (entered.length ()) { Date dt (::atoi (entered.c_str ())); entered = dt.toString (conf.get ("dateformat", "m/d/Y")); } // Now format the matching task. bool imminent = false; bool overdue = false; std::string due = refTask.getAttribute ("due"); if (due.length ()) { switch (getDueState (due)) { case 2: overdue = true; break; case 1: imminent = true; break; case 0: default: break; } Date dt (::atoi (due.c_str ())); due = dt.toString (conf.get ("dateformat", "m/d/Y")); } std::string age; std::string created = refTask.getAttribute ("entry"); if (created.length ()) { Date dt (::atoi (created.c_str ())); formatTimeDeltaDays (age, (time_t) (now - dt)); } // Make a list of tags. std::string tags; std::vector all; refTask.getTags (all); join (tags, " ", all); // All criteria match, so add refTask to the output table. int row = table.addRow (); table.addCell (row, 0, refTask.getId ()); table.addCell (row, 1, refTask.getAttribute ("project")); table.addCell (row, 2, refTask.getAttribute ("priority")); table.addCell (row, 3, entered); table.addCell (row, 4, started); table.addCell (row, 5, due); if (showAge) table.addCell (row, 6, age); table.addCell (row, (showAge ? 7 : 6), tags); table.addCell (row, (showAge ? 8 : 7), refTask.getDescription ()); if (conf.get ("color", true)) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); autoColorize (refTask, fg, bg); table.setRowFg (row, fg); table.setRowBg (row, bg); if (fg == Text::nocolor) { if (overdue) table.setCellFg (row, 3, Text::red); else if (imminent) table.setCellFg (row, 3, Text::yellow); } } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") << std::endl; else std::cout << "No matches." << std::endl; } //////////////////////////////////////////////////////////////////////////////// // Project Tasks Avg Age Status // A 12 13d XXXXXXXX------ // B 109 3d 12h XX------------ void handleReportSummary (TDB& tdb, T& task, Config& conf) { // Generate unique list of project names. tdb.gc (); std::map allProjects; std::vector pending; tdb.allPendingT (pending); handleRecurrence (tdb, pending); filter (pending, task); for (unsigned int i = 0; i < pending.size (); ++i) { T task (pending[i]); allProjects[task.getAttribute ("project")] = false; } std::vector completed; tdb.allCompletedT (completed); filter (completed, task); for (unsigned int i = 0; i < completed.size (); ++i) { T task (completed[i]); allProjects[task.getAttribute ("project")] = false; } // Initialize counts, sum. std::map countPending; std::map countCompleted; std::map sumEntry; std::map counter; time_t now = time (NULL); foreach (i, allProjects) { countPending [i->first] = 0; countCompleted [i->first] = 0; sumEntry [i->first] = 0.0; counter [i->first] = 0; } // Count the pending tasks. for (unsigned int i = 0; i < pending.size (); ++i) { T task (pending[i]); std::string project = task.getAttribute ("project"); if (task.getStatus () == T::pending) ++countPending[project]; time_t entry = ::atoi (task.getAttribute ("entry").c_str ()); if (entry) { sumEntry[project] = sumEntry[project] + (double) (now - entry); ++counter[project]; } } // Count the completed tasks. for (unsigned int i = 0; i < completed.size (); ++i) { T task (completed[i]); std::string project = task.getAttribute ("project"); countCompleted[project] = countCompleted[project] + 1; ++counter[project]; time_t entry = ::atoi (task.getAttribute ("entry").c_str ()); time_t end = ::atoi (task.getAttribute ("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 (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); table.setColumnUnderline (3); } table.setColumnJustification (1, Table::right); table.setColumnJustification (2, Table::right); table.setColumnJustification (3, Table::right); table.sortOn (0, Table::ascendingCharacter); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); 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]) { std::string age; formatTimeDeltaDays (age, (time_t) (sumEntry[i->first] / counter[i->first])); table.addCell (row, 2, age); } int c = countCompleted[i->first]; int p = countPending[i->first]; int completedBar = (c * barWidth) / (c + p); std::string bar; if (conf.get ("color", true)) { bar = "\033[42m"; for (int b = 0; b < completedBar; ++b) bar += " "; bar += "\033[40m"; for (int b = 0; b < barWidth - completedBar; ++b) bar += " "; bar += "\033[0m"; } else { for (int b = 0; b < completedBar; ++b) bar += "="; for (int b = 0; b < barWidth - completedBar; ++b) bar += " "; } table.addCell (row, 4, bar); char percent[12]; sprintf (percent, "%d%%", 100 * c / (c + p)); table.addCell (row, 3, percent); } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " project" : " projects") << std::endl; else std::cout << "No projects." << std::endl; } //////////////////////////////////////////////////////////////////////////////// // A summary of the most important pending tasks. // // For every project, pull important tasks to present as an 'immediate' task // list. This hides the overwhelming quantity of other tasks. // // Present at most three tasks for every project. Select the tasks using // these criteria: // - due:< 1wk, pri:* // - due:*, pri:H // - pri:H // - due:*, pri:M // - pri:M // - due:*, pri:L // - pri:L // - due:*, pri:* <-- everything else // // Make the "three" tasks a configurable number // void handleReportNext (TDB& tdb, T& task, Config& conf) { // Load all pending. tdb.gc (); std::vector pending; tdb.allPendingT (pending); handleRecurrence (tdb, pending); filter (pending, task); // Restrict to matching subset. std::vector matching; gatherNextTasks (tdb, task, conf, pending, matching); // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif tdb.gc (); // Get the pending tasks. std::vector tasks; tdb.pendingT (tasks); filter (tasks, task); initializeColorRules (conf); bool showAge = conf.get ("showage", true); // Create a table for output. Table table; table.setTableWidth (width); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); table.addColumn ("Due"); table.addColumn ("Active"); if (showAge) table.addColumn ("Age"); table.addColumn ("Description"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); table.setColumnUnderline (3); table.setColumnUnderline (4); table.setColumnUnderline (5); if (showAge) table.setColumnUnderline (6); } table.setColumnWidth (0, Table::minimum); table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::minimum); table.setColumnWidth (3, Table::minimum); table.setColumnWidth (4, Table::minimum); if (showAge) table.setColumnWidth (5, Table::minimum); table.setColumnWidth ((showAge ? 6 : 5), Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); if (showAge) table.setColumnJustification (5, Table::right); table.sortOn (3, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); table.sortOn (1, Table::ascendingCharacter); // Iterate over each task, and apply selection criteria. foreach (i, matching) { T refTask (pending[*i]); Date now; // Now format the matching task. bool imminent = false; bool overdue = false; std::string due = refTask.getAttribute ("due"); if (due.length ()) { switch (getDueState (due)) { case 2: overdue = true; break; case 1: imminent = true; break; case 0: default: break; } Date dt (::atoi (due.c_str ())); due = dt.toString (conf.get ("dateformat", "m/d/Y")); } std::string active; if (refTask.getAttribute ("start") != "") active = "*"; std::string age; std::string created = refTask.getAttribute ("entry"); if (created.length ()) { Date dt (::atoi (created.c_str ())); formatTimeDeltaDays (age, (time_t) (now - dt)); } // All criteria match, so add refTask to the output table. int row = table.addRow (); table.addCell (row, 0, refTask.getId ()); table.addCell (row, 1, refTask.getAttribute ("project")); table.addCell (row, 2, refTask.getAttribute ("priority")); table.addCell (row, 3, due); table.addCell (row, 4, active); if (showAge) table.addCell (row, 5, age); table.addCell (row, (showAge ? 6 : 5), refTask.getDescription ()); if (conf.get ("color", true)) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); autoColorize (refTask, fg, bg); table.setRowFg (row, fg); table.setRowBg (row, bg); if (fg == Text::nocolor) { if (overdue) table.setCellFg (row, 3, Text::red); else if (imminent) table.setCellFg (row, 3, Text::yellow); } } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") << std::endl; else std::cout << "No matches." << std::endl; } //////////////////////////////////////////////////////////////////////////////// // Year Month Added Completed Deleted // 2006 November 87 63 14 // 2006 December 21 6 1 time_t monthlyEpoch (const std::string& date) { // Convert any date in epoch form to m/d/y, then convert back // to epoch form for the date m/1/y. if (date.length ()) { Date d1 (::atoi (date.c_str ())); int m, d, y; d1.toMDY (m, d, y); Date d2 (m, 1, y); time_t epoch; d2.toEpoch (epoch); return epoch; } return 0; } void handleReportHistory (TDB& tdb, T& task, Config& conf) { std::map groups; std::map addedGroup; std::map completedGroup; std::map deletedGroup; // Scan the pending tasks. tdb.gc (); std::vector pending; tdb.allPendingT (pending); handleRecurrence (tdb, pending); filter (pending, task); for (unsigned int i = 0; i < pending.size (); ++i) { T task (pending[i]); time_t epoch = monthlyEpoch (task.getAttribute ("entry")); if (epoch) { groups[epoch] = 0; if (addedGroup.find (epoch) != addedGroup.end ()) addedGroup[epoch] = addedGroup[epoch] + 1; else addedGroup[epoch] = 1; if (task.getStatus () == T::deleted) { epoch = monthlyEpoch (task.getAttribute ("end")); if (deletedGroup.find (epoch) != deletedGroup.end ()) deletedGroup[epoch] = deletedGroup[epoch] + 1; else deletedGroup[epoch] = 1; } else if (task.getStatus () == T::completed) { epoch = monthlyEpoch (task.getAttribute ("end")); if (completedGroup.find (epoch) != completedGroup.end ()) completedGroup[epoch] = completedGroup[epoch] + 1; else completedGroup[epoch] = 1; } } } // Scan the completed tasks. std::vector completed; tdb.allCompletedT (completed); filter (completed, task); for (unsigned int i = 0; i < completed.size (); ++i) { T task (completed[i]); time_t epoch = monthlyEpoch (task.getAttribute ("entry")); if (epoch) { groups[epoch] = 0; if (addedGroup.find (epoch) != addedGroup.end ()) addedGroup[epoch] = addedGroup[epoch] + 1; else addedGroup[epoch] = 1; epoch = monthlyEpoch (task.getAttribute ("end")); if (task.getStatus () == T::deleted) { epoch = monthlyEpoch (task.getAttribute ("end")); if (deletedGroup.find (epoch) != deletedGroup.end ()) deletedGroup[epoch] = deletedGroup[epoch] + 1; else deletedGroup[epoch] = 1; } else if (task.getStatus () == T::completed) { epoch = monthlyEpoch (task.getAttribute ("end")); if (completedGroup.find (epoch) != completedGroup.end ()) completedGroup[epoch] = completedGroup[epoch] + 1; else completedGroup[epoch] = 1; } } } // Now build the table. Table table; table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("Year"); table.addColumn ("Month"); table.addColumn ("Added"); table.addColumn ("Completed"); table.addColumn ("Deleted"); table.addColumn ("Net"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); table.setColumnUnderline (3); table.setColumnUnderline (4); table.setColumnUnderline (5); } 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 (conf.get ("color", true) && net) table.setCellFg (row, 5, net > 0 ? Text::red: Text::green); } if (table.rowCount ()) { table.addRow (); row = table.addRow (); table.addCell (row, 1, "Average"); if (conf.get ("color", true)) table.setRowFg (row, Text::bold); 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)); } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << std::endl; else std::cout << "No tasks." << std::endl; } //////////////////////////////////////////////////////////////////////////////// void handleReportGHistory (TDB& tdb, T& task, Config& conf) { // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif int widthOfBar = width - 15; // strlen ("2008 September ") std::map groups; std::map addedGroup; std::map completedGroup; std::map deletedGroup; // Scan the pending tasks. tdb.gc (); std::vector pending; tdb.allPendingT (pending); handleRecurrence (tdb, pending); filter (pending, task); for (unsigned int i = 0; i < pending.size (); ++i) { T task (pending[i]); time_t epoch = monthlyEpoch (task.getAttribute ("entry")); if (epoch) { groups[epoch] = 0; if (addedGroup.find (epoch) != addedGroup.end ()) addedGroup[epoch] = addedGroup[epoch] + 1; else addedGroup[epoch] = 1; if (task.getStatus () == T::deleted) { epoch = monthlyEpoch (task.getAttribute ("end")); if (deletedGroup.find (epoch) != deletedGroup.end ()) deletedGroup[epoch] = deletedGroup[epoch] + 1; else deletedGroup[epoch] = 1; } else if (task.getStatus () == T::completed) { epoch = monthlyEpoch (task.getAttribute ("end")); if (completedGroup.find (epoch) != completedGroup.end ()) completedGroup[epoch] = completedGroup[epoch] + 1; else completedGroup[epoch] = 1; } } } // Scan the completed tasks. std::vector completed; tdb.allCompletedT (completed); filter (completed, task); for (unsigned int i = 0; i < completed.size (); ++i) { T task (completed[i]); time_t epoch = monthlyEpoch (task.getAttribute ("entry")); if (epoch) { groups[epoch] = 0; if (addedGroup.find (epoch) != addedGroup.end ()) addedGroup[epoch] = addedGroup[epoch] + 1; else addedGroup[epoch] = 1; epoch = monthlyEpoch (task.getAttribute ("end")); if (task.getStatus () == T::deleted) { epoch = monthlyEpoch (task.getAttribute ("end")); if (deletedGroup.find (epoch) != deletedGroup.end ()) deletedGroup[epoch] = deletedGroup[epoch] + 1; else deletedGroup[epoch] = 1; } else if (task.getStatus () == T::completed) { epoch = monthlyEpoch (task.getAttribute ("end")); if (completedGroup.find (epoch) != completedGroup.end ()) completedGroup[epoch] = completedGroup[epoch] + 1; else completedGroup[epoch] = 1; } } } // Now build the table. Table table; table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("Year"); table.addColumn ("Month"); table.addColumn ("Added/Completed/Deleted"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); } // Determine the longest line. int maxLine = 0; foreach (i, groups) { int line = addedGroup[i->first] + completedGroup[i->first] + deletedGroup[i->first]; if (line > maxLine) maxLine = line; } if (maxLine > 0) { int totalAdded = 0; int totalCompleted = 0; int totalDeleted = 0; int priorYear = 0; int row = 0; foreach (i, groups) { row = table.addRow (); totalAdded += addedGroup[i->first]; totalCompleted += completedGroup[i->first]; totalDeleted += deletedGroup[i->first]; Date dt (i->first); int m, d, y; dt.toMDY (m, d, y); if (y != priorYear) { table.addCell (row, 0, y); priorYear = y; } table.addCell (row, 1, Date::monthName(m)); unsigned int addedBar = (widthOfBar * addedGroup[i->first]) / maxLine; unsigned int completedBar = (widthOfBar * completedGroup[i->first]) / maxLine; unsigned int deletedBar = (widthOfBar * deletedGroup[i->first]) / maxLine; std::string bar; if (conf.get ("color", true)) { char number[24]; std::string aBar = ""; if (addedGroup[i->first]) { sprintf (number, "%d", addedGroup[i->first]); aBar = number; while (aBar.length () < addedBar) aBar = " " + aBar; } std::string cBar = ""; if (completedGroup[i->first]) { sprintf (number, "%d", completedGroup[i->first]); cBar = number; while (cBar.length () < completedBar) cBar = " " + cBar; } std::string dBar = ""; if (deletedGroup[i->first]) { sprintf (number, "%d", deletedGroup[i->first]); dBar = number; while (dBar.length () < deletedBar) dBar = " " + dBar; } bar = Text::colorize (Text::black, Text::on_red, aBar); bar += Text::colorize (Text::black, Text::on_green, cBar); bar += Text::colorize (Text::black, Text::on_yellow, 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 = aBar + cBar + dBar; } table.addCell (row, 2, bar); } } if (table.rowCount ()) { std::cout << optionalBlankLine (conf) << table.render () << std::endl; if (conf.get ("color", true)) std::cout << "Legend: " << Text::colorize (Text::black, Text::on_red, "added") << ", " << Text::colorize (Text::black, Text::on_green, "completed") << ", " << Text::colorize (Text::black, Text::on_yellow, "deleted") << optionalBlankLine (conf) << std::endl; else std::cout << "Legend: + added, X completed, - deleted" << std::endl; } else std::cout << "No tasks." << std::endl; } //////////////////////////////////////////////////////////////////////////////// // A summary of the command usage. Not useful to users, but used to display // usage statistics for feedback. // // 2006-12-04 19:59:43 "task list" // void handleReportUsage (const TDB& tdb, T& task, Config& conf) { if (conf.get ("command.logging") == "on") { std::map usage; std::vector all; tdb.logRead (all); for (unsigned int i = 0; i < all.size (); ++i) { // 0123456789012345678901 // v 21 // 2006-12-04 19:59:43 "task list" std::string command = all[i].substr (21, all[i].length () - 22); // Parse as a command line. std::vector args; split (args, command, " "); try { T task; std::string commandName; parse (args, commandName, task, conf); usage[commandName]++; } // Deliberately ignore errors from parsing the command log, as there may // be commands from a prior version of task in there, which were // abbreviated, and are now ambiguous. catch (...) {} } // Now render the table. Table table; table.addColumn ("Command"); table.addColumn ("Frequency"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); } table.setColumnJustification (1, Table::right); table.sortOn (1, Table::descendingNumeric); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); foreach (i, usage) { int row = table.addRow (); table.addCell (row, 0, (i->first == "" ? "(modify)" : i->first)); table.addCell (row, 1, i->second); } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << std::endl; else std::cout << "No usage." << std::endl; } else std::cout << "Command logging is not enabled, so no history has been kept." << std::endl; } //////////////////////////////////////////////////////////////////////////////// std::string renderMonths ( int firstMonth, int firstYear, const Date& today, std::vector & all, Config& conf) { Table table; table.setDateFormat (conf.get ("dateformat", "m/d/Y")); int monthsPerLine = (conf.get ("monthsperline", 1)); // Build table for the number of months to be displayed. for (int i = 0 ; i < (monthsPerLine * 8); i += 8) { table.addColumn (" "); table.addColumn ("Su"); table.addColumn ("Mo"); table.addColumn ("Tu"); table.addColumn ("We"); table.addColumn ("Th"); table.addColumn ("Fr"); table.addColumn ("Sa"); if (conf.get ("color", true)) { table.setColumnUnderline (i + 1); table.setColumnUnderline (i + 2); table.setColumnUnderline (i + 3); table.setColumnUnderline (i + 4); table.setColumnUnderline (i + 5); table.setColumnUnderline (i + 6); table.setColumnUnderline (i + 7); } table.setColumnJustification (i + 0, Table::right); table.setColumnJustification (i + 1, Table::right); table.setColumnJustification (i + 2, Table::right); table.setColumnJustification (i + 3, Table::right); table.setColumnJustification (i + 4, Table::right); table.setColumnJustification (i + 5, Table::right); table.setColumnJustification (i + 6, Table::right); table.setColumnJustification (i + 7, Table::right); } // At most, we need 6 rows. table.addRow (); table.addRow (); table.addRow (); table.addRow (); table.addRow (); table.addRow (); // Set number of days per month, months to render, and years to render. std::vector years; std::vector months; std::vector daysInMonth; int thisYear = firstYear; int thisMonth = firstMonth; for (int i = 0 ; i < monthsPerLine ; i++) { if (thisMonth < 13) { years.push_back (thisYear); } else { thisMonth -= 12; years.push_back (++thisYear); } months.push_back (thisMonth); daysInMonth.push_back (Date::daysInMonth (thisMonth++, thisYear)); } int row = 0; // Loop through months to be added on this line. for (int c = 0; c < monthsPerLine ; c++) { // Reset row counter for subsequent months if (c != 0) row = 0; // Loop through days in month and add to table. for (int d = 1; d <= daysInMonth.at (c); ++d) { Date temp (months.at (c), d, years.at (c)); int dow = temp.dayOfWeek (); int thisCol = dow + 1 + (8 * c); table.addCell (row, thisCol, d); if (conf.get ("color", true) && today.day () == d && today.month () == months.at (c) && today.year () == years.at (c)) table.setCellFg (row, thisCol, Text::cyan); std::vector ::iterator it; for (it = all.begin (); it != all.end (); ++it) { Date due (::atoi (it->getAttribute ("due").c_str ())); if (conf.get ("color", true) && due.day () == d && due.month () == months.at (c) && due.year () == years.at (c)) { table.setCellFg (row, thisCol, Text::black); table.setCellBg (row, thisCol, due < today ? Text::on_red : Text::on_yellow); } } // Check for end of week, and... if (dow == 6 && d < daysInMonth.at (c)) row++; } } return table.render (); } //////////////////////////////////////////////////////////////////////////////// void handleReportCalendar (TDB& tdb, T& task, Config& conf) { // Load all the pending tasks. tdb.gc (); std::vector pending; tdb.allPendingT (pending); handleRecurrence (tdb, pending); filter (pending, task); // Find the oldest pending due date. Date oldest; Date newest; std::vector ::iterator it; for (it = pending.begin (); it != pending.end (); ++it) { if (it->getAttribute ("due") != "") { Date d (::atoi (it->getAttribute ("due").c_str ())); if (d < oldest) oldest = d; if (d > newest) newest = d; } } // Iterate from oldest due month, year to newest month, year. Date today; int mFrom = oldest.month (); int yFrom = oldest.year (); int mTo = newest.month (); int yTo = newest.year (); std::cout << std::endl; std::string output; int monthsPerLine = (conf.get ("monthsperline", 1)); 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); int left = (18 - month.length ()) / 2 + 1; int right = 18 - left - month.length (); std::cout << std::setw (left) << ' ' << month << ' ' << nextY << std::setw (right) << ' '; if (++nextM > 12) { nextM = 1; nextY++; } } std::cout << std::endl << optionalBlankLine (conf) << renderMonths (mFrom, yFrom, today, pending, conf) << std::endl; mFrom += monthsPerLine; if (mFrom > 12) { mFrom -= 12; ++yFrom; } } std::cout << "Legend: " << Text::colorize (Text::cyan, Text::nocolor, "today") << ", " << Text::colorize (Text::black, Text::on_yellow, "due") << ", " << Text::colorize (Text::black, Text::on_red, "overdue") << "." << optionalBlankLine (conf) << std::endl; } //////////////////////////////////////////////////////////////////////////////// void handleReportActive (TDB& tdb, T& task, Config& conf) { // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif // Get all the tasks. tdb.gc (); std::vector tasks; tdb.pendingT (tasks); filter (tasks, task); initializeColorRules (conf); // Create a table for output. Table table; table.setTableWidth (width); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); table.addColumn ("Due"); table.addColumn ("Description"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); table.setColumnUnderline (3); table.setColumnUnderline (4); } table.setColumnWidth (0, Table::minimum); table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::minimum); table.setColumnWidth (3, Table::minimum); table.setColumnWidth (4, Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); table.sortOn (3, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); table.sortOn (1, Table::ascendingCharacter); // Iterate over each task, and apply selection criteria. for (unsigned int i = 0; i < tasks.size (); ++i) { T refTask (tasks[i]); if (refTask.getAttribute ("start") != "") { Date now; bool imminent = false; bool overdue = false; std::string due = refTask.getAttribute ("due"); if (due.length ()) { switch (getDueState (due)) { case 2: overdue = true; break; case 1: imminent = true; break; case 0: default: break; } Date dt (::atoi (due.c_str ())); due = dt.toString (conf.get ("dateformat", "m/d/Y")); } // All criteria match, so add refTask to the output table. int row = table.addRow (); table.addCell (row, 0, refTask.getId ()); table.addCell (row, 1, refTask.getAttribute ("project")); table.addCell (row, 2, refTask.getAttribute ("priority")); table.addCell (row, 3, due); table.addCell (row, 4, refTask.getDescription ()); if (conf.get ("color", true)) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); autoColorize (refTask, fg, bg); table.setRowFg (row, fg); table.setRowBg (row, bg); if (fg == Text::nocolor) { if (overdue) table.setCellFg (row, 3, Text::red); else if (imminent) table.setCellFg (row, 3, Text::yellow); } } } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") << std::endl; else std::cout << "No active tasks." << std::endl; } //////////////////////////////////////////////////////////////////////////////// void handleReportOverdue (TDB& tdb, T& task, Config& conf) { // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif // Get all the tasks. std::vector tasks; tdb.pendingT (tasks); filter (tasks, task); initializeColorRules (conf); // Create a table for output. Table table; table.setTableWidth (width); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); table.addColumn ("Due"); table.addColumn ("Description"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); table.setColumnUnderline (3); table.setColumnUnderline (4); } table.setColumnWidth (0, Table::minimum); table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::minimum); table.setColumnWidth (3, Table::minimum); table.setColumnWidth (4, Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); table.sortOn (3, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); table.sortOn (1, Table::ascendingCharacter); Date now; // Iterate over each task, and apply selection criteria. for (unsigned int i = 0; i < tasks.size (); ++i) { T refTask (tasks[i]); std::string due; if ((due = refTask.getAttribute ("due")) != "") { if (due.length ()) { Date dt (::atoi (due.c_str ())); due = dt.toString (conf.get ("dateformat", "m/d/Y")); // If overdue. if (dt < now) { // All criteria match, so add refTask to the output table. int row = table.addRow (); table.addCell (row, 0, refTask.getId ()); table.addCell (row, 1, refTask.getAttribute ("project")); table.addCell (row, 2, refTask.getAttribute ("priority")); table.addCell (row, 3, due); table.addCell (row, 4, refTask.getDescription ()); if (conf.get ("color", true)) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); autoColorize (refTask, fg, bg); table.setRowFg (row, fg); table.setRowBg (row, bg); if (fg == Text::nocolor) table.setCellFg (row, 3, Text::red); } } } } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") << std::endl; else std::cout << "No overdue tasks." << std::endl; } //////////////////////////////////////////////////////////////////////////////// // Successively apply filters based on the task object built from the command // line. Tasks that match all the specified criteria are listed. void handleReportOldest (TDB& tdb, T& task, Config& conf) { // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif // Get the pending tasks. tdb.gc (); std::vector tasks; tdb.allPendingT (tasks); handleRecurrence (tdb, tasks); filter (tasks, task); initializeColorRules (conf); bool showAge = conf.get ("showage", true); unsigned int quantity = conf.get ("oldest", 10); // Create a table for output. Table table; table.setTableWidth (width); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); table.addColumn ("Due"); table.addColumn ("Active"); if (showAge) table.addColumn ("Age"); table.addColumn ("Description"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); table.setColumnUnderline (3); table.setColumnUnderline (4); table.setColumnUnderline (5); if (showAge) table.setColumnUnderline (6); } table.setColumnWidth (0, Table::minimum); table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::minimum); table.setColumnWidth (3, Table::minimum); table.setColumnWidth (4, Table::minimum); if (showAge) table.setColumnWidth (5, Table::minimum); table.setColumnWidth ((showAge ? 6 : 5), Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); if (showAge) table.setColumnJustification (5, Table::right); table.sortOn (3, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); table.sortOn (1, Table::ascendingCharacter); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); for (unsigned int i = 0; i < min (quantity, tasks.size ()); ++i) { T refTask (tasks[i]); Date now; // Now format the matching task. bool imminent = false; bool overdue = false; std::string due = refTask.getAttribute ("due"); if (due.length ()) { switch (getDueState (due)) { case 2: overdue = true; break; case 1: imminent = true; break; case 0: default: break; } Date dt (::atoi (due.c_str ())); due = dt.toString (conf.get ("dateformat", "m/d/Y")); } std::string active; if (refTask.getAttribute ("start") != "") active = "*"; std::string age; std::string created = refTask.getAttribute ("entry"); if (created.length ()) { Date dt (::atoi (created.c_str ())); formatTimeDeltaDays (age, (time_t) (now - dt)); } // All criteria match, so add refTask to the output table. int row = table.addRow (); table.addCell (row, 0, refTask.getId ()); table.addCell (row, 1, refTask.getAttribute ("project")); table.addCell (row, 2, refTask.getAttribute ("priority")); table.addCell (row, 3, due); table.addCell (row, 4, active); if (showAge) table.addCell (row, 5, age); table.addCell (row, (showAge ? 6 : 5), refTask.getDescription ()); if (conf.get ("color", true)) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); autoColorize (refTask, fg, bg); table.setRowFg (row, fg); table.setRowBg (row, bg); if (fg == Text::nocolor) { if (overdue) table.setCellFg (row, 3, Text::red); else if (imminent) table.setCellFg (row, 3, Text::yellow); } } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") << std::endl; else std::cout << "No matches." << std::endl; } //////////////////////////////////////////////////////////////////////////////// // Successively apply filters based on the task object built from the command // line. Tasks that match all the specified criteria are listed. void handleReportNewest (TDB& tdb, T& task, Config& conf) { // Determine window size, and set table accordingly. int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES if (conf.get ("curses", true)) { WINDOW* w = initscr (); width = w->_maxx + 1; endwin (); } #endif // Get the pending tasks. tdb.gc (); std::vector tasks; tdb.allPendingT (tasks); handleRecurrence (tdb, tasks); filter (tasks, task); initializeColorRules (conf); bool showAge = conf.get ("showage", true); int quantity = conf.get ("newest", 10); // Create a table for output. Table table; table.setTableWidth (width); table.addColumn ("ID"); table.addColumn ("Project"); table.addColumn ("Pri"); table.addColumn ("Due"); table.addColumn ("Active"); if (showAge) table.addColumn ("Age"); table.addColumn ("Description"); if (conf.get ("color", true)) { table.setColumnUnderline (0); table.setColumnUnderline (1); table.setColumnUnderline (2); table.setColumnUnderline (3); table.setColumnUnderline (4); table.setColumnUnderline (5); if (showAge) table.setColumnUnderline (6); } table.setColumnWidth (0, Table::minimum); table.setColumnWidth (1, Table::minimum); table.setColumnWidth (2, Table::minimum); table.setColumnWidth (3, Table::minimum); table.setColumnWidth (4, Table::minimum); if (showAge) table.setColumnWidth (5, Table::minimum); table.setColumnWidth ((showAge ? 6 : 5), Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); if (showAge) table.setColumnJustification (5, Table::right); table.sortOn (3, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); table.sortOn (1, Table::ascendingCharacter); table.setDateFormat (conf.get ("dateformat", "m/d/Y")); int total = tasks.size (); for (int i = total - 1; i >= max (0, total - quantity); --i) { T refTask (tasks[i]); Date now; // Now format the matching task. bool imminent = false; bool overdue = false; std::string due = refTask.getAttribute ("due"); if (due.length ()) { switch (getDueState (due)) { case 2: overdue = true; break; case 1: imminent = true; break; case 0: default: break; } Date dt (::atoi (due.c_str ())); due = dt.toString (conf.get ("dateformat", "m/d/Y")); } std::string active; if (refTask.getAttribute ("start") != "") active = "*"; std::string age; std::string created = refTask.getAttribute ("entry"); if (created.length ()) { Date dt (::atoi (created.c_str ())); formatTimeDeltaDays (age, (time_t) (now - dt)); } // All criteria match, so add refTask to the output table. int row = table.addRow (); table.addCell (row, 0, refTask.getId ()); table.addCell (row, 1, refTask.getAttribute ("project")); table.addCell (row, 2, refTask.getAttribute ("priority")); table.addCell (row, 3, due); table.addCell (row, 4, active); if (showAge) table.addCell (row, 5, age); table.addCell (row, (showAge ? 6 : 5), refTask.getDescription ()); if (conf.get ("color", true)) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); autoColorize (refTask, fg, bg); table.setRowFg (row, fg); table.setRowBg (row, bg); if (fg == Text::nocolor) { if (overdue) table.setCellFg (row, 3, Text::red); else if (imminent) table.setCellFg (row, 3, Text::yellow); } } } if (table.rowCount ()) std::cout << optionalBlankLine (conf) << table.render () << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") << std::endl; else std::cout << "No matches." << std::endl; } //////////////////////////////////////////////////////////////////////////////// void handleReportStats (TDB& tdb, T& task, Config& conf) { // Get all the tasks. std::vector tasks; tdb.allT (tasks); filter (tasks, task); Date now; time_t earliest = time (NULL); time_t latest = 1; int totalT = 0; int deletedT = 0; int pendingT = 0; int completedT = 0; int taggedT = 0; int recurringT = 0; float daysPending = 0.0; int descLength = 0; std::vector ::iterator it; for (it = tasks.begin (); it != tasks.end (); ++it) { ++totalT; if (it->getStatus () == T::deleted) ++deletedT; if (it->getStatus () == T::pending) ++pendingT; if (it->getStatus () == T::completed) ++completedT; if (it->getStatus () == T::recurring) ++recurringT; time_t entry = ::atoi (it->getAttribute ("entry").c_str ()); if (entry < earliest) earliest = entry; if (entry > latest) latest = entry; if (it->getStatus () == T::completed) { time_t end = ::atoi (it->getAttribute ("end").c_str ()); daysPending += (end - entry) / 86400.0; } if (it->getStatus () == T::pending) daysPending += (now - entry) / 86400.0; descLength += it->getDescription ().length (); std::vector tags; it->getTags (tags); if (tags.size ()) ++taggedT; } std::cout << "Pending " << pendingT << std::endl << "Recurring " << recurringT << std::endl << "Completed " << completedT << std::endl << "Deleted " << deletedT << std::endl << "Total " << totalT << std::endl; if (tasks.size ()) { Date e (earliest); std::cout << "Oldest task " << e.toString (conf.get ("dateformat", "m/d/Y")) << std::endl; Date l (latest); std::cout << "Newest task " << l.toString (conf.get ("dateformat", "m/d/Y")) << std::endl; std::cout << "Task used for " << formatSeconds (latest - earliest) << std::endl; } if (totalT) std::cout << "Task added every " << formatSeconds ((latest - earliest) / totalT) << std::endl; if (completedT) std::cout << "Task completed every " << formatSeconds ((latest - earliest) / completedT) << std::endl; if (deletedT) std::cout << "Task deleted every " << formatSeconds ((latest - earliest) / deletedT) << std::endl; if (pendingT || completedT) std::cout << "Average time pending " << formatSeconds ((int) ((daysPending / (pendingT + completedT)) * 86400)) << std::endl; if (totalT) { std::cout << "Average desc length " << (int) (descLength / totalT) << " characters" << std::endl; std::cout << "Tasks tagged " << std::setprecision (3) << (100.0 * taggedT / totalT) << "%" << std::endl; } } //////////////////////////////////////////////////////////////////////////////// void gatherNextTasks ( const TDB& tdb, T& task, Config& conf, std::vector & pending, std::vector & all) { // For counting tasks by project. std::map countByProject; std::map matching; Date now; // How many items per project? Default 3. int limit = conf.get ("next", 3); // due:< 1wk, pri:* for (unsigned int i = 0; i < pending.size (); ++i) { if (pending[i].getStatus () == T::pending) { std::string due = pending[i].getAttribute ("due"); if (due != "") { Date d (::atoi (due.c_str ())); if (d < now + (7 * 24 * 60 * 60)) // if due:< 1wk { std::string project = pending[i].getAttribute ("project"); if (countByProject[project] < limit && matching.find (i) == matching.end ()) { ++countByProject[project]; matching[i] = true; } } } } } // due:*, pri:H for (unsigned int i = 0; i < pending.size (); ++i) { if (pending[i].getStatus () == T::pending) { std::string due = pending[i].getAttribute ("due"); if (due != "") { std::string priority = pending[i].getAttribute ("priority"); if (priority == "H") { std::string project = pending[i].getAttribute ("project"); if (countByProject[project] < limit && matching.find (i) == matching.end ()) { ++countByProject[project]; matching[i] = true; } } } } } // pri:H for (unsigned int i = 0; i < pending.size (); ++i) { if (pending[i].getStatus () == T::pending) { std::string priority = pending[i].getAttribute ("priority"); if (priority == "H") { std::string project = pending[i].getAttribute ("project"); if (countByProject[project] < limit && matching.find (i) == matching.end ()) { ++countByProject[project]; matching[i] = true; } } } } // due:*, pri:M for (unsigned int i = 0; i < pending.size (); ++i) { if (pending[i].getStatus () == T::pending) { std::string due = pending[i].getAttribute ("due"); if (due != "") { std::string priority = pending[i].getAttribute ("priority"); if (priority == "M") { std::string project = pending[i].getAttribute ("project"); if (countByProject[project] < limit && matching.find (i) == matching.end ()) { ++countByProject[project]; matching[i] = true; } } } } } // pri:M for (unsigned int i = 0; i < pending.size (); ++i) { if (pending[i].getStatus () == T::pending) { std::string priority = pending[i].getAttribute ("priority"); if (priority == "M") { std::string project = pending[i].getAttribute ("project"); if (countByProject[project] < limit && matching.find (i) == matching.end ()) { ++countByProject[project]; matching[i] = true; } } } } // due:*, pri:L for (unsigned int i = 0; i < pending.size (); ++i) { if (pending[i].getStatus () == T::pending) { std::string due = pending[i].getAttribute ("due"); if (due != "") { std::string priority = pending[i].getAttribute ("priority"); if (priority == "L") { std::string project = pending[i].getAttribute ("project"); if (countByProject[project] < limit && matching.find (i) == matching.end ()) { ++countByProject[project]; matching[i] = true; } } } } } // pri:L for (unsigned int i = 0; i < pending.size (); ++i) { if (pending[i].getStatus () == T::pending) { std::string priority = pending[i].getAttribute ("priority"); if (priority == "L") { std::string project = pending[i].getAttribute ("project"); if (countByProject[project] < limit && matching.find (i) == matching.end ()) { ++countByProject[project]; matching[i] = true; } } } } // due:, pri: for (unsigned int i = 0; i < pending.size (); ++i) { if (pending[i].getStatus () == T::pending) { std::string due = pending[i].getAttribute ("due"); if (due == "") { std::string priority = pending[i].getAttribute ("priority"); if (priority == "") { std::string project = pending[i].getAttribute ("project"); if (countByProject[project] < limit && matching.find (i) == matching.end ()) { ++countByProject[project]; matching[i] = true; } } } } } // Convert map to vector. foreach (i, matching) all.push_back (i->first); } ////////////////////////////////////////////////////////////////////////////////