diff --git a/ChangeLog b/ChangeLog index b8a4081c1..5fbf9f7f6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -66,7 +66,10 @@ + Fixed bug that showed a calendar for the year 2037 when 'task calendar due' was run, and there are no tasks with due dates. + Fixed bug #360 which prevented certain modifications to recurring tasks - (thanks to John Flrorian). + (thanks to John Florian). + + Fixed bug #299 which prevented excluding multiple projects from a report, + by using "task list project.isnt:foo project.isnt:bar" (thanks to John + Florian). ------ old releases ------------------------------ diff --git a/src/Context.cpp b/src/Context.cpp index f2712f364..2907e8635 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -591,6 +591,8 @@ void Context::parse ( parseTask[name + "." + mod] = attribute; else parseTask[name] = attribute; + + autoFilter (attribute, parseFilter); } // *arg has the appearance of an attribute (foo:bar), but isn't @@ -656,6 +658,19 @@ void Context::parse ( { debug ("parse description '" + descCandidate + "'"); parseTask.set ("description", descCandidate); + + // Now convert the description to a filter on each word, if necessary. + if (parseCmd.isReadOnlyCommand ()) + { + std::vector words; + split (words, descCandidate, ' '); + std::vector ::iterator it; + for (it = words.begin (); it != words.end (); ++it) + { + Att a ("description", "contains", *it); + autoFilter (a, parseFilter); + } + } } // At this point, either a sequence or a command should have been found. @@ -663,9 +678,10 @@ void Context::parse ( parseCmd.parse (descCandidate); // Read-only command (reports, status, info ...) use filters. Write commands - // (add, done ...) do not. + // (add, done ...) do not. The filter was constructed iteratively above, but + // tags were omitted, so they are added now. if (parseCmd.isReadOnlyCommand ()) - autoFilter (parseTask, parseFilter); + autoFilter (parseFilter); // If no command was specified, and there were no command line arguments // then invoke the default command. @@ -726,68 +742,70 @@ void Context::clear () //////////////////////////////////////////////////////////////////////////////// // Add all the attributes in the task to the filter. All except uuid. -void Context::autoFilter (Task& t, Filter& f) +void Context::autoFilter (Att& a, Filter& f) { - foreach (att, t) + // Words are found in the description using the .has modifier. + if (a.name () == "description" && a.mod () == "") { - // Words are found in the description using the .has modifier. - if (att->second.name () == "description" && att->second.mod () == "") + std::vector words; + split (words, a.value (), ' '); + foreach (word, words) { - std::vector words; - split (words, att->second.value (), ' '); - foreach (word, words) - { - f.push_back (Att ("description", "has", *word)); - debug ("auto filter: " + att->second.name () + ".has:" + *word); - } - } - - // Projects are matched left-most. - else if (att->second.name () == "project" && att->second.mod () == "") - { - if (att->second.value () != "") - { - f.push_back (Att ("project", "startswith", att->second.value ())); - debug ("auto filter: " + att->second.name () + ".startswith:" + att->second.value ()); - } - else - { - f.push_back (Att ("project", "is", att->second.value ())); - debug ("auto filter: " + att->second.name () + ".is:" + att->second.value ()); - } - } - - // The limit attribute does not participate in filtering, and needs to be - // specifically handled in handleCustomReport. - else if (att->second.name () == "limit") - { - } - - // Every task has a unique uuid by default, and it shouldn't be included, - // because it is guaranteed to not match. - else if (att->second.name () == "uuid") - { - } - - // The mechanism for filtering on tags is +/-. - // Do not handle here - see below. - else if (att->second.name () == "tags") - { - } - - // Generic attribute matching. - else - { - f.push_back (att->second); - debug ("auto filter: " + - att->second.name () + - (att->second.mod () != "" ? - ("." + att->second.mod () + ":") : - ":") + - att->second.value ()); + f.push_back (Att ("description", "has", *word)); + debug ("auto filter: " + a.name () + ".has:" + *word); } } + // Projects are matched left-most. + else if (a.name () == "project" && a.mod () == "") + { + if (a.value () != "") + { + f.push_back (Att ("project", "startswith", a.value ())); + debug ("auto filter: " + a.name () + ".startswith:" + a.value ()); + } + else + { + f.push_back (Att ("project", "is", a.value ())); + debug ("auto filter: " + a.name () + ".is:" + a.value ()); + } + } + + // The limit attribute does not participate in filtering, and needs to be + // specifically handled in handleCustomReport. + else if (a.name () == "limit") + { + } + + // Every task has a unique uuid by default, and it shouldn't be included, + // because it is guaranteed to not match. + else if (a.name () == "uuid") + { + } + + // The mechanism for filtering on tags is +/-. + // Do not handle here - see below. + else if (a.name () == "tags") + { + } + + // Generic attribute matching. + else + { + f.push_back (a); + debug ("auto filter: " + + a.name () + + (a.mod () != "" ? + ("." + a.mod () + ":") : + ":") + + a.value ()); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Add all the tags in the task to the filter. +void Context::autoFilter (Filter& f) +{ // This is now a correct implementation of a filter on the presence or absence // of a tag. The prior code provided the illusion of leftmost partial tag // matches, but was really using the 'contains' and 'nocontains' attribute diff --git a/src/Context.h b/src/Context.h index dfd1ba279..8ab7ee523 100644 --- a/src/Context.h +++ b/src/Context.h @@ -70,7 +70,8 @@ public: private: void loadCorrectConfigFile (); void loadAliases (); - void autoFilter (Task&, Filter&); + void autoFilter (Att&, Filter&); + void autoFilter (Filter&); public: Config config; diff --git a/src/tests/bug.299.t b/src/tests/bug.299.t new file mode 100755 index 000000000..061b5408b --- /dev/null +++ b/src/tests/bug.299.t @@ -0,0 +1,68 @@ +#! /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 => 8; + +# Create the rc file. +if (open my $fh, '>', 'bug.rc') +{ + print $fh "data.location=.\n", + "confirmation=no\n"; + close $fh; + ok (-r 'bug.rc', 'Created bug.rc'); +} + +# Setup: Add three unique tasks with different project names. +qx{../task rc:bug.rc add project:one foo}; +qx{../task rc:bug.rc add project:two bar}; +qx{../task rc:bug.rc add project:three baz}; + +# Result: Run list but exclude two of the three projects names using +# project.hasnt: +my $output = qx{../task rc:bug.rc list project.isnt:one project.isnt:two}; +unlike ($output, qr/one.*foo/ms, 'project.isnt:one project.isnt:two - no foo'); +unlike ($output, qr/two.*bar/ms, 'project.isnt:one project.isnt:two - no bar'); +like ($output, qr/three.*baz/ms, 'project.isnt:one project.isnt:two - yes baz'); + +# 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 'bug.rc'; +ok (!-r 'bug.rc', 'Removed bug.rc'); + +exit 0; +