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.
This commit is contained in:
Paul Beckingham 2009-05-07 00:33:17 -04:00
parent 72ff15ea7a
commit aec64afc5c
4 changed files with 427 additions and 554 deletions

View file

@ -8,6 +8,10 @@
Charles Yun). Charles Yun).
+ Now writes a sample "defaultwidth" configuration variable to the default + Now writes a sample "defaultwidth" configuration variable to the default
.taskrc file (thanks to T. Charles Yun). .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 ------------------------------ ------ old releases ------------------------------

View file

@ -1,153 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Task Usage</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="task.css" type="text/css" />
</head>
<body>
<div id="container">
<table>
<tr>
<td>
<div id="toolbar">
<a href="task.html">Home</a>
<a href="setup.html">Setup</a>
<a href="30second.html">30-second Tutorial</a>
<a href="simple.html">Simple</a>
<a href="advanced.html">Advanced</a>
<a href="shell.html">Shell</a>
<a href="config.html">Configuration</a>
<a href="color.html">Colors</a>
<a href="usage.html">Usage</a>
<a href="recur.html">Recurrence</a>
<a href="date.html">Date Handling</a>
<a href="faq.html">FAQ</a>
<a href="versions.html">Old Versions</a>
<a href="links.html">Task on the Web</a>
</div>
<div id="content">
<br />
<br />
<br />
<h2 class="title"><a name="usage">Command Usage<a></h2>
<div class="content">
<pre><code>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:
$ ! ' " ( ) ; \ ` * ? { } [ ] < > | & % # ~</code></pre>
<div>
<br />
<br />
<div class="content">
<p>
Copyright 2006-2009, P. Beckingham. All rights reserved.
</p>
</div>
</div>
</td>
<td align="right" valign="top" width="200px">
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<script type="text/javascript"><!--
google_ad_client = "pub-9709799404235424";
/* Task Main */
google_ad_slot = "8660617875";
google_ad_width = 120;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</td>
</tr>
</table>
</div>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
var pageTracker = _gat._getTracker("UA-4737637-1");
pageTracker._initData();
pageTracker._trackPageview();
</script>
</body>
</html>

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::string handleUndelete (TDB& tdb, T& task, Config& conf)
{ {
std::stringstream out; std::stringstream out;
std::vector <T> all; std::vector <T> all;
tdb.allPendingT (all); tdb.allPendingT (all);
filterSequence (all, task);
int id = task.getId (); foreach (t, all)
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == id) if (t->getStatus () == T::deleted)
{ {
if (it->getStatus () == T::deleted) if (t->getAttribute ("recur") != "")
{ out << "Task does not support 'undo' for recurring tasks.\n";
if (it->getAttribute ("recur") != "")
{
out << "Task does not support 'undelete' for recurring tasks." << std::endl;
return out.str ();
}
T restored (*it); t->setStatus (T::pending);
restored.setStatus (T::pending); t->removeAttribute ("end");
restored.removeAttribute ("end"); tdb.modifyT (*t);
tdb.modifyT (restored);
out << "Task " << id << " successfully undeleted." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' successfully undeleted.\n";
return out.str ();
} }
else else
{ {
out << "Task " << id << " is not deleted - therefore cannot undelete." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' is not deleted - therefore cannot be undeleted.\n";
return out.str ();
}
} }
} }
out << "Task " << id out << "\n"
<< " not found - tasks can only be reliably undeleted if the undelete" << std::endl << "Please note that tasks can only be reliably undeleted if the undelete "
<< "command is run immediately after the errant delete command." << std::endl; << "command is run immediately after the errant delete command."
<< std::endl;
return out.str (); return out.str ();
} }
@ -242,37 +234,31 @@ std::string handleUndo (TDB& tdb, T& task, Config& conf)
std::vector <T> all; std::vector <T> all;
tdb.allPendingT (all); tdb.allPendingT (all);
filterSequence (all, task);
int id = task.getId (); foreach (t, all)
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == id) if (t->getStatus () == T::completed)
{ {
if (it->getStatus () == T::completed) if (t->getAttribute ("recur") != "")
{ out << "Task does not support 'undo' for recurring tasks.\n";
if (it->getAttribute ("recur") != "")
return std::string ("Task does not support 'undo' for recurring tasks.\n");
T restored (*it); t->setStatus (T::pending);
restored.setStatus (T::pending); t->removeAttribute ("end");
restored.removeAttribute ("end"); tdb.modifyT (*t);
tdb.modifyT (restored);
out << "Task " << id << " successfully undone." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' successfully undone." << std::endl;
return out.str ();
} }
else else
{ {
out << "Task " << id << " is not done - therefore cannot be undone." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' is not done - therefore cannot be undone." << std::endl;
return out.str ();
}
} }
} }
out << "Task " << id out << std::endl
<< " not found - tasks can only be reliably undone if the undo" << 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; << "command is run immediately after the errant done command."
<< std::endl;
return out.str (); return out.str ();
} }
@ -367,6 +353,7 @@ std::string handleVersion (Config& conf)
// Complain about configuration variables that are not recognized. // Complain about configuration variables that are not recognized.
// These are the regular configuration variables. // These are the regular configuration variables.
// Note that there is a leading and trailing space.
std::string recognized = 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 " "color.pri.L color.pri.M color.pri.none color.recurring color.tagged "
@ -387,7 +374,10 @@ std::string handleVersion (Config& conf)
std::vector <std::string> unrecognized; std::vector <std::string> unrecognized;
foreach (i, all) 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 // These are special configuration variables, because their name is
// dynamic. // dynamic.
@ -441,11 +431,18 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
std::vector <T> all; std::vector <T> all;
tdb.allPendingT (all); tdb.allPendingT (all);
filterSequence (all, task);
foreach (t, all) foreach (t, all)
{ {
if (t->getId () == task.getId () || task.sequenceContains (t->getId ())) std::stringstream question;
{ question << "Permanently delete task "
if (!conf.get (std::string ("confirmation"), false) || confirm ("Permanently delete task?")) << t->getId ()
<< " '"
<< t->getDescription ()
<< "'?";
if (!conf.get (std::string ("confirmation"), false) || confirm (question.str ()))
{ {
// Check for the more complex case of a recurring task. If this is a // Check for the more complex case of a recurring task. If this is a
// recurring task, get confirmation to delete them all. // recurring task, get confirmation to delete them all.
@ -465,8 +462,9 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Deleting recurring task " out << "Deleting recurring task "
<< sibling->getId () << sibling->getId ()
<< " " << " '"
<< sibling->getDescription () << sibling->getDescription ()
<< "'"
<< std::endl; << std::endl;
} }
} }
@ -479,8 +477,9 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
tdb.deleteT (*t); tdb.deleteT (*t);
out << "Deleting recurring task " out << "Deleting recurring task "
<< t->getId () << t->getId ()
<< " " << " '"
<< t->getDescription () << t->getDescription ()
<< "'"
<< std::endl; << std::endl;
} }
} }
@ -490,15 +489,15 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Deleting task " out << "Deleting task "
<< t->getId () << t->getId ()
<< " " << " '"
<< t->getDescription () << t->getDescription ()
<< "'"
<< std::endl; << std::endl;
} }
} }
else else
out << "Task not deleted." << std::endl; out << "Task not deleted." << std::endl;
} }
}
return out.str (); return out.str ();
} }
@ -506,112 +505,97 @@ std::string handleDelete (TDB& tdb, T& task, Config& conf)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string handleStart (TDB& tdb, T& task, Config& conf) std::string handleStart (TDB& tdb, T& task, Config& conf)
{ {
std::vector <T> all;
tdb.pendingT (all);
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{
if (it->getId () == task.getId ())
{
T original (*it);
std::stringstream out; std::stringstream out;
if (original.getAttribute ("start") == "") std::vector <T> all;
tdb.pendingT (all);
filterSequence (all, task);
foreach (t, all)
{
if (t->getAttribute ("start") == "")
{ {
char startTime[16]; char startTime[16];
sprintf (startTime, "%u", (unsigned int) time (NULL)); sprintf (startTime, "%u", (unsigned int) time (NULL));
original.setAttribute ("start", startTime); t->setAttribute ("start", startTime);
original.setId (task.getId ()); tdb.modifyT (*t);
tdb.modifyT (original);
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Started " out << "Started "
<< original.getId () << t->getId ()
<< " " << " '"
<< original.getDescription () << t->getDescription ()
<< "'"
<< std::endl; << std::endl;
nag (tdb, task, conf); nag (tdb, task, conf);
} }
else else
{ {
out << "Task " << task.getId () << " already started." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' already started." << std::endl;
}
} }
return out.str (); return out.str ();
} }
}
throw std::string ("Task not found.");
return std::string (""); // To satisfy gcc.
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string handleStop (TDB& tdb, T& task, Config& conf) std::string handleStop (TDB& tdb, T& task, Config& conf)
{ {
std::vector <T> all;
tdb.pendingT (all);
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{
if (it->getId () == task.getId ())
{
T original (*it);
std::stringstream out; std::stringstream out;
if (original.getAttribute ("start") != "") std::vector <T> all;
tdb.pendingT (all);
filterSequence (all, task);
foreach (t, all)
{ {
original.removeAttribute ("start"); if (t->getAttribute ("start") != "")
original.setId (task.getId ()); {
tdb.modifyT (original); t->removeAttribute ("start");
tdb.modifyT (*t);
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Stopped " << original.getId () << " " << original.getDescription () << std::endl; out << "Stopped " << t->getId () << " '" << t->getDescription () << "'" << std::endl;
} }
else else
{ {
out << "Task " << task.getId () << " not started." << std::endl; out << "Task " << t->getId () << " '" << t->getDescription () << "' not started." << std::endl;
}
} }
return out.str (); return out.str ();
} }
}
throw std::string ("Task not found.");
return std::string (""); // To satisfy gcc.
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string handleDone (TDB& tdb, T& task, Config& conf) std::string handleDone (TDB& tdb, T& task, Config& conf)
{ {
std::stringstream out; std::stringstream out;
if (!tdb.completeT (task)) std::vector <T> all;
tdb.allPendingT (all);
std::vector <T> filtered = all;
filterSequence (filtered, task);
foreach (t, filtered)
{
t->setStatus (T::completed);
if (!tdb.completeT (*t))
throw std::string ("Could not mark task as completed."); throw std::string ("Could not mark task as completed.");
// Now update mask in parent. // 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)) if (conf.get ("echo.command", true))
out << "Completed " out << "Completed "
<< t->getId () << t->getId ()
<< " " << " '"
<< t->getDescription () << t->getDescription ()
<< "'"
<< std::endl; << std::endl;
t->setStatus (T::completed);
updateRecurrenceMask (tdb, all, *t); updateRecurrenceMask (tdb, all, *t);
break; nag (tdb, task, conf);
}
} }
nag (tdb, task, conf);
return out.str (); return out.str ();
} }
@ -675,163 +659,50 @@ std::string handleExport (TDB& tdb, T& task, Config& conf)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string handleModify (TDB& tdb, T& task, Config& conf) std::string handleModify (TDB& tdb, T& task, Config& conf)
{ {
int count = 0;
std::stringstream out; std::stringstream out;
std::vector <T> all; std::vector <T> all;
tdb.allPendingT (all); tdb.allPendingT (all);
// Lookup the complete task. std::vector <T> filtered = all;
T complete = findT (task.getId (), all); filterSequence (filtered, task);
foreach (seq, filtered)
{
// Perform some logical consistency checks. // Perform some logical consistency checks.
if (task.getAttribute ("recur") != "" && if (task.getAttribute ("recur") != "" &&
task.getAttribute ("due") == "" && task.getAttribute ("due") == "" &&
complete.getAttribute ("due") == "") seq->getAttribute ("due") == "")
throw std::string ("You cannot specify a recurring task without a due date."); throw std::string ("You cannot specify a recurring task without a due date.");
if (task.getAttribute ("until") != "" && if (task.getAttribute ("until") != "" &&
task.getAttribute ("recur") == "" && task.getAttribute ("recur") == "" &&
complete.getAttribute ("recur") == "") seq->getAttribute ("recur") == "")
throw std::string ("You cannot specify an until date for a non-recurring task."); throw std::string ("You cannot specify an until date for a non-recurring task.");
int count = 0; // Make all changes.
std::vector <T>::iterator it; foreach (other, all)
for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == complete.getId () || // Self if (other->getId () == seq->getId () || // Self
(complete.getAttribute ("parent") != "" && (seq->getAttribute ("parent") != "" &&
it->getAttribute ("parent") == complete.getAttribute ("parent")) || // Sibling seq->getAttribute ("parent") == other->getAttribute ("parent")) || // Sibling
it->getUUID () == complete.getAttribute ("parent")) // Parent other->getUUID () == seq->getAttribute ("parent")) // Parent
{ {
T original (*it);
// A non-zero value forces a file write. // A non-zero value forces a file write.
int changes = 0; int changes = 0;
// Apply a new description, if any. // Apply other deltas.
if (task.getDescription () != "") changes += deltaDescription (*other, task);
{ changes += deltaTags (*other, task);
original.setDescription (task.getDescription ()); changes += deltaAttributes (*other, task);
++changes; changes += deltaSubstitutions (*other, task);
}
// 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) if (changes)
tdb.modifyT (original); tdb.modifyT (*other);
++count; ++count;
} }
} }
}
if (count == 0)
throw std::string ("Task not found.");
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl; out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl;
@ -842,91 +713,46 @@ std::string handleModify (TDB& tdb, T& task, Config& conf)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string handleAppend (TDB& tdb, T& task, Config& conf) std::string handleAppend (TDB& tdb, T& task, Config& conf)
{ {
int count = 0;
std::stringstream out; std::stringstream out;
std::vector <T> all; std::vector <T> all;
tdb.allPendingT (all); tdb.allPendingT (all);
// Lookup the complete task. std::vector <T> filtered = all;
T complete = findT (task.getId (), all); filterSequence (filtered, task);
foreach (seq, filtered)
int count = 0;
std::vector <T>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == complete.getId () || // Self foreach (other, all)
(complete.getAttribute ("parent") != "" && {
it->getAttribute ("parent") == complete.getAttribute ("parent")) || // Sibling if (other->getId () == seq->getId () || // Self
it->getUUID () == complete.getAttribute ("parent")) // Parent (seq->getAttribute ("parent") != "" &&
seq->getAttribute ("parent") == other->getAttribute ("parent")) || // Sibling
other->getUUID () == seq->getAttribute ("parent")) // Parent
{ {
T original (*it);
// A non-zero value forces a file write. // A non-zero value forces a file write.
int changes = 0; int changes = 0;
// Apply a new description, if any. // Apply other deltas.
if (task.getDescription () != "") changes += deltaAppend (*other, task);
{ changes += deltaTags (*other, task);
original.setDescription (original.getDescription () + changes += deltaAttributes (*other, task);
" " +
task.getDescription ());
++changes;
}
// 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) if (changes)
{ {
tdb.modifyT (original); tdb.modifyT (*other);
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Appended '" out << "Appended '"
<< task.getDescription () << task.getDescription ()
<< "' to task " << "' to task "
<< original.getId () << other->getId ()
<< std::endl; << std::endl;
} }
++count; ++count;
} }
} }
}
if (count == 0)
throw std::string ("Task not found.");
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl; 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::stringstream out;
std::vector <T> all; std::vector <T> all;
tdb.pendingT (all); tdb.pendingT (all);
filterSequence (all, task);
std::vector <T>::iterator it; foreach (t, all)
for (it = all.begin (); it != all.end (); ++it)
{ {
if (it->getId () == task.getId ()) t->addAnnotation (task.getDescription ());
{ tdb.modifyT (*t);
it->addAnnotation (task.getDescription ());
tdb.modifyT (*it);
if (conf.get ("echo.command", true)) if (conf.get ("echo.command", true))
out << "Annotated " out << "Annotated "
<< task.getId () << t->getId ()
<< " with '" << " with '"
<< task.getDescription () << t->getDescription ()
<< "'" << "'"
<< std::endl; << std::endl;
return out.str ();
}
} }
throw std::string ("Task not found.");
return out.str (); return out.str ();
} }
@ -1065,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

