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:

-
% task TPS report due:thursday recur:weekly until:8/31/2008
+
% task add TPS report due:thursday recur:weekly until:8/31/2008

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.

+

+ For the latest news, discussion of proposed task features, and + somewhere to voice your opinions, join us at + http://groups.google.com/group/taskprogram. +

+

Get the Latest Stable Release

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).

diff --git a/src/report.cpp b/src/report.cpp index cdf2a6a79..d95c93de4 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -299,13 +299,17 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf) table.addCell (row, 0, "ID"); table.addCell (row, 1, refTask.getId ()); + std::string status = refTask.getStatus () == T::pending ? "Pending" + : refTask.getStatus () == T::completed ? "Completed" + : refTask.getStatus () == T::deleted ? "Deleted" + : refTask.getStatus () == T::recurring ? "Recurring" + : ""; + if (refTask.getAttribute ("parent") != "") + status += " (Recurring)"; + row = table.addRow (); table.addCell (row, 0, "Status"); - table.addCell (row, 1, ( refTask.getStatus () == T::pending ? "Pending" - : refTask.getStatus () == T::completed ? "Completed" - : refTask.getStatus () == T::deleted ? "Deleted" - : refTask.getStatus () == T::recurring ? "Recurring" - : "")); + table.addCell (row, 1, status); std::string description = refTask.getDescription (); std::string when; @@ -336,26 +340,36 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf) table.addCell (row, 1, refTask.getAttribute ("priority")); } - if (refTask.getStatus () == T::recurring) + if (refTask.getStatus () == T::recurring || + refTask.getAttribute ("parent") != "") { - row = table.addRow (); - table.addCell (row, 0, "Recurrence"); - table.addCell (row, 1, refTask.getAttribute ("recur")); + if (refTask.getAttribute ("recur") != "") + { + row = table.addRow (); + table.addCell (row, 0, "Recurrence"); + table.addCell (row, 1, refTask.getAttribute ("recur")); + } - row = table.addRow (); - table.addCell (row, 0, "Recur until"); - table.addCell (row, 1, refTask.getAttribute ("until")); + if (refTask.getAttribute ("until") != "") + { + row = table.addRow (); + table.addCell (row, 0, "Recur until"); + table.addCell (row, 1, refTask.getAttribute ("until")); + } - row = table.addRow (); - table.addCell (row, 0, "Mask"); - table.addCell (row, 1, refTask.getAttribute ("mask")); - } + if (refTask.getAttribute ("mask") != "") + { + row = table.addRow (); + table.addCell (row, 0, "Mask"); + table.addCell (row, 1, refTask.getAttribute ("mask")); + } - if (refTask.getAttribute ("parent") != "") - { - row = table.addRow (); - table.addCell (row, 0, "Parent task"); - table.addCell (row, 1, refTask.getAttribute ("parent")); + if (refTask.getAttribute ("parent") != "") + { + row = table.addRow (); + table.addCell (row, 0, "Parent task"); + table.addCell (row, 1, refTask.getAttribute ("parent")); + } row = table.addRow (); table.addCell (row, 0, "Mask Index"); From fb674a562676bafd9d65554cb6c7f30ca40a3f23 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 5 May 2009 01:59:07 -0400 Subject: [PATCH 06/16] Enhancement - parse sequence like 1,3,5-10 for IDs - Now parses sequences as well as IDs. - Sequences implemented for the info report. --- src/T.cpp | 13 +++++++++++ src/T.h | 1 + src/parse.cpp | 10 +-------- src/report.cpp | 59 ++++++++++++++++++++++++++------------------------ src/task.cpp | 6 +++++ 5 files changed, 52 insertions(+), 37 deletions(-) diff --git a/src/T.cpp b/src/T.cpp index 842613c56..b589ba628 100644 --- a/src/T.cpp +++ b/src/T.cpp @@ -37,6 +37,7 @@ T::T () mUUID = uuid (); mStatus = pending; mId = 0; + mSequence.clear (); mTags.clear (); mAttributes.clear (); mDescription = ""; @@ -59,6 +60,7 @@ T::T (const T& other) mStatus = other.mStatus; mUUID = other.mUUID; mId = other.mId; + mSequence = other.mSequence; mDescription = other.mDescription; mTags = other.mTags; mRemoveTags = other.mRemoveTags; @@ -74,6 +76,7 @@ T& T::operator= (const T& other) mStatus = other.mStatus; mUUID = other.mUUID; mId = other.mId; + mSequence = other.mSequence; mDescription = other.mDescription; mTags = other.mTags; mRemoveTags = other.mRemoveTags; @@ -286,6 +289,16 @@ void T::addAnnotation (const std::string& description) mAnnotations[time (NULL)] = sanitized; } +//////////////////////////////////////////////////////////////////////////////// +bool T::inSequence (int id) const +{ + foreach (seq, mSequence) + if (*seq == id) + return true; + + return false; +} + //////////////////////////////////////////////////////////////////////////////// // uuid status [tags] [attributes] [annotations] description // diff --git a/src/T.h b/src/T.h index 5e457aba1..23a219fb9 100644 --- a/src/T.h +++ b/src/T.h @@ -84,6 +84,7 @@ public: void getAnnotations (std::map &) const; void setAnnotations (const std::map &); void addAnnotation (const std::string&); + bool inSequence (int) const; const std::string compose () const; const std::string composeCSV (); diff --git a/src/parse.cpp b/src/parse.cpp index c57762ec9..c5bbfbbea 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -487,17 +487,9 @@ void parse ( std::vector sequence; // An id is the first argument found that contains all digits. - if (lowerCase (command) != "add" && -/* - task.getSequenceCount () == 0 && -*/ + if (lowerCase (command) != "add" && // "add" doesn't require an ID validSequence (arg, sequence)) { -/* - for (?) - task.addSequence (?) -*/ - std::cout << "# look like a sequence" << std::endl; foreach (id, sequence) task.addId (*id); } diff --git a/src/report.cpp b/src/report.cpp index cdf2a6a79..e389190e7 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -265,35 +265,38 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf) std::vector tasks; tdb.allPendingT (tasks); - Table table; - table.setTableWidth (width); - table.setDateFormat (conf.get ("dateformat", "m/d/Y")); - - table.addColumn ("Name"); - table.addColumn ("Value"); - - if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) - { - table.setColumnUnderline (0); - table.setColumnUnderline (1); - } - else - table.setTableDashedUnderline (); - - table.setColumnWidth (0, Table::minimum); - table.setColumnWidth (1, Table::flexible); - - table.setColumnJustification (0, Table::left); - table.setColumnJustification (1, Table::left); - // Find the task. + int count = 0; for (unsigned int i = 0; i < tasks.size (); ++i) { T refTask (tasks[i]); - if (refTask.getId () == task.getId ()) + if (refTask.getId () == task.getId () || + task.inSequence (refTask.getId ())) { - Date now; + ++count; + + Table table; + table.setTableWidth (width); + table.setDateFormat (conf.get ("dateformat", "m/d/Y")); + + table.addColumn ("Name"); + table.addColumn ("Value"); + + if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false)) + { + table.setColumnUnderline (0); + table.setColumnUnderline (1); + } + else + table.setTableDashedUnderline (); + + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::flexible); + + table.setColumnJustification (0, Table::left); + table.setColumnJustification (1, Table::left); + Date now; int row = table.addRow (); table.addCell (row, 0, "ID"); @@ -440,14 +443,14 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf) } table.addCell (row, 1, entry + " (" + age + ")"); + + out << optionalBlankLine (conf) + << table.render () + << std::endl; } } - if (table.rowCount ()) - out << optionalBlankLine (conf) - << table.render () - << std::endl; - else + if (! count) out << "No matches." << std::endl; return out.str (); diff --git a/src/task.cpp b/src/task.cpp index e64fc0256..190d86daa 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -227,6 +227,12 @@ static std::string longUsage (Config& conf) std::stringstream out; out << shortUsage (conf) << "ID is the numeric identifier displayed by the 'task list' command." << "\n" + << "You can specify multiple IDs for task commands, and multiple tasks" << "\n" + << "will be affected. To specify multiple IDs make sure you use one" << "\n" + << "of these forms:" << "\n" + << " task delete 1,2,3" << "\n" + << " task info 1-3" << "\n" + << " task pri:H 1,2-5,19" << "\n" << "\n" << "Tags are arbitrary words, any quantity:" << "\n" << " +tag The + means add the tag" << "\n" From e03e1ec7b053bd3389cefb7a98ba8f1067f76d81 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 5 May 2009 02:00:20 -0400 Subject: [PATCH 07/16] Bug Fix - missing defaultwidth in generated .taskrc - The defaultwidth variable is now written to the default .taskrc file, but is commented out to allow ncurses to do it's thing. --- src/Config.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Config.cpp b/src/Config.cpp index 5403accec..1b07606a8 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -149,6 +149,7 @@ void Config::createDefault (const std::string& home) fprintf (out, "next=2\n"); fprintf (out, "dateformat=m/d/Y\n"); fprintf (out, "#monthsperline=2\n"); + fprintf (out, "#defaultwidth=80\n"); fprintf (out, "curses=on\n"); fprintf (out, "color=on\n"); fprintf (out, "due=7\n"); From 07819a1885e4701df93a680e39cb9fc99da936e9 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 5 May 2009 02:03:25 -0400 Subject: [PATCH 08/16] Documentation Update - defaultwidth - Defaultwidth is now included in the default .taskrc file. --- ChangeLog | 2 ++ html/task.html | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 402093c70..6f57932d0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,8 @@ or completed.data file (thanks to T. Charles Yun). + Added details to the "info" report about recurring tasks (thanks to T. Charles Yun). + + Now writes a sample "defaultwidth" configuration variable to the default + .taskrc file (thanks to T. Charles Yun). ------ old releases ------------------------------ diff --git a/html/task.html b/html/task.html index 4c9e3f970..b8a482b59 100644 --- a/html/task.html +++ b/html/task.html @@ -118,6 +118,8 @@ or completed.data file (thanks to T. Charles Yun).

  • Added details to the "info" report about recurring tasks (thanks to T. Charles Yun). +
  • Now writes a sample "defaultwidth" configuration variable to the default + .taskrc file (thanks to T. Charles Yun).

    From b67b27f5cdd1bdaf673df0a532a2c1e1ac0a5a7e Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 5 May 2009 02:14:43 -0400 Subject: [PATCH 09/16] Enhancement - delete sequences - Implemented sequences for delete command - Renamed T::inSequence to T::sequenceContains, which makes the code read more naturally. --- src/T.cpp | 2 +- src/T.h | 2 +- src/command.cpp | 16 +++++++--------- src/report.cpp | 3 +-- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/T.cpp b/src/T.cpp index b589ba628..ac4943e86 100644 --- a/src/T.cpp +++ b/src/T.cpp @@ -290,7 +290,7 @@ void T::addAnnotation (const std::string& description) } //////////////////////////////////////////////////////////////////////////////// -bool T::inSequence (int id) const +bool T::sequenceContains (int id) const { foreach (seq, mSequence) if (*seq == id) diff --git a/src/T.h b/src/T.h index 23a219fb9..29537053f 100644 --- a/src/T.h +++ b/src/T.h @@ -84,7 +84,7 @@ public: void getAnnotations (std::map &) const; void setAnnotations (const std::map &); void addAnnotation (const std::string&); - bool inSequence (int) const; + bool sequenceContains (int) const; const std::string compose () const; const std::string composeCSV (); diff --git a/src/command.cpp b/src/command.cpp index 968763656..d04953734 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -439,13 +439,13 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf) { std::stringstream out; - if (!conf.get (std::string ("confirmation"), false) || confirm ("Permanently delete task?")) + std::vector all; + tdb.allPendingT (all); + foreach (t, all) { - std::vector all; - tdb.allPendingT (all); - foreach (t, all) + if (t->getId () == task.getId () || task.sequenceContains (t->getId ())) { - if (t->getId () == task.getId ()) + 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. @@ -494,13 +494,11 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf) << t->getDescription () << std::endl; } - - break; // No point continuing the loop. } + else + out << "Task not deleted." << std::endl; } } - else - out << "Task not deleted." << std::endl; return out.str (); } diff --git a/src/report.cpp b/src/report.cpp index e389190e7..540a603fb 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -271,8 +271,7 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf) { T refTask (tasks[i]); - if (refTask.getId () == task.getId () || - task.inSequence (refTask.getId ())) + if (refTask.getId () == task.getId () || task.sequenceContains (refTask.getId ())) { ++count; From 708995093bc1fcd9181c3b1bb2ae97a5f407e7e0 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Wed, 6 May 2009 23:58:21 -0400 Subject: [PATCH 10/16] Documentation Update - Removed unnecessary usage.html links from other pages. - Added new sequence.html page to describe how to use ID sequences. --- html/30second.html | 1 - html/advanced.html | 1 - html/color.html | 1 - html/config.html | 1 - html/custom.html | 1 - html/date.html | 1 - html/faq.html | 1 - html/filter.html | 1 - html/import.html | 1 - html/links.html | 1 - html/recur.html | 1 - html/sequence.html | 154 +++++++++++++++++++++++++++++++++++++++++++++ html/setup.html | 1 - html/shadow.html | 1 - html/shell.html | 1 - html/simple.html | 1 - html/task.html | 7 ++- html/versions.html | 1 - 18 files changed, 159 insertions(+), 18 deletions(-) create mode 100644 html/sequence.html diff --git a/html/30second.html b/html/30second.html index 4a3ac3c2d..38cef96ff 100644 --- a/html/30second.html +++ b/html/30second.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/advanced.html b/html/advanced.html index ec369652e..b4abe30b0 100644 --- a/html/advanced.html +++ b/html/advanced.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/color.html b/html/color.html index ecc060cb8..37e985f0d 100644 --- a/html/color.html +++ b/html/color.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/config.html b/html/config.html index 96c23422c..6ad919b81 100644 --- a/html/config.html +++ b/html/config.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/custom.html b/html/custom.html index 02ae73e5b..fa03a5249 100644 --- a/html/custom.html +++ b/html/custom.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/date.html b/html/date.html index 68ff201a0..d113993f7 100644 --- a/html/date.html +++ b/html/date.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/faq.html b/html/faq.html index 289c58d10..73c47fc2c 100644 --- a/html/faq.html +++ b/html/faq.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/filter.html b/html/filter.html index a561bdd58..373be27f5 100644 --- a/html/filter.html +++ b/html/filter.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/import.html b/html/import.html index de227c2a7..3a488af36 100644 --- a/html/import.html +++ b/html/import.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/links.html b/html/links.html index 15642d0b2..609f023a4 100644 --- a/html/links.html +++ b/html/links.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/recur.html b/html/recur.html index 5d7a7106a..a6963b842 100644 --- a/html/recur.html +++ b/html/recur.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/sequence.html b/html/sequence.html new file mode 100644 index 000000000..aa556ac3e --- /dev/null +++ b/html/sequence.html @@ -0,0 +1,154 @@ + + + + Task Usage + + + + + +

    + + + + + + +
    + + +
    +
    +
    +
    +

    ID Sequences

    +
    +

    + 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. +

    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + +
    + +
    + + + + + + + diff --git a/html/setup.html b/html/setup.html index bf8109565..2cbcad404 100644 --- a/html/setup.html +++ b/html/setup.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/shadow.html b/html/shadow.html index 63285866b..1488a0167 100644 --- a/html/shadow.html +++ b/html/shadow.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/shell.html b/html/shell.html index d868b5bec..661f3723c 100644 --- a/html/shell.html +++ b/html/shell.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/simple.html b/html/simple.html index 1b201cbb5..c256422fd 100644 --- a/html/simple.html +++ b/html/simple.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ diff --git a/html/task.html b/html/task.html index b8a482b59..ac3e2038e 100644 --- a/html/task.html +++ b/html/task.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ @@ -50,7 +49,6 @@
  • Interacting with the Shell
  • Configuring Task
  • Color -
  • Task Command Usage
  • Recurring Tasks
  • Date Handling
  • Old Versions @@ -59,6 +57,7 @@
  • Custom Reports
  • Data Import
  • Frequently Asked Questions +
  • ID Sequences

    @@ -120,6 +119,10 @@ Charles Yun).

  • Now writes a sample "defaultwidth" configuration variable to the default .taskrc file (thanks to T. Charles Yun). +
  • Task allows commands that require an ID to now be given a sequence, which + is a set of IDs. This allows commands like "task delete 1 2 5-10,12". +
  • Fixed bug in the ghistory report, which caused it to only show a new + month if a task was added during that month.

    diff --git a/html/versions.html b/html/versions.html index 0ec6168ab..957f38a44 100644 --- a/html/versions.html +++ b/html/versions.html @@ -20,7 +20,6 @@ Shell Configuration Colors - Usage Recurrence Date Handling FAQ From c78be053cb6d2a2c791218315eb2e57f62d83b00 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 7 May 2009 00:11:43 -0400 Subject: [PATCH 11/16] Documentation Update - release checklist - Create a release checklist document detailing release process. --- checklist.txt | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 checklist.txt diff --git a/checklist.txt b/checklist.txt new file mode 100644 index 000000000..5cec663c1 --- /dev/null +++ b/checklist.txt @@ -0,0 +1,26 @@ +Release Checklist +----------------- + +- Update "Upcoming Features" document on group +- Ensure all unit tests pass on OS X +- Ensure clean build on OS X +- Make a source package (1) +- Ensure clean build on latest Fedora Core from source package +- Git clone and rebuild, ensure all unit tests pass +- Ensure clean build on latest Ubuntu from source package +- Git clone and rebuild, ensure all unit tests pass +- Ensure clean build on Windows/Cygwin from source package +- Git clone and rebuild, ensure all unit tests pass +- Make a new source package (2) +- Add actual release date to ChangeLog +- Add actual release date to html/task.html +- Merge version branch to master +- Tag master +- Make a new source package (3) +- Send source package to package maintainer +- Make OS X .pkg package +- Wait for all packages +- Upload all packages to website +- Upload all docs to website +- Send announcement to group + From 2975b9244aecc51ec1951da4a652b5e66e6e8614 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 7 May 2009 00:13:31 -0400 Subject: [PATCH 12/16] Enhancement - ID and sequences - The first ID added as a sequence is accessible through the original T::getId and T::setId interface. - An ID added via T::getId is accessible as the first ID in a sequence. - Allows some commands to operate exclusively with sequences, and others with a single ID. --- src/T.cpp | 2 -- src/T.h | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/T.cpp b/src/T.cpp index ac4943e86..58a280f62 100644 --- a/src/T.cpp +++ b/src/T.cpp @@ -588,8 +588,6 @@ void T::parse (const std::string& line) openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1); std::vector pairs; split (pairs, attributes, ' '); - if (pairs.size () == 0) - throw std::string ("Could not find any attributes."); for (size_t i = 0; i < pairs.size (); ++i) { diff --git a/src/T.h b/src/T.h index 29537053f..fac345efa 100644 --- a/src/T.h +++ b/src/T.h @@ -49,9 +49,9 @@ public: void setUUID (const std::string& uuid) { mUUID = uuid; } int getId () const { return mId; } - void setId (int id) { mId = id; } + void setId (int id) { mId = id; mSequence.push_back (id); } std::vector getAllIds () const { return mSequence; } - void addId (int id) { mSequence.push_back (id); } + void addId (int id) { if (mId == 0) mId = id; mSequence.push_back (id); } status getStatus () const { return mStatus; } void setStatus (status s) { mStatus = s; } From 98391a0c242e26638adc7bfce8eaf127245e093f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 7 May 2009 00:24:30 -0400 Subject: [PATCH 13/16] Enhancement - Command Line Parsing - Fixed problem where a blank ID was considered valid. For example, the command "task 1 -2" should use -2 as the description, but instead considered this to be the sequence 1,0,2. - Replaced old validId calls with the new validSequence calls. - A sequence has been redefined to be the first set of consecutive arguments that look like sequences. Once broken by a non-sequence argument, all remaining args, even if they look like a sequence, are not considered part of the sequence. This allows commands like "task append 1,3-5 Write 10 emails", where 10 is not part of the sequence because of the intervening "Write". - Unit tests (parse.t.cpp) that exercise the parsing of sequences. Should probably be expanded to cover more. --- src/parse.cpp | 44 ++++++++------ src/tests/.gitignore | 1 + src/tests/Makefile | 6 +- src/tests/parse.t.cpp | 134 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 src/tests/parse.t.cpp diff --git a/src/parse.cpp b/src/parse.cpp index c5bbfbbea..e0f35039b 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -299,6 +299,9 @@ static bool validAttribute ( //////////////////////////////////////////////////////////////////////////////// static bool validId (const std::string& input) { + if (input.length () == 0) + return false; + for (size_t i = 0; i < input.length (); ++i) if (!::isdigit (input[i])) return false; @@ -329,7 +332,6 @@ static bool validSequence ( int id = ::atoi (range[0].c_str ()); ids.push_back (id); -// std::cout << "# seq: " << id << std::endl; break; case 2: @@ -344,10 +346,7 @@ static bool validSequence ( return false; for (int i = low; i <= high; ++i) -// { ids.push_back (i); -// std::cout << "# seq: " << i << std::endl; -// } } break; @@ -357,7 +356,7 @@ static bool validSequence ( } } - return ids.size () > 1 ? true : false; + return ids.size () ? true : false; } //////////////////////////////////////////////////////////////////////////////// @@ -448,8 +447,6 @@ bool validDuration (std::string& input) // ------- ---------------------------------- // command first non-id recognized argument // -// id ::= \d+ -// // substitution ::= "/" from "/" to "/g" // | "/" from "/" to "/" ; // @@ -459,8 +456,8 @@ bool validDuration (std::string& input) // attributes ::= word ":" value // | word ":" // -// sequence ::= id "," sequence -// | id "-" id ; +// sequence ::= \d+ "," sequence +// | \d+ "-" \d+ ; // // description (whatever isn't one of the above) void parse ( @@ -471,6 +468,9 @@ void parse ( { command = ""; + bool foundSequence = false; + bool foundSomethingAfterSequence = false; + std::string descCandidate = ""; for (size_t i = 0; i < args.size (); ++i) { @@ -488,22 +488,20 @@ void parse ( // An id is the first argument found that contains all digits. if (lowerCase (command) != "add" && // "add" doesn't require an ID - validSequence (arg, sequence)) + validSequence (arg, sequence) && + ! foundSomethingAfterSequence) { + foundSequence = true; 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)) { + if (foundSequence) + foundSomethingAfterSequence = true; + if (arg[0] == '+') task.addTag (arg.substr (1, std::string::npos)); else if (arg[0] == '-') @@ -514,6 +512,9 @@ void parse ( // value. else if ((colon = arg.find (":")) != std::string::npos) { + if (foundSequence) + foundSomethingAfterSequence = true; + std::string name = lowerCase (arg.substr (0, colon)); std::string value = arg.substr (colon + 1, std::string::npos); @@ -536,12 +537,18 @@ void parse ( // Substitution of description text. else if (validSubstitution (arg, from, to, global)) { + if (foundSequence) + foundSomethingAfterSequence = true; + task.setSubstitution (from, to, global); } // Command. else if (command == "") { + if (foundSequence) + foundSomethingAfterSequence = true; + std::string l = lowerCase (arg); if (isCommand (l) && validCommand (l)) command = l; @@ -556,6 +563,9 @@ void parse ( // Anything else is just considered description. else { + if (foundSequence) + foundSomethingAfterSequence = true; + if (descCandidate.length ()) descCandidate += " "; descCandidate += arg; diff --git a/src/tests/.gitignore b/src/tests/.gitignore index f25e411bc..6f7048780 100644 --- a/src/tests/.gitignore +++ b/src/tests/.gitignore @@ -5,3 +5,4 @@ date.t duration.t text.t autocomplete.t +parse.t diff --git a/src/tests/Makefile b/src/tests/Makefile index 0650a8a92..a71c8633e 100644 --- a/src/tests/Makefile +++ b/src/tests/Makefile @@ -1,4 +1,5 @@ -PROJECT = t.t tdb.t date.t duration.t t.benchmark.t text.t autocomplete.t +PROJECT = t.t tdb.t date.t duration.t t.benchmark.t text.t autocomplete.t \ + parse.t CFLAGS = -I. -I.. -Wall -pedantic -ggdb3 -fno-rtti LFLAGS = -L/usr/local/lib OBJECTS = ../TDB.o ../T.o ../parse.o ../text.o ../Date.o ../util.o ../Config.o @@ -38,3 +39,6 @@ text.t: text.t.o $(OBJECTS) test.o autocomplete.t: autocomplete.t.o $(OBJECTS) test.o g++ autocomplete.t.o $(OBJECTS) test.o $(LFLAGS) -o autocomplete.t +parse.t: parse.t.o $(OBJECTS) test.o + g++ parse.t.o $(OBJECTS) test.o $(LFLAGS) -o parse.t + diff --git a/src/tests/parse.t.cpp b/src/tests/parse.t.cpp new file mode 100644 index 000000000..3065cd952 --- /dev/null +++ b/src/tests/parse.t.cpp @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2009, 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 +// +//////////////////////////////////////////////////////////////////////////////// +#include +#include "task.h" +#include "test.h" + +//////////////////////////////////////////////////////////////////////////////// +int main (int argc, char** argv) +{ + UnitTest t (18); + + std::vector args; + std::string command; + + Config conf; + conf.set ("dateformat", "m/d/Y"); + + { + T task; + split (args, "add foo", ' '); + parse (args, command, task, conf); + t.is (command, "add", "(1) command found"); + t.is (task.getId (), 0, "(1) zero id on add"); + t.is (task.getDescription (), "foo", "(1) correct description"); + } + + { + T task; + split (args, "delete 1,3-5,7", ' '); + parse (args, command, task, conf); + std::vector sequence = task.getAllIds (); + t.is (sequence.size (), (size_t)5, "(2) sequence length"); + if (sequence.size () == 5) + { + t.is (sequence[0], 1, "(2) sequence[0] == 1"); + t.is (sequence[1], 3, "(2) sequence[1] == 3"); + t.is (sequence[2], 4, "(2) sequence[2] == 4"); + t.is (sequence[3], 5, "(2) sequence[3] == 5"); + t.is (sequence[4], 7, "(2) sequence[4] == 7"); + } + else + { + t.fail ("(2) sequence[0] == 1"); + t.fail ("(2) sequence[1] == 3"); + t.fail ("(2) sequence[2] == 4"); + t.fail ("(2) sequence[3] == 5"); + t.fail ("(2) sequence[4] == 7"); + } + } + + { + T task; + split (args, "delete 1,2 3,4", ' '); + parse (args, command, task, conf); + std::vector sequence = task.getAllIds (); + t.is (sequence.size (), (size_t)4, "(3) sequence length"); + if (sequence.size () == 4) + { + t.is (sequence[0], 1, "(3) sequence[0] == 1"); + t.is (sequence[1], 2, "(3) sequence[1] == 2"); + t.is (sequence[2], 3, "(3) sequence[2] == 3"); + t.is (sequence[3], 4, "(3) sequence[3] == 4"); + } + else + { + t.fail ("(3) sequence[0] == 1"); + t.fail ("(3) sequence[1] == 2"); + t.fail ("(3) sequence[2] == 3"); + t.fail ("(3) sequence[3] == 4"); + } + } + + { + T task; + split (args, "1 There are 7 days in a week", ' '); + parse (args, command, task, conf); + std::vector sequence = task.getAllIds (); + t.is (sequence.size (), (size_t)1, "(4) sequence length"); + if (sequence.size () == 1) + { + t.is (sequence[0], 1, "(4) sequence[0] == 1"); + } + else + { + t.fail ("(4) sequence[0] == 1"); + } + } + + { + T task; + args.clear (); + args.push_back ("1"); + args.push_back ("4-123 is back-ordered"); + parse (args, command, task, conf); + std::vector sequence = task.getAllIds (); + t.is (sequence.size (), (size_t)1, "(5) sequence length"); + if (sequence.size () == 1) + { + t.is (sequence[0], 1, "(5) sequence[0] == 1"); + } + else + { + t.fail ("(5) sequence[0] == 1"); + } + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + From fea19e036ac322b9782815974888064bcb96bdd1 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 7 May 2009 00:26:49 -0400 Subject: [PATCH 14/16] Unit Tests - confirmation, delete, subst, undo - Modified unit tests to accommodate changes in the verbose output of task. - Added tests to verify /delete-me// substitutions. - Fixed typos in test descriptions. --- src/tests/confirmation.t | 22 +++++++++++----------- src/tests/delete.t | 2 +- src/tests/substitute.t | 12 +++++++++++- src/tests/undo.t | 4 ++-- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/tests/confirmation.t b/src/tests/confirmation.t index 99bc8e378..955cace13 100755 --- a/src/tests/confirmation.t +++ b/src/tests/confirmation.t @@ -51,49 +51,49 @@ qx{../task rc:confirm.rc add foo} for 1 .. 10; # Test the various forms of "yes". my $output = qx{echo "yes" | ../task rc:confirm.rc del 1}; -like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - yes works'); +like ($output, qr/Permanently delete task 1 'foo'\? \(y\/n\)/, 'confirmation - yes works'); unlike ($output, qr/Task not deleted\./, 'confirmation - yes works'); $output = qx{echo "ye" | ../task rc:confirm.rc del 2}; -like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - ye works'); +like ($output, qr/Permanently delete task 2 'foo'\? \(y\/n\)/, 'confirmation - ye works'); unlike ($output, qr/Task not deleted\./, 'confirmation - ye works'); $output = qx{echo "y" | ../task rc:confirm.rc del 3}; -like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - y works'); +like ($output, qr/Permanently delete task 3 'foo'\? \(y\/n\)/, 'confirmation - y works'); unlike ($output, qr/Task not deleted\./, 'confirmation - y works'); $output = qx{echo "YES" | ../task rc:confirm.rc del 4}; -like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - YES works'); +like ($output, qr/Permanently delete task 4 'foo'\? \(y\/n\)/, 'confirmation - YES works'); unlike ($output, qr/Task not deleted\./, 'confirmation - YES works'); $output = qx{echo "YE" | ../task rc:confirm.rc del 5}; -like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - YE works'); +like ($output, qr/Permanently delete task 5 'foo'\? \(y\/n\)/, 'confirmation - YE works'); unlike ($output, qr/Task not deleted\./, 'confirmation - YE works'); $output = qx{echo "Y" | ../task rc:confirm.rc del 6}; -like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - Y works'); +like ($output, qr/Permanently delete task 6 'foo'\? \(y\/n\)/, 'confirmation - Y works'); unlike ($output, qr/Task not deleted\./, 'confirmation - Y works'); # Test the various forms of "no". $output = qx{echo "no" | ../task rc:confirm.rc del 7}; -like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - no works'); +like ($output, qr/Permanently delete task 7 'foo'\? \(y\/n\)/, 'confirmation - no works'); like ($output, qr/Task not deleted\./, 'confirmation - no works'); $output = qx{echo "n" | ../task rc:confirm.rc del 7}; -like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - n works'); +like ($output, qr/Permanently delete task 7 'foo'\? \(y\/n\)/, 'confirmation - n works'); like ($output, qr/Task not deleted\./, 'confirmation - n works'); $output = qx{echo "NO" | ../task rc:confirm.rc del 7}; -like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - NO works'); +like ($output, qr/Permanently delete task 7 'foo'\? \(y\/n\)/, 'confirmation - NO works'); like ($output, qr/Task not deleted\./, 'confirmation - NO works'); $output = qx{echo "N" | ../task rc:confirm.rc del 7}; -like ($output, qr/Permanently delete task\? \(y\/n\)/, 'confirmation - N works'); +like ($output, qr/Permanently delete task 7 'foo'\? \(y\/n\)/, 'confirmation - N works'); like ($output, qr/Task not deleted\./, 'confirmation - N works'); # Test newlines. $output = qx{cat response.txt | ../task rc:confirm.rc del 7}; -like ($output, qr/(Permanently delete task\? \(y\/n\)) \1 \1/, 'confirmation - \n re-prompt works'); +like ($output, qr/(Permanently delete task 7 'foo'\? \(y\/n\)) \1 \1/, 'confirmation - \n re-prompt works'); # Cleanup. unlink 'pending.data'; diff --git a/src/tests/delete.t b/src/tests/delete.t index d3a34a84c..92a1f0414 100755 --- a/src/tests/delete.t +++ b/src/tests/delete.t @@ -57,7 +57,7 @@ like ($output, qr/^No matches/, 'No matches'); ok (-r 'completed.data', 'completed.data created'); $output = qx{../task rc:undelete.rc undelete 1}; -like ($output, qr/reliably undeleted/, 'can only be reliable undeleted...'); +like ($output, qr/Task 1 not found/, 'Task 1 not found'); $output = qx{../task rc:undelete.rc info 1}; like ($output, qr/No matches./, 'no matches'); diff --git a/src/tests/substitute.t b/src/tests/substitute.t index 9365df970..16bb13d4d 100755 --- a/src/tests/substitute.t +++ b/src/tests/substitute.t @@ -28,7 +28,7 @@ use strict; use warnings; -use Test::More tests => 7; +use Test::More tests => 9; # Create the rc file. if (open my $fh, '>', 'subst.rc') @@ -58,6 +58,16 @@ qx{../task rc:subst.rc 1 /bar/BAR/g}; $output = qx{../task rc:subst.rc info 1}; like ($output, qr/BAR BAR BAR/, 'global substitution in annotation'); +qx{../task rc:subst.rc 1 /FOO/aaa/}; +qx{../task rc:subst.rc 1 /FOO/bbb/}; +qx{../task rc:subst.rc 1 /FOO/ccc/}; +$output = qx{../task rc:subst.rc info 1}; +like ($output, qr/aaa bbb ccc/, 'individual successive substitution in description'); + +qx{../task rc:subst.rc 1 /bbb//}; +$output = qx{../task rc:subst.rc info 1}; +like ($output, qr/aaa ccc/, 'word deletion in description'); + # Cleanup. unlink 'pending.data'; ok (!-r 'pending.data', 'Removed pending.data'); diff --git a/src/tests/undo.t b/src/tests/undo.t index 24803cd88..32131f21a 100755 --- a/src/tests/undo.t +++ b/src/tests/undo.t @@ -56,8 +56,8 @@ $output = qx{../task rc:undo.rc do 1; ../task rc:undo.rc list}; like ($output, qr/^No matches/, 'No matches'); $output = qx{../task rc:undo.rc undo 1; ../task rc:undo.rc info 1}; -like ($output, qr/Task 1 not found/, 'task not found'); -like ($output, qr/reliably undone/, 'can only be reliable undone...'); +like ($output, qr/Task 1 not found/, 'Task 1 not found'); +like ($output, qr/No matches/, 'No matches'); # Cleanup. ok (-r 'pending.data', 'Need to remove pending.data'); From 72ff15ea7a51fad34efaf65e3ddc35a1e5145525 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 7 May 2009 00:30:20 -0400 Subject: [PATCH 15/16] Enhancements - Fixed word wrapping problem on "help" page. - Writes errors to std::cout because std::cerr confuses tests. Presumably no users will care. I don't. - Added listDiff template function. --- src/task.cpp | 8 ++++---- src/task.h | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/task.cpp b/src/task.cpp index 190d86daa..bf725f8a5 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -226,9 +226,9 @@ static std::string longUsage (Config& conf) { std::stringstream out; out << shortUsage (conf) - << "ID is the numeric identifier displayed by the 'task list' command." << "\n" - << "You can specify multiple IDs for task commands, and multiple tasks" << "\n" - << "will be affected. To specify multiple IDs make sure you use one" << "\n" + << "ID is the numeric identifier displayed by the 'task list' command. " + << "You can specify multiple IDs for task commands, and multiple tasks " + << "will be affected. To specify multiple IDs make sure you use one " << "of these forms:" << "\n" << " task delete 1,2,3" << "\n" << " task info 1-3" << "\n" @@ -341,7 +341,7 @@ int main (int argc, char** argv) catch (std::string& error) { - std::cerr << error << std::endl; + std::cout << error << std::endl; return -1; } diff --git a/src/task.h b/src/task.h index ab4faa514..2ecc4f755 100644 --- a/src/task.h +++ b/src/task.h @@ -90,8 +90,14 @@ std::string handleUndo (TDB&, T&, Config&); std::string handleColor (Config&); std::string handleAnnotate (TDB&, T&, Config&); T findT (int, const std::vector &); +int deltaAppend (T&, T&); +int deltaDescription (T&, T&); +int deltaTags (T&, T&); +int deltaAttributes (T&, T&); +int deltaSubstitutions (T&, T&); // report.cpp +void filterSequence (std::vector&, T&); void filter (std::vector&, T&); std::string handleInfo (TDB&, T&, Config&); std::string handleCompleted (TDB&, T&, Config&); @@ -151,4 +157,45 @@ void autoColorize (T&, Text::color&, Text::color&, Config&); // import.cpp std::string handleImport (TDB&, T&, Config&); +// list template +/////////////////////////////////////////////////////////////////////////////// +template void listDiff ( + const T& left, const T& right, T& leftOnly, T& rightOnly) +{ + leftOnly.clear (); + rightOnly.clear (); + + for (unsigned int l = 0; l < left.size (); ++l) + { + bool found = false; + for (unsigned int r = 0; r < right.size (); ++r) + { + if (left[l] == right[r]) + { + found = true; + break; + } + } + + if (!found) + leftOnly.push_back (left[l]); + } + + for (unsigned int r = 0; r < right.size (); ++r) + { + bool found = false; + for (unsigned int l = 0; l < left.size (); ++l) + { + if (left[l] == right[r]) + { + found = true; + break; + } + } + + if (!found) + rightOnly.push_back (right[r]); + } +} + //////////////////////////////////////////////////////////////////////////////// From aec64afc5c2741462ef1b64831ce7db8220dd3d8 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 7 May 2009 00:33:17 -0400 Subject: [PATCH 16/16] Enhancements, Bug Fix - Removed obsolete usage.html page. - Added filterSequence helper function to subset tasks to the affected sequence of tasks. - Added series of helper functions to facilitate applying modifications to tasks, allowing more commands the ability to modify tasks in all ways. - Added calls to helper functions for append and modify commands. - Fixed bug that would mistakenly validate the configuration variable "bcd" if there was a valid "abcd", "bcde" or "abcde" configuration variable. - Made the messages generated by task after a command is complete more verbose and consistent across all commands. - Fixed bug that caused the ghistory command to skip months where no tasks were added, but were deleted or done. This is the same bug that was fixed in 1.6.0 for the history report. So much for cut and paste. - Allowed all commands that take an ID to now operate on a sequence. --- ChangeLog | 4 + html/usage.html | 153 ---------- src/command.cpp | 775 +++++++++++++++++++++++------------------------- src/report.cpp | 49 +++ 4 files changed, 427 insertions(+), 554 deletions(-) delete mode 100644 html/usage.html diff --git a/ChangeLog b/ChangeLog index 6f57932d0..4656eec5e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,10 @@ Charles Yun). + Now writes a sample "defaultwidth" configuration variable to the default .taskrc file (thanks to T. Charles Yun). + + Task allows commands that require an ID to now be given a sequence, which + is a set of IDs. This allows commands like "task delete 1 2 5-10,12". + + Fixed bug in the ghistory report, which caused it to only show a new + month if a task was added during that month. ------ old releases ------------------------------ diff --git a/html/usage.html b/html/usage.html deleted file mode 100644 index 419e04b0c..000000000 --- a/html/usage.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - Task Usage - - - - - -

    - - - - - - -
    - - -
    -
    -
    -
    -

    Command Usage

    -
    -
    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;