Merge branch '1.9.0' of tasktools.org:task into 1.9.0
This commit is contained in:
@@ -53,6 +53,8 @@
|
|||||||
+ The coloring of due tasks in reports can now be enabled for all tasks, and not
|
+ The coloring of due tasks in reports can now be enabled for all tasks, and not
|
||||||
only the imminent ones, by setting the configuration variable due=0.
|
only the imminent ones, by setting the configuration variable due=0.
|
||||||
+ Added a new 'task-faq' man page for common questions and answers.
|
+ Added a new 'task-faq' man page for common questions and answers.
|
||||||
|
+ Added a new 'task-color' man page detailing how to set up and use color in
|
||||||
|
task.
|
||||||
+ Fixed bug #316 which caused the timesheet report to display an oddly sorted
|
+ Fixed bug #316 which caused the timesheet report to display an oddly sorted
|
||||||
list.
|
list.
|
||||||
+ Fixed bug #317 which colored tasks in the 'completed' report according to
|
+ Fixed bug #317 which colored tasks in the 'completed' report according to
|
||||||
@@ -63,6 +65,11 @@
|
|||||||
+ Fixed bug that was causing the 'completed' report to sort incorrectly.
|
+ Fixed bug that was causing the 'completed' report to sort incorrectly.
|
||||||
+ Fixed bug that showed a calendar for the year 2037 when 'task calendar due'
|
+ Fixed bug that showed a calendar for the year 2037 when 'task calendar due'
|
||||||
was run, and there are no tasks with due dates.
|
was run, and there are no tasks with due dates.
|
||||||
|
+ Fixed bug #360 which prevented certain modifications to recurring tasks
|
||||||
|
(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 ------------------------------
|
------ old releases ------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
SUBDIRS = src
|
SUBDIRS = src
|
||||||
|
|
||||||
dist_man_MANS = doc/man/task.1 doc/man/taskrc.5 doc/man/task-tutorial.5 doc/man/task-faq.5
|
dist_man_MANS = doc/man/task.1 doc/man/taskrc.5 doc/man/task-tutorial.5 doc/man/task-faq.5 doc/man/task-color.5
|
||||||
|
|
||||||
docdir = $(datadir)/doc/${PACKAGE}-${VERSION}
|
docdir = $(datadir)/doc/${PACKAGE}-${VERSION}
|
||||||
doc_DATA = AUTHORS ChangeLog COPYING NEWS README
|
doc_DATA = AUTHORS ChangeLog COPYING NEWS README
|
||||||
|
|||||||
134
src/Context.cpp
134
src/Context.cpp
@@ -591,6 +591,8 @@ void Context::parse (
|
|||||||
parseTask[name + "." + mod] = attribute;
|
parseTask[name + "." + mod] = attribute;
|
||||||
else
|
else
|
||||||
parseTask[name] = attribute;
|
parseTask[name] = attribute;
|
||||||
|
|
||||||
|
autoFilter (attribute, parseFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// *arg has the appearance of an attribute (foo:bar), but isn't
|
// *arg has the appearance of an attribute (foo:bar), but isn't
|
||||||
@@ -656,6 +658,19 @@ void Context::parse (
|
|||||||
{
|
{
|
||||||
debug ("parse description '" + descCandidate + "'");
|
debug ("parse description '" + descCandidate + "'");
|
||||||
parseTask.set ("description", descCandidate);
|
parseTask.set ("description", descCandidate);
|
||||||
|
|
||||||
|
// Now convert the description to a filter on each word, if necessary.
|
||||||
|
if (parseCmd.isReadOnlyCommand ())
|
||||||
|
{
|
||||||
|
std::vector <std::string> words;
|
||||||
|
split (words, descCandidate, ' ');
|
||||||
|
std::vector <std::string>::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.
|
// At this point, either a sequence or a command should have been found.
|
||||||
@@ -663,9 +678,10 @@ void Context::parse (
|
|||||||
parseCmd.parse (descCandidate);
|
parseCmd.parse (descCandidate);
|
||||||
|
|
||||||
// Read-only command (reports, status, info ...) use filters. Write commands
|
// 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 ())
|
if (parseCmd.isReadOnlyCommand ())
|
||||||
autoFilter (parseTask, parseFilter);
|
autoFilter (parseFilter);
|
||||||
|
|
||||||
// If no command was specified, and there were no command line arguments
|
// If no command was specified, and there were no command line arguments
|
||||||
// then invoke the default command.
|
// 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.
|
// 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.
|
std::vector <std::string> words;
|
||||||
if (att->second.name () == "description" && att->second.mod () == "")
|
split (words, a.value (), ' ');
|
||||||
|
foreach (word, words)
|
||||||
{
|
{
|
||||||
std::vector <std::string> words;
|
f.push_back (Att ("description", "has", *word));
|
||||||
split (words, att->second.value (), ' ');
|
debug ("auto filter: " + a.name () + ".has:" + *word);
|
||||||
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 +/-<tag>.
|
|
||||||
// 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 ());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 +/-<tag>.
|
||||||
|
// 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
|
// 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
|
// of a tag. The prior code provided the illusion of leftmost partial tag
|
||||||
// matches, but was really using the 'contains' and 'nocontains' attribute
|
// matches, but was really using the 'contains' and 'nocontains' attribute
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
void loadCorrectConfigFile ();
|
void loadCorrectConfigFile ();
|
||||||
void loadAliases ();
|
void loadAliases ();
|
||||||
void autoFilter (Task&, Filter&);
|
void autoFilter (Att&, Filter&);
|
||||||
|
void autoFilter (Filter&);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Config config;
|
Config config;
|
||||||
|
|||||||
@@ -1153,13 +1153,14 @@ int handleModify (std::string &outs)
|
|||||||
!task->has ("recur"))
|
!task->has ("recur"))
|
||||||
throw std::string ("You cannot specify an until date for a non-recurring task.");
|
throw std::string ("You cannot specify an until date for a non-recurring task.");
|
||||||
|
|
||||||
if (task->has ("due") &&
|
if (task->has ("due") &&
|
||||||
!context.task.has ("due") &&
|
context.task.has ("due") &&
|
||||||
context.task.has ("recur"))
|
context.task.get ("due") == "")
|
||||||
throw std::string ("You cannot remove the due date from a recurring task.");
|
throw std::string ("You cannot remove the due date from a recurring task.");
|
||||||
|
|
||||||
if (task->has ("recur") &&
|
if (task->has ("recur") &&
|
||||||
!context.task.has ("recur"))
|
context.task.has ("recur") &&
|
||||||
|
context.task.get ("recur") == "")
|
||||||
throw std::string ("You cannot remove the recurrence from a recurring task.");
|
throw std::string ("You cannot remove the recurrence from a recurring task.");
|
||||||
|
|
||||||
// Make all changes.
|
// Make all changes.
|
||||||
|
|||||||
68
src/tests/bug.299.t
Executable file
68
src/tests/bug.299.t
Executable file
@@ -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:<name>
|
||||||
|
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;
|
||||||
|
|
||||||
70
src/tests/bug.360.t
Executable file
70
src/tests/bug.360.t
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#! /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 => 7;
|
||||||
|
|
||||||
|
# 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 a recurring task, generate an instance, then add a project.
|
||||||
|
qx{../task rc:bug.rc add foo due:tomorrow recur:daily};
|
||||||
|
qx{../task rc:bug.rc ls};
|
||||||
|
|
||||||
|
# Result: trying to add the project generates an error about removing
|
||||||
|
# recurrence from a task.
|
||||||
|
my $output = qx{../task rc:bug.rc 2 project:bar};
|
||||||
|
unlike ($output, qr/You cannot remove the recurrence from a recurring task./ms, 'No recurrence removal error');
|
||||||
|
|
||||||
|
# Now try to generate the error above via regular means - ie, is it actually
|
||||||
|
# doing what it should?
|
||||||
|
$output = qx{../task rc:bug.rc 2 recur:};
|
||||||
|
like ($output, qr/You cannot remove the recurrence from a recurring task./ms, 'Recurrence removal error');
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
|
||||||
Reference in New Issue
Block a user