Merge branch '1.7.0' into timesheet

This commit is contained in:
Paul Beckingham 2009-05-07 00:44:12 -04:00
commit c61a295df7
37 changed files with 1037 additions and 663 deletions

View file

@ -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");

View file

@ -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::sequenceContains (int id) const
{
foreach (seq, mSequence)
if (*seq == id)
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
// uuid status [tags] [attributes] [annotations] description
//
@ -575,8 +588,6 @@ void T::parse (const std::string& line)
openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
std::vector <std::string> 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)
{

View file

@ -49,7 +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 <int> getAllIds () const { return mSequence; }
void addId (int id) { if (mId == 0) mId = id; mSequence.push_back (id); }
status getStatus () const { return mStatus; }
void setStatus (status s) { mStatus = s; }
@ -82,6 +84,7 @@ public:
void getAnnotations (std::map <time_t, std::string>&) const;
void setAnnotations (const std::map <time_t, std::string>&);
void addAnnotation (const std::string&);
bool sequenceContains (int) const;
const std::string compose () const;
const std::string composeCSV ();
@ -95,6 +98,7 @@ private:
status mStatus;
std::string mUUID;
int mId;
std::vector <int> mSequence;
std::string mDescription;
std::vector<std::string> mTags;
std::vector<std::string> mRemoveTags;

View file

@ -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 <T> all;
tdb.allPendingT (all);
filterSequence (all, task);
int id = task.getId ();
std::vector <T>::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 <T> all;
tdb.allPendingT (all);
filterSequence (all, task);
int id = task.getId ();
std::vector <T>::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 <std::string> 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.
@ -439,68 +429,75 @@ 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 <T> all;
tdb.allPendingT (all);
filterSequence (all, task);
foreach (t, all)
{
std::vector <T> all;
tdb.allPendingT (all);
foreach (t, all)
std::stringstream question;
question << "Permanently delete task "
<< t->getId ()
<< " '"
<< t->getDescription ()
<< "'?";
if (!conf.get (std::string ("confirmation"), false) || confirm (question.str ()))
{
if (t->getId () == task.getId ())
// 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;
}
break; // No point continuing the loop.
}
else
{
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;
}
else
out << "Task not deleted." << std::endl;
return out.str ();
}
@ -508,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 <T> all;
tdb.pendingT (all);
filterSequence (all, task);
std::vector <T>::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 <T> all;
tdb.pendingT (all);
filterSequence (all, task);
std::vector <T>::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 ();
}
////////////////////////////////////////////////////////////////////////////////
@ -590,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 <T> 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 <T> 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 ();
}
@ -677,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 <T> 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 <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
std::vector <T> 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 <std::string> 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 <std::string, std::string> 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 <time_t, std::string> annotations;
original.getAnnotations (annotations);
std::map <time_t, std::string>::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 <time_t, std::string> annotations;
original.getAnnotations (annotations);
std::map <time_t, std::string>::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;
@ -844,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 <T> all;
tdb.allPendingT (all);
// Lookup the complete task.
T complete = findT (task.getId (), all);
int count = 0;
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
std::vector <T> 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 <std::string> 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 <std::string, std::string> 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;
@ -1030,28 +854,22 @@ std::string handleAnnotate (TDB& tdb, T& task, Config& conf)
std::stringstream out;
std::vector <T> all;
tdb.pendingT (all);
filterSequence (all, task);
std::vector <T>::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 ();
}
@ -1067,3 +885,156 @@ T findT (int id, const std::vector <T>& 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 <std::string> 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 <std::string, std::string> 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 <time_t, std::string> annotations;
task.getAnnotations (annotations);
std::map <time_t, std::string>::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 <time_t, std::string> annotations;
task.getAnnotations (annotations);
std::map <time_t, std::string>::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;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -301,6 +301,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;
@ -308,6 +311,56 @@ static bool validId (const std::string& input)
return true;
}
////////////////////////////////////////////////////////////////////////////////
// 1,2-4,6
static bool validSequence (
const std::string& input,
std::vector <int>& ids)
{
std::vector <std::string> ranges;
split (ranges, input, ',');
std::vector <std::string>::iterator it;
for (it = ranges.begin (); it != ranges.end (); ++it)
{
std::vector <std::string> 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);
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);
}
break;
default:
return false;
break;
}
}
return ids.size () ? true : false;
}
////////////////////////////////////////////////////////////////////////////////
static bool validTag (const std::string& input)
{
@ -392,15 +445,23 @@ 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
//
// substitution ::= "/" from "/" to "/g"
// | "/" from "/" to "/" ;
//
// tags ::= "+" word
// | "-" word ;
//
// attributes ::= word ":" value
// | word ":"
//
// sequence ::= \d+ "," sequence
// | \d+ "-" \d+ ;
//
// description (whatever isn't one of the above)
void parse (
std::vector <std::string>& args,
std::string& command,
@ -409,6 +470,9 @@ void parse (
{
command = "";
bool foundSequence = false;
bool foundSomethingAfterSequence = false;
std::string descCandidate = "";
for (size_t i = 0; i < args.size (); ++i)
{
@ -422,16 +486,24 @@ void parse (
std::string from;
std::string to;
bool global;
std::vector <int> 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))
task.setId (::atoi (arg.c_str ()));
if (lowerCase (command) != "add" && // "add" doesn't require an ID
validSequence (arg, sequence) &&
! foundSomethingAfterSequence)
{
foundSequence = true;
foreach (id, sequence)
task.addId (*id);
}
// 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] == '-')
@ -442,6 +514,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);
@ -464,12 +539,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;
@ -484,6 +565,9 @@ void parse (
// Anything else is just considered description.
else
{
if (foundSequence)
foundSomethingAfterSequence = true;
if (descCandidate.length ())
descCandidate += " ";
descCandidate += arg;

View file

@ -46,6 +46,53 @@
#include <ncurses.h>
#endif
////////////////////////////////////////////////////////////////////////////////
void filterSequence (std::vector<T>& all, T& task)
{
std::vector <int> sequence = task.getAllIds ();
std::vector <T> filtered;
std::vector <T>::iterator t;
for (t = all.begin (); t != all.end (); ++t)
{
std::vector <int>::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 <int> filteredSequence;
std::vector <T>::iterator fs;
for (fs = filtered.begin (); fs != filtered.end (); ++fs)
filteredSequence.push_back (fs->getId ());
std::vector <int> left;
std::vector <int> 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 <int>::iterator s;
for (s = right.begin (); s != right.end (); ++s)
out << " " << *s;
out << " not found";
throw out.str ();
}
}
all = filtered;
}
////////////////////////////////////////////////////////////////////////////////
void filter (std::vector<T>& all, T& task)
{
@ -265,47 +312,53 @@ std::string handleInfo (TDB& tdb, T& task, Config& conf)
std::vector <T> 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.sequenceContains (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");
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 +389,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");
@ -440,14 +503,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 ();
@ -1038,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;
@ -1047,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;

View file

@ -230,7 +230,13 @@ static std::string longUsage (Config& conf)
{
std::stringstream out;
out << shortUsage (conf)
<< "ID is the numeric identifier displayed by the 'task list' command." << "\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"
<< " task pri:H 1,2-5,19" << "\n"
<< "\n"
<< "Tags are arbitrary words, any quantity:" << "\n"
<< " +tag The + means add the tag" << "\n"
@ -339,7 +345,7 @@ int main (int argc, char** argv)
catch (std::string& error)
{
std::cerr << error << std::endl;
std::cout << error << std::endl;
return -1;
}

View file

@ -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 <T>&);
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>&, T&);
void filter (std::vector<T>&, T&);
std::string handleInfo (TDB&, T&, Config&);
std::string handleCompleted (TDB&, T&, Config&);
@ -152,4 +158,45 @@ void autoColorize (T&, Text::color&, Text::color&, Config&);
// import.cpp
std::string handleImport (TDB&, T&, Config&);
// list template
///////////////////////////////////////////////////////////////////////////////
template <class T> 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]);
}
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -5,3 +5,4 @@ date.t
duration.t
text.t
autocomplete.t
parse.t

View file

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

View file

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

View file

@ -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');

134
src/tests/parse.t.cpp Normal file
View file

@ -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 <iostream>
#include "task.h"
#include "test.h"
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (18);
std::vector <std::string> 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 <int> 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 <int> 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 <int> 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 <int> 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;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -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');

View file

@ -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');