@ -46,6 +46,53 @@
#include <ncurses.h> #include <ncurses.h>
#endif #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) void filter (std::vector<T>& all, T& task)
{ {
@ -1054,6 +1101,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
if (task.getStatus () == T::deleted) if (task.getStatus () == T::deleted)
{ {
epoch = monthlyEpoch (task.getAttribute ("end")); epoch = monthlyEpoch (task.getAttribute ("end"));
groups[epoch] = 0;
if (deletedGroup.find (epoch) != deletedGroup.end ()) if (deletedGroup.find (epoch) != deletedGroup.end ())
deletedGroup[epoch] = deletedGroup[epoch] + 1; deletedGroup[epoch] = deletedGroup[epoch] + 1;
@ -1063,6 +1111,7 @@ std::string handleReportGHistory (TDB& tdb, T& task, Config& conf)
else if (task.getStatus () == T::completed) else if (task.getStatus () == T::completed)
{ {
epoch = monthlyEpoch (task.getAttribute ("end")); epoch = monthlyEpoch (task.getAttribute ("end"));
groups[epoch] = 0;
if (completedGroup.find (epoch) != completedGroup.end ()) if (completedGroup.find (epoch) != completedGroup.end ())
completedGroup[epoch] = completedGroup[epoch] + 1; completedGroup[epoch] = completedGroup[epoch] + 1;