- Added feature #710, which adds an attribute modifier prefix to return the
  complement of a filtered set (thanks to Dan White).
- Added missing description to the 'help' command.

Signed-off-by: Paul Beckingham <paul@beckingham.net>
This commit is contained in:
Dan White
2011-03-19 00:55:57 -04:00
committed by Paul Beckingham
parent 17f97651a3
commit 4b71fa73f8
10 changed files with 241 additions and 11 deletions

View File

@@ -41,6 +41,7 @@ The following submitted code, packages or analysis, and deserve special thanks:
Eric Fluger Eric Fluger
Andreas Poisel Andreas Poisel
Timm Reitinger Timm Reitinger
Dan White
Thanks to the following, who submitted detailed bug reports and excellent Thanks to the following, who submitted detailed bug reports and excellent
suggestions: suggestions:

View File

@@ -10,6 +10,8 @@
+ Added feature #700, which adds tab-completion of built-in tags. + Added feature #700, which adds tab-completion of built-in tags.
+ Corrected sorting to use std::stable_sort instead of std::sort, which is not + Corrected sorting to use std::stable_sort instead of std::sort, which is not
guaranteed stable (thanks to Stefan Hacker). guaranteed stable (thanks to Stefan Hacker).
+ Added feature #710, which adds an attribute modifier prefix to return the
complement of a filtered set (thanks to Dan White).
------ old releases ------------------------------ ------ old releases ------------------------------

2
NEWS
View File

@@ -5,6 +5,8 @@ New Features in taskwarrior 2.0.0
of the actual tasks. For advanced pipeline use. of the actual tasks. For advanced pipeline use.
- Now supplements the command line with data read from standard input, which - Now supplements the command line with data read from standard input, which
allows commands like: echo 'add Pay the bills' | task allows commands like: echo 'add Pay the bills' | task
- Attribute modifiers may be prefixed with '~' to return the opposite of a
filter's results.
Please refer to the ChangeLog file for full details. There are too many to Please refer to the ChangeLog file for full details. There are too many to
list here. list here.

View File

@@ -424,7 +424,8 @@ id1 and id2 should be completed before this task. Consequently, this task will
then show up on the 'blocked' report. then show up on the 'blocked' report.
.SH ATTRIBUTE MODIFIERS .SH ATTRIBUTE MODIFIERS
Attribute modifiers improve filters. Supported modifiers are: Attribute modifiers improve filters. Prefixing any modifier with '~' returns
the complementary set of items. Supported modifiers are:
.RS .RS
.B before (synonyms under, below) .B before (synonyms under, below)
@@ -517,7 +518,11 @@ modifier matches against the left, or beginning of an attribute, such that:
task list project.startswith:H task list project.startswith:H
task list project:H task list project:H
are equivalent and will match any project starting with 'H'. are equivalent and will match any project starting with 'H'. Matching all
projects not starting with 'H' is done with:
task list project.~startswith:H
task list project.not:H
The The
.I endswith .I endswith

View File

