From 3979c3283e56b1d8b6cc0462efa16956c8e36619 Mon Sep 17 00:00:00 2001
From: Paul Beckingham
+ Allows an annotation to be attached to an existing task. Each + annotation has a time stamp, and when displayed, the annotations + are shown under the task description. For example: +
+ +% task add Go to the supermarket
+% task annotate 1 need milk
+% task ls
+
+ID Project Pri Due Active Age Description
+ 1 Go to the supermarket
+ 3/23/2009 need milk
+ + The date of the annotation uses the "dateformat" configuration + variable. +
+ % task delete <id>There are two ways of getting rid of tasks - mark them as done, or diff --git a/html/custom.html b/html/custom.html index 1ee499a35..67f51b9e0 100644 --- a/html/custom.html +++ b/html/custom.html @@ -100,6 +100,7 @@ report.mine.sort=priority-,project+
diff --git a/html/usage.html b/html/usage.html index f8ab9e814..51979c632 100644 --- a/html/usage.html +++ b/html/usage.html @@ -37,6 +37,7 @@
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/
diff --git a/src/Config.cpp b/src/Config.cpp
index de73f42dc..eef4e5899 100644
--- a/src/Config.cpp
+++ b/src/Config.cpp
@@ -171,6 +171,7 @@ void Config::createDefault (const std::string& home)
// Custom reports.
fprintf (out, "# Fields: id,uuid,project,priority,entry,start,due,recur,age,active,tags,description\n");
+ fprintf (out, "# description_only\n");
fprintf (out, "# Description: This report is ...\n");
fprintf (out, "# Sort: due+,priority-,project+\n");
fprintf (out, "# Filter: pro:x pri:H +bug\n");
diff --git a/src/T.cpp b/src/T.cpp
index f490d8681..b7f038c35 100644
--- a/src/T.cpp
+++ b/src/T.cpp
@@ -25,6 +25,7 @@
//
////////////////////////////////////////////////////////////////////////////////
#include
+#include
#include
#include "task.h"
#include "T.h"
@@ -39,6 +40,7 @@ T::T ()
mTags.clear ();
mAttributes.clear ();
mDescription = "";
+ mAnnotations.clear ();
}
////////////////////////////////////////////////////////////////////////////////
@@ -58,6 +60,7 @@ T::T (const T& other)
mTags = other.mTags;
mRemoveTags = other.mRemoveTags;
mAttributes = other.mAttributes;
+ mAnnotations = other.mAnnotations;
}
////////////////////////////////////////////////////////////////////////////////
@@ -72,6 +75,7 @@ T& T::operator= (const T& other)
mTags = other.mTags;
mRemoveTags = other.mRemoveTags;
mAttributes = other.mAttributes;
+ mAnnotations = other.mAnnotations;
}
return *this;
@@ -244,7 +248,23 @@ void T::setSubstitution (const std::string& from, const std::string& to)
}
////////////////////////////////////////////////////////////////////////////////
-// uuid status [tags] [attributes] description
+void T::getAnnotations (std::map & all)
+{
+ all = mAnnotations;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void T::addAnnotation (const std::string& description)
+{
+ std::string sanitized = description;
+ std::replace (sanitized.begin (), sanitized.end (), '\'', '"');
+ std::replace (sanitized.begin (), sanitized.end (), '[', '(');
+ std::replace (sanitized.begin (), sanitized.end (), ']', ')');
+ mAnnotations[time (NULL)] = sanitized;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// uuid status [tags] [attributes] [annotations] description
//
// uuid \x{8}-\x{4}-\x{4}-\x{4}-\x{12}
// status - + X r
@@ -282,10 +302,26 @@ const std::string T::compose () const
++count;
}
- line += "] ";
+ line += "] [";
+
+ // Annotations
+ std::stringstream annotation;
+ bool first = true;
+ foreach (note, mAnnotations)
+ {
+ if (first)
+ first = false;
+ else
+ annotation << " ";
+
+ annotation << note->first << ":'" << note->second << "'";
+ }
+ line += annotation.str () + "] ";
// Description
line += mDescription;
+
+ // EOL
line += "\n";
if (line.length () > T_LINE_MAX)
@@ -421,10 +457,12 @@ void T::parse (const std::string& line)
}
else
throw std::string ("Line too short");
+
+ mAnnotations.clear ();
}
break;
- // File format version 2, from 2008.1.1
+ // File format version 2, from 2008.1.1 - 2009.3.23
case 2:
{
if (line.length () > 46) // ^.{36} . \[\] \[\] \n
@@ -446,24 +484,122 @@ void T::parse (const std::string& line)
if (openAttrBracket != std::string::npos &&
closeAttrBracket != std::string::npos)
{
- std::string tags = line.substr (
- openTagBracket + 1, closeTagBracket - openTagBracket - 1);
- std::vector rawTags;
- split (mTags, tags, ' ');
+ std::string tags = line.substr (
+ openTagBracket + 1, closeTagBracket - openTagBracket - 1);
+ std::vector rawTags;
+ split (mTags, tags, ' ');
- std::string attributes = line.substr (
- openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
- std::vector pairs;
- split (pairs, attributes, ' ');
- for (size_t i = 0; i < pairs.size (); ++i)
- {
- std::vector pair;
- split (pair, pairs[i], ':');
- if (pair.size () == 2)
- mAttributes[pair[0]] = pair[1];
- }
+ std::string attributes = line.substr (
+ openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
+ std::vector pairs;
+ split (pairs, attributes, ' ');
+ for (size_t i = 0; i < pairs.size (); ++i)
+ {
+ std::vector pair;
+ split (pair, pairs[i], ':');
+ if (pair.size () == 2)
+ mAttributes[pair[0]] = pair[1];
+ }
- mDescription = line.substr (closeAttrBracket + 2, std::string::npos);
+ mDescription = line.substr (closeAttrBracket + 2, std::string::npos);
+ }
+ else
+ throw std::string ("Missing attribute brackets");
+ }
+ else
+ throw std::string ("Missing tag brackets");
+ }
+ else
+ throw std::string ("Line too short");
+
+ mAnnotations.clear ();
+ }
+ break;
+
+ // File format version 3, from 2009.3.23
+ case 3:
+ {
+ if (line.length () > 49) // ^.{36} . \[\] \[\] \[\] \n
+ {
+ mUUID = line.substr (0, 36);
+
+ mStatus = line[37] == '+' ? completed
+ : line[37] == 'X' ? deleted
+ : line[37] == 'r' ? recurring
+ : pending;
+
+ size_t openTagBracket = line.find ("[");
+ size_t closeTagBracket = line.find ("]", openTagBracket);
+ if (openTagBracket != std::string::npos &&
+ closeTagBracket != std::string::npos)
+ {
+ size_t openAttrBracket = line.find ("[", closeTagBracket);
+ size_t closeAttrBracket = line.find ("]", openAttrBracket);
+ if (openAttrBracket != std::string::npos &&
+ closeAttrBracket != std::string::npos)
+ {
+ size_t openAnnoBracket = line.find ("[", closeAttrBracket);
+ size_t closeAnnoBracket = line.find ("]", openAnnoBracket);
+ if (openAnnoBracket != std::string::npos &&
+ closeAnnoBracket != std::string::npos)
+ {
+ std::string tags = line.substr (
+ openTagBracket + 1, closeTagBracket - openTagBracket - 1);
+ std::vector rawTags;
+ split (mTags, tags, ' ');
+
+ std::string attributes = line.substr (
+ openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
+ std::vector pairs;
+ split (pairs, attributes, ' ');
+ for (size_t i = 0; i < pairs.size (); ++i)
+ {
+ std::vector pair;
+ split (pair, pairs[i], ':');
+ if (pair.size () == 2)
+ mAttributes[pair[0]] = pair[1];
+ }
+
+ // Extract and split the annotations, which are of the form:
+ // 1234:'...' 5678:'...'
+ std::string annotations = line.substr (
+ openAnnoBracket + 1, closeAnnoBracket - openAnnoBracket - 1);
+ pairs.clear ();
+
+ std::string::size_type start = 0;
+ std::string::size_type end = 0;
+ do
+ {
+ end = annotations.find ('\'', start);
+ if (end != std::string::npos)
+ {
+ end = annotations.find ('\'', end + 1);
+
+ if (start != std::string::npos &&
+ end != std::string::npos)
+ {
+ pairs.push_back (annotations.substr (start, end - start + 1));
+ start = end + 2;
+ }
+ }
+ }
+ while (start != std::string::npos &&
+ end != std::string::npos);
+
+ for (size_t i = 0; i < pairs.size (); ++i)
+ {
+ std::string pair = pairs[i];
+ std::string::size_type colon = pair.find (":");
+ if (colon != std::string::npos)
+ {
+ std::string name = pair.substr (0, colon);
+ std::string value = pair.substr (colon + 2, pair.length () - colon - 3);
+ mAnnotations[::atoi (name.c_str ())] = value;
+ }
+ }
+
+ mDescription = line.substr (closeAnnoBracket + 2, std::string::npos);
+ }
}
else
throw std::string ("Missing attribute brackets");
@@ -512,16 +648,31 @@ int T::determineVersion (const std::string& line)
line[23] == '-' &&
line[36] == ' ' &&
(line[37] == '-' || line[37] == '+' || line[37] == 'X' || line[37] == 'r'))
- return 2;
+ {
+ // Version 3 looks like:
+ //
+ // uuid status [tags] [attributes] [annotations] description\n
+ //
+ // Scan for the number of [] pairs.
+ std::string::size_type tagAtts = line.find ("] [", 0);
+ std::string::size_type attsAnno = line.find ("] [", tagAtts + 1);
+ std::string::size_type annoDesc = line.find ("] ", attsAnno + 1);
+ if (tagAtts != std::string::npos &&
+ attsAnno != std::string::npos &&
+ annoDesc != std::string::npos)
+ return 3;
+ else
+ return 2;
+ }
- // Version 3?
+ // Version 4?
//
- // Fortunately, with the hindsight that will come with version 3, the
- // identifying characteristics of 1 and 2 may be modified such that if 3 has
- // a UUID followed by a status, then there is still a way to differentiate
- // between 2 and 3.
+ // Fortunately, with the hindsight that will come with version 4, the
+ // identifying characteristics of 1, 2 and 3 may be modified such that if 4
+ // has a UUID followed by a status, then there is still a way to differentiate
+ // between 2, 3 and 4.
//
- // The danger is that a version 2 binary reads and misinterprets a version 3
+ // The danger is that a version 3 binary reads and misinterprets a version 4
// file. This is why it is a good idea to rely on an explicit version
// declaration rather than chance positioning.
diff --git a/src/T.h b/src/T.h
index fc126ae34..7f3898e2b 100644
--- a/src/T.h
+++ b/src/T.h
@@ -56,6 +56,7 @@ public:
const std::string getDescription () const { return mDescription; }
void setDescription (const std::string& description) { mDescription = description; }
+ int getAnnotationCount () const { return mAnnotations.size (); }
void getSubstitution (std::string&, std::string&) const;
void setSubstitution (const std::string&, const std::string&);
@@ -77,6 +78,9 @@ public:
void removeAttribute (const std::string&);
void removeAttributes ();
+ void getAnnotations (std::map &);
+ void addAnnotation (const std::string&);
+
const std::string compose () const;
const std::string composeCSV ();
void parse (const std::string&);
@@ -93,9 +97,9 @@ private:
std::vector mTags;
std::vector mRemoveTags;
std::map mAttributes;
-
std::string mFrom;
std::string mTo;
+ std::map mAnnotations;
};
#endif
diff --git a/src/Table.cpp b/src/Table.cpp
index 6f20c1b50..0ea6a57cb 100644
--- a/src/Table.cpp
+++ b/src/Table.cpp
@@ -227,7 +227,7 @@ void Table::setRowBg (const int row, const Text::color c)
////////////////////////////////////////////////////////////////////////////////
void Table::addCell (const int row, const int col, const std::string& data)
{
- int length = 0;
+ unsigned int length = 0;
if (mSuppressWS)
{
@@ -238,7 +238,19 @@ void Table::addCell (const int row, const int col, const std::string& data)
data2 = data;
clean (data2);
- length = data2.length ();
+ // For multi-line cells, find the longest line.
+ if (data2.find ("\n") != std::string::npos)
+ {
+ length = 0;
+ std::vector lines;
+ split (lines, data2, "\n");
+ for (unsigned int i = 0; i < lines.size (); ++i)
+ if (lines[i].length () > length)
+ length = lines[i].length ();
+ }
+ else
+ length = data2.length ();
+
mData.add (row, col, data2);
}
else
@@ -248,11 +260,22 @@ void Table::addCell (const int row, const int col, const std::string& data)
else
mData.add (row, col, data);
- length = data.length ();
+ // For multi-line cells, find the longest line.
+ if (data.find ("\n") != std::string::npos)
+ {
+ length = 0;
+ std::vector lines;
+ split (lines, data, "\n");
+ for (unsigned int i = 0; i < lines.size (); ++i)
+ if (lines[i].length () > length)
+ length = lines[i].length ();
+ }
+ else
+ length = data.length ();
}
// Automatically maintain max width.
- mMaxDataWidth[col] = max (mMaxDataWidth[col], length);
+ mMaxDataWidth[col] = max (mMaxDataWidth[col], (int)length);
}
////////////////////////////////////////////////////////////////////////////////
@@ -508,9 +531,7 @@ void Table::calculateColumnWidths ()
}
else
{
-// std::cout << "# insufficient room, considering only flexible columns." << std::endl;
-
- // The fallback position is to assume no width was specificed, and just
+ // The fallback position is to assume no width was specified, and just
// calculate widths accordingly.
mTableWidth = 0;
calculateColumnWidths ();
diff --git a/src/command.cpp b/src/command.cpp
index b4f039803..e356ae6d8 100644
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -845,6 +845,14 @@ std::string handleAppend (TDB& tdb, T& task, Config& conf)
{
original.setId (task.getId ());
tdb.modifyT (original);
+
+ if (conf.get ("echo.command", true))
+ out << "Appended '"
+ << task.getDescription ()
+ << "' to task "
+ << task.getId ()
+ << std::endl;
+
}
return out.str ();
@@ -944,3 +952,34 @@ std::string handleColor (Config& conf)
}
////////////////////////////////////////////////////////////////////////////////
+std::string handleAnnotate (TDB& tdb, T& task, Config& conf)
+{
+ std::stringstream out;
+ std::vector all;
+ tdb.pendingT (all);
+
+ std::vector ::iterator it;
+ for (it = all.begin (); it != all.end (); ++it)
+ {
+ if (it->getId () == task.getId ())
+ {
+ it->addAnnotation (task.getDescription ());
+ tdb.modifyT (*it);
+
+ if (conf.get ("echo.command", true))
+ out << "Annotated "
+ << task.getId ()
+ << " with '"
+ << task.getDescription ()
+ << "'"
+ << std::endl;
+
+ return out.str ();
+ }
+ }
+
+ throw std::string ("Task not found.");
+ return out.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/src/parse.cpp b/src/parse.cpp
index bcda9e778..56b32a98a 100644
--- a/src/parse.cpp
+++ b/src/parse.cpp
@@ -121,6 +121,7 @@ static const char* commands[] =
"active",
"add",
"append",
+ "annotate",
"calendar",
"colors",
"completed",
diff --git a/src/report.cpp b/src/report.cpp
index 44752327a..76c5c75f4 100644
--- a/src/report.cpp
+++ b/src/report.cpp
@@ -207,7 +207,18 @@ std::string handleCompleted (TDB& tdb, T& task, Config& conf)
table.addCell (row, 0, end.toString (conf.get ("dateformat", "m/d/Y")));
table.addCell (row, 1, refTask.getAttribute ("project"));
- table.addCell (row, 2, refTask.getDescription ());
+
+ std::string description = refTask.getDescription ();
+ std::string when;
+ std::map annotations;
+ refTask.getAnnotations (annotations);
+ foreach (anno, annotations)
+ {
+ Date dt (anno->first);
+ when = dt.toString (conf.get ("dateformat", "m/d/Y"));
+ description += "\n" + when + " " + anno->second;
+ }
+ table.addCell (row, 2, description);
if (conf.get ("color", true) || conf.get (std::string ("_forcecolor"), false))
{
@@ -270,7 +281,7 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
table.setTableDashedUnderline ();
table.setColumnWidth (0, Table::minimum);
- table.setColumnWidth (1, Table::minimum);
+ table.setColumnWidth (1, Table::flexible);
table.setColumnJustification (0, Table::left);
table.setColumnJustification (1, Table::left);
@@ -296,9 +307,20 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
: refTask.getStatus () == T::recurring ? "Recurring"
: ""));
+ std::string description = refTask.getDescription ();
+ std::string when;
+ std::map annotations;
+ refTask.getAnnotations (annotations);
+ foreach (anno, annotations)
+ {
+ Date dt (anno->first);
+ when = dt.toString (conf.get ("dateformat", "m/d/Y"));
+ description += "\n" + when + " " + anno->second;
+ }
+
row = table.addRow ();
table.addCell (row, 0, "Description");
- table.addCell (row, 1, refTask.getDescription ());
+ table.addCell (row, 1, description);
if (refTask.getAttribute ("project") != "")
{
@@ -1683,6 +1705,7 @@ std::string handleReportStats (TDB& tdb, T& task, Config& conf)
int pendingT = 0;
int completedT = 0;
int taggedT = 0;
+ int annotationsT = 0;
int recurringT = 0;
float daysPending = 0.0;
int descLength = 0;
@@ -1713,6 +1736,8 @@ std::string handleReportStats (TDB& tdb, T& task, Config& conf)
descLength += it->getDescription ().length ();
+ annotationsT += it->getAnnotationCount ();
+
std::vector tags;
it->getTags (tags);
if (tags.size ()) ++taggedT;
@@ -1760,6 +1785,7 @@ std::string handleReportStats (TDB& tdb, T& task, Config& conf)
out << "Tasks tagged " << std::setprecision (3) << (100.0 * taggedT / totalT) << "%" << std::endl;
}
+ out << "Annotations " << annotationsT << std::endl;
out << "Unique tags " << allTags.size () << std::endl;
out << "Projects " << allProjects.size () << std::endl;
@@ -2164,7 +2190,7 @@ std::string handleCustomReport (
}
}
- else if (*col == "description")
+ else if (*col == "description_only")
{
table.addColumn ("Description");
table.setColumnWidth (columnCount, Table::flexible);
@@ -2174,6 +2200,30 @@ std::string handleCustomReport (
table.addCell (row, columnCount, tasks[row].getDescription ());
}
+ else if (*col == "description")
+ {
+ table.addColumn ("Description");
+ table.setColumnWidth (columnCount, Table::flexible);
+ table.setColumnJustification (columnCount, Table::left);
+
+ std::string description;
+ std::string when;
+ for (unsigned int row = 0; row < tasks.size(); ++row)
+ {
+ description = tasks[row].getDescription ();
+ std::map annotations;
+ tasks[row].getAnnotations (annotations);
+ foreach (anno, annotations)
+ {
+ Date dt (anno->first);
+ when = dt.toString (conf.get ("dateformat", "m/d/Y"));
+ description += "\n" + when + " " + anno->second;
+ }
+
+ table.addCell (row, columnCount, description);
+ }
+ }
+
else if (*col == "recur")
{
table.addColumn ("Recur");
@@ -2302,17 +2352,18 @@ void validReportColumns (const std::vector & columns)
std::vector ::const_iterator it;
for (it = columns.begin (); it != columns.end (); ++it)
- if (*it != "id" &&
- *it != "uuid" &&
- *it != "project" &&
- *it != "priority" &&
- *it != "entry" &&
- *it != "start" &&
- *it != "due" &&
- *it != "age" &&
- *it != "active" &&
- *it != "tags" &&
- *it != "recur" &&
+ if (*it != "id" &&
+ *it != "uuid" &&
+ *it != "project" &&
+ *it != "priority" &&
+ *it != "entry" &&
+ *it != "start" &&
+ *it != "due" &&
+ *it != "age" &&
+ *it != "active" &&
+ *it != "tags" &&
+ *it != "recur" &&
+ *it != "description_only" &&
*it != "description")
bad.push_back (*it);
diff --git a/src/task.cpp b/src/task.cpp
index 70d2f550c..ac3ed392b 100644
--- a/src/task.cpp
+++ b/src/task.cpp
@@ -88,6 +88,10 @@ static std::string shortUsage (Config& conf)
table.addCell (row, 1, "task append [tags] [attrs] desc...");
table.addCell (row, 2, "Appends more description to an existing task");
+ row = table.addRow ();
+ table.addCell (row, 1, "task annotate ID desc...");
+ table.addCell (row, 2, "Adds an annotation to an existing task");
+
row = table.addRow ();
table.addCell (row, 1, "task completed [tags] [attrs] desc...");
table.addCell (row, 2, "Chronological listing of all completed tasks matching the specified criteria");
@@ -759,12 +763,12 @@ void updateShadowFile (TDB& tdb, Config& conf)
catch (std::string& error)
{
- std::cout << error << std::endl;
+ std::cerr << error << std::endl;
}
catch (...)
{
- std::cout << "Unknown error." << std::endl;
+ std::cerr << "Unknown error." << std::endl;
}
}
@@ -833,6 +837,7 @@ std::string runTaskCommand (
else if (command == "" && task.getId ()) { cmdMod = true; out = handleModify (tdb, task, conf); }
else if (command == "add") { cmdMod = true; out = handleAdd (tdb, task, conf); }
else if (command == "append") { cmdMod = true; out = handleAppend (tdb, task, conf); }
+ else if (command == "annotate") { cmdMod = true; out = handleAnnotate (tdb, task, conf); }
else if (command == "done") { cmdMod = true; out = handleDone (tdb, task, conf); }
else if (command == "undelete") { cmdMod = true; out = handleUndelete (tdb, task, conf); }
else if (command == "delete") { cmdMod = true; out = handleDelete (tdb, task, conf); }
diff --git a/src/task.h b/src/task.h
index 5dacbd4c3..ab25f2d14 100644
--- a/src/task.h
+++ b/src/task.h
@@ -88,6 +88,7 @@ std::string handleStart (TDB&, T&, Config&);
std::string handleStop (TDB&, T&, Config&);
std::string handleUndo (TDB&, T&, Config&);
std::string handleColor (Config&);
+std::string handleAnnotate (TDB&, T&, Config&);
// report.cpp
void filter (std::vector&, T&);
diff --git a/src/tests/annotate.t b/src/tests/annotate.t
new file mode 100755
index 000000000..bd867f8d4
--- /dev/null
+++ b/src/tests/annotate.t
@@ -0,0 +1,74 @@
+#! /usr/bin/perl
+################################################################################
+## 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
+##
+################################################################################
+
+use strict;
+use warnings;
+use Test::More tests => 8;
+
+# Create the rc file.
+if (open my $fh, '>', 'annotate.rc')
+{
+ print $fh "data.location=.\n",
+ "report.r.description=r\n",
+ "report.r.columns=id,description\n",
+ "report.r.sort=id+\n";
+ close $fh;
+ ok (-r 'annotate.rc', 'Created annotate.rc');
+}
+
+# Add two tasks, annotate one twice.
+qx{../task rc:annotate.rc add one};
+qx{../task rc:annotate.rc add two};
+qx{../task rc:annotate.rc annotate 1 foo};
+sleep 2;
+qx{../task rc:annotate.rc annotate 1 bar};
+my $output = qx{../task rc:annotate.rc r};
+
+# ID Description
+# -- -------------------------------
+# 1 one
+# 3/24/2009 foo
+# 3/24/2009 bar
+# 2 two
+#
+# 2 tasks
+like ($output, qr/1 one/, 'task 1');
+like ($output, qr/2 two/, 'task 2');
+like ($output, qr/one.+\d{1,2}\/\d{1,2}\/\d{4} foo/ms, 'first annotation');
+like ($output, qr/foo.+\d{1,2}\/\d{1,2}\/\d{4} bar/ms, 'second annotation');
+like ($output, qr/2 tasks/, 'count');
+
+# Cleanup.
+unlink 'pending.data';
+ok (!-r 'pending.data', 'Removed pending.data');
+
+unlink 'annotate.rc';
+ok (!-r 'annotate.rc', 'Removed annotate.rc');
+
+exit 0;
+
diff --git a/src/tests/t.t.cpp b/src/tests/t.t.cpp
index ae76b18c6..d98fca274 100644
--- a/src/tests/t.t.cpp
+++ b/src/tests/t.t.cpp
@@ -31,11 +31,11 @@
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
- UnitTest test (5);
+ UnitTest test (8);
T t;
std::string s = t.compose ();
- test.is ((int)s.length (), 46, "T::T (); T::compose ()");
+ test.is ((int)s.length (), 49, "T::T (); T::compose ()");
test.diag (s);
t.setStatus (T::completed);
@@ -54,11 +54,26 @@ int main (int argc, char** argv)
test.diag (s);
// Round trip test.
- std::string sample = "00000000-0000-0000-0000-000000000000 - [] [] Sample";
+ std::string sample = "00000000-0000-0000-0000-000000000000 - [] [] [] Sample";
T t2;
t2.parse (sample);
sample += "\n";
test.is (t2.compose (), sample, "T::parse -> T::compose round trip");
+
+ // b10b3236-70d8-47bb-840a-b4c430758fb6 - [foo] [bar:baz] [1237865996:'woof'] sample\n
+ // ....:....|....:....|....:....|....:....|....:....|....:....|....:....|....:....|....:....|
+ // ^ ^ ^
+ // 0 36 66
+ t.setStatus (T::pending);
+ t.addTag ("foo");
+ t.setAttribute ("bar", "baz");
+ t.addAnnotation ("woof");
+ t.setDescription ("sample");
+ std::string format = t.compose ();
+ test.is (format.substr (36, 20), " - [foo] [bar:baz] [", "compose tag, attribute");
+ test.is (format.substr (66, 16), ":'woof'] sample\n", "compose annotation");
+ test.is (t.getAnnotationCount (), 1, "annotation count");
+
return 0;
}