diff --git a/ChangeLog b/ChangeLog index de3524351..7619471c7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,12 +17,13 @@ + Added support for custom reports, comprised of a set of column names and sort order, with optional filtering in the configuration file. This means user-defined reports can be written, and the reports currently - in the configuration file can be renamed. + in the configuration file can be renamed. Several of task's built in + reports have been converted to user-defined reports. + New online documentation for custom reports. + New algorithm for determining when the "nag" message is displayed. + Fixed bug where task hangs with a certain combination of recurring tasks and shadow files. - + Fixed bug with the task sort alogrithm, which led to an unstable sequence + + Fixed bug with the task sort algorithm, which led to an unstable sequence when there were only a handful of tasks. + Performance enhanced by eliminating unnecessary sorting. + Task now has a large (and growing) test suite and bug regression tests diff --git a/html/config.html b/html/config.html index 6fb09b51f..bfe97b9ac 100644 --- a/html/config.html +++ b/html/config.html @@ -179,18 +179,6 @@ only show as many that will fit. -
oldest
-
- Determines how many tasks the "task oldest" command displays. - Defaults to 10. -
- -
newest
-
- Determines how many tasks the "task newest" command displays. - Defaults to 10. -
-
defaultwidth
The width of tables used when ncurses support is not available. diff --git a/html/custom.html b/html/custom.html index a4a64c803..1ee499a35 100644 --- a/html/custom.html +++ b/html/custom.html @@ -36,9 +36,10 @@

Task allows you to customize reports, to a limited degree. - The "list", "long", and "ls" reports are all now custom - reports, whereas in previous releases of task they were not - mutable. This means they can be modified, renamed, or deleted. + The "list", "long", "ls", "oldest" and "newest" reports are + all now custom reports, whereas in previous releases of task + they were not mutable. This means they can be modified, + renamed, or deleted.

@@ -74,6 +75,14 @@ report.mine.sort=priority-,project+ definition is optional.

+

+ An optional limit can also be specified, which limits the + number of tasks shown in the report. If a limit is not + specified, then the number of tasks is not limited. +

+ +
report.mine.limit=10
+

Here is a list of all the possible columns that may be included in a report: diff --git a/html/task.html b/html/task.html index 9f4ef60cc..956c26730 100644 --- a/html/task.html +++ b/html/task.html @@ -111,7 +111,8 @@

  • Added support for custom reports, comprised of a set of column names and sort order, with optional filtering in the configuration file. This means user-defined reports can be written, and the reports currently - in the configuration file can be renamed. + in the configuration file can be renamed. Several of task's built in + reports have been converted to user-defined reports.
  • New online documentation for custom reports.
  • New algorithm for determining when the "nag" message is displayed.
  • Fixed bug where task hangs with a certain combination of recurring tasks diff --git a/html/usage.html b/html/usage.html index 9970427c6..840eb9b15 100644 --- a/html/usage.html +++ b/html/usage.html @@ -34,12 +34,10 @@

    Command Usage

    -
    task add [tags] [attrs] desc...
    -       task list [tags] [attrs] desc...
    -       task long [tags] [attrs] desc...
    -       task ls [tags] [attrs] desc...
    +                
    Usage: task
    +       task add [tags] [attrs] desc...
            task completed [tags] [attrs] desc...
    -       task ID [tags] [attrs] ["desc..."]
    +       task ID [tags] [attrs] [desc...]
            task ID /from/to/
            task delete ID
            task undelete ID
    @@ -57,12 +55,18 @@
            task calendar
            task active
            task overdue
    -       task oldest
    -       task newest
            task stats
            task export
            task color
            task version
    +       task help
    +       task list [tags] [attrs] desc...
    +       task long [tags] [attrs] desc...
    +       task ls [tags] [attrs] desc...
    +       task newest [tags] [attrs] desc...
    +       task oldest [tags] [attrs] desc...
    +
    +See http://www.beckingham.net/task.html for the latest releases and a full tutorial.
     
     ID is the numeric identifier displayed by the 'task list' command
     
    @@ -74,8 +78,11 @@ Attributes are:
       project:           Project name
       priority:          Priority
       due:               Due date
    +  recur:             Recurrence frequency
    +  until:             Recurrence end date
       fg:                Foreground color
       bg:                Background color
    +  rc:                Alternate .taskrc file
     
     Any command or attribute name may be abbreviated if still unique:
       task list project:Home
    @@ -86,7 +93,7 @@ Some task descriptions need to be escaped because of the shell:
       task add escaped \' quote
     
     Many characters have special meaning to the shell, including:
    -  $ ! ' " ( ) ; \ ` * ? { } [ ] < > | & % # ~
    + $ ! ' " ( ) ; \ ` * ? { } [ ] < > | & % # ~

    diff --git a/src/Config.cpp b/src/Config.cpp index 48f549ea6..3476bc6f8 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -39,17 +39,32 @@ // These are default (but overridable) reports. These entries are necessary // because these three reports were converted from hard-coded reports to custom // reports, and therefore need these config file entries. However, users are -// already used to seeing these three reports, but do not have these entries. +// already used to seeing these five reports, but do not have these entries. // The choice was a) make users edit their .taskrc files, b) write a .taskrc // upgrade program to make the change, or c) this. Config::Config () { - (*this)["report.long.columns"] = "id,project,priority,entry,start,due,age,tags,description"; - (*this)["report.long.sort"] = "due+,priority-,project+"; - (*this)["report.list.columns"] = "id,project,priority,due,active,age,description"; - (*this)["report.list.sort"] = "due+,priority-,project+"; - (*this)["report.ls.columns"] = "id,project,priority,description"; - (*this)["report.ls.sort"] = "priority-,project+"; + (*this)["report.long.description"] = "Lists all task, all data, matching the specified criteria"; + (*this)["report.long.columns"] = "id,project,priority,entry,start,due,recur,age,tags,description"; + (*this)["report.long.sort"] = "due+,priority-,project+"; + + (*this)["report.list.description"] = "Lists all tasks matching the specified criteria"; + (*this)["report.list.columns"] = "id,project,priority,due,active,age,description"; + (*this)["report.list.sort"] = "due+,priority-,project+"; + + (*this)["report.ls.description"] = "Minimal listing of all tasks matching the specified criteria"; + (*this)["report.ls.columns"] = "id,project,priority,description"; + (*this)["report.ls.sort"] = "priority-,project+"; + + (*this)["report.newest.description"] = "Shows the newest tasks"; + (*this)["report.newest.columns"] = "id,project,priority,due,active,age,description"; + (*this)["report.newest.sort"] = "id-"; + (*this)["report.newest.limit"] = "10"; + + (*this)["report.oldest.description"] = "Shows the oldest tasks"; + (*this)["report.oldest.columns"] = "id,project,priority,due,active,age,description"; + (*this)["report.oldest.sort"] = "id+"; + (*this)["report.oldest.limit"] = "10"; } //////////////////////////////////////////////////////////////////////////////// @@ -59,9 +74,9 @@ Config::Config (const std::string& file) } //////////////////////////////////////////////////////////////////////////////// -// Read the Configuration filee and populate the *this map. The file format -// is simply lines with name=value pairs. Whitespace between name, = and value -// is not tolerated, but blank lines and comments starting with # are allowed. +// Read the Configuration file and populate the *this map. The file format is +// simply lines with name=value pairs. Whitespace between name, = and value is +// not tolerated, but blank lines and comments starting with # are allowed. bool Config::load (const std::string& file) { std::ifstream in; @@ -154,17 +169,33 @@ void Config::createDefault (const std::string& home) fprintf (out, "default.command=list\n"); // Custom reports. - fprintf (out, "# Fields: id,uuid,project,priority,entry,start,due,age,active,tags,description\n"); - fprintf (out, "# Sort: due+,priority-,project+\n"); - fprintf (out, "# Filter: pro:x pri:H +bug\n"); - fprintf (out, "report.large.columns=id,uuid,project,priority,entry,start,due,age,active,tags,description\n"); - fprintf (out, "report.large.sort=due+,priority-,project+\n"); - fprintf (out, "report.long.columns=id,project,priority,entry,start,due,age,tags,description\n"); - fprintf (out, "report.long.sort=due+,priority-,project+\n"); - fprintf (out, "report.list.columns=id,project,priority,due,active,age,description\n"); - fprintf (out, "report.list.sort=due+,priority-,project+\n"); - fprintf (out, "report.ls.columns=id,project,priority,description\n"); - fprintf (out, "report.ls.sort=priority-,project+\n"); + fprintf (out, "# Fields: id,uuid,project,priority,entry,start,due,recur,age,active,tags,description\n"); + fprintf (out, "# Description: This report is ...\n"); + fprintf (out, "# Sort: due+,priority-,project+\n"); + fprintf (out, "# Filter: pro:x pri:H +bug\n"); + fprintf (out, "# Limit: 10\n"); + + fprintf (out, "report.long.description=Lists all task, all data, matching the specified criteria"); + fprintf (out, "report.long.columns=id,project,priority,entry,start,due,recur,age,tags,description"); + fprintf (out, "report.long.sort=due+,priority-,project+"); + + fprintf (out, "report.list.description=Lists all tasks matching the specified criteria"); + fprintf (out, "report.list.columns=id,project,priority,due,active,age,description"); + fprintf (out, "report.list.sort=due+,priority-,project+"); + + fprintf (out, "report.ls.description=Minimal listing of all tasks matching the specified criteria"); + fprintf (out, "report.ls.columns=id,project,priority,description"); + fprintf (out, "report.ls.sort=priority-,project+"); + + fprintf (out, "report.newest.description=Shows the newest tasks"); + fprintf (out, "report.newest.columns=id,project,priority,due,active,age,description"); + fprintf (out, "report.newest.sort=id-"); + fprintf (out, "report.newest.limit=10"); + + fprintf (out, "report.oldest.description=Shows the oldest tasks"); + fprintf (out, "report.oldest.columns=id,project,priority,due,active,age,description"); + fprintf (out, "report.oldest.sort=id+"); + fprintf (out, "report.oldest.limit=10"); fclose (out); diff --git a/src/Grid.cpp b/src/Grid.cpp index a391aa75b..bd1f0f6f5 100644 --- a/src/Grid.cpp +++ b/src/Grid.cpp @@ -325,7 +325,7 @@ Grid::Cell::operator int () const case CELL_INT: return mInt; case CELL_FLOAT: return (int) mFloat; case CELL_DOUBLE: return (int) mDouble; - case CELL_STRING: return mString.length (); + case CELL_STRING: return ::atoi (mString.c_str ()); } return 0; @@ -340,7 +340,7 @@ Grid::Cell::operator float () const case CELL_INT: return (float) mInt; case CELL_FLOAT: return mFloat; case CELL_DOUBLE: return (float) mDouble; - case CELL_STRING: return (float) mString.length (); + case CELL_STRING: return (float) ::atof (mString.c_str ()); } return 0.0; @@ -355,7 +355,7 @@ Grid::Cell::operator double () const case CELL_INT: return (double) mInt; case CELL_FLOAT: return (double) mFloat; case CELL_DOUBLE: return mDouble; - case CELL_STRING: return (double) mString.length (); + case CELL_STRING: return (double) ::atof (mString.c_str ()); } return 0.0; diff --git a/src/Table.cpp b/src/Table.cpp index 071837cdf..8052ca0b3 100644 --- a/src/Table.cpp +++ b/src/Table.cpp @@ -994,7 +994,7 @@ void Table::clean (std::string& value) } //////////////////////////////////////////////////////////////////////////////// -const std::string Table::render () +const std::string Table::render (int maximum /* = 0 */) { calculateColumnWidths (); @@ -1028,8 +1028,14 @@ const std::string Table::render () if (mSortColumns.size ()) sort (order); + // If a non-zero maximum is specified, then it limits the number of rows of + // the table that are rendered. + int limit = mRows; + if (maximum != 0) + limit = maximum; + // Print all rows. - for (int row = 0; row < mRows; ++row) + for (int row = 0; row < limit; ++row) { std::vector > columns; std::vector blanks; diff --git a/src/Table.h b/src/Table.h index fe098e896..aed5587e1 100644 --- a/src/Table.h +++ b/src/Table.h @@ -91,7 +91,7 @@ public: int rowCount (); int columnCount (); - const std::string render (); + const std::string render (int maximum = 0); private: std::string getCell (const int, const int); diff --git a/src/command.cpp b/src/command.cpp index 0a6e9756a..4197810ba 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -367,8 +367,8 @@ std::string handleVersion (Config& conf) "blanklines color color.active color.due color.overdue color.pri.H " "color.pri.L color.pri.M color.pri.none color.recurring color.tagged " "confirmation curses data.location dateformat default.command " - "default.priority defaultwidth due locking monthsperline nag newest next " - "oldest project shadow.command shadow.file shadow.notify"; + "default.priority defaultwidth due locking monthsperline nag next project " + "shadow.command shadow.file shadow.notify"; // This configuration variable is supported, but not documented. It exists // so that unit tests can force color to be on even when the output from task diff --git a/src/parse.cpp b/src/parse.cpp index b87ce8dc9..b0a1c5197 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -130,12 +130,7 @@ static const char* commands[] = "history", "ghistory", "info", - "list", - "long", - "ls", - "newest", "next", - "oldest", "overdue", "projects", "start", diff --git a/src/report.cpp b/src/report.cpp index c88633ba0..ff26b34c8 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1678,302 +1678,6 @@ std::string handleReportOverdue (TDB& tdb, T& task, Config& conf) return out.str (); } -//////////////////////////////////////////////////////////////////////////////// -// Successively apply filters based on the task object built from the command -// line. Tasks that match all the specified criteria are listed. -std::string handleReportOldest (TDB& tdb, T& task, Config& conf) -{ - std::stringstream out; - - // 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. - std::vector tasks; - tdb.allPendingT (tasks); - handleRecurrence (tdb, tasks); - filter (tasks, task); - - initializeColorRules (conf); - - 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"); - table.addColumn ("Age"); - table.addColumn ("Description"); - - if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) - { - table.setColumnUnderline (0); - table.setColumnUnderline (1); - table.setColumnUnderline (2); - table.setColumnUnderline (3); - table.setColumnUnderline (4); - table.setColumnUnderline (5); - table.setColumnUnderline (6); - } - else - table.setTableDashedUnderline (); - - 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); - table.setColumnWidth (6, Table::flexible); - - table.setColumnJustification (0, Table::right); - table.setColumnJustification (3, Table::right); - 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); - table.addCell (row, 5, age); - table.addCell (row, 6, refTask.getDescription ()); - - if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) - { - Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); - Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg, conf); - table.setRowFg (row, fg); - table.setRowBg (row, bg); - - if (fg == Text::nocolor) - { - if (overdue) - table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red"))); - else if (imminent) - table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow"))); - } - } - } - - if (table.rowCount ()) - out << optionalBlankLine (conf) - << table.render () - << optionalBlankLine (conf) - << table.rowCount () - << (table.rowCount () == 1 ? " task" : " tasks") - << std::endl; - else - out << "No matches." - << std::endl; - - return out.str (); -} - -//////////////////////////////////////////////////////////////////////////////// -// Successively apply filters based on the task object built from the command -// line. Tasks that match all the specified criteria are listed. -std::string handleReportNewest (TDB& tdb, T& task, Config& conf) -{ - std::stringstream out; - - // 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. - std::vector tasks; - tdb.allPendingT (tasks); - handleRecurrence (tdb, tasks); - filter (tasks, task); - - initializeColorRules (conf); - - 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"); - table.addColumn ("Age"); - table.addColumn ("Description"); - - if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) - { - table.setColumnUnderline (0); - table.setColumnUnderline (1); - table.setColumnUnderline (2); - table.setColumnUnderline (3); - table.setColumnUnderline (4); - table.setColumnUnderline (5); - table.setColumnUnderline (6); - } - else - table.setTableDashedUnderline (); - - 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); - table.setColumnWidth (6, Table::flexible); - - table.setColumnJustification (0, Table::right); - table.setColumnJustification (3, Table::right); - 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); - table.addCell (row, 5, age); - table.addCell (row, 6, refTask.getDescription ()); - - if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) - { - Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); - Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg, conf); - table.setRowFg (row, fg); - table.setRowBg (row, bg); - - if (fg == Text::nocolor) - { - if (overdue) - table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red"))); - else if (imminent) - table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow"))); - } - } - } - - if (table.rowCount ()) - out << optionalBlankLine (conf) - << table.render () - << optionalBlankLine (conf) - << table.rowCount () - << (table.rowCount () == 1 ? " task" : " tasks") - << std::endl; - else - out << "No matches." - << std::endl; - - return out.str (); -} - - //////////////////////////////////////////////////////////////////////////////// std::string handleReportStats (TDB& tdb, T& task, Config& conf) { @@ -2575,10 +2279,12 @@ std::string handleCustomReport ( } } + int maximum = conf.get (std::string ("report.") + report + ".limit", (int)0); + std::stringstream out; if (table.rowCount ()) out << optionalBlankLine (conf) - << table.render () + << table.render (maximum) << optionalBlankLine (conf) << table.rowCount () << (table.rowCount () == 1 ? " task" : " tasks") diff --git a/src/task.cpp b/src/task.cpp index 3c1b2f629..eacaaf7f7 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -158,14 +158,6 @@ static void shortUsage (Config& conf) table.addCell (row, 1, "task overdue"); table.addCell (row, 2, "Shows all incomplete tasks that are beyond their due date"); - row = table.addRow (); - table.addCell (row, 1, "task oldest"); - table.addCell (row, 2, "Shows the oldest tasks"); - - row = table.addRow (); - table.addCell (row, 1, "task newest"); - table.addCell (row, 2, "Shows the newest tasks"); - row = table.addRow (); table.addCell (row, 1, "task stats"); table.addCell (row, 2, "Shows task database statistics"); @@ -817,8 +809,6 @@ std::string runTaskCommand ( else if (command == "calendar") { if (gc) tdb.gc (); out = handleReportCalendar (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } else if (command == "active") { if (gc) tdb.gc (); out = handleReportActive (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } // TODO replace with Custom else if (command == "overdue") { if (gc) tdb.gc (); out = handleReportOverdue (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } // TODO replace with Custom - else if (command == "oldest") { if (gc) tdb.gc (); out = handleReportOldest (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } // TODO replace with Custom - else if (command == "newest") { if (gc) tdb.gc (); out = handleReportNewest (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } // TODO replace with Custom else if (command == "colors") { out = handleColor ( conf ); } else if (command == "version") { out = handleVersion ( conf ); } else if (command == "help") { longUsage ( conf ); } diff --git a/src/task.h b/src/task.h index dffc3ef17..05b710497 100644 --- a/src/task.h +++ b/src/task.h @@ -100,8 +100,6 @@ std::string handleReportCalendar (TDB&, T&, Config&); std::string handleReportActive (TDB&, T&, Config&); std::string handleReportOverdue (TDB&, T&, Config&); std::string handleReportStats (TDB&, T&, Config&); -std::string handleReportOldest (TDB&, T&, Config&); -std::string handleReportNewest (TDB&, T&, Config&); std::string handleCustomReport (TDB&, T&, Config&, const std::string&); void validReportColumns (const std::vector &);