@@ -113,6 +113,7 @@ Att::Att ()
: mName ("") : mName ("")
, mValue ("") , mValue ("")
, mMod ("") , mMod ("")
, mSense ("positive")
{ {
} }
@@ -122,6 +123,17 @@ Att::Att (const std::string& name, const std::string& mod, const std::string& va
mName = name; mName = name;
mValue = value; mValue = value;
mMod = mod; mMod = mod;
mSense = "positive";
}
////////////////////////////////////////////////////////////////////////////////
Att::Att (const std::string& name, const std::string& mod, const std::string& value,
const std::string& sense)
{
mName = name;
mValue = value;
mMod = mod;
mSense = sense;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -134,6 +146,7 @@ Att::Att (const std::string& name, const std::string& mod, int value)
mValue = s.str (); mValue = s.str ();
mMod = mod; mMod = mod;
mSense = "positive";
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -142,6 +155,7 @@ Att::Att (const std::string& name, const std::string& value)
mName = name; mName = name;
mValue = value; mValue = value;
mMod = ""; mMod = "";
mSense = "positive";
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -154,6 +168,7 @@ Att::Att (const std::string& name, int value)
mValue = s.str (); mValue = s.str ();
mMod = ""; mMod = "";
mSense = "positive";
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -162,6 +177,7 @@ Att::Att (const Att& other)
mName = other.mName; mName = other.mName;
mValue = other.mValue; mValue = other.mValue;
mMod = other.mMod; mMod = other.mMod;
mSense = other.mSense;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -172,6 +188,7 @@ Att& Att::operator= (const Att& other)
mName = other.mName; mName = other.mName;
mValue = other.mValue; mValue = other.mValue;
mMod = other.mMod; mMod = other.mMod;
mSense = other.mSense;
} }
return *this; return *this;
@@ -180,9 +197,10 @@ Att& Att::operator= (const Att& other)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
bool Att::operator== (const Att& other) const bool Att::operator== (const Att& other) const
{ {
return mName == other.mName && return mName == other.mName &&
mMod == other.mMod && mMod == other.mMod &&
mValue == other.mValue; mValue == other.mValue &&
mSense == other.mSense;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -190,6 +208,20 @@ Att::~Att ()
{ {
} }
////////////////////////////////////////////////////////////////////////////////
bool Att::logicSense (bool match) const
{
if (mSense == "positive")
return match;
else if (mSense == "negative")
return ! match;
else
{
context.debug ("mSense: " + mSense);
throw ("unknown mSense " + mSense);
}
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// For parsing. // For parsing.
bool Att::valid (const std::string& input) const bool Att::valid (const std::string& input) const
@@ -515,6 +547,7 @@ void Att::parse (Nibbler& n)
mName = ""; mName = "";
mValue = ""; mValue = "";
mMod = ""; mMod = "";
mSense = "positive";
if (n.getUntilOneOf (".:", mName)) if (n.getUntilOneOf (".:", mName))
{ {
@@ -524,6 +557,13 @@ void Att::parse (Nibbler& n)
if (n.skip ('.')) if (n.skip ('.'))
{ {
std::string mod; std::string mod;
if (n.skip ('~'))
{
context.debug ("using negative logic");
mSense = "negative";
}
if (n.getUntil (":", mod)) if (n.getUntil (":", mod))
{ {
if (validMod (mod)) if (validMod (mod))

View File

@@ -36,6 +36,7 @@ class Att
public: public:
Att (); Att ();
Att (const std::string&, const std::string&, const std::string&); Att (const std::string&, const std::string&, const std::string&);
Att (const std::string&, const std::string&, const std::string&, const std::string&);
Att (const std::string&, const std::string&, int); Att (const std::string&, const std::string&, int);
Att (const std::string&, const std::string&); Att (const std::string&, const std::string&);
Att (const std::string&, int); Att (const std::string&, int);
@@ -44,6 +45,7 @@ public:
bool operator== (const Att&) const; bool operator== (const Att&) const;
~Att (); ~Att ();
bool logicSense (bool match) const;
bool valid (const std::string&) const; bool valid (const std::string&) const;
static bool validInternalName (const std::string&); static bool validInternalName (const std::string&);
static bool validModifiableName (const std::string&); static bool validModifiableName (const std::string&);
@@ -82,6 +84,7 @@ private:
std::string mName; std::string mName;
std::string mValue; std::string mValue;
std::string mMod; std::string mMod;
std::string mSense;
}; };
#endif #endif

View File

@@ -861,12 +861,20 @@ void Context::autoFilter (Att& a, Filter& f)
} }
// Projects are matched left-most. // Projects are matched left-most.
else if (a.name () == "project" && a.mod () == "") else if (a.name () == "project" && (a.mod () == "" || a.mod () == "not"))
{ {
if (a.value () != "") if (a.value () != "")
{ {
f.push_back (Att ("project", "startswith", a.value ())); if (a.mod () == "not")
debug ("auto filter: " + a.name () + ".startswith:" + a.value ()); {
f.push_back (Att ("project", "startswith", a.value (), "negative"));
debug ("auto filter: " + a.name () + ".~startswith:" + a.value ());
}
else
{
f.push_back (Att ("project", "startswith", a.value ()));
debug ("auto filter: " + a.name () + ".startswith:" + a.value ());
}
} }
else else
{ {

View File

@@ -81,12 +81,12 @@ bool Filter::pass (const Record& record) const
// are willing to invest a week understanding and testing it. // are willing to invest a week understanding and testing it.
if (att->modType (att->mod ()) == "positive") if (att->modType (att->mod ()) == "positive")
{ {
if (! (pass || annotation_pass_count)) if (! att->logicSense (pass || annotation_pass_count))
return false; return false;
} }
else else
{ {
if (! (pass && annotation_fail_count == 0)) if (! att->logicSense (pass && annotation_fail_count == 0))
return false; return false;
} }
} }
@@ -102,7 +102,7 @@ bool Filter::pass (const Record& record) const
// An individual attribute match failure is enough to fail the filter. // An individual attribute match failure is enough to fail the filter.
if ((r = record.find (att->name ())) != record.end ()) if ((r = record.find (att->name ())) != record.end ())
{ {
if (! att->match (r->second)) if (! att->logicSense (att->match (r->second)))
return false; return false;
} }
else if (! att->match (Att ())) else if (! att->match (Att ()))

View File

@@ -346,6 +346,9 @@ int longUsage (std::string& outs)
<< " word" << "\n" << " word" << "\n"
<< " noword" << "\n" << " noword" << "\n"
<< "\n" << "\n"
<< "Modifiers can be inverted with the ~ character:" << "\n"
<< " project.~is is equivalent to project.isnt" << "\n"
<< "\n"
<< " For example:" << "\n" << " For example:" << "\n"
<< " task list due.before:eom priority.not:L" << "\n" << " task list due.before:eom priority.not:L" << "\n"
<< "\n" << "\n"

166
test/filter-prefix.t Executable file
View File

@@ -0,0 +1,166 @@
#! /usr/bin/perl
################################################################################
## taskwarrior - a command line task list manager.
##
## Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez.
## 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 => 85;
# 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{../src/task rc:filter.rc add project:foo.uno priority:H +tag one foo};
qx{../src/task rc:filter.rc add project:foo.dos priority:H two};
qx{../src/task rc:filter.rc add project:foo.tres three};
qx{../src/task rc:filter.rc add project:bar.uno priority:H four};
qx{../src/task rc:filter.rc add project:bar.dos +tag five};
qx{../src/task rc:filter.rc add project:bar.tres six foo};
qx{../src/task rc:filter.rc add project:bazuno seven bar foo};
qx{../src/task rc:filter.rc add project:bazdos eight bar foo};
my $output = qx{../src/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');
like ($output, qr/eight/, 'a8');
$output = qx{../src/task rc:filter.rc list project:foo};
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');
unlike ($output, qr/eight/, 'b8');
$output = qx{../src/task rc:filter.rc list project.not:foo};
unlike ($output, qr/one/, 'c1');
unlike ($output, qr/two/, 'c2');
unlike ($output, qr/three/, 'c3');
like ($output, qr/four/, 'c4');
like ($output, qr/five/, 'c5');
like ($output, qr/six/, 'c6');
like ($output, qr/seven/, 'c7');
like ($output, qr/eight/, 'c8');
$output = qx{../src/task rc:filter.rc list project.startswith:bar};
unlike ($output, qr/one/, 'd1');
unlike ($output, qr/two/, 'd2');
unlike ($output, qr/three/, 'd3');
like ($output, qr/four/, 'd4');
like ($output, qr/five/, 'd5');
like ($output, qr/six/, 'd6');
unlike ($output, qr/seven/, 'd7');
unlike ($output, qr/eight/, 'd8');
$output = qx{../src/task rc:filter.rc list project.~startswith:bar};
like ($output, qr/one/, 'e1');
like ($output, qr/two/, 'e2');
like ($output, qr/three/, 'e3');
unlike ($output, qr/four/, 'e4');
unlike ($output, qr/five/, 'e5');
unlike ($output, qr/six/, 'e6');
like ($output, qr/seven/, 'e7');
like ($output, qr/eight/, 'e8');
$output = qx{../src/task rc:filter.rc list project:ba};
unlike ($output, qr/one/, 'f1');
unlike ($output, qr/two/, 'f2');
unlike ($output, qr/three/, 'f3');
like ($output, qr/four/, 'f4');
like ($output, qr/five/, 'f5');
like ($output, qr/six/, 'f6');
like ($output, qr/seven/, 'f7');
like ($output, qr/eight/, 'f8');
$output = qx{../src/task rc:filter.rc list project.not:ba};
like ($output, qr/one/, 'g1');
like ($output, qr/two/, 'g2');
like ($output, qr/three/, 'g3');
unlike ($output, qr/four/, 'g4');
unlike ($output, qr/five/, 'g5');
unlike ($output, qr/six/, 'g6');
unlike ($output, qr/seven/, 'g7');
unlike ($output, qr/eight/, 'g8');
$output = qx{../src/task rc:filter.rc list project.~startswith:ba};
like ($output, qr/one/, 'h1');
like ($output, qr/two/, 'h2');
like ($output, qr/three/, 'h3');
unlike ($output, qr/four/, 'h4');
unlike ($output, qr/five/, 'h5');
unlike ($output, qr/six/, 'h6');
unlike ($output, qr/seven/, 'h7');
unlike ($output, qr/eight/, 'h8');
$output = qx{../src/task rc:filter.rc list description.has:foo};
like ($output, qr/one/, 'i1');
unlike ($output, qr/two/, 'i2');
unlike ($output, qr/three/, 'i3');
unlike ($output, qr/four/, 'i4');
unlike ($output, qr/five/, 'i5');
like ($output, qr/six/, 'i6');
like ($output, qr/seven/, 'i7');
like ($output, qr/eight/, 'i8');
$output = qx{../src/task rc:filter.rc list description.~has:foo};
unlike ($output, qr/one/, 'j1');
like ($output, qr/two/, 'j2');
like ($output, qr/three/, 'j3');
like ($output, qr/four/, 'j4');
like ($output, qr/five/, 'j5');
unlike ($output, qr/six/, 'j6');
unlike ($output, qr/seven/, 'j7');
unlike ($output, qr/eight/, 'j8');
# 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 'filter.rc';
ok (!-r 'filter.rc', 'Removed filter.rc');
exit 0;