From 0987171280e2c250d39b0dbc72eca37a0b44481a Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 19 Oct 2008 11:47:03 -0400 Subject: [PATCH 001/103] - Added support for "task stop " command, that removes the start time from a task. - Updated documentation accordingly. --- ChangeLog | 1 + html/advanced.html | 5 +++++ html/task.html | 1 + html/usage.html | 1 + src/command.cpp | 30 ++++++++++++++++++++++++++++++ src/parse.cpp | 1 + src/task.cpp | 12 ++++++------ src/task.h | 1 + 8 files changed, 46 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index c2b641218..52f729398 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,7 @@ + Removed deprecated TUTORIAL file. + Removed "usage" command, and support for "command.logging" configuration variable. + + "task stop" can now remove the start time from a started task. ------ old releases ------------------------------ diff --git a/html/advanced.html b/html/advanced.html index 30fc4a436..1aeb75361 100644 --- a/html/advanced.html +++ b/html/advanced.html @@ -157,6 +157,11 @@ ID Project Pri Due Active Age Description "task start ..." command was run, as shown above.

+ % task stop <id> +

+ Marks a task as inactive, by removing the start time. +

+ % task overdue

Simply lists all the task that have a due date that is past, in diff --git a/html/task.html b/html/task.html index b4ba90991..3118b1ea4 100644 --- a/html/task.html +++ b/html/task.html @@ -99,6 +99,7 @@

  • Removed deprecated TUTORIAL file.
  • Removed "usage" command, and support for "command.logging" configuration variable. +
  • "task stop" can remove the start time from a started task.

    diff --git a/html/usage.html b/html/usage.html index 92b7b1d88..7c42de3a5 100644 --- a/html/usage.html +++ b/html/usage.html @@ -44,6 +44,7 @@ task undelete ID task info ID task start ID + task stop ID task done ID task undo ID task projects diff --git a/src/command.cpp b/src/command.cpp index 6d0b70508..b444b9515 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -441,6 +441,36 @@ void handleStart (TDB& tdb, T& task, Config& conf) throw std::string ("Task not found."); } +//////////////////////////////////////////////////////////////////////////////// +void handleStop (TDB& tdb, T& task, Config& conf) +{ + std::vector all; + tdb.pendingT (all); + + std::vector ::iterator it; + for (it = all.begin (); it != all.end (); ++it) + { + if (it->getId () == task.getId ()) + { + T original (*it); + + if (original.getAttribute ("start") != "") + { + original.removeAttribute ("start"); + original.setId (task.getId ()); + tdb.modifyT (original); + + nag (tdb, task, conf); + return; + } + else + std::cout << "Task " << task.getId () << " not started." << std::endl; + } + } + + throw std::string ("Task not found."); +} + //////////////////////////////////////////////////////////////////////////////// void handleDone (TDB& tdb, T& task, Config& conf) { diff --git a/src/parse.cpp b/src/parse.cpp index bf835ac55..1e88ef534 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -138,6 +138,7 @@ static const char* commands[] = "projects", "start", "stats", + "stop", "summary", "tags", "undelete", diff --git a/src/task.cpp b/src/task.cpp index 88711d8cf..1a54418f9 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -125,7 +125,11 @@ static void shortUsage (Config& conf) row = table.addRow (); table.addCell (row, 1, "task start ID"); - table.addCell (row, 2, "Marks specified task as started, starts the clock ticking"); + table.addCell (row, 2, "Marks specified task as started"); + + row = table.addRow (); + table.addCell (row, 1, "task stop ID"); + table.addCell (row, 2, "Removes the 'start' time from a task"); row = table.addRow (); table.addCell (row, 1, "task done ID"); @@ -300,10 +304,6 @@ int main (int argc, char** argv) std::string dataLocation = expandPath (conf.get ("data.location")); tdb.dataDirectory (dataLocation); - // Log commands, if desired. - if (conf.get ("command.logging") == "on") - tdb.logCommand (argc, argv); - // Set up TDB callback. std::string shadowFile = expandPath (conf.get ("shadow.file")); if (shadowFile != "") @@ -766,6 +766,7 @@ void runTaskCommand ( else if (command == "completed") handleCompleted (tdb, task, conf); else if (command == "delete") handleDelete (tdb, task, conf); else if (command == "start") handleStart (tdb, task, conf); + else if (command == "stop") handleStop (tdb, task, conf); else if (command == "done") handleDone (tdb, task, conf); else if (command == "undo") handleUndo (tdb, task, conf); else if (command == "export") handleExport (tdb, task, conf); @@ -780,7 +781,6 @@ void runTaskCommand ( else if (command == "oldest") handleReportOldest (tdb, task, conf); else if (command == "newest") handleReportNewest (tdb, task, conf); else if (command == "stats") handleReportStats (tdb, task, conf); - else if (command == "usage") handleReportUsage (tdb, task, conf); else if (command == "" && task.getId ()) handleModify (tdb, task, conf); else if (command == "help") longUsage (conf); else shortUsage (conf); diff --git a/src/task.h b/src/task.h index bc15fbfc3..1f550bd34 100644 --- a/src/task.h +++ b/src/task.h @@ -79,6 +79,7 @@ void handleVersion (Config&); void handleExport (TDB&, T&, Config&); void handleDelete (TDB&, T&, Config&); void handleStart (TDB&, T&, Config&); +void handleStop (TDB&, T&, Config&); void handleDone (TDB&, T&, Config&); void handleUndo (TDB&, T&, Config&); void handleModify (TDB&, T&, Config&); From d6b30466c13eb45b73f70b13f6178f9b751fc44f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 1 Nov 2008 15:44:25 -0400 Subject: [PATCH 002/103] - "task ghistory" now displays a differently aligned graph, allowing easier comparison by month of tasks added versus completed and deleted. --- ChangeLog | 2 ++ html/task.html | 2 ++ src/report.cpp | 34 +++++++++++++++++++++++----------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index 52f729398..396fd1785 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,8 @@ + Removed "usage" command, and support for "command.logging" configuration variable. + "task stop" can now remove the start time from a started task. + + "task ghistory" now displays a differently aligned graph, allowing + easier comparison by month of tasks added versus completed and deleted. ------ old releases ------------------------------ diff --git a/html/task.html b/html/task.html index 57083e0a7..ccbbd3274 100644 --- a/html/task.html +++ b/html/task.html @@ -96,6 +96,8 @@

  • Removed "usage" command, and support for "command.logging" configuration variable.
  • "task stop" can remove the start time from a started task. +
  • "task ghistory" now displays a differently aligned graph, allowing + easier comparison by month of tasks added versus completed and deleted.

    diff --git a/src/report.cpp b/src/report.cpp index 2e60e498e..eb3a03de0 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1380,7 +1380,7 @@ void handleReportGHistory (TDB& tdb, T& task, Config& conf) endwin (); } #endif - int widthOfBar = width - 15; // strlen ("2008 September ") + int widthOfBar = width - 15; // 15 == strlen ("2008 September ") std::map groups; std::map addedGroup; @@ -1480,18 +1480,24 @@ void handleReportGHistory (TDB& tdb, T& task, Config& conf) else table.setTableDashedUnderline (); - // Determine the longest line. - int maxLine = 0; + // Determine the longest line, and the longest "added" line. + int maxAddedLine = 0; + int maxRemovedLine = 0; foreach (i, groups) { - int line = addedGroup[i->first] + completedGroup[i->first] + deletedGroup[i->first]; + if (completedGroup[i->first] + deletedGroup[i->first] > maxRemovedLine) + maxRemovedLine = completedGroup[i->first] + deletedGroup[i->first]; - if (line > maxLine) - maxLine = line; + 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; @@ -1521,7 +1527,7 @@ void handleReportGHistory (TDB& tdb, T& task, Config& conf) unsigned int completedBar = (widthOfBar * completedGroup[i->first]) / maxLine; unsigned int deletedBar = (widthOfBar * deletedGroup[i->first]) / maxLine; - std::string bar; + std::string bar = ""; if (conf.get ("color", true)) { char number[24]; @@ -1552,9 +1558,12 @@ void handleReportGHistory (TDB& tdb, T& task, Config& conf) 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); + while (bar.length () < leftOffset - aBar.length ()) + bar += " "; + + 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 { @@ -1562,7 +1571,10 @@ void handleReportGHistory (TDB& tdb, T& task, Config& conf) std::string cBar = ""; while (cBar.length () < completedBar) cBar += "X"; std::string dBar = ""; while (dBar.length () < deletedBar) dBar += "-"; - bar = aBar + cBar + dBar; + while (bar.length () < leftOffset - aBar.length ()) + bar += " "; + + bar += aBar + cBar + dBar; } table.addCell (row, 2, bar); From 5f85550664f094f847af43bc19da755560e30ef4 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 8 Nov 2008 22:43:40 -0500 Subject: [PATCH 003/103] - Removed support for the "showage" configuration variable. --- ChangeLog | 1 + html/config.html | 6 --- html/troubleshooting.html | 26 ------------- src/Config.cpp | 1 - src/report.cpp | 82 ++++++++++++++++++--------------------- 5 files changed, 38 insertions(+), 78 deletions(-) diff --git a/ChangeLog b/ChangeLog index 13dae5fc0..e765818e0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,7 @@ + Removed deprecated TUTORIAL file. + Removed "usage" command, and support for "command.logging" configuration variable. + + Removed "showage" configuration variable. + "task stop" can now remove the start time from a started task. + "task ghistory" now displays a differently aligned graph, allowing easier comparison by month of tasks added versus completed and deleted. diff --git a/html/config.html b/html/config.html index bbdb16b73..30db4d940 100644 --- a/html/config.html +++ b/html/config.html @@ -171,12 +171,6 @@

    -
    showage
    -
    - May be "yes" or "no". Determines whether the "Age" - column appears on the "list" and "next" reports. -
    -
    monthsperline
    Determines how many months the "task calendar" command diff --git a/html/troubleshooting.html b/html/troubleshooting.html index 5e3147631..1876c7c66 100644 --- a/html/troubleshooting.html +++ b/html/troubleshooting.html @@ -60,32 +60,6 @@

    -
    -

    How to get rid of the "Age" column

    -
    -

    - The "Age" column that shows up on several reports is proving - to be unpopular. In task 1.2.0 and later, here is how to - remove it from the reports - make sure you have the line: -

    - -
    showage=no
    - -

    - in your ~/.taskrc file. - - Note that the "task long" report does not obey this setting - in versions prior to 1.3.1. -

    - -

    - The "showage" setting is supported in task 1.2.0 or later. -
    - The "task long" report supports this setting in versions 1.3.1 - or later. -

    -
    -

    How do I build task under Cygwin?

    diff --git a/src/Config.cpp b/src/Config.cpp index 03ca730dd..deff2922d 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -115,7 +115,6 @@ void Config::createDefault (const std::string& home) fprintf (out, "confirmation=yes\n"); fprintf (out, "next=2\n"); fprintf (out, "dateformat=m/d/Y\n"); - fprintf (out, "showage=yes\n"); fprintf (out, "monthsperline=1\n"); fprintf (out, "curses=on\n"); fprintf (out, "color=on\n"); diff --git a/src/report.cpp b/src/report.cpp index 2f29f95ed..869786be6 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -134,8 +134,6 @@ std::string handleList (TDB& tdb, T& task, Config& conf) initializeColorRules (conf); - bool showAge = conf.get ("showage", true); - // Create a table for output. Table table; table.setTableWidth (width); @@ -144,7 +142,7 @@ std::string handleList (TDB& tdb, T& task, Config& conf) table.addColumn ("Pri"); table.addColumn ("Due"); table.addColumn ("Active"); - if (showAge) table.addColumn ("Age"); + table.addColumn ("Age"); table.addColumn ("Description"); if (conf.get (std::string ("color"), true)) @@ -155,7 +153,7 @@ std::string handleList (TDB& tdb, T& task, Config& conf) table.setColumnUnderline (3); table.setColumnUnderline (4); table.setColumnUnderline (5); - if (showAge) table.setColumnUnderline (6); + table.setColumnUnderline (6); } else table.setTableDashedUnderline (); @@ -165,12 +163,12 @@ std::string handleList (TDB& tdb, T& task, Config& conf) 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.setColumnWidth (5, Table::minimum); + table.setColumnWidth (6, Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); - if (showAge) table.setColumnJustification (5, Table::right); + table.setColumnJustification (5, Table::right); table.sortOn (3, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); @@ -222,8 +220,8 @@ std::string handleList (TDB& tdb, T& task, Config& conf) 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 ()); + table.addCell (row, 5, age); + table.addCell (row, 6, refTask.getDescription ()); if (conf.get ("color", true)) { @@ -704,8 +702,6 @@ std::string handleLongList (TDB& tdb, T& task, Config& conf) initializeColorRules (conf); - bool showAge = conf.get ("showage", true); - // Create a table for output. Table table; table.setTableWidth (width); @@ -716,7 +712,7 @@ std::string handleLongList (TDB& tdb, T& task, Config& conf) table.addColumn ("Entry"); table.addColumn ("Start"); table.addColumn ("Due"); - if (showAge) table.addColumn ("Age"); + table.addColumn ("Age"); table.addColumn ("Tags"); table.addColumn ("Description"); @@ -730,7 +726,7 @@ std::string handleLongList (TDB& tdb, T& task, Config& conf) table.setColumnUnderline (5); table.setColumnUnderline (6); table.setColumnUnderline (7); - if (showAge) table.setColumnUnderline (8); + table.setColumnUnderline (8); } else table.setTableDashedUnderline (); @@ -741,15 +737,15 @@ std::string handleLongList (TDB& tdb, T& task, Config& conf) 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.setColumnWidth (6, Table::minimum); + table.setColumnWidth (7, Table::minimum); + table.setColumnWidth (8, 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.setColumnJustification (6, Table::right); table.sortOn (5, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); @@ -816,9 +812,9 @@ std::string handleLongList (TDB& tdb, T& task, Config& conf) 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 ()); + table.addCell (row, 6, age); + table.addCell (row, 7, tags); + table.addCell (row, 8, refTask.getDescription ()); if (conf.get ("color", true)) { @@ -1062,8 +1058,6 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf) initializeColorRules (conf); - bool showAge = conf.get ("showage", true); - // Create a table for output. Table table; table.setTableWidth (width); @@ -1073,7 +1067,7 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf) table.addColumn ("Pri"); table.addColumn ("Due"); table.addColumn ("Active"); - if (showAge) table.addColumn ("Age"); + table.addColumn ("Age"); table.addColumn ("Description"); if (conf.get ("color", true)) @@ -1084,7 +1078,7 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf) table.setColumnUnderline (3); table.setColumnUnderline (4); table.setColumnUnderline (5); - if (showAge) table.setColumnUnderline (6); + table.setColumnUnderline (6); } else table.setTableDashedUnderline (); @@ -1094,12 +1088,12 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf) 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.setColumnWidth (5, Table::minimum); + table.setColumnWidth (6, Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); - if (showAge) table.setColumnJustification (5, Table::right); + table.setColumnJustification (5, Table::right); table.sortOn (3, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); @@ -1148,8 +1142,8 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf) 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 ()); + table.addCell (row, 5, age); + table.addCell (row, 6, refTask.getDescription ()); if (conf.get ("color", true)) { @@ -2102,7 +2096,6 @@ std::string handleReportOldest (TDB& tdb, T& task, Config& conf) initializeColorRules (conf); - bool showAge = conf.get ("showage", true); unsigned int quantity = conf.get ("oldest", 10); // Create a table for output. @@ -2113,7 +2106,7 @@ std::string handleReportOldest (TDB& tdb, T& task, Config& conf) table.addColumn ("Pri"); table.addColumn ("Due"); table.addColumn ("Active"); - if (showAge) table.addColumn ("Age"); + table.addColumn ("Age"); table.addColumn ("Description"); if (conf.get ("color", true)) @@ -2124,7 +2117,7 @@ std::string handleReportOldest (TDB& tdb, T& task, Config& conf) table.setColumnUnderline (3); table.setColumnUnderline (4); table.setColumnUnderline (5); - if (showAge) table.setColumnUnderline (6); + table.setColumnUnderline (6); } else table.setTableDashedUnderline (); @@ -2134,12 +2127,12 @@ std::string handleReportOldest (TDB& tdb, T& task, Config& conf) 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.setColumnWidth (5, Table::minimum); + table.setColumnWidth (6, Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); - if (showAge) table.setColumnJustification (5, Table::right); + table.setColumnJustification (5, Table::right); table.sortOn (3, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); @@ -2189,8 +2182,8 @@ std::string handleReportOldest (TDB& tdb, T& task, Config& conf) 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 ()); + table.addCell (row, 5, age); + table.addCell (row, 6, refTask.getDescription ()); if (conf.get ("color", true)) { @@ -2250,7 +2243,6 @@ std::string handleReportNewest (TDB& tdb, T& task, Config& conf) initializeColorRules (conf); - bool showAge = conf.get ("showage", true); int quantity = conf.get ("newest", 10); // Create a table for output. @@ -2261,7 +2253,7 @@ std::string handleReportNewest (TDB& tdb, T& task, Config& conf) table.addColumn ("Pri"); table.addColumn ("Due"); table.addColumn ("Active"); - if (showAge) table.addColumn ("Age"); + table.addColumn ("Age"); table.addColumn ("Description"); if (conf.get ("color", true)) @@ -2272,7 +2264,7 @@ std::string handleReportNewest (TDB& tdb, T& task, Config& conf) table.setColumnUnderline (3); table.setColumnUnderline (4); table.setColumnUnderline (5); - if (showAge) table.setColumnUnderline (6); + table.setColumnUnderline (6); } else table.setTableDashedUnderline (); @@ -2282,12 +2274,12 @@ std::string handleReportNewest (TDB& tdb, T& task, Config& conf) 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.setColumnWidth (5, Table::minimum); + table.setColumnWidth (6, Table::flexible); table.setColumnJustification (0, Table::right); table.setColumnJustification (3, Table::right); - if (showAge) table.setColumnJustification (5, Table::right); + table.setColumnJustification (5, Table::right); table.sortOn (3, Table::ascendingDate); table.sortOn (2, Table::descendingPriority); @@ -2338,8 +2330,8 @@ std::string handleReportNewest (TDB& tdb, T& task, Config& conf) 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 ()); + table.addCell (row, 5, age); + table.addCell (row, 6, refTask.getDescription ()); if (conf.get ("color", true)) { From 8d920f9dc46061826f17614b87721e66d7474c57 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 8 Nov 2008 22:45:27 -0500 Subject: [PATCH 004/103] - Updated documentation to reflect removal of the "showage" configuration variable. --- ChangeLog | 5 ++--- html/task.html | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index e765818e0..7d05412eb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,8 +3,6 @@ 1.5.0 (?) + Removed deprecated TUTORIAL file. - + Removed "usage" command, and support for "command.logging" configuration - variable. + Removed "showage" configuration variable. + "task stop" can now remove the start time from a started task. + "task ghistory" now displays a differently aligned graph, allowing @@ -21,7 +19,8 @@ + Task now displays a message whenever a shadow file is updated, if the "shadow.notify" configuration variable is set "on" + Bug: adding a task with a \n, \r or \f in it now fails properly - + Removed "task usage" command. + + Removed "usage" command, and support for "command.logging" configuration + variable. + Added documentation for Shadow files. + Added documentation for task filters. diff --git a/html/task.html b/html/task.html index 1716ca000..c93cbb5be 100644 --- a/html/task.html +++ b/html/task.html @@ -96,6 +96,7 @@

    New in version 1.5.0 (?)

    • Removed deprecated TUTORIAL file. +
    • Removed support for the "showage" configuration variable.
    • "task stop" can remove the start time from a started task.
    • "task ghistory" now displays a differently aligned graph, allowing easier comparison by month of tasks added versus completed and deleted. From ecdfb31553fb143d710092074ff09835963188b3 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 8 Nov 2008 23:32:29 -0500 Subject: [PATCH 005/103] - "task version" command now reports unrecognized configuration variables, which may be spelling mistakes or deprecated variables. --- ChangeLog | 2 ++ html/config.html | 10 ++++++++++ html/task.html | 2 ++ src/command.cpp | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/ChangeLog b/ChangeLog index 7d05412eb..fdd4d643e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,8 @@ + "task stop" can now remove the start time from a started task. + "task ghistory" now displays a differently aligned graph, allowing easier comparison by month of tasks added versus completed and deleted. + + "task version" command now reports unrecognized configuration variables, + which may be spelling mistakes or deprecated variables. ------ old releases ------------------------------ diff --git a/html/config.html b/html/config.html index 30db4d940..09add7bef 100644 --- a/html/config.html +++ b/html/config.html @@ -316,6 +316,16 @@ ID Project Pri Description whenever the shadow file is updated by some task command.
    +

    + Note that the command: +

    + +
    task version
    + +

    + will display the configuration variables found in the .taskrc file, + and will warn you of any variables that are not recognized. +


    diff --git a/html/task.html b/html/task.html index c93cbb5be..cd398f0a3 100644 --- a/html/task.html +++ b/html/task.html @@ -100,6 +100,8 @@
  • "task stop" can remove the start time from a started task.
  • "task ghistory" now displays a differently aligned graph, allowing easier comparison by month of tasks added versus completed and deleted. +
  • "task version" command now reports unrecognized configuration variables, + which may be spelling mistakes or deprecated variables.

    diff --git a/src/command.cpp b/src/command.cpp index a404dc399..3030d695c 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -355,6 +355,42 @@ std::string handleVersion (Config& conf) << link.render () << std::endl; + // Complain about configuration variables that are not recognized. + // These are the regular configuration variables. + std::string recognized = + "blanklines color color.active color.due color.overdue color.pri.H " + "color.pri.L color.pri.M color.pri.none color.tagged confirmation curses " + "data.location dateformat default.command default.priority defaultwidth due " + "monthsperline nag newest next oldest project shadow.command shadow.file " + "shadow.notify"; + + std::vector unrecognized; + foreach (i, all) + { + if (recognized.find (*i) == std::string::npos) + { + // These are special configuration variables, because their name is + // dynamic. + if (i->find ("color.keyword.") == std::string::npos && + i->find ("color.project.") == std::string::npos && + i->find ("color.tag.") == std::string::npos) + { + unrecognized.push_back (*i); + } + } + } + + if (unrecognized.size ()) + { + out << "Your .taskrc file contains these unrecognized variables:" + << std::endl; + + foreach (i, unrecognized) + out << " " << *i << std::endl; + + out << std::endl; + } + // Verify installation. This is mentioned in the documentation as the way to // ensure everything is properly installed. From 28ceeac796c456d08b1fe7f36b88c929d256380b Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 8 Nov 2008 23:48:19 -0500 Subject: [PATCH 006/103] - Beginnings of the "task list pri:!H" inverse filtering capability. Doesn't work, and is commented out for now. Need a better approach because of the priority attribute validation of "!H" failing, and the Unix shell interpreting "!", thus requiring an escape, which makes the command ("task list pri:\!H") ugly. --- src/report.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/report.cpp b/src/report.cpp index 869786be6..ac19825a4 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -86,9 +86,40 @@ void filter (std::vector& all, T& task) if (a->second.length () <= refTask.getAttribute (a->first).length ()) if (a->second == refTask.getAttribute (a->first).substr (0, a->second.length ())) ++matches; +/* + TODO Attempt at allowing "pri:!H", thwarted by a lack of coffee and the + validation of "!H" as a priority value. To be revisited soon. + { + if (a->second[0] == '!') // Inverted search. + { + if (a->second.substr (1, std::string::npos) != refTask.getAttribute (a->first).substr (0, a->second.length ())) + ++matches; + } + else + { + if (a->second == refTask.getAttribute (a->first).substr (0, a->second.length ())) + ++matches; + } + } +*/ } else if (a->second == refTask.getAttribute (a->first)) ++matches; +/* + else + { + if (a->second[0] == '!') // Inverted search. + { + if (a->second.substr (1, std::string::npos) != refTask.getAttribute (a->first)) + ++matches; + } + else + { + if (a->second == refTask.getAttribute (a->first)) + ++matches; + } + } +*/ } if (matches == attrList.size ()) From aafcba436e97bd6efcc9c0efdb607ac4cad3a39b Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 9 Nov 2008 00:17:45 -0500 Subject: [PATCH 007/103] - Clarified old statements in ChangeLog. - Removed now obsolete ideas.txt file. --- ChangeLog | 10 ++++++---- ideas.txt | 22 ---------------------- 2 files changed, 6 insertions(+), 26 deletions(-) delete mode 100644 ideas.txt diff --git a/ChangeLog b/ChangeLog index fdd4d643e..2cfba9798 100644 --- a/ChangeLog +++ b/ChangeLog @@ -187,7 +187,7 @@ + Added more missing files. + Added all source code. + Generic OSS files added. - + Initial commit. + + Initial commit on Github. 0.9.3 (4/6/2008) + Added "task completed" command. @@ -198,7 +198,7 @@ + "task" duplicated to "task_rel" for preparation of a fork. 0.9.1 (4/1/2008) - + Blank attributes read are longer be written out. + + Blank attributes read are no longer written out. + Completed "task export" command. + Added configuration values to "task version" command. + Consolidated header files, removed unnecessary ones. @@ -226,10 +226,12 @@ + File locking + retain deleted tasks + "task info ID" report showing all metadata - + File format v2 + + File format v2, including UUID [Development hiatus while planning for T, TDB API, new features and the future -of the project. Seeded to two testers for feedback, suggestions.] +of the project. Seeded to two testers for feedback, suggestions. Development +deliberately stopped to allow extended use of task, allowing command logging and +regular usage to determine which features were needed or unnecessary.] 0.6.0 Reports (12/27/2006) + "task history" diff --git a/ideas.txt b/ideas.txt deleted file mode 100644 index 09cea7499..000000000 --- a/ideas.txt +++ /dev/null @@ -1,22 +0,0 @@ -Real Parsing - define grammar for command line - implement flex/bison parser - new grammar includes: - - task delete [ ...] - - task done [ ...] - -User-Defined Reports - report.large=id,uuid,project,priority,entry,start,due,active,tags,description - report.long=id,project,priority,entry,start,due,tags,description - report.list=id,project,priority,due,active,description - report.ls=id,project,priority,description - - Sorting is always: due+ priority- project+ - - ID UUID Project Priority Entry Start Due Active Tags Description - -Test Suite - - allow .taskrc override - - debug=on to cause all cout to be csv - - regression tests for every bug, command, feature - From 6d5309527c65fd0a4ed7f435b99adaef6bf1b1da Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 9 Nov 2008 01:42:30 -0500 Subject: [PATCH 008/103] - Enabled "configure --enable-debug" to suppress optimization, therefore allowing debugging without the debugger showing the unoptimized source while stepping through optimized code. --- ChangeLog | 2 ++ configure.ac | 25 +++++++++++++++++++++++++ html/task.html | 2 ++ src/Makefile.am | 1 - src/Makefile.in | 1 - 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2cfba9798..109aaa601 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,8 @@ easier comparison by month of tasks added versus completed and deleted. + "task version" command now reports unrecognized configuration variables, which may be spelling mistakes or deprecated variables. + + "configure --enable-debug" now supported to suppress compiler optimization + to allow debugging. ------ old releases ------------------------------ diff --git a/configure.ac b/configure.ac index 120a41cc0..364500f72 100644 --- a/configure.ac +++ b/configure.ac @@ -3,6 +3,29 @@ AC_PREREQ(2.61) AC_INIT(task, 1.5.0, bugs@beckingham.net) + + +CFLAGS="${CFLAGS=}" +CXXFLAGS="${CXXFLAGS=}" +# this macro is used to get the arguments supplied +# to the configure script (./configure --enable-debug) +# Check if we have enable debug support. +AC_MSG_CHECKING(whether to enable debugging) +debug_default="yes" +AC_ARG_ENABLE(debug, [ --enable-debug=[no/yes] turn on debugging + [default=$debug_default]],, enable_debug=$debug_default) +# Yes, shell scripts can be used +if test "x$enable_debug" = "xyes"; then +CFLAGS="$CFLAGS -Wall -pedantic -ggdb3 -DDEBUG" +CXXFLAGS="$CFLAGS -Wall -pedantic -ggdb3 -DDEBUG" +AC_MSG_RESULT(yes) +else +CFLAGS="$CFLAGS -O3" +CXXFLAGS="$CFLAGS -O3" +AC_MSG_RESULT(no) +fi + + AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([src/task.cpp]) AC_CONFIG_HEADER([auto.h]) @@ -12,6 +35,8 @@ AC_PROG_CXX AC_PROG_CC AC_LANG(C++) +AC_SUBST(CFLAGS) + # Checks for libraries. AC_CHECK_LIB(ncurses,initscr) AC_CHECK_LIB(ncurses,endwin) diff --git a/html/task.html b/html/task.html index cd398f0a3..3a4bfd7f2 100644 --- a/html/task.html +++ b/html/task.html @@ -102,6 +102,8 @@ easier comparison by month of tasks added versus completed and deleted.

  • "task version" command now reports unrecognized configuration variables, which may be spelling mistakes or deprecated variables. +
  • "configure --enable-debug" now supported to suppress compiler optimization + to allow debugging.

    diff --git a/src/Makefile.am b/src/Makefile.am index eae651e2b..2e92854a2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,3 +1,2 @@ bin_PROGRAMS = task task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h color.h task.h -AM_CPPFLAGS = -Wall -pedantic -ggdb3 -fno-rtti diff --git a/src/Makefile.in b/src/Makefile.in index 120185c41..c8ca21f5f 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -155,7 +155,6 @@ target_alias = @target_alias@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h color.h task.h -AM_CPPFLAGS = -Wall -pedantic -ggdb3 -fno-rtti all: all-am .SUFFIXES: From 748300631a45f1f4292ca84a640107a1f192c8d5 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 9 Nov 2008 22:46:12 -0500 Subject: [PATCH 009/103] - Now parses the command line and can distinguish regular commands, as well as custom reports. --- src/command.cpp | 1 + src/parse.cpp | 87 ++++++++++++++++++++++++++++++++++++++++++------- src/report.cpp | 19 +++++++++++ src/task.cpp | 63 ++++++++++++++++++----------------- src/task.h | 4 +++ 5 files changed, 133 insertions(+), 41 deletions(-) diff --git a/src/command.cpp b/src/command.cpp index 3030d695c..3308fcd59 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -571,6 +571,7 @@ void handleExport (TDB& tdb, T& task, Config& conf) if (out.good ()) { out << "'id'," + << "'uuid'," << "'status'," << "'tags'," << "'entry'," diff --git a/src/parse.cpp b/src/parse.cpp index 43bc893c4..6bce33139 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -35,6 +35,8 @@ #include "T.h" //////////////////////////////////////////////////////////////////////////////// +// NOTE: These are static arrays only because there is no initializer list for +// std::vector. static const char* colors[] = { "bold", @@ -147,6 +149,9 @@ static const char* commands[] = "", }; +static std::vector customReports; + +//////////////////////////////////////////////////////////////////////////////// void guess (const std::string& type, const char** list, std::string& candidate) { std::vector options; @@ -180,6 +185,36 @@ void guess (const std::string& type, const char** list, std::string& candidate) } } +//////////////////////////////////////////////////////////////////////////////// +void guess (const std::string& type, std::vector& options, std::string& candidate) +{ + std::vector matches; + autoComplete (candidate, options, matches); + if (1 == matches.size ()) + candidate = matches[0]; + + else if (0 == matches.size ()) +// throw std::string ("Unrecognized ") + type + " '" + candidate + "'"; + candidate = ""; + + else + { + std::string error = "Ambiguous "; + error += type; + error += " '"; + error += candidate; + error += "' - could be either of "; + for (size_t i = 0; i < matches.size (); ++i) + { + if (i) + error += ", "; + error += matches[i]; + } + + throw error; + } +} + //////////////////////////////////////////////////////////////////////////////// static bool isCommand (const std::string& candidate) { @@ -190,7 +225,11 @@ static bool isCommand (const std::string& candidate) std::vector matches; autoComplete (candidate, options, matches); if (0 == matches.size ()) - return false; + { + autoComplete (candidate, customReports, matches); + if (0 == matches.size ()) + return false; + } return true; } @@ -284,15 +323,6 @@ static bool validTag (const std::string& input) //////////////////////////////////////////////////////////////////////////////// static bool validDescription (const std::string& input) { -/* - if (input.length () > 0 && - input.find ("\r") == std::string::npos && - input.find ("\f") == std::string::npos && - input.find ("\n") == std::string::npos) - return true; - - return false; -*/ if (input.length () == 0) return false; if (input.find ("\r") != std::string::npos) return false; if (input.find ("\f") != std::string::npos) return false; @@ -307,7 +337,12 @@ static bool validCommand (std::string& input) std::string copy = input; guess ("command", commands, copy); if (copy == "") - return false; + { + copy = input; + guess ("command", customReports, copy); + if (copy == "") + return false; + } input = copy; return true; @@ -450,4 +485,34 @@ void parse ( } //////////////////////////////////////////////////////////////////////////////// +void loadCustomReports (Config& conf) +{ + std::vector all; + conf.all (all); + + foreach (i, all) + { + if (i->substr (0, 7) == "report.") + { + std::string report = i->substr (7, std::string::npos); + unsigned int columns = report.find (".columns"); + if (columns != std::string::npos) + { + report = report.substr (0, columns); + customReports.push_back (report); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +bool isCustomReport (const std::string& report) +{ + foreach (i, customReports) + if (*i == report) + return true; + + return false; +} +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/report.cpp b/src/report.cpp index ac19825a4..50f26257d 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -2673,3 +2673,22 @@ void gatherNextTasks ( } //////////////////////////////////////////////////////////////////////////////// +std::string handleCustomReport ( + TDB& tdb, + T& task, + Config& conf, + const std::string& report) +{ + std::cout << "# woohoo!" << std::endl; + + std::stringstream out; + + // TODO Load columns. + // TODO Load sort order. + + + + return out.str (); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/task.cpp b/src/task.cpp index 98489b952..53b8b098e 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -763,42 +763,45 @@ std::string runTaskCommand ( std::cout << "[task " << defaultCommand << "]" << std::endl; } + loadCustomReports (conf); + std::string command; T task; parse (args, command, task, conf); std::string out = ""; - if (command == "" && task.getId ()) { handleModify (tdb, task, conf); } - else if (command == "add") { handleAdd (tdb, task, conf); } - else if (command == "done") { handleDone (tdb, task, conf); } - else if (command == "export") { handleExport (tdb, task, conf); } - else if (command == "projects") { out = handleProjects (tdb, task, conf); } - else if (command == "tags") { out = handleTags (tdb, task, conf); } - else if (command == "info") { out = handleInfo (tdb, task, conf); } - else if (command == "undelete") { out = handleUndelete (tdb, task, conf); } - else if (command == "delete") { out = handleDelete (tdb, task, conf); } - else if (command == "start") { out = handleStart (tdb, task, conf); } - else if (command == "stop") { out = handleStop (tdb, task, conf); } - else if (command == "undo") { out = handleUndo (tdb, task, conf); } - else if (command == "stats") { out = handleReportStats (tdb, task, conf); } - else if (command == "list") { if (gc) tdb.gc (); out = handleList (tdb, task, conf); } - else if (command == "long") { if (gc) tdb.gc (); out = handleLongList (tdb, task, conf); } - else if (command == "ls") { if (gc) tdb.gc (); out = handleSmallList (tdb, task, conf); } - else if (command == "completed") { if (gc) tdb.gc (); out = handleCompleted (tdb, task, conf); } - else if (command == "summary") { if (gc) tdb.gc (); out = handleReportSummary (tdb, task, conf); } - else if (command == "next") { if (gc) tdb.gc (); out = handleReportNext (tdb, task, conf); } - else if (command == "history") { if (gc) tdb.gc (); out = handleReportHistory (tdb, task, conf); } - else if (command == "ghistory") { if (gc) tdb.gc (); out = handleReportGHistory (tdb, task, conf); } - else if (command == "calendar") { if (gc) tdb.gc (); out = handleReportCalendar (tdb, task, conf); } - else if (command == "active") { if (gc) tdb.gc (); out = handleReportActive (tdb, task, conf); } - else if (command == "overdue") { if (gc) tdb.gc (); out = handleReportOverdue (tdb, task, conf); } - else if (command == "oldest") { if (gc) tdb.gc (); out = handleReportOldest (tdb, task, conf); } - else if (command == "newest") { if (gc) tdb.gc (); out = handleReportNewest (tdb, task, conf); } - else if (command == "colors") { out = handleColor ( conf); } - else if (command == "version") { out = handleVersion ( conf); } - else if (command == "help") { longUsage ( conf); } - else { shortUsage ( conf); } + if (command == "" && task.getId ()) { handleModify (tdb, task, conf ); } + else if (command == "add") { handleAdd (tdb, task, conf ); } + else if (command == "done") { handleDone (tdb, task, conf ); } + else if (command == "export") { handleExport (tdb, task, conf ); } + else if (command == "projects") { out = handleProjects (tdb, task, conf ); } + else if (command == "tags") { out = handleTags (tdb, task, conf ); } + else if (command == "info") { out = handleInfo (tdb, task, conf ); } + else if (command == "undelete") { out = handleUndelete (tdb, task, conf ); } + else if (command == "delete") { out = handleDelete (tdb, task, conf ); } + else if (command == "start") { out = handleStart (tdb, task, conf ); } + else if (command == "stop") { out = handleStop (tdb, task, conf ); } + else if (command == "undo") { out = handleUndo (tdb, task, conf ); } + else if (command == "stats") { out = handleReportStats (tdb, task, conf ); } + else if (command == "list") { if (gc) tdb.gc (); out = handleList (tdb, task, conf ); } // TODO replace with Custom + else if (command == "long") { if (gc) tdb.gc (); out = handleLongList (tdb, task, conf ); } // TODO replace with Custom + else if (command == "ls") { if (gc) tdb.gc (); out = handleSmallList (tdb, task, conf ); } // TODO replace with Custom + else if (command == "completed") { if (gc) tdb.gc (); out = handleCompleted (tdb, task, conf ); } // TODO replace with Custom + else if (command == "summary") { if (gc) tdb.gc (); out = handleReportSummary (tdb, task, conf ); } + else if (command == "next") { if (gc) tdb.gc (); out = handleReportNext (tdb, task, conf ); } // TODO replace with Custom + else if (command == "history") { if (gc) tdb.gc (); out = handleReportHistory (tdb, task, conf ); } + else if (command == "ghistory") { if (gc) tdb.gc (); out = handleReportGHistory (tdb, task, conf ); } + else if (command == "calendar") { if (gc) tdb.gc (); out = handleReportCalendar (tdb, task, conf ); } + else if (command == "active") { if (gc) tdb.gc (); out = handleReportActive (tdb, task, conf ); } // TODO replace with Custom + else if (command == "overdue") { if (gc) tdb.gc (); out = handleReportOverdue (tdb, task, conf ); } // TODO replace with Custom + else if (command == "oldest") { if (gc) tdb.gc (); out = handleReportOldest (tdb, task, conf ); } // TODO replace with Custom + else if (command == "newest") { if (gc) tdb.gc (); out = handleReportNewest (tdb, task, 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 ); } + else if (isCustomReport (command)) { if (gc) tdb.gc (); out = handleCustomReport (tdb, task, conf, command); } // New Custom reports + else { shortUsage ( conf ); } return out; } diff --git a/src/task.h b/src/task.h index b74aa1e6a..79c79ddf9 100644 --- a/src/task.h +++ b/src/task.h @@ -57,6 +57,8 @@ for (typeof (c) *foreach_p = & (c); \ void parse (std::vector &, std::string&, T&, Config&); bool validPriority (const std::string&); bool validDate (std::string&, Config&); +void loadCustomReports (Config&); +bool isCustomReport (const std::string&); // task.cpp void gatherNextTasks (const TDB&, T&, Config&, std::vector &, std::vector &); @@ -103,6 +105,8 @@ 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&); + // util.cpp bool confirm (const std::string&); void wrapText (std::vector &, const std::string&, const int); From 6e1dbfb16e3a013bd98a5a9ac348ba4eade4b7db Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 10 Nov 2008 09:53:49 -0500 Subject: [PATCH 010/103] - Now handles the configuration variable recognition of the new custom report variables. --- src/command.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/command.cpp b/src/command.cpp index 3308fcd59..fb97800d6 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -367,17 +367,18 @@ std::string handleVersion (Config& conf) std::vector unrecognized; foreach (i, all) { - if (recognized.find (*i) == std::string::npos) - { - // These are special configuration variables, because their name is - // dynamic. - if (i->find ("color.keyword.") == std::string::npos && - i->find ("color.project.") == std::string::npos && - i->find ("color.tag.") == std::string::npos) - { - unrecognized.push_back (*i); - } - } + if (recognized.find (*i) == std::string::npos) + { + // These are special configuration variables, because their name is + // dynamic. + if (i->find ("color.keyword.") == std::string::npos && + i->find ("color.project.") == std::string::npos && + i->find ("color.tag.") == std::string::npos && + i->find ("report.") == std::string::npos) + { + unrecognized.push_back (*i); + } + } } if (unrecognized.size ()) From 8639e9260646c8c9224e0fc47e5d2443b46eecfc Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 11 Nov 2008 08:53:59 -0500 Subject: [PATCH 011/103] - Updated release date for 1.4.3. --- html/versions.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/versions.html b/html/versions.html index 3240655f9..a3b304dc4 100644 --- a/html/versions.html +++ b/html/versions.html @@ -55,7 +55,7 @@ changes. Useful for integrating with "Samurize".

  • Task now displays a message whenever a shadow file is updated, if the "shadow.notify" configuration variable is set "on". -
  • Fixed bug whereby adding a task with a \n, \r or \f dit not fail properly. +
  • Fixed bug whereby adding a task with a \n, \r or \f did not fail properly.
  • Removed "task usage" command.
  • Added documentation for Shadow files.
  • Added documentation for task filters. From 14d3abacf48b63f4b917f9648607298efc748d84 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Wed, 19 Nov 2008 00:33:43 -0500 Subject: [PATCH 012/103] - Beginning to fill out processing of the generalized custom report. --- src/report.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/report.cpp b/src/report.cpp index 50f26257d..16ffcaf9b 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -2679,14 +2679,51 @@ std::string handleCustomReport ( Config& conf, const std::string& report) { - std::cout << "# woohoo!" << std::endl; + // 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 + + // Load report configuration. + std::string columnList = conf.get ("report." + report + ".columns"); + std::vector columns; + split (columns, columnList, ','); + + std::string sortList = conf.get ("report." + report + ".sort"); + std::vector sortOrder; + split (sortOrder, sortList, ','); + + std::string filter = conf.get ("report." + report + ".filter"); + + std::cout << "# columns " << columnList << std::endl + << "# sort " << sortList << std::endl + << "# filter " << filter << std::endl; + + Table table; + table.setTableWidth (width); + + // TODO Load pending tasks. + // TODO Apply filters. + // TODO Add columns. + // TODO Add data. std::stringstream out; - - // TODO Load columns. - // TODO Load sort order. - - + 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 (); } From 50ccb6718539fc177a9f40767a3570e9caf5801f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 14 Dec 2008 11:09:15 -0500 Subject: [PATCH 013/103] - Added builtin command detection - Now allows override of due/overdue coloration --- src/parse.cpp | 12 ++++++++++++ src/report.cpp | 53 +++++++++++++++++++++++++++++++------------------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/parse.cpp b/src/parse.cpp index 6bce33139..a2daea1fa 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -348,6 +348,18 @@ static bool validCommand (std::string& input) return true; } +//////////////////////////////////////////////////////////////////////////////// +static bool validBuiltinCommand (std::string& input) +{ + std::string copy = input; + guess ("command", commands, copy); + if (copy == "") + return false; + + input = copy; + return true; +} + //////////////////////////////////////////////////////////////////////////////// static bool validSubstitution ( std::string& input, diff --git a/src/report.cpp b/src/report.cpp index 16ffcaf9b..4d8de003f 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -265,9 +265,9 @@ std::string handleList (TDB& tdb, T& task, Config& conf) if (fg == Text::nocolor) { if (overdue) - table.setCellFg (row, 3, Text::red); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red"))); else if (imminent) - table.setCellFg (row, 3, Text::yellow); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow"))); } } } @@ -397,9 +397,9 @@ std::string handleSmallList (TDB& tdb, T& task, Config& conf) if (fg == Text::nocolor) { if (overdue) - table.setCellFg (row, 3, Text::red); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red"))); else if (imminent) - table.setCellFg (row, 3, Text::yellow); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow"))); } } } @@ -638,9 +638,9 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf) if (conf.get ("color", true)) { if (overdue) - table.setCellFg (row, 1, Text::red); + table.setCellFg (row, 1, Text::colorCode (conf.get ("color.overdue", "red"))); else if (imminent) - table.setCellFg (row, 1, Text::yellow); + table.setCellFg (row, 1, Text::colorCode (conf.get ("color.due", "yellow"))); } } } @@ -858,9 +858,9 @@ std::string handleLongList (TDB& tdb, T& task, Config& conf) if (fg == Text::nocolor) { if (overdue) - table.setCellFg (row, 3, Text::red); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red"))); else if (imminent) - table.setCellFg (row, 3, Text::yellow); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow"))); } } } @@ -1187,9 +1187,9 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf) if (fg == Text::nocolor) { if (overdue) - table.setCellFg (row, 3, Text::red); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red"))); else if (imminent) - table.setCellFg (row, 3, Text::yellow); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow"))); } } } @@ -1969,9 +1969,9 @@ std::string handleReportActive (TDB& tdb, T& task, Config& conf) if (fg == Text::nocolor) { if (overdue) - table.setCellFg (row, 3, Text::red); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red"))); else if (imminent) - table.setCellFg (row, 3, Text::yellow); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow"))); } } } @@ -2227,9 +2227,9 @@ std::string handleReportOldest (TDB& tdb, T& task, Config& conf) if (fg == Text::nocolor) { if (overdue) - table.setCellFg (row, 3, Text::red); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red"))); else if (imminent) - table.setCellFg (row, 3, Text::yellow); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow"))); } } } @@ -2375,9 +2375,9 @@ std::string handleReportNewest (TDB& tdb, T& task, Config& conf) if (fg == Text::nocolor) { if (overdue) - table.setCellFg (row, 3, Text::red); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.overdue", "red"))); else if (imminent) - table.setCellFg (row, 3, Text::yellow); + table.setCellFg (row, 3, Text::colorCode (conf.get ("color.due", "yellow"))); } } } @@ -2705,13 +2705,26 @@ std::string handleCustomReport ( << "# sort " << sortList << std::endl << "# filter " << filter << std::endl; + // TODO Load pending tasks. + std::vector tasks; + tdb.allT (tasks); +// filter (tasks, task); + + // TODO Apply filters. + Table table; table.setTableWidth (width); - // TODO Load pending tasks. - // TODO Apply filters. - // TODO Add columns. - // TODO Add data. + foreach (col, columns) + { + // TODO Add column. + // TODO Add underline. + + // TODO Add data. + foreach (t, tasks) + { + } + } std::stringstream out; if (table.rowCount ()) From 3d4beaf41f3ebc4bb0ee71135486b0afbcc45644 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 14 Dec 2008 15:18:33 -0500 Subject: [PATCH 014/103] - Enhanced split algorithm to be non-destrutive, and therefore faster - Added autoconf testing to detect Solaris - Added Solaris-specific flock implementation --- configure.ac | 12 ++++++++++-- src/TDB.cpp | 12 ------------ src/task.h | 9 +++++++++ src/text.cpp | 34 ++++++++++++++++++++-------------- src/util.cpp | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 28 deletions(-) diff --git a/configure.ac b/configure.ac index 364500f72..a528345e1 100644 --- a/configure.ac +++ b/configure.ac @@ -4,7 +4,6 @@ AC_PREREQ(2.61) AC_INIT(task, 1.5.0, bugs@beckingham.net) - CFLAGS="${CFLAGS=}" CXXFLAGS="${CXXFLAGS=}" # this macro is used to get the arguments supplied @@ -25,6 +24,15 @@ CXXFLAGS="$CFLAGS -O3" AC_MSG_RESULT(no) fi +# Check for OS. +OS=`uname|sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'` +if test "$OS" = "sunos"; then + AC_MSG_NOTICE([OS Solaris detected]) + AC_DEFINE([SOLARIS], [], [Compiling on Solaris]) +else + AC_MSG_NOTICE([OS Non-Solaris detected]) + AC_DEFINE([LINUX], [], [Compiling on Non-Solaris]) +fi AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([src/task.cpp]) @@ -58,7 +66,7 @@ AC_STRUCT_TM AC_FUNC_MKTIME AC_FUNC_SELECT_ARGTYPES AC_CHECK_FUNCS([select]) -AC_CHECK_FUNC(flock, [AC_DEFINE([HAVE_FLOCK], [1], [Found flock])]) +#AC_CHECK_FUNC(flock, [AC_DEFINE([HAVE_FLOCK], [1], [Found flock])]) AC_CHECK_FUNC(uuid_unparse_lower, [AC_DEFINE([HAVE_UUID], [1], [Found uuid_unparse_lower])]) AC_CHECK_FUNC(random, [AC_DEFINE([HAVE_RANDOM], [1], [Found random])]) AC_CHECK_FUNC(srandom, [AC_DEFINE([HAVE_SRANDOM], [1], [Found srandom])]) diff --git a/src/TDB.cpp b/src/TDB.cpp index e531f10a1..ba7b4eb2b 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -289,11 +289,7 @@ bool TDB::modifyT (const T& t) //////////////////////////////////////////////////////////////////////////////// bool TDB::lock (FILE* file) const { -#ifdef HAVE_FLOCK return flock (fileno (file), LOCK_EX) ? false : true; -#else - return true; -#endif } //////////////////////////////////////////////////////////////////////////////// @@ -303,11 +299,9 @@ bool TDB::overwritePending (std::vector & all) FILE* out; if ((out = fopen (mPendingFile.c_str (), "w"))) { -#ifdef HAVE_FLOCK int retry = 0; while (flock (fileno (out), LOCK_EX) && ++retry <= 3) delay (0.25); -#endif std::vector ::iterator it; for (it = all.begin (); it != all.end (); ++it) @@ -328,11 +322,9 @@ bool TDB::writePending (const T& t) FILE* out; if ((out = fopen (mPendingFile.c_str (), "a"))) { -#ifdef HAVE_FLOCK int retry = 0; while (flock (fileno (out), LOCK_EX) && ++retry <= 3) delay (0.25); -#endif fputs (t.compose ().c_str (), out); @@ -351,11 +343,9 @@ bool TDB::writeCompleted (const T& t) FILE* out; if ((out = fopen (mCompletedFile.c_str (), "a"))) { -#ifdef HAVE_FLOCK int retry = 0; while (flock (fileno (out), LOCK_EX) && ++retry <= 3) delay (0.25); -#endif fputs (t.compose ().c_str (), out); @@ -380,11 +370,9 @@ bool TDB::readLockedFile ( FILE* in; if ((in = fopen (file.c_str (), "r"))) { -#ifdef HAVE_FLOCK int retry = 0; while (flock (fileno (in), LOCK_EX) && ++retry <= 3) delay (0.25); -#endif char line[T_LINE_MAX]; while (fgets (line, T_LINE_MAX, in)) diff --git a/src/task.h b/src/task.h index 79c79ddf9..c2f609d6d 100644 --- a/src/task.h +++ b/src/task.h @@ -130,6 +130,15 @@ const char* optionalBlankLine (Config&); int convertDuration (std::string&); std::string expandPath (const std::string&); +#ifdef SOLARIS + #define LOCK_SH 1 + #define LOCK_EX 2 + #define LOCK_NB 4 + #define LOCK_UN 8 + + int flock (int, int); +#endif + // rules.cpp void initializeColorRules (Config&); void autoColorize (T&, Text::color&, Text::color&); diff --git a/src/text.cpp b/src/text.cpp index 066d84f86..90fed8aa2 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -49,33 +49,39 @@ void wrapText ( } //////////////////////////////////////////////////////////////////////////////// -void split (std::vector& results, const std::string& input, const char delimiter) +void split ( + std::vector& results, + const std::string& input, + const char delimiter) { - std::string temp = input; + std::string::size_type start = 0; std::string::size_type i; - while ((i = temp.find (delimiter)) != std::string::npos) + while ((i = input.find (delimiter, start)) != std::string::npos) { - std::string token = temp.substr (0, i); - results.push_back (token); - temp.erase (0, i + 1); + results.push_back (input.substr (start, i - start)); + start = i + 1; } - if (temp.length ()) results.push_back (temp); + results.push_back (input.substr (start, std::string::npos)); } //////////////////////////////////////////////////////////////////////////////// -void split (std::vector& results, const std::string& input, const std::string& delimiter) +void split ( + std::vector& results, + const std::string& input, + const std::string& delimiter) { - std::string temp = input; + std::string::size_type length = delimiter.length (); + + std::string::size_type start = 0; std::string::size_type i; - while ((i = temp.find (delimiter)) != std::string::npos) + while ((i = input.find (delimiter, start)) != std::string::npos) { - std::string token = temp.substr (0, i); - results.push_back (token); - temp.erase (0, i + delimiter.length ()); + results.push_back (input.substr (start, i - start)); + start = i + length; } - if (temp.length ()) results.push_back (temp); + results.push_back (input.substr (start, std::string::npos)); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/util.cpp b/src/util.cpp index 8717619da..fc52bb7bf 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -331,3 +331,37 @@ std::string expandPath (const std::string& in) } //////////////////////////////////////////////////////////////////////////////// +// On Solaris no flock function exists. +#ifdef SOLARIS +int flock (int fd, int operation) +{ + struct flock flock; + + switch (operation & ~LOCK_NB) + { + case LOCK_SH: + flock.l_type = F_RDLCK; + break; + + case LOCK_EX: + flock.l_type = F_WRLCK; + break; + + case LOCK_UN: + flock.l_type = F_UNLCK; + break; + + default: + errno = EINVAL; + return -1; + } + + flock.l_whence = 0; + flock.l_start = 0; + flock.l_len = 0; + + return fcntl (fd, (operation & LOCK_NB) ? F_SETLK : F_SETLKW, &flock); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// From b55eaf8f162e2e65024c5e469f94441694c6708c Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Wed, 28 Jan 2009 11:51:29 -0500 Subject: [PATCH 015/103] Cleanup - renamed grammar.txt to grammar.bnf --- grammar.txt => grammar.bnf | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename grammar.txt => grammar.bnf (100%) diff --git a/grammar.txt b/grammar.bnf similarity index 100% rename from grammar.txt rename to grammar.bnf From c28c698bbf1b645c96ea9d4fd79d0a54865a3304 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Wed, 28 Jan 2009 12:09:24 -0500 Subject: [PATCH 016/103] Cleanup - Converted grammar.bnf to the EBNF used by Parser. --- grammar.bnf | 86 ++++++++++++++++------------------------------------- 1 file changed, 26 insertions(+), 60 deletions(-) diff --git a/grammar.bnf b/grammar.bnf index aad7d610c..18c082005 100644 --- a/grammar.bnf +++ b/grammar.bnf @@ -1,69 +1,35 @@ -This is a full BNF grammar for the task command line. It is intended that a -future release of task will incorporate a complete lexer/parser implementing -this grammar. +# This is a full BNF grammar for the task command line. It is intended that a +# future release of task will incorporate a complete lexer/parser implementing +# this grammar. +command ::= simple_command + | filter_command filter? + | id_command + | "export" file + | + | -command: - VERSION - | HELP - | PROJECTS - | TAGS - | SUMMARY - | HISTORY - | NEXT - | CALENDAR - | ACTIVE - | OVERDUE - | STATS - | USAGE - | OLDEST - | NEWEST - | EXPORT - | COLOR - | DELETE - | UNDELETE - | INFO - | START - | DONE - | ADD [] [] [] - | LIST [] [] [] - | LONG [] [] [] - | LS [] [] [] - | COMPLETED [] [] [] - | [] [] [] - | +simple_command ::= "version" | "help" | "projects" | "tags" | "next" | "stats" + | "color" ; -id: - \d+ - | \d{8}-\d{4}-\d{4}-\d{12} +filter_command ::= "summary" | "history" | "calendar" | "active" | "overdue" + | "oldest" | "newest" | "add" | "list" | "long" | "ls" + | "completed" ; -tags: - + - | - +id_command ::= "delete" | "undelete" | "info" | "start" | "end" | "done" + | "undo" ; -tag: - \w+ +filter ::= filter_part+ ; -attrs: - - | +filter_part ::= tag_add | tag_remove | attribute | word ; -attr: - : - -name: - \w+ - -value: - .+ - -substitution: - / / / - -pattern: - .+ - -file: - ? +tag_add ::= "+" word ; +tag_remove ::= "-" word ; +attribute ::= word ":" word ; +word ::= ... +file ::= ... +id ::= digit+ ; +digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +substitution ::= "/" word+ "/" word* "/" ; From 2f7060ce56f84f85bebc3aca13e908d093e88e78 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 31 Jan 2009 12:08:03 -0500 Subject: [PATCH 017/103] Unit Tests - Fixed long-broken unit tests that were expecting wrong values. --- src/tests/tdb.t.cpp | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/tests/tdb.t.cpp b/src/tests/tdb.t.cpp index 795f92334..1ec2160d6 100644 --- a/src/tests/tdb.t.cpp +++ b/src/tests/tdb.t.cpp @@ -80,7 +80,7 @@ int main (int argc, char** argv) // Complete task. ok (tdb.completeT (t1), "TDB::completeT t1");; - ok (!tdb.pendingT (all), "TDB::pendingT read db"); + ok (tdb.pendingT (all), "TDB::pendingT read db"); is ((int) all.size (), 0, "empty db"); ok (tdb.allPendingT (all), "TDB::allPendingT read db"); is ((int) all.size (), 1, "empty db"); @@ -90,9 +90,9 @@ int main (int argc, char** argv) is ((int) all.size (), 0, "empty db"); is (tdb.gc (), 1, "TDB::gc"); - ok (!tdb.pendingT (all), "TDB::pendingT read empty db"); + ok (tdb.pendingT (all), "TDB::pendingT read empty db"); is ((int) all.size (), 0, "empty db"); - ok (!tdb.allPendingT (all), "TDB::allPendingT read empty db"); + ok (tdb.allPendingT (all), "TDB::allPendingT read empty db"); is ((int) all.size (), 0, "empty db"); ok (tdb.completedT (all), "TDB::completedT read db"); is ((int) all.size (), 1, "empty db"); @@ -116,16 +116,6 @@ int main (int argc, char** argv) // GC the files. is (tdb.gc (), 1, "1 <- TDB::gc"); - - // Read log file. - std::vector entries; - tdb.logRead (entries); - std::vector ::iterator it; - for (it = entries.begin (); it != entries.end (); ++it) - diag (*it); - - // TODO Verify contents of above transactions. - fail ("verify"); } catch (std::string& error) From eba05513f74cb9cc5ae3c4c7274daa7ca5070c04 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Feb 2009 17:05:50 -0500 Subject: [PATCH 018/103] Unit Tests - Converted unit tests to use a UnitTest object, with more methods and and exit summary. - Removed "fail" tests in tdb.t.cpp, because it artificially reduces the number of passing tests - the comments in the code suffice. --- src/tests/date.t.cpp | 244 +++++++++++++++++++---------------- src/tests/duration.t.cpp | 60 ++++++--- src/tests/t.t.cpp | 22 ++-- src/tests/tdb.t.cpp | 93 +++++++------- src/tests/test.cpp | 266 ++++++++++++++++++++++++++++++--------- src/tests/test.h | 49 +++++--- 6 files changed, 470 insertions(+), 264 deletions(-) diff --git a/src/tests/date.t.cpp b/src/tests/date.t.cpp index fe830f5aa..517313576 100644 --- a/src/tests/date.t.cpp +++ b/src/tests/date.t.cpp @@ -1,5 +1,27 @@ //////////////////////////////////////////////////////////////////////////////// -// Copyright 2005 - 2008, Paul Beckingham. All rights reserved. +// task - a command line task list manager. +// +// Copyright 2006 - 2009, 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 @@ -9,7 +31,7 @@ //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - plan (100); + UnitTest t (100); try { @@ -17,207 +39,207 @@ int main (int argc, char** argv) Date yesterday; yesterday -= 1; - ok (yesterday <= now, "yesterday <= now"); - ok (yesterday < now, "yesterday < now"); - notok (yesterday == now, "!(yesterday == now)"); - ok (yesterday != now, "yesterday != now"); - ok (now >= yesterday, "now >= yesterday"); - ok (now > yesterday, "now > yesterday"); + t.ok (yesterday <= now, "yesterday <= now"); + t.ok (yesterday < now, "yesterday < now"); + t.notok (yesterday == now, "!(yesterday == now)"); + t.ok (yesterday != now, "yesterday != now"); + t.ok (now >= yesterday, "now >= yesterday"); + t.ok (now > yesterday, "now > yesterday"); // Loose comparisons. Date left ("7/4/2008"); Date comp1 ("7/4/2008"); - ok (left.sameDay (comp1), "7/4/2008 is on the same day as 7/4/2008"); - ok (left.sameMonth (comp1), "7/4/2008 is in the same month as 7/4/2008"); - ok (left.sameYear (comp1), "7/4/2008 is in the same year as 7/4/2008"); + t.ok (left.sameDay (comp1), "7/4/2008 is on the same day as 7/4/2008"); + t.ok (left.sameMonth (comp1), "7/4/2008 is in the same month as 7/4/2008"); + t.ok (left.sameYear (comp1), "7/4/2008 is in the same year as 7/4/2008"); Date comp2 ("7/5/2008"); - notok (left.sameDay (comp2), "7/4/2008 is not on the same day as 7/5/2008"); - ok (left.sameMonth (comp2), "7/4/2008 is in the same month as 7/5/2008"); - ok (left.sameYear (comp2), "7/4/2008 is in the same year as 7/5/2008"); + t.notok (left.sameDay (comp2), "7/4/2008 is not on the same day as 7/5/2008"); + t.ok (left.sameMonth (comp2), "7/4/2008 is in the same month as 7/5/2008"); + t.ok (left.sameYear (comp2), "7/4/2008 is in the same year as 7/5/2008"); Date comp3 ("8/4/2008"); - notok (left.sameDay (comp3), "7/4/2008 is not on the same day as 8/4/2008"); - notok (left.sameMonth (comp3), "7/4/2008 is not in the same month as 8/4/2008"); - ok (left.sameYear (comp3), "7/4/2008 is in the same year as 8/4/2008"); + t.notok (left.sameDay (comp3), "7/4/2008 is not on the same day as 8/4/2008"); + t.notok (left.sameMonth (comp3), "7/4/2008 is not in the same month as 8/4/2008"); + t.ok (left.sameYear (comp3), "7/4/2008 is in the same year as 8/4/2008"); Date comp4 ("7/4/2009"); - notok (left.sameDay (comp4), "7/4/2008 is not on the same day as 7/4/2009"); - notok (left.sameMonth (comp4), "7/4/2008 is not in the same month as 7/4/2009"); - notok (left.sameYear (comp4), "7/4/2008 is not in the same year as 7/4/2009"); + t.notok (left.sameDay (comp4), "7/4/2008 is not on the same day as 7/4/2009"); + t.notok (left.sameMonth (comp4), "7/4/2008 is not in the same month as 7/4/2009"); + t.notok (left.sameYear (comp4), "7/4/2008 is not in the same year as 7/4/2009"); // Validity. - ok (Date::valid (2, 29, 2008), "valid: 2/29/2008"); - notok (Date::valid (2, 29, 2007), "invalid: 2/29/2007"); + t.ok (Date::valid (2, 29, 2008), "valid: 2/29/2008"); + t.notok (Date::valid (2, 29, 2007), "invalid: 2/29/2007"); // Leap year. - ok (Date::leapYear (2008), "2008 is a leap year"); - notok (Date::leapYear (2007), "2007 is not a leap year"); - ok (Date::leapYear (2000), "2000 is a leap year"); - ok (Date::leapYear (1900), "1900 is a leap year"); + t.ok (Date::leapYear (2008), "2008 is a leap year"); + t.notok (Date::leapYear (2007), "2007 is not a leap year"); + t.ok (Date::leapYear (2000), "2000 is a leap year"); + t.ok (Date::leapYear (1900), "1900 is a leap year"); // Days in month. - is (Date::daysInMonth (2, 2008), 29, "29 days in February 2008"); - is (Date::daysInMonth (2, 2007), 28, "28 days in February 2007"); + t.is (Date::daysInMonth (2, 2008), 29, "29 days in February 2008"); + t.is (Date::daysInMonth (2, 2007), 28, "28 days in February 2007"); // Names. - is (Date::monthName (1), "January", "1 = January"); - is (Date::monthName (2), "February", "2 = February"); - is (Date::monthName (3), "March", "3 = March"); - is (Date::monthName (4), "April", "4 = April"); - is (Date::monthName (5), "May", "5 = May"); - is (Date::monthName (6), "June", "6 = June"); - is (Date::monthName (7), "July", "7 = July"); - is (Date::monthName (8), "August", "8 = August"); - is (Date::monthName (9), "September", "9 = September"); - is (Date::monthName (10), "October", "10 = October"); - is (Date::monthName (11), "November", "11 = November"); - is (Date::monthName (12), "December", "12 = December"); + t.is (Date::monthName (1), "January", "1 = January"); + t.is (Date::monthName (2), "February", "2 = February"); + t.is (Date::monthName (3), "March", "3 = March"); + t.is (Date::monthName (4), "April", "4 = April"); + t.is (Date::monthName (5), "May", "5 = May"); + t.is (Date::monthName (6), "June", "6 = June"); + t.is (Date::monthName (7), "July", "7 = July"); + t.is (Date::monthName (8), "August", "8 = August"); + t.is (Date::monthName (9), "September", "9 = September"); + t.is (Date::monthName (10), "October", "10 = October"); + t.is (Date::monthName (11), "November", "11 = November"); + t.is (Date::monthName (12), "December", "12 = December"); - is (Date::dayName (0), "Sunday", "0 == Sunday"); - is (Date::dayName (1), "Monday", "1 == Monday"); - is (Date::dayName (2), "Tuesday", "2 == Tuesday"); - is (Date::dayName (3), "Wednesday", "3 == Wednesday"); - is (Date::dayName (4), "Thursday", "4 == Thursday"); - is (Date::dayName (5), "Friday", "5 == Friday"); - is (Date::dayName (6), "Saturday", "6 == Saturday"); + t.is (Date::dayName (0), "Sunday", "0 == Sunday"); + t.is (Date::dayName (1), "Monday", "1 == Monday"); + t.is (Date::dayName (2), "Tuesday", "2 == Tuesday"); + t.is (Date::dayName (3), "Wednesday", "3 == Wednesday"); + t.is (Date::dayName (4), "Thursday", "4 == Thursday"); + t.is (Date::dayName (5), "Friday", "5 == Friday"); + t.is (Date::dayName (6), "Saturday", "6 == Saturday"); - is (Date::dayOfWeek ("SUNDAY"), 0, "SUNDAY == 0"); - is (Date::dayOfWeek ("sunday"), 0, "sunday == 0"); - is (Date::dayOfWeek ("Sunday"), 0, "Sunday == 0"); - is (Date::dayOfWeek ("Monday"), 1, "Monday == 1"); - is (Date::dayOfWeek ("Tuesday"), 2, "Tuesday == 2"); - is (Date::dayOfWeek ("Wednesday"), 3, "Wednesday == 3"); - is (Date::dayOfWeek ("Thursday"), 4, "Thursday == 4"); - is (Date::dayOfWeek ("Friday"), 5, "Friday == 5"); - is (Date::dayOfWeek ("Saturday"), 6, "Saturday == 6"); + t.is (Date::dayOfWeek ("SUNDAY"), 0, "SUNDAY == 0"); + t.is (Date::dayOfWeek ("sunday"), 0, "sunday == 0"); + t.is (Date::dayOfWeek ("Sunday"), 0, "Sunday == 0"); + t.is (Date::dayOfWeek ("Monday"), 1, "Monday == 1"); + t.is (Date::dayOfWeek ("Tuesday"), 2, "Tuesday == 2"); + t.is (Date::dayOfWeek ("Wednesday"), 3, "Wednesday == 3"); + t.is (Date::dayOfWeek ("Thursday"), 4, "Thursday == 4"); + t.is (Date::dayOfWeek ("Friday"), 5, "Friday == 5"); + t.is (Date::dayOfWeek ("Saturday"), 6, "Saturday == 6"); Date happyNewYear (1, 1, 2008); - is (happyNewYear.dayOfWeek (), 2, "1/1/2008 == Tuesday"); - is (happyNewYear.month (), 1, "1/1/2008 == January"); - is (happyNewYear.day (), 1, "1/1/2008 == 1"); - is (happyNewYear.year (), 2008, "1/1/2008 == 2008"); + t.is (happyNewYear.dayOfWeek (), 2, "1/1/2008 == Tuesday"); + t.is (happyNewYear.month (), 1, "1/1/2008 == January"); + t.is (happyNewYear.day (), 1, "1/1/2008 == 1"); + t.is (happyNewYear.year (), 2008, "1/1/2008 == 2008"); - is (now - yesterday, 1, "today - yesterday == 1"); + t.is (now - yesterday, 1, "today - yesterday == 1"); - is (happyNewYear.toString (), "1/1/2008", "toString 1/1/2008"); + t.is (happyNewYear.toString (), "1/1/2008", "toString 1/1/2008"); int m, d, y; happyNewYear.toMDY (m, d, y); - is (m, 1, "1/1/2008 == January"); - is (d, 1, "1/1/2008 == 1"); - is (y, 2008, "1/1/2008 == 2008"); + t.is (m, 1, "1/1/2008 == January"); + t.is (d, 1, "1/1/2008 == 1"); + t.is (y, 2008, "1/1/2008 == 2008"); Date epoch (9, 8, 2001); - ok ((int)epoch.toEpoch () < 1000000000, "9/8/2001 < 1,000,000,000"); + t.ok ((int)epoch.toEpoch () < 1000000000, "9/8/2001 < 1,000,000,000"); epoch += 86400; - ok ((int)epoch.toEpoch () > 1000000000, "9/9/2001 > 1,000,000,000"); + t.ok ((int)epoch.toEpoch () > 1000000000, "9/9/2001 > 1,000,000,000"); Date fromEpoch (epoch.toEpoch ()); - is (fromEpoch.toString (), epoch.toString (), "ctor (time_t)"); + t.is (fromEpoch.toString (), epoch.toString (), "ctor (time_t)"); // Date parsing. Date fromString1 ("1/1/2008"); - is (fromString1.month (), 1, "ctor (std::string) -> m"); - is (fromString1.day (), 1, "ctor (std::string) -> d"); - is (fromString1.year (), 2008, "ctor (std::string) -> y"); + t.is (fromString1.month (), 1, "ctor (std::string) -> m"); + t.is (fromString1.day (), 1, "ctor (std::string) -> d"); + t.is (fromString1.year (), 2008, "ctor (std::string) -> y"); Date fromString2 ("1/1/2008", "m/d/Y"); - is (fromString2.month (), 1, "ctor (std::string) -> m"); - is (fromString2.day (), 1, "ctor (std::string) -> d"); - is (fromString2.year (), 2008, "ctor (std::string) -> y"); + t.is (fromString2.month (), 1, "ctor (std::string) -> m"); + t.is (fromString2.day (), 1, "ctor (std::string) -> d"); + t.is (fromString2.year (), 2008, "ctor (std::string) -> y"); Date fromString3 ("20080101", "YMD"); - is (fromString3.month (), 1, "ctor (std::string) -> m"); - is (fromString3.day (), 1, "ctor (std::string) -> d"); - is (fromString3.year (), 2008, "ctor (std::string) -> y"); + t.is (fromString3.month (), 1, "ctor (std::string) -> m"); + t.is (fromString3.day (), 1, "ctor (std::string) -> d"); + t.is (fromString3.year (), 2008, "ctor (std::string) -> y"); Date fromString4 ("12/31/2007"); - is (fromString4.month (), 12, "ctor (std::string) -> m"); - is (fromString4.day (), 31, "ctor (std::string) -> d"); - is (fromString4.year (), 2007, "ctor (std::string) -> y"); + t.is (fromString4.month (), 12, "ctor (std::string) -> m"); + t.is (fromString4.day (), 31, "ctor (std::string) -> d"); + t.is (fromString4.year (), 2007, "ctor (std::string) -> y"); Date fromString5 ("12/31/2007", "m/d/Y"); - is (fromString5.month (), 12, "ctor (std::string) -> m"); - is (fromString5.day (), 31, "ctor (std::string) -> d"); - is (fromString5.year (), 2007, "ctor (std::string) -> y"); + t.is (fromString5.month (), 12, "ctor (std::string) -> m"); + t.is (fromString5.day (), 31, "ctor (std::string) -> d"); + t.is (fromString5.year (), 2007, "ctor (std::string) -> y"); Date fromString6 ("20071231", "YMD"); - is (fromString6.month (), 12, "ctor (std::string) -> m"); - is (fromString6.day (), 31, "ctor (std::string) -> d"); - is (fromString6.year (), 2007, "ctor (std::string) -> y"); + t.is (fromString6.month (), 12, "ctor (std::string) -> m"); + t.is (fromString6.day (), 31, "ctor (std::string) -> d"); + t.is (fromString6.year (), 2007, "ctor (std::string) -> y"); Date fromString7 ("01/01/2008", "m/d/Y"); - is (fromString7.month (), 1, "ctor (std::string) -> m"); - is (fromString7.day (), 1, "ctor (std::string) -> d"); - is (fromString7.year (), 2008, "ctor (std::string) -> y"); + t.is (fromString7.month (), 1, "ctor (std::string) -> m"); + t.is (fromString7.day (), 1, "ctor (std::string) -> d"); + t.is (fromString7.year (), 2008, "ctor (std::string) -> y"); // Relative dates. Date r1 ("today"); - ok (r1.sameDay (now), "today = now"); + t.ok (r1.sameDay (now), "today = now"); Date r2 ("tomorrow"); - ok (r2.sameDay (now + 86400), "tomorrow = now + 1d"); + t.ok (r2.sameDay (now + 86400), "tomorrow = now + 1d"); Date r3 ("yesterday"); - ok (r3.sameDay (now - 86400), "yesterday = now - 1d"); + t.ok (r3.sameDay (now - 86400), "yesterday = now - 1d"); Date r4 ("sunday"); if (now.dayOfWeek () >= 0) - ok (r4.sameDay (now + (0 - now.dayOfWeek () + 7) * 86400), "next sunday"); + t.ok (r4.sameDay (now + (0 - now.dayOfWeek () + 7) * 86400), "next sunday"); else - ok (r4.sameDay (now + (0 - now.dayOfWeek ()) * 86400), "next sunday");; + t.ok (r4.sameDay (now + (0 - now.dayOfWeek ()) * 86400), "next sunday");; Date r5 ("monday"); if (now.dayOfWeek () >= 1) - ok (r5.sameDay (now + (1 - now.dayOfWeek () + 7) * 86400), "next monday"); + t.ok (r5.sameDay (now + (1 - now.dayOfWeek () + 7) * 86400), "next monday"); else - ok (r5.sameDay (now + (1 - now.dayOfWeek ()) * 86400), "next monday");; + t.ok (r5.sameDay (now + (1 - now.dayOfWeek ()) * 86400), "next monday");; Date r6 ("tuesday"); if (now.dayOfWeek () >= 2) - ok (r6.sameDay (now + (2 - now.dayOfWeek () + 7) * 86400), "next tuesday"); + t.ok (r6.sameDay (now + (2 - now.dayOfWeek () + 7) * 86400), "next tuesday"); else - ok (r6.sameDay (now + (2 - now.dayOfWeek ()) * 86400), "next tuesday");; + t.ok (r6.sameDay (now + (2 - now.dayOfWeek ()) * 86400), "next tuesday");; Date r7 ("wednesday"); if (now.dayOfWeek () >= 3) - ok (r7.sameDay (now + (3 - now.dayOfWeek () + 7) * 86400), "next wednesday"); + t.ok (r7.sameDay (now + (3 - now.dayOfWeek () + 7) * 86400), "next wednesday"); else - ok (r7.sameDay (now + (3 - now.dayOfWeek ()) * 86400), "next wednesday");; + t.ok (r7.sameDay (now + (3 - now.dayOfWeek ()) * 86400), "next wednesday");; Date r8 ("thursday"); if (now.dayOfWeek () >= 4) - ok (r8.sameDay (now + (4 - now.dayOfWeek () + 7) * 86400), "next thursday"); + t.ok (r8.sameDay (now + (4 - now.dayOfWeek () + 7) * 86400), "next thursday"); else - ok (r8.sameDay (now + (4 - now.dayOfWeek ()) * 86400), "next thursday");; + t.ok (r8.sameDay (now + (4 - now.dayOfWeek ()) * 86400), "next thursday");; Date r9 ("friday"); if (now.dayOfWeek () >= 5) - ok (r9.sameDay (now + (5 - now.dayOfWeek () + 7) * 86400), "next friday"); + t.ok (r9.sameDay (now + (5 - now.dayOfWeek () + 7) * 86400), "next friday"); else - ok (r9.sameDay (now + (5 - now.dayOfWeek ()) * 86400), "next friday");; + t.ok (r9.sameDay (now + (5 - now.dayOfWeek ()) * 86400), "next friday");; Date r10 ("saturday"); if (now.dayOfWeek () >= 6) - ok (r10.sameDay (now + (6 - now.dayOfWeek () + 7) * 86400), "next saturday"); + t.ok (r10.sameDay (now + (6 - now.dayOfWeek () + 7) * 86400), "next saturday"); else - ok (r10.sameDay (now + (6 - now.dayOfWeek ()) * 86400), "next saturday");; + t.ok (r10.sameDay (now + (6 - now.dayOfWeek ()) * 86400), "next saturday");; Date r11 ("eow"); - ok (r11 < now + (8 * 86400), "eow < 7 days away"); + t.ok (r11 < now + (8 * 86400), "eow < 7 days away"); Date r12 ("eom"); - ok (r12.sameMonth (now), "eom in same month as now"); + t.ok (r12.sameMonth (now), "eom in same month as now"); Date r13 ("eoy"); - ok (r13.sameYear (now), "eoy in same year as now"); + t.ok (r13.sameYear (now), "eoy in same year as now"); } catch (std::string& e) { - fail ("Exception thrown."); - diag (e); + t.fail ("Exception thrown."); + t.diag (e); } return 0; diff --git a/src/tests/duration.t.cpp b/src/tests/duration.t.cpp index 584b94458..a50b6a11d 100644 --- a/src/tests/duration.t.cpp +++ b/src/tests/duration.t.cpp @@ -1,5 +1,27 @@ //////////////////////////////////////////////////////////////////////////////// -// Copyright 2005 - 2008, Paul Beckingham. All rights reserved. +// task - a command line task list manager. +// +// Copyright 2006 - 2009, 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 @@ -16,27 +38,27 @@ // biannual, biyearly, annual, semiannual, yearly, Ny int main (int argc, char** argv) { - plan (17); + UnitTest t (17); std::string d; - d = "daily"; is (convertDuration (d), 1, "duration daily = 1"); - d = "day"; is (convertDuration (d), 1, "duration day = 1"); - d = "0d"; is (convertDuration (d), 0, "duration 0d = 0"); - d = "1d"; is (convertDuration (d), 1, "duration 1d = 1"); - d = "7d"; is (convertDuration (d), 7, "duration 7d = 7"); - d = "10d"; is (convertDuration (d), 10, "duration 10d = 10"); - d = "100d"; is (convertDuration (d), 100, "duration 100d = 100"); + d = "daily"; t.is (convertDuration (d), 1, "duration daily = 1"); + d = "day"; t.is (convertDuration (d), 1, "duration day = 1"); + d = "0d"; t.is (convertDuration (d), 0, "duration 0d = 0"); + d = "1d"; t.is (convertDuration (d), 1, "duration 1d = 1"); + d = "7d"; t.is (convertDuration (d), 7, "duration 7d = 7"); + d = "10d"; t.is (convertDuration (d), 10, "duration 10d = 10"); + d = "100d"; t.is (convertDuration (d), 100, "duration 100d = 100"); - d = "weekly"; is (convertDuration (d), 7, "duration weekly = 7"); - d = "sennight"; is (convertDuration (d), 7, "duration sennight = 7"); - d = "biweekly"; is (convertDuration (d), 14, "duration biweekly = 14"); - d = "fortnight"; is (convertDuration (d), 14, "duration fortnight = 14"); - d = "week"; is (convertDuration (d), 7, "duration week = 7"); - d = "0w"; is (convertDuration (d), 0, "duration 0w = 0"); - d = "1w"; is (convertDuration (d), 7, "duration 1w = 7"); - d = "7w"; is (convertDuration (d), 49, "duration 7w = 49"); - d = "10w"; is (convertDuration (d), 70, "duration 10w = 70"); - d = "100w"; is (convertDuration (d), 700, "duration 100w = 700"); + d = "weekly"; t.is (convertDuration (d), 7, "duration weekly = 7"); + d = "sennight"; t.is (convertDuration (d), 7, "duration sennight = 7"); + d = "biweekly"; t.is (convertDuration (d), 14, "duration biweekly = 14"); + d = "fortnight"; t.is (convertDuration (d), 14, "duration fortnight = 14"); + d = "week"; t.is (convertDuration (d), 7, "duration week = 7"); + d = "0w"; t.is (convertDuration (d), 0, "duration 0w = 0"); + d = "1w"; t.is (convertDuration (d), 7, "duration 1w = 7"); + d = "7w"; t.is (convertDuration (d), 49, "duration 7w = 49"); + d = "10w"; t.is (convertDuration (d), 70, "duration 10w = 70"); + d = "100w"; t.is (convertDuration (d), 700, "duration 100w = 700"); return 0; } diff --git a/src/tests/t.t.cpp b/src/tests/t.t.cpp index f8d38e23b..ae76b18c6 100644 --- a/src/tests/t.t.cpp +++ b/src/tests/t.t.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -31,34 +31,34 @@ //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - plan (5); + UnitTest test (5); T t; std::string s = t.compose (); - is ((int)s.length (), 46, "T::T (); T::compose ()"); - diag (s); + test.is ((int)s.length (), 46, "T::T (); T::compose ()"); + test.diag (s); t.setStatus (T::completed); s = t.compose (); - is (s[37], '+', "T::setStatus (completed)"); - diag (s); + test.is (s[37], '+', "T::setStatus (completed)"); + test.diag (s); t.setStatus (T::deleted); s = t.compose (); - is (s[37], 'X', "T::setStatus (deleted)"); - diag (s); + test.is (s[37], 'X', "T::setStatus (deleted)"); + test.diag (s); t.setStatus (T::recurring); s = t.compose (); - is (s[37], 'r', "T::setStatus (recurring)"); - diag (s); + test.is (s[37], 'r', "T::setStatus (recurring)"); + test.diag (s); // Round trip test. std::string sample = "00000000-0000-0000-0000-000000000000 - [] [] Sample"; T t2; t2.parse (sample); sample += "\n"; - is (t2.compose (), sample, "T::parse -> T::compose round trip"); + test.is (t2.compose (), sample, "T::parse -> T::compose round trip"); return 0; } diff --git a/src/tests/tdb.t.cpp b/src/tests/tdb.t.cpp index 1ec2160d6..129a0cdbf 100644 --- a/src/tests/tdb.t.cpp +++ b/src/tests/tdb.t.cpp @@ -34,7 +34,7 @@ //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - plan (43); + UnitTest t (38); try { @@ -46,14 +46,14 @@ int main (int argc, char** argv) TDB tdb; tdb.dataDirectory ("."); std::vector all; - ok (!tdb.pendingT (all), "TDB::pendingT read empty db"); - is ((int) all.size (), 0, "empty db"); - ok (!tdb.allPendingT (all), "TDB::allPendingT read empty db"); - is ((int) all.size (), 0, "empty db"); - ok (!tdb.completedT (all), "TDB::completedT read empty db"); - is ((int) all.size (), 0, "empty db"); - ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db"); - is ((int) all.size (), 0, "empty db"); + t.ok (!tdb.pendingT (all), "TDB::pendingT read empty db"); + t.is ((int) all.size (), 0, "empty db"); + t.ok (!tdb.allPendingT (all), "TDB::allPendingT read empty db"); + t.is ((int) all.size (), 0, "empty db"); + t.ok (!tdb.completedT (all), "TDB::completedT read empty db"); + t.is ((int) all.size (), 0, "empty db"); + t.ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db"); + t.is ((int) all.size (), 0, "empty db"); // Add a new task. T t1; @@ -61,76 +61,69 @@ int main (int argc, char** argv) t1.setStatus (T::pending); t1.setAttribute ("project", "p1"); t1.setDescription ("task 1"); - diag (t1.compose ()); - ok (tdb.addT (t1), "TDB::addT t1"); + t.diag (t1.compose ()); + t.ok (tdb.addT (t1), "TDB::addT t1"); // Verify as above. - ok (tdb.pendingT (all), "TDB::pendingT read db"); - is ((int) all.size (), 1, "empty db"); - ok (tdb.allPendingT (all), "TDB::allPendingT read db"); - is ((int) all.size (), 1, "empty db"); - ok (!tdb.completedT (all), "TDB::completedT read empty db"); - is ((int) all.size (), 0, "empty db"); - ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db"); - is ((int) all.size (), 0, "empty db"); + t.ok (tdb.pendingT (all), "TDB::pendingT read db"); + t.is ((int) all.size (), 1, "empty db"); + t.ok (tdb.allPendingT (all), "TDB::allPendingT read db"); + t.is ((int) all.size (), 1, "empty db"); + t.ok (!tdb.completedT (all), "TDB::completedT read empty db"); + t.is ((int) all.size (), 0, "empty db"); + t.ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db"); + t.is ((int) all.size (), 0, "empty db"); // TODO Modify task. - fail ("modify"); - fail ("verify"); // Complete task. - ok (tdb.completeT (t1), "TDB::completeT t1");; - ok (tdb.pendingT (all), "TDB::pendingT read db"); - is ((int) all.size (), 0, "empty db"); - ok (tdb.allPendingT (all), "TDB::allPendingT read db"); - is ((int) all.size (), 1, "empty db"); - ok (!tdb.completedT (all), "TDB::completedT read empty db"); - is ((int) all.size (), 0, "empty db"); - ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db"); - is ((int) all.size (), 0, "empty db"); + t.ok (tdb.completeT (t1), "TDB::completeT t1");; + t.ok (tdb.pendingT (all), "TDB::pendingT read db"); + t.is ((int) all.size (), 0, "empty db"); + t.ok (tdb.allPendingT (all), "TDB::allPendingT read db"); + t.is ((int) all.size (), 1, "empty db"); + t.ok (!tdb.completedT (all), "TDB::completedT read empty db"); + t.is ((int) all.size (), 0, "empty db"); + t.ok (!tdb.allCompletedT (all), "TDB::allCompletedT read empty db"); + t.is ((int) all.size (), 0, "empty db"); - is (tdb.gc (), 1, "TDB::gc"); - ok (tdb.pendingT (all), "TDB::pendingT read empty db"); - is ((int) all.size (), 0, "empty db"); - ok (tdb.allPendingT (all), "TDB::allPendingT read empty db"); - is ((int) all.size (), 0, "empty db"); - ok (tdb.completedT (all), "TDB::completedT read db"); - is ((int) all.size (), 1, "empty db"); - ok (tdb.allCompletedT (all), "TDB::allCompletedT read db"); - is ((int) all.size (), 1, "empty db"); + t.is (tdb.gc (), 1, "TDB::gc"); + t.ok (tdb.pendingT (all), "TDB::pendingT read empty db"); + t.is ((int) all.size (), 0, "empty db"); + t.ok (tdb.allPendingT (all), "TDB::allPendingT read empty db"); + t.is ((int) all.size (), 0, "empty db"); + t.ok (tdb.completedT (all), "TDB::completedT read db"); + t.is ((int) all.size (), 1, "empty db"); + t.ok (tdb.allCompletedT (all), "TDB::allCompletedT read db"); + t.is ((int) all.size (), 1, "empty db"); // Add a new task. T t2; t2.setId (2); t2.setAttribute ("project", "p2"); t2.setDescription ("task 2"); - diag (t2.compose ()); - ok (tdb.addT (t2), "TDB::addT t2"); - - fail ("verify"); + t.diag (t2.compose ()); + t.ok (tdb.addT (t2), "TDB::addT t2"); // Delete task. - ok (tdb.deleteT (t2), "TDB::deleteT t2"); - - fail ("verify"); + t.ok (tdb.deleteT (t2), "TDB::deleteT t2"); // GC the files. - is (tdb.gc (), 1, "1 <- TDB::gc"); + t.is (tdb.gc (), 1, "1 <- TDB::gc"); } catch (std::string& error) { - diag (error); + t.diag (error); return -1; } catch (...) { - diag ("Unknown error."); + t.diag ("Unknown error."); return -2; } - unlink ("./pending.data"); unlink ("./completed.data"); diff --git a/src/tests/test.cpp b/src/tests/test.cpp index 1937e9230..4c8f201b3 100644 --- a/src/tests/test.cpp +++ b/src/tests/test.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -25,81 +25,157 @@ // //////////////////////////////////////////////////////////////////////////////// #include +#include #include #include - -static int total = 0; -static int counter = 0; +#include "test.h" /////////////////////////////////////////////////////////////////////////////// -static void check (void) +UnitTest::UnitTest () +: mPlanned (0) +, mCounter (0) +, mPassed (0) +, mFailed (0) +, mSkipped (0) { - if (counter > total) - std::cout << "# Warning: There are more tests than planned." +} + +/////////////////////////////////////////////////////////////////////////////// +UnitTest::UnitTest (int planned) +: mPlanned (planned) +, mCounter (0) +, mPassed (0) +, mFailed (0) +, mSkipped (0) +{ + std::cout << "1.." << mPlanned << std::endl; +} + +/////////////////////////////////////////////////////////////////////////////// +UnitTest::~UnitTest () +{ + float percentPassed = 0.0; + if (mPlanned > 0) + percentPassed = (100.0 * mPassed) / max (mPlanned, mPassed + mFailed + mSkipped); + + if (mCounter < mPlanned) + { + std::cout << "# Only " + << mCounter + << " tests, out of a planned " + << mPlanned + << " were run." << std::endl; + mSkipped += mPlanned - mCounter; + } + + else if (mCounter > mPlanned) + std::cout << "# " + << mCounter + << " tests were run, but only " + << mPlanned + << " were planned." + << std::endl; + + std::cout << "# " + << mPassed + << " passed, " + << mFailed + << " failed, " + << mSkipped + << " skipped. " + << std::setprecision (3) << percentPassed + << "% passed." + << std::endl; } /////////////////////////////////////////////////////////////////////////////// -void plan (int quantity) +void UnitTest::plan (int planned) { - total = quantity; - std::cout << "1.." << quantity << std::endl; - check (); + mPlanned = planned; + mCounter = 0; + mPassed = 0; + mFailed = 0; + mSkipped = 0; + + std::cout << "1.." << mPlanned << std::endl; } /////////////////////////////////////////////////////////////////////////////// -void ok (bool expression, const std::string& name) +void UnitTest::planMore (int extra) { - ++counter; + mPlanned += extra; + std::cout << "1.." << mPlanned << std::endl; +} + +/////////////////////////////////////////////////////////////////////////////// +void UnitTest::ok (bool expression, const std::string& name) +{ + ++mCounter; if (expression) + { + ++mPassed; std::cout << "ok " - << counter + << mCounter << " - " << name << std::endl; + } else + { + ++mFailed; std::cout << "not ok " - << counter + << mCounter << " - " << name << std::endl; - check (); + } } /////////////////////////////////////////////////////////////////////////////// -void notok (bool expression, const std::string& name) +void UnitTest::notok (bool expression, const std::string& name) { - ++counter; + ++mCounter; if (!expression) + { + ++mPassed; std::cout << "ok " - << counter + << mCounter << " - " << name << std::endl; + } else + { + ++mFailed; std::cout << "not ok " - << counter + << mCounter << " - " << name << std::endl; - check (); + } } /////////////////////////////////////////////////////////////////////////////// -void is (bool actual, bool expected, const std::string& name) +void UnitTest::is (bool actual, bool expected, const std::string& name) { - ++counter; + ++mCounter; if (actual == expected) + { + ++mPassed; std::cout << "ok " - << counter + << mCounter << " - " << name << std::endl; + } else + { + ++mFailed; std::cout << "not ok " - << counter + << mCounter << " - " << name << std::endl @@ -109,22 +185,27 @@ void is (bool actual, bool expected, const std::string& name) << "# got: " << actual << std::endl; - check (); + } } /////////////////////////////////////////////////////////////////////////////// -void is (size_t actual, size_t expected, const std::string& name) +void UnitTest::is (size_t actual, size_t expected, const std::string& name) { - ++counter; + ++mCounter; if (actual == expected) + { + ++mPassed; std::cout << "ok " - << counter + << mCounter << " - " << name << std::endl; + } else + { + ++mFailed; std::cout << "not ok " - << counter + << mCounter << " - " << name << std::endl @@ -134,22 +215,27 @@ void is (size_t actual, size_t expected, const std::string& name) << "# got: " << actual << std::endl; - check (); + } } /////////////////////////////////////////////////////////////////////////////// -void is (int actual, int expected, const std::string& name) +void UnitTest::is (int actual, int expected, const std::string& name) { - ++counter; + ++mCounter; if (actual == expected) + { + ++mPassed; std::cout << "ok " - << counter + << mCounter << " - " << name << std::endl; + } else + { + ++mFailed; std::cout << "not ok " - << counter + << mCounter << " - " << name << std::endl @@ -159,22 +245,27 @@ void is (int actual, int expected, const std::string& name) << "# got: " << actual << std::endl; - check (); + } } /////////////////////////////////////////////////////////////////////////////// -void is (double actual, double expected, const std::string& name) +void UnitTest::is (double actual, double expected, const std::string& name) { - ++counter; + ++mCounter; if (actual == expected) + { + ++mPassed; std::cout << "ok " - << counter + << mCounter << " - " << name << std::endl; + } else + { + ++mFailed; std::cout << "not ok " - << counter + << mCounter << " - " << name << std::endl @@ -184,22 +275,27 @@ void is (double actual, double expected, const std::string& name) << "# got: " << actual << std::endl; - check (); + } } /////////////////////////////////////////////////////////////////////////////// -void is (char actual, char expected, const std::string& name) +void UnitTest::is (char actual, char expected, const std::string& name) { - ++counter; + ++mCounter; if (actual == expected) + { + ++mPassed; std::cout << "ok " - << counter + << mCounter << " - " << name << std::endl; + } else + { + ++mFailed; std::cout << "not ok " - << counter + << mCounter << " - " << name << std::endl @@ -209,25 +305,30 @@ void is (char actual, char expected, const std::string& name) << "# got: " << actual << std::endl; - check (); + } } /////////////////////////////////////////////////////////////////////////////// -void is ( +void UnitTest::is ( const std::string& actual, const std::string& expected, const std::string& name) { - ++counter; + ++mCounter; if (actual == expected) + { + ++mPassed; std::cout << "ok " - << counter + << mCounter << " - " << name << std::endl; + } else + { + ++mFailed; std::cout << "not ok " - << counter + << mCounter << " - " << name << std::endl @@ -239,11 +340,46 @@ void is ( << actual << "'" << std::endl; - check (); + } } /////////////////////////////////////////////////////////////////////////////// -void diag (const std::string& text) +void UnitTest::is ( + const char* actual, + const char* expected, + const std::string& name) +{ + ++mCounter; + if (! strcmp (actual, expected)) + { + ++mPassed; + std::cout << "ok " + << mCounter + << " - " + << name + << std::endl; + } + else + { + ++mFailed; + std::cout << "not ok " + << mCounter + << " - " + << name + << std::endl + << "# expected: '" + << expected + << "'" + << std::endl + << "# got: '" + << actual + << "'" + << std::endl; + } +} + +/////////////////////////////////////////////////////////////////////////////// +void UnitTest::diag (const std::string& text) { std::string trimmed = trim (text, " \t\n\r\f"); @@ -251,22 +387,36 @@ void diag (const std::string& text) } /////////////////////////////////////////////////////////////////////////////// -void pass (const std::string& text) +void UnitTest::pass (const std::string& text) { - ++counter; + ++mCounter; + ++mPassed; std::cout << "ok " - << counter + << mCounter << " " << text << std::endl; } /////////////////////////////////////////////////////////////////////////////// -void fail (const std::string& text) +void UnitTest::fail (const std::string& text) { - ++counter; + ++mCounter; + ++mFailed; std::cout << "not ok " - << counter + << mCounter + << " " + << text + << std::endl; +} + +/////////////////////////////////////////////////////////////////////////////// +void UnitTest::skip (const std::string& text) +{ + ++mCounter; + ++mSkipped; + std::cout << "skip " + << mCounter << " " << text << std::endl; diff --git a/src/tests/test.h b/src/tests/test.h index 36cfa2f67..b4bcf7f66 100644 --- a/src/tests/test.h +++ b/src/tests/test.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -24,23 +24,42 @@ // USA // //////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDED_TEST -#define INCLUDED_TEST +#ifndef INCLUDED_UNITTEST +#define INCLUDED_UNITTEST #include -void plan (int); -void ok (bool, const std::string&); -void notok (bool, const std::string&); -void is (bool, bool, const std::string&); -void is (int, int, const std::string&); -void is (size_t, size_t, const std::string&); -void is (double, double, const std::string&); -void is (char, char, const std::string&); -void is (const std::string&, const std::string&, const std::string&); -void diag (const std::string&); -void fail (const std::string&); -void pass (const std::string&); +class UnitTest +{ +public: + UnitTest (); + UnitTest (int); + ~UnitTest (); + + void plan (int); + void planMore (int); + void ok (bool, const std::string&); + void notok (bool, const std::string&); + void is (bool, bool, const std::string&); + void is (size_t, size_t, const std::string&); + void is (int, int, const std::string&); + void is (double, double, const std::string&); + void is (char, char, const std::string&); + void is (const std::string&, const std::string&, const std::string&); + void is (const char*, const char*, const std::string&); + void diag (const std::string&); + void pass (const std::string&); + void fail (const std::string&); + void skip (const std::string&); + +private: + int mPlanned; + int mCounter; + int mPassed; + int mFailed; + int mSkipped; +}; #endif + //////////////////////////////////////////////////////////////////////////////// From 2307dcab8a955f5ce63907319d4588358caa7c72 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Feb 2009 17:50:38 -0500 Subject: [PATCH 019/103] Copyright Update - bumped the year, on the source copyright notices. --- html/30second.html | 2 +- html/advanced.html | 2 +- html/color.html | 2 +- html/config.html | 2 +- html/date.html | 2 +- html/filter.html | 2 +- html/links.html | 2 +- html/recur.html | 2 +- html/setup.html | 2 +- html/shadow.html | 2 +- html/shell.html | 2 +- html/simple.html | 2 +- html/task.html | 2 +- html/troubleshooting.html | 2 +- html/usage.html | 2 +- html/versions.html | 2 +- src/Config.cpp | 2 +- src/Config.h | 2 +- src/Date.cpp | 2 +- src/Date.h | 2 +- src/Grid.cpp | 2 +- src/Grid.h | 2 +- src/T.cpp | 2 +- src/T.h | 2 +- src/TDB.cpp | 2 +- src/TDB.h | 2 +- src/Table.cpp | 2 +- src/Table.h | 2 +- src/color.cpp | 2 +- src/color.h | 2 +- src/command.cpp | 4 ++-- src/parse.cpp | 2 +- src/report.cpp | 2 +- src/rules.cpp | 2 +- src/task.cpp | 2 +- src/task.h | 2 +- src/tests/tdb.t.cpp | 2 +- src/text.cpp | 2 +- src/util.cpp | 2 +- 39 files changed, 40 insertions(+), 40 deletions(-) diff --git a/html/30second.html b/html/30second.html index 6cde1c6c2..e40d6814b 100644 --- a/html/30second.html +++ b/html/30second.html @@ -80,7 +80,7 @@ No matches

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/advanced.html b/html/advanced.html index 60a99893e..d3450e56b 100644 --- a/html/advanced.html +++ b/html/advanced.html @@ -398,7 +398,7 @@ on_white on_bright_white

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/color.html b/html/color.html index 2d939a2e6..32e18fd71 100644 --- a/html/color.html +++ b/html/color.html @@ -77,7 +77,7 @@ on_white on_bright_white

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/config.html b/html/config.html index 09add7bef..0b10b7224 100644 --- a/html/config.html +++ b/html/config.html @@ -332,7 +332,7 @@ ID Project Pri Description

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/date.html b/html/date.html index 9c02cd399..a06f291db 100644 --- a/html/date.html +++ b/html/date.html @@ -110,7 +110,7 @@

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/filter.html b/html/filter.html index 4f353bc44..04e3090b6 100644 --- a/html/filter.html +++ b/html/filter.html @@ -82,7 +82,7 @@

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/links.html b/html/links.html index c2739c586..817dbacd4 100644 --- a/html/links.html +++ b/html/links.html @@ -137,7 +137,7 @@

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/recur.html b/html/recur.html index 978c94cef..fd81b6aca 100644 --- a/html/recur.html +++ b/html/recur.html @@ -155,7 +155,7 @@ recurrences of this same task? (y/n) y

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/setup.html b/html/setup.html index 7030b3cbc..b6ae3d485 100644 --- a/html/setup.html +++ b/html/setup.html @@ -90,7 +90,7 @@ Done.

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/shadow.html b/html/shadow.html index 0fe592d36..6b66de763 100644 --- a/html/shadow.html +++ b/html/shadow.html @@ -75,7 +75,7 @@ shadow.command=list pri:H

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/shell.html b/html/shell.html index ad33441b4..cb3873dcf 100644 --- a/html/shell.html +++ b/html/shell.html @@ -80,7 +80,7 @@

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/simple.html b/html/simple.html index 9e7f0f88c..860421d6a 100644 --- a/html/simple.html +++ b/html/simple.html @@ -305,7 +305,7 @@ ID Project Pri Due Active Age Description

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/task.html b/html/task.html index 3a4bfd7f2..5bae3744c 100644 --- a/html/task.html +++ b/html/task.html @@ -143,7 +143,7 @@

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/troubleshooting.html b/html/troubleshooting.html index 1876c7c66..f175d56d2 100644 --- a/html/troubleshooting.html +++ b/html/troubleshooting.html @@ -87,7 +87,7 @@

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/usage.html b/html/usage.html index feadd5ddd..9970427c6 100644 --- a/html/usage.html +++ b/html/usage.html @@ -93,7 +93,7 @@ Many characters have special meaning to the shell, including:

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/html/versions.html b/html/versions.html index a3b304dc4..60403f682 100644 --- a/html/versions.html +++ b/html/versions.html @@ -250,7 +250,7 @@

    - Copyright 2006-2008, P. Beckingham. All rights reserved. + Copyright 2006-2009, P. Beckingham. All rights reserved.

    diff --git a/src/Config.cpp b/src/Config.cpp index deff2922d..e9f1201b2 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -1,7 +1,7 @@ /////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/Config.h b/src/Config.h index 4488ec468..0286ea950 100644 --- a/src/Config.h +++ b/src/Config.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/Date.cpp b/src/Date.cpp index 67ac7e7c7..995011345 100644 --- a/src/Date.cpp +++ b/src/Date.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/Date.h b/src/Date.h index ad3f659e0..e8538435d 100644 --- a/src/Date.h +++ b/src/Date.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/Grid.cpp b/src/Grid.cpp index faaa4e90f..a391aa75b 100644 --- a/src/Grid.cpp +++ b/src/Grid.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/Grid.h b/src/Grid.h index cf9ff8ecb..e1315e8c3 100644 --- a/src/Grid.h +++ b/src/Grid.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/T.cpp b/src/T.cpp index e243c1db6..a21b31638 100644 --- a/src/T.cpp +++ b/src/T.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/T.h b/src/T.h index e26d25b5f..fc126ae34 100644 --- a/src/T.h +++ b/src/T.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/TDB.cpp b/src/TDB.cpp index ba7b4eb2b..bce2d6e4e 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/TDB.h b/src/TDB.h index d335e909b..e654acc05 100644 --- a/src/TDB.h +++ b/src/TDB.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/Table.cpp b/src/Table.cpp index 6de729b4b..f62fb2eae 100644 --- a/src/Table.cpp +++ b/src/Table.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/Table.h b/src/Table.h index 6a793af0c..e1d244e14 100644 --- a/src/Table.h +++ b/src/Table.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/color.cpp b/src/color.cpp index 9de9b7942..6231dfe1f 100644 --- a/src/color.cpp +++ b/src/color.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/color.h b/src/color.h index 7c48d8e6a..d90b47c8d 100644 --- a/src/color.h +++ b/src/color.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/command.cpp b/src/command.cpp index fb97800d6..084125bfd 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under @@ -343,7 +343,7 @@ std::string handleVersion (Config& conf) } } - out << "Copyright (C) 2006 - 2008, P. Beckingham." + out << "Copyright (C) 2006 - 2009, P. Beckingham." << std::endl << (conf.get ("color", true) ? Text::colorize (Text::bold, Text::nocolor, PACKAGE) : PACKAGE) << " " diff --git a/src/parse.cpp b/src/parse.cpp index a2daea1fa..99f2d9187 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/report.cpp b/src/report.cpp index 4d8de003f..7b5fdcc46 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/rules.cpp b/src/rules.cpp index 4d9ccabe2..20f79ac5e 100644 --- a/src/rules.cpp +++ b/src/rules.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/task.cpp b/src/task.cpp index 53b8b098e..e222e2702 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/task.h b/src/task.h index c2f609d6d..d84d6210f 100644 --- a/src/task.h +++ b/src/task.h @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/tests/tdb.t.cpp b/src/tests/tdb.t.cpp index 129a0cdbf..3676f6517 100644 --- a/src/tests/tdb.t.cpp +++ b/src/tests/tdb.t.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/text.cpp b/src/text.cpp index 90fed8aa2..46ae3bbe4 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under diff --git a/src/util.cpp b/src/util.cpp index fc52bb7bf..fd73c3b39 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // task - a command line task list manager. // -// Copyright 2006 - 2008, Paul Beckingham. +// Copyright 2006 - 2009, Paul Beckingham. // All rights reserved. // // This program is free software; you can redistribute it and/or modify it under From 6faf1e44f53a4affc369d80462f68d97f3fec2de Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Feb 2009 20:04:34 -0500 Subject: [PATCH 020/103] Bug Fix - lower case priorities - Changed a call to isupper to islower. This was preventing the internal modification to upper case. - Updated ChangeLog accordingly. --- ChangeLog | 1 + src/text.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 109aaa601..535a46ca6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,7 @@ which may be spelling mistakes or deprecated variables. + "configure --enable-debug" now supported to suppress compiler optimization to allow debugging. + + Allow lower case priorities, and automatically upper case them. ------ old releases ------------------------------ diff --git a/src/text.cpp b/src/text.cpp index 46ae3bbe4..b459e2856 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -296,7 +296,7 @@ std::string upperCase (const std::string& input) { std::string output = input; for (int i = 0; i < (int) input.length (); ++i) - if (::isupper (input[i])) + if (::islower (input[i])) output[i] = ::toupper (input[i]); return output; From 01b3cb190ccd6e7ec362a8545564db648286f212 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Feb 2009 20:19:47 -0500 Subject: [PATCH 021/103] Configuration Variable - due - Added support for the "due" configuration variable that defines how many days into the future when a task is considered due. --- ChangeLog | 2 ++ html/config.html | 8 ++++++++ html/task.html | 3 +++ src/Config.cpp | 1 + src/report.cpp | 18 +++++++++--------- src/rules.cpp | 8 ++++++-- src/task.h | 2 +- 7 files changed, 30 insertions(+), 12 deletions(-) diff --git a/ChangeLog b/ChangeLog index 535a46ca6..be2fb45f4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,8 @@ + "configure --enable-debug" now supported to suppress compiler optimization to allow debugging. + Allow lower case priorities, and automatically upper case them. + + Added support for "due" configuration variable which defines the number + of days in the future when a task is considered due. ------ old releases ------------------------------ diff --git a/html/config.html b/html/config.html index 0b10b7224..fd4bc71a4 100644 --- a/html/config.html +++ b/html/config.html @@ -195,6 +195,14 @@ Defaults to 80. +
    due
    +
    + + This is the number of days into the future that define when a + task is considered due, and is colored accordingly. + Defaults to 7. +
    +
    color
    May be "on" or "off". Determines whether task uses color. diff --git a/html/task.html b/html/task.html index 5bae3744c..f222bf0fa 100644 --- a/html/task.html +++ b/html/task.html @@ -104,6 +104,9 @@ which may be spelling mistakes or deprecated variables.
  • "configure --enable-debug" now supported to suppress compiler optimization to allow debugging. +
  • Allow lower case priorities, and automatically upper case them. +
  • Added support for "due" configuration variable which defines the number + of days in the future when a task is considered due.

    diff --git a/src/Config.cpp b/src/Config.cpp index e9f1201b2..7e1eeadd4 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -118,6 +118,7 @@ void Config::createDefault (const std::string& home) fprintf (out, "monthsperline=1\n"); fprintf (out, "curses=on\n"); fprintf (out, "color=on\n"); + fprintf (out, "due=7\n"); fprintf (out, "color.overdue=bold_red\n"); fprintf (out, "#color.due=on_bright_yellow\n"); diff --git a/src/report.cpp b/src/report.cpp index 7b5fdcc46..ecc8081bd 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -258,7 +258,7 @@ std::string handleList (TDB& tdb, T& task, Config& conf) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg); + autoColorize (refTask, fg, bg, conf); table.setRowFg (row, fg); table.setRowBg (row, bg); @@ -390,7 +390,7 @@ std::string handleSmallList (TDB& tdb, T& task, Config& conf) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg); + autoColorize (refTask, fg, bg, conf); table.setRowFg (row, fg); table.setRowBg (row, bg); @@ -489,7 +489,7 @@ std::string handleCompleted (TDB& tdb, T& task, Config& conf) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg); + autoColorize (refTask, fg, bg, conf); table.setRowFg (row, fg); table.setRowBg (row, bg); } @@ -851,7 +851,7 @@ std::string handleLongList (TDB& tdb, T& task, Config& conf) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg); + autoColorize (refTask, fg, bg, conf); table.setRowFg (row, fg); table.setRowBg (row, bg); @@ -1180,7 +1180,7 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg); + autoColorize (refTask, fg, bg, conf); table.setRowFg (row, fg); table.setRowBg (row, bg); @@ -1962,7 +1962,7 @@ std::string handleReportActive (TDB& tdb, T& task, Config& conf) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg); + autoColorize (refTask, fg, bg, conf); table.setRowFg (row, fg); table.setRowBg (row, bg); @@ -2076,7 +2076,7 @@ std::string handleReportOverdue (TDB& tdb, T& task, Config& conf) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg); + autoColorize (refTask, fg, bg, conf); table.setRowFg (row, fg); table.setRowBg (row, bg); @@ -2220,7 +2220,7 @@ std::string handleReportOldest (TDB& tdb, T& task, Config& conf) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg); + autoColorize (refTask, fg, bg, conf); table.setRowFg (row, fg); table.setRowBg (row, bg); @@ -2368,7 +2368,7 @@ std::string handleReportNewest (TDB& tdb, T& task, Config& conf) { Text::color fg = Text::colorCode (refTask.getAttribute ("fg")); Text::color bg = Text::colorCode (refTask.getAttribute ("bg")); - autoColorize (refTask, fg, bg); + autoColorize (refTask, fg, bg, conf); table.setRowFg (row, fg); table.setRowBg (row, bg); diff --git a/src/rules.cpp b/src/rules.cpp index 20f79ac5e..f33354c9f 100644 --- a/src/rules.cpp +++ b/src/rules.cpp @@ -80,7 +80,11 @@ void initializeColorRules (Config& conf) } //////////////////////////////////////////////////////////////////////////////// -void autoColorize (T& task, Text::color& fg, Text::color& bg) +void autoColorize ( + T& task, + Text::color& fg, + Text::color& bg, + Config& conf) { // Note: fg, bg already contain colors specifically assigned via command. // Note: These rules form a hierarchy - the last rule is king. @@ -159,7 +163,7 @@ void autoColorize (T& task, Text::color& fg, Text::color& bg) { Date dueDate (::atoi (due.c_str ())); Date now; - Date then (now + 7 * 86400); + Date then (now + conf.get ("due", 7) * 86400); // Overdue if (dueDate < now) diff --git a/src/task.h b/src/task.h index d84d6210f..8c151cb83 100644 --- a/src/task.h +++ b/src/task.h @@ -141,6 +141,6 @@ std::string expandPath (const std::string&); // rules.cpp void initializeColorRules (Config&); -void autoColorize (T&, Text::color&, Text::color&); +void autoColorize (T&, Text::color&, Text::color&, Config&); //////////////////////////////////////////////////////////////////////////////// From e65a45ce17e73c72e7a9793a1d89026cf4c1124c Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Feb 2009 23:13:31 -0500 Subject: [PATCH 022/103] Bug Fix - Fixed bug in split functions, which was causing empty strings to be split into a single element list consisting of one empty string. The symptom was that all tasks without tags appeared to have one zero-length tag and the task was colored according to color.tagged. --- src/text.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/text.cpp b/src/text.cpp index b459e2856..f18fec8e4 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -54,6 +54,7 @@ void split ( const std::string& input, const char delimiter) { + results.clear (); std::string::size_type start = 0; std::string::size_type i; while ((i = input.find (delimiter, start)) != std::string::npos) @@ -62,7 +63,8 @@ void split ( start = i + 1; } - results.push_back (input.substr (start, std::string::npos)); + if (input.length ()) + results.push_back (input.substr (start, std::string::npos)); } //////////////////////////////////////////////////////////////////////////////// @@ -71,6 +73,7 @@ void split ( const std::string& input, const std::string& delimiter) { + results.clear (); std::string::size_type length = delimiter.length (); std::string::size_type start = 0; @@ -81,7 +84,8 @@ void split ( start = i + length; } - results.push_back (input.substr (start, std::string::npos)); + if (input.length ()) + results.push_back (input.substr (start, std::string::npos)); } //////////////////////////////////////////////////////////////////////////////// From 096a4b9bdb254bd987c00f9054800a6e37cda6ae Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Feb 2009 23:13:31 -0500 Subject: [PATCH 023/103] Bug Fix - split - Fixed bug in split functions, which was causing empty strings to be split into a single element list consisting of one empty string. The symptom was that all tasks without tags appeared to have one zero-length tag and the task was colored according to color.tagged. --- src/text.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/text.cpp b/src/text.cpp index b459e2856..f18fec8e4 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -54,6 +54,7 @@ void split ( const std::string& input, const char delimiter) { + results.clear (); std::string::size_type start = 0; std::string::size_type i; while ((i = input.find (delimiter, start)) != std::string::npos) @@ -62,7 +63,8 @@ void split ( start = i + 1; } - results.push_back (input.substr (start, std::string::npos)); + if (input.length ()) + results.push_back (input.substr (start, std::string::npos)); } //////////////////////////////////////////////////////////////////////////////// @@ -71,6 +73,7 @@ void split ( const std::string& input, const std::string& delimiter) { + results.clear (); std::string::size_type length = delimiter.length (); std::string::size_type start = 0; @@ -81,7 +84,8 @@ void split ( start = i + length; } - results.push_back (input.substr (start, std::string::npos)); + if (input.length ()) + results.push_back (input.substr (start, std::string::npos)); } //////////////////////////////////////////////////////////////////////////////// From 6764a6a7ec18c62167a03ab5223c16f7ab508987 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Feb 2009 14:54:54 -0500 Subject: [PATCH 024/103] Custom Reports - basic implementation - Custom reports can be defined and run. - Custom columns included. - Custom filter applied. - Custom sorting applied. --- src/report.cpp | 279 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 268 insertions(+), 11 deletions(-) diff --git a/src/report.cpp b/src/report.cpp index ecc8081bd..7a1fdc688 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -2673,6 +2673,8 @@ void gatherNextTasks ( } //////////////////////////////////////////////////////////////////////////////// +// This report will eventually become the one report that many others morph into +// via the .taskrc file. std::string handleCustomReport ( TDB& tdb, T& task, @@ -2699,30 +2701,285 @@ std::string handleCustomReport ( std::vector sortOrder; split (sortOrder, sortList, ','); - std::string filter = conf.get ("report." + report + ".filter"); + std::string filterList = conf.get ("report." + report + ".filter"); std::cout << "# columns " << columnList << std::endl << "# sort " << sortList << std::endl - << "# filter " << filter << std::endl; + << "# filter " << filterList << std::endl; - // TODO Load pending tasks. + // Load all pending tasks. std::vector tasks; - tdb.allT (tasks); -// filter (tasks, task); + tdb.allPendingT (tasks); - // TODO Apply filters. + // Apply filters. + { + std::vector args; + split (args, filterList, ' '); + + std::string ignore; + T filterTask; + parse (args, ignore, filterTask, conf); + + filter (tasks, filterTask); + } + + // Initialize colorization for subsequent auto colorization. + initializeColorRules (conf); Table table; table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); + for (unsigned int i = 0; i < tasks.size (); ++i) + table.addRow (); + + int columnCount = 0; + int dueColumn = -1; foreach (col, columns) { - // TODO Add column. - // TODO Add underline. - - // TODO Add data. - foreach (t, tasks) + // Add each column individually. + if (*col == "id") { + table.addColumn ("ID"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + for (unsigned int row = 0; row < tasks.size(); ++row) + table.addCell (row, columnCount, tasks[row].getId ()); + } + + else if (*col == "uuid") + { + table.addColumn ("UUID"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + for (unsigned int row = 0; row < tasks.size(); ++row) + table.addCell (row, columnCount, tasks[row].getUUID ()); + } + + else if (*col == "project") + { + table.addColumn ("Project"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + for (unsigned int row = 0; row < tasks.size(); ++row) + table.addCell (row, columnCount, tasks[row].getAttribute ("project")); + } + + else if (*col == "priority") + { + table.addColumn ("Pri"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + for (unsigned int row = 0; row < tasks.size(); ++row) + table.addCell (row, columnCount, tasks[row].getAttribute ("priority")); + } + + else if (*col == "entry") + { + table.addColumn ("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].getAttribute ("entry"); + if (entered.length ()) + { + Date dt (::atoi (entered.c_str ())); + entered = dt.toString (conf.get ("dateformat", "m/d/Y")); + table.addCell (row, columnCount, entered); + } + } + } + + else if (*col == "start") + { + table.addColumn ("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].getAttribute ("start"); + if (started.length ()) + { + Date dt (::atoi (started.c_str ())); + started = dt.toString (conf.get ("dateformat", "m/d/Y")); + table.addCell (row, columnCount, started); + } + } + } + + else if (*col == "due") + { + table.addColumn ("Due"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + std::string due; + for (unsigned int row = 0; row < tasks.size(); ++row) + { + due = tasks[row].getAttribute ("due"); + if (due.length ()) + { + Date dt (::atoi (due.c_str ())); + due = dt.toString (conf.get ("dateformat", "m/d/Y")); + table.addCell (row, columnCount, due); + } + } + + dueColumn = columnCount; + } + + else if (*col == "age") + { + table.addColumn ("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].getAttribute ("entry"); + if (created.length ()) + { + Date dt (::atoi (created.c_str ())); + formatTimeDeltaDays (age, (time_t) (now - dt)); + table.addCell (row, columnCount, age); + } + } + } + + else if (*col == "active") + { + table.addColumn ("Active"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + for (unsigned int row = 0; row < tasks.size(); ++row) + if (tasks[row].getAttribute ("start") != "") + table.addCell (row, columnCount, "*"); + } + + else if (*col == "tags") + { + table.addColumn ("Tags"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::left); + + std::vector all; + std::string tags; + for (unsigned int row = 0; row < tasks.size(); ++row) + { + tasks[row].getTags (all); + join (tags, " ", all); + table.addCell (row, columnCount, tags); + } + } + + else if (*col == "description") + { + table.addColumn ("Description"); + table.setColumnWidth (columnCount, Table::flexible); + table.setColumnJustification (columnCount, Table::left); + + for (unsigned int row = 0; row < tasks.size(); ++row) + table.addCell (row, columnCount, tasks[row].getDescription ()); + } + + // Common to all columns. + // Add underline. + if (conf.get (std::string ("color"), true)) + 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]; + + if (column == "id") + 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 == "due") + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::ascendingDate : + Table::descendingDate)); + + else + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::ascendingCharacter : + Table::descendingCharacter)); + } + + // Now auto colorize all rows. + std::string due; + bool imminent; + bool overdue; + for (unsigned int row = 0; row < tasks.size (); ++row) + { + imminent = false; + overdue = false; + due = tasks[row].getAttribute ("due"); + if (due.length ()) + { + switch (getDueState (due)) + { + case 2: overdue = true; break; + case 1: imminent = true; break; + case 0: + default: break; + } + } + + if (conf.get ("color", true)) + { + Text::color fg = Text::colorCode (tasks[row].getAttribute ("fg")); + Text::color bg = Text::colorCode (tasks[row].getAttribute ("bg")); + autoColorize (tasks[row], fg, bg, conf); + table.setRowFg (row, fg); + table.setRowBg (row, bg); + + if (fg == Text::nocolor) + { + if (dueColumn != -1) + { + if (overdue) + table.setCellFg (row, columnCount, Text::colorCode (conf.get ("color.overdue", "red"))); + else if (imminent) + table.setCellFg (row, columnCount, Text::colorCode (conf.get ("color.due", "yellow"))); + } + } } } From 481a0aa1ebf1133803820eed5ac6c0d1f1546105 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Feb 2009 15:13:24 -0500 Subject: [PATCH 025/103] Custom Reports - old reports removed --- src/report.cpp | 464 +------------------------------------------------ src/task.cpp | 3 - src/task.h | 3 - 3 files changed, 5 insertions(+), 465 deletions(-) diff --git a/src/report.cpp b/src/report.cpp index 7a1fdc688..740a88712 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -139,285 +139,6 @@ void filter (std::vector& all, T& task) all = filtered; } -//////////////////////////////////////////////////////////////////////////////// -// Successively apply filters based on the task object built from the command -// line. Tasks that match all the specified criteria are listed. -std::string handleList (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); - - // 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 (std::string ("color"), true)) - { - 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 < 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); - table.addCell (row, 5, age); - table.addCell (row, 6, 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, 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. Show a narrow -// list that works better on mobile devices. -std::string handleSmallList (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); - - // 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); - } - else - table.setTableDashedUnderline (); - - 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, 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. @@ -707,177 +428,6 @@ std::string handleInfo (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 handleLongList (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 all the tasks. - 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 ("Entry"); - table.addColumn ("Start"); - table.addColumn ("Due"); - 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); - table.setColumnUnderline (8); - } - 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::minimum); - table.setColumnWidth (7, Table::minimum); - table.setColumnWidth (8, Table::flexible); - - table.setColumnJustification (0, Table::right); - table.setColumnJustification (3, Table::right); - table.setColumnJustification (4, Table::right); - table.setColumnJustification (5, Table::right); - 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); - table.addCell (row, 6, age); - table.addCell (row, 7, tags); - table.addCell (row, 8, 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, 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 (); -} - //////////////////////////////////////////////////////////////////////////////// // Project Tasks Avg Age Status // A 12 13d XXXXXXXX------ @@ -2702,10 +2252,8 @@ std::string handleCustomReport ( split (sortOrder, sortList, ','); std::string filterList = conf.get ("report." + report + ".filter"); - - std::cout << "# columns " << columnList << std::endl - << "# sort " << sortList << std::endl - << "# filter " << filterList << std::endl; + std::vector filterArgs; + split (filterArgs, filterList, ' '); // Load all pending tasks. std::vector tasks; @@ -2713,14 +2261,12 @@ std::string handleCustomReport ( // Apply filters. { - std::vector args; - split (args, filterList, ' '); - std::string ignore; T filterTask; - parse (args, ignore, filterTask, conf); + parse (filterArgs, ignore, filterTask, conf); - filter (tasks, filterTask); + filter (tasks, filterTask); // Filter from custom report + filter (tasks, task); // Filter from command line } // Initialize colorization for subsequent auto colorization. diff --git a/src/task.cpp b/src/task.cpp index e222e2702..84a74b24c 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -784,9 +784,6 @@ std::string runTaskCommand ( else if (command == "stop") { out = handleStop (tdb, task, conf ); } else if (command == "undo") { out = handleUndo (tdb, task, conf ); } else if (command == "stats") { out = handleReportStats (tdb, task, conf ); } - else if (command == "list") { if (gc) tdb.gc (); out = handleList (tdb, task, conf ); } // TODO replace with Custom - else if (command == "long") { if (gc) tdb.gc (); out = handleLongList (tdb, task, conf ); } // TODO replace with Custom - else if (command == "ls") { if (gc) tdb.gc (); out = handleSmallList (tdb, task, conf ); } // TODO replace with Custom else if (command == "completed") { if (gc) tdb.gc (); out = handleCompleted (tdb, task, conf ); } // TODO replace with Custom else if (command == "summary") { if (gc) tdb.gc (); out = handleReportSummary (tdb, task, conf ); } else if (command == "next") { if (gc) tdb.gc (); out = handleReportNext (tdb, task, conf ); } // TODO replace with Custom diff --git a/src/task.h b/src/task.h index 8c151cb83..57b19f652 100644 --- a/src/task.h +++ b/src/task.h @@ -89,10 +89,7 @@ std::string handleColor (Config&); // report.cpp void filter (std::vector&, T&); -std::string handleList (TDB&, T&, Config&); std::string handleInfo (TDB&, T&, Config&); -std::string handleLongList (TDB&, T&, Config&); -std::string handleSmallList (TDB&, T&, Config&); std::string handleCompleted (TDB&, T&, Config&); std::string handleReportSummary (TDB&, T&, Config&); std::string handleReportNext (TDB&, T&, Config&); From 4e63d93005757e2009ce63fac2dcc66db8243545 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Feb 2009 16:54:59 -0500 Subject: [PATCH 026/103] Documentation Update - Added commit ids to ChangeLog - Added tags to respective commit ids --- ChangeLog | 30 +++++++++++++++++------------- html/task.html | 4 ++++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/ChangeLog b/ChangeLog index be2fb45f4..41018682f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,10 +14,14 @@ + Allow lower case priorities, and automatically upper case them. + Added support for "due" configuration variable which defines the number of days in the future when a task is considered due. + + 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. ------ old releases ------------------------------ -1.4.3 (11/1/2008) +1.4.3 (11/1/2008) 8639e9260646c8c9224e0fc47e5d2443b46eecfc + Fixed misleading task count at bottom on "info" report. + Added support for a shadow file that contains a plain text task report, with the "shadow.file" and "shadow.command" configuration variables @@ -31,7 +35,7 @@ + Added documentation for Shadow files. + Added documentation for task filters. -1.4.2 (9/18/2008) +1.4.2 (9/18/2008) e7304e86ce9bb80978c7055fd2a9e999619a6fb8 + "task undo" can now retract a "task done" command, provided no reports have been run (and therefore TDB::gc run) + Task now correctly sorts on entire strings, instead of just the first @@ -54,13 +58,13 @@ + Bug: Source now properly includes in order to build clean using gcc 4.3 (thanks to H. İbrahim Güngör) -1.4.1 (7/18/2008) +1.4.1 (7/18/2008) e080c3168c6064628ab85b21bd859d9875a3a9a7 + Bug: Descriptions can not be altered with "task 123 New description" + Tweak: For "task calendar" month names are now centered over the month + Removed TUTORIAL file contents in favor of online version + Provided Mac .pkg binary -1.4.0 (7/10/2008) +1.4.0 (7/10/2008) 60b7d15a1d22e064acf0974c5d7eabbb57dd8071 + New recurring tasks feature + "task undelete" can now undelete erroneously deleted tasks, provided no reports have been run (and therefore TDB::gc run) @@ -78,7 +82,7 @@ + Bug: Adding a blank priority resulted in an assigned garbage value + Bug: Fixed parsing of date "07/08/2008" when using dateformat "m/d/Y" -1.3.1 (6/21/2008) +1.3.1 (6/21/2008) 3a6de7d9402f2609a773a73b16eff97b14a32869 + New configuration variable, "defaultwidth" that determines the width of tables when ncurses support is not available + Bug: "showage" configuration variable should apply to all reports, not @@ -89,7 +93,7 @@ + Bug: Task now will recreate a missing ~/.taskrc file, OR a missing ~/.task directory -1.3.0 (6/18/2008) +1.3.0 (6/18/2008) 6673e408a223af98c38779c20b08524042c0edfa + "task calendar" now displays multiple months per line, adjustable by the "monthsperline" configuration variable. Feature added by Damian Glenny + "task export" can now filter tasks like the reports @@ -104,7 +108,7 @@ days gets added to the entry date of task 2..n + Bug: Fixed bug whereby "1 wks" was being improperly pluralized -1.2.0 (6/13/2008) +1.2.0 (6/13/2008) c393d47cdfe7e197a31e94f4bb764474fa05ad8d + Bug: "dateformat" configuration variable used to display dates, but not parse them + "task list x" now performs a caseless comparison between "x" and the @@ -114,7 +118,7 @@ "list" and "next" reports + Improved TUTORIAL -1.1.0 (6/7/2008) +1.1.0 (6/7/2008) 73286e86628725b346db2a25fbcd4bd68efb9b3a + "blanklines" configuration to stop displaying unnecessary white space and thus work better on small-screen devices + "dateformat" configuration now determines how dates are formatted @@ -122,11 +126,11 @@ + http://www.beckingham.net/task.html home page set up + Added tags to the "task long" report -1.0.1 (6/4/2008) +1.0.1 (6/4/2008) d216d401217027d93581808fc8944ab7d6b85fb0 + Bug: UUID generator not properly terminating string. + Bug: srandom/srand not called prior to UUID generation. -1.0.0 (6/3/2008) +1.0.0 (6/3/2008) f3de5c07118c597091a05c7d7fe8bdeae95474c1 + New movie made, uploaded + Bug: assertion fails on mobile for t v + Bug: configure.ac does not properly determine ncurses availability @@ -139,19 +143,19 @@ + Added rules for colorization by tag, project and keyword + Added legend to "task calendar" -0.9.9 (5/27/2008) +0.9.9 (5/27/2008) 2ecf50032226c91b406f247417a063dc17c8e324 + Autoconf/automake behaving properly. + Clean build on OS X 10.5. + Clean build on Ubuntu 8.0. + Clean build on Fedora Core 8. + Clean build on Fedora Core 9. -0.9.8 (5/25/2008) +0.9.8 (5/25/2008) 18fd59a1edb20e5c68d086a97fae5fa9f6bb348a + Added "task color" command. + Removed unnecessary files. + Completed documentation. -0.9.7 (5/24/2008) +0.9.7 (5/24/2008) 25dc4150947a3e612c8118838d04b3bbe68441f7 + Migrated old compiler flags into Makefile.am + Added ncurses endwin function check to configure.ac + Set up structure for AUTHORS file. diff --git a/html/task.html b/html/task.html index f222bf0fa..7e6e77a03 100644 --- a/html/task.html +++ b/html/task.html @@ -107,6 +107,10 @@

  • Allow lower case priorities, and automatically upper case them.
  • Added support for "due" configuration variable which defines the number of days in the future when a task is considered due. +
  • 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.

    From 1a4469d38839ad37f66f814d7ce2bf216cac40a9 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Feb 2009 22:33:18 -0500 Subject: [PATCH 027/103] Error handling - Validates specified columns in custom reports against list of good column names. - Validates list of sort columns in custom reports against list of specified column names. - Minor fix to grammar file. --- grammar.bnf | 6 +++--- src/report.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/task.h | 2 ++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/grammar.bnf b/grammar.bnf index 18c082005..ba82df08e 100644 --- a/grammar.bnf +++ b/grammar.bnf @@ -8,7 +8,7 @@ command ::= simple_command | id_command | "export" file | - | + | ; simple_command ::= "version" | "help" | "projects" | "tags" | "next" | "stats" | "color" ; @@ -27,8 +27,8 @@ filter_part ::= tag_add | tag_remove | attribute | word ; tag_add ::= "+" word ; tag_remove ::= "-" word ; attribute ::= word ":" word ; -word ::= ... -file ::= ... +word ::= +file ::= id ::= digit+ ; digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; substitution ::= "/" word+ "/" word* "/" ; diff --git a/src/report.cpp b/src/report.cpp index 740a88712..8c4c1664d 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -2246,10 +2246,12 @@ std::string handleCustomReport ( std::string columnList = conf.get ("report." + report + ".columns"); std::vector columns; split (columns, columnList, ','); + validReportColumns (columns); std::string sortList = conf.get ("report." + report + ".sort"); std::vector sortOrder; split (sortOrder, sortList, ','); + validSortColumns (columns, sortOrder); std::string filterList = conf.get ("report." + report + ".filter"); std::vector filterArgs; @@ -2545,3 +2547,57 @@ std::string handleCustomReport ( } //////////////////////////////////////////////////////////////////////////////// +void validReportColumns (const std::vector & columns) +{ + std::vector bad; + + std::vector ::const_iterator it; + for (it = columns.begin (); it != columns.end (); ++it) + if (*it != "id" && + *it != "uuid" && + *it != "project" && + *it != "priority" && + *it != "entry" && + *it != "start" && + *it != "due" && + *it != "age" && + *it != "active" && + *it != "tags" && + *it != "description") + bad.push_back (*it); + + if (bad.size ()) + { + std::string error; + join (error, ", ", bad); + throw std::string ("Unrecognized column name: ") + error; + } +} + +//////////////////////////////////////////////////////////////////////////////// +void validSortColumns ( + const std::vector & columns, + const std::vector & sortColumns) +{ + std::vector bad; + std::vector ::const_iterator sc; + for (sc = sortColumns.begin (); sc != sortColumns.end (); ++sc) + { + std::vector ::const_iterator co; + for (co = columns.begin (); co != columns.end (); ++co) + if (sc->substr (0, sc->length () - 1) == *co) + break; + + if (co == columns.end ()) + bad.push_back (*sc); + } + + if (bad.size ()) + { + std::string error; + join (error, ", ", bad); + throw std::string ("Sort column is not part of the report: ") + error; + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/task.h b/src/task.h index 57b19f652..151794c7d 100644 --- a/src/task.h +++ b/src/task.h @@ -103,6 +103,8 @@ 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 &); +void validSortColumns (const std::vector &, const std::vector &); // util.cpp bool confirm (const std::string&); From cc7c1819cedb83005b7f39d865ac5932cbb757f8 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Feb 2009 22:45:50 -0500 Subject: [PATCH 028/103] Sample .taskrc - update - Added recent .taskrc file changes to the default file that is created when task is run the first time. --- src/Config.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Config.cpp b/src/Config.cpp index 7e1eeadd4..f63a58700 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -115,25 +115,42 @@ void Config::createDefault (const std::string& home) fprintf (out, "confirmation=yes\n"); fprintf (out, "next=2\n"); fprintf (out, "dateformat=m/d/Y\n"); - fprintf (out, "monthsperline=1\n"); + fprintf (out, "monthsperline=2\n"); fprintf (out, "curses=on\n"); fprintf (out, "color=on\n"); fprintf (out, "due=7\n"); + fprintf (out, "nag=You have higher priority tasks.\n"); fprintf (out, "color.overdue=bold_red\n"); - fprintf (out, "#color.due=on_bright_yellow\n"); - fprintf (out, "#color.pri.H=on_red\n"); + fprintf (out, "color.due=bold_yellow\n"); + fprintf (out, "color.pri.H=bold\n"); fprintf (out, "#color.pri.M=on_yellow\n"); fprintf (out, "#color.pri.L=on_green\n"); + fprintf (out, "#color.pri.none=white on_blue\n"); fprintf (out, "color.active=bold_cyan\n"); fprintf (out, "color.tagged=yellow\n"); fprintf (out, "#color.tag.bug=yellow\n"); - fprintf (out, "#color.project.home=on_green\n"); + fprintf (out, "#color.project.garden=on_green\n"); fprintf (out, "#color.keyword.car=on_blue\n"); fprintf (out, "#shadow.file=%s/shadow.txt\n", dataDir.c_str ()); fprintf (out, "#shadow.command=list\n"); fprintf (out, "#shadow.notify=on\n"); - fprintf (out, "#default.command=list\n"); + fprintf (out, "#default.project=foo\n"); + fprintf (out, "#default.priority=M\n"); + 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"); fclose (out); From 0219ed4fe3c4ce8cd1feaf6081e59fb93c4d7e08 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Feb 2009 23:26:15 -0500 Subject: [PATCH 029/103] Packaging - Added README.1.5.0 detailing the new custom report configuration variables that must be added. - Added README.1.5.0 to the distribution. - Added new custom.html documentation. - Added warning to task.html about the README.1.5.0 changes. --- Makefile.am | 2 +- README.1.5.0 | 15 +++++ html/custom.html | 152 +++++++++++++++++++++++++++++++++++++++++++++++ html/task.html | 16 +++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 README.1.5.0 create mode 100644 html/custom.html diff --git a/Makefile.am b/Makefile.am index fb05a3a4a..549946a75 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ SUBDIRS = src -EXTRA_DIST = DEVELOPERS +EXTRA_DIST = DEVELOPERS README.1.5.0 diff --git a/README.1.5.0 b/README.1.5.0 new file mode 100644 index 000000000..23dc8bd5d --- /dev/null +++ b/README.1.5.0 @@ -0,0 +1,15 @@ +Task 1.5.0 has a custom reports feature. Three of the existing task reports +are no longer implemented in task, and need to be added as custom reports. +Simply copy the following six lines into your existing .taskrc file. + +New task users need not do this - task will create an initial .taskrc file +on first startup. + + +report.long.columns=id,project,priority,entry,start,due,age,tags,description +report.long.sort=due+,priority-,project+ +report.list.columns=id,project,priority,due,active,age,description +report.list.sort=due+,priority-,project+ +report.ls.columns=id,project,priority,description +report.ls.sort=priority-,project+ + diff --git a/html/custom.html b/html/custom.html new file mode 100644 index 000000000..7a970e58e --- /dev/null +++ b/html/custom.html @@ -0,0 +1,152 @@ + + + + Custom Reports + + + + + +

    + + + + + + +
    + + +
    +
    +
    +
    +

    Custom Reports

    +
    +

    + 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. +

    + +

    + More importantly, you can define your own. Here are the + two necessary items in the .taskrc file that define a new + report: +

    + +
    report.mine.columns=id,project,priority,description
    +report.mine.sort=priority-,project+
    + +

    + This defines a report, called "mine", that has four columns: + id, project, priority and description. It will be sorted on + two columns: by descending priority then ascending project. + Because this report is called "mine", it can be run with the + command: +

    + +
    % task mine
    + +

    + A filter can also be specified like this: +

    + +
    report.mine.filter=priority:H +bug
    + +

    + This adds a filter so that only tasks with priority "H" and + with the "bug" tag are included in the report. This filter + definition is optional. +

    + +

    + Here is a list of all the possible columns that may be included + in a report: +

    + +
      +
    • id +
    • uuid +
    • project +
    • priority +
    • entry +
    • start +
    • due +
    • age +
    • active +
    • tags +
    • description +
    + +

    + Custom reports will show up in the task command line usage. +

    + +
    + +
    +
    +
    +

    + Copyright 2006-2009, P. Beckingham. All rights reserved. +

    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + +
    + +
    + + + + + + + diff --git a/html/task.html b/html/task.html index 7e6e77a03..696a0a6b5 100644 --- a/html/task.html +++ b/html/task.html @@ -57,6 +57,7 @@
  • Old Versions
  • Filters
  • Shadow Files +
  • Custom Reports

    @@ -113,6 +114,21 @@ in the configuration file can be renamed. +

    + Note that users of task prior to version 1.5.0 will need to add + the following six lines to their .taskrc file. See also the + README.1.5.0 file in the distribution for more details. +

    + +
    report.long.columns=id,project,priority,entry,start,due,age,tags,description
    +report.long.sort=due+,priority-,project+
    +
    +report.list.columns=id,project,priority,due,active,age,description
    +report.list.sort=due+,priority-,project+
    +
    +report.ls.columns=id,project,priority,description
    +report.ls.sort=priority-,project+
    +

    (Find out what was new in prior versions)

    From 6d551357ff0f95b764c4af109782aac3641075a5 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Feb 2009 23:44:58 -0500 Subject: [PATCH 030/103] Packaging - Began modification of script.txt in preparation for next movie! - Added README.1.5.0 warning to configure.ac. Do you think people will see it? And then read the file? I may need to provide an automated solution. --- configure.ac | 7 +++++++ script.txt | 14 +++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index a528345e1..0d4bf6556 100644 --- a/configure.ac +++ b/configure.ac @@ -73,3 +73,10 @@ AC_CHECK_FUNC(srandom, [AC_DEFINE([HAVE_SRANDOM], [1], [Found srandom])]) AC_CONFIG_FILES([Makefile src/Makefile]) AC_OUTPUT + +AC_MSG_NOTICE([...]) +AC_MSG_NOTICE([...]) +AC_MSG_NOTICE([Existing task users please read the README.1.5.0 file!]) +AC_MSG_NOTICE([...]) +AC_MSG_NOTICE([...]) + diff --git a/script.txt b/script.txt index e7d1913e6..db62d92a9 100644 --- a/script.txt +++ b/script.txt @@ -3,18 +3,18 @@ task add do laundry Let's add some tasks I need to do laundry -task add project:garage order dumpster Oh yeah, the dumpster +task add project:garage order dumpster Oh yeah, I need to order the dumpster -task add +phone tell mom i loveher Must call Mom (that "phone" there is a tag - they are - useful for searching, categorizing) +task add +phone tell mom i loveher Must call Mom (that "phone" there is a tag - they can + be useful for searching and categorizing) task add +phone pro:garage schedule goodwill pickup -task ad +email pro:garage ask Tom if Notice I can abbreviating commands +task ad +email pro:garage ask Tom if Notice I can abbreviate commands he wants that old bkie task ls Let's see what we've got - I spelled bike wrong + Oh, I spelled bike wrong task 5 /bkie/bike/ task ls That's better @@ -97,6 +97,10 @@ task summary Summary shows progress on all projec task history History shows general activity - how many added, completed etc, by month +task ghistory This report shows a histogram of tasks that were + added (in red), completed (in green) and deleted + (in yellow), all by month. + And that's it. There are more commands than this covered in the online documentation, but this should give the basic idea. From bcf512e529f73e63384a6ac6462f8e04b84a0dfa Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 16 Feb 2009 21:09:00 -0500 Subject: [PATCH 031/103] Nag Rewrite - Now uses a better escalating scale of "importance". --- src/command.cpp | 1 - src/task.cpp | 63 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/command.cpp b/src/command.cpp index 084125bfd..9bd940fb2 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -520,7 +520,6 @@ std::string handleStop (TDB& tdb, T& task, Config& conf) original.setId (task.getId ()); tdb.modifyT (original); - nag (tdb, task, conf); return std::string (""); } else diff --git a/src/task.cpp b/src/task.cpp index 84a74b24c..88075102c 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -347,14 +347,56 @@ void nag (TDB& tdb, T& task, Config& conf) std::vector pending; tdb.allPendingT (pending); - // Restrict to matching subset. - std::vector matching; - gatherNextTasks (tdb, task, conf, pending, matching); + // Counters. + int overdue = 0; + int high = 0; + int medium = 0; + int low = 0; - foreach (i, matching) - if (pending[*i].getId () == task.getId ()) - return; + // Scan all pending tasks. + foreach (t, pending) + { + if (t->getId () != task.getId ()) + { + if (getDueState (t->getAttribute ("due")) == 2) + overdue++; + std::string priority = t->getAttribute ("priority"); + if (priority.length ()) + { + switch (priority[0]) + { + case 'H': high++; break; + case 'M': medium++; break; + case 'L': low++; break; + } + } + } + } + + // Scan the current task. + bool isOverdue = getDueState (task.getAttribute ("due")) == 2 ? true : false; + + char pri = ' '; + std::string priority = task.getAttribute ("priority"); + if (priority.length ()) + pri = priority[0]; + + // General form is "if there are no more deserving tasks", suppress the nag. + std::cout << "# isOverdue = " << (isOverdue ? "true" : "false") << std::endl; + std::cout << "# pri = " << pri << std::endl; + std::cout << "# overdue = " << overdue << std::endl; + std::cout << "# high = " << high << std::endl; + std::cout << "# medium = " << medium << std::endl; + std::cout << "# low = " << low << std::endl; + + if (isOverdue ) return; + if (pri == 'H' && !overdue ) return; + if (pri == 'M' && !overdue && !high ) return; + if (pri == 'L' && !overdue && !high && !medium ) return; + if (pri == ' ' && !overdue && !high && !medium && !low) return; + + // All the excuses are made, all that remains is to nag the user. std::cout << nagMessage << std::endl; } } @@ -372,15 +414,12 @@ int getDueState (const std::string& due) // rightNow is the current date + time. Date rightNow; + Date midnight (rightNow.month (), rightNow.day (), rightNow.year ()); - // By performing this conversion, today is set up as the same date, but - // midnight. - Date today (rightNow.month (), rightNow.day (), rightNow.year ()); - - if (dt < today) + if (dt < midnight) return 2; - Date nextweek = today + 7 * 86400; + Date nextweek = midnight + 7 * 86400; if (dt < nextweek) return 1; } From 72efddc0668c23dcbf01fc9faf8278f4aa66f27f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 16 Feb 2009 21:35:26 -0500 Subject: [PATCH 032/103] Sample .taskrc - update - Added default config variables for new reports. - Removed README.1.5.0. - Removed messages configure.ac --- ChangeLog | 1 + Makefile.am | 2 +- README.1.5.0 | 15 --------------- configure.ac | 6 ------ html/task.html | 16 +--------------- src/Config.cpp | 9 +++++++++ 6 files changed, 12 insertions(+), 37 deletions(-) delete mode 100644 README.1.5.0 diff --git a/ChangeLog b/ChangeLog index 41018682f..9ffbbce77 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,7 @@ 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. + + New algorithm for determining when the "nag" message is displayed. ------ old releases ------------------------------ diff --git a/Makefile.am b/Makefile.am index 549946a75..fb05a3a4a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ SUBDIRS = src -EXTRA_DIST = DEVELOPERS README.1.5.0 +EXTRA_DIST = DEVELOPERS diff --git a/README.1.5.0 b/README.1.5.0 deleted file mode 100644 index 23dc8bd5d..000000000 --- a/README.1.5.0 +++ /dev/null @@ -1,15 +0,0 @@ -Task 1.5.0 has a custom reports feature. Three of the existing task reports -are no longer implemented in task, and need to be added as custom reports. -Simply copy the following six lines into your existing .taskrc file. - -New task users need not do this - task will create an initial .taskrc file -on first startup. - - -report.long.columns=id,project,priority,entry,start,due,age,tags,description -report.long.sort=due+,priority-,project+ -report.list.columns=id,project,priority,due,active,age,description -report.list.sort=due+,priority-,project+ -report.ls.columns=id,project,priority,description -report.ls.sort=priority-,project+ - diff --git a/configure.ac b/configure.ac index 0d4bf6556..d6e95db0f 100644 --- a/configure.ac +++ b/configure.ac @@ -74,9 +74,3 @@ AC_CHECK_FUNC(srandom, [AC_DEFINE([HAVE_SRANDOM], [1], [Found srandom])]) AC_CONFIG_FILES([Makefile src/Makefile]) AC_OUTPUT -AC_MSG_NOTICE([...]) -AC_MSG_NOTICE([...]) -AC_MSG_NOTICE([Existing task users please read the README.1.5.0 file!]) -AC_MSG_NOTICE([...]) -AC_MSG_NOTICE([...]) - diff --git a/html/task.html b/html/task.html index 696a0a6b5..03c8eee60 100644 --- a/html/task.html +++ b/html/task.html @@ -112,23 +112,9 @@ 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. +
  • New algorithm for determining when the "nag" message is displayed. -

    - Note that users of task prior to version 1.5.0 will need to add - the following six lines to their .taskrc file. See also the - README.1.5.0 file in the distribution for more details. -

    - -
    report.long.columns=id,project,priority,entry,start,due,age,tags,description
    -report.long.sort=due+,priority-,project+
    -
    -report.list.columns=id,project,priority,due,active,age,description
    -report.list.sort=due+,priority-,project+
    -
    -report.ls.columns=id,project,priority,description
    -report.ls.sort=priority-,project+
    -

    (Find out what was new in prior versions)

    diff --git a/src/Config.cpp b/src/Config.cpp index f63a58700..3f46613a0 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -38,6 +38,15 @@ //////////////////////////////////////////////////////////////////////////////// Config::Config () { + // These are default (but overridable) reports. + (*this)["report.large.columns"] = "id,uuid,project,priority,entry,start,due,age,active,tags,description"; + (*this)["report.large.sort"] = "due+,priority-,project+"; + (*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+"; } //////////////////////////////////////////////////////////////////////////////// From 92ba36bdec79d14c2e64fc9f5aa1c91273fe27ac Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 16 Feb 2009 23:12:04 -0500 Subject: [PATCH 033/103] Unit Tests - add, delete, info, /// - Began set of high-level integration tests, in Perl. --- src/Config.cpp | 2 -- src/tests/add.t | 72 +++++++++++++++++++++++++++++++++++++++++++++++ src/tests/basic.t | 57 +++++++++++++++++++++++++++++++++++++ src/tests/in | 15 ---------- 4 files changed, 129 insertions(+), 17 deletions(-) create mode 100755 src/tests/add.t create mode 100755 src/tests/basic.t delete mode 100755 src/tests/in diff --git a/src/Config.cpp b/src/Config.cpp index 3f46613a0..fee5cf39a 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -39,8 +39,6 @@ Config::Config () { // These are default (but overridable) reports. - (*this)["report.large.columns"] = "id,uuid,project,priority,entry,start,due,age,active,tags,description"; - (*this)["report.large.sort"] = "due+,priority-,project+"; (*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"; diff --git a/src/tests/add.t b/src/tests/add.t new file mode 100755 index 000000000..26a82f0ee --- /dev/null +++ b/src/tests/add.t @@ -0,0 +1,72 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 14; + +# Create the rc file. +if (open my $fh, '>', 'add.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'add.rc', 'Created add.rc'); +} + +# Test the add command. +my $output = qx{../task rc:add.rc add This is a test; ../task rc:add.rc info 1}; +like ($output, qr/ID\s+1\n/, 'add ID'); +like ($output, qr/Description\s+This is a test\n/, 'add ID'); +like ($output, qr/Status\s+Pending\n/, 'add Pending'); +like ($output, qr/UUID\s+[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\n/, 'add UUID'); + +# Test the /// modifier. +$output = qx{../task rc:add.rc 1 /test/TEST/; ../task rc:add.rc 1 "/is //"; ../task rc:add.rc info 1}; +like ($output, qr/ID\s+1\n/, 'add ID'); +like ($output, qr/Status\s+Pending\n/, 'add Pending'); +like ($output, qr/Description\s+This a TEST\n/, 'add ID'); + +# Test delete. +$output = qx{../task rc:add.rc delete 1; ../task rc:add.rc info 1}; +like ($output, qr/ID\s+1\n/, 'add ID'); +like ($output, qr/Status\s+Deleted\n/, 'add Deleted'); + +# Test undelete. +$output = qx{../task rc:add.rc undelete 1; ../task rc:add.rc info 1}; +like ($output, qr/ID\s+1\n/, 'add ID'); +like ($output, qr/Status\s+Pending\n/, 'add Pending'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pendind.data', 'Removed pending.data'); + +unlink 'add.rc'; +ok (!-r 'add.rc', 'Removed add.rc'); + +exit 0; + diff --git a/src/tests/basic.t b/src/tests/basic.t new file mode 100755 index 000000000..e369b773d --- /dev/null +++ b/src/tests/basic.t @@ -0,0 +1,57 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 7; + +# Create the rc file. +if (open my $fh, '>', 'basic.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'basic.rc', 'Created basic.rc'); +} + +# Test the usage command. +my $output = qx{../task rc:basic.rc}; +like ($output, qr/Usage: task/, 'usage'); +like ($output, qr/http:\/\/www\.beckingham\.net\/task\.html/, 'usage - url'); + +# Test the version command. +$output = qx{../task rc:basic.rc version}; +like ($output, qr/task \d+\.\d+\.\d+/, 'version - task version number'); +like ($output, qr/ABSOLUTELY NO WARRANTY/, 'version - warranty'); +like ($output, qr/http:\/\/www\.beckingham\.net\/task\.html/, 'version - url'); + +# Cleanup. +unlink 'basic.rc'; +ok (!-r 'basic.rc', 'Removed basic.rc'); + +exit 0; + diff --git a/src/tests/in b/src/tests/in deleted file mode 100755 index 761c1f877..000000000 --- a/src/tests/in +++ /dev/null @@ -1,15 +0,0 @@ -./task add monday due:monday -./task add tuesday due:tuesday -./task add wednesday due:wednesday -./task add thursday due:thursday -./task add friday due:friday -./task add saturday due:saturday -./task add sunday due:sunday -./task add yesterday due:yesterday -./task add today due:today -./task add tomorrow due:tomorrow -./task add eow due:eow -./task add eom due:eom -./task add eoy due:eoy -./task add 21st due:21st - From e2fca47a2711d945a7874655bc14c7621d94a21f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 20 Feb 2009 21:08:39 -0500 Subject: [PATCH 034/103] Typo - Added missing "http://" to "www.samurize.com", at the suggestion of Carlos Yoder. --- html/shadow.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/shadow.html b/html/shadow.html index 6b66de763..e6ae6f07c 100644 --- a/html/shadow.html +++ b/html/shadow.html @@ -43,7 +43,7 @@

    This means there is always a current version of the task report kept in a text file. Products such as - Samurize, + Samurize, MkConsole, or GeekTool From f9272773ac52c7bb308f68f7e91ca5895df6845d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 21 Feb 2009 17:24:07 -0500 Subject: [PATCH 035/103] Credit - Added Carlos Yoder to AUTHORS, for his contribution. - Added Russell Friesenhahn to AUTHORS, for his contribution. --- AUTHORS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index 027de8b31..33a5b9368 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,4 +18,6 @@ With thanks to: Vincent Fleuranceau T. Charles Yun ArchiMark + Carlos Yoder + Russell Friesenhahn From 060516123678eb5032fe6b1d4e1065adcd28b10e Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 23 Feb 2009 10:38:01 -0500 Subject: [PATCH 036/103] Updated OS Compatibility List - Fedora Core 10 - Ubuntu 8.10 Intrepid Ibex --- html/task.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/html/task.html b/html/task.html index 03c8eee60..3f383af5c 100644 --- a/html/task.html +++ b/html/task.html @@ -129,8 +129,10 @@

  • OS X 10.5 Leopard
  • Fedora Core 8
  • Fedora Core 9 +
  • Fedora Core 10
  • Ubuntu 7 Feisty Fawn
  • Ubuntu 8 Hardy Heron +
  • Ubuntu 8.10 Intrepid Ibex
  • Solaris 10
  • Cygwin 1.5.25-14 From 8c484a333d51978c38a6a4dbf5a5573b9c62cda7 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 23 Feb 2009 22:59:17 -0500 Subject: [PATCH 037/103] Documentation Update - Added folks to AUTHORS file. - Added Fedora Core 10, Ubuntu 8.10 Intrepid Ibex to compatibility list. --- AUTHORS | 3 +++ html/links.html | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/AUTHORS b/AUTHORS index 33a5b9368..9fae397e7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,6 +5,7 @@ Contributing Authors: Damian Glenny Andy Lester H. İbrahim Güngör + Stefan Dorn With thanks to: Eugene Kramer @@ -20,4 +21,6 @@ With thanks to: ArchiMark Carlos Yoder Russell Friesenhahn + Paolo Marsi + Eric Farris diff --git a/html/links.html b/html/links.html index 817dbacd4..77dc5a15b 100644 --- a/html/links.html +++ b/html/links.html @@ -37,6 +37,32 @@ Task links from around the web...

    +
    + February 2009, Todo.txt CLI Manages Your Tasks from the Command Line +
    +
    + Gina Trapani generously mentions task in an article about the newly updated, todo.sh 2.0. +
    +
    + +
    + February, 2009, My command line based task management tools +
    +
    + Richard Querin talks about his task management tools. + Richard generously provides the Debian packages for task. +
    +
    + +
    + February, 2009, Common Apps +
    +
    + Archlinux.org mentions task on a page which is + a point of reference for people looking for software to fill a particular need. +
    +
    +
    November 2008, Task repository on GitHub
    From 76c9d3565c985dc0901567c51700dbdb8d0f8efa Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 24 Feb 2009 22:27:51 -0500 Subject: [PATCH 038/103] Documentation Update - Added paragraph tags. Don't know why, but the rendering was odd. --- html/task.html | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/html/task.html b/html/task.html index 3f383af5c..f3f309b08 100644 --- a/html/task.html +++ b/html/task.html @@ -124,18 +124,20 @@ Task has been built from source and tested in the following environments:

    -
      -
    • OS X 10.4 Tiger -
    • OS X 10.5 Leopard -
    • Fedora Core 8 -
    • Fedora Core 9 -
    • Fedora Core 10 -
    • Ubuntu 7 Feisty Fawn -
    • Ubuntu 8 Hardy Heron -
    • Ubuntu 8.10 Intrepid Ibex -
    • Solaris 10 -
    • Cygwin 1.5.25-14 -
    +

    +

      +
    • OS X 10.4 Tiger +
    • OS X 10.5 Leopard +
    • Fedora Core 8 +
    • Fedora Core 9 +
    • Fedora Core 10 +
    • Ubuntu 7 Feisty Fawn +
    • Ubuntu 8 Hardy Heron +
    • Ubuntu 8.10 Intrepid Ibex +
    • Solaris 10 +
    • Cygwin 1.5.25-14 +
    +

    If you have difficulties building task, have found a bug, have a From 59a014d86601a419be8818673ba5dd4865a609dd Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 1 Mar 2009 23:52:28 -0500 Subject: [PATCH 039/103] Unit Tests - nag - Added unit tests to exercise the nag option. --- src/tests/nag.t | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100755 src/tests/nag.t diff --git a/src/tests/nag.t b/src/tests/nag.t new file mode 100755 index 000000000..e6ff4a211 --- /dev/null +++ b/src/tests/nag.t @@ -0,0 +1,66 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 9; + +# Create the rc file. +if (open my $fh, '>', 'nag.rc') +{ + print $fh "data.location=.\n", + "nag=NAG\n"; + close $fh; + ok (-r 'nag.rc', 'Created add.rc'); +} + +my $setup = "../task rc:nag.rc add due:yesterday one;" + . "../task rc:nag.rc add due:tomorrow two;" + . "../task rc:nag.rc add priority:H three;" + . "../task rc:nag.rc add priority:M four;" + . "../task rc:nag.rc add priority:L five;" + . "../task rc:nag.rc add six;"; +qx{$setup}; + +my $output = qx{../task rc:nag.rc do 6}; +like (qx{../task rc:nag.rc do 6}, qr/NAG/, 'do pri: -> nag'); +like (qx{../task rc:nag.rc do 5}, qr/NAG/, 'do pri:L -> nag'); +like (qx{../task rc:nag.rc do 4}, qr/NAG/, 'do pri:M-> nag'); +like (qx{../task rc:nag.rc do 3}, qr/NAG/, 'do pri:H-> nag'); +like (qx{../task rc:nag.rc do 2}, qr/NAG/, 'do due:tomorrow -> nag'); +ok (qx{../task rc:nag.rc do 1} !~ qr/NAG/, 'do due:yesterday -> no nag'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pendind.data', 'Removed pending.data'); + +unlink 'nag.rc'; +ok (!-r 'nag.rc', 'Removed nag.rc'); + +exit 0; + From 6e956b45ad0091e4a8ec3b1082f9266e425d86e9 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 2 Mar 2009 00:44:28 -0500 Subject: [PATCH 040/103] Code Cleanup - Fixed typo in unit test --- src/tests/nag.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/nag.t b/src/tests/nag.t index e6ff4a211..53c5c92ba 100755 --- a/src/tests/nag.t +++ b/src/tests/nag.t @@ -36,7 +36,7 @@ if (open my $fh, '>', 'nag.rc') print $fh "data.location=.\n", "nag=NAG\n"; close $fh; - ok (-r 'nag.rc', 'Created add.rc'); + ok (-r 'nag.rc', 'Created nag.rc'); } my $setup = "../task rc:nag.rc add due:yesterday one;" From 1e704001437c416fd02808e24b69b488989b5f89 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 2 Mar 2009 23:47:41 -0500 Subject: [PATCH 041/103] Shadow File Rewrite - No longer writes shadow files based on TDB onChange trigger. - Addressed bug whereby adding a recurring task trigger a shadow file rewrite, which in turn performs trigger another rewrite... --- src/TDB.cpp | 20 ------- src/TDB.h | 4 -- src/report.cpp | 1 + src/task.cpp | 151 ++++++++++++++++++++++--------------------------- src/task.h | 4 +- 5 files changed, 72 insertions(+), 108 deletions(-) diff --git a/src/TDB.cpp b/src/TDB.cpp index bce2d6e4e..98a67e119 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -308,7 +308,6 @@ bool TDB::overwritePending (std::vector & all) fputs (it->compose ().c_str (), out); fclose (out); - dbChanged (); return true; } @@ -329,7 +328,6 @@ bool TDB::writePending (const T& t) fputs (t.compose ().c_str (), out); fclose (out); - dbChanged (); return true; } @@ -350,8 +348,6 @@ bool TDB::writeCompleted (const T& t) fputs (t.compose ().c_str (), out); fclose (out); - // Note: No call to dbChanged here because this call never occurs by itself. - // It is always accompanied by an overwritePending call. return true; } @@ -436,20 +432,4 @@ int TDB::nextId () } //////////////////////////////////////////////////////////////////////////////// -void TDB::onChange (void (*callback)()) -{ - if (callback) - mOnChange.push_back (callback); -} - -//////////////////////////////////////////////////////////////////////////////// -// Iterate over callbacks. -void TDB::dbChanged () -{ - foreach (i, mOnChange) - if (*i) - (**i) (); -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/TDB.h b/src/TDB.h index e654acc05..b354d97f3 100644 --- a/src/TDB.h +++ b/src/TDB.h @@ -51,21 +51,17 @@ public: int gc (); int nextId (); - void onChange (void (*)()); - private: bool lock (FILE*) const; bool overwritePending (std::vector &); bool writePending (const T&); bool writeCompleted (const T&); bool readLockedFile (const std::string&, std::vector &) const; - void dbChanged (); private: std::string mPendingFile; std::string mCompletedFile; int mId; - std::vector mOnChange; }; #endif diff --git a/src/report.cpp b/src/report.cpp index 8c4c1664d..a43d50706 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -2260,6 +2260,7 @@ std::string handleCustomReport ( // Load all pending tasks. std::vector tasks; tdb.allPendingT (tasks); + handleRecurrence (tdb, tasks); // Apply filters. { diff --git a/src/task.cpp b/src/task.cpp index 88075102c..ae94d6c2d 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -46,11 +46,6 @@ #include #endif -//////////////////////////////////////////////////////////////////////////////// -// Globals for exclusive use by callback function. -static TDB* gTdb = NULL; -static Config* gConf = NULL; - //////////////////////////////////////////////////////////////////////////////// static void shortUsage (Config& conf) { @@ -289,7 +284,6 @@ int main (int argc, char** argv) // Load the config file from the home directory. If the file cannot be // found, offer to create a sample one. Config conf; - gConf = &conf; loadConfFile (argc, argv, conf); // When redirecting output to a file, do not use color, curses. @@ -300,11 +294,10 @@ int main (int argc, char** argv) } TDB tdb; - gTdb = &tdb; std::string dataLocation = expandPath (conf.get ("data.location")); tdb.dataDirectory (dataLocation); - // Set up TDB callback. + // Check for silly shadow file settings. std::string shadowFile = expandPath (conf.get ("shadow.file")); if (shadowFile != "") { @@ -315,8 +308,6 @@ int main (int argc, char** argv) if (shadowFile == dataLocation + "/completed.data") throw std::string ("Configuration variable 'shadow.file' is set to " "overwrite your completed tasks. Please change it."); - - tdb.onChange (&onChangeCallback); } std::cout << runTaskCommand (argc, argv, tdb, conf); @@ -383,12 +374,12 @@ void nag (TDB& tdb, T& task, Config& conf) pri = priority[0]; // General form is "if there are no more deserving tasks", suppress the nag. - std::cout << "# isOverdue = " << (isOverdue ? "true" : "false") << std::endl; - std::cout << "# pri = " << pri << std::endl; - std::cout << "# overdue = " << overdue << std::endl; - std::cout << "# high = " << high << std::endl; - std::cout << "# medium = " << medium << std::endl; - std::cout << "# low = " << low << std::endl; + std::cout << "# task.isOverdue = " << (isOverdue ? "true" : "false") << std::endl; + std::cout << "# task.pri = " << pri << std::endl; + std::cout << "# task.overdue = " << overdue << std::endl; + std::cout << "# pending.high = " << high << std::endl; + std::cout << "# pending.medium = " << medium << std::endl; + std::cout << "# pending.low = " << low << std::endl; if (isOverdue ) return; if (pri == 'H' && !overdue ) return; @@ -711,51 +702,45 @@ void updateRecurrenceMask ( } //////////////////////////////////////////////////////////////////////////////// -// Using gTdb and gConf, generate a report. -void onChangeCallback () +void updateShadowFile (TDB& tdb, Config& conf) { try { - if (gConf && gTdb) + // Determine if shadow file is enabled. + std::string shadowFile = expandPath (conf.get ("shadow.file")); + if (shadowFile != "") { - // Determine if shadow file is enabled. - std::string shadowFile = expandPath (gConf->get ("shadow.file")); - if (shadowFile != "") + std::string oldCurses = conf.get ("curses"); + std::string oldColor = conf.get ("color"); + conf.set ("curses", "off"); + conf.set ("color", "off"); + + // Run report. Use shadow.command, using default.command as a fallback + // with "list" as a default. + std::string command = conf.get ("shadow.command", + conf.get ("default.command", "list")); + std::vector args; + split (args, command, ' '); + std::string result = runTaskCommand (args, tdb, conf); + + std::ofstream out (shadowFile.c_str ()); + if (out.good ()) { - std::string oldCurses = gConf->get ("curses"); - std::string oldColor = gConf->get ("color"); - gConf->set ("curses", "off"); - gConf->set ("color", "off"); - - // Run report. Use shadow.command, using default.command as a fallback - // with "list" as a default. - std::string command = gConf->get ("shadow.command", - gConf->get ("default.command", "list")); - std::vector args; - split (args, command, ' '); - std::string result = runTaskCommand (args, *gTdb, *gConf); - - std::ofstream out (shadowFile.c_str ()); - if (out.good ()) - { - out << result; - out.close (); - } - else - throw std::string ("Could not write file '") + shadowFile + "'"; - - gConf->set ("curses", oldCurses); - gConf->set ("color", oldColor); + out << result; + out.close (); } else - throw std::string ("No specified shadow file '") + shadowFile + "'."; + throw std::string ("Could not write file '") + shadowFile + "'"; - // Optionally display a notification that the shadow file was updated. - if (gConf->get (std::string ("shadow.notify"), false)) - std::cout << "[Shadow file '" << shadowFile << "' updated]" << std::endl; + conf.set ("curses", oldCurses); + conf.set ("color", oldColor); } else - throw std::string ("Internal error (TDB/Config)."); + throw std::string ("No specified shadow file '") + shadowFile + "'."; + + // Optionally display a notification that the shadow file was updated. + if (conf.get (std::string ("shadow.notify"), false)) + std::cout << "[Shadow file '" << shadowFile << "' updated]" << std::endl; } catch (std::string& error) @@ -775,13 +760,14 @@ std::string runTaskCommand ( char** argv, TDB& tdb, Config& conf, - bool gc /* = true */) + bool gc /* = true */, + bool shadow /* = true */) { std::vector args; for (int i = 1; i < argc; ++i) args.push_back (argv[i]); - return runTaskCommand (args, tdb, conf, gc); + return runTaskCommand (args, tdb, conf, gc, shadow); } //////////////////////////////////////////////////////////////////////////////// @@ -789,7 +775,8 @@ std::string runTaskCommand ( std::vector & args, TDB& tdb, Config& conf, - bool gc /* = false */) + bool gc /* = false */, + bool shadow /* = false */) { // If argc == 1 and the default.command configuration variable is set, // then use that, otherwise stick with argc/argv. @@ -810,34 +797,34 @@ std::string runTaskCommand ( std::string out = ""; - if (command == "" && task.getId ()) { handleModify (tdb, task, conf ); } - else if (command == "add") { handleAdd (tdb, task, conf ); } - else if (command == "done") { handleDone (tdb, task, conf ); } - else if (command == "export") { handleExport (tdb, task, conf ); } - else if (command == "projects") { out = handleProjects (tdb, task, conf ); } - else if (command == "tags") { out = handleTags (tdb, task, conf ); } - else if (command == "info") { out = handleInfo (tdb, task, conf ); } - else if (command == "undelete") { out = handleUndelete (tdb, task, conf ); } - else if (command == "delete") { out = handleDelete (tdb, task, conf ); } - else if (command == "start") { out = handleStart (tdb, task, conf ); } - else if (command == "stop") { out = handleStop (tdb, task, conf ); } - else if (command == "undo") { out = handleUndo (tdb, task, conf ); } - else if (command == "stats") { out = handleReportStats (tdb, task, conf ); } - else if (command == "completed") { if (gc) tdb.gc (); out = handleCompleted (tdb, task, conf ); } // TODO replace with Custom - else if (command == "summary") { if (gc) tdb.gc (); out = handleReportSummary (tdb, task, conf ); } - else if (command == "next") { if (gc) tdb.gc (); out = handleReportNext (tdb, task, conf ); } // TODO replace with Custom - else if (command == "history") { if (gc) tdb.gc (); out = handleReportHistory (tdb, task, conf ); } - else if (command == "ghistory") { if (gc) tdb.gc (); out = handleReportGHistory (tdb, task, conf ); } - else if (command == "calendar") { if (gc) tdb.gc (); out = handleReportCalendar (tdb, task, conf ); } - else if (command == "active") { if (gc) tdb.gc (); out = handleReportActive (tdb, task, conf ); } // TODO replace with Custom - else if (command == "overdue") { if (gc) tdb.gc (); out = handleReportOverdue (tdb, task, conf ); } // TODO replace with Custom - else if (command == "oldest") { if (gc) tdb.gc (); out = handleReportOldest (tdb, task, conf ); } // TODO replace with Custom - else if (command == "newest") { if (gc) tdb.gc (); out = handleReportNewest (tdb, task, 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 ); } - else if (isCustomReport (command)) { if (gc) tdb.gc (); out = handleCustomReport (tdb, task, conf, command); } // New Custom reports - else { shortUsage ( conf ); } + if (command == "" && task.getId ()) { handleModify (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + else if (command == "add") { handleAdd (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + else if (command == "done") { handleDone (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + else if (command == "export") { handleExport (tdb, task, conf ); } + else if (command == "projects") { out = handleProjects (tdb, task, conf ); } + else if (command == "tags") { out = handleTags (tdb, task, conf ); } + else if (command == "info") { out = handleInfo (tdb, task, conf ); } + else if (command == "undelete") { out = handleUndelete (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + else if (command == "delete") { out = handleDelete (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + else if (command == "start") { out = handleStart (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + else if (command == "stop") { out = handleStop (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + else if (command == "undo") { out = handleUndo (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + else if (command == "stats") { out = handleReportStats (tdb, task, conf ); } + else if (command == "completed") { if (gc) tdb.gc (); out = handleCompleted (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } // TODO replace with Custom + else if (command == "summary") { if (gc) tdb.gc (); out = handleReportSummary (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + else if (command == "next") { if (gc) tdb.gc (); out = handleReportNext (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } // TODO replace with Custom + else if (command == "history") { if (gc) tdb.gc (); out = handleReportHistory (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + else if (command == "ghistory") { if (gc) tdb.gc (); out = handleReportGHistory (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } + 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 ); } + else if (isCustomReport (command)) { if (gc) tdb.gc (); out = handleCustomReport (tdb, task, conf, command); if (shadow) updateShadowFile (tdb, conf); } // New Custom reports + else { shortUsage ( conf ); } return out; } diff --git a/src/task.h b/src/task.h index 151794c7d..03a5d5f7d 100644 --- a/src/task.h +++ b/src/task.h @@ -69,8 +69,8 @@ bool generateDueDates (T&, std::vector &); Date getNextRecurrence (Date&, std::string&); void updateRecurrenceMask (TDB&, std::vector &, T&); void onChangeCallback (); -std::string runTaskCommand (int, char**, TDB&, Config&, bool gc = true); -std::string runTaskCommand (std::vector &, TDB&, Config&, bool gc = false); +std::string runTaskCommand (int, char**, TDB&, Config&, bool gc = true, bool shadow = true); +std::string runTaskCommand (std::vector &, TDB&, Config&, bool gc = false, bool shadow = false); // command.cpp void handleAdd (TDB&, T&, Config&); From a1b7516cf894a9eac4291bec03028eafd259ce2f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 2 Mar 2009 23:49:13 -0500 Subject: [PATCH 042/103] Sort Algorithm Fix - The sort algorithm (Combsort11) was broken because it didn't consider all the possible variations of present/missing, same/ different combinations of data, when performing a compare. This led to an unstable sort, which is an infinite loop in Combsort11. --- src/Table.cpp | 66 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/src/Table.cpp b/src/Table.cpp index f62fb2eae..2bf97be2b 100644 --- a/src/Table.cpp +++ b/src/Table.cpp @@ -764,6 +764,32 @@ void Table::optimize (std::string& output) //////////////////////////////////////////////////////////////////////////////// // Combsort11, with O(n log n) average, O(n log n) worst case performance. +// +// function combsort11(array input) +// gap := input.size +// +// loop until gap <= 1 and swaps = 0 +// if gap > 1 +// gap := gap / 1.3 +// if gap = 10 or gap = 9 +// gap := 11 +// end if +// end if +// +// i := 0 +// swaps := 0 +// +// loop until i + gap >= input.size +// if input[i] > input[i+gap] +// swap(input[i], input[i+gap]) +// swaps := 1 +// end if +// i := i + 1 +// end loop +// +// end loop +// end function + #define SWAP \ { \ int temp = order[r]; \ @@ -776,7 +802,7 @@ void Table::sort (std::vector & order) int gap = order.size (); int swaps = 1; - while (gap > 1 || swaps != 0) + while (gap > 1 || swaps > 0) { if (gap > 1) { @@ -797,10 +823,28 @@ void Table::sort (std::vector & order) Grid::Cell* left = mData.byRow (order[r], mSortColumns[c]); Grid::Cell* right = mData.byRow (order[r + gap], mSortColumns[c]); - if (left == NULL && right != NULL) - SWAP - if (left && right && *left != *right) + // Data takes precedence over missing data. + if (left == NULL && right != NULL) + { + SWAP + break; + } + + // No data - try comparing the next column. + else if (left == NULL && right == NULL) + { + keepScanning = true; + } + + // Identical data - try comparing the next column. + else if (left && right && *left == *right) + { + keepScanning = true; + } + + // Differing data - do a proper comparison. + else if (left && right && *left != *right) { switch (mSortOrder[mSortColumns[c]]) { @@ -861,24 +905,20 @@ void Table::sort (std::vector & order) break; case ascendingPriority: - if (((std::string)*left == "" && (std::string)*right != "") || - ((std::string)*left == "M" && (std::string)*right == "L") || - ((std::string)*left == "H" && ((std::string)*right == "L" || (std::string)*right == "M"))) + if (((std::string)*left == "" && (std::string)*right != "") || + ((std::string)*left == "M" && (std::string)*right == "L") || + ((std::string)*left == "H" && ((std::string)*right == "L" || (std::string)*right == "M"))) SWAP break; case descendingPriority: - if (((std::string)*left == "" && (std::string)*right != "") || + if (((std::string)*left == "" && (std::string)*right != "") || ((std::string)*left == "L" && ((std::string)*right == "M" || (std::string)*right == "H")) || - ((std::string)*left == "M" && (std::string)*right == "H")) + ((std::string)*left == "M" && (std::string)*right == "H")) SWAP break; } - - break; } - else - keepScanning = true; } ++r; From 8157c729d62e126d2ba0eb793891ff3fccc0b8bf Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 3 Mar 2009 00:08:06 -0500 Subject: [PATCH 043/103] Unit Tests - bug_sort - Added a unit test to cover the bug whereby certain combinations of adding tasks causes Table::sort to loop indefinitely. --- src/tests/bug_sort.t | 62 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 src/tests/bug_sort.t diff --git a/src/tests/bug_sort.t b/src/tests/bug_sort.t new file mode 100755 index 000000000..4b9c4973a --- /dev/null +++ b/src/tests/bug_sort.t @@ -0,0 +1,62 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'bug_sort.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'bug_sort.rc', 'Created bug_sort.rc'); +} + +my $setup = "../task rc:bug_sort.rc add one;" + . "../task rc:bug_sort.rc add two;" + . "../task rc:bug_sort.rc add three recur:daily due:eom;"; +qx{$setup}; + +my $output = qx{../task rc:bug_sort.rc list}; +#diag ($output); +like ($output, qr/three.*(?:one.*two|two.*one)/msi, 'list did not hang'); + +qx{../task rc:bug_sort.rc 1 priority:H}; +$output = qx{../task rc:bug_sort.rc list}; +like ($output, qr/three.*one.*two/msi, 'list did not hang after pri:H on 1'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pendind.data', 'Removed pending.data'); + +unlink 'bug_sort.rc'; +ok (!-r 'bug_sort.rc', 'Removed bug_sort.rc'); + +exit 0; + From 964d04322ca9d6972837bd6e79bbbcbdee5913fe Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 3 Mar 2009 00:46:02 -0500 Subject: [PATCH 044/103] Bug Fix - nag - Implemented new nag algorithm, and debugged why it then broke. --- src/task.cpp | 35 +++++++++++++++++++---------------- src/tests/nag.t | 1 - 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/task.cpp b/src/task.cpp index ae94d6c2d..727db1f16 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -334,20 +334,31 @@ void nag (TDB& tdb, T& task, Config& conf) std::string nagMessage = conf.get ("nag", std::string ("")); if (nagMessage != "") { - // Load all pending. + // Load all pending tasks. std::vector pending; tdb.allPendingT (pending); // Counters. - int overdue = 0; - int high = 0; - int medium = 0; - int low = 0; + int overdue = 0; + int high = 0; + int medium = 0; + int low = 0; + bool isOverdue = false; + char pri = ' '; // Scan all pending tasks. foreach (t, pending) { - if (t->getId () != task.getId ()) + if (t->getId () == task.getId ()) + { + if (getDueState (t->getAttribute ("due")) == 2) + isOverdue = true; + + std::string priority = t->getAttribute ("priority"); + if (priority.length ()) + pri = priority[0]; + } + else if (t->getStatus () == T::pending) { if (getDueState (t->getAttribute ("due")) == 2) overdue++; @@ -365,21 +376,15 @@ void nag (TDB& tdb, T& task, Config& conf) } } - // Scan the current task. - bool isOverdue = getDueState (task.getAttribute ("due")) == 2 ? true : false; - - char pri = ' '; - std::string priority = task.getAttribute ("priority"); - if (priority.length ()) - pri = priority[0]; - // General form is "if there are no more deserving tasks", suppress the nag. +/* std::cout << "# task.isOverdue = " << (isOverdue ? "true" : "false") << std::endl; std::cout << "# task.pri = " << pri << std::endl; std::cout << "# task.overdue = " << overdue << std::endl; std::cout << "# pending.high = " << high << std::endl; std::cout << "# pending.medium = " << medium << std::endl; std::cout << "# pending.low = " << low << std::endl; +*/ if (isOverdue ) return; if (pri == 'H' && !overdue ) return; @@ -735,8 +740,6 @@ void updateShadowFile (TDB& tdb, Config& conf) conf.set ("curses", oldCurses); conf.set ("color", oldColor); } - else - throw std::string ("No specified shadow file '") + shadowFile + "'."; // Optionally display a notification that the shadow file was updated. if (conf.get (std::string ("shadow.notify"), false)) diff --git a/src/tests/nag.t b/src/tests/nag.t index 53c5c92ba..d9dc94d76 100755 --- a/src/tests/nag.t +++ b/src/tests/nag.t @@ -47,7 +47,6 @@ my $setup = "../task rc:nag.rc add due:yesterday one;" . "../task rc:nag.rc add six;"; qx{$setup}; -my $output = qx{../task rc:nag.rc do 6}; like (qx{../task rc:nag.rc do 6}, qr/NAG/, 'do pri: -> nag'); like (qx{../task rc:nag.rc do 5}, qr/NAG/, 'do pri:L -> nag'); like (qx{../task rc:nag.rc do 4}, qr/NAG/, 'do pri:M-> nag'); From 5c89c0f1be2e3fd998b5928ebbb81607f6375b3d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 3 Mar 2009 00:53:32 -0500 Subject: [PATCH 045/103] Documentation Update - Modified ChangeLog and task.html to reflect new bug fixes. --- ChangeLog | 5 +++++ html/task.html | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/ChangeLog b/ChangeLog index 9ffbbce77..e85e949a0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,7 +18,12 @@ 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. + + 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 + when there were only a handful of tasks. ------ old releases ------------------------------ diff --git a/html/task.html b/html/task.html index f3f309b08..1ed9df5e0 100644 --- a/html/task.html +++ b/html/task.html @@ -112,7 +112,12 @@ 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. +

  • 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 + when there were only a handful of tasks.

    From d69d6585311b4e2703401196c3e91a437aa73f55 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 3 Mar 2009 17:15:40 -0500 Subject: [PATCH 046/103] Unit Tests - tag - Added unit tests to test the +tag and -tag task modification feature. --- src/tests/tag.t | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100755 src/tests/tag.t diff --git a/src/tests/tag.t b/src/tests/tag.t new file mode 100755 index 000000000..ceeb24dea --- /dev/null +++ b/src/tests/tag.t @@ -0,0 +1,74 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 9; + +# Create the rc file. +if (open my $fh, '>', 'tag.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'tag.rc', 'Created tag.rc'); +} + +# Add task with tags. +my $output = qx{../task rc:tag.rc add +1 This +2 is a test +3; ../task rc:tag.rc info 1}; +#like ($output, qr/^Tags\s+1 2 3$/ms, 'tags found'); +like ($output, qr/^Tags\s+1 2 3\n/m, 'tags found'); + +# Remove tags. +$output = qx{../task rc:tag.rc 1 -3 -2 -1; ../task rc:tag.rc info 1}; +unlike ($output, qr/^Tags/m, '-3 -2 -1 tag removed'); + +# Add tags. +$output = qx{../task rc:tag.rc 1 +4 +5 +6; ../task rc:tag.rc info 1}; +like ($output, qr/^Tags\s+4 5 6\n/m, 'tags found'); + +# Remove tags. +$output = qx{../task rc:tag.rc 1 -4 -5 -6; ../task rc:tag.rc info 1}; +unlike ($output, qr/^Tags/m, '-4 -5 -6 tag removed'); + +# Add and remove tags. +$output = qx{../task rc:tag.rc 1 +duplicate -duplicate; ../task rc:tag.rc info 1}; +unlike ($output, qr/^Tags/m, '+duplicate -duplicate NOP'); + +# Remove missing tag. +$output = qx{../task rc:tag.rc 1 -missing; ../task rc:tag.rc info 1}; +unlike ($output, qr/^Tags/m, '-missing NOP'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pendind.data', 'Removed pending.data'); + +unlink 'tag.rc'; +ok (!-r 'tag.rc', 'Removed tag.rc'); + +exit 0; + From d7a9d06360b4aba2bfe580e9f4065a4d4f815375 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 3 Mar 2009 21:19:07 -0500 Subject: [PATCH 047/103] Unit Tests - add, bug_hang, bug_period, bug_sort, nag, tag - Implemented unit tests to verify tag manipulation - Implemented unit tests to verify nag functionality - Implemented unit tests to verify bug fix for hang on shadow write - Implemented unit tests to verify bug fix for unsupported recurrence periods - Implemented unit tests to verify bug fix for hang on sort - Corrected typo in add.t --- src/tests/add.t | 2 +- src/tests/bug_hang.t | 83 +++++++++++++++++++++ src/tests/bug_period.t | 164 +++++++++++++++++++++++++++++++++++++++++ src/tests/bug_sort.t | 2 +- src/tests/nag.t | 2 +- src/tests/tag.t | 3 +- 6 files changed, 251 insertions(+), 5 deletions(-) create mode 100755 src/tests/bug_hang.t create mode 100755 src/tests/bug_period.t diff --git a/src/tests/add.t b/src/tests/add.t index 26a82f0ee..db6f5c553 100755 --- a/src/tests/add.t +++ b/src/tests/add.t @@ -63,7 +63,7 @@ like ($output, qr/Status\s+Pending\n/, 'add Pending'); # Cleanup. unlink 'pending.data'; -ok (!-r 'pendind.data', 'Removed pending.data'); +ok (!-r 'pending.data', 'Removed pending.data'); unlink 'add.rc'; ok (!-r 'add.rc', 'Removed add.rc'); diff --git a/src/tests/bug_hang.t b/src/tests/bug_hang.t new file mode 100755 index 000000000..dbe229919 --- /dev/null +++ b/src/tests/bug_hang.t @@ -0,0 +1,83 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'hang.rc') +{ + print $fh "data.location=.\n", + "shadow.file=shadow.txt\n", + "shadow.command=list\n"; + close $fh; + ok (-r 'hang.rc', 'Created hang.rc'); +} + +=pod +I found a bug in the current version of task. Using recur and a shadow file will +lead to an infinite loop. To reproduce it, define a shadow file in the .taskrc, +set a command for it that rebuilds the database, e.g. "list", and then add a +task with a recurrence set, e.g. "task add due:today recur:1d infinite loop". +Task will then loop forever and add the same recurring task until it runs out of +memory. So I checked the source and I believe I found the cause. +handleRecurrence() in task.cpp will modify the mask, but writes it only after it +has added all new tasks. Adding the task will, however, invoke onChangeCallback, +which starts the same process all over again. +=cut + +eval +{ + $SIG{'ALRM'} = sub {die "alarm\n"}; + alarm 10; + my $output = qx{../task rc:hang.rc list; + ../task rc:hang.rc add due:today recur:1d infinite loop; + ../task rc:hang.rc info 1}; + alarm 0; + + like ($output, qr/^Description\s+infinite loop\n/m, 'no hang'); +}; + +if ($@ eq "alarm\n") +{ + fail ('task hang on add or recurring task, with shadow file, for 10s'); +} + +# Cleanup. +unlink 'shadow.txt'; +ok (!-r 'shadow.txt', 'Removed shadow.txt'); + +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'hang.rc'; +ok (!-r 'hang.rc', 'Removed hang.rc'); + +exit 0; + diff --git a/src/tests/bug_period.t b/src/tests/bug_period.t new file mode 100755 index 000000000..dff6888ff --- /dev/null +++ b/src/tests/bug_period.t @@ -0,0 +1,164 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 41; + +# Create the rc file. +if (open my $fh, '>', 'period.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'period.rc', 'Created period.rc'); +} + +=pod +http://github.com/pbeckingham/task/blob/857f813a24f7ce15fea9f2c28aadad84cb5c8847/src/task.cpp +619 // If the period is an 'easy' one, add it to current, and we're done. +620 int days = convertDuration (period); + +Date getNextRecurrence (Date& current, std::string& period) + +starting at line 509 special cases several possibilities for period, '\d\s?m' +'monthly', 'quarterly', 'semiannual', 'bimonthly', 'biannual', 'biyearly'. +Everything else falls through with period being passed to convertDuration. +convertDuration doesn't know about 'daily' though so it seems to be returning 0. + +Confirmed: + getNextRecurrence convertDuration + ----------------- --------------- + daily + day + weekly + sennight + biweekly + fortnight + monthly monthly + quarterly quarterly + semiannual semiannual + bimonthly bimonthly + biannual biannual + biyearly biyearly + annual + yearly + *m *m + *q *q + *d + *w + *y +=cut + +my $output = qx{../task rc:period.rc add daily due:tomorrow recur:daily}; +like ($output, qr/^$/, 'recur:daily'); + +$output = qx{../task rc:period.rc add day due:tomorrow recur:day}; +like ($output, qr/^$/, 'recur:day'); + +$output = qx{../task rc:period.rc add weekly due:tomorrow recur:weekly}; +like ($output, qr/^$/, 'recur:weekly'); + +$output = qx{../task rc:period.rc add sennight due:tomorrow recur:sennight}; +like ($output, qr/^$/, 'recur:sennight'); + +$output = qx{../task rc:period.rc add biweekly due:tomorrow recur:biweekly}; +like ($output, qr/^$/, 'recur:biweekly'); + +$output = qx{../task rc:period.rc add fortnight due:tomorrow recur:fortnight}; +like ($output, qr/^$/, 'recur:fortnight'); + +$output = qx{../task rc:period.rc add monthly due:tomorrow recur:monthly}; +like ($output, qr/^$/, 'recur:monthly'); + +$output = qx{../task rc:period.rc add quarterly due:tomorrow recur:quarterly}; +like ($output, qr/^$/, 'recur:quarterly'); + +$output = qx{../task rc:period.rc add semiannual due:tomorrow recur:semiannual}; +like ($output, qr/^$/, 'recur:semiannual'); + +$output = qx{../task rc:period.rc add bimonthly due:tomorrow recur:bimonthly}; +like ($output, qr/^$/, 'recur:bimonthly'); + +$output = qx{../task rc:period.rc add biannual due:tomorrow recur:biannual}; +like ($output, qr/^$/, 'recur:biannual'); + +$output = qx{../task rc:period.rc add biyearly due:tomorrow recur:biyearly}; +like ($output, qr/^$/, 'recur:biyearly'); + +$output = qx{../task rc:period.rc add annual due:tomorrow recur:annual}; +like ($output, qr/^$/, 'recur:annual'); + +$output = qx{../task rc:period.rc add yearly due:tomorrow recur:yearly}; +like ($output, qr/^$/, 'recur:yearly'); + +$output = qx{../task rc:period.rc add 2d due:tomorrow recur:2d}; +like ($output, qr/^$/, 'recur:2m'); + +$output = qx{../task rc:period.rc add 2w due:tomorrow recur:2w}; +like ($output, qr/^$/, 'recur:2q'); + +$output = qx{../task rc:period.rc add 2m due:tomorrow recur:2m}; +like ($output, qr/^$/, 'recur:2d'); + +$output = qx{../task rc:period.rc add 2q due:tomorrow recur:2q}; +like ($output, qr/^$/, 'recur:2w'); + +$output = qx{../task rc:period.rc add 2y due:tomorrow recur:2y}; +like ($output, qr/^$/, 'recur:2y'); + +# Verify that the recurring task instances get created. One of each. +$output = qx{../task rc:period.rc list}; +like ($output, qr/\bdaily\b/, 'verify daily'); +like ($output, qr/\bday\b/, 'verify day'); +like ($output, qr/\bweekly\b/, 'verify weekly'); +like ($output, qr/\bsennight\b/, 'verify sennight'); +like ($output, qr/\bbiweekly\b/, 'verify biweekly'); +like ($output, qr/\bfortnight\b/, 'verify fortnight'); +like ($output, qr/\bmonthly\b/, 'verify monthly'); +like ($output, qr/\bquarterly\b/, 'verify quarterly'); +like ($output, qr/\bsemiannual\b/, 'verify semiannual'); +like ($output, qr/\bbimonthly\b/, 'verify bimonthly'); +like ($output, qr/\bbiannual\b/, 'verify biannual'); +like ($output, qr/\bbiyearly\b/, 'verify biyearly'); +like ($output, qr/\bannual\b/, 'verify annual'); +like ($output, qr/\byearly\b/, 'verify yearly'); +like ($output, qr/\b2d\b/, 'verify 2d'); +like ($output, qr/\b2w\b/, 'verify 2w'); +like ($output, qr/\b2m\b/, 'verify 2m'); +like ($output, qr/\b2q\b/, 'verify 2q'); +like ($output, qr/\b2y\b/, 'verify 2y'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'period.rc'; +ok (!-r 'period.rc', 'Removed period.rc'); + +exit 0; + diff --git a/src/tests/bug_sort.t b/src/tests/bug_sort.t index 4b9c4973a..0d0d49eac 100755 --- a/src/tests/bug_sort.t +++ b/src/tests/bug_sort.t @@ -53,7 +53,7 @@ like ($output, qr/three.*one.*two/msi, 'list did not hang after pri:H on 1'); # Cleanup. unlink 'pending.data'; -ok (!-r 'pendind.data', 'Removed pending.data'); +ok (!-r 'pending.data', 'Removed pending.data'); unlink 'bug_sort.rc'; ok (!-r 'bug_sort.rc', 'Removed bug_sort.rc'); diff --git a/src/tests/nag.t b/src/tests/nag.t index d9dc94d76..d55aac456 100755 --- a/src/tests/nag.t +++ b/src/tests/nag.t @@ -56,7 +56,7 @@ ok (qx{../task rc:nag.rc do 1} !~ qr/NAG/, 'do due:yesterday -> no nag'); # Cleanup. unlink 'pending.data'; -ok (!-r 'pendind.data', 'Removed pending.data'); +ok (!-r 'pending.data', 'Removed pending.data'); unlink 'nag.rc'; ok (!-r 'nag.rc', 'Removed nag.rc'); diff --git a/src/tests/tag.t b/src/tests/tag.t index ceeb24dea..f55ba0dbf 100755 --- a/src/tests/tag.t +++ b/src/tests/tag.t @@ -40,7 +40,6 @@ if (open my $fh, '>', 'tag.rc') # Add task with tags. my $output = qx{../task rc:tag.rc add +1 This +2 is a test +3; ../task rc:tag.rc info 1}; -#like ($output, qr/^Tags\s+1 2 3$/ms, 'tags found'); like ($output, qr/^Tags\s+1 2 3\n/m, 'tags found'); # Remove tags. @@ -65,7 +64,7 @@ unlike ($output, qr/^Tags/m, '-missing NOP'); # Cleanup. unlink 'pending.data'; -ok (!-r 'pendind.data', 'Removed pending.data'); +ok (!-r 'pending.data', 'Removed pending.data'); unlink 'tag.rc'; ok (!-r 'tag.rc', 'Removed tag.rc'); From d831ab335a2bc44c52ca81d2798e3e4088be497b Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 3 Mar 2009 23:13:31 -0500 Subject: [PATCH 048/103] Report Column Header - Added "Number" to the ghistory graph title. --- src/report.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/report.cpp b/src/report.cpp index a43d50706..f1e806fde 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1070,7 +1070,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf) table.setDateFormat (conf.get ("dateformat", "m/d/Y")); table.addColumn ("Year"); table.addColumn ("Month"); - table.addColumn ("Added/Completed/Deleted"); + table.addColumn ("Number Added/Completed/Deleted"); if (conf.get ("color", true)) { From d573599a7e1e2bed945b51067220a7f8607520c8 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Wed, 4 Mar 2009 00:04:09 -0500 Subject: [PATCH 049/103] Unit Tests - subproject - Implemented unit test to verify that the project and subproject filtering is working properly. --- src/tests/subproject.t | 73 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100755 src/tests/subproject.t diff --git a/src/tests/subproject.t b/src/tests/subproject.t new file mode 100755 index 000000000..04703a28a --- /dev/null +++ b/src/tests/subproject.t @@ -0,0 +1,73 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 11; + +# Create the rc file. +if (open my $fh, '>', 'sp.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'sp.rc', 'Created sp.rc'); +} + +my $setup = "../task rc:sp.rc add project:abc abc;" + . "../task rc:sp.rc add project:ab ab;" + . "../task rc:sp.rc add project:a a;" + . "../task rc:sp.rc add project:b b;"; +qx{$setup}; + +my $output = qx{../task rc:sp.rc list project:b}; +like ($output, qr/\bb\s*$/m, 'abc,ab,a,b | b -> b'); + +$output = qx{../task rc:sp.rc list project:a}; +like ($output, qr/\babc\s*$/m, 'abc,ab,a,b | a -> abc'); +like ($output, qr/\bab\s*$/m, 'abc,ab,a,b | a -> ab'); +like ($output, qr/\ba\s*$/m, 'abc,ab,a,b | a -> a'); + +$output = qx{../task rc:sp.rc list project:ab}; +like ($output, qr/\babc\s*$/m, 'abc,ab,a,b | a -> abc'); +like ($output, qr/\bab\s*$/m, 'abc,ab,a,b | a -> ab'); + +$output = qx{../task rc:sp.rc list project:abc}; +like ($output, qr/\babc\s*$/m, 'abc,ab,a,b | a -> abc'); + +$output = qx{../task rc:sp.rc list project:abcd}; +like ($output, qr/^No matches.$/, 'abc,ab,a,b | abcd -> nul'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'sp.rc'; +ok (!-r 'sp.rc', 'Removed sp.rc'); + +exit 0; + From 9988ecec5efce4eecc5bccc34fa0343f79b82bfd Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Wed, 4 Mar 2009 09:37:00 -0500 Subject: [PATCH 050/103] Portability - Modified util.cpp to allow clean compilation on Solaris. --- src/util.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/util.cpp b/src/util.cpp index fd73c3b39..bc1bea9f2 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -29,10 +29,12 @@ #include #include #include +#include #include #include #include #include +#include #include "Date.h" #include "Table.h" #include "task.h" @@ -335,20 +337,20 @@ std::string expandPath (const std::string& in) #ifdef SOLARIS int flock (int fd, int operation) { - struct flock flock; + struct flock fl; switch (operation & ~LOCK_NB) { case LOCK_SH: - flock.l_type = F_RDLCK; + fl.l_type = F_RDLCK; break; case LOCK_EX: - flock.l_type = F_WRLCK; + fl.l_type = F_WRLCK; break; case LOCK_UN: - flock.l_type = F_UNLCK; + fl.l_type = F_UNLCK; break; default: @@ -356,11 +358,11 @@ int flock (int fd, int operation) return -1; } - flock.l_whence = 0; - flock.l_start = 0; - flock.l_len = 0; + fl.l_whence = 0; + fl.l_start = 0; + fl.l_len = 0; - return fcntl (fd, (operation & LOCK_NB) ? F_SETLK : F_SETLKW, &flock); + return fcntl (fd, (operation & LOCK_NB) ? F_SETLK : F_SETLKW, &fl); } #endif From 9535121c1ef2e8ee2568546715aaa55feae15bdc Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 5 Mar 2009 10:08:25 -0500 Subject: [PATCH 051/103] Performance - Removed the unnecessary sort in the 'completed' report. The tasks are already sorted. --- src/report.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/report.cpp b/src/report.cpp index f1e806fde..d2bb6e1e2 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -189,7 +189,10 @@ std::string handleCompleted (TDB& tdb, T& task, Config& conf) table.setColumnJustification (1, Table::left); table.setColumnJustification (2, Table::left); - table.sortOn (0, Table::ascendingDate); + // Note: There is deliberately no sorting. The original sorting was on the + // end date. Tasks are appended to completed.data naturally sorted by + // the end date, so that sequence is assumed to remain unchanged, and + // relied upon here. // Iterate over each task, and apply selection criteria. for (unsigned int i = 0; i < tasks.size (); ++i) From 41b8b207d4f23141a565635956e69a26fea98eaf Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 5 Mar 2009 10:13:10 -0500 Subject: [PATCH 052/103] Documentation Update - Added examples to the grammar file. - Added recent change to ChangeLog, html/task.html. --- ChangeLog | 1 + grammar.bnf | 7 ++++++- html/task.html | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index e85e949a0..9ec97dcbb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -24,6 +24,7 @@ and shadow files. + Fixed bug with the task sort alogrithm, which led to an unstable sequence when there were only a handful of tasks. + + Performance enhanced by eliminating unnecessary sorting. ------ old releases ------------------------------ diff --git a/grammar.bnf b/grammar.bnf index ba82df08e..2905420f4 100644 --- a/grammar.bnf +++ b/grammar.bnf @@ -1,7 +1,12 @@ # This is a full BNF grammar for the task command line. It is intended that a # future release of task will incorporate a complete lexer/parser implementing -# this grammar. +# this grammar, which will allow for more sophisticated command lines, for +# example: +# +# task delete 1 2 4-7 +# task add pri:H pro:X -- pro pri 1 /// +# command ::= simple_command | filter_command filter? diff --git a/html/task.html b/html/task.html index 1ed9df5e0..81cae7bf0 100644 --- a/html/task.html +++ b/html/task.html @@ -118,6 +118,7 @@ and shadow files.

  • Fixed bug with the task sort alogrithm, which led to an unstable sequence when there were only a handful of tasks. +
  • Performance enhanced by eliminating unnecessary sorting.

    From 463c968cacb46597e68d04a5d583680257f45f75 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 6 Mar 2009 00:39:28 -0500 Subject: [PATCH 053/103] Unit Tests - undo.t - Added unit tests for the undo command, which verify that a task may only be undone if a TDB::gc has not occurred. --- src/tests/undo.t | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100755 src/tests/undo.t diff --git a/src/tests/undo.t b/src/tests/undo.t new file mode 100755 index 000000000..ec023b31d --- /dev/null +++ b/src/tests/undo.t @@ -0,0 +1,74 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 15; + +# Create the rc file. +if (open my $fh, '>', 'undo.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'undo.rc', 'Created undo.rc'); +} + +# Test the add/do/undo commands. +my $output = qx{../task rc:undo.rc add one; ../task rc:undo.rc info 1}; +ok (-r 'pending.data', 'pending.data created'); +like ($output, qr/Status\s+Pending\n/, 'Pending'); + +$output = qx{../task rc:undo.rc do 1; ../task rc:undo.rc info 1}; +ok (! -r 'completed.data', 'completed.data not created'); +like ($output, qr/Status\s+Completed\n/, 'Completed'); + +$output = qx{../task rc:undo.rc undo 1; ../task rc:undo.rc info 1}; +ok (! -r 'completed.data', 'completed.data not created'); +like ($output, qr/Status\s+Pending\n/, 'Pending'); + +$output = qx{../task rc:undo.rc do 1; ../task rc:undo.rc list}; +like ($output, qr/^No matches/, 'No matches'); + +$output = qx{../task rc:undo.rc undo 1; ../task rc:undo.rc info 1}; +like ($output, qr/Task 1 not found/, 'task not found'); +like ($output, qr/reliably undone/, 'can only be reliable undone...'); + +# Cleanup. +ok (-r 'pending.data', 'Need to remove pending.data'); +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +ok (-r 'completed.data', 'Need to remove completed.data'); +unlink 'completed.data'; +ok (!-r 'completed.data', 'Removed completed.data'); + +unlink 'undo.rc'; +ok (!-r 'undo.rc', 'Removed undo.rc'); + +exit 0; + From 3b1d396e0a3ea005df1c18ef7de89f20fa35459d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 6 Mar 2009 21:59:25 -0500 Subject: [PATCH 054/103] Acknowledgement - Michael Greb acknowledged for his help in reporting several bugs in sufficient detail, and narrowing down the cause. --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 9fae397e7..7fcc9c14b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,6 +6,7 @@ Contributing Authors: Andy Lester H. İbrahim Güngör Stefan Dorn + Michael Greb With thanks to: Eugene Kramer From 6a7c66aa05feca13f8c1e66939bb7f563613fd3d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 7 Mar 2009 00:14:58 -0500 Subject: [PATCH 055/103] Unit Tests - color.disable color.pri config.obsolete - Added unit tests to cover automatic colorization by priority. - Added unit tests to cover automatic disabling of color when !isatty. - Added unit tests to cover display of unsupported configuration variable in the 'version' report. - Added support the '_forcecolor' configuration variable to allow the possibility of unit tests that test color support, yet redirect output to a file. This configuration variable will not be documented. --- src/Config.cpp | 7 ++- src/command.cpp | 19 +++++-- src/report.cpp | 54 +++++++++---------- src/task.cpp | 4 +- src/tests/{bug_hang.t => bug.hang.t} | 0 src/tests/{bug_period.t => bug.period.t} | 0 src/tests/{bug_sort.t => bug.sort.t} | 0 src/tests/color.disable.t | 58 +++++++++++++++++++++ src/tests/color.pri.t | 66 ++++++++++++++++++++++++ src/tests/config.obsolete.t | 57 ++++++++++++++++++++ 10 files changed, 231 insertions(+), 34 deletions(-) rename src/tests/{bug_hang.t => bug.hang.t} (100%) rename src/tests/{bug_period.t => bug.period.t} (100%) rename src/tests/{bug_sort.t => bug.sort.t} (100%) create mode 100755 src/tests/color.disable.t create mode 100755 src/tests/color.pri.t create mode 100755 src/tests/config.obsolete.t diff --git a/src/Config.cpp b/src/Config.cpp index fee5cf39a..64e07b04c 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -36,9 +36,14 @@ #include "Config.h" //////////////////////////////////////////////////////////////////////////////// +// 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. +// 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 () { - // These are default (but overridable) reports. (*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"; diff --git a/src/command.cpp b/src/command.cpp index 9bd940fb2..03e6e473d 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -113,7 +113,7 @@ std::string handleProjects (TDB& tdb, T& task, Config& conf) table.addColumn ("Project"); table.addColumn ("Tasks"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -316,7 +316,7 @@ std::string handleVersion (Config& conf) table.addColumn ("Config variable"); table.addColumn ("Value"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -345,9 +345,13 @@ std::string handleVersion (Config& conf) out << "Copyright (C) 2006 - 2009, P. Beckingham." << std::endl - << (conf.get ("color", true) ? Text::colorize (Text::bold, Text::nocolor, PACKAGE) : PACKAGE) + << ((conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) + ? Text::colorize (Text::bold, Text::nocolor, PACKAGE) + : PACKAGE) << " " - << (conf.get ("color", true) ? Text::colorize (Text::bold, Text::nocolor, VERSION) : VERSION) + << ((conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) + ? Text::colorize (Text::bold, Text::nocolor, VERSION) + : VERSION) << std::endl << disclaimer.render () << std::endl @@ -364,6 +368,11 @@ std::string handleVersion (Config& conf) "monthsperline nag newest next oldest 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 + // is redirected to a file, or stdout is not a tty. + recognized += " _forcecolor"; + std::vector unrecognized; foreach (i, all) { @@ -696,7 +705,7 @@ std::string handleColor (Config& conf) { std::stringstream out; - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { out << optionalBlankLine (conf) << "Foreground" << std::endl << " " diff --git a/src/report.cpp b/src/report.cpp index d2bb6e1e2..afa460821 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -172,7 +172,7 @@ std::string handleCompleted (TDB& tdb, T& task, Config& conf) table.addColumn ("Project"); table.addColumn ("Description"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -209,7 +209,7 @@ std::string handleCompleted (TDB& tdb, T& task, Config& conf) table.addCell (row, 1, refTask.getAttribute ("project")); table.addCell (row, 2, refTask.getDescription ()); - if (conf.get ("color", true)) + 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")); @@ -261,7 +261,7 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf) table.addColumn ("Name"); table.addColumn ("Value"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -359,7 +359,7 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf) Date nextweek = now + 7 * 86400; imminent = dt < nextweek ? true : false; - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { if (overdue) table.setCellFg (row, 1, Text::colorCode (conf.get ("color.overdue", "red"))); @@ -513,7 +513,7 @@ std::string handleReportSummary (TDB& tdb, T& task, Config& conf) table.addColumn ("Complete"); table.addColumn ("0% 100%"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -550,7 +550,7 @@ std::string handleReportSummary (TDB& tdb, T& task, Config& conf) int completedBar = (c * barWidth) / (c + p); std::string bar; - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { bar = "\033[42m"; for (int b = 0; b < completedBar; ++b) @@ -654,7 +654,7 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf) table.addColumn ("Age"); table.addColumn ("Description"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -729,7 +729,7 @@ std::string handleReportNext (TDB& tdb, T& task, Config& conf) table.addCell (row, 5, age); table.addCell (row, 6, refTask.getDescription ()); - if (conf.get ("color", true)) + 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")); @@ -879,7 +879,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf) table.addColumn ("Deleted"); table.addColumn ("Net"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -942,7 +942,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf) } table.addCell (row, 5, net); - if (conf.get ("color", true) && net) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false) && net) table.setCellFg (row, 5, net > 0 ? Text::red: Text::green); } @@ -952,7 +952,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf) row = table.addRow (); table.addCell (row, 1, "Average"); - if (conf.get ("color", true)) table.setRowFg (row, Text::bold); + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) 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)); @@ -1075,7 +1075,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf) table.addColumn ("Month"); table.addColumn ("Number Added/Completed/Deleted"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -1131,7 +1131,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf) unsigned int deletedBar = (widthOfBar * deletedGroup[i->first]) / maxLine; std::string bar = ""; - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { char number[24]; std::string aBar = ""; @@ -1190,7 +1190,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf) << table.render () << std::endl; - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) out << "Legend: " << Text::colorize (Text::black, Text::on_red, "added") << ", " @@ -1232,7 +1232,7 @@ std::string renderMonths ( table.addColumn ("Fr"); table.addColumn ("Sa"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (i + 1); table.setColumnUnderline (i + 2); @@ -1302,7 +1302,7 @@ std::string renderMonths ( table.addCell (row, thisCol, d); - if (conf.get ("color", true) && + if ((conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) && today.day () == d && today.month () == months.at (c) && today.year () == years.at (c)) @@ -1313,7 +1313,7 @@ std::string renderMonths ( { Date due (::atoi (it->getAttribute ("due").c_str ())); - if (conf.get ("color", true) && + if ((conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) && due.day () == d && due.month () == months.at (c) && due.year () == years.at (c)) @@ -1455,7 +1455,7 @@ std::string handleReportActive (TDB& tdb, T& task, Config& conf) table.addColumn ("Due"); table.addColumn ("Description"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -1511,7 +1511,7 @@ std::string handleReportActive (TDB& tdb, T& task, Config& conf) table.addCell (row, 3, due); table.addCell (row, 4, refTask.getDescription ()); - if (conf.get ("color", true)) + 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")); @@ -1576,7 +1576,7 @@ std::string handleReportOverdue (TDB& tdb, T& task, Config& conf) table.addColumn ("Due"); table.addColumn ("Description"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -1625,7 +1625,7 @@ std::string handleReportOverdue (TDB& tdb, T& task, Config& conf) table.addCell (row, 3, due); table.addCell (row, 4, refTask.getDescription ()); - if (conf.get ("color", true)) + 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")); @@ -1693,7 +1693,7 @@ std::string handleReportOldest (TDB& tdb, T& task, Config& conf) table.addColumn ("Age"); table.addColumn ("Description"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -1769,7 +1769,7 @@ std::string handleReportOldest (TDB& tdb, T& task, Config& conf) table.addCell (row, 5, age); table.addCell (row, 6, refTask.getDescription ()); - if (conf.get ("color", true)) + 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")); @@ -1840,7 +1840,7 @@ std::string handleReportNewest (TDB& tdb, T& task, Config& conf) table.addColumn ("Age"); table.addColumn ("Description"); - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { table.setColumnUnderline (0); table.setColumnUnderline (1); @@ -1917,7 +1917,7 @@ std::string handleReportNewest (TDB& tdb, T& task, Config& conf) table.addCell (row, 5, age); table.addCell (row, 6, refTask.getDescription ()); - if (conf.get ("color", true)) + 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")); @@ -2449,7 +2449,7 @@ std::string handleCustomReport ( // Common to all columns. // Add underline. - if (conf.get (std::string ("color"), true)) + if (conf.get (std::string ("color"), true) || conf.get (std::string ("_forcecolor"), false)) table.setColumnUnderline (columnCount); else table.setTableDashedUnderline (); @@ -2514,7 +2514,7 @@ std::string handleCustomReport ( } } - if (conf.get ("color", true)) + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) { Text::color fg = Text::colorCode (tasks[row].getAttribute ("fg")); Text::color bg = Text::colorCode (tasks[row].getAttribute ("bg")); diff --git a/src/task.cpp b/src/task.cpp index 727db1f16..04c73c3e6 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -290,7 +290,9 @@ int main (int argc, char** argv) if (!isatty (fileno (stdout))) { conf.set ("curses", "off"); - conf.set ("color", "off"); + + if (! conf.get (std::string ("_forcecolor"), false)) + conf.set ("color", "off"); } TDB tdb; diff --git a/src/tests/bug_hang.t b/src/tests/bug.hang.t similarity index 100% rename from src/tests/bug_hang.t rename to src/tests/bug.hang.t diff --git a/src/tests/bug_period.t b/src/tests/bug.period.t similarity index 100% rename from src/tests/bug_period.t rename to src/tests/bug.period.t diff --git a/src/tests/bug_sort.t b/src/tests/bug.sort.t similarity index 100% rename from src/tests/bug_sort.t rename to src/tests/bug.sort.t diff --git a/src/tests/color.disable.t b/src/tests/color.disable.t new file mode 100755 index 000000000..c4a0f4a58 --- /dev/null +++ b/src/tests/color.disable.t @@ -0,0 +1,58 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 6; + +# Create the rc file. +if (open my $fh, '>', 'color.rc') +{ + print $fh "data.location=.\n", + "color.pri.H=red\n"; + close $fh; + ok (-r 'color.rc', 'Created color.rc'); +} + +# Test the add command. +qx{../task rc:color.rc add priority:H red}; +my $output = qx{../task rc:color.rc list}; + +like ($output, qr/red/, 'color.disable - found red'); +unlike ($output, qr/\033\[31m/, 'color.disable - no color red'); +unlike ($output, qr/\033\[0m/, 'color.disable - no color reset'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'color.rc'; +ok (!-r 'color.rc', 'Removed color.rc'); + +exit 0; + diff --git a/src/tests/color.pri.t b/src/tests/color.pri.t new file mode 100755 index 000000000..14a2ee42d --- /dev/null +++ b/src/tests/color.pri.t @@ -0,0 +1,66 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 7; + +# Create the rc file. +if (open my $fh, '>', 'color.rc') +{ + print $fh "data.location=.\n", + "color.pri.H=red\n", + "color.pri.M=green\n", + "color.pri.L=blue\n", + "color.pri.none=yellow\n", + "_forcecolor=1\n"; + close $fh; + ok (-r 'color.rc', 'Created color.rc'); +} + +# Test the add command. +qx{../task rc:color.rc add priority:H red}; +qx{../task rc:color.rc add priority:M green}; +qx{../task rc:color.rc add priority:L blue}; +qx{../task rc:color.rc add yellow}; +my $output = qx{../task rc:color.rc list}; + +like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.pri.H'); +like ($output, qr/ \033\[32m .* green .* \033\[0m /x, 'color.pri.M'); +like ($output, qr/ \033\[34m .* blue .* \033\[0m /x, 'color.pri.L'); +like ($output, qr/ \033\[33m .* yellow .* \033\[0m /x, 'color.pri.none'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'color.rc'; +ok (!-r 'color.rc', 'Removed color.rc'); + +exit 0; + diff --git a/src/tests/config.obsolete.t b/src/tests/config.obsolete.t new file mode 100755 index 000000000..7a5f34bc1 --- /dev/null +++ b/src/tests/config.obsolete.t @@ -0,0 +1,57 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'obsolete.rc') +{ + print $fh "data.location=.\n", + "foo=1\n"; + close $fh; + ok (-r 'obsolete.rc', 'Created obsolete.rc'); +} + +# Test the add command. +my $output = qx{../task rc:obsolete.rc version}; + +like ($output, qr/Your .taskrc file contains these unrecognized variables:\n/, + 'unsupported configuration variable'); +like ($output, qr/ foo\n/, 'unsupported configuration variable'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'obsolete.rc'; +ok (!-r 'obsolete.rc', 'Removed obsolete.rc'); + +exit 0; + From 3088e1ebe1f9624b1f303e2d8f5db1114d532944 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 7 Mar 2009 02:06:13 -0500 Subject: [PATCH 056/103] Unit Tests - abbreviation, filter, benchmark - Added tests for attribute abbreviation. - Added tests for filter permutation testing. - Added benchmark for ongoing performance measurement. - Mentioned test suite in docs. Why not? --- ChangeLog | 2 + html/task.html | 2 + src/tests/abbreviation.t | 77 ++++++++++++++++ src/tests/benchmark.t | 96 +++++++++++++++++++ src/tests/filter.t | 193 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 370 insertions(+) create mode 100755 src/tests/abbreviation.t create mode 100755 src/tests/benchmark.t create mode 100755 src/tests/filter.t diff --git a/ChangeLog b/ChangeLog index 9ec97dcbb..4ab22d837 100644 --- a/ChangeLog +++ b/ChangeLog @@ -25,6 +25,8 @@ + Fixed bug with the task sort alogrithm, 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 + to help ensure higher quality releases. ------ old releases ------------------------------ diff --git a/html/task.html b/html/task.html index 81cae7bf0..a65a68a96 100644 --- a/html/task.html +++ b/html/task.html @@ -119,6 +119,8 @@

  • Fixed bug with the task sort alogrithm, 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 + to help ensure higher quality releases.

    diff --git a/src/tests/abbreviation.t b/src/tests/abbreviation.t new file mode 100755 index 000000000..4d9b27515 --- /dev/null +++ b/src/tests/abbreviation.t @@ -0,0 +1,77 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 15; + +# Create the rc file. +if (open my $fh, '>', 'abbrev.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'abbrev.rc', 'Created abbrev.rc'); +} + +# Test the add command. +qx{../task rc:abbrev.rc add priority:H with}; +qx{../task rc:abbrev.rc add without}; + +my $output = qx{../task rc:abbrev.rc list priority:H}; +like ($output, qr/\bwith\b/, 'priority:H with'); +unlike ($output, qr/\bwithout\b/, 'priority:H without'); + +$output = qx{../task rc:abbrev.rc list priorit:H}; +like ($output, qr/\bwith\b/, 'priorit:H with'); +unlike ($output, qr/\bwithout\b/, 'priorit:H without'); + +$output = qx{../task rc:abbrev.rc list priori:H}; +like ($output, qr/\bwith\b/, 'priori:H with'); +unlike ($output, qr/\bwithout\b/, 'priori:H without'); + +$output = qx{../task rc:abbrev.rc list prior:H}; +like ($output, qr/\bwith\b/, 'prior:H with'); +unlike ($output, qr/\bwithout\b/, 'prior:H without'); + +$output = qx{../task rc:abbrev.rc list prio:H}; +like ($output, qr/\bwith\b/, 'prio:H with'); +unlike ($output, qr/\bwithout\b/, 'prio:H without'); + +$output = qx{../task rc:abbrev.rc list pri:H}; +like ($output, qr/\bwith\b/, 'pri:H with'); +unlike ($output, qr/\bwithout\b/, 'pri:H without'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'abbrev.rc'; +ok (!-r 'abbrev.rc', 'Removed abbrev.rc'); + +exit 0; + diff --git a/src/tests/benchmark.t b/src/tests/benchmark.t new file mode 100755 index 000000000..dd069deb4 --- /dev/null +++ b/src/tests/benchmark.t @@ -0,0 +1,96 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 4; + +# Create the rc file. +if (open my $fh, '>', 'bench.rc') +{ + print $fh "data.location=.\n", + "_forcecolor=1\n"; + close $fh; + ok (-r 'bench.rc', 'Created bench.rc'); +} + +# Do lots of things. Time it all. + +my @tags = qw(t_one t_two t_three t_four t_five t_six t_seven t_eight); +my @projects = qw(p_one p_two p_three p_foud p_five p_six p_seven p_eight); +my @priorities = qw(H M L); +my $description = 'This is a medium-sized description with no special characters'; + +# Start the clock. +my $start = time (); +diag ("start=$start"); + +# Make a mess. +for my $i (1 .. 1000) +{ + my $project = $projects[rand % 8]; + my $priority = $priorities[rand % 3]; + my $tag = $tags[rand % 8]; + + qx{../task rc:bench.rc add project:$project priority:$priority +$tag $i $description}; +} +diag ("1000 tasks added"); + +qx{../task rc:bench.rc /with/WITH/} for 1 .. 200; +qx{../task rc:bench.rc done $_} for 201 .. 400; +qx{../task rc:bench.rc start $_} for 401 .. 600; +diag ("600 tasks altered"); + +# Report it all. +qx{../task rc:bench.rc ls}; +qx{../task rc:bench.rc list}; +qx{../task rc:bench.rc list priority:H}; +qx{../task rc:bench.rc list +tag}; +qx{../task rc:bench.rc list project_A}; +qx{../task rc:bench.rc long}; +qx{../task rc:bench.rc completed}; +qx{../task rc:bench.rc history}; +qx{../task rc:bench.rc ghistory}; + +# Stop the clock. +my $stop = time (); +diag ("stop=$stop"); +diag ("total=" . ($stop - $start)); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'completed.data'; +ok (!-r 'completed.data', 'Removed completed.data'); + +unlink 'bench.rc'; +ok (!-r 'bench.rc', 'Removed bench.rc'); + +exit 0; + diff --git a/src/tests/filter.t b/src/tests/filter.t new file mode 100755 index 000000000..5fd4e918b --- /dev/null +++ b/src/tests/filter.t @@ -0,0 +1,193 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 108; + +# Create the rc file. +if (open my $fh, '>', 'filter.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'filter.rc', 'Created filter.rc'); +} + +# Test the filters. +qx{../task rc:filter.rc add project:A priority:H +tag one foo}; +qx{../task rc:filter.rc add project:A priority:H two}; +qx{../task rc:filter.rc add project:A three}; +qx{../task rc:filter.rc add priority:H four}; +qx{../task rc:filter.rc add +tag five}; +qx{../task rc:filter.rc add six foo}; +qx{../task rc:filter.rc add priority:L seven bar foo}; + +my $output = qx{../task rc:filter.rc list}; +like ($output, qr/one/, 'a1'); +like ($output, qr/two/, 'a2'); +like ($output, qr/three/, 'a3'); +like ($output, qr/four/, 'a4'); +like ($output, qr/five/, 'a5'); +like ($output, qr/six/, 'a6'); +like ($output, qr/seven/, 'a7'); + +$output = qx{../task rc:filter.rc list project:A}; +like ($output, qr/one/, 'b1'); +like ($output, qr/two/, 'b2'); +like ($output, qr/three/, 'b3'); +unlike ($output, qr/four/, 'b4'); +unlike ($output, qr/five/, 'b5'); +unlike ($output, qr/six/, 'b6'); +unlike ($output, qr/seven/, 'b7'); + +$output = qx{../task rc:filter.rc list priority:H}; +like ($output, qr/one/, 'c1'); +like ($output, qr/two/, 'c2'); +unlike ($output, qr/three/, 'c3'); +like ($output, qr/four/, 'c4'); +unlike ($output, qr/five/, 'c5'); +unlike ($output, qr/six/, 'c6'); +unlike ($output, qr/seven/, 'c7'); + +$output = qx{../task rc:filter.rc list priority:}; +unlike ($output, qr/one/, 'd1'); +unlike ($output, qr/two/, 'd2'); +like ($output, qr/three/, 'd3'); +unlike ($output, qr/four/, 'd4'); +like ($output, qr/five/, 'd5'); +like ($output, qr/six/, 'd6'); +unlike ($output, qr/seven/, 'd7'); + +$output = qx{../task rc:filter.rc list foo}; +like ($output, qr/one/, 'e1'); +unlike ($output, qr/two/, 'e2'); +unlike ($output, qr/three/, 'e3'); +unlike ($output, qr/four/, 'e4'); +unlike ($output, qr/five/, 'e5'); +like ($output, qr/six/, 'e6'); +like ($output, qr/seven/, 'e7'); + +$output = qx{../task rc:filter.rc list foo bar}; +unlike ($output, qr/one/, 'f1'); +unlike ($output, qr/two/, 'f2'); +unlike ($output, qr/three/, 'f3'); +unlike ($output, qr/four/, 'f4'); +unlike ($output, qr/five/, 'f5'); +unlike ($output, qr/six/, 'f6'); +like ($output, qr/seven/, 'f7'); + +$output = qx{../task rc:filter.rc list +tag}; +like ($output, qr/one/, 'g1'); +unlike ($output, qr/two/, 'g2'); +unlike ($output, qr/three/, 'g3'); +unlike ($output, qr/four/, 'g4'); +like ($output, qr/five/, 'g5'); +unlike ($output, qr/six/, 'g6'); +unlike ($output, qr/seven/, 'g7'); + +$output = qx{../task rc:filter.rc list project:A priority:H}; +like ($output, qr/one/, 'h1'); +like ($output, qr/two/, 'h2'); +unlike ($output, qr/three/, 'h3'); +unlike ($output, qr/four/, 'h4'); +unlike ($output, qr/five/, 'h5'); +unlike ($output, qr/six/, 'h6'); +unlike ($output, qr/seven/, 'h7'); + +$output = qx{../task rc:filter.rc list project:A priority:}; +unlike ($output, qr/one/, 'i1'); +unlike ($output, qr/two/, 'i2'); +like ($output, qr/three/, 'i3'); +unlike ($output, qr/four/, 'i4'); +unlike ($output, qr/five/, 'i5'); +unlike ($output, qr/six/, 'i6'); +unlike ($output, qr/seven/, 'i7'); + +$output = qx{../task rc:filter.rc list project:A foo}; +like ($output, qr/one/, 'j1'); +unlike ($output, qr/two/, 'j2'); +unlike ($output, qr/three/, 'j3'); +unlike ($output, qr/four/, 'j4'); +unlike ($output, qr/five/, 'j5'); +unlike ($output, qr/six/, 'j6'); +unlike ($output, qr/seven/, 'j7'); + +$output = qx{../task rc:filter.rc list project:A +tag}; +like ($output, qr/one/, 'k1'); +unlike ($output, qr/two/, 'k2'); +unlike ($output, qr/three/, 'k3'); +unlike ($output, qr/four/, 'k4'); +unlike ($output, qr/five/, 'k5'); +unlike ($output, qr/six/, 'k6'); +unlike ($output, qr/seven/, 'k7'); + +$output = qx{../task rc:filter.rc list project:A priority:H foo}; +like ($output, qr/one/, 'l1'); +unlike ($output, qr/two/, 'l2'); +unlike ($output, qr/three/, 'l3'); +unlike ($output, qr/four/, 'l4'); +unlike ($output, qr/five/, 'l5'); +unlike ($output, qr/six/, 'l6'); +unlike ($output, qr/seven/, 'l7'); + +$output = qx{../task rc:filter.rc list project:A priority:H +tag}; +like ($output, qr/one/, 'm1'); +unlike ($output, qr/two/, 'm2'); +unlike ($output, qr/three/, 'm3'); +unlike ($output, qr/four/, 'm4'); +unlike ($output, qr/five/, 'm5'); +unlike ($output, qr/six/, 'm6'); +unlike ($output, qr/seven/, 'm7'); + +$output = qx{../task rc:filter.rc list project:A priority:H foo +tag}; +like ($output, qr/one/, 'n1'); +unlike ($output, qr/two/, 'n2'); +unlike ($output, qr/three/, 'n3'); +unlike ($output, qr/four/, 'n4'); +unlike ($output, qr/five/, 'n5'); +unlike ($output, qr/six/, 'n6'); +unlike ($output, qr/seven/, 'n7'); + +$output = qx{../task rc:filter.rc list project:A priority:H foo +tag baz}; +unlike ($output, qr/one/, 'n1'); +unlike ($output, qr/two/, 'n2'); +unlike ($output, qr/three/, 'n3'); +unlike ($output, qr/four/, 'n4'); +unlike ($output, qr/five/, 'n5'); +unlike ($output, qr/six/, 'n6'); +unlike ($output, qr/seven/, 'n7'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'filter.rc'; +ok (!-r 'filter.rc', 'Removed filter.rc'); + +exit 0; + From 4fa4c5f532313ea0d7b853fb8e09b3741b6b3113 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 8 Mar 2009 17:59:27 -0400 Subject: [PATCH 057/103] Unit Tests - t.benchmark.t - Added benchmark to measure time taken to parse 1,000,000 T records. --- configure.ac | 12 +++---- src/T.cpp | 2 +- src/tests/.gitignore | 3 +- src/tests/Makefile | 5 ++- src/tests/t.benchmark.t.cpp | 69 +++++++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 src/tests/t.benchmark.t.cpp diff --git a/configure.ac b/configure.ac index d6e95db0f..4ae1d6760 100644 --- a/configure.ac +++ b/configure.ac @@ -14,14 +14,12 @@ debug_default="yes" AC_ARG_ENABLE(debug, [ --enable-debug=[no/yes] turn on debugging [default=$debug_default]],, enable_debug=$debug_default) # Yes, shell scripts can be used -if test "x$enable_debug" = "xyes"; then -CFLAGS="$CFLAGS -Wall -pedantic -ggdb3 -DDEBUG" -CXXFLAGS="$CFLAGS -Wall -pedantic -ggdb3 -DDEBUG" -AC_MSG_RESULT(yes) +if test "$enable_debug" = "yes"; then + CXXFLAGS="$CFLAGS -Wall -pedantic -ggdb3 -DDEBUG" + AC_MSG_RESULT(yes) else -CFLAGS="$CFLAGS -O3" -CXXFLAGS="$CFLAGS -O3" -AC_MSG_RESULT(no) + CXXFLAGS="$CFLAGS -O3" + AC_MSG_RESULT(no) fi # Check for OS. diff --git a/src/T.cpp b/src/T.cpp index a21b31638..61acd52f4 100644 --- a/src/T.cpp +++ b/src/T.cpp @@ -468,7 +468,7 @@ void T::parse (const std::string& line) break; default: - throw std::string (); + throw std::string ("Unrecognized task file format."); break; } } diff --git a/src/tests/.gitignore b/src/tests/.gitignore index 1445bb223..94ab5cd82 100644 --- a/src/tests/.gitignore +++ b/src/tests/.gitignore @@ -1,7 +1,6 @@ t.t +t.benchmark.t tdb.t date.t duration.t -pending.data -completed.data diff --git a/src/tests/Makefile b/src/tests/Makefile index c6c3ac3d8..b98b58bd4 100644 --- a/src/tests/Makefile +++ b/src/tests/Makefile @@ -1,4 +1,4 @@ -PROJECT = t.t tdb.t date.t duration.t +PROJECT = t.t tdb.t date.t duration.t t.benchmark.t CFLAGS = -I. -I.. -Wall -pedantic -ggdb3 -fno-rtti LFLAGS = -L/usr/local/lib OBJECTS = ../TDB.o ../T.o ../parse.o ../text.o ../Date.o ../util.o ../Config.o @@ -29,3 +29,6 @@ date.t: date.t.o $(OBJECTS) test.o duration.t: duration.t.o $(OBJECTS) test.o g++ duration.t.o $(OBJECTS) test.o $(LFLAGS) -o duration.t +t.benchmark.t: t.benchmark.t.o $(OBJECTS) test.o + g++ t.benchmark.t.o $(OBJECTS) test.o $(LFLAGS) -o t.benchmark.t + diff --git a/src/tests/t.benchmark.t.cpp b/src/tests/t.benchmark.t.cpp new file mode 100644 index 000000000..9adf4d578 --- /dev/null +++ b/src/tests/t.benchmark.t.cpp @@ -0,0 +1,69 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2009, 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 "../T.h" +#include "../task.h" +#include "test.h" + +//////////////////////////////////////////////////////////////////////////////// +int main (int argc, char** argv) +{ + UnitTest test (1); + + std::string sample = "d346065c-7ef6-49af-ae77-19c1825807f5 " + "- " + "[bug performance solaris linux osx] " + "[due:1236142800 entry:1236177552 priority:H project:task-1.5.0 start:1236231761] " + "Profile task and identify performance bottlenecks"; + + // Start clock + test.diag ("start"); + struct timeval start; + gettimeofday (&start, NULL); + + for (int i = 0; i < 1000000; i++) + { + T t (sample); + } + + // End clock + struct timeval end; + gettimeofday (&end, NULL); + test.diag ("end"); + + int diff = ((end.tv_sec * 1000000) + end.tv_usec) - + ((start.tv_sec * 1000000) + start.tv_usec); + + char s[16]; + sprintf (s, "%d.%06d", diff/1000000, diff%1000000); + test.pass (std::string ("1,000,000 T::parse calls in ") + s + "s"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + From 0362b41f3b7ae97f77ab91ec0f4e5133bd8d0715 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 8 Mar 2009 20:49:33 -0400 Subject: [PATCH 058/103] Performance - Added Timer class to display high resolution timing information. - Found terrible bug in Table::optimize that was taking up 99.7%, on average, of the Table::rendering time, including sorting. This fix naturally causes a 187-fold speedup of rendering. - Changed report.cpp in handleCustomReport to only load pending tasks, instead of all pending tasks. Subtle, but important difference. --- .gitignore | 1 - ChangeLog | 1 + Makefile.in | 595 ++++++++++++++++++++++++++++++++++++++++++ html/task.html | 1 + src/Makefile.am | 2 +- src/Makefile.in | 10 +- src/Table.cpp | 40 ++- src/Timer.cpp | 56 ++++ src/Timer.h | 46 ++++ src/report.cpp | 2 +- src/tests/benchmark.t | 31 ++- 11 files changed, 765 insertions(+), 20 deletions(-) create mode 100644 Makefile.in create mode 100644 src/Timer.cpp create mode 100644 src/Timer.h diff --git a/.gitignore b/.gitignore index c61760acc..3fb778785 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -Makefile.in aclocal.m4 autom4te.cache auto.h* diff --git a/ChangeLog b/ChangeLog index 4ab22d837..1b41b79b0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -27,6 +27,7 @@ + Performance enhanced by eliminating unnecessary sorting. + Task now has a large (and growing) test suite and bug regression tests to help ensure higher quality releases. + + Fixed bug that caused performance hit during table rendering. ------ old releases ------------------------------ diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 000000000..4e4244870 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,595 @@ +# Makefile.in generated by automake 1.10 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = . +DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in $(srcdir)/auto.h.in \ + $(top_srcdir)/configure AUTHORS COPYING ChangeLog INSTALL NEWS \ + depcomp install-sh missing +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ + configure.lineno config.status.lineno +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = auto.h +CONFIG_CLEAN_FILES = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ + html-recursive info-recursive install-data-recursive \ + install-dvi-recursive install-exec-recursive \ + install-html-recursive install-info-recursive \ + install-pdf-recursive install-ps-recursive install-recursive \ + installcheck-recursive installdirs-recursive pdf-recursive \ + ps-recursive uninstall-recursive +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +distdir = $(PACKAGE)-$(VERSION) +top_distdir = $(distdir) +am__remove_distdir = \ + { test ! -d $(distdir) \ + || { find $(distdir) -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -fr $(distdir); }; } +DIST_ARCHIVES = $(distdir).tar.gz +GZIP_ENV = --best +distuninstallcheck_listfiles = find . -type f -print +distcleancheck_listfiles = find . -type f -print +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = src +EXTRA_DIST = DEVELOPERS +all: auto.h + $(MAKE) $(AM_MAKEFLAGS) all-recursive + +.SUFFIXES: +am--refresh: + @: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + echo ' cd $(srcdir) && $(AUTOMAKE) --gnu '; \ + cd $(srcdir) && $(AUTOMAKE) --gnu \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + echo ' $(SHELL) ./config.status'; \ + $(SHELL) ./config.status;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + $(SHELL) ./config.status --recheck + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(srcdir) && $(AUTOCONF) +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) + +auto.h: stamp-h1 + @if test ! -f $@; then \ + rm -f stamp-h1; \ + $(MAKE) $(AM_MAKEFLAGS) stamp-h1; \ + else :; fi + +stamp-h1: $(srcdir)/auto.h.in $(top_builddir)/config.status + @rm -f stamp-h1 + cd $(top_builddir) && $(SHELL) ./config.status auto.h +$(srcdir)/auto.h.in: $(am__configure_deps) + cd $(top_srcdir) && $(AUTOHEADER) + rm -f stamp-h1 + touch $@ + +distclean-hdr: + -rm -f auto.h stamp-h1 + +# This directory's subdirectories are mostly independent; you can cd +# into them and run `make' without going through this Makefile. +# To change the values of `make' variables: instead of editing Makefiles, +# (1) if the variable is set in `config.status', edit `config.status' +# (which will cause the Makefiles to be regenerated when you run `make'); +# (2) otherwise, pass the desired values on the `make' command line. +$(RECURSIVE_TARGETS): + @failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +$(RECURSIVE_CLEAN_TARGETS): + @failcom='exit 1'; \ + for f in x $$MAKEFLAGS; do \ + case $$f in \ + *=* | --[!k]*);; \ + *k*) failcom='fail=yes';; \ + esac; \ + done; \ + dot_seen=no; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + rev=''; for subdir in $$list; do \ + if test "$$subdir" = "."; then :; else \ + rev="$$subdir $$rev"; \ + fi; \ + done; \ + rev="$$rev ."; \ + target=`echo $@ | sed s/-recursive//`; \ + for subdir in $$rev; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done && test -z "$$fail" +tags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \ + done +ctags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: tags-recursive $(HEADERS) $(SOURCES) auto.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + tags="$$tags $$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + list='$(SOURCES) $(HEADERS) auto.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: ctags-recursive $(HEADERS) $(SOURCES) auto.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) auto.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + $(am__remove_distdir) + test -d $(distdir) || mkdir $(distdir) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done + list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + distdir=`$(am__cd) $(distdir) && pwd`; \ + top_distdir=`$(am__cd) $(top_distdir) && pwd`; \ + (cd $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$top_distdir" \ + distdir="$$distdir/$$subdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + distdir) \ + || exit 1; \ + fi; \ + done + -find $(distdir) -type d ! -perm -777 -exec chmod a+rwx {} \; -o \ + ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ + || chmod -R a+r $(distdir) +dist-gzip: distdir + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + $(am__remove_distdir) + +dist-bzip2: distdir + tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2 + $(am__remove_distdir) + +dist-tarZ: distdir + tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z + $(am__remove_distdir) + +dist-shar: distdir + shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz + $(am__remove_distdir) + +dist-zip: distdir + -rm -f $(distdir).zip + zip -rq $(distdir).zip $(distdir) + $(am__remove_distdir) + +dist dist-all: distdir + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + $(am__remove_distdir) + +# This target untars the dist file and tries a VPATH configuration. Then +# it guarantees that the distribution is self-contained by making another +# tarfile. +distcheck: dist + case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + GZIP=$(GZIP_ENV) gunzip -c $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bunzip2 -c $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + GZIP=$(GZIP_ENV) gunzip -c $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + esac + chmod -R a-w $(distdir); chmod a+w $(distdir) + mkdir $(distdir)/_build + mkdir $(distdir)/_inst + chmod a-w $(distdir) + dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ + && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ + && cd $(distdir)/_build \ + && ../configure --srcdir=.. --prefix="$$dc_install_base" \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) dvi \ + && $(MAKE) $(AM_MAKEFLAGS) check \ + && $(MAKE) $(AM_MAKEFLAGS) install \ + && $(MAKE) $(AM_MAKEFLAGS) installcheck \ + && $(MAKE) $(AM_MAKEFLAGS) uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ + distuninstallcheck \ + && chmod -R a-w "$$dc_install_base" \ + && ({ \ + (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ + distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ + } || { rm -rf "$$dc_destdir"; exit 1; }) \ + && rm -rf "$$dc_destdir" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && rm -rf $(DIST_ARCHIVES) \ + && $(MAKE) $(AM_MAKEFLAGS) distcleancheck + $(am__remove_distdir) + @(echo "$(distdir) archives ready for distribution: "; \ + list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ + sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' +distuninstallcheck: + @cd $(distuninstallcheck_dir) \ + && test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \ + || { echo "ERROR: files left after uninstall:" ; \ + if test -n "$(DESTDIR)"; then \ + echo " (check DESTDIR support)"; \ + fi ; \ + $(distuninstallcheck_listfiles) ; \ + exit 1; } >&2 +distcleancheck: distclean + @if test '$(srcdir)' = . ; then \ + echo "ERROR: distcleancheck can only run from a VPATH build" ; \ + exit 1 ; \ + fi + @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left in build directory after distclean:" ; \ + $(distcleancheck_listfiles) ; \ + exit 1; } >&2 +check-am: all-am +check: check-recursive +all-am: Makefile auto.h +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-hdr distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-exec-am: + +install-html: install-html-recursive + +install-info: install-info-recursive + +install-man: + +install-pdf: install-pdf-recursive + +install-ps: install-ps-recursive + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf $(top_srcdir)/autom4te.cache + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) install-am \ + install-strip + +.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \ + all all-am am--refresh check check-am clean clean-generic \ + ctags ctags-recursive dist dist-all dist-bzip2 dist-gzip \ + dist-shar dist-tarZ dist-zip distcheck distclean \ + distclean-generic distclean-hdr distclean-tags distcleancheck \ + distdir distuninstallcheck dvi dvi-am html html-am info \ + info-am install install-am install-data install-data-am \ + install-dvi install-dvi-am install-exec install-exec-am \ + install-html install-html-am install-info install-info-am \ + install-man install-pdf install-pdf-am install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs installdirs-am maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic pdf \ + pdf-am ps ps-am tags tags-recursive uninstall uninstall-am + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/html/task.html b/html/task.html index a65a68a96..64b8cba47 100644 --- a/html/task.html +++ b/html/task.html @@ -121,6 +121,7 @@

  • Performance enhanced by eliminating unnecessary sorting.
  • Task now has a large (and growing) test suite and bug regression tests to help ensure higher quality releases. +
  • Fixed bug that caused performance hit during table rendering.

    diff --git a/src/Makefile.am b/src/Makefile.am index 2e92854a2..8203dbb14 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,2 +1,2 @@ bin_PROGRAMS = task -task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h color.h task.h +task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp Timer.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h Timer.h color.h task.h diff --git a/src/Makefile.in b/src/Makefile.in index c8ca21f5f..ae8c519e5 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -44,9 +44,10 @@ am__installdirs = "$(DESTDIR)$(bindir)" binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) PROGRAMS = $(bin_PROGRAMS) am_task_OBJECTS = Config.$(OBJEXT) Date.$(OBJEXT) T.$(OBJEXT) \ - TDB.$(OBJEXT) Table.$(OBJEXT) Grid.$(OBJEXT) color.$(OBJEXT) \ - parse.$(OBJEXT) task.$(OBJEXT) command.$(OBJEXT) \ - report.$(OBJEXT) util.$(OBJEXT) text.$(OBJEXT) rules.$(OBJEXT) + TDB.$(OBJEXT) Table.$(OBJEXT) Grid.$(OBJEXT) Timer.$(OBJEXT) \ + color.$(OBJEXT) parse.$(OBJEXT) task.$(OBJEXT) \ + command.$(OBJEXT) report.$(OBJEXT) util.$(OBJEXT) \ + text.$(OBJEXT) rules.$(OBJEXT) task_OBJECTS = $(am_task_OBJECTS) task_LDADD = $(LDADD) DEFAULT_INCLUDES = -I. -I$(top_builddir)@am__isrc@ @@ -154,7 +155,7 @@ sysconfdir = @sysconfdir@ target_alias = @target_alias@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h color.h task.h +task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp Timer.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h Timer.h color.h task.h all: all-am .SUFFIXES: @@ -227,6 +228,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/T.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TDB.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Table.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Timer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@ diff --git a/src/Table.cpp b/src/Table.cpp index 2bf97be2b..26ed0bacb 100644 --- a/src/Table.cpp +++ b/src/Table.cpp @@ -749,12 +749,50 @@ void Table::optimize (std::string& output) */ // \s\n -> \n +/* + Well, how about that! + + The benchmark.t unit test adds a 1000 tasks, fiddles with some of them, then + runs a series of reports. The results are timed, and look like this: + + 1000 tasks added in 3 seconds + 600 tasks altered in 32 seconds + 'task ls' in 26 seconds + 'task list' in 17 seconds + 'task list pri:H' in 19 seconds + 'task list +tag' in 0 seconds + 'task list project_A' in 0 seconds + 'task long' in 29 seconds + 'task completed' in 2 seconds + 'task history' in 0 seconds + 'task ghistory' in 0 seconds + + This performance is terrible. To identify the worst offender, Various Timer + objects were added in Table::render, assuming that table sorting is the major + bottleneck. But no, it is Table::optimize that is the problem. After + commenting out the code below, the results are now: + + 1000 tasks added in 3 seconds + 600 tasks altered in 29 seconds + 'task ls' in 0 seconds + 'task list' in 0 seconds + 'task list pri:H' in 1 seconds + 'task list +tag' in 0 seconds + 'task list project_A' in 0 seconds + 'task long' in 0 seconds + 'task completed' in 0 seconds + 'task history' in 0 seconds + 'task ghistory' in 0 seconds + + Much better. Table::optimize is currently disabled. + size_t i = 0; while ((i = output.find (" \n")) != std::string::npos) { output = output.substr (0, i) + - output.substr (i + 1, std::string::npos); + output.substr (i + 2, std::string::npos); } +*/ /* std::cout << int ((100 * (start - output.length ()) / start)) diff --git a/src/Timer.cpp b/src/Timer.cpp new file mode 100644 index 000000000..cbd9f1dc7 --- /dev/null +++ b/src/Timer.cpp @@ -0,0 +1,56 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2009, 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 + +//////////////////////////////////////////////////////////////////////////////// +// Timer starts when the object is constructed. +Timer::Timer (const std::string& description) +: mDescription (description) +{ + ::gettimeofday (&mStart, NULL); +} + +//////////////////////////////////////////////////////////////////////////////// +// Timer stops when the object is desctructed. +Timer::~Timer () +{ + struct timeval end; + ::gettimeofday (&end, NULL); + + std::cout << "Timer " + << mDescription + << " " + << std::setprecision (6) + << ((end.tv_sec - mStart.tv_sec) + + ((end.tv_usec - mStart.tv_usec ) / 1000000.0)) + << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/Timer.h b/src/Timer.h new file mode 100644 index 000000000..6b561aac2 --- /dev/null +++ b/src/Timer.h @@ -0,0 +1,46 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2009, 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 +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_TIMER +#define INCLUDED_TIMER + +#include +#include + +class Timer +{ +public: + Timer (const std::string&); + ~Timer (); + +private: + std::string mDescription; + struct timeval mStart; +}; + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/report.cpp b/src/report.cpp index afa460821..a762a5c69 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -2262,7 +2262,7 @@ std::string handleCustomReport ( // Load all pending tasks. std::vector tasks; - tdb.allPendingT (tasks); + tdb.pendingT (tasks); handleRecurrence (tdb, tasks); // Apply filters. diff --git a/src/tests/benchmark.t b/src/tests/benchmark.t index dd069deb4..73088a4a0 100755 --- a/src/tests/benchmark.t +++ b/src/tests/benchmark.t @@ -48,6 +48,7 @@ my $description = 'This is a medium-sized description with no special characters # Start the clock. my $start = time (); +my $cursor = $start; diag ("start=$start"); # Make a mess. @@ -59,23 +60,29 @@ for my $i (1 .. 1000) qx{../task rc:bench.rc add project:$project priority:$priority +$tag $i $description}; } -diag ("1000 tasks added"); +diag ("1000 tasks added in " . (time () - $cursor) . " seconds"); +$cursor = time (); qx{../task rc:bench.rc /with/WITH/} for 1 .. 200; qx{../task rc:bench.rc done $_} for 201 .. 400; qx{../task rc:bench.rc start $_} for 401 .. 600; -diag ("600 tasks altered"); +diag ("600 tasks altered in " . (time () - $cursor) . " seconds"); +$cursor = time (); -# Report it all. -qx{../task rc:bench.rc ls}; -qx{../task rc:bench.rc list}; -qx{../task rc:bench.rc list priority:H}; -qx{../task rc:bench.rc list +tag}; -qx{../task rc:bench.rc list project_A}; -qx{../task rc:bench.rc long}; -qx{../task rc:bench.rc completed}; -qx{../task rc:bench.rc history}; -qx{../task rc:bench.rc ghistory}; +# Report it all. Note that all Timer information is displayed. + +for (1 .. 100) +{ + diag (grep {/^Timer /} qx{../task rc:bench.rc ls}); + diag (grep {/^Timer /} qx{../task rc:bench.rc list}); + diag (grep {/^Timer /} qx{../task rc:bench.rc list priority:H}); + diag (grep {/^Timer /} qx{../task rc:bench.rc list +tag}); + diag (grep {/^Timer /} qx{../task rc:bench.rc list project_A}); + diag (grep {/^Timer /} qx{../task rc:bench.rc long}); + diag (grep {/^Timer /} qx{../task rc:bench.rc completed}); + diag (grep {/^Timer /} qx{../task rc:bench.rc history}); + diag (grep {/^Timer /} qx{../task rc:bench.rc ghistory}); +} # Stop the clock. my $stop = time (); From 3f418c6fdc8e833c5d83a4e1d3d5975347246f13 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 8 Mar 2009 21:29:55 -0400 Subject: [PATCH 059/103] Performance - Made Table::optimize a public method. - Table::optimize called only from handleReportGHistory, where it's needed. - Retaining benchmark.txt, to allow further improvements. --- src/Table.cpp | 26 +++++++++++++++++++++----- src/Table.h | 2 +- src/report.cpp | 6 +++++- src/tests/benchmark.txt | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 src/tests/benchmark.txt diff --git a/src/Table.cpp b/src/Table.cpp index 26ed0bacb..cce934cfe 100644 --- a/src/Table.cpp +++ b/src/Table.cpp @@ -742,7 +742,7 @@ int Table::columnCount () // ^[[31mName^[[0m ^[[31mValue^[[0m -> ^[[31mName Value^[[0m // // This method is a work in progress. -void Table::optimize (std::string& output) +void Table::optimize (std::string& output) const { /* int start = output.length (); @@ -785,14 +785,32 @@ void Table::optimize (std::string& output) 'task ghistory' in 0 seconds Much better. Table::optimize is currently disabled. +*/ size_t i = 0; - while ((i = output.find (" \n")) != std::string::npos) + while ((i = output.find (" \n")) != std::string::npos) + { + output = output.substr (0, i) + + output.substr (i + 8, std::string::npos); + } + + while ((i = output.find (" \n")) != std::string::npos) + { + output = output.substr (0, i) + + output.substr (i + 4, std::string::npos); + } + + while ((i = output.find (" \n")) != std::string::npos) { output = output.substr (0, i) + output.substr (i + 2, std::string::npos); } -*/ + + while ((i = output.find (" \n")) != std::string::npos) + { + output = output.substr (0, i) + + output.substr (i + 1, std::string::npos); + } /* std::cout << int ((100 * (start - output.length ()) / start)) @@ -1066,8 +1084,6 @@ const std::string Table::render () output += "\n"; } - // Eliminate redundant color codes. - optimize (output); return output; } diff --git a/src/Table.h b/src/Table.h index e1d244e14..765c1116a 100644 --- a/src/Table.h +++ b/src/Table.h @@ -85,6 +85,7 @@ public: int rowCount (); int columnCount (); const std::string render (); + void optimize (std::string&) const; private: std::string getCell (const int, const int); @@ -101,7 +102,6 @@ private: const std::string formatHeader (const int, const int, const int); const std::string formatHeaderDashedUnderline (const int, const int, const int); void formatCell (const int, const int, const int, const int, std::vector &, std::string&); - void optimize (std::string&); void sort (std::vector &); void clean (std::string&); diff --git a/src/report.cpp b/src/report.cpp index a762a5c69..f6aa675ca 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1205,7 +1205,11 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf) else out << "No tasks." << std::endl; - return out.str (); + // Eliminate redundant color codes. + std::string optimized = out.str (); + table.optimize (optimized); + + return optimized; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/tests/benchmark.txt b/src/tests/benchmark.txt new file mode 100644 index 000000000..78cfba505 --- /dev/null +++ b/src/tests/benchmark.txt @@ -0,0 +1,40 @@ +3/8/2009 + Before: + Table::render + 26.1792 + 16.67 + 18.9697 + 28.6328 + 1.86553 + 0.00044 + 0.000319 + --------- + 92.317989 + + After Table::optimize removed: + Table::render + 0.146177 + 0.145928 + 0.184444 + 0.014784 + 0.000512 + 0.000267 + --------- + 0.492112 + + Speedup: + 92.317989 / 0.492112 = 187.6 + +3/8/2009 + New benchmark: + 1..4 + ok 1 - Created bench.rc + # start=1236558734 + # 1000 tasks added in 3 seconds + # 600 tasks altered in 29 seconds + # stop=1236558924 + # total=190 + ok 2 - Removed pending.data + ok 3 - Removed completed.data + ok 4 - Removed bench.rc + From 28e997691ff72033c4e1ff94cc90eefc38252d24 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 8 Mar 2009 22:56:47 -0400 Subject: [PATCH 060/103] Unit Tests - repair - Added auto right trim to all table rows, which is a much more efficient way of doing what Table::optimize was doing. - Table::optimize is now a nop. --- src/Table.cpp | 48 ++++++++++------------------------------- src/Table.h | 2 +- src/report.cpp | 8 ++----- src/tests/benchmark.txt | 8 +++---- src/tests/bug.sort.t | 1 - 5 files changed, 18 insertions(+), 49 deletions(-) diff --git a/src/Table.cpp b/src/Table.cpp index cce934cfe..fc0ff1296 100644 --- a/src/Table.cpp +++ b/src/Table.cpp @@ -736,19 +736,14 @@ int Table::columnCount () //////////////////////////////////////////////////////////////////////////////// // Removes extraneous output characters, such as: -// - spaces followed by a newline is collapsed to just a newline, if there is -// no Bg color. // - removal of redundant color codes: // ^[[31mName^[[0m ^[[31mValue^[[0m -> ^[[31mName Value^[[0m // // This method is a work in progress. void Table::optimize (std::string& output) const { -/* - int start = output.length (); -*/ +// int start = output.length (); - // \s\n -> \n /* Well, how about that! @@ -770,7 +765,7 @@ void Table::optimize (std::string& output) const This performance is terrible. To identify the worst offender, Various Timer objects were added in Table::render, assuming that table sorting is the major bottleneck. But no, it is Table::optimize that is the problem. After - commenting out the code below, the results are now: + commenting out this method, the results are now: 1000 tasks added in 3 seconds 600 tasks altered in 29 seconds @@ -784,38 +779,11 @@ void Table::optimize (std::string& output) const 'task history' in 0 seconds 'task ghistory' in 0 seconds - Much better. Table::optimize is currently disabled. + Much better. */ - size_t i = 0; - while ((i = output.find (" \n")) != std::string::npos) - { - output = output.substr (0, i) + - output.substr (i + 8, std::string::npos); - } - - while ((i = output.find (" \n")) != std::string::npos) - { - output = output.substr (0, i) + - output.substr (i + 4, std::string::npos); - } - - while ((i = output.find (" \n")) != std::string::npos) - { - output = output.substr (0, i) + - output.substr (i + 2, std::string::npos); - } - - while ((i = output.find (" \n")) != std::string::npos) - { - output = output.substr (0, i) + - output.substr (i + 1, std::string::npos); - } - -/* - std::cout << int ((100 * (start - output.length ()) / start)) - << "%" << std::endl; -*/ +// std::cout << int ((100 * (start - output.length ()) / start)) +// << "%" << std::endl; } //////////////////////////////////////////////////////////////////////////////// @@ -1077,11 +1045,17 @@ const std::string Table::render () else output += blanks[col]; + // Trim right. + output.erase (output.find_last_not_of (" ") + 1); output += "\n"; } } else + { + // Trim right. + output.erase (output.find_last_not_of (" ") + 1); output += "\n"; + } } return output; diff --git a/src/Table.h b/src/Table.h index 765c1116a..4048dae70 100644 --- a/src/Table.h +++ b/src/Table.h @@ -85,7 +85,6 @@ public: int rowCount (); int columnCount (); const std::string render (); - void optimize (std::string&) const; private: std::string getCell (const int, const int); @@ -104,6 +103,7 @@ private: void formatCell (const int, const int, const int, const int, std::vector &, std::string&); void sort (std::vector &); void clean (std::string&); + void optimize (std::string&) const; private: std::vector mColumns; diff --git a/src/report.cpp b/src/report.cpp index f6aa675ca..afa460821 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1205,11 +1205,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf) else out << "No tasks." << std::endl; - // Eliminate redundant color codes. - std::string optimized = out.str (); - table.optimize (optimized); - - return optimized; + return out.str (); } //////////////////////////////////////////////////////////////////////////////// @@ -2266,7 +2262,7 @@ std::string handleCustomReport ( // Load all pending tasks. std::vector tasks; - tdb.pendingT (tasks); + tdb.allPendingT (tasks); handleRecurrence (tdb, tasks); // Apply filters. diff --git a/src/tests/benchmark.txt b/src/tests/benchmark.txt index 78cfba505..c64d3369e 100644 --- a/src/tests/benchmark.txt +++ b/src/tests/benchmark.txt @@ -29,11 +29,11 @@ New benchmark: 1..4 ok 1 - Created bench.rc - # start=1236558734 + # start=1236565862 # 1000 tasks added in 3 seconds - # 600 tasks altered in 29 seconds - # stop=1236558924 - # total=190 + # 600 tasks altered in 28 seconds + # stop=1236566048 + # total=186 ok 2 - Removed pending.data ok 3 - Removed completed.data ok 4 - Removed bench.rc diff --git a/src/tests/bug.sort.t b/src/tests/bug.sort.t index 0d0d49eac..c1e4588f5 100755 --- a/src/tests/bug.sort.t +++ b/src/tests/bug.sort.t @@ -44,7 +44,6 @@ my $setup = "../task rc:bug_sort.rc add one;" qx{$setup}; my $output = qx{../task rc:bug_sort.rc list}; -#diag ($output); like ($output, qr/three.*(?:one.*two|two.*one)/msi, 'list did not hang'); qx{../task rc:bug_sort.rc 1 priority:H}; From 209f7ffb00c89dbf6254d3398a1769f5150edb98 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 9 Mar 2009 02:52:36 -0400 Subject: [PATCH 061/103] Updated Documentation - Added new platforms to NEWS file. --- NEWS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 7698ef623..8d2345ad2 100644 --- a/NEWS +++ b/NEWS @@ -6,8 +6,10 @@ Task has been built and tested on the following configurations: - OS X 10.5 Leopard - Fedora Core 8 - Fedora Core 9 + - Fedora Core 10 + - Ubuntu 7 Feisty Fawn - Ubuntu 8 Hardy Heron - - Ubuntu 9 Feisty Fawn + - Ubunto 8.10 Intrepid Ibex - Solaris 10 - Cygwin 1.5.25-14 From 9e7844796b89961ddf42998c9eb08d01e2397b80 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 9 Mar 2009 02:53:44 -0400 Subject: [PATCH 062/103] Updated Documentation - Added 'beta' download section to main web page. --- .gitignore | 1 + html/task.html | 32 ++++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 3fb778785..e17fbd40d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ stamp-h1 Makefile configure config.log +www.xls diff --git a/html/task.html b/html/task.html index 64b8cba47..f0e38f23d 100644 --- a/html/task.html +++ b/html/task.html @@ -71,7 +71,7 @@


    -

    Get the Latest Release

    +

    Get the Latest Stable Release

    @@ -116,18 +116,42 @@
  • 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 to help ensure higher quality releases. -
  • Fixed bug that caused performance hit during table rendering. +
  • Fixed bug that caused large performance hit during table rendering.

    (Find out what was new in prior versions)

    - +

    Troubleshooting

    Task has been built from source and tested in the following environments: From 012e47267f523ad8b2d6016da740f50f5656daa3 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 9 Mar 2009 03:06:41 -0400 Subject: [PATCH 063/103] Bug Fix - concatenated description on modify - When a task was modified, the new description was concatenated without spaces. --- src/parse.cpp | 12 ++++++-- src/tests/bug.concat.t | 66 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100755 src/tests/bug.concat.t diff --git a/src/parse.cpp b/src/parse.cpp index 99f2d9187..d6528436c 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -475,12 +475,20 @@ void parse ( if (isCommand (l) && validCommand (l)) command = l; else - descCandidate += arg; + { + if (descCandidate.length ()) + descCandidate += " "; + descCandidate += std::string (arg); + } } // Anything else is just considered description. else - descCandidate += std::string (arg) + " "; + { + if (descCandidate.length ()) + descCandidate += " "; + descCandidate += std::string (arg); + } } } diff --git a/src/tests/bug.concat.t b/src/tests/bug.concat.t new file mode 100755 index 000000000..74a82896f --- /dev/null +++ b/src/tests/bug.concat.t @@ -0,0 +1,66 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'bug_concat.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'bug_concat.rc', 'Created bug_concat.rc'); +} + +# When a task is modified like this: +# +# % task 1 This is a new description +# +# The arguments are concatenated thus: +# +# Thisisanewdescription + +qx{../task rc:bug_concat.rc add This is the original text}; + +my $output = qx{../task rc:bug_concat.rc info 1}; +like ($output, qr/Description\s+This is the original text\n/, 'original correct'); + +qx{../task rc:bug_concat.rc 1 This is the modified text}; +$output = qx{../task rc:bug_concat.rc info 1}; +like ($output, qr/Description\s+This is the modified text\n/, 'modified correct'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'bug_concat.rc'; +ok (!-r 'bug_concat.rc', 'Removed bug_concat.rc'); + +exit 0; + From 751094cffb90458711a4cc357d716351526f93d1 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 9 Mar 2009 03:09:43 -0400 Subject: [PATCH 064/103] Documentation Update - Added recent bug fix details. --- ChangeLog | 1 + html/task.html | 1 + 2 files changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index 1b41b79b0..7b2c3a6ff 100644 --- a/ChangeLog +++ b/ChangeLog @@ -28,6 +28,7 @@ + Task now has a large (and growing) test suite and bug regression tests to help ensure higher quality releases. + Fixed bug that caused performance hit during table rendering. + + Fixed bug that concatenated a modified description without spaces. ------ old releases ------------------------------ diff --git a/html/task.html b/html/task.html index f0e38f23d..dd258f2fc 100644 --- a/html/task.html +++ b/html/task.html @@ -122,6 +122,7 @@

  • Task now has a large (and growing) test suite and bug regression tests to help ensure higher quality releases.
  • Fixed bug that caused large performance hit during table rendering. +
  • Fixed bug that concatenated a modified description without spaces.

    From 17de9fec9f182f8599241f40e530e5f0ea7c249f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 9 Mar 2009 22:01:08 -0400 Subject: [PATCH 065/103] New Column - recur - Added new column 'recur' for use in custom reports. - Implemented Table::ascendingPeriod, Table::descendingPeriod allowing sorting on the recur column. - Added unit tests to both use the new column and test the sorting. - Code cleanup. --- ChangeLog | 2 ++ html/custom.html | 1 + html/task.html | 2 ++ src/Table.cpp | 18 +++++++++++++ src/Table.h | 13 ++++++--- src/report.cpp | 17 ++++++++++++ src/task.h | 2 +- src/tests/recur.t | 67 +++++++++++++++++++++++++++++++++++++++++++++++ src/util.cpp | 15 +++++------ 9 files changed, 125 insertions(+), 12 deletions(-) create mode 100755 src/tests/recur.t diff --git a/ChangeLog b/ChangeLog index 7b2c3a6ff..b25b789ba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -29,6 +29,8 @@ to help ensure higher quality releases. + Fixed bug that caused performance hit during table rendering. + Fixed bug that concatenated a modified description without spaces. + + Added new column 'recur' that displays the recurrence period of any + recurring tasks. This column can be added to any custom report. ------ old releases ------------------------------ diff --git a/html/custom.html b/html/custom.html index 7a970e58e..8b202f5c1 100644 --- a/html/custom.html +++ b/html/custom.html @@ -88,6 +88,7 @@ report.mine.sort=priority-,project+

  • age
  • active
  • tags +
  • recur
  • description diff --git a/html/task.html b/html/task.html index dd258f2fc..6634516dd 100644 --- a/html/task.html +++ b/html/task.html @@ -123,6 +123,8 @@ to help ensure higher quality releases.
  • Fixed bug that caused large performance hit during table rendering.
  • Fixed bug that concatenated a modified description without spaces. +
  • Added new column 'recur' that displays the recurrence period of any + recurring tasks. This column can be added to any custom report.

    diff --git a/src/Table.cpp b/src/Table.cpp index fc0ff1296..071837cdf 100644 --- a/src/Table.cpp +++ b/src/Table.cpp @@ -941,6 +941,24 @@ void Table::sort (std::vector & order) ((std::string)*left == "M" && (std::string)*right == "H")) SWAP break; + + case ascendingPeriod: + if ((std::string)*left == "" && (std::string)*right != "") + break; + else if ((std::string)*left != "" && (std::string)*right == "") + SWAP + else if (convertDuration ((std::string)*left) > convertDuration ((std::string)*right)) + SWAP + break; + + case descendingPeriod: + if ((std::string)*left != "" && (std::string)*right == "") + break; + else if ((std::string)*left == "" && (std::string)*right != "") + SWAP + else if (convertDuration ((std::string)*left) < convertDuration ((std::string)*right)) + SWAP + break; } } } diff --git a/src/Table.h b/src/Table.h index 4048dae70..fe098e896 100644 --- a/src/Table.h +++ b/src/Table.h @@ -37,9 +37,16 @@ class Table { public: enum just {left, center, right}; - enum order {ascendingNumeric, ascendingCharacter, ascendingPriority, - ascendingDate, descendingNumeric, descendingCharacter, - descendingPriority, descendingDate}; + enum order {ascendingNumeric, + ascendingCharacter, + ascendingPriority, + ascendingDate, + ascendingPeriod, + descendingNumeric, + descendingCharacter, + descendingPriority, + descendingDate, + descendingPeriod}; enum sizing {minimum = -1, flexible = 0}; Table (); diff --git a/src/report.cpp b/src/report.cpp index afa460821..6f25c90a4 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -2447,6 +2447,16 @@ std::string handleCustomReport ( table.addCell (row, columnCount, tasks[row].getDescription ()); } + else if (*col == "recur") + { + table.addColumn ("Recur"); + table.setColumnWidth (columnCount, Table::minimum); + table.setColumnJustification (columnCount, Table::right); + + for (unsigned int row = 0; row < tasks.size (); ++row) + table.addCell (row, columnCount, tasks[row].getAttribute ("recur")); + } + // Common to all columns. // Add underline. if (conf.get (std::string ("color"), true) || conf.get (std::string ("_forcecolor"), false)) @@ -2487,6 +2497,12 @@ std::string handleCustomReport ( Table::ascendingDate : Table::descendingDate)); + else if (column == "recur") + table.sortOn (columnIndex[column], + (direction == '+' ? + Table::ascendingPeriod : + Table::descendingPeriod)); + else table.sortOn (columnIndex[column], (direction == '+' ? @@ -2567,6 +2583,7 @@ void validReportColumns (const std::vector & columns) *it != "age" && *it != "active" && *it != "tags" && + *it != "recur" && *it != "description") bad.push_back (*it); diff --git a/src/task.h b/src/task.h index 03a5d5f7d..925b75811 100644 --- a/src/task.h +++ b/src/task.h @@ -126,7 +126,7 @@ void formatTimeDeltaDays (std::string&, time_t); std::string formatSeconds (time_t); const std::string uuid (); const char* optionalBlankLine (Config&); -int convertDuration (std::string&); +int convertDuration (const std::string&); std::string expandPath (const std::string&); #ifdef SOLARIS diff --git a/src/tests/recur.t b/src/tests/recur.t new file mode 100755 index 000000000..96a251bd2 --- /dev/null +++ b/src/tests/recur.t @@ -0,0 +1,67 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 6; + +# Create the rc file. +if (open my $fh, '>', 'recur.rc') +{ + print $fh "data.location=.\n", + "report.asc.columns=id,recur,description\n", + "report.asc.sort=recur+\n", + "report.desc.columns=id,recur,description\n", + "report.desc.sort=recur-\n"; + close $fh; + ok (-r 'recur.rc', 'Created recur.rc'); +} + +# Create a few recurring tasks, and test the sort order of the recur column. +qx{../task rc:recur.rc add due:tomorrow recur:daily first}; +qx{../task rc:recur.rc add due:tomorrow recur:weekly second}; +qx{../task rc:recur.rc add due:tomorrow recur:3d third}; + +my $output = qx{../task rc:recur.rc asc}; +like ($output, qr/first .* third .* second/msx, 'daily 3d weekly'); + +$output = qx{../task rc:recur.rc desc}; +like ($output, qr/second .* third .* first/msx, 'weekly 3d daily'); + +# Cleanup. +unlink 'shadow.txt'; +ok (!-r 'shadow.txt', 'Removed shadow.txt'); + +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'recur.rc'; +ok (!-r 'recur.rc', 'Removed recur.rc'); + +exit 0; + diff --git a/src/util.cpp b/src/util.cpp index bc1bea9f2..7f1e6b911 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -241,9 +241,9 @@ const std::string uuid () //////////////////////////////////////////////////////////////////////////////// // Recognize the following constructs, and return the number of days represented -int convertDuration (std::string& input) +int convertDuration (const std::string& input) { - input = lowerCase (input); + std::string lower_input = lowerCase (input); Date today; std::vector supported; @@ -263,7 +263,7 @@ int convertDuration (std::string& input) supported.push_back ("yearly"); std::vector matches; - if (autoComplete (input, supported, matches) == 1) + if (autoComplete (lower_input, supported, matches) == 1) { std::string found = matches[0]; @@ -279,19 +279,18 @@ int convertDuration (std::string& input) } // Support \d+ d|w|m|q|y - else { // Verify all digits followed by d, w, m, q, or y. - unsigned int length = input.length (); + unsigned int length = lower_input.length (); for (unsigned int i = 0; i < length; ++i) { - if (! isdigit (input[i]) && + if (! isdigit (lower_input[i]) && i == length - 1) { - int number = ::atoi (input.substr (0, i).c_str ()); + int number = ::atoi (lower_input.substr (0, i).c_str ()); - switch (input[length - 1]) + switch (lower_input[length - 1]) { case 'd': return number * 1; break; case 'w': return number * 7; break; From 1999e38ba53456cac1817e585efa649dd1b34968 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 9 Mar 2009 22:12:49 -0400 Subject: [PATCH 066/103] Colorization - color.recurring - Added support for "color.recurring" configuration variable to colorize recurring tasks. - Updated docs. --- ChangeLog | 2 ++ html/config.html | 3 ++- html/task.html | 2 ++ src/Config.cpp | 1 + src/rules.cpp | 57 +++++++++++++++++++++++++++++------------------- 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/ChangeLog b/ChangeLog index b25b789ba..4dc041060 100644 --- a/ChangeLog +++ b/ChangeLog @@ -31,6 +31,8 @@ + Fixed bug that concatenated a modified description without spaces. + Added new column 'recur' that displays the recurrence period of any recurring tasks. This column can be added to any custom report. + + Added support for "color.recurring" configuration variable which + specifies the color of recurring tasks. ------ old releases ------------------------------ diff --git a/html/config.html b/html/config.html index fd4bc71a4..20b211fcf 100644 --- a/html/config.html +++ b/html/config.html @@ -218,7 +218,8 @@ color.pri.L
    color.pri.none
    color.active
    - color.tagged + color.tagged
    + color.recurring

    These are the coloration rules. They correspond to a particular diff --git a/html/task.html b/html/task.html index 6634516dd..04bc81f96 100644 --- a/html/task.html +++ b/html/task.html @@ -125,6 +125,8 @@
  • Fixed bug that concatenated a modified description without spaces.
  • Added new column 'recur' that displays the recurrence period of any recurring tasks. This column can be added to any custom report. +
  • Added support for "color.recurring" configuration variable which + specifies the color of recurring tasks.

    diff --git a/src/Config.cpp b/src/Config.cpp index 64e07b04c..643c0ac16 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -144,6 +144,7 @@ void Config::createDefault (const std::string& home) fprintf (out, "#color.tag.bug=yellow\n"); fprintf (out, "#color.project.garden=on_green\n"); fprintf (out, "#color.keyword.car=on_blue\n"); + fprintf (out, "#color.recurring=on_red\n"); fprintf (out, "#shadow.file=%s/shadow.txt\n", dataDir.c_str ()); fprintf (out, "#shadow.command=list\n"); fprintf (out, "#shadow.notify=on\n"); diff --git a/src/rules.cpp b/src/rules.cpp index f33354c9f..cac04156e 100644 --- a/src/rules.cpp +++ b/src/rules.cpp @@ -89,6 +89,17 @@ void autoColorize ( // Note: fg, bg already contain colors specifically assigned via command. // Note: These rules form a hierarchy - the last rule is king. + // Colorization of the recurring. + if (gsFg["color.recurring"] != Text::nocolor || + gsBg["color.recurring"] != Text::nocolor) + { + if (task.getAttribute ("recur") != "") + { + fg = gsFg["color.recurring"]; + bg = gsBg["color.recurring"]; + } + } + // Colorization of the tagged. if (gsFg["color.tagged"] != Text::nocolor || gsBg["color.tagged"] != Text::nocolor) @@ -157,29 +168,6 @@ void autoColorize ( } } - // Colorization of the due and overdue. - std::string due = task.getAttribute ("due"); - if (due != "") - { - Date dueDate (::atoi (due.c_str ())); - Date now; - Date then (now + conf.get ("due", 7) * 86400); - - // Overdue - if (dueDate < now) - { - fg = gsFg["color.overdue"]; - bg = gsBg["color.overdue"]; - } - - // Imminent - else if (dueDate < then) - { - fg = gsFg["color.due"]; - bg = gsBg["color.due"]; - } - } - // Colorization by tag value. std::map ::iterator it; for (it = gsFg.begin (); it != gsFg.end (); ++it) @@ -223,6 +211,29 @@ void autoColorize ( } } } + + // Colorization of the due and overdue. + std::string due = task.getAttribute ("due"); + if (due != "") + { + Date dueDate (::atoi (due.c_str ())); + Date now; + Date then (now + conf.get ("due", 7) * 86400); + + // Overdue + if (dueDate < now) + { + fg = gsFg["color.overdue"]; + bg = gsBg["color.overdue"]; + } + + // Imminent + else if (dueDate < then) + { + fg = gsFg["color.due"]; + bg = gsBg["color.due"]; + } + } } //////////////////////////////////////////////////////////////////////////////// From 0ff33d1c1690e6ad50b1c499343e66e654b5c23d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 9 Mar 2009 22:53:54 -0400 Subject: [PATCH 067/103] Version command changes - Added color.recurring to the list of valid config values. - Added message to "version" command hinting that folks should look periodically for updated versions of task. Task does not "call home" and check for updates (and never will), and so it is easy to not realize that there may be newer versions of task with bug fixes and new features. --- src/command.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/command.cpp b/src/command.cpp index 03e6e473d..a129323af 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -307,7 +307,9 @@ std::string handleVersion (Config& conf) link.setColumnWidth (0, Table::flexible); link.setColumnJustification (0, Table::left); link.addCell (link.addRow (), 0, - "See http://www.beckingham.net/task.html for the latest releases and a full tutorial."); + "See http://www.beckingham.net/task.html for the latest releases and a " + "full tutorial. New releases containing fixes and enhancements are " + "released frequently."); // Create a table for output. Table table; @@ -363,10 +365,10 @@ std::string handleVersion (Config& conf) // These are the regular configuration variables. std::string recognized = "blanklines color color.active color.due color.overdue color.pri.H " - "color.pri.L color.pri.M color.pri.none color.tagged confirmation curses " - "data.location dateformat default.command default.priority defaultwidth due " - "monthsperline nag newest next oldest project shadow.command shadow.file " - "shadow.notify"; + "color.pri.L color.pri.M color.pri.none color.recurring color.tagged " + "confirmation curses data.location dateformat default.command " + "default.priority defaultwidth due monthsperline nag newest next oldest " + "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 From 6fade845352b83bd45bdb43736e342cc25094e55 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 10 Mar 2009 00:08:40 -0400 Subject: [PATCH 068/103] Unit Tests - color.*, abbreviation - Added unit tests for all auto coloration configuration settings. - Tweaked colorization rule precedence to allow color.due to override the built-in coloration of due tasks. --- src/rules.cpp | 24 +++++++------- src/tests/abbreviation.t | 26 ++++++++++++++-- src/tests/color.active.t | 60 +++++++++++++++++++++++++++++++++++ src/tests/color.due.t | 59 +++++++++++++++++++++++++++++++++++ src/tests/color.keyword.t | 62 +++++++++++++++++++++++++++++++++++++ src/tests/color.overdue.t | 59 +++++++++++++++++++++++++++++++++++ src/tests/color.project.t | 59 +++++++++++++++++++++++++++++++++++ src/tests/color.recurring.t | 59 +++++++++++++++++++++++++++++++++++ src/tests/color.tag.t | 62 +++++++++++++++++++++++++++++++++++++ src/tests/color.tagged.t | 59 +++++++++++++++++++++++++++++++++++ 10 files changed, 515 insertions(+), 14 deletions(-) create mode 100755 src/tests/color.active.t create mode 100755 src/tests/color.due.t create mode 100755 src/tests/color.keyword.t create mode 100755 src/tests/color.overdue.t create mode 100755 src/tests/color.project.t create mode 100755 src/tests/color.recurring.t create mode 100755 src/tests/color.tag.t create mode 100755 src/tests/color.tagged.t diff --git a/src/rules.cpp b/src/rules.cpp index cac04156e..5744dd3ca 100644 --- a/src/rules.cpp +++ b/src/rules.cpp @@ -87,18 +87,7 @@ void autoColorize ( Config& conf) { // Note: fg, bg already contain colors specifically assigned via command. - // Note: These rules form a hierarchy - the last rule is king. - - // Colorization of the recurring. - if (gsFg["color.recurring"] != Text::nocolor || - gsBg["color.recurring"] != Text::nocolor) - { - if (task.getAttribute ("recur") != "") - { - fg = gsFg["color.recurring"]; - bg = gsBg["color.recurring"]; - } - } + // Note: These rules form a hierarchy - the last rule is King. // Colorization of the tagged. if (gsFg["color.tagged"] != Text::nocolor || @@ -234,6 +223,17 @@ void autoColorize ( bg = gsBg["color.due"]; } } + + // Colorization of the recurring. + if (gsFg["color.recurring"] != Text::nocolor || + gsBg["color.recurring"] != Text::nocolor) + { + if (task.getAttribute ("recur") != "") + { + fg = gsFg["color.recurring"]; + bg = gsBg["color.recurring"]; + } + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/tests/abbreviation.t b/src/tests/abbreviation.t index 4d9b27515..18d330e65 100755 --- a/src/tests/abbreviation.t +++ b/src/tests/abbreviation.t @@ -28,7 +28,7 @@ use strict; use warnings; -use Test::More tests => 15; +use Test::More tests => 22; # Create the rc file. if (open my $fh, '>', 'abbrev.rc') @@ -38,7 +38,7 @@ if (open my $fh, '>', 'abbrev.rc') ok (-r 'abbrev.rc', 'Created abbrev.rc'); } -# Test the add command. +# Test the priority attribute abbrevations. qx{../task rc:abbrev.rc add priority:H with}; qx{../task rc:abbrev.rc add without}; @@ -66,6 +66,28 @@ $output = qx{../task rc:abbrev.rc list pri:H}; like ($output, qr/\bwith\b/, 'pri:H with'); unlike ($output, qr/\bwithout\b/, 'pri:H without'); +# Test the version command abbreviations. +$output = qx{../task version}; +like ($output, qr/ABSOLUTELY NO WARRANTY/, 'version'); + +$output = qx{../task versio}; +like ($output, qr/ABSOLUTELY NO WARRANTY/, 'versio'); + +$output = qx{../task versi}; +like ($output, qr/ABSOLUTELY NO WARRANTY/, 'versi'); + +$output = qx{../task vers}; +like ($output, qr/ABSOLUTELY NO WARRANTY/, 'vers'); + +$output = qx{../task ver}; +like ($output, qr/ABSOLUTELY NO WARRANTY/, 'ver'); + +$output = qx{../task ve}; +like ($output, qr/ABSOLUTELY NO WARRANTY/, 've'); + +$output = qx{../task v}; +like ($output, qr/ABSOLUTELY NO WARRANTY/, 'v'); + # Cleanup. unlink 'pending.data'; ok (!-r 'pending.data', 'Removed pending.data'); diff --git a/src/tests/color.active.t b/src/tests/color.active.t new file mode 100755 index 000000000..0ad678223 --- /dev/null +++ b/src/tests/color.active.t @@ -0,0 +1,60 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'color.rc') +{ + print $fh "data.location=.\n", + "color.active=red\n", + "_forcecolor=1\n"; + close $fh; + ok (-r 'color.rc', 'Created color.rc'); +} + +# Test the add command. +qx{../task rc:color.rc add nothing}; +qx{../task rc:color.rc add red}; +qx{../task rc:color.rc start 2}; +my $output = qx{../task rc:color.rc list}; + +like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none'); +like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.active'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'color.rc'; +ok (!-r 'color.rc', 'Removed color.rc'); + +exit 0; + diff --git a/src/tests/color.due.t b/src/tests/color.due.t new file mode 100755 index 000000000..af1f0077e --- /dev/null +++ b/src/tests/color.due.t @@ -0,0 +1,59 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'color.rc') +{ + print $fh "data.location=.\n", + "color.due=red\n", + "_forcecolor=1\n"; + close $fh; + ok (-r 'color.rc', 'Created color.rc'); +} + +# Test the add command. +qx{../task rc:color.rc add due:eoy nothing}; +qx{../task rc:color.rc add due:tomorrow red}; +my $output = qx{../task rc:color.rc list}; + +like ($output, qr/ (?!<\033\[\d\dm) \d{1,2}\/\d{1,2}\/\d{4} (?!>\033\[0m) .* nothing /x, 'none'); +like ($output, qr/ \033\[31m .* red .* \033\[0m/x, 'color.due'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'color.rc'; +ok (!-r 'color.rc', 'Removed color.rc'); + +exit 0; + diff --git a/src/tests/color.keyword.t b/src/tests/color.keyword.t new file mode 100755 index 000000000..4e99e7400 --- /dev/null +++ b/src/tests/color.keyword.t @@ -0,0 +1,62 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 6; + +# Create the rc file. +if (open my $fh, '>', 'color.rc') +{ + print $fh "data.location=.\n", + "color.keyword.red=red\n", + "color.keyword.green=green\n", + "_forcecolor=1\n"; + close $fh; + ok (-r 'color.rc', 'Created color.rc'); +} + +# Test the add command. +qx{../task rc:color.rc add nothing}; +qx{../task rc:color.rc add red}; +qx{../task rc:color.rc add green}; +my $output = qx{../task rc:color.rc list}; + +like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none'); +like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.keyword.red'); +like ($output, qr/ \033\[32m .* green .* \033\[0m /x, 'color.keyword.green'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'color.rc'; +ok (!-r 'color.rc', 'Removed color.rc'); + +exit 0; + diff --git a/src/tests/color.overdue.t b/src/tests/color.overdue.t new file mode 100755 index 000000000..9555a92cd --- /dev/null +++ b/src/tests/color.overdue.t @@ -0,0 +1,59 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'color.rc') +{ + print $fh "data.location=.\n", + "color.overdue=red\n", + "_forcecolor=1\n"; + close $fh; + ok (-r 'color.rc', 'Created color.rc'); +} + +# Test the add command. +qx{../task rc:color.rc add due:tomorrow nothing}; +qx{../task rc:color.rc add due:yesterday red}; +my $output = qx{../task rc:color.rc list}; + +like ($output, qr/ (?!<\033\[\d\dm) \d{1,2}\/\d{1,2}\/\d{4} (?!>\033\[0m) .* nothing /x, 'none'); +like ($output, qr/ \033\[31m .* red .* \033\[0m/x, 'color.overdue'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'color.rc'; +ok (!-r 'color.rc', 'Removed color.rc'); + +exit 0; + diff --git a/src/tests/color.project.t b/src/tests/color.project.t new file mode 100755 index 000000000..2b90ec59c --- /dev/null +++ b/src/tests/color.project.t @@ -0,0 +1,59 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'color.rc') +{ + print $fh "data.location=.\n", + "color.project.x=red\n", + "_forcecolor=1\n"; + close $fh; + ok (-r 'color.rc', 'Created color.rc'); +} + +# Test the add command. +qx{../task rc:color.rc add nothing}; +qx{../task rc:color.rc add project:x red}; +my $output = qx{../task rc:color.rc list}; + +like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none'); +like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.project.red'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'color.rc'; +ok (!-r 'color.rc', 'Removed color.rc'); + +exit 0; + diff --git a/src/tests/color.recurring.t b/src/tests/color.recurring.t new file mode 100755 index 000000000..27990b1aa --- /dev/null +++ b/src/tests/color.recurring.t @@ -0,0 +1,59 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'color.rc') +{ + print $fh "data.location=.\n", + "color.recurring=red\n", + "_forcecolor=1\n"; + close $fh; + ok (-r 'color.rc', 'Created color.rc'); +} + +# Test the add command. +qx{../task rc:color.rc add nothing}; +qx{../task rc:color.rc add due:tomorrow recur:1w red}; +my $output = qx{../task rc:color.rc list}; + +like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none'); +like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.recurring'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'color.rc'; +ok (!-r 'color.rc', 'Removed color.rc'); + +exit 0; + diff --git a/src/tests/color.tag.t b/src/tests/color.tag.t new file mode 100755 index 000000000..da870e39e --- /dev/null +++ b/src/tests/color.tag.t @@ -0,0 +1,62 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 6; + +# Create the rc file. +if (open my $fh, '>', 'color.rc') +{ + print $fh "data.location=.\n", + "color.tag.red=red\n", + "color.tag.green=green\n", + "_forcecolor=1\n"; + close $fh; + ok (-r 'color.rc', 'Created color.rc'); +} + +# Test the add command. +qx{../task rc:color.rc add nothing}; +qx{../task rc:color.rc add +red red}; +qx{../task rc:color.rc add +green green}; +my $output = qx{../task rc:color.rc list}; + +like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none'); +like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.tag.red'); +like ($output, qr/ \033\[32m .* green .* \033\[0m /x, 'color.tag.green'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'color.rc'; +ok (!-r 'color.rc', 'Removed color.rc'); + +exit 0; + diff --git a/src/tests/color.tagged.t b/src/tests/color.tagged.t new file mode 100755 index 000000000..947a467e9 --- /dev/null +++ b/src/tests/color.tagged.t @@ -0,0 +1,59 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'color.rc') +{ + print $fh "data.location=.\n", + "color.tagged=red\n", + "_forcecolor=1\n"; + close $fh; + ok (-r 'color.rc', 'Created color.rc'); +} + +# Test the add command. +qx{../task rc:color.rc add nothing}; +qx{../task rc:color.rc add +tag red}; +my $output = qx{../task rc:color.rc list}; + +like ($output, qr/ (?!<\033\[\d\dm) .* nothing .* (?!>\033\[0m) /x, 'none'); +like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.tagged'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'color.rc'; +ok (!-r 'color.rc', 'Removed color.rc'); + +exit 0; + From 9f278b1ffcbe6735b242c391ba3c433b7ab8fab5 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 10 Mar 2009 00:22:23 -0400 Subject: [PATCH 069/103] Unit Tests - export - Added unit tests to export tasks and compare. --- src/tests/export.t | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100755 src/tests/export.t diff --git a/src/tests/export.t b/src/tests/export.t new file mode 100755 index 000000000..4218a3c10 --- /dev/null +++ b/src/tests/export.t @@ -0,0 +1,72 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 7; + +# Create the rc file. +if (open my $fh, '>', 'export.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'export.rc', 'Created export.rc'); +} + +# Add two tasks, export, examine result. +qx{../task rc:export.rc add priority:H project:A one}; +qx{../task rc:export.rc add +tag1 +tag2 two}; +qx{../task rc:export.rc export ./export.txt}; + +my @lines; +if (open my $fh, '<', './export.txt') +{ + @lines = <$fh>; + close $fh; +} + +my $line1 = qr/'id','uuid','status','tags','entry','start','due','end','project','priority','fg','bg','description'\n/; +my $line2 = qr/'.{8}-.{4}-.{4}-.{4}-.{12}','pending','',\d+,,,,'A','H',,,'one'\n/; +my $line3 = qr/'.{8}-.{4}-.{4}-.{4}-.{12}','pending','tag1 tag2',\d+,,,,,,,,'two'\n/; + +like ($lines[0], $line1, "export line one"); +like ($lines[1], $line2, "export line two"); +like ($lines[2], $line3, "export line three"); + +# Cleanup. +unlink 'export.txt'; +ok (!-r 'export.txt', 'Removed export.txt'); + +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'export.rc'; +ok (!-r 'export.rc', 'Removed export.rc'); + +exit 0; + From dc946e175e0b09f2d1922d5f7b1b6fe7e3fad585 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 10 Mar 2009 14:32:32 -0400 Subject: [PATCH 070/103] Unit Tests - completed, delete - Added unit tests to verify that the completed.data file is not created until the first report is run after the task is marked as done. - Added unit tests to verify that delete/undelete work as expected. --- src/tests/completed.t | 64 +++++++++++++++++++++++++++++++++++ src/tests/delete.t | 77 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100755 src/tests/completed.t create mode 100755 src/tests/delete.t diff --git a/src/tests/completed.t b/src/tests/completed.t new file mode 100755 index 000000000..a53c7e652 --- /dev/null +++ b/src/tests/completed.t @@ -0,0 +1,64 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 6; + +# Create the rc file. +if (open my $fh, '>', 'completed.rc') +{ + print $fh "data.location=.\n", + "confirmation=no\n"; + close $fh; + ok (-r 'completed.rc', 'Created completed.rc'); +} + +# Add two tasks, mark 1 as done, the other as deleted. +qx{../task rc:completed.rc add one}; +qx{../task rc:completed.rc add two}; +qx{../task rc:completed.rc 1 done}; +qx{../task rc:completed.rc 2 delete}; + +# Generate completed report. +my $output = qx{../task rc:completed.rc completed}; +like ($output, qr/one/, 'one -> completed'); +unlike ($output, qr/two/, 'two -> deleted'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'completed.data'; +ok (!-r 'completed.data', 'Removed completed.data'); + +unlink 'completed.rc'; +ok (!-r 'completed.rc', 'Removed completed.rc'); + +exit 0; + diff --git a/src/tests/delete.t b/src/tests/delete.t new file mode 100755 index 000000000..9f36f517b --- /dev/null +++ b/src/tests/delete.t @@ -0,0 +1,77 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 16; + +# Create the rc file. +if (open my $fh, '>', 'undelete.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'undelete.rc', 'Created undelete.rc'); +} + +# Add a task, delete it, undelete it. +my $output = qx{../task rc:undelete.rc add one; ../task rc:undelete.rc info 1}; +ok (-r 'pending.data', 'pending.data created'); +like ($output, qr/Status\s+Pending\n/, 'Pending'); + +$output = qx{../task rc:undelete.rc delete 1; ../task rc:undelete.rc info 1}; +like ($output, qr/Status\s+Deleted\n/, 'Deleted'); +ok (! -r 'completed.data', 'completed.data not created'); + +$output = qx{../task rc:undelete.rc undelete 1; ../task rc:undelete.rc info 1}; +like ($output, qr/Status\s+Pending\n/, 'Pending'); +ok (! -r 'completed.data', 'completed.data not created'); + +$output = qx{../task rc:undelete.rc delete 1; ../task rc:undelete.rc list}; +like ($output, qr/^No matches/, 'No matches'); +ok (-r 'completed.data', 'completed.data created'); + +$output = qx{../task rc:undelete.rc undelete 1}; +like ($output, qr/reliably undeleted/, 'can only be reliable undeleted...'); + +$output = qx{../task rc:undelete.rc info 1}; +like ($output, qr/No matches./, 'no matches'); + +# Cleanup. +ok (-r 'pending.data', 'Need to remove pending.data'); +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +ok (-r 'completed.data', 'Need to remove completed.data'); +unlink 'completed.data'; +ok (!-r 'completed.data', 'Removed completed.data'); + +unlink 'undelete.rc'; +ok (!-r 'undelete.rc', 'Removed undelete.rc'); + +exit 0; + From d174bb11437ac5201d39626c13c9cc23d3a8e8c1 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 10 Mar 2009 15:21:29 -0400 Subject: [PATCH 071/103] Bug Workaround - locking - Added support for the "locking" configuration variable that disables file locking. This can be helpful to folks who use task on Solaris, and store their task data files on an NFS mount. --- ChangeLog | 2 ++ html/config.html | 15 +++++++++++++++ html/task.html | 2 ++ src/Config.cpp | 13 ++++++++----- src/TDB.cpp | 30 ++++++++++++++++++++++-------- src/TDB.h | 3 +++ src/command.cpp | 4 ++-- src/task.cpp | 4 ++++ 8 files changed, 58 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4dc041060..de3524351 100644 --- a/ChangeLog +++ b/ChangeLog @@ -33,6 +33,8 @@ recurring tasks. This column can be added to any custom report. + Added support for "color.recurring" configuration variable which specifies the color of recurring tasks. + + Added support for "locking" configuration variable that controls whether + file locking is used. ------ old releases ------------------------------ diff --git a/html/config.html b/html/config.html index 20b211fcf..c614f3ad0 100644 --- a/html/config.html +++ b/html/config.html @@ -325,6 +325,21 @@ ID Project Pri Description whenever the shadow file is updated by some task command.

  • +
    locking
    +
    +

    + Determines whether task uses file locking when accessing the pending.data + and completed.data files. Default to "on". Solaris users who store + the task data files on an NFS mount may need to set locking to "off". +

    + +

    + Note that setting this value to "off" is dangerous. It means that + another program may write to the task.pending file when task is + attempting to do the same. +

    +
    +

    Note that the command:

    diff --git a/html/task.html b/html/task.html index 04bc81f96..9f4ef60cc 100644 --- a/html/task.html +++ b/html/task.html @@ -127,6 +127,8 @@ recurring tasks. This column can be added to any custom report.
  • Added support for "color.recurring" configuration variable which specifies the color of recurring tasks. +
  • Added support for "locking" configuration variable that controls whether + file locking is used.

    diff --git a/src/Config.cpp b/src/Config.cpp index 643c0ac16..7ac95b1a7 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -132,6 +132,7 @@ void Config::createDefault (const std::string& home) fprintf (out, "color=on\n"); fprintf (out, "due=7\n"); fprintf (out, "nag=You have higher priority tasks.\n"); + fprintf (out, "locking=on\n"); fprintf (out, "color.overdue=bold_red\n"); fprintf (out, "color.due=bold_yellow\n"); @@ -222,11 +223,13 @@ bool Config::get (const std::string& key, bool default_value) { std::string value = lowerCase ((*this)[key]); - if (value == "t" || - value == "true" || - value == "1" || - value == "yes" || - value == "on") + if (value == "t" || + value == "true" || + value == "1" || + value == "yes" || + value == "on" || + value == "enable" || + value == "enabled") return true; return false; diff --git a/src/TDB.cpp b/src/TDB.cpp index 98a67e119..c77febb7a 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -38,6 +38,7 @@ TDB::TDB () : mPendingFile ("") , mCompletedFile ("") , mId (1) +, mNoLock (false) { } @@ -289,6 +290,9 @@ bool TDB::modifyT (const T& t) //////////////////////////////////////////////////////////////////////////////// bool TDB::lock (FILE* file) const { + if (mNoLock) + return true; + return flock (fileno (file), LOCK_EX) ? false : true; } @@ -300,8 +304,9 @@ bool TDB::overwritePending (std::vector & all) if ((out = fopen (mPendingFile.c_str (), "w"))) { int retry = 0; - while (flock (fileno (out), LOCK_EX) && ++retry <= 3) - delay (0.25); + if (!mNoLock) + while (flock (fileno (out), LOCK_EX) && ++retry <= 3) + delay (0.1); std::vector ::iterator it; for (it = all.begin (); it != all.end (); ++it) @@ -322,8 +327,9 @@ bool TDB::writePending (const T& t) if ((out = fopen (mPendingFile.c_str (), "a"))) { int retry = 0; - while (flock (fileno (out), LOCK_EX) && ++retry <= 3) - delay (0.25); + if (!mNoLock) + while (flock (fileno (out), LOCK_EX) && ++retry <= 3) + delay (0.1); fputs (t.compose ().c_str (), out); @@ -342,8 +348,9 @@ bool TDB::writeCompleted (const T& t) if ((out = fopen (mCompletedFile.c_str (), "a"))) { int retry = 0; - while (flock (fileno (out), LOCK_EX) && ++retry <= 3) - delay (0.25); + if (!mNoLock) + while (flock (fileno (out), LOCK_EX) && ++retry <= 3) + delay (0.1); fputs (t.compose ().c_str (), out); @@ -367,8 +374,9 @@ bool TDB::readLockedFile ( if ((in = fopen (file.c_str (), "r"))) { int retry = 0; - while (flock (fileno (in), LOCK_EX) && ++retry <= 3) - delay (0.25); + if (!mNoLock) + while (flock (fileno (in), LOCK_EX) && ++retry <= 3) + delay (0.1); char line[T_LINE_MAX]; while (fgets (line, T_LINE_MAX, in)) @@ -432,4 +440,10 @@ int TDB::nextId () } //////////////////////////////////////////////////////////////////////////////// +void TDB::noLock () +{ + mNoLock = true; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/TDB.h b/src/TDB.h index b354d97f3..49a68fa11 100644 --- a/src/TDB.h +++ b/src/TDB.h @@ -51,6 +51,8 @@ public: int gc (); int nextId (); + void noLock (); + private: bool lock (FILE*) const; bool overwritePending (std::vector &); @@ -62,6 +64,7 @@ private: std::string mPendingFile; std::string mCompletedFile; int mId; + bool mNoLock; }; #endif diff --git a/src/command.cpp b/src/command.cpp index a129323af..0a6e9756a 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 monthsperline nag newest next oldest " - "project shadow.command shadow.file shadow.notify"; + "default.priority defaultwidth due locking monthsperline nag newest next " + "oldest 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/task.cpp b/src/task.cpp index 04c73c3e6..00b21d9cb 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -299,6 +299,10 @@ int main (int argc, char** argv) std::string dataLocation = expandPath (conf.get ("data.location")); tdb.dataDirectory (dataLocation); + // Allow user override of file locking. Solaris/NFS machines may want this. + if (! conf.get ("locking", true)) + tdb.noLock (); + // Check for silly shadow file settings. std::string shadowFile = expandPath (conf.get ("shadow.file")); if (shadowFile != "") From 6d8cb5181fb37725f4064afa0413956ad404def6 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 10 Mar 2009 15:40:48 -0400 Subject: [PATCH 072/103] Bug Fix - unit test tdb.t - Fixed two failing unit tests in tdb.t.cpp, which were both due to incorrect test logic, rather than a TDB bug. --- src/TDB.cpp | 4 ++++ src/tests/tdb.t.cpp | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/TDB.cpp b/src/TDB.cpp index c77febb7a..0ab4ed10a 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -398,6 +398,8 @@ bool TDB::readLockedFile ( } //////////////////////////////////////////////////////////////////////////////// +// Scans the pending tasks for any that are completed or deleted, and if so, +// moves them to the completed.data file. Returns a count of tasks moved. int TDB::gc () { int count = 0; @@ -415,7 +417,9 @@ int TDB::gc () // Some tasks stay in the pending file. if (it->getStatus () == T::pending || it->getStatus () == T::recurring) + { pending.push_back (*it); + } // Others are transferred to the completed file. else diff --git a/src/tests/tdb.t.cpp b/src/tests/tdb.t.cpp index 3676f6517..c874c00fa 100644 --- a/src/tests/tdb.t.cpp +++ b/src/tests/tdb.t.cpp @@ -99,10 +99,9 @@ int main (int argc, char** argv) // Add a new task. T t2; - t2.setId (2); + t2.setId (1); t2.setAttribute ("project", "p2"); t2.setDescription ("task 2"); - t.diag (t2.compose ()); t.ok (tdb.addT (t2), "TDB::addT t2"); // Delete task. From dac1942cad860e2cefb5a925a1372f78091858b6 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 10 Mar 2009 16:12:59 -0400 Subject: [PATCH 073/103] Bug Fix - calendar - Task now displays as many calendars will fit across the window, unless a lower value is specified in the "monthsperline" configuration variable. - Task now obeys the "color" configuration variable when determining whether to add a legend to the calendar output. --- html/config.html | 4 +++- src/Config.cpp | 2 +- src/report.cpp | 47 +++++++++++++++++++++++++++++++++-------------- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/html/config.html b/html/config.html index c614f3ad0..6fb09b51f 100644 --- a/html/config.html +++ b/html/config.html @@ -174,7 +174,9 @@

    monthsperline
    Determines how many months the "task calendar" command - renders across the screen. Defaults to 1. + renders across the screen. Defaults to however many will + fit. If more months that will fit are specified, task will + only show as many that will fit.
    oldest
    diff --git a/src/Config.cpp b/src/Config.cpp index 7ac95b1a7..48f549ea6 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -127,7 +127,7 @@ void Config::createDefault (const std::string& home) fprintf (out, "confirmation=yes\n"); fprintf (out, "next=2\n"); fprintf (out, "dateformat=m/d/Y\n"); - fprintf (out, "monthsperline=2\n"); + fprintf (out, "#monthsperline=2\n"); fprintf (out, "curses=on\n"); fprintf (out, "color=on\n"); fprintf (out, "due=7\n"); diff --git a/src/report.cpp b/src/report.cpp index 6f25c90a4..d4e405723 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1214,11 +1214,11 @@ std::string renderMonths ( int firstYear, const Date& today, std::vector & all, - Config& conf) + Config& conf, + int monthsPerLine) { 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) @@ -1337,6 +1337,26 @@ std::string handleReportCalendar (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 + + // Each month requires 23 text columns width. See how many will actually + // fit. But if a preference is specified, and it fits, use it. + int preferredMonthsPerLine = (conf.get (std::string ("monthsperline"), 0)); + int monthsThatFit = width / 23; + + int monthsPerLine = monthsThatFit; + if (preferredMonthsPerLine != 0 && preferredMonthsPerLine < monthsThatFit) + monthsPerLine = preferredMonthsPerLine; + // Load all the pending tasks. std::vector pending; tdb.allPendingT (pending); @@ -1369,8 +1389,6 @@ std::string handleReportCalendar (TDB& tdb, T& task, Config& conf) out << std::endl; std::string output; - int monthsPerLine = (conf.get ("monthsperline", 1)); - while (yFrom < yTo || (yFrom == yTo && mFrom <= mTo)) { int nextM = mFrom; @@ -1398,7 +1416,7 @@ std::string handleReportCalendar (TDB& tdb, T& task, Config& conf) out << std::endl << optionalBlankLine (conf) - << renderMonths (mFrom, yFrom, today, pending, conf) + << renderMonths (mFrom, yFrom, today, pending, conf, monthsPerLine) << std::endl; mFrom += monthsPerLine; @@ -1409,15 +1427,16 @@ std::string handleReportCalendar (TDB& tdb, T& task, Config& conf) } } - out << "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; + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) + out << "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; return out.str (); } From 1f45e47e367911f87863860fdb67f40c70050e7a Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 10 Mar 2009 16:37:35 -0400 Subject: [PATCH 074/103] Bug Fix - history/ghistory triggered only by add - Fixed bug whereby if a new month rolls around, and no task is added, no row of data is shown in the history or ghistory reports, even though tasks may have been completed or deleted ni the new month. --- src/report.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/report.cpp b/src/report.cpp index d4e405723..c88633ba0 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -813,6 +813,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf) if (task.getStatus () == T::deleted) { epoch = monthlyEpoch (task.getAttribute ("end")); + groups[epoch] = 0; if (deletedGroup.find (epoch) != deletedGroup.end ()) deletedGroup[epoch] = deletedGroup[epoch] + 1; @@ -822,6 +823,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf) else if (task.getStatus () == T::completed) { epoch = monthlyEpoch (task.getAttribute ("end")); + groups[epoch] = 0; if (completedGroup.find (epoch) != completedGroup.end ()) completedGroup[epoch] = completedGroup[epoch] + 1; @@ -852,6 +854,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf) if (task.getStatus () == T::deleted) { epoch = monthlyEpoch (task.getAttribute ("end")); + groups[epoch] = 0; if (deletedGroup.find (epoch) != deletedGroup.end ()) deletedGroup[epoch] = deletedGroup[epoch] + 1; @@ -861,6 +864,8 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf) else if (task.getStatus () == T::completed) { epoch = monthlyEpoch (task.getAttribute ("end")); + groups[epoch] = 0; + if (completedGroup.find (epoch) != completedGroup.end ()) completedGroup[epoch] = completedGroup[epoch] + 1; else @@ -906,9 +911,9 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf) { 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; From 2d07b08260773e7b974f10a2a694bec1ffc13ad1 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 10 Mar 2009 22:21:23 -0400 Subject: [PATCH 075/103] Custom Reports - usage - Added defined custom reports to the usage text. This includes the new "report.X.description" configuration variable. --- html/custom.html | 12 +++++----- src/parse.cpp | 7 +++++- src/task.cpp | 38 ++++++++++++------------------- src/task.h | 1 + src/tests/custom.t | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 30 deletions(-) create mode 100755 src/tests/custom.t diff --git a/html/custom.html b/html/custom.html index 8b202f5c1..a4a64c803 100644 --- a/html/custom.html +++ b/html/custom.html @@ -43,25 +43,27 @@

    More importantly, you can define your own. Here are the - two necessary items in the .taskrc file that define a new + three necessary items in the .taskrc file that define a new report:

    -
    report.mine.columns=id,project,priority,description
    +                
    report.mine.description=Just the essentials
    +report.mine.columns=id,project,priority,description
     report.mine.sort=priority-,project+

    This defines a report, called "mine", that has four columns: id, project, priority and description. It will be sorted on two columns: by descending priority then ascending project. - Because this report is called "mine", it can be run with the - command: + The description that shows up in the task command usage page + is "Just the essentials". Because this report is called + "mine", it can be run with the command:

    % task mine

    - A filter can also be specified like this: + An optional filter can also be specified like this:

    report.mine.filter=priority:H +bug
    diff --git a/src/parse.cpp b/src/parse.cpp index d6528436c..b87ce8dc9 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -164,7 +164,6 @@ void guess (const std::string& type, const char** list, std::string& candidate) candidate = matches[0]; else if (0 == matches.size ()) -// throw std::string ("Unrecognized ") + type + " '" + candidate + "'"; candidate = ""; else @@ -535,4 +534,10 @@ bool isCustomReport (const std::string& report) return false; } //////////////////////////////////////////////////////////////////////////////// +void allCustomReports (std::vector & all) +{ + all = customReports; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/task.cpp b/src/task.cpp index 00b21d9cb..3c1b2f629 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -82,18 +82,6 @@ static void shortUsage (Config& conf) table.addCell (row, 1, "task add [tags] [attrs] desc..."); table.addCell (row, 2, "Adds a new task"); - row = table.addRow (); - table.addCell (row, 1, "task list [tags] [attrs] desc..."); - table.addCell (row, 2, "Lists all tasks matching the specified criteria"); - - row = table.addRow (); - table.addCell (row, 1, "task long [tags] [attrs] desc..."); - table.addCell (row, 2, "Lists all task, all data, matching the specified criteria"); - - row = table.addRow (); - table.addCell (row, 1, "task ls [tags] [attrs] desc..."); - table.addCell (row, 2, "Minimal listing of all tasks matching the specified criteria"); - row = table.addRow (); table.addCell (row, 1, "task completed [tags] [attrs] desc..."); table.addCell (row, 2, "Chronological listing of all completed tasks matching the specified criteria"); @@ -198,6 +186,20 @@ static void shortUsage (Config& conf) table.addCell (row, 1, "task help"); table.addCell (row, 2, "Shows the long usage text"); + // Add custom reports here... + std::vector all; + allCustomReports (all); + foreach (report, all) + { + std::string command = std::string ("task ") + *report + std::string (" [tags] [attrs] desc..."); + std::string description = conf.get ( + std::string ("report.") + *report + ".description", std::string ("(missing description)")); + + row = table.addRow (); + table.addCell (row, 1, command); + table.addCell (row, 2, description); + } + std::cout << table.render () << std::endl << "See http://www.beckingham.net/task.html for the latest releases and a full tutorial." @@ -269,9 +271,6 @@ void loadConfFile (int argc, char** argv, Config& conf) //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { -// TODO Find out what this is, and either promote it to live code, or remove it. -// std::set_terminate (__gnu_cxx::__verbose_terminate_handler); - // Set up randomness. #ifdef HAVE_SRANDOM srandom (time (NULL)); @@ -383,15 +382,6 @@ void nag (TDB& tdb, T& task, Config& conf) } // General form is "if there are no more deserving tasks", suppress the nag. -/* - std::cout << "# task.isOverdue = " << (isOverdue ? "true" : "false") << std::endl; - std::cout << "# task.pri = " << pri << std::endl; - std::cout << "# task.overdue = " << overdue << std::endl; - std::cout << "# pending.high = " << high << std::endl; - std::cout << "# pending.medium = " << medium << std::endl; - std::cout << "# pending.low = " << low << std::endl; -*/ - if (isOverdue ) return; if (pri == 'H' && !overdue ) return; if (pri == 'M' && !overdue && !high ) return; diff --git a/src/task.h b/src/task.h index 925b75811..dffc3ef17 100644 --- a/src/task.h +++ b/src/task.h @@ -59,6 +59,7 @@ bool validPriority (const std::string&); bool validDate (std::string&, Config&); void loadCustomReports (Config&); bool isCustomReport (const std::string&); +void allCustomReports (std::vector &); // task.cpp void gatherNextTasks (const TDB&, T&, Config&, std::vector &, std::vector &); diff --git a/src/tests/custom.t b/src/tests/custom.t new file mode 100755 index 000000000..193e9711e --- /dev/null +++ b/src/tests/custom.t @@ -0,0 +1,56 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 4; + +# Create the rc file. +if (open my $fh, '>', 'custom.rc') +{ + print $fh "data.location=.\n", + "report.foo.description=DESC\n", + "report.foo.columns=id,description\n", + "report.foo.sort=id+\n"; + close $fh; + ok (-r 'custom.rc', 'Created custom.rc'); +} + +# Generate the usage screen, and locate the custom report on it. +my $output = qx{../task rc:custom.rc usage}; +like ($output, qr/task foo \[tags\] \[attrs\] desc\.\.\.\s+DESC\n/m, 'report.foo'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'custom.rc'; +ok (!-r 'custom.rc', 'Removed custom.rc'); + +exit 0; + From 79d644c257139343811924ae3b707a0c994c53a2 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 10 Mar 2009 22:30:15 -0400 Subject: [PATCH 076/103] Unit Tests - custom - Added unit tests to verify correct functioning of custom report filters. --- src/tests/custom.t | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tests/custom.t b/src/tests/custom.t index 193e9711e..5ca345aa0 100755 --- a/src/tests/custom.t +++ b/src/tests/custom.t @@ -28,7 +28,7 @@ use strict; use warnings; -use Test::More tests => 4; +use Test::More tests => 6; # Create the rc file. if (open my $fh, '>', 'custom.rc') @@ -36,7 +36,8 @@ if (open my $fh, '>', 'custom.rc') print $fh "data.location=.\n", "report.foo.description=DESC\n", "report.foo.columns=id,description\n", - "report.foo.sort=id+\n"; + "report.foo.sort=id+\n", + "report.foo.filter=project:A\n"; close $fh; ok (-r 'custom.rc', 'Created custom.rc'); } @@ -45,6 +46,12 @@ if (open my $fh, '>', 'custom.rc') my $output = qx{../task rc:custom.rc usage}; like ($output, qr/task foo \[tags\] \[attrs\] desc\.\.\.\s+DESC\n/m, 'report.foo'); +qx{../task rc:custom.rc add project:A one}; +qx{../task rc:custom.rc add two}; +$output = qx{../task rc:custom.rc foo}; +like ($output, qr/one/, 'custom filter included'); +unlike ($output, qr/two/, 'custom filter excluded'); + # Cleanup. unlink 'pending.data'; ok (!-r 'pending.data', 'Removed pending.data'); From 8c95e82a63629419e718b1c39cb3a7052b2f5131 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 10 Mar 2009 23:06:02 -0400 Subject: [PATCH 077/103] Unit Tests - start/stop/acive - Added unit tests to test the start and stop commands via the active report. --- src/tests/start.t | 73 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100755 src/tests/start.t diff --git a/src/tests/start.t b/src/tests/start.t new file mode 100755 index 000000000..20b7d92bc --- /dev/null +++ b/src/tests/start.t @@ -0,0 +1,73 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 12; + +# Create the rc file. +if (open my $fh, '>', 'start.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'start.rc', 'Created start.rc'); +} + +# Test the add/start/stop commands. +qx{../task rc:start.rc add one}; +qx{../task rc:start.rc add two}; +my $output = qx{../task rc:start.rc active}; +unlike ($output, qr/one/, 'one not active'); +unlike ($output, qr/two/, 'two not active'); + +qx{../task rc:start.rc start 1}; +qx{../task rc:start.rc start 2}; +$output = qx{../task rc:start.rc active}; +like ($output, qr/one/, 'one active'); +like ($output, qr/two/, 'two active'); + +qx{../task rc:start.rc stop 1}; +$output = qx{../task rc:start.rc active}; +unlike ($output, qr/one/, 'one not active'); +like ($output, qr/two/, 'two active'); + +qx{../task rc:start.rc stop 2}; +$output = qx{../task rc:start.rc active}; +unlike ($output, qr/one/, 'one not active'); +unlike ($output, qr/two/, 'two not active'); + +# Cleanup. +ok (-r 'pending.data', 'Need to remove pending.data'); +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'start.rc'; +ok (!-r 'start.rc', 'Removed start.rc'); + +exit 0; + From c35a76401935ed1acff71739be6f16a8d39c1e4f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 12 Mar 2009 22:34:45 -0400 Subject: [PATCH 078/103] Custom Reports - oldest, newest - Added support for the "report.X.limit" configuration variable, to restrict the number of rows a report generates. - Added support for Table::render (limit) to limit the number of rows that are rendered. - Removed "oldest" and "newest" report code. - Added "oldest" and "newest" custom report details to Config.cpp - Updated various documentation. --- ChangeLog | 5 +- html/config.html | 12 -- html/custom.html | 15 ++- html/task.html | 3 +- html/usage.html | 23 ++-- src/Config.cpp | 73 ++++++++---- src/Grid.cpp | 6 +- src/Table.cpp | 10 +- src/Table.h | 2 +- src/command.cpp | 4 +- src/parse.cpp | 5 - src/report.cpp | 300 +---------------------------------------------- src/task.cpp | 10 -- src/task.h | 2 - 14 files changed, 101 insertions(+), 369 deletions(-) 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 &); From 05b5273136924505b52f24be75e6450414f4b034 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 12 Mar 2009 22:56:22 -0400 Subject: [PATCH 079/103] Unit Tests - oldest - Implemented unit tests to verify that the "oldest" report does indeed show the oldest 10 tasks. - Implemented unit tests to verify that the "newest" report does indeed show the newest 10 tasks. --- src/tests/oldest.t | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100755 src/tests/oldest.t diff --git a/src/tests/oldest.t b/src/tests/oldest.t new file mode 100755 index 000000000..395ed7adf --- /dev/null +++ b/src/tests/oldest.t @@ -0,0 +1,89 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 25; + +# Create the rc file. +if (open my $fh, '>', 'oldest.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'oldest.rc', 'Created oldest.rc'); +} + +# Add 11 tasks. Oldest should show 1-10, newest should show 2-11. +diag ("Adding 11 tasks - takes 10 seconds"); +qx{../task rc:oldest.rc add one; sleep 1}; +qx{../task rc:oldest.rc add two; sleep 1}; +qx{../task rc:oldest.rc add three; sleep 1}; +qx{../task rc:oldest.rc add four; sleep 1}; +qx{../task rc:oldest.rc add five; sleep 1}; +qx{../task rc:oldest.rc add six; sleep 1}; +qx{../task rc:oldest.rc add seven; sleep 1}; +qx{../task rc:oldest.rc add eight; sleep 1}; +qx{../task rc:oldest.rc add nine; sleep 1}; +qx{../task rc:oldest.rc add ten; sleep 1}; +qx{../task rc:oldest.rc add eleven}; + +my $output = qx{../task rc:oldest.rc oldest}; +like ($output, qr/one/, 'oldest: one'); +like ($output, qr/two/, 'oldest: two'); +like ($output, qr/three/, 'oldest: three'); +like ($output, qr/four/, 'oldest: four'); +like ($output, qr/five/, 'oldest: five'); +like ($output, qr/six/, 'oldest: six'); +like ($output, qr/seven/, 'oldest: seven'); +like ($output, qr/eight/, 'oldest: eight'); +like ($output, qr/nine/, 'oldest: nine'); +like ($output, qr/ten/, 'oldest: ten'); +unlike ($output, qr/eleven/, 'no: eleven'); + +$output = qx{../task rc:oldest.rc newest}; +unlike ($output, qr/one/, 'no: one'); +like ($output, qr/two/, 'newest: two'); +like ($output, qr/three/, 'newest: three'); +like ($output, qr/four/, 'newest: four'); +like ($output, qr/five/, 'newest: five'); +like ($output, qr/six/, 'newest: six'); +like ($output, qr/seven/, 'newest: seven'); +like ($output, qr/eight/, 'newest: eight'); +like ($output, qr/nine/, 'newest: nine'); +like ($output, qr/ten/, 'newest: ten'); +like ($output, qr/eleven/, 'newest: eleven'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'oldest.rc'; +ok (!-r 'oldest.rc', 'Removed oldest.rc'); + +exit 0; + From 0cfc9c720e21b3d7d550e8e79e3318a5d602c339 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 13 Mar 2009 09:06:23 -0400 Subject: [PATCH 080/103] Compile Bug - missing - Added stdlib.h to Grid.cpp, thanks to Benjamin Tegardin. --- AUTHORS | 1 + src/Grid.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index 7fcc9c14b..825c87b96 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,4 +24,5 @@ With thanks to: Russell Friesenhahn Paolo Marsi Eric Farris + Benjamin Tegarden diff --git a/src/Grid.cpp b/src/Grid.cpp index bd1f0f6f5..59e366b32 100644 --- a/src/Grid.cpp +++ b/src/Grid.cpp @@ -60,6 +60,7 @@ //////////////////////////////////////////////////////////////////////////////// #include +#include #include //////////////////////////////////////////////////////////////////////////////// From 28c97f181a6e6a3820a52a6c15a806c72bbe3f2d Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 13 Mar 2009 10:35:17 -0400 Subject: [PATCH 081/103] Grammar - Changed wording of the help output. --- src/command.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command.cpp b/src/command.cpp index 4197810ba..769fa4af1 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -309,7 +309,7 @@ std::string handleVersion (Config& conf) link.addCell (link.addRow (), 0, "See http://www.beckingham.net/task.html for the latest releases and a " "full tutorial. New releases containing fixes and enhancements are " - "released frequently."); + "made frequently."); // Create a table for output. Table table; From 2216eee67809f567bf9c3d41d27c8f2b8f322a32 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 13 Mar 2009 13:12:34 -0400 Subject: [PATCH 082/103] Help Consistency - Added note about frequent releases to the shortUsage output, so it is now consistent with that of "version". --- src/task.cpp | 56 +++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/task.cpp b/src/task.cpp index eacaaf7f7..4c958684d 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -194,7 +194,9 @@ static void shortUsage (Config& conf) std::cout << table.render () << std::endl - << "See http://www.beckingham.net/task.html for the latest releases and a full tutorial." + << "See http://www.beckingham.net/task.html for the latest releases and a " + << "full tutorial. New releases containing fixes and enhancements are " + << "made frequently." << std::endl << std::endl; } @@ -205,32 +207,32 @@ static void longUsage (Config& conf) shortUsage (conf); std::cout - << "ID is the numeric identifier displayed by the 'task list' command" << "\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" - << " rc: Alternate .taskrc file" << "\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" - << "Many characters have special meaning to the shell, including:" << "\n" - << " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~" << "\n" + << "ID is the numeric identifier displayed by the 'task list' command." << "\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" + << " rc: Alternate .taskrc file" << "\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" + << "Many characters have special meaning to the shell, including:" << "\n" + << " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~" << "\n" << std::endl; } From 4a524a220e26ba34050d7f667e08eb7900daae60 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Mar 2009 00:21:42 -0400 Subject: [PATCH 083/103] Bug Fix - default command, default unit test - Task runs the default command when no arguments are provided, but when an "rc:..." argument is provided, it does not run the default command. - Implemented unit tests to verify the functioning of default commands, default project and default priority. --- src/task.cpp | 4 ++- src/tests/default.t | 83 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100755 src/tests/default.t diff --git a/src/task.cpp b/src/task.cpp index 4c958684d..132879e4c 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -774,7 +774,9 @@ std::string runTaskCommand ( // If argc == 1 and the default.command configuration variable is set, // then use that, otherwise stick with argc/argv. std::string defaultCommand = conf.get ("default.command"); - if (args.size () == 0 && defaultCommand != "") + if ((args.size () == 0 || + (args.size () == 1 && args[0].substr (0, 3) == "rc:")) && + defaultCommand != "") { // Stuff the command line. args.clear (); diff --git a/src/tests/default.t b/src/tests/default.t new file mode 100755 index 000000000..6851b0063 --- /dev/null +++ b/src/tests/default.t @@ -0,0 +1,83 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 16; + +# Create the rc file. +if (open my $fh, '>', 'default.rc') +{ + print $fh "data.location=.\n", + "default.command=list\n", + "default.project=PROJECT\n", + "default.priority=M\n"; + close $fh; + ok (-r 'default.rc', 'Created default.rc'); +} + +# Set up a default command, project and priority. +qx{../task rc:default.rc add all defaults}; +my $output = qx{../task rc:default.rc list}; +like ($output, qr/ all defaults/, 'task added'); +like ($output, qr/ PROJECT /, 'default project added'); +like ($output, qr/ M /, 'default priority added'); +unlink 'pending.data'; + +qx{../task rc:default.rc add project:specific priority:L all specified}; +$output = qx{../task rc:default.rc list}; +like ($output, qr/ all specified/, 'task added'); +like ($output, qr/ specific /, 'project specified'); +like ($output, qr/ L /, 'priority specified'); +unlink 'pending.data'; + +qx{../task rc:default.rc add project:specific project specified}; +$output = qx{../task rc:default.rc list}; +like ($output, qr/ project specified/, 'task added'); +like ($output, qr/ specific /, 'project specified'); +like ($output, qr/ M /, 'default priority added'); +unlink 'pending.data'; + +qx{../task rc:default.rc add priority:L priority specified}; +$output = qx{../task rc:default.rc list}; +like ($output, qr/ priority specified/, 'task added'); +like ($output, qr/ PROJECT /, 'default project added'); +like ($output, qr/ L /, 'priority specified'); + +$output = qx{../task rc:default.rc}; +like ($output, qr/1 PROJECT L .+ priority specified/, 'default command worked'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'default.rc'; +ok (!-r 'default.rc', 'Removed default.rc'); + +exit 0; + From 7c87bbc19ae1f729189ebb4f3fb46a5ece463a23 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Mar 2009 12:02:33 -0400 Subject: [PATCH 084/103] Unit Tests - dateformat - Unit tests determine whether the dateformat configuration variable determines how dates are parsed, and how dates are rendered. --- src/tests/dateformat.t | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100755 src/tests/dateformat.t diff --git a/src/tests/dateformat.t b/src/tests/dateformat.t new file mode 100755 index 000000000..83fe0b293 --- /dev/null +++ b/src/tests/dateformat.t @@ -0,0 +1,54 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'date.rc') +{ + print $fh "data.location=.\n", + "dateformat=YMD\n"; + close $fh; + ok (-r 'date.rc', 'Created date.rc'); +} + +qx{../task rc:date.rc add foo due:20091231}; +my $output = qx{../task rc:date.rc info 1}; +like ($output, qr/\b20091231\b/, 'date format YMD parsed'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'date.rc'; +ok (!-r 'date.rc', 'Removed date.rc'); + +exit 0; + From 64cfc26ff3c3dd149f21fceec4fd90cc7dcca55b Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Mar 2009 12:04:25 -0400 Subject: [PATCH 085/103] Enhanced Stats Report - now reports number of unique tags (given filtering) - now reports number of unique projects (given filtering) --- src/report.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/report.cpp b/src/report.cpp index ff26b34c8..5820bd7c5 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1699,6 +1699,8 @@ std::string handleReportStats (TDB& tdb, T& task, Config& conf) 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) @@ -1727,6 +1729,13 @@ std::string handleReportStats (TDB& tdb, T& task, Config& conf) std::vector tags; it->getTags (tags); if (tags.size ()) ++taggedT; + + foreach (t, tags) + allTags[*t] = 0; + + std::string project = it->getAttribute ("project"); + if (project != "") + allProjects[project] = 0; } out << "Pending " << pendingT << std::endl @@ -1764,6 +1773,9 @@ std::string handleReportStats (TDB& tdb, T& task, Config& conf) out << "Tasks tagged " << std::setprecision (3) << (100.0 * taggedT / totalT) << "%" << std::endl; } + out << "Unique tags " << allTags.size () << std::endl; + out << "Projects " << allProjects.size () << std::endl; + return out.str (); } From c9a6d2a7504f0c1985c70e679fd44c25c2f11528 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Mar 2009 12:05:32 -0400 Subject: [PATCH 086/103] Improved GC and Shadow File Handling - Every command now returns an output string, or at least has an opportunity to do so. - TDB::gc is only performed a) when allowed, and b) when the command will display line numbers. - updateShadowFile is only performed when a) shadow updates are allowed, and either b) when a command is guaranteed to have modified a task or c) when TDB:gc has already made changes. --- src/command.cpp | 23 ++++++-- src/task.cpp | 150 +++++++++++++++++++++++++++--------------------- src/task.h | 8 +-- 3 files changed, 107 insertions(+), 74 deletions(-) diff --git a/src/command.cpp b/src/command.cpp index 769fa4af1..16740bf59 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -48,8 +48,10 @@ #endif //////////////////////////////////////////////////////////////////////////////// -void handleAdd (TDB& tdb, T& task, Config& conf) +std::string handleAdd (TDB& tdb, T& task, Config& conf) { + std::stringstream out; + char entryTime[16]; sprintf (entryTime, "%u", (unsigned int) time (NULL)); task.setAttribute ("entry", entryTime); @@ -86,6 +88,8 @@ void handleAdd (TDB& tdb, T& task, Config& conf) if (!tdb.addT (task)) throw std::string ("Could not create new task."); + + return out.str (); } //////////////////////////////////////////////////////////////////////////////// @@ -547,8 +551,10 @@ std::string handleStop (TDB& tdb, T& task, Config& conf) } //////////////////////////////////////////////////////////////////////////////// -void handleDone (TDB& tdb, T& task, Config& conf) +std::string handleDone (TDB& tdb, T& task, Config& conf) { + std::stringstream out; + if (!tdb.completeT (task)) throw std::string ("Could not mark task as completed."); @@ -566,11 +572,14 @@ void handleDone (TDB& tdb, T& task, Config& conf) } nag (tdb, task, conf); + return out.str (); } //////////////////////////////////////////////////////////////////////////////// -void handleExport (TDB& tdb, T& task, Config& conf) +std::string handleExport (TDB& tdb, T& task, Config& conf) { + std::stringstream out; + // Use the description as a file name, then clobber the description so the // file name isn't used for filtering. std::string file = trim (task.getDescription ()); @@ -610,11 +619,14 @@ void handleExport (TDB& tdb, T& task, Config& conf) } else throw std::string ("You must specify a file to write to."); + + return out.str (); } //////////////////////////////////////////////////////////////////////////////// -void handleModify (TDB& tdb, T& task, Config& conf) +std::string handleModify (TDB& tdb, T& task, Config& conf) { + std::stringstream out; std::vector all; tdb.pendingT (all); @@ -695,11 +707,12 @@ void handleModify (TDB& tdb, T& task, Config& conf) tdb.modifyT (original); } - return; + return out.str (); } } throw std::string ("Task not found."); + return out.str (); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/task.cpp b/src/task.cpp index 132879e4c..d31952f02 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -47,8 +48,9 @@ #endif //////////////////////////////////////////////////////////////////////////////// -static void shortUsage (Config& conf) +static std::string shortUsage (Config& conf) { + std::stringstream out; Table table; int width = conf.get ("defaultwidth", 80); #ifdef HAVE_LIBNCURSES @@ -192,48 +194,51 @@ static void shortUsage (Config& conf) table.addCell (row, 2, description); } - std::cout << table.render () - << std::endl - << "See http://www.beckingham.net/task.html for the latest releases and a " - << "full tutorial. New releases containing fixes and enhancements are " - << "made frequently." - << std::endl - << std::endl; + out << table.render () + << std::endl + << "See http://www.beckingham.net/task.html for the latest releases and a " + << "full tutorial. New releases containing fixes and enhancements are " + << "made frequently." + << std::endl + << std::endl; + + return out.str (); } //////////////////////////////////////////////////////////////////////////////// -static void longUsage (Config& conf) +static std::string longUsage (Config& conf) { - shortUsage (conf); + std::stringstream out; + out << shortUsage (conf) + << "ID is the numeric identifier displayed by the 'task list' command." << "\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" + << " rc: Alternate .taskrc file" << "\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" + << "Many characters have special meaning to the shell, including:" << "\n" + << " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~" << "\n" + << std::endl; - std::cout - << "ID is the numeric identifier displayed by the 'task list' command." << "\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" - << " rc: Alternate .taskrc file" << "\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" - << "Many characters have special meaning to the shell, including:" << "\n" - << " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~" << "\n" - << std::endl; + return out.str (); } //////////////////////////////////////////////////////////////////////////////// @@ -790,34 +795,49 @@ std::string runTaskCommand ( T task; parse (args, command, task, conf); - std::string out = ""; + bool gcMod = false; // Change occurred by way of gc. + bool cmdMod = false; // Change occurred by way of command type. + std::string out; - if (command == "" && task.getId ()) { handleModify (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - else if (command == "add") { handleAdd (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - else if (command == "done") { handleDone (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - else if (command == "export") { handleExport (tdb, task, conf ); } - else if (command == "projects") { out = handleProjects (tdb, task, conf ); } - else if (command == "tags") { out = handleTags (tdb, task, conf ); } - else if (command == "info") { out = handleInfo (tdb, task, conf ); } - else if (command == "undelete") { out = handleUndelete (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - else if (command == "delete") { out = handleDelete (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - else if (command == "start") { out = handleStart (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - else if (command == "stop") { out = handleStop (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - else if (command == "undo") { out = handleUndo (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - else if (command == "stats") { out = handleReportStats (tdb, task, conf ); } - else if (command == "completed") { if (gc) tdb.gc (); out = handleCompleted (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } // TODO replace with Custom - else if (command == "summary") { if (gc) tdb.gc (); out = handleReportSummary (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - else if (command == "next") { if (gc) tdb.gc (); out = handleReportNext (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } // TODO replace with Custom - else if (command == "history") { if (gc) tdb.gc (); out = handleReportHistory (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - else if (command == "ghistory") { if (gc) tdb.gc (); out = handleReportGHistory (tdb, task, conf ); if (shadow) updateShadowFile (tdb, conf); } - 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 == "colors") { out = handleColor ( conf ); } - else if (command == "version") { out = handleVersion ( conf ); } - else if (command == "help") { longUsage ( conf ); } - else if (isCustomReport (command)) { if (gc) tdb.gc (); out = handleCustomReport (tdb, task, conf, command); if (shadow) updateShadowFile (tdb, conf); } // New Custom reports - else { shortUsage ( conf ); } + // Read-only commands with no side effects. + if (command == "export") { out = handleExport (tdb, task, conf); } + else if (command == "projects") { out = handleProjects (tdb, task, conf); } + else if (command == "tags") { out = handleTags (tdb, task, conf); } + else if (command == "info") { out = handleInfo (tdb, task, conf); } + else if (command == "stats") { out = handleReportStats (tdb, task, conf); } + else if (command == "history") { out = handleReportHistory (tdb, task, conf); } + else if (command == "ghistory") { out = handleReportGHistory (tdb, task, conf); } + else if (command == "calendar") { out = handleReportCalendar (tdb, task, conf); } + else if (command == "summary") { out = handleReportSummary (tdb, task, conf); } + else if (command == "colors") { out = handleColor ( conf); } + else if (command == "version") { out = handleVersion ( conf); } + else if (command == "help") { out = longUsage ( conf); } + + // Commands that cause updates. + else if (command == "" && task.getId ()) { cmdMod = true; out = handleModify (tdb, task, conf); } + else if (command == "add") { cmdMod = true; out = handleAdd (tdb, task, conf); } + else if (command == "done") { cmdMod = true; out = handleDone (tdb, task, conf); } + else if (command == "undelete") { cmdMod = true; out = handleUndelete (tdb, task, conf); } + else if (command == "delete") { cmdMod = true; out = handleDelete (tdb, task, conf); } + else if (command == "start") { cmdMod = true; out = handleStart (tdb, task, conf); } + else if (command == "stop") { cmdMod = true; out = handleStop (tdb, task, conf); } + else if (command == "undo") { cmdMod = true; out = handleUndo (tdb, task, conf); } + + // Command that display IDs and therefore need TDB::gc first. + + else if (command == "completed") { if (gc) gcMod = tdb.gc (); out = handleCompleted (tdb, task, conf); } + else if (command == "next") { if (gc) gcMod = tdb.gc (); out = handleReportNext (tdb, task, conf); } + else if (command == "active") { if (gc) gcMod = tdb.gc (); out = handleReportActive (tdb, task, conf); } + else if (command == "overdue") { if (gc) gcMod = tdb.gc (); out = handleReportOverdue (tdb, task, conf); } + else if (isCustomReport (command)) { if (gc) gcMod = tdb.gc (); out = handleCustomReport (tdb, task, conf, command); } + + // If the command is not recognized, display usage. + else { out = shortUsage (conf); } + + // Only update the shadow file if such an update was not suppressed (shadow), + // and if an actual change occurred (gcMod || cmdMod). + if (shadow && (gcMod || cmdMod)) + updateShadowFile (tdb, conf); return out; } diff --git a/src/task.h b/src/task.h index 05b710497..5ddd81f6f 100644 --- a/src/task.h +++ b/src/task.h @@ -74,10 +74,10 @@ std::string runTaskCommand (int, char**, TDB&, Config&, bool gc = true, bool sha std::string runTaskCommand (std::vector &, TDB&, Config&, bool gc = false, bool shadow = false); // command.cpp -void handleAdd (TDB&, T&, Config&); -void handleExport (TDB&, T&, Config&); -void handleDone (TDB&, T&, Config&); -void handleModify (TDB&, T&, Config&); +std::string handleAdd (TDB&, T&, Config&); +std::string handleExport (TDB&, T&, Config&); +std::string handleDone (TDB&, T&, Config&); +std::string handleModify (TDB&, T&, Config&); std::string handleProjects (TDB&, T&, Config&); std::string handleTags (TDB&, T&, Config&); std::string handleUndelete (TDB&, T&, Config&); From 8ac3978222e05e3ab09e01dcb59506bfb1912ec5 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Mar 2009 12:54:11 -0400 Subject: [PATCH 087/103] Unit Tests - dateformat, shadow - Improved dateformat.t tests to cover all acceptable date format characters. - Unit tests for shadow file update notification. - Unit tests for shadow file updates under certain circumstances. - Unit tests for shadow file no updates under other circumstances. --- src/tests/dateformat.t | 32 ++++++++++--- src/tests/shadow.t | 101 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 7 deletions(-) create mode 100755 src/tests/shadow.t diff --git a/src/tests/dateformat.t b/src/tests/dateformat.t index 83fe0b293..c08cdb884 100755 --- a/src/tests/dateformat.t +++ b/src/tests/dateformat.t @@ -28,27 +28,45 @@ use strict; use warnings; -use Test::More tests => 5; +use Test::More tests => 8; # Create the rc file. -if (open my $fh, '>', 'date.rc') +if (open my $fh, '>', 'date1.rc') { print $fh "data.location=.\n", "dateformat=YMD\n"; close $fh; - ok (-r 'date.rc', 'Created date.rc'); + ok (-r 'date1.rc', 'Created date1.rc'); } -qx{../task rc:date.rc add foo due:20091231}; -my $output = qx{../task rc:date.rc info 1}; +if (open my $fh, '>', 'date2.rc') +{ + print $fh "data.location=.\n", + "dateformat=m/d/y\n"; + close $fh; + ok (-r 'date2.rc', 'Created date2.rc'); +} + +qx{../task rc:date1.rc add foo due:20091231}; +my $output = qx{../task rc:date1.rc info 1}; like ($output, qr/\b20091231\b/, 'date format YMD parsed'); +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +qx{../task rc:date2.rc add foo due:12/1/09}; +$output = qx{../task rc:date2.rc info 1}; +like ($output, qr/\b12\/1\/09\b/, 'date format m/d/y parsed'); + # Cleanup. unlink 'pending.data'; ok (!-r 'pending.data', 'Removed pending.data'); -unlink 'date.rc'; -ok (!-r 'date.rc', 'Removed date.rc'); +unlink 'date1.rc'; +ok (!-r 'date1.rc', 'Removed date1.rc'); + +unlink 'date2.rc'; +ok (!-r 'date2.rc', 'Removed date2.rc'); exit 0; diff --git a/src/tests/shadow.t b/src/tests/shadow.t new file mode 100755 index 000000000..abea89205 --- /dev/null +++ b/src/tests/shadow.t @@ -0,0 +1,101 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 21; + +# Create the rc file. +if (open my $fh, '>', 'shadow.rc') +{ + print $fh "data.location=.\n", + "shadow.file=./shadow.txt\n", + "shadow.command=stats\n", + "shadow.notify=on\n"; + close $fh; + ok (-r 'shadow.rc', 'Created shadow.rc'); +} + +my $output = qx{../task rc:shadow.rc add one}; +like ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file updated on add'); + +$output = qx{../task rc:shadow.rc list}; +unlike ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file not updated on list'); + +$output = qx{../task rc:shadow.rc delete 1}; +like ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file updated on delete'); + +$output = qx{../task rc:shadow.rc list}; +like ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file updated on list'); + +# Inspect the shadow file. +my $file = slurp ('./shadow.txt'); +like ($file, qr/Pending\s+0\n/, 'Pending 0'); +like ($file, qr/Recurring\s+0\n/, 'Recurring 0'); +like ($file, qr/Completed\s+0\n/, 'Completed 0'); +like ($file, qr/Deleted\s+1\n/, 'Deleted 1'); +like ($file, qr/Total\s+1\n/, 'Total 1'); +like ($file, qr/Task used for\s+-\n/, 'Task used for -'); +like ($file, qr/Task added every\s+-\n/, 'Task added every -'); +like ($file, qr/Task deleted every\s+-\n/, 'Task deleted every -'); +like ($file, qr/Average desc length\s+3 characters\n/, 'Average desc length 3 characters'); +like ($file, qr/Tasks tagged\s+0%\n/, 'Tasks tagged 0%'); +like ($file, qr/Unique tags\s+0\n/, 'Unique tags 0'); +like ($file, qr/Projects\s+0\n/, 'Projects 0'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'completed.data'; +ok (!-r 'completed.data', 'Removed completed.data'); + +unlink 'shadow.rc'; +ok (!-r 'shadow.rc', 'Removed shadow.rc'); + +unlink 'shadow.txt'; +ok (!-r 'shadow.txt', 'Removed shadow.txt'); + +exit 0; + +################################################################################ +sub slurp +{ + my ($file) = @_; + local $/; + + if (open my $fh, '<', $file) + { + my $contents = <$fh>; + close $fh; + return $contents; + } + + ''; +} + From 5383943fa72f4f110a743bbd931ab5ec36ee094c Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Mar 2009 13:36:32 -0400 Subject: [PATCH 088/103] Enhanced export command - Now sanitizes output by replacing ' with " in descriptions. - Added 'recur' attribute to exported output. - Removed recurring, deleted and complete tasks from the export. --- src/T.cpp | 11 ++++++++++- src/command.cpp | 17 +++++++++++++---- src/util.cpp | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/T.cpp b/src/T.cpp index 61acd52f4..f490d8681 100644 --- a/src/T.cpp +++ b/src/T.cpp @@ -328,6 +328,11 @@ const std::string T::composeCSV () line += value; line += ","; + value = mAttributes["recur"]; + if (value != "") + line += value; + line += ","; + value = mAttributes["end"]; if (value != "") line += value; @@ -353,7 +358,11 @@ const std::string T::composeCSV () line += "'" + value + "'"; line += ","; - line += "'" + mDescription + "'\n"; + // Convert single quotes to double quotes, because single quotes are used to + // delimit the values that need it. + std::string clean = mDescription; + std::replace (clean.begin (), clean.end (), '\'', '"'); + line += "'" + clean + "'\n"; return line; } diff --git a/src/command.cpp b/src/command.cpp index 16740bf59..72724f668 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -578,7 +578,7 @@ std::string handleDone (TDB& tdb, T& task, Config& conf) //////////////////////////////////////////////////////////////////////////////// std::string handleExport (TDB& tdb, T& task, Config& conf) { - std::stringstream out; + std::stringstream output; // Use the description as a file name, then clobber the description so the // file name isn't used for filtering. @@ -597,6 +597,7 @@ std::string handleExport (TDB& tdb, T& task, Config& conf) << "'entry'," << "'start'," << "'due'," + << "'recur'," << "'end'," << "'project'," << "'priority'," @@ -605,14 +606,22 @@ std::string handleExport (TDB& tdb, T& task, Config& conf) << "'description'" << "\n"; + int count = 0; std::vector all; - tdb.allT (all); + tdb.allPendingT (all); filter (all, task); foreach (t, all) { - out << t->composeCSV ().c_str (); + if (t->getStatus () != T::recurring && + t->getStatus () != T::deleted) + { + out << t->composeCSV ().c_str (); + ++count; + } } out.close (); + + output << count << " tasks exported to '" << file << "'" << std::endl; } else throw std::string ("Could not write to export file."); @@ -620,7 +629,7 @@ std::string handleExport (TDB& tdb, T& task, Config& conf) else throw std::string ("You must specify a file to write to."); - return out.str (); + return output.str (); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/util.cpp b/src/util.cpp index 7f1e6b911..a114c9a0b 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -187,6 +187,7 @@ static char randomHexDigit () { static char digits[] = "0123456789abcdef"; #ifdef HAVE_RANDOM + // random is better than rand. return digits[random () % 16]; #else return digits[rand () % 16]; From bdd1b16ba0bf255d55a87a980c72a6a27d1af784 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Mar 2009 13:38:39 -0400 Subject: [PATCH 089/103] Documentation Update - Updated docs to reflect recent changes. --- ChangeLog | 2 ++ html/task.html | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 7619471c7..1a0f7a89b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -36,6 +36,8 @@ specifies the color of recurring tasks. + Added support for "locking" configuration variable that controls whether file locking is used. + + Task export feature now includes recurrence information, removes nested + quotes, and limits output to pending tasks. ------ old releases ------------------------------ diff --git a/html/task.html b/html/task.html index 956c26730..fa0f4ded2 100644 --- a/html/task.html +++ b/html/task.html @@ -130,6 +130,8 @@ specifies the color of recurring tasks.
  • Added support for "locking" configuration variable that controls whether file locking is used. +
  • Task export feature now includes recurrence information, removes nested + quotes, and limits output to pending tasks.

    From 2d2bd4707556a34fd36a210d5fec631a6f844074 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 14 Mar 2009 13:47:48 -0400 Subject: [PATCH 090/103] Bug Fix - summary report included deleted tasks - Applied patch from Benjamin Tegarden to exclude deleted tasks from the summary report. --- AUTHORS | 2 +- ChangeLog | 2 ++ html/task.html | 2 ++ src/report.cpp | 9 +++++++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 825c87b96..bab7cef3e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -7,6 +7,7 @@ Contributing Authors: H. İbrahim Güngör Stefan Dorn Michael Greb + Benjamin Tegarden With thanks to: Eugene Kramer @@ -24,5 +25,4 @@ With thanks to: Russell Friesenhahn Paolo Marsi Eric Farris - Benjamin Tegarden diff --git a/ChangeLog b/ChangeLog index 1a0f7a89b..bf4907020 100644 --- a/ChangeLog +++ b/ChangeLog @@ -38,6 +38,8 @@ file locking is used. + Task export feature now includes recurrence information, removes nested quotes, and limits output to pending tasks. + + Task no longer includes deleted tasks in the summary report (thanks to + Benjamin Tegarden). ------ old releases ------------------------------ diff --git a/html/task.html b/html/task.html index fa0f4ded2..ed65b903a 100644 --- a/html/task.html +++ b/html/task.html @@ -132,6 +132,8 @@ file locking is used.

  • Task export feature now includes recurrence information, removes nested quotes, and limits output to pending tasks. +
  • Task no longer includes deleted tasks in the summary report (thanks to + Benjamin Tegarden).

    diff --git a/src/report.cpp b/src/report.cpp index 5820bd7c5..606a4e3ab 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -496,13 +496,18 @@ std::string handleReportSummary (TDB& tdb, T& task, Config& conf) { T task (completed[i]); std::string project = task.getAttribute ("project"); - countCompleted[project] = countCompleted[project] + 1; - ++counter[project]; + if (task.getStatus () == T::deleted) + continue; + + ++countCompleted[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); + ++counter[project]; + } } // Create a table for output. From df82fade2ca1e1badf835150f5c272d1a199b5df Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 11:31:27 -0400 Subject: [PATCH 091/103] Unit Tests - bug.summry, custom.columns, next - Added unit tests to verify that the next report returns the correct tasks. - Added unit test to verify that unrecognized columns in a custom report are flagged. - Added unit tests to verify that only pending and completed tasks are included in the summary report. --- src/tests/bug.summary.t | 67 ++++++++++++++++++++++++++++++++++++++ src/tests/custom.columns.t | 57 ++++++++++++++++++++++++++++++++ src/tests/next.t | 61 ++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100755 src/tests/bug.summary.t create mode 100755 src/tests/custom.columns.t create mode 100755 src/tests/next.t diff --git a/src/tests/bug.summary.t b/src/tests/bug.summary.t new file mode 100755 index 000000000..c98b58d03 --- /dev/null +++ b/src/tests/bug.summary.t @@ -0,0 +1,67 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 6; + +# Create the rc file. +if (open my $fh, '>', 'summary.rc') +{ + print $fh "data.location=.\n", + "confirmation=no\n"; + close $fh; + ok (-r 'summary.rc', 'Created summary.rc'); +} + +# Add three tasks. Do 1, delete 1, leave 1 pending. Summary should depict a +# 50% completion. +qx{../task rc:summary.rc add project:A one}; +qx{../task rc:summary.rc add project:A two}; +qx{../task rc:summary.rc add project:A three}; +qx{../task rc:summary.rc do 1}; +qx{../task rc:summary.rc delete 2}; +my $output = qx{../task rc:summary.rc summary}; +like ($output, qr/A\s+1\s+-\s+50%/, 'summary correctly shows 50% before report'); + +qx{../task rc:summary.rc list}; +$output = qx{../task rc:summary.rc summary}; +like ($output, qr/A\s+1\s+-\s+50%/, 'summary correctly shows 50% after report'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'completed.data'; +ok (!-r 'completed.data', 'Removed completed.data'); + +unlink 'summary.rc'; +ok (!-r 'summary.rc', 'Removed summary.rc'); + +exit 0; + diff --git a/src/tests/custom.columns.t b/src/tests/custom.columns.t new file mode 100755 index 000000000..ab1927b65 --- /dev/null +++ b/src/tests/custom.columns.t @@ -0,0 +1,57 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 4; + +# Create the rc file. +if (open my $fh, '>', 'custom.rc') +{ + print $fh "data.location=.\n", + "report.foo.description=DESC\n", + "report.foo.columns=id,foo,description\n", + "report.foo.sort=id+\n", + "report.foo.filter=project:A\n"; + close $fh; + ok (-r 'custom.rc', 'Created custom.rc'); +} + +# Generate the usage screen, and locate the custom report on it. +my $output = qx{../task rc:custom.rc foo 2>&1}; +like ($output, qr/Unrecognized column name: foo\n/, 'custom report spotted invalid column'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'custom.rc'; +ok (!-r 'custom.rc', 'Removed custom.rc'); + +exit 0; + diff --git a/src/tests/next.t b/src/tests/next.t new file mode 100755 index 000000000..2130f6626 --- /dev/null +++ b/src/tests/next.t @@ -0,0 +1,61 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 5; + +# Create the rc file. +if (open my $fh, '>', 'next.rc') +{ + print $fh "data.location=.\n", + "next=1\n"; + close $fh; + ok (-r 'next.rc', 'Created next.rc'); +} + +# Add two tasks for each of two projects, then run next. There should be only +# one task from each project shown. +qx{../task rc:next.rc add project:A priority:H AH}; +qx{../task rc:next.rc add project:A priority:M AM}; +qx{../task rc:next.rc add project:B priority:H BH}; +qx{../task rc:next.rc add project:B Bnone}; + +my $output = qx{../task rc:next.rc next}; +like ($output, qr/\s1\sA\s+H\s+-\sAH\n/, 'AH shown'); +like ($output, qr/\s3\sB\s+H\s+-\sBH\n/, 'BH shown'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'next.rc'; +ok (!-r 'next.rc', 'Removed next.rc'); + +exit 0; + From e33a918c2435cfbfb9afb55f3ec697cb177e227b Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 11:37:05 -0400 Subject: [PATCH 092/103] Bug Fix - summary report - Fixed bug in summary report where recently completed (and therefore not yet in the completed.data file) tasks were not included in the report. --- ChangeLog | 2 ++ html/task.html | 2 ++ src/report.cpp | 78 +++++++++++++++++++------------------------------- 3 files changed, 34 insertions(+), 48 deletions(-) diff --git a/ChangeLog b/ChangeLog index bf4907020..e9c743989 100644 --- a/ChangeLog +++ b/ChangeLog @@ -40,6 +40,8 @@ quotes, and limits output to pending tasks. + Task no longer includes deleted tasks in the summary report (thanks to Benjamin Tegarden). + + Fixed bug that prevented the summary report from properly reporting + recently completed tasks. ------ old releases ------------------------------ diff --git a/html/task.html b/html/task.html index ed65b903a..e2336047c 100644 --- a/html/task.html +++ b/html/task.html @@ -134,6 +134,8 @@ quotes, and limits output to pending tasks.

  • Task no longer includes deleted tasks in the summary report (thanks to Benjamin Tegarden). +
  • Fixed bug that prevented the summary report from properly reporting + recently completed tasks.

    diff --git a/src/report.cpp b/src/report.cpp index 606a4e3ab..7b540b2d5 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -432,33 +432,23 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf) } //////////////////////////////////////////////////////////////////////////////// -// Project Tasks Avg Age Status -// A 12 13d XXXXXXXX------ -// B 109 3d 12h XX------------ +// Project Remaining Avg Age Complete 0% 100% +// A 12 13d 55% XXXXXXXXXXXXX----------- +// B 109 3d 12h 10% XXX--------------------- std::string handleReportSummary (TDB& tdb, T& task, Config& conf) { std::stringstream out; - // Generate unique list of project names. - 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 tasks; + tdb.allT (tasks); + handleRecurrence (tdb, tasks); + filter (tasks, task); - 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; - } + // Generate unique list of project names from all pending tasks. + std::map allProjects; + foreach (t, tasks) + if (t->getStatus () == T::pending) + allProjects[t->getAttribute ("project")] = false; // Initialize counts, sum. std::map countPending; @@ -467,6 +457,7 @@ std::string handleReportSummary (TDB& tdb, T& task, Config& conf) std::map counter; time_t now = time (NULL); + // Initialize counters. foreach (i, allProjects) { countPending [i->first] = 0; @@ -475,38 +466,29 @@ std::string handleReportSummary (TDB& tdb, T& task, Config& conf) counter [i->first] = 0; } - // Count the pending tasks. - for (unsigned int i = 0; i < pending.size (); ++i) + // Count the various tasks. + foreach (t, tasks) { - T task (pending[i]); - std::string project = task.getAttribute ("project"); - if (task.getStatus () == T::pending) + std::string project = t->getAttribute ("project"); + ++counter[project]; + + if (t->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]; + time_t entry = ::atoi (t->getAttribute ("entry").c_str ()); + if (entry) + sumEntry[project] = sumEntry[project] + (double) (now - entry); } - } - // Count the completed tasks. - for (unsigned int i = 0; i < completed.size (); ++i) - { - T task (completed[i]); - std::string project = task.getAttribute ("project"); - if (task.getStatus () == T::deleted) - continue; - - ++countCompleted[project]; - - time_t entry = ::atoi (task.getAttribute ("entry").c_str ()); - time_t end = ::atoi (task.getAttribute ("end").c_str ()); - if (entry && end) + else if (t->getStatus () == T::completed) { - sumEntry[project] = sumEntry[project] + (double) (end - entry); - ++counter[project]; + ++countCompleted[project]; + + time_t entry = ::atoi (t->getAttribute ("entry").c_str ()); + time_t end = ::atoi (task.getAttribute ("end").c_str ()); + if (entry && end) + sumEntry[project] = sumEntry[project] + (double) (end - entry); } } From cd85a28e989b8f8dd62202cfeb940d097390edc4 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 15:04:52 -0400 Subject: [PATCH 093/103] Unit Tests - due, overdue - Unit tests to verify that the "overdue" report is properly displaying tasks. - Unit tests to verify that "due" can be defined. --- src/tests/due.t | 66 +++++++++++++++++++++++++++++++++++++++++++++ src/tests/overdue.t | 60 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100755 src/tests/due.t create mode 100755 src/tests/overdue.t diff --git a/src/tests/due.t b/src/tests/due.t new file mode 100755 index 000000000..a032aa339 --- /dev/null +++ b/src/tests/due.t @@ -0,0 +1,66 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 14; + +# Create the rc file. +if (open my $fh, '>', 'due.rc') +{ + print $fh "data.location=.\n", + "due=4\n", + "color=on\n", + "color.due=red\n", + "_forcecolor=on\n"; + close $fh; + ok (-r 'due.rc', 'Created due.rc'); +} + +# Add a task that is almost due, and one that is just due. +my ($d, $m, $y) = (localtime (time + 3 * 86_400))[3..5]; +my $just = sprintf ("%d/%02d/%d", $m + 1, $d, $y + 1900); + +($d, $m, $y) = (localtime (time + 5 * 86_400))[3..5]; +my $almost = sprintf ("%d/%02d/%d", $m + 1, $d, $y + 1900); + +qx{../task rc:due.rc add one due:$just}; +qx{../task rc:due.rc add two due:$almost}; +my $output = qx{../task rc:due.rc list}; +like ($output, qr/\[31m.+$just.+\[0m/, 'one marked due'); +like ($output, qr/\s+$almost\s+/, 'two not marked due'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'due.rc'; +ok (!-r 'due.rc', 'Removed due.rc'); + +exit 0; + diff --git a/src/tests/overdue.t b/src/tests/overdue.t new file mode 100755 index 000000000..94229c438 --- /dev/null +++ b/src/tests/overdue.t @@ -0,0 +1,60 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, 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 +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 6; + +# Create the rc file. +if (open my $fh, '>', 'due.rc') +{ + print $fh "data.location=.\n", + "due=4\n"; + close $fh; + ok (-r 'due.rc', 'Created due.rc'); +} + +# Add an overdue task, a due task, and a regular task. The "overdue" report +# should list only the one task. +qx{../task rc:due.rc add due:yesterday one}; +qx{../task rc:due.rc add due:tomorrow two}; +qx{../task rc:due.rc add due:eoy three}; +my $output = qx{../task rc:due.rc overdue}; +like ($output, qr/one/, 'overdue: task 1 shows up'); +unlike ($output, qr/two/, 'overdue: task 2 does not show up'); +unlike ($output, qr/three/, 'overdue: task 3 does not show up'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'due.rc'; +ok (!-r 'due.rc', 'Removed due.rc'); + +exit 0; + From a3882160fa73b52fa7535177700c1822755a916e Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 15:12:16 -0400 Subject: [PATCH 094/103] Documentation Update - Updated docs to reflect the 1.5.0 release date. --- ChangeLog | 2 +- html/task.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index e9c743989..794dc591a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,7 @@ ------ current release --------------------------- -1.5.0 (?) +1.5.0 (3/15/2009) + Removed deprecated TUTORIAL file. + Removed "showage" configuration variable. + "task stop" can now remove the start time from a started task. diff --git a/html/task.html b/html/task.html index e2336047c..1534e1542 100644 --- a/html/task.html +++ b/html/task.html @@ -94,7 +94,7 @@ -->

  • -

    New in version 1.5.0 (?)

    +

    New in version 1.5.0 (3/15/2009)

    • Removed deprecated TUTORIAL file.
    • Removed support for the "showage" configuration variable. From 4baf30cf9c320415da13b379f933cf8b833e50a0 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 16:13:02 -0400 Subject: [PATCH 095/103] Portability - Ubuntu 8 - Changed unsigned int to size_t for std::string::npos comparison. - Removed validBuiltinCommand function that is not used. --- src/parse.cpp | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/parse.cpp b/src/parse.cpp index b0a1c5197..b5adb8388 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -342,18 +342,6 @@ static bool validCommand (std::string& input) return true; } -//////////////////////////////////////////////////////////////////////////////// -static bool validBuiltinCommand (std::string& input) -{ - std::string copy = input; - guess ("command", commands, copy); - if (copy == "") - return false; - - input = copy; - return true; -} - //////////////////////////////////////////////////////////////////////////////// static bool validSubstitution ( std::string& input, @@ -509,7 +497,7 @@ void loadCustomReports (Config& conf) if (i->substr (0, 7) == "report.") { std::string report = i->substr (7, std::string::npos); - unsigned int columns = report.find (".columns"); + size_t columns = report.find (".columns"); if (columns != std::string::npos) { report = report.substr (0, columns); From 429d0f307150409d1d541972262579724f62c1d5 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 16:14:16 -0400 Subject: [PATCH 096/103] Portability - Fedora 9 - Compiler pointed out an expression (a || b && c) that clearly needs parentheses around (a || b). Gcc on other OSes don't mention this. --- src/report.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/report.cpp b/src/report.cpp index 7b540b2d5..44752327a 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -934,7 +934,7 @@ std::string handleReportHistory (TDB& tdb, T& task, Config& conf) } table.addCell (row, 5, net); - if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false) && net) + if ((conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) && net) table.setCellFg (row, 5, net > 0 ? Text::red: Text::green); } From 65f74da7a48294b43aadc5495b8f5070e964aa5f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 16:26:20 -0400 Subject: [PATCH 097/103] Portability - Fedora 9 - Using size_t as a result for std::string::find causes a silent error, reported only on Ubuntu 8. size_t is not large enough. The proper result type is std::string::size_type. This fixes a problem with the command "task old" responding with "Ambiguous commane 'old' - could be one of oldest, oldest.description, oldest.limit, oldest.sort". --- src/parse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse.cpp b/src/parse.cpp index b5adb8388..a8e41a914 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -497,7 +497,7 @@ void loadCustomReports (Config& conf) if (i->substr (0, 7) == "report.") { std::string report = i->substr (7, std::string::npos); - size_t columns = report.find (".columns"); + std::string::size_type columns = report.find (".columns"); if (columns != std::string::npos) { report = report.substr (0, columns); From b5690f00e2e27a3336eb06e77f4331e5e05a05d6 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 16:39:41 -0400 Subject: [PATCH 098/103] Portabiliy - Fedora 9, Ubuntu 8 - The custom report limit "report.x.limit" was being used to limit the rendered rows in the table. Instead, there should be something like min (limit, actual_rows) used, in Table.cpp. The symptom was duplicate tasks in a "task oldest" report, when there were less than 10 tasks. --- src/Table.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Table.cpp b/src/Table.cpp index 8052ca0b3..6f20c1b50 100644 --- a/src/Table.cpp +++ b/src/Table.cpp @@ -1032,7 +1032,7 @@ const std::string Table::render (int maximum /* = 0 */) // the table that are rendered. int limit = mRows; if (maximum != 0) - limit = maximum; + limit = min (maximum, mRows); // Print all rows. for (int row = 0; row < limit; ++row) From e8a795befbbe7570f7483cd1027fecebdcbe0a44 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 17:01:22 -0400 Subject: [PATCH 099/103] Unit Tests - due, export - due.t was incorrectly reporting the number of tests it intended to run. - export.t was not updated when the export command was updated to include recurrence information. --- src/tests/due.t | 2 +- src/tests/export.t | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tests/due.t b/src/tests/due.t index a032aa339..b320c9563 100755 --- a/src/tests/due.t +++ b/src/tests/due.t @@ -28,7 +28,7 @@ use strict; use warnings; -use Test::More tests => 14; +use Test::More tests => 5; # Create the rc file. if (open my $fh, '>', 'due.rc') diff --git a/src/tests/export.t b/src/tests/export.t index 4218a3c10..143a687da 100755 --- a/src/tests/export.t +++ b/src/tests/export.t @@ -50,9 +50,9 @@ if (open my $fh, '<', './export.txt') close $fh; } -my $line1 = qr/'id','uuid','status','tags','entry','start','due','end','project','priority','fg','bg','description'\n/; -my $line2 = qr/'.{8}-.{4}-.{4}-.{4}-.{12}','pending','',\d+,,,,'A','H',,,'one'\n/; -my $line3 = qr/'.{8}-.{4}-.{4}-.{4}-.{12}','pending','tag1 tag2',\d+,,,,,,,,'two'\n/; +my $line1 = qr/'id','uuid','status','tags','entry','start','due','recur','end','project','priority','fg','bg','description'\n/; +my $line2 = qr/'.{8}-.{4}-.{4}-.{4}-.{12}','pending','',\d+,,,,,'A','H',,,'one'\n/; +my $line3 = qr/'.{8}-.{4}-.{4}-.{4}-.{12}','pending','tag1 tag2',\d+,,,,,,,,,'two'\n/; like ($lines[0], $line1, "export line one"); like ($lines[1], $line2, "export line two"); From 8efd8620c84df83ad44f7adada1c94c7a9498384 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 17:21:54 -0400 Subject: [PATCH 100/103] Portability - Ubuntu 8.10 - When creating a new .taskrc file, no newlines were included at EOL. This needs a 1.5.1 regression test. --- src/Config.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Config.cpp b/src/Config.cpp index 3476bc6f8..c2cb928fc 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -175,27 +175,27 @@ void Config::createDefault (const std::string& home) 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.long.description=Lists all task, all data, matching the specified criteria\n"); + fprintf (out, "report.long.columns=id,project,priority,entry,start,due,recur,age,tags,description\n"); + fprintf (out, "report.long.sort=due+,priority-,project+\n"); - 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.list.description=Lists all tasks matching the specified criteria\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.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.ls.description=Minimal listing of all tasks matching the specified criteria\n"); + fprintf (out, "report.ls.columns=id,project,priority,description\n"); + fprintf (out, "report.ls.sort=priority-,project+\n"); - 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.newest.description=Shows the newest tasks\n"); + fprintf (out, "report.newest.columns=id,project,priority,due,active,age,description\n"); + fprintf (out, "report.newest.sort=id-\n"); + fprintf (out, "report.newest.limit=10\n"); - 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"); + fprintf (out, "report.oldest.description=Shows the oldest tasks\n"); + fprintf (out, "report.oldest.columns=id,project,priority,due,active,age,description\n"); + fprintf (out, "report.oldest.sort=id+\n"); + fprintf (out, "report.oldest.limit=10\n"); fclose (out); From f8af5d999af800850cd0d7792fc03158cb1ee288 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 17:38:54 -0400 Subject: [PATCH 101/103] Unit Tests - run_all - Added script to run all unit tests and capture output. --- src/tests/run_all | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100755 src/tests/run_all diff --git a/src/tests/run_all b/src/tests/run_all new file mode 100755 index 000000000..f92ea5412 --- /dev/null +++ b/src/tests/run_all @@ -0,0 +1,11 @@ +!# /bin/bash + +date > all.log + +for i in *.t +do + ./$i >> all.log 2>&1 +done + +date >> all.log + From 7b1dec0d776f9540f9e869986f0166834752d2a9 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 19:15:35 -0400 Subject: [PATCH 102/103] Bug Fix - abbreviation.t - abbreviation.t contains unit tests that fail to specify an alternate rc file (rc:abbrev.rc), and so instead rely on ~/.taskrc. For a new installation, there is no .taskrc, so task offers to create one. When done in the context of a unit test, task hangs waiting for input. --- src/tests/abbreviation.t | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/tests/abbreviation.t b/src/tests/abbreviation.t index 18d330e65..4f798c0ee 100755 --- a/src/tests/abbreviation.t +++ b/src/tests/abbreviation.t @@ -67,25 +67,25 @@ like ($output, qr/\bwith\b/, 'pri:H with'); unlike ($output, qr/\bwithout\b/, 'pri:H without'); # Test the version command abbreviations. -$output = qx{../task version}; +$output = qx{../task rc:abbrev.rc version}; like ($output, qr/ABSOLUTELY NO WARRANTY/, 'version'); -$output = qx{../task versio}; +$output = qx{../task rc:abbrev.rc versio}; like ($output, qr/ABSOLUTELY NO WARRANTY/, 'versio'); -$output = qx{../task versi}; +$output = qx{../task rc:abbrev.rc versi}; like ($output, qr/ABSOLUTELY NO WARRANTY/, 'versi'); -$output = qx{../task vers}; +$output = qx{../task rc:abbrev.rc vers}; like ($output, qr/ABSOLUTELY NO WARRANTY/, 'vers'); -$output = qx{../task ver}; +$output = qx{../task rc:abbrev.rc ver}; like ($output, qr/ABSOLUTELY NO WARRANTY/, 'ver'); -$output = qx{../task ve}; +$output = qx{../task rc:abbrev.rc ve}; like ($output, qr/ABSOLUTELY NO WARRANTY/, 've'); -$output = qx{../task v}; +$output = qx{../task rc:abbrev.rc v}; like ($output, qr/ABSOLUTELY NO WARRANTY/, 'v'); # Cleanup. From 87be68e2e83d7bb628be1e5679b16a49a26d3549 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 15 Mar 2009 22:12:18 -0400 Subject: [PATCH 103/103] Bug Fix - Shebang in tests/run_all was backwards. --- src/tests/run_all | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/run_all b/src/tests/run_all index f92ea5412..9853f957f 100755 --- a/src/tests/run_all +++ b/src/tests/run_all @@ -1,4 +1,4 @@ -!# /bin/bash +#! /bin/bash date > all.log