From d135dc233761c15f192914b21eff5f6fe949ee23 Mon Sep 17 00:00:00 2001
From: Paul Beckingham
Date: Mon, 4 May 2009 22:24:43 -0400
Subject: [PATCH 01/16] Enhancement - id sequences
- Recognizes id sequences on the command line. Doesn't do anything
with them yet.
---
src/T.h | 3 ++
src/parse.cpp | 104 ++++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 96 insertions(+), 11 deletions(-)
diff --git a/src/T.h b/src/T.h
index 14004a9c3..5e457aba1 100644
--- a/src/T.h
+++ b/src/T.h
@@ -50,6 +50,8 @@ public:
int getId () const { return mId; }
void setId (int id) { mId = id; }
+ std::vector getAllIds () const { return mSequence; }
+ void addId (int id) { mSequence.push_back (id); }
status getStatus () const { return mStatus; }
void setStatus (status s) { mStatus = s; }
@@ -95,6 +97,7 @@ private:
status mStatus;
std::string mUUID;
int mId;
+ std::vector mSequence;
std::string mDescription;
std::vector mTags;
std::vector mRemoveTags;
diff --git a/src/parse.cpp b/src/parse.cpp
index f4778307e..c57762ec9 100644
--- a/src/parse.cpp
+++ b/src/parse.cpp
@@ -306,6 +306,60 @@ static bool validId (const std::string& input)
return true;
}
+////////////////////////////////////////////////////////////////////////////////
+// 1,2-4,6
+static bool validSequence (
+ const std::string& input,
+ std::vector & ids)
+{
+ std::vector ranges;
+ split (ranges, input, ',');
+
+ std::vector ::iterator it;
+ for (it = ranges.begin (); it != ranges.end (); ++it)
+ {
+ std::vector range;
+ split (range, *it, '-');
+
+ switch (range.size ())
+ {
+ case 1:
+ if (! validId (range[0]))
+ return false;
+
+ int id = ::atoi (range[0].c_str ());
+ ids.push_back (id);
+// std::cout << "# seq: " << id << std::endl;
+ break;
+
+ case 2:
+ {
+ if (! validId (range[0]) ||
+ ! validId (range[1]))
+ return false;
+
+ int low = ::atoi (range[0].c_str ());
+ int high = ::atoi (range[1].c_str ());
+ if (low >= high)
+ return false;
+
+ for (int i = low; i <= high; ++i)
+// {
+ ids.push_back (i);
+// std::cout << "# seq: " << i << std::endl;
+// }
+ }
+ break;
+
+ default:
+ return false;
+ break;
+ }
+ }
+
+ return ids.size () > 1 ? true : false;
+}
+
////////////////////////////////////////////////////////////////////////////////
static bool validTag (const std::string& input)
{
@@ -390,15 +444,25 @@ bool validDuration (std::string& input)
}
////////////////////////////////////////////////////////////////////////////////
-// Token Distinguishing characteristic
-// ------- -----------------------------
-// command first positional
-// id \d+
-// description default, accumulate
-// substitution /\w+/\w*/
-// tags [-+]\w+
-// attributes \w+:.+
+// Token EBNF
+// ------- ----------------------------------
+// command first non-id recognized argument
//
+// id ::= \d+
+//
+// substitution ::= "/" from "/" to "/g"
+// | "/" from "/" to "/" ;
+//
+// tags ::= "+" word
+// | "-" word ;
+//
+// attributes ::= word ":" value
+// | word ":"
+//
+// sequence ::= id "," sequence
+// | id "-" id ;
+//
+// description (whatever isn't one of the above)
void parse (
std::vector & args,
std::string& command,
@@ -420,12 +484,30 @@ void parse (
std::string from;
std::string to;
bool global;
+ std::vector sequence;
// An id is the first argument found that contains all digits.
- if (lowerCase (command) != "add" && // "add" doesn't require an ID
- task.getId () == 0 &&
- validId (arg))
+ if (lowerCase (command) != "add" &&
+/*
+ task.getSequenceCount () == 0 &&
+*/
+ validSequence (arg, sequence))
+ {
+/*
+ for (?)
+ task.addSequence (?)
+*/
+ std::cout << "# look like a sequence" << std::endl;
+ foreach (id, sequence)
+ task.addId (*id);
+ }
+
+ else if (lowerCase (command) != "add" && // "add" doesn't require an ID
+ task.getId () == 0 &&
+ validId (arg))
+ {
task.setId (::atoi (arg.c_str ()));
+ }
// Tags begin with + or - and contain arbitrary text.
else if (validTag (arg))
From d7f9b2165c84c79b5ae15a93456cad12fcd35b18 Mon Sep 17 00:00:00 2001
From: Paul Beckingham
Date: Mon, 4 May 2009 22:31:14 -0400
Subject: [PATCH 02/16] Documentation Update - Correction
- Fixed sample commands, which were missing the "add" keyword (thanks
to T. Charles Yun).
---
html/recur.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/html/recur.html b/html/recur.html
index e4365fcec..5d7a7106a 100644
--- a/html/recur.html
+++ b/html/recur.html
@@ -52,7 +52,7 @@
generated on a regular basis. Consider the example:
-
% task Pay rent due:7/1/2008 recur:monthly
+
% task add Pay rent due:7/1/2008 recur:monthly
If today's date is 7/10, for example, then that due date is in the past, and
@@ -81,7 +81,7 @@ ID Project Pri Due Active Age Description
Thursdays instead:
This create a weekly recurring task that expires on 8/31/2008. What this means
From 51a78ab996817d267de9b1786eb2b9d66dff7905 Mon Sep 17 00:00:00 2001
From: Paul Beckingham
Date: Mon, 4 May 2009 23:22:34 -0400
Subject: [PATCH 03/16] Documentation Update - Mention new mailing list
- Added mention of new mailing list, as a place to discuss task
features.
---
html/task.html | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/html/task.html b/html/task.html
index b46178eab..06812a799 100644
--- a/html/task.html
+++ b/html/task.html
@@ -71,6 +71,12 @@
which illustrates many of task's features.
From 2cc625f631a58f2db37419cfca7785b388615201 Mon Sep 17 00:00:00 2001
From: Paul Beckingham
Date: Mon, 4 May 2009 23:27:22 -0400
Subject: [PATCH 04/16] Documentation Update - Mentioned mailing list
- Added mention of the mailing list as a better alternative than
sending email to PB.
---
README | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README b/README
index 01b01accd..5427555d0 100644
--- a/README
+++ b/README
@@ -44,7 +44,11 @@ All feedback is welcome, in addition to any bug reports or patches to:
task@beckingham.net
-Got an idea for an enhancement? Send a message!
+Or better yet, get involved in the discussion at
+
+ http://groups.google.com/group/taskprogram
+
+Got an idea for an enhancement? Post a message!
I have found that task makes me more productive and organized.
I hope task can do the same for you.
From 410a63fe1423edfbca4c815a20b5ee1ea6fc6e9d Mon Sep 17 00:00:00 2001
From: Paul Beckingham
Date: Tue, 5 May 2009 01:03:07 -0400
Subject: [PATCH 05/16] Enhancement - info on recurring tasks
- Added recurrence information (parent, mask, imask, recur, until)
to the info report when the task is either a recurring task or a
synthetic task.
---
ChangeLog | 2 ++
html/task.html | 2 ++
src/report.cpp | 56 +++++++++++++++++++++++++++++++-------------------
3 files changed, 39 insertions(+), 21 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 584e4e922..402093c70 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,8 @@
1.7.0 (?) ?
+ Improved the errors when parsing a corrupt or unrecognized pending.data
or completed.data file (thanks to T. Charles Yun).
+ + Added details to the "info" report about recurring tasks (thanks to T.
+ Charles Yun).
------ old releases ------------------------------
diff --git a/html/task.html b/html/task.html
index 06812a799..4c9e3f970 100644
--- a/html/task.html
+++ b/html/task.html
@@ -116,6 +116,8 @@
Improved the errors when parsing a corrupt or unrecognized pending.data
or completed.data file (thanks to T. Charles Yun).
+
Added details to the "info" report about recurring tasks (thanks to T.
+ Charles Yun).
+ Some task commands require an ID to be specified. For example:
+
+
+
% task 3 done
+
+
+ This marks a single task as done. But if you wanted to mark
+ several tasks as done, you could use:
+
+
+
% task 3,4,5 done
+
+
+ Which would mark tasks 3, 4 and 5 as all done. In this example,
+ the three IDs are consecutive, which means you could also have
+ entered:
+
+
+
% task 3-5 done
+
+
+ Or in a more complex example:
+
+
+
% task 1,3-5,12 23-25 done
+
+
+ This would mark tasks 1, 3, 4, 5, 12, 23, 24 and 25 as done.
+ Note that this example uses two sequences, separated by a space.
+
+
+
+ You must be careful though. Task tries very carefully to do
+ the right thing when it interprets the command line, but must
+ still impose some rules so that it can unambiguously read the
+ command. If you use one or more sequences, then they must
+ appear on the command line adjacent to each other. If they
+ are separated by something else, then task assumes the second
+ and subsequent set is not a sequence. Here is an example
+ of this:
+
+
+
% task 3 Order part number 4-123
+
+
+ Clearly the 4-123 is a part number, and not a sequence.
+ Task is being asked to modify the description of task 3 to be
+ "Order part number 4-123". Note that the ID is separated
+ from the part number by something other than a sequence.
+ Here is a bad example that task will misinterpret:
+
+
+
% task 3 4-123 is back-ordered, try again next week
+
+
+ The intent here is that task 3 have its description modified to be
+ "40123 is back-ordered, try again next week", but will be
+ misinterpreted as tasks 3, 4, 5, 6 ... 123 will all be modified
+ to have the description "is back-ordered, try again next week".
+ The solution is to quote the whole description:
+
+
+
% task 3 "4-123 is back-ordered, try again next week"
+
+
+
+
+
+
+ Copyright 2006-2009, P. Beckingham. All rights reserved.
+
Usage: task
- task add [tags] [attrs] desc...
- task append [tags] [attrs] desc...
- task annotate ID desc...
- task completed [tags] [attrs] desc...
- task ID [tags] [attrs] [desc...]
- task ID /from/to/
- task delete ID
- task undelete ID
- task info ID
- task start ID
- task stop ID
- task done ID
- task undo ID
- task projects
- task tags
- task summary
- task history
- task ghistory
- task next
- task calendar
- task active
- task overdue
- 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
-
-Tags are arbitrary words, any quantity:
- +tag The + means add the tag
- -tag The - means remove the tag
-
-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
- task li pro:Home
-
-Some task descriptions need to be escaped because of the shell:
- task add "quoted ' quote"
- task add escaped \' quote
-
-Many characters have special meaning to the shell, including:
- $ ! ' " ( ) ; \ ` * ? { } [ ] < > | & % # ~
-
-
-
-
-
-
- Copyright 2006-2009, P. Beckingham. All rights reserved.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/command.cpp b/src/command.cpp
index d04953734..fcc512fb9 100644
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -193,42 +193,34 @@ std::string handleTags (TDB& tdb, T& task, Config& conf)
std::string handleUndelete (TDB& tdb, T& task, Config& conf)
{
std::stringstream out;
+
std::vector all;
tdb.allPendingT (all);
+ filterSequence (all, task);
- int id = task.getId ();
- std::vector ::iterator it;
- for (it = all.begin (); it != all.end (); ++it)
+ foreach (t, all)
{
- if (it->getId () == id)
+ if (t->getStatus () == T::deleted)
{
- if (it->getStatus () == T::deleted)
- {
- if (it->getAttribute ("recur") != "")
- {
- out << "Task does not support 'undelete' for recurring tasks." << std::endl;
- return out.str ();
- }
+ if (t->getAttribute ("recur") != "")
+ out << "Task does not support 'undo' for recurring tasks.\n";
- T restored (*it);
- restored.setStatus (T::pending);
- restored.removeAttribute ("end");
- tdb.modifyT (restored);
+ t->setStatus (T::pending);
+ t->removeAttribute ("end");
+ tdb.modifyT (*t);
- out << "Task " << id << " successfully undeleted." << std::endl;
- return out.str ();
- }
- else
- {
- out << "Task " << id << " is not deleted - therefore cannot undelete." << std::endl;
- return out.str ();
- }
+ out << "Task " << t->getId () << " '" << t->getDescription () << "' successfully undeleted.\n";
+ }
+ else
+ {
+ out << "Task " << t->getId () << " '" << t->getDescription () << "' is not deleted - therefore cannot be undeleted.\n";
}
}
- out << "Task " << id
- << " not found - tasks can only be reliably undeleted if the undelete" << std::endl
- << "command is run immediately after the errant delete command." << std::endl;
+ out << "\n"
+ << "Please note that tasks can only be reliably undeleted if the undelete "
+ << "command is run immediately after the errant delete command."
+ << std::endl;
return out.str ();
}
@@ -242,37 +234,31 @@ std::string handleUndo (TDB& tdb, T& task, Config& conf)
std::vector all;
tdb.allPendingT (all);
+ filterSequence (all, task);
- int id = task.getId ();
- std::vector ::iterator it;
- for (it = all.begin (); it != all.end (); ++it)
+ foreach (t, all)
{
- if (it->getId () == id)
+ if (t->getStatus () == T::completed)
{
- if (it->getStatus () == T::completed)
- {
- if (it->getAttribute ("recur") != "")
- return std::string ("Task does not support 'undo' for recurring tasks.\n");
+ if (t->getAttribute ("recur") != "")
+ out << "Task does not support 'undo' for recurring tasks.\n";
- T restored (*it);
- restored.setStatus (T::pending);
- restored.removeAttribute ("end");
- tdb.modifyT (restored);
+ t->setStatus (T::pending);
+ t->removeAttribute ("end");
+ tdb.modifyT (*t);
- out << "Task " << id << " successfully undone." << std::endl;
- return out.str ();
- }
- else
- {
- out << "Task " << id << " is not done - therefore cannot be undone." << std::endl;
- return out.str ();
- }
+ out << "Task " << t->getId () << " '" << t->getDescription () << "' successfully undone." << std::endl;
+ }
+ else
+ {
+ out << "Task " << t->getId () << " '" << t->getDescription () << "' is not done - therefore cannot be undone." << std::endl;
}
}
- out << "Task " << id
- << " not found - tasks can only be reliably undone if the undo" << std::endl
- << "command is run immediately after the errant done command." << std::endl;
+ out << std::endl
+ << "Please note that tasks can only be reliably undone if the undo "
+ << "command is run immediately after the errant done command."
+ << std::endl;
return out.str ();
}
@@ -367,8 +353,9 @@ std::string handleVersion (Config& conf)
// Complain about configuration variables that are not recognized.
// These are the regular configuration variables.
+ // Note that there is a leading and trailing space.
std::string recognized =
- "blanklines color color.active color.due color.overdue color.pri.H "
+ " 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 echo.command locking monthsperline nag "
@@ -377,7 +364,7 @@ std::string handleVersion (Config& conf)
"import.synonym.tags import.synonym.entry import.synonym.start "
"import.synonym.due import.synonym.recur import.synonym.end "
"import.synonym.project import.synonym.priority import.synonym.fg "
- "import.synonym.bg import.synonym.description";
+ "import.synonym.bg import.synonym.description ";
// 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
@@ -387,7 +374,10 @@ std::string handleVersion (Config& conf)
std::vector unrecognized;
foreach (i, all)
{
- if (recognized.find (*i) == std::string::npos)
+ // Disallow partial matches by tacking a leading an trailing space on each
+ // variable name.
+ std::string pattern = " " + *i + " ";
+ if (recognized.find (pattern) == std::string::npos)
{
// These are special configuration variables, because their name is
// dynamic.
@@ -441,63 +431,72 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
std::vector all;
tdb.allPendingT (all);
+ filterSequence (all, task);
+
foreach (t, all)
{
- if (t->getId () == task.getId () || task.sequenceContains (t->getId ()))
+ std::stringstream question;
+ question << "Permanently delete task "
+ << t->getId ()
+ << " '"
+ << t->getDescription ()
+ << "'?";
+
+ if (!conf.get (std::string ("confirmation"), false) || confirm (question.str ()))
{
- if (!conf.get (std::string ("confirmation"), false) || confirm ("Permanently delete task?"))
+ // Check for the more complex case of a recurring task. If this is a
+ // recurring task, get confirmation to delete them all.
+ std::string parent = t->getAttribute ("parent");
+ if (parent != "")
{
- // Check for the more complex case of a recurring task. If this is a
- // recurring task, get confirmation to delete them all.
- std::string parent = t->getAttribute ("parent");
- if (parent != "")
+ if (confirm ("This is a recurring task. Do you want to delete all pending recurrences of this same task?"))
{
- if (confirm ("This is a recurring task. Do you want to delete all pending recurrences of this same task?"))
+ // Scan all pending tasks for siblings of this task, and the parent
+ // itself, and delete them.
+ foreach (sibling, all)
{
- // Scan all pending tasks for siblings of this task, and the parent
- // itself, and delete them.
- foreach (sibling, all)
+ if (sibling->getAttribute ("parent") == parent ||
+ sibling->getUUID () == parent)
{
- if (sibling->getAttribute ("parent") == parent ||
- sibling->getUUID () == parent)
- {
- tdb.deleteT (*sibling);
- if (conf.get ("echo.command", true))
- out << "Deleting recurring task "
- << sibling->getId ()
- << " "
- << sibling->getDescription ()
- << std::endl;
- }
+ tdb.deleteT (*sibling);
+ if (conf.get ("echo.command", true))
+ out << "Deleting recurring task "
+ << sibling->getId ()
+ << " '"
+ << sibling->getDescription ()
+ << "'"
+ << std::endl;
}
}
- else
- {
- // Update mask in parent.
- t->setStatus (T::deleted);
- updateRecurrenceMask (tdb, all, *t);
- tdb.deleteT (*t);
- out << "Deleting recurring task "
- << t->getId ()
- << " "
- << t->getDescription ()
- << std::endl;
- }
}
else
{
+ // Update mask in parent.
+ t->setStatus (T::deleted);
+ updateRecurrenceMask (tdb, all, *t);
tdb.deleteT (*t);
- if (conf.get ("echo.command", true))
- out << "Deleting task "
- << t->getId ()
- << " "
- << t->getDescription ()
- << std::endl;
+ out << "Deleting recurring task "
+ << t->getId ()
+ << " '"
+ << t->getDescription ()
+ << "'"
+ << std::endl;
}
}
else
- out << "Task not deleted." << std::endl;
+ {
+ tdb.deleteT (*t);
+ if (conf.get ("echo.command", true))
+ out << "Deleting task "
+ << t->getId ()
+ << " '"
+ << t->getDescription ()
+ << "'"
+ << std::endl;
+ }
}
+ else
+ out << "Task not deleted." << std::endl;
}
return out.str ();
@@ -506,81 +505,66 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
////////////////////////////////////////////////////////////////////////////////
std::string handleStart (TDB& tdb, T& task, Config& conf)
{
+ std::stringstream out;
+
std::vector all;
tdb.pendingT (all);
+ filterSequence (all, task);
- std::vector ::iterator it;
- for (it = all.begin (); it != all.end (); ++it)
+ foreach (t, all)
{
- if (it->getId () == task.getId ())
+ if (t->getAttribute ("start") == "")
{
- T original (*it);
- std::stringstream out;
+ char startTime[16];
+ sprintf (startTime, "%u", (unsigned int) time (NULL));
+ t->setAttribute ("start", startTime);
- if (original.getAttribute ("start") == "")
- {
- char startTime[16];
- sprintf (startTime, "%u", (unsigned int) time (NULL));
- original.setAttribute ("start", startTime);
+ tdb.modifyT (*t);
- original.setId (task.getId ());
- tdb.modifyT (original);
-
- if (conf.get ("echo.command", true))
- out << "Started "
- << original.getId ()
- << " "
- << original.getDescription ()
- << std::endl;
- nag (tdb, task, conf);
- }
- else
- {
- out << "Task " << task.getId () << " already started." << std::endl;
- }
-
- return out.str ();
+ if (conf.get ("echo.command", true))
+ out << "Started "
+ << t->getId ()
+ << " '"
+ << t->getDescription ()
+ << "'"
+ << std::endl;
+ nag (tdb, task, conf);
+ }
+ else
+ {
+ out << "Task " << t->getId () << " '" << t->getDescription () << "' already started." << std::endl;
}
}
- throw std::string ("Task not found.");
- return std::string (""); // To satisfy gcc.
+ return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
std::string handleStop (TDB& tdb, T& task, Config& conf)
{
+ std::stringstream out;
+
std::vector all;
tdb.pendingT (all);
+ filterSequence (all, task);
- std::vector ::iterator it;
- for (it = all.begin (); it != all.end (); ++it)
+ foreach (t, all)
{
- if (it->getId () == task.getId ())
+ if (t->getAttribute ("start") != "")
{
- T original (*it);
- std::stringstream out;
+ t->removeAttribute ("start");
+ tdb.modifyT (*t);
- if (original.getAttribute ("start") != "")
- {
- original.removeAttribute ("start");
- original.setId (task.getId ());
- tdb.modifyT (original);
-
- if (conf.get ("echo.command", true))
- out << "Stopped " << original.getId () << " " << original.getDescription () << std::endl;
- }
- else
- {
- out << "Task " << task.getId () << " not started." << std::endl;
- }
-
- return out.str ();
+ if (conf.get ("echo.command", true))
+ out << "Stopped " << t->getId () << " '" << t->getDescription () << "'" << std::endl;
+ }
+ else
+ {
+ out << "Task " << t->getId () << " '" << t->getDescription () << "' not started." << std::endl;
}
}
- throw std::string ("Task not found.");
- return std::string (""); // To satisfy gcc.
+ return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
@@ -588,30 +572,30 @@ 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.");
-
- // Now update mask in parent.
std::vector all;
tdb.allPendingT (all);
- foreach (t, all)
- {
- if (t->getId () == task.getId ())
- {
- if (conf.get ("echo.command", true))
- out << "Completed "
- << t->getId ()
- << " "
- << t->getDescription ()
- << std::endl;
+ std::vector filtered = all;
+ filterSequence (filtered, task);
- t->setStatus (T::completed);
- updateRecurrenceMask (tdb, all, *t);
- break;
- }
+ foreach (t, filtered)
+ {
+ t->setStatus (T::completed);
+ if (!tdb.completeT (*t))
+ throw std::string ("Could not mark task as completed.");
+
+ // Now update mask in parent.
+ if (conf.get ("echo.command", true))
+ out << "Completed "
+ << t->getId ()
+ << " '"
+ << t->getDescription ()
+ << "'"
+ << std::endl;
+
+ updateRecurrenceMask (tdb, all, *t);
+ nag (tdb, task, conf);
}
- nag (tdb, task, conf);
return out.str ();
}
@@ -675,164 +659,51 @@ std::string handleExport (TDB& tdb, T& task, Config& conf)
////////////////////////////////////////////////////////////////////////////////
std::string handleModify (TDB& tdb, T& task, Config& conf)
{
+ int count = 0;
std::stringstream out;
std::vector all;
tdb.allPendingT (all);
- // Lookup the complete task.
- T complete = findT (task.getId (), all);
-
- // Perform some logical consistency checks.
- if (task.getAttribute ("recur") != "" &&
- task.getAttribute ("due") == "" &&
- complete.getAttribute ("due") == "")
- throw std::string ("You cannot specify a recurring task without a due date.");
-
- if (task.getAttribute ("until") != "" &&
- task.getAttribute ("recur") == "" &&
- complete.getAttribute ("recur") == "")
- throw std::string ("You cannot specify an until date for a non-recurring task.");
-
- int count = 0;
- std::vector ::iterator it;
- for (it = all.begin (); it != all.end (); ++it)
+ std::vector filtered = all;
+ filterSequence (filtered, task);
+ foreach (seq, filtered)
{
- if (it->getId () == complete.getId () || // Self
- (complete.getAttribute ("parent") != "" &&
- it->getAttribute ("parent") == complete.getAttribute ("parent")) || // Sibling
- it->getUUID () == complete.getAttribute ("parent")) // Parent
+ // Perform some logical consistency checks.
+ if (task.getAttribute ("recur") != "" &&
+ task.getAttribute ("due") == "" &&
+ seq->getAttribute ("due") == "")
+ throw std::string ("You cannot specify a recurring task without a due date.");
+
+ if (task.getAttribute ("until") != "" &&
+ task.getAttribute ("recur") == "" &&
+ seq->getAttribute ("recur") == "")
+ throw std::string ("You cannot specify an until date for a non-recurring task.");
+
+ // Make all changes.
+ foreach (other, all)
{
- T original (*it);
-
- // A non-zero value forces a file write.
- int changes = 0;
-
- // Apply a new description, if any.
- if (task.getDescription () != "")
+ if (other->getId () == seq->getId () || // Self
+ (seq->getAttribute ("parent") != "" &&
+ seq->getAttribute ("parent") == other->getAttribute ("parent")) || // Sibling
+ other->getUUID () == seq->getAttribute ("parent")) // Parent
{
- original.setDescription (task.getDescription ());
- ++changes;
+ // A non-zero value forces a file write.
+ int changes = 0;
+
+ // Apply other deltas.
+ changes += deltaDescription (*other, task);
+ changes += deltaTags (*other, task);
+ changes += deltaAttributes (*other, task);
+ changes += deltaSubstitutions (*other, task);
+
+ if (changes)
+ tdb.modifyT (*other);
+
+ ++count;
}
-
- // Apply or remove tags, if any.
- std::vector tags;
- task.getTags (tags);
- for (unsigned int i = 0; i < tags.size (); ++i)
- {
- if (tags[i][0] == '+')
- original.addTag (tags[i].substr (1, std::string::npos));
- else
- original.addTag (tags[i]);
-
- ++changes;
- }
-
- task.getRemoveTags (tags);
- for (unsigned int i = 0; i < tags.size (); ++i)
- {
- if (tags[i][0] == '-')
- original.removeTag (tags[i].substr (1, std::string::npos));
- else
- original.removeTag (tags[i]);
-
- ++changes;
- }
-
- // Apply or remove attributes, if any.
- std::map attributes;
- task.getAttributes (attributes);
- foreach (i, attributes)
- {
- if (i->second == "")
- original.removeAttribute (i->first);
- else
- {
- original.setAttribute (i->first, i->second);
-
- // If a "recur" attribute is added, upgrade to a recurring task.
- if (i->first == "recur")
- original.setStatus (T::recurring);
- }
-
- ++changes;
- }
-
- std::string from;
- std::string to;
- bool global;
- task.getSubstitution (from, to, global);
- if (from != "")
- {
- std::string description = original.getDescription ();
- size_t pattern;
-
- if (global)
- {
- // Perform all subs on description.
- while ((pattern = description.find (from)) != std::string::npos)
- {
- description.replace (pattern, from.length (), to);
- ++changes;
- }
-
- original.setDescription (description);
-
- // Perform all subs on annotations.
- std::map annotations;
- original.getAnnotations (annotations);
- std::map ::iterator it;
- for (it = annotations.begin (); it != annotations.end (); ++it)
- {
- while ((pattern = it->second.find (from)) != std::string::npos)
- {
- it->second.replace (pattern, from.length (), to);
- ++changes;
- }
- }
-
- original.setAnnotations (annotations);
- }
- else
- {
- // Perform first description substitution.
- if ((pattern = description.find (from)) != std::string::npos)
- {
- description.replace (pattern, from.length (), to);
- original.setDescription (description);
- ++changes;
- }
- // Failing that, perform the first annotation substitution.
- else
- {
- std::map annotations;
- original.getAnnotations (annotations);
-
- std::map ::iterator it;
- for (it = annotations.begin (); it != annotations.end (); ++it)
- {
- if ((pattern = it->second.find (from)) != std::string::npos)
- {
- it->second.replace (pattern, from.length (), to);
- ++changes;
- break;
- }
- }
-
- original.setAnnotations (annotations);
- }
- }
- }
-
- if (changes)
- tdb.modifyT (original);
-
- ++count;
}
}
- if (count == 0)
- throw std::string ("Task not found.");
-
if (conf.get ("echo.command", true))
out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl;
@@ -842,92 +713,47 @@ std::string handleModify (TDB& tdb, T& task, Config& conf)
////////////////////////////////////////////////////////////////////////////////
std::string handleAppend (TDB& tdb, T& task, Config& conf)
{
+ int count = 0;
std::stringstream out;
std::vector all;
tdb.allPendingT (all);
- // Lookup the complete task.
- T complete = findT (task.getId (), all);
-
- int count = 0;
- std::vector ::iterator it;
- for (it = all.begin (); it != all.end (); ++it)
+ std::vector filtered = all;
+ filterSequence (filtered, task);
+ foreach (seq, filtered)
{
- if (it->getId () == complete.getId () || // Self
- (complete.getAttribute ("parent") != "" &&
- it->getAttribute ("parent") == complete.getAttribute ("parent")) || // Sibling
- it->getUUID () == complete.getAttribute ("parent")) // Parent
+ foreach (other, all)
{
- T original (*it);
-
- // A non-zero value forces a file write.
- int changes = 0;
-
- // Apply a new description, if any.
- if (task.getDescription () != "")
+ if (other->getId () == seq->getId () || // Self
+ (seq->getAttribute ("parent") != "" &&
+ seq->getAttribute ("parent") == other->getAttribute ("parent")) || // Sibling
+ other->getUUID () == seq->getAttribute ("parent")) // Parent
{
- original.setDescription (original.getDescription () +
- " " +
- task.getDescription ());
- ++changes;
+ // A non-zero value forces a file write.
+ int changes = 0;
+
+ // Apply other deltas.
+ changes += deltaAppend (*other, task);
+ changes += deltaTags (*other, task);
+ changes += deltaAttributes (*other, task);
+
+ if (changes)
+ {
+ tdb.modifyT (*other);
+
+ if (conf.get ("echo.command", true))
+ out << "Appended '"
+ << task.getDescription ()
+ << "' to task "
+ << other->getId ()
+ << std::endl;
+ }
+
+ ++count;
}
-
- // Apply or remove tags, if any.
- std::vector tags;
- task.getTags (tags);
- for (unsigned int i = 0; i < tags.size (); ++i)
- {
- if (tags[i][0] == '+')
- original.addTag (tags[i].substr (1, std::string::npos));
- else
- original.addTag (tags[i]);
-
- ++changes;
- }
-
- task.getRemoveTags (tags);
- for (unsigned int i = 0; i < tags.size (); ++i)
- {
- if (tags[i][0] == '-')
- original.removeTag (tags[i].substr (1, std::string::npos));
- else
- original.removeTag (tags[i]);
-
- ++changes;
- }
-
- // Apply or remove attributes, if any.
- std::map attributes;
- task.getAttributes (attributes);
- foreach (i, attributes)
- {
- if (i->second == "")
- original.removeAttribute (i->first);
- else
- original.setAttribute (i->first, i->second);
-
- ++changes;
- }
-
- if (changes)
- {
- tdb.modifyT (original);
-
- if (conf.get ("echo.command", true))
- out << "Appended '"
- << task.getDescription ()
- << "' to task "
- << original.getId ()
- << std::endl;
- }
-
- ++count;
}
}
- if (count == 0)
- throw std::string ("Task not found.");
-
if (conf.get ("echo.command", true))
out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl;
@@ -1028,28 +854,22 @@ std::string handleAnnotate (TDB& tdb, T& task, Config& conf)
std::stringstream out;
std::vector all;
tdb.pendingT (all);
+ filterSequence (all, task);
- std::vector ::iterator it;
- for (it = all.begin (); it != all.end (); ++it)
+ foreach (t, all)
{
- if (it->getId () == task.getId ())
- {
- it->addAnnotation (task.getDescription ());
- tdb.modifyT (*it);
+ t->addAnnotation (task.getDescription ());
+ tdb.modifyT (*t);
- if (conf.get ("echo.command", true))
- out << "Annotated "
- << task.getId ()
- << " with '"
- << task.getDescription ()
- << "'"
- << std::endl;
-
- return out.str ();
- }
+ if (conf.get ("echo.command", true))
+ out << "Annotated "
+ << t->getId ()
+ << " with '"
+ << t->getDescription ()
+ << "'"
+ << std::endl;
}
- throw std::string ("Task not found.");
return out.str ();
}
@@ -1065,3 +885,156 @@ T findT (int id, const std::vector & all)
}
////////////////////////////////////////////////////////////////////////////////
+int deltaAppend (T& task, T& delta)
+{
+ if (delta.getDescription () != "")
+ {
+ task.setDescription (
+ task.getDescription () +
+ " " +
+ delta.getDescription ());
+
+ return 1;
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int deltaDescription (T& task, T& delta)
+{
+ if (delta.getDescription () != "")
+ {
+ task.setDescription (delta.getDescription ());
+ return 1;
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int deltaTags (T& task, T& delta)
+{
+ int changes = 0;
+
+ // Apply or remove tags, if any.
+ std::vector tags;
+ delta.getTags (tags);
+ for (unsigned int i = 0; i < tags.size (); ++i)
+ {
+ if (tags[i][0] == '+')
+ task.addTag (tags[i].substr (1, std::string::npos));
+ else
+ task.addTag (tags[i]);
+
+ ++changes;
+ }
+
+ delta.getRemoveTags (tags);
+ for (unsigned int i = 0; i < tags.size (); ++i)
+ {
+ if (tags[i][0] == '-')
+ task.removeTag (tags[i].substr (1, std::string::npos));
+ else
+ task.removeTag (tags[i]);
+
+ ++changes;
+ }
+
+ return changes;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int deltaAttributes (T& task, T& delta)
+{
+ int changes = 0;
+
+ std::map attributes;
+ delta.getAttributes (attributes);
+ foreach (i, attributes)
+ {
+ if (i->second == "")
+ task.removeAttribute (i->first);
+ else
+ task.setAttribute (i->first, i->second);
+
+ ++changes;
+ }
+
+ return changes;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+int deltaSubstitutions (T& task, T& delta)
+{
+ int changes = 0;
+ std::string from;
+ std::string to;
+ bool global;
+ delta.getSubstitution (from, to, global);
+
+ if (from != "")
+ {
+ std::string description = task.getDescription ();
+ size_t pattern;
+
+ if (global)
+ {
+ // Perform all subs on description.
+ while ((pattern = description.find (from)) != std::string::npos)
+ {
+ description.replace (pattern, from.length (), to);
+ ++changes;
+ }
+
+ task.setDescription (description);
+
+ // Perform all subs on annotations.
+ std::map annotations;
+ task.getAnnotations (annotations);
+ std::map ::iterator it;
+ for (it = annotations.begin (); it != annotations.end (); ++it)
+ {
+ while ((pattern = it->second.find (from)) != std::string::npos)
+ {
+ it->second.replace (pattern, from.length (), to);
+ ++changes;
+ }
+ }
+
+ task.setAnnotations (annotations);
+ }
+ else
+ {
+ // Perform first description substitution.
+ if ((pattern = description.find (from)) != std::string::npos)
+ {
+ description.replace (pattern, from.length (), to);
+ task.setDescription (description);
+ ++changes;
+ }
+ // Failing that, perform the first annotation substitution.
+ else
+ {
+ std::map annotations;
+ task.getAnnotations (annotations);
+
+ std::map ::iterator it;
+ for (it = annotations.begin (); it != annotations.end (); ++it)
+ {
+ if ((pattern = it->second.find (from)) != std::string::npos)
+ {
+ it->second.replace (pattern, from.length (), to);
+ ++changes;
+ break;
+ }
+ }
+
+ task.setAnnotations (annotations);
+ }
+ }
+ }
+ return changes;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/src/report.cpp b/src/report.cpp
index 841724881..858075a3b 100644
--- a/src/report.cpp
+++ b/src/report.cpp
@@ -46,6 +46,53 @@
#include
#endif
+////////////////////////////////////////////////////////////////////////////////
+void filterSequence (std::vector& all, T& task)
+{
+ std::vector sequence = task.getAllIds ();
+
+ std::vector filtered;
+ std::vector ::iterator t;
+ for (t = all.begin (); t != all.end (); ++t)
+ {
+ std::vector ::iterator s;
+ for (s = sequence.begin (); s != sequence.end (); ++s)
+ if (t->getId () == *s)
+ filtered.push_back (*t);
+ }
+
+ if (sequence.size () != filtered.size ())
+ {
+ std::vector filteredSequence;
+ std::vector ::iterator fs;
+ for (fs = filtered.begin (); fs != filtered.end (); ++fs)
+ filteredSequence.push_back (fs->getId ());
+
+ std::vector left;
+ std::vector right;
+ listDiff (filteredSequence, sequence, left, right);
+ if (left.size ())
+ throw std::string ("Sequence filtering error - please report this error");
+
+ if (right.size ())
+ {
+ std::stringstream out;
+ out << "Task";
+
+ if (right.size () > 1) out << "s";
+
+ std::vector ::iterator s;
+ for (s = right.begin (); s != right.end (); ++s)
+ out << " " << *s;
+
+ out << " not found";
+ throw out.str ();
+ }
+ }
+
+ all = filtered;
+}
+
////////////////////////////////////////////////////////////////////////////////
void filter (std::vector& all, T& task)
{
@@ -1054,6 +1101,7 @@ std::string handleReportGHistory (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;
@@ -1063,6 +1111,7 @@ std::string handleReportGHistory (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;