From 5c235ce1efe6baa13234a88951a5055f94b6f9ff Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 20 Aug 2010 23:53:57 -0400 Subject: [PATCH] Feature #471 - Added feature #471, which makes greater use of projects by reporting changes to the completion percentage when it changes. - Added unit tests. --- ChangeLog | 2 ++ NEWS | 3 +- src/command.cpp | 26 +++++++++++++- src/main.h | 7 ++-- src/report.cpp | 82 +++++++++++++++++++++++++++++++++++++++++++++ src/tests/project.t | 79 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 193 insertions(+), 6 deletions(-) create mode 100755 src/tests/project.t diff --git a/ChangeLog b/ChangeLog index 890d67cd5..e64e8cd4c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -24,6 +24,8 @@ + Added feature #446, task supports now 'sow', 'som' and 'soy' as dates for 'due', 'wait' and 'until' (thanks to T. Charles Yun). Added as well synonyms soww/eoww plus new socw/eocw for calendar weeks. + + Added feature #471, which makes greater use of projects by reporting + changes to the completion percentage when it changes. + New 'depends' column for custom reports. + New 'blocked' report for showing blocked tasks. + Improved man pages (thanks to Andy Lester). diff --git a/NEWS b/NEWS index 75f33b519..8ab6a30ef 100644 --- a/NEWS +++ b/NEWS @@ -7,11 +7,12 @@ New Features in task 1.9.3 $ task ... due:4d $ task ... due:3wks - Now supports the beginning of the week, month and year in dates. - - Now supports 'now' as a date. + - Now supports 'now' as a date/time. - Now defines an overdue task as being one second after the due date, instead of the day after the due date. - Import and export of YAML 1.1, including round-trip capability. - New merge capability for syncing task data files. + - When completing or modifying a task, the project status is displayed. Please refer to the ChangeLog file for full details. There are too many to list here. diff --git a/src/command.cpp b/src/command.cpp index 526e8f066..47db3c46a 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -140,6 +140,8 @@ int handleAdd (std::string &outs) out << "Created task " << context.tdb.nextId () << "." << std::endl; #endif + out << onProjectChange (context.task); + context.tdb.commit (); context.tdb.unlock (); @@ -206,6 +208,8 @@ int handleLog (std::string &outs) if (context.config.getBoolean ("echo.command")) out << "Logged task." << std::endl; + out << onProjectChange (context.task); + outs = out.str (); context.hooks.trigger ("post-log-command"); } @@ -720,7 +724,8 @@ int handleShow (std::string &outs) // Complain about configuration variables that are not recognized. // These are the regular configuration variables. - // Note that there is a leading and trailing space, to make searching easier. + // Note that there is a leading and trailing space, to make it easier to + // search for whole words. std::string recognized = " annotations blanklines bulk calendar.details calendar.details.report " "calendar.holidays calendar.legend color color.active color.due color.due.today " @@ -1183,6 +1188,8 @@ int handleDelete (std::string &outs) << task->get ("description") << "'." << std::endl; + + out << onProjectChange (*task); } } else @@ -1203,6 +1210,8 @@ int handleDelete (std::string &outs) << task->get ("description") << "'." << std::endl; + + out << onProjectChange (*task); } } else { @@ -1413,6 +1422,8 @@ int handleDone (std::string &outs) << "'." << std::endl; + out << onProjectChange (*task, false); + ++count; context.hooks.trigger ("post-completed", *task); } @@ -1551,6 +1562,10 @@ int handleModify (std::string &outs) if (changes && permission.confirmed (before, taskDifferences (before, *other) + "Proceed with change?")) { context.tdb.update (*other); + + if (before.get ("project") != other->get ("project")) + out << onProjectChange (before, *other); + ++count; } } @@ -1626,6 +1641,9 @@ int handleAppend (std::string &outs) << "." << std::endl; + if (before.get ("project") != other->get ("project")) + out << onProjectChange (before, *other); + ++count; } } @@ -1703,6 +1721,9 @@ int handlePrepend (std::string &outs) << "." << std::endl; + if (before.get ("project") != other->get ("project")) + out << onProjectChange (before, *other); + ++count; } } @@ -1784,6 +1805,9 @@ int handleDuplicate (std::string &outs) << task->get ("description") << "'." << std::endl; + + out << onProjectChange (dup); + ++count; } diff --git a/src/main.h b/src/main.h index 338390be1..5b3903c30 100644 --- a/src/main.h +++ b/src/main.h @@ -42,10 +42,6 @@ #include "Color.h" #include "../auto.h" -// task.cpp -void gatherNextTasks (std::vector &); -void onChangeCallback (); - // recur.cpp void handleRecurrence (); Date getNextRecurrence (Date&, std::string&); @@ -107,8 +103,11 @@ int handleReportGHistoryAnnual (std::string &); int handleReportCalendar (std::string &); int handleReportStats (std::string &); int handleReportTimesheet (std::string &); +void gatherNextTasks (std::vector &); std::string getFullDescription (Task&, const std::string&); std::string getDueDate (Task&, const std::string&); +std::string onProjectChange (Task&, bool scope = true); +std::string onProjectChange (Task&, Task&); // custom.cpp int handleCustomReport (const std::string&, std::string &); diff --git a/src/report.cpp b/src/report.cpp index 612e8eadf..664b52ac4 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -51,6 +51,8 @@ #include #endif +static void countTasks (const std::vector &, const std::string&, const std::string&, int&, int&); + extern Context context; //////////////////////////////////////////////////////////////////////////////// @@ -2828,3 +2830,83 @@ std::string getDueDate (Task& task, const std::string& format) } /////////////////////////////////////////////////////////////////////////////// +std::string onProjectChange (Task& task, bool scope /* = true */) +{ + std::stringstream msg; + std::string project = task.get ("project"); + + if (project != "") + { + if (scope) + msg << "The scope of project '" + << project + << "' has changed. "; + + // Count pending and done tasks, for this project. + int count_pending = 0; + int count_done = 0; + + std::vector all; + Filter filter; + context.tdb.load (all, filter); + + countTasks (all, project, task.get ("uuid"), count_pending, count_done); + countTasks (context.tdb.getAllNew (), project, "nope", count_pending, count_done); + countTasks (context.tdb.getAllModified (), project, "nope", count_pending, count_done); + + msg << "Project '" + << project + << "' is " + << (count_done * 100 / (count_done + count_pending)) + << "% complete (" + << count_pending + << " of " + << (count_pending + count_done) + << " tasks remaining).\n"; + } + + return msg.str (); +} + +/////////////////////////////////////////////////////////////////////////////// +std::string onProjectChange (Task& task1, Task& task2) +{ + std::string messages = onProjectChange (task1); + messages += onProjectChange (task2); + + return messages; +} + +/////////////////////////////////////////////////////////////////////////////// +static void countTasks ( + const std::vector & all, + const std::string& project, + const std::string& skip, + int& count_pending, + int& count_done) +{ + std::vector ::const_iterator it; + for (it = all.begin (); it != all.end (); ++it) + { + if (it->get ("project") == project && + it->get ("uuid") != skip) + { + switch (it->getStatus ()) + { + case Task::pending: + case Task::waiting: + ++count_pending; + break; + + case Task::completed: + ++count_done; + break; + + default: + break; + } + } + } +} + +/////////////////////////////////////////////////////////////////////////////// diff --git a/src/tests/project.t b/src/tests/project.t new file mode 100755 index 000000000..3e724c107 --- /dev/null +++ b/src/tests/project.t @@ -0,0 +1,79 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2010, 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 => 13; + +# Create the rc file. +if (open my $fh, '>', 'pro.rc') +{ + print $fh "data.location=.\n", + "confirmation=off\n"; + close $fh; + ok (-r 'pro.rc', 'Created pro.rc'); +} + +# Test the project status numbers. +my $output = qx{../task rc:pro.rc add one pro:foo}; +like ($output, qr/The scope of project 'foo' has changed\. Project 'foo' is 0% complete \(1 of 1 tasks remaining\)\./, 'add one'); + +$output = qx{../task rc:pro.rc add two pro:'foo'}; +like ($output, qr/The scope of project 'foo' has changed\. Project 'foo' is 0% complete \(2 of 2 tasks remaining\)\./, 'add two'); + +$output = qx{../task rc:pro.rc add three pro:'foo'}; +like ($output, qr/The scope of project 'foo' has changed\. Project 'foo' is 0% complete \(3 of 3 tasks remaining\)\./, 'add three'); + +$output = qx{../task rc:pro.rc add four pro:'foo'}; +like ($output, qr/The scope of project 'foo' has changed\. Project 'foo' is 0% complete \(4 of 4 tasks remaining\)\./, 'add four'); + +$output = qx{../task rc:pro.rc 1 done}; +like ($output, qr/Project 'foo' is 25% complete \(3 of 4 tasks remaining\)\./, 'done one'); + +$output = qx{../task rc:pro.rc 2 delete}; +like ($output, qr/The scope of project 'foo' has changed\. Project 'foo' is 33% complete \(2 of 3 tasks remaining\)\./, 'delete two'); + +$output = qx{../task rc:pro.rc 3 pro:bar}; +like ($output, qr/The scope of project 'foo' has changed\. Project 'foo' is 50% complete \(1 of 2 tasks remaining\)\./, 'change project'); +like ($output, qr/The scope of project 'bar' has changed\. Project 'bar' is 0% complete \(1 of 1 tasks remaining\)\./, 'change project'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'completed.data'; +ok (!-r 'completed.data', 'Removed completed.data'); + +unlink 'undo.data'; +ok (!-r 'undo.data', 'Removed undo.data'); + +unlink 'pro.rc'; +ok (!-r 'pro.rc', 'Removed pro.rc'); + +exit 0; +