<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>doc/man/task-tutorial.5</filename>
    </added>
    <added>
      <filename>doc/man/task.1</filename>
    </added>
    <added>
      <filename>doc/man/taskrc.5</filename>
    </added>
    <added>
      <filename>doc/misc/grammar.bnf</filename>
    </added>
    <added>
      <filename>doc/misc/script.txt</filename>
    </added>
    <added>
      <filename>i18n/strings.de-DE</filename>
    </added>
    <added>
      <filename>i18n/strings.en-US</filename>
    </added>
    <added>
      <filename>i18n/strings.es-ES</filename>
    </added>
    <added>
      <filename>i18n/strings.fr-FR</filename>
    </added>
    <added>
      <filename>i18n/strings.nl-NL</filename>
    </added>
    <added>
      <filename>i18n/strings.sv-SE</filename>
    </added>
    <added>
      <filename>i18n/tips.de-DE</filename>
    </added>
    <added>
      <filename>i18n/tips.en-US</filename>
    </added>
    <added>
      <filename>i18n/tips.sv-SE</filename>
    </added>
    <added>
      <filename>package-config/README</filename>
    </added>
    <added>
      <filename>package-config/cygwin/CYGWIN-PATCHES/setup.hint</filename>
    </added>
    <added>
      <filename>package-config/cygwin/CYGWIN-PATCHES/task-1.7.1-1.README</filename>
    </added>
    <added>
      <filename>package-config/cygwin/task-1.7.1-1.patch</filename>
    </added>
    <added>
      <filename>package-config/cygwin/usr/share/doc/Cygwin/task-1.7.1-1.README</filename>
    </added>
    <added>
      <filename>package-config/fedora/task.spec</filename>
    </added>
    <added>
      <filename>package-config/osx/binary/COPYING.txt</filename>
    </added>
    <added>
      <filename>package-config/osx/binary/README.txt</filename>
    </added>
    <added>
      <filename>package-config/osx/task.pmdoc/01task-contents.xml</filename>
    </added>
    <added>
      <filename>package-config/osx/task.pmdoc/01task.xml</filename>
    </added>
    <added>
      <filename>package-config/osx/task.pmdoc/index.xml</filename>
    </added>
    <added>
      <filename>package-config/ubuntu/debian/changelog</filename>
    </added>
    <added>
      <filename>package-config/ubuntu/debian/compat</filename>
    </added>
    <added>
      <filename>package-config/ubuntu/debian/control</filename>
    </added>
    <added>
      <filename>package-config/ubuntu/debian/copyright</filename>
    </added>
    <added>
      <filename>package-config/ubuntu/debian/docs</filename>
    </added>
    <added>
      <filename>package-config/ubuntu/debian/rules</filename>
    </added>
    <added>
      <filename>package-config/ubuntu/debian/watch</filename>
    </added>
    <added>
      <filename>scripts/bash/task_completion.sh</filename>
    </added>
    <added>
      <filename>scripts/vim/README</filename>
    </added>
    <added>
      <filename>scripts/vim/ftdetect/task.vim</filename>
    </added>
    <added>
      <filename>scripts/vim/syntax/taskdata.vim</filename>
    </added>
    <added>
      <filename>scripts/vim/syntax/taskedit.vim</filename>
    </added>
    <added>
      <filename>scripts/zsh/_task</filename>
    </added>
    <added>
      <filename>src/Att.cpp</filename>
    </added>
    <added>
      <filename>src/Att.h</filename>
    </added>
    <added>
      <filename>src/Cmd.cpp</filename>
    </added>
    <added>
      <filename>src/Cmd.h</filename>
    </added>
    <added>
      <filename>src/Context.cpp</filename>
    </added>
    <added>
      <filename>src/Context.h</filename>
    </added>
    <added>
      <filename>src/Duration.cpp</filename>
    </added>
    <added>
      <filename>src/Duration.h</filename>
    </added>
    <added>
      <filename>src/Filter.cpp</filename>
    </added>
    <added>
      <filename>src/Filter.h</filename>
    </added>
    <added>
      <filename>src/Keymap.cpp</filename>
    </added>
    <added>
      <filename>src/Keymap.h</filename>
    </added>
    <added>
      <filename>src/Location.cpp</filename>
    </added>
    <added>
      <filename>src/Location.h</filename>
    </added>
    <added>
      <filename>src/Nibbler.cpp</filename>
    </added>
    <added>
      <filename>src/Nibbler.h</filename>
    </added>
    <added>
      <filename>src/Permission.cpp</filename>
    </added>
    <added>
      <filename>src/Permission.h</filename>
    </added>
    <added>
      <filename>src/Record.cpp</filename>
    </added>
    <added>
      <filename>src/Record.h</filename>
    </added>
    <added>
      <filename>src/Sequence.cpp</filename>
    </added>
    <added>
      <filename>src/Sequence.h</filename>
    </added>
    <added>
      <filename>src/StringTable.cpp</filename>
    </added>
    <added>
      <filename>src/StringTable.h</filename>
    </added>
    <added>
      <filename>src/Subst.cpp</filename>
    </added>
    <added>
      <filename>src/Subst.h</filename>
    </added>
    <added>
      <filename>src/Task.cpp</filename>
    </added>
    <added>
      <filename>src/Task.h</filename>
    </added>
    <added>
      <filename>src/custom.cpp</filename>
    </added>
    <added>
      <filename>src/i18n.h</filename>
    </added>
    <added>
      <filename>src/interactive.cpp</filename>
    </added>
    <added>
      <filename>src/main.cpp</filename>
    </added>
    <added>
      <filename>src/main.h</filename>
    </added>
    <added>
      <filename>src/recur.cpp</filename>
    </added>
    <added>
      <filename>src/tests/alias.t</filename>
    </added>
    <added>
      <filename>src/tests/args.t</filename>
    </added>
    <added>
      <filename>src/tests/att.t.cpp</filename>
    </added>
    <added>
      <filename>src/tests/bug.annotate.t</filename>
    </added>
    <added>
      <filename>src/tests/bug.before.t</filename>
    </added>
    <added>
      <filename>src/tests/bug.bulk.t</filename>
    </added>
    <added>
      <filename>src/tests/bug.uuid.t</filename>
    </added>
    <added>
      <filename>src/tests/cal.t</filename>
    </added>
    <added>
      <filename>src/tests/cmd.t.cpp</filename>
    </added>
    <added>
      <filename>src/tests/color.t.cpp</filename>
    </added>
    <added>
      <filename>src/tests/config.t.cpp</filename>
    </added>
    <added>
      <filename>src/tests/filt.t.cpp</filename>
    </added>
    <added>
      <filename>src/tests/fontunderline.t</filename>
    </added>
    <added>
      <filename>src/tests/nibbler.t.cpp</filename>
    </added>
    <added>
      <filename>src/tests/rc.override.t</filename>
    </added>
    <added>
      <filename>src/tests/rc.t</filename>
    </added>
    <added>
      <filename>src/tests/record.t.cpp</filename>
    </added>
    <added>
      <filename>src/tests/seq.t.cpp</filename>
    </added>
    <added>
      <filename>src/tests/stringtable.t.cpp</filename>
    </added>
    <added>
      <filename>src/tests/subst.t.cpp</filename>
    </added>
    <added>
      <filename>src/tests/util.t.cpp</filename>
    </added>
    <added>
      <filename>src/text.h</filename>
    </added>
    <added>
      <filename>src/utf8.cpp</filename>
    </added>
    <added>
      <filename>src/utf8.h</filename>
    </added>
    <added>
      <filename>src/util.h</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,7 +1,10 @@
-Principal Author:
-  Paul Beckingham
+The development of task was made possible by the significant contributions of the following people:
+  Paul Beckingham    (Principal Author)
+  Federico Hernandez (Package Maintainer &amp; Contributing Author)
+  David J Patrick    (Designer)
+  John Florian       (Contributing Author)
 
-Contributing Authors:
+The following submitted code, packages or analysis, and deserve special thanks:
   Damian Glenny
   Andy Lester
   H. &#304;brahim G&#252;ng&#246;r
@@ -10,14 +13,12 @@ Contributing Authors:
   Benjamin Tegarden
   Chris Pride
   Richard Querin
-  Federico Hernandez
   T. Charles Yun
-  David J Patrick
   P.C. Shyamshankar
   Johan Friis
   Steven de Brouwer
 
-With thanks to:
+Thanks to the following, who submitted detailed bug reports and excellent suggestions:
   Eugene Kramer
   Srijith K
   Bruce Israel
@@ -33,4 +34,6 @@ With thanks to:
   Eric Farris
   Bruce Dillahunty
   Askme Too
+  Thomas@BIC
+  Ian Mortimer
 </diff>
      <filename>AUTHORS</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,83 @@
 
 ------ current release ---------------------------
 
-1.7.0 (5/14/2009)
+1.8.0 (7/21/2009)
+ + Added zsh tab completion script (thanks to P.C. Shyamshankar).
+ + Fixed bug that cause the _forcecolor configuration variable to be
+   considered obsolete (thank to Bruce Dillahunty).
+ + Fixed documentation errors (thanks to Thomas@BIC).
+ + The 'weekstart' configuration variable now controls the 'calendar'
+   report (thanks to Federico Hernandez).
+ + The 'displayweeknumber' configuration variable now controls the display
+   of week number in the 'calendar' report (thanks to Federico Hernandez).
+ + Supports '--' argument to indicate that all subsequence arguments are
+   part of the description, despite what they otherwise might mean.
+ + Removed support for the obsolete task file format 1 (never released).
+ + Fixed bug that allowed blank annotations to be added (thanks to Bruce
+   Dillahunty).
+ + Supports negative tag filters, so that (task list +foo -bar) now filters
+   tasks that have the &quot;foo&quot; tag, but do not have the &quot;bar&quot; tag (thanks to
+   Chris Pride).
+ + Custom reports now support a more compact form of the &quot;age&quot; column,
+   called &quot;age_compact&quot; (thanks to T. Charles Yun).
+ + Supports 'rc.name:value' for a command line override to .taskrc data
+   (thanks to Federico Hernandez).
+ + Removed obsolete DEVELOPERS file.  The online support forums at
+   http://taskwarrior.org will provide better information.
+ + Fixed bug that kept some deleted tasks showing up on the calendar report
+   (thanks to Federico Hernandez).
+ + Now asks the user to confirm large changes if configuration variable
+   'confirmation' is set to 'yes'.  A large change is one that completely
+   replaces a task description, or operates on a large number of tasks,
+   which defaults to 4 but is configurable via the 'bulk' configuration
+   variable  (thanks to John Florian).
+ + Now echoes back the new task ID on 'add' (thanks to Bruce Dillahunty).
+ + The new &quot;shell&quot; command provides an interactive shell for task.  All
+   commands are supported (thanks to Bruce Dillahunty, Federico Hernandez,
+   and John Florian).
+ + New &quot;recurring&quot; report to list all recurring tasks.
+ + New, more flexible, more consistent, grep-able file format.
+ + If task is renamed to &quot;cal&quot;, or there is a symlink to task called &quot;cal&quot;,
+   then task can act as a replacement for the Unix &quot;cal&quot; command.
+ + Supports arguments to the cal command like &quot;month year&quot;, &quot;year&quot;, etc.
+ + The &quot;tags&quot; report now shows the tag usage count.
+ + The &quot;projects&quot; report now shows totals by project and priority.
+ + Now supports attribute modifiers that allow much finer control over report
+   filtering, for example &quot;task list due.before:friday&quot;, or &quot;task list
+   pri.not:H&quot; and many more.
+ + Now supports new &quot;age_compact&quot; and &quot;wait&quot; custom report columns.
+ + Now supports colorization of the header and footnote messages that are
+   printed before and after report output, with the 'color.header' and
+   'color.footnote' configuration variables.
+ + Now supports the 'limit' attribute, to control the number of tasks that
+   are shown, for example: &quot;task list limit:10&quot;.
+ + Now supports a debug mode that can be used to generate helpful information
+   when reporting a problem.  Just run the command with &quot;task rc.debug:on ...&quot;
+   and diagnostics will be generated that will help pinpoint a problem.
+ + The new &quot;undo&quot; command replaces the old &quot;undo&quot; and &quot;undelete&quot; command
+   with a complete undo stack that can rollback all changes.
+ + While waiting for a file lock, task states the reason for the delay.
+ + Now supports a 'waiting' state that causes tasks to not appear until
+   a certain date, for example &quot;task &lt;ID&gt; wait:&lt;date&gt;&quot;.  The task
+   will then not show up on any report (except 'all') until that date.
+ + The &quot;active&quot;, &quot;completed&quot;, &quot;overdue&quot; and &quot;next&quot; reports are now custom
+   reports, and therefore modifiable.
+ + Now supports a 'waiting' custom report to list all waiting tasks.
+ + Now supports a 'recurring' custom report to list all recurring tasks.
+ + Now supports an 'all' report to list all tasks, including deleted
+ + Supports command aliases - create an alias for any command by creating
+   a .taskrc entry like &quot;alias.new_name=old_name&quot;.
+   and completed tasks.
+ + Now over 1,600 unit tests, helping to maintain code quality.
+
+------ old releases ------------------------------
+
+1.7.1 (6/8/2009)
+ + Fixed build failure on OpenBSD (thanks to Mike Adonay).
+ + Took the opportunity of a patch release to update the various email
+   addresses and URLs in the various documents.
+
+1.7.0 (5/14/2009) f6b8b39d8b4a85c30a457e9e78b582b74531bfe4
  + Improved the errors when parsing a corrupt or unrecognized pending.data
    or completed.data file (thanks to T. Charles Yun).
  + Added details to the &quot;info&quot; report about recurring tasks (thanks to T.
@@ -31,16 +107,14 @@
    to /usr/local/share/task (thanks to Federico Hernandez).
  + Applied patch to allow task to build on Arch Linux (thanks to Johan Friis).
  + Applied patch to fix a UUID bug on Solaris 8 (thanks to Steven de Brouwer).
- + The task man page is now installed.  Try &quot;man task&quot; (thanks to Federico
-   Hernandez and P.C. Shyamshankar).
+ + The task and taskrc man pages are here.  Try &quot;man task&quot;, &quot;man taskrc&quot;
+   (thanks to Federico Hernandez and P.C. Shyamshankar).
  + Fixed bug that causes task to create a default .task directory, even if
    data.location specified otherwise (thanks to Federico Hernandez).
  + New &quot;edit&quot; command that fires up a text editor (uses 'editor' configuration
    variable, $VISUAL or $EDITOR environment variable) and allows direct
    editing of all editable task details.
 
------- old releases ------------------------------
-
 1.6.1 (4/24/2009) 1b6faf57c998617024d0348a87b941a5d2ab2249
   + Fixed bug that caused new, first-time .taskrc files to be written without
     including the custom report labels (thanks to P.C. Shyamshankar).
@@ -282,7 +356,7 @@
   + Made unit tests compile and run again.
   + Removed tests from distibution.
 
-0.9.6 (5/13/208)
+0.9.6 (5/13/2008)
   + Corrected wrong include file in Table.cpp.
   + Replaced color management code.
   + Improved color rules code.
@@ -329,7 +403,7 @@
   + Rules-based colorization.
 
 0.8.1 (1/28/2008) - 0.8.16 (3/13/2008)
-  + autoconf conversion (many builds).
+  + autoconf conversion
 
 0.8.0 Polish (1/25/2008)
   + Code cleanup, reorganization.</diff>
      <filename>ChangeLog</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,20 @@
 SUBDIRS = src
-EXTRA_DIST = task_completion.sh doc/man1/task.1 doc/man5/taskrc.5
-man1_MANS = doc/man1/task.1
-man5_MANS = doc/man5/taskrc.5
-otherdir = $(datadir)/doc/task-1.7.0
-other_DATA = AUTHORS ChangeLog COPYING INSTALL NEWS README task_completion.sh
+
+dist_man_MANS = doc/man/task.1 doc/man/taskrc.5 doc/man/task-tutorial.5
+
+docdir = $(datadir)/doc/${PACKAGE}-${VERSION}
+doc_DATA = AUTHORS ChangeLog COPYING NEWS README
+
+EXTRA_DIST = INSTALL
+
+bashscriptsdir = $(docdir)
+nobase_dist_bashscripts_DATA = scripts/bash/task_completion.sh
+
+zshscriptsdir = $(docdir)
+nobase_dist_zshscripts_DATA = scripts/zsh/_task
+
+vimscriptsdir = $(docdir)
+nobase_dist_vimscripts_DATA = scripts/vim/README scripts/vim/ftdetect/task.vim scripts/vim/syntax/taskdata.vim scripts/vim/syntax/taskedit.vim
+
+i18ndir = $(docdir)
+nobase_dist_i18n_DATA = i18n/strings.de-DE i18n/strings.en-US i18n/strings.es-ES i18n/strings.fr-FR i18n/strings.nl-NL i18n/strings.sv-SE i18n/tips.de-DE i18n/tips.en-US i18n/tips.sv-SE</diff>
      <filename>Makefile.am</filename>
    </modified>
    <modified>
      <diff>@@ -1,41 +1,48 @@
-Welcome to Task 1.7.0.
+
+New Features in task 1.8.0
+
+  - Attribute modifiers, for precise queries
+  - Improved calendar feature
+  - Full undo capability
+  - All reports now customizable
+  - Command aliases can now be created
+  - In addition to being a standard part of Fedora 10 and 11 (yum install task),
+    task is now also a standard part of Cygwin 1.5
+  - There are new demo movies on taskwarrior.org
+
+  Please refer to the ChangeLog file for full details.  There are too many to
+  list here.
 
 Task has been built and tested on the following configurations:
 
-  - OS X 10.4 Tiger
   - OS X 10.5 Leopard
-  - Fedora Core 8
-  - Fedora Core 9
-  - Fedora Core 10
-  - Ubuntu 7 Feisty Fawn
-  - Ubuntu 8 Hardy Heron
-  - Ubuntu 8.10 Intrepid Ibex
+  - OS X 10.4 Tiger
+  - Fedora Core 11 Leonidas
+  - Fedora Core 10 Cambridge
   - Ubuntu 9.04 Jaunty Jackalope
+  - Ubuntu 8.10 Intrepid Ibex
+  - Ubuntu 8.04 Hardy Heron
+  - Slackware 12.2
   - Arch Linux
-  - Solaris 8
   - Solaris 10
+  - Solaris 8
+  - OpenBSD 4.5
+  - FreeBSD
   - Cygwin 1.5.25-14
 
 While Task has undergone testing, bugs are sure to remain.  If you encounter a
-bug, please contact me at task@beckingham.net.  Here is what you could do, in
-order of increasing effort (to you) and usefulness (to me):
+bug, please enter a new issue at:
 
-  - Do nothing.  Bug probably won't get fixed.
+  http://taskwarrior.org/projects/taskwarrior/issues/new
 
-  - Send an email to task@beckingham.net, explaining what you saw.  The bug
-    will be addressed, and a new release will be made.  You will be a hero.
+Or you can also report the issue in the forums at:
 
-  - Send an email, and a reproducible test case in the form of the few commands
-    it takes to recreate the problem.  The bug will be addressed, and a new
-    release will be made.  You will be a hero.
+  http://taskwarrior.org/projects/taskwarrior/boards
 
-  - If you are a developer, send a patch that fixes the problem.  Your patch
-    will be applied and tested, and a new release will be made.  You will be a
-    hero.
+Or just send a message to:
 
-  - Another option involves using Github's issue tracker, which can be found
-    at http://github.com/pbeckingham/task/issues which has the advantage that
-    everyone gets to see and track the issue.  You will still be a hero.
+  support@taskwarrior.org
 
 Thank you.
 
+---</diff>
      <filename>NEWS</filename>
    </modified>
    <modified>
      <diff>@@ -1,55 +1,24 @@
-Thank you for taking a look at task.  Task is a GTD utility featuring:
 
-  - Robust C++ implementation
-  - Tags
-  - Colorful, tabular output
-  - Reports, graphs
-  - Lots of commands
-  - Low-level API
-  - Abbreviations for all commands, options
-  - Multi-user file locking
-  - Clean architecture allowing quick addition of new features
-  - Recurring tasks
+Thank you for taking a look at task!
 
-It is intended that features, mainly in the form of reports will be added
-frequently, with best practices and useful reports evolving from usage patterns.
+Task is a GTD, todo list, task management, command line utility with a multitude
+of features.  It is a portable, well supported, very active project, and it is
+Open Source.  Task has binary distributions, online documentation, demonstration
+movies, and you'll find all the details at the site:
 
-Task is scope-limited to GTD-like functionality only.
+    http://taskwarrior.org
 
-You may want to watch the old task movie on YouTube:
+At the site you'll find a wiki, discussion forums, downloads, news and more.
 
-  http://www.youtube.com/watch?v=l68LCl6BYvs
 
-or the new improved one:
+Your contributions are especially welcome.  Whether it comes in the form of
+code patches, ideas, discussion, bug reports or just encouragement, your input
+is needed.
 
-  http://www.youtube.com/watch?v=D2Kn4DMOVSw
+Please send your support questions and code patches to:
 
-Either will give you a fairly good idea of what task is capable of, and
-whether it fits in to your way of working.  As a command line application,
-task is not for everyone and some of you may prefer to not proceed.  The
-movie or online tutorial file are the quickest way for you to make that
-decision.  The online tutorial can be found at:
+    support@taskwarrior.org
 
-  http://www.beckingham.net/task.html
-
-Task is based on ideas presented in the todo.sh script, found on:
-
-  http://todotxt.org
-
-Task has many more features than todo.sh, but fundamentally, they are
-both working toward the same goals, which is to help you follow basic
-Getting Things Done (GTD) principles.
-
-All feedback is welcome, in addition to any bug reports or patches to:
-
-  task@beckingham.net
-
-Or better yet, get involved in the discussion at
-
-  http://groups.google.com/group/taskprogram
-
-Got an idea for an enhancement?  Post a message!
-
-I have found that task makes me more productive and organized.
-I hope task can do the same for you.  
+Consider joining taskwarrior.org and participating in the future of task.
 
+---</diff>
      <filename>README</filename>
    </modified>
    <modified>
      <diff>@@ -2,7 +2,7 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ(2.61)
-AC_INIT(task, 1.7.0, bugs@beckingham.net)
+AC_INIT(task, 1.8.0, support@taskwarrior.org)
 
 CFLAGS=&quot;${CFLAGS=}&quot;
 CXXFLAGS=&quot;${CXXFLAGS=}&quot;
@@ -10,7 +10,7 @@ CXXFLAGS=&quot;${CXXFLAGS=}&quot;
 # to the configure script (./configure --enable-debug)
 # Check if we have enable debug support.
 AC_MSG_CHECKING(whether to enable debugging)
-debug_default=&quot;yes&quot;
+debug_default=&quot;no&quot;
 AC_ARG_ENABLE(debug, [  --enable-debug=[no/yes] turn on debugging
                        [default=$debug_default]],, enable_debug=$debug_default)
 # Yes, shell scripts can be used
@@ -33,7 +33,7 @@ else
 fi
 
 AM_INIT_AUTOMAKE
-AC_CONFIG_SRCDIR([src/task.cpp])
+AC_CONFIG_SRCDIR([src/main.cpp])
 AC_CONFIG_HEADER([auto.h])
 
 # Checks for programs.</diff>
      <filename>configure.ac</filename>
    </modified>
    <modified>
      <diff>@@ -1 +1,2 @@
 *.o
+Makefile.in</diff>
      <filename>src/.gitignore</filename>
    </modified>
    <modified>
      <diff>@@ -32,8 +32,9 @@
 #include &lt;unistd.h&gt;
 #include &lt;stdlib.h&gt;
 #include &lt;pwd.h&gt;
-#include &quot;task.h&quot;
 #include &quot;Config.h&quot;
+#include &quot;text.h&quot;
+#include &quot;util.h&quot;
 
 ////////////////////////////////////////////////////////////////////////////////
 // These are default (but overridable) reports.  These entries are necessary
@@ -44,32 +45,7 @@
 // upgrade program to make the change, or c) this.
 Config::Config ()
 {
-  (*this)[&quot;report.long.description&quot;]   = &quot;Lists all task, all data, matching the specified criteria&quot;;
-  (*this)[&quot;report.long.columns&quot;]       = &quot;id,project,priority,entry,start,due,recur,age,tags,description&quot;;
-  (*this)[&quot;report.long.labels&quot;]        = &quot;ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description&quot;;
-  (*this)[&quot;report.long.sort&quot;]          = &quot;due+,priority-,project+&quot;;
-
-  (*this)[&quot;report.list.description&quot;]   = &quot;Lists all tasks matching the specified criteria&quot;;
-  (*this)[&quot;report.list.columns&quot;]       = &quot;id,project,priority,due,active,age,description&quot;;
-  (*this)[&quot;report.list.labels&quot;]        = &quot;ID,Project,Pri,Due,Active,Age,Description&quot;;
-  (*this)[&quot;report.list.sort&quot;]          = &quot;due+,priority-,project+&quot;;
-
-  (*this)[&quot;report.ls.description&quot;]     = &quot;Minimal listing of all tasks matching the specified criteria&quot;;
-  (*this)[&quot;report.ls.columns&quot;]         = &quot;id,project,priority,description&quot;;
-  (*this)[&quot;report.ls.labels&quot;]          = &quot;ID,Project,Pri,Description&quot;;
-  (*this)[&quot;report.ls.sort&quot;]            = &quot;priority-,project+&quot;;
-
-  (*this)[&quot;report.newest.description&quot;] = &quot;Shows the newest tasks&quot;;
-  (*this)[&quot;report.newest.columns&quot;]     = &quot;id,project,priority,due,active,age,description&quot;;
-  (*this)[&quot;report.newest.labels&quot;]      = &quot;ID,Project,Pri,Due,Active,Age,Description&quot;;
-  (*this)[&quot;report.newest.sort&quot;]        = &quot;id-&quot;;
-  (*this)[&quot;report.newest.limit&quot;]       = &quot;10&quot;;
-
-  (*this)[&quot;report.oldest.description&quot;] = &quot;Shows the oldest tasks&quot;;
-  (*this)[&quot;report.oldest.columns&quot;]     = &quot;id,project,priority,due,active,age,description&quot;;
-  (*this)[&quot;report.oldest.labels&quot;]      = &quot;ID,Project,Pri,Due,Active,Age,Description&quot;;
-  (*this)[&quot;report.oldest.sort&quot;]        = &quot;id+&quot;;
-  (*this)[&quot;report.oldest.limit&quot;]       = &quot;10&quot;;
+  setDefaults ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -92,20 +68,20 @@ bool Config::load (const std::string&amp; file)
     while (getline (in, line))
     {
       // Remove comments.
-      size_type pound = line.find (&quot;#&quot;);
+      std::string::size_type pound = line.find (&quot;#&quot;); // no i18n
       if (pound != std::string::npos)
         line = line.substr (0, pound);
 
-      line = trim (line, &quot; \t&quot;);
+      line = trim (line, &quot; \t&quot;); // no i18n
 
       // Skip empty lines.
       if (line.length () &gt; 0)
       {
-        size_type equal = line.find (&quot;=&quot;);
+        std::string::size_type equal = line.find (&quot;=&quot;); // no i18n
         if (equal != std::string::npos)
         {
-          std::string key   = trim (line.substr (0, equal), &quot; \t&quot;);
-          std::string value = trim (line.substr (equal+1, line.length () - equal), &quot; \t&quot;);
+          std::string key   = trim (line.substr (0, equal), &quot; \t&quot;); // no i18n
+          std::string value = trim (line.substr (equal+1, line.length () - equal), &quot; \t&quot;); // no i18n
           (*this)[key] = value;
         }
       }
@@ -119,112 +95,234 @@ bool Config::load (const std::string&amp; file)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-void Config::createDefault (const std::string&amp; home)
+void Config::createDefaultRC (const std::string&amp; rc, const std::string&amp; data)
 {
-  // Strip trailing slash off home directory, if necessary.
-  std::string terminatedHome = home;
-  if (home[home.length () - 1] == '/')
-    terminatedHome = home.substr (0, home.length () - 1);
-
-  // Determine default names of init file and task directory.
-  std::string rcFile  = terminatedHome + &quot;/.taskrc&quot;;
-  std::string dataDir = terminatedHome + &quot;/.task&quot;;;
-
-  // If rcFile is not found, offer to create one.
-  if (-1 == access (rcFile.c_str (), F_OK))
-  {
-    if (confirm (
-          &quot;A configuration file could not be found in &quot;
-        + rcFile
-        + &quot;\n\n&quot;
-        + &quot;Would you like a sample .taskrc created, so task can proceed?&quot;))
-    {
-      // Create a sample .taskrc file.
-      FILE* out;
-      if ((out = fopen (rcFile.c_str (), &quot;w&quot;)))
-      {
-        fprintf (out, &quot;data.location=%s\n&quot;, dataDir.c_str ());
-        fprintf (out, &quot;confirmation=yes\n&quot;);
-        fprintf (out, &quot;echo.command=yes\n&quot;);
-        fprintf (out, &quot;next=2\n&quot;);
-        fprintf (out, &quot;dateformat=m/d/Y\n&quot;);
-        fprintf (out, &quot;#monthsperline=2\n&quot;);
-        fprintf (out, &quot;#defaultwidth=80\n&quot;);
-        fprintf (out, &quot;curses=on\n&quot;);
-        fprintf (out, &quot;color=on\n&quot;);
-        fprintf (out, &quot;due=7\n&quot;);
-        fprintf (out, &quot;nag=You have higher priority tasks.\n&quot;);
-        fprintf (out, &quot;locking=on\n&quot;);
-        fprintf (out, &quot;#editor=vi\n&quot;);
-
-        fprintf (out, &quot;color.overdue=bold_red\n&quot;);
-        fprintf (out, &quot;color.due=bold_yellow\n&quot;);
-        fprintf (out, &quot;color.pri.H=bold\n&quot;);
-        fprintf (out, &quot;#color.pri.M=on_yellow\n&quot;);
-        fprintf (out, &quot;#color.pri.L=on_green\n&quot;);
-        fprintf (out, &quot;#color.pri.none=white on_blue\n&quot;);
-        fprintf (out, &quot;color.active=bold_cyan\n&quot;);
-        fprintf (out, &quot;color.tagged=yellow\n&quot;);
-        fprintf (out, &quot;#color.tag.bug=yellow\n&quot;);
-        fprintf (out, &quot;#color.project.garden=on_green\n&quot;);
-        fprintf (out, &quot;#color.keyword.car=on_blue\n&quot;);
-        fprintf (out, &quot;#color.recurring=on_red\n&quot;);
-        fprintf (out, &quot;#shadow.file=%s/shadow.txt\n&quot;, dataDir.c_str ());
-        fprintf (out, &quot;#shadow.command=list\n&quot;);
-        fprintf (out, &quot;#shadow.notify=on\n&quot;);
-        fprintf (out, &quot;#default.project=foo\n&quot;);
-        fprintf (out, &quot;#default.priority=M\n&quot;);
-        fprintf (out, &quot;default.command=list\n&quot;);
-
-        // Custom reports.
-        fprintf (out, &quot;# Fields: id,uuid,project,priority,entry,start,due,recur,age,active,tags,description\n&quot;);
-        fprintf (out, &quot;#         description_only\n&quot;);
-        fprintf (out, &quot;# Description:   This report is ...\n&quot;);
-        fprintf (out, &quot;# Sort:          due+,priority-,project+\n&quot;);
-        fprintf (out, &quot;# Filter:        pro:x pri:H +bug\n&quot;);
-        fprintf (out, &quot;# Limit:         10\n&quot;);
-
-        fprintf (out, &quot;report.long.description=Lists all task, all data, matching the specified criteria\n&quot;);
-        fprintf (out, &quot;report.long.labels=ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description\n&quot;);
-        fprintf (out, &quot;report.long.columns=id,project,priority,entry,start,due,recur,age,tags,description\n&quot;);
-        fprintf (out, &quot;report.long.sort=due+,priority-,project+\n&quot;);
-
-        fprintf (out, &quot;report.list.description=Lists all tasks matching the specified criteria\n&quot;);
-        fprintf (out, &quot;report.list.labels=ID,Project,Pri,Due,Active,Age,Description\n&quot;);
-        fprintf (out, &quot;report.list.columns=id,project,priority,due,active,age,description\n&quot;);
-        fprintf (out, &quot;report.list.sort=due+,priority-,project+\n&quot;);
-
-        fprintf (out, &quot;report.ls.description=Minimal listing of all tasks matching the specified criteria\n&quot;);
-        fprintf (out, &quot;report.ls.labels=ID,Project,Pri,Description\n&quot;);
-        fprintf (out, &quot;report.ls.columns=id,project,priority,description\n&quot;);
-        fprintf (out, &quot;report.ls.sort=priority-,project+\n&quot;);
-
-        fprintf (out, &quot;report.newest.description=Shows the newest tasks\n&quot;);
-        fprintf (out, &quot;report.newest.labels=ID,Project,Pri,Due,Active,Age,Description\n&quot;);
-        fprintf (out, &quot;report.newest.columns=id,project,priority,due,active,age,description\n&quot;);
-        fprintf (out, &quot;report.newest.sort=id-\n&quot;);
-        fprintf (out, &quot;report.newest.limit=10\n&quot;);
-
-        fprintf (out, &quot;report.oldest.description=Shows the oldest tasks\n&quot;);
-        fprintf (out, &quot;report.oldest.labels=ID,Project,Pri,Due,Active,Age,Description\n&quot;);
-        fprintf (out, &quot;report.oldest.columns=id,project,priority,due,active,age,description\n&quot;);
-        fprintf (out, &quot;report.oldest.sort=id+\n&quot;);
-        fprintf (out, &quot;report.oldest.limit=10\n&quot;);
-
-        fclose (out);
-
-        std::cout &lt;&lt; &quot;Done.&quot; &lt;&lt; std::endl;
-      }
-    }
-  }
+  // Create a sample .taskrc file.
+  std::stringstream contents;
+  contents &lt;&lt; &quot;# Task program configuration file.\n&quot;
+           &lt;&lt; &quot;# For more documentation, see http://taskwarrior.org\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# Files\n&quot;
+           &lt;&lt; &quot;data.location=&quot; &lt;&lt; data &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;locking=on                             # Use file-level locking\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# Terminal\n&quot;
+           &lt;&lt; &quot;curses=on                              # Use ncurses library to determine terminal width\n&quot;
+           &lt;&lt; &quot;#defaultwidth=80                       # Without ncurses, assumed width\n&quot;
+           &lt;&lt; &quot;#editor=vi                             # Preferred text editor\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# Miscellaneous\n&quot;
+           &lt;&lt; &quot;confirmation=yes                       # Confirmation on delete, big changes\n&quot;
+           &lt;&lt; &quot;echo.command=yes                       # Details on command just run\n&quot;
+           &lt;&lt; &quot;next=2                                 # How many tasks per project in next report\n&quot;
+           &lt;&lt; &quot;bulk=2                                 # &gt; 2 tasks considered 'a lot', for confirmation\n&quot;
+           &lt;&lt; &quot;nag=You have higher priority tasks.    # Nag message to keep you honest\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# Dates\n&quot;
+           &lt;&lt; &quot;dateformat=m/d/Y                       # Preferred input and display date format\n&quot;
+           &lt;&lt; &quot;weekstart=Sunday                       # Sunday or Monday only\n&quot;
+           &lt;&lt; &quot;displayweeknumber=yes                  # Show week numbers on calendar\n&quot;
+           &lt;&lt; &quot;due=7                                  # Task is considered due in 7 days\n&quot;
+           &lt;&lt; &quot;#monthsperline=2                       # Number of calendar months on a line\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# Color controls.\n&quot;
+           &lt;&lt; &quot;color=on                               # Use color\n&quot;
+           &lt;&lt; &quot;color.overdue=bold_red                 # Color of overdue tasks\n&quot;
+           &lt;&lt; &quot;color.due=bold_yellow                  # Color of due tasks\n&quot;
+           &lt;&lt; &quot;color.pri.H=bold                       # Color of priority:H tasks\n&quot;
+           &lt;&lt; &quot;#color.pri.M=on_yellow                 # Color of priority:M tasks\n&quot;
+           &lt;&lt; &quot;#color.pri.L=on_green                  # Color of priority:L tasks\n&quot;
+           &lt;&lt; &quot;#color.pri.none=white on_blue          # Color of priority:  tasks\n&quot;
+           &lt;&lt; &quot;color.active=bold_cyan                 # Color of active tasks\n&quot;
+           &lt;&lt; &quot;color.tagged=yellow                    # Color of tagged tasks\n&quot;
+           &lt;&lt; &quot;#color.tag.bug=yellow                  # Color of +bug tasks\n&quot;
+           &lt;&lt; &quot;#color.project.garden=on_green         # Color of project:garden tasks\n&quot;
+           &lt;&lt; &quot;#color.keyword.car=on_blue             # Color of description.contains:car tasks\n&quot;
+           &lt;&lt; &quot;#color.recurring=on_red                # Color of recur.any: tasks\n&quot;
+           &lt;&lt; &quot;#color.header=bold_green               # Color of header messages\n&quot;
+           &lt;&lt; &quot;#color.footnote=bold_green             # Color of footnote messages\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;#shadow.file=/tmp/shadow.txt           # Location of shadow file\n&quot;
+           &lt;&lt; &quot;#shadow.command=list                   # Task command for shadow file\n&quot;
+           &lt;&lt; &quot;#shadow.notify=on                      # Footnote when updated\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;#default.project=foo                   # Unless otherwise specified\n&quot;
+           &lt;&lt; &quot;#default.priority=M                    # Unless otherwise specified\n&quot;
+           &lt;&lt; &quot;default.command=list                   # Unless otherwise specified\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# Fields: id,uuid,project,priority,entry,start,due,recur,recur_ind,age,\n&quot;
+           &lt;&lt; &quot;#          age_compact,active,tags,description,description_only\n&quot;
+           &lt;&lt; &quot;# Description:   This report is ...\n&quot;
+           &lt;&lt; &quot;# Sort:          due+,priority-,project+\n&quot;
+           &lt;&lt; &quot;# Filter:        pro:x pri:H +bug limit:10\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task long\n&quot;
+           &lt;&lt; &quot;report.long.description=Lists all task, all data, matching the specified criteria\n&quot;
+           &lt;&lt; &quot;report.long.columns=id,project,priority,entry,start,due,recur,age,tags,description\n&quot;
+           &lt;&lt; &quot;report.long.labels=ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description\n&quot;
+           &lt;&lt; &quot;report.long.sort=due+,priority-,project+\n&quot;
+           &lt;&lt; &quot;report.long.filter=status:pending\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task list\n&quot;
+           &lt;&lt; &quot;report.list.description=Lists all tasks matching the specified criteria\n&quot;
+           &lt;&lt; &quot;report.list.columns=id,project,priority,due,active,age,description\n&quot;
+           &lt;&lt; &quot;report.list.labels=ID,Project,Pri,Due,Active,Age,Description\n&quot;
+           &lt;&lt; &quot;report.list.sort=due+,priority-,project+\n&quot;
+           &lt;&lt; &quot;report.list.filter=status:pending\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task ls\n&quot;
+           &lt;&lt; &quot;report.ls.description=Minimal listing of all tasks matching the specified criteria\n&quot;
+           &lt;&lt; &quot;report.ls.columns=id,project,priority,description\n&quot;
+           &lt;&lt; &quot;report.ls.labels=ID,Project,Pri,Description\n&quot;
+           &lt;&lt; &quot;report.ls.sort=priority-,project+\n&quot;
+           &lt;&lt; &quot;report.ls.filter=status:pending\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task newest\n&quot;
+           &lt;&lt; &quot;report.newest.description=Shows the newest tasks\n&quot;
+           &lt;&lt; &quot;report.newest.columns=id,project,priority,due,active,age,description\n&quot;
+           &lt;&lt; &quot;report.newest.labels=ID,Project,Pri,Due,Active,Age,Description\n&quot;
+           &lt;&lt; &quot;report.newest.sort=id-\n&quot;
+           &lt;&lt; &quot;report.newest.filter=status:pending limit:10\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task oldest\n&quot;
+           &lt;&lt; &quot;report.oldest.description=Shows the oldest tasks\n&quot;
+           &lt;&lt; &quot;report.oldest.columns=id,project,priority,due,active,age,description\n&quot;
+           &lt;&lt; &quot;report.oldest.labels=ID,Project,Pri,Due,Active,Age,Description\n&quot;
+           &lt;&lt; &quot;report.oldest.sort=id+\n&quot;
+           &lt;&lt; &quot;report.oldest.filter=status:pending limit:10\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task overdue\n&quot;
+           &lt;&lt; &quot;report.overdue.description=Lists overdue tasks matching the specified criteria\n&quot;
+           &lt;&lt; &quot;report.overdue.columns=id,project,priority,due,active,age,description\n&quot;
+           &lt;&lt; &quot;report.overdue.labels=ID,Project,Pri,Due,Active,Age,Description\n&quot;
+           &lt;&lt; &quot;report.overdue.sort=due+,priority-,project+\n&quot;
+           &lt;&lt; &quot;report.overdue.filter=status:pending due.before:today\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task active\n&quot;
+           &lt;&lt; &quot;report.active.description=Lists active tasks matching the specified criteria\n&quot;
+           &lt;&lt; &quot;report.active.columns=id,project,priority,due,active,age,description\n&quot;
+           &lt;&lt; &quot;report.active.labels=ID,Project,Pri,Due,Active,Age,Description\n&quot;
+           &lt;&lt; &quot;report.active.sort=due+,priority-,project+\n&quot;
+           &lt;&lt; &quot;report.active.filter=status:pending start.any:\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task completed\n&quot;
+           &lt;&lt; &quot;report.completed.description=Lists completed tasks matching the specified criteria\n&quot;
+           &lt;&lt; &quot;report.completed.columns=end,project,priority,age,description\n&quot;
+           &lt;&lt; &quot;report.completed.labels=Complete,Project,Pri,Age,Description\n&quot;
+           &lt;&lt; &quot;report.completed.sort=end+,priority-,project+\n&quot;
+           &lt;&lt; &quot;report.completed.filter=status:completed\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task recurring\n&quot;
+           &lt;&lt; &quot;report.recurring.description=Lists recurring tasks matching the specified criteria\n&quot;
+           &lt;&lt; &quot;report.recurring.columns=id,project,priority,due,recur,active,age,description\n&quot;
+           &lt;&lt; &quot;report.recurring.labels=ID,Project,Pri,Due,Recur,Active,Age,Description\n&quot;
+           &lt;&lt; &quot;report.recurring.sort=due+,priority-,project+\n&quot;
+           &lt;&lt; &quot;report.recurring.filter=status:pending parent.any:\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task waiting\n&quot;
+           &lt;&lt; &quot;report.waiting.description=Lists all waiting tasks matching the specified criteria\n&quot;
+           &lt;&lt; &quot;report.waiting.columns=id,project,priority,wait,age,description\n&quot;
+           &lt;&lt; &quot;report.waiting.labels=ID,Project,Pri,Wait,Age,Description\n&quot;
+           &lt;&lt; &quot;report.waiting.sort=wait+,priority-,project+\n&quot;
+           &lt;&lt; &quot;report.waiting.filter=status:waiting\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task all\n&quot;
+           &lt;&lt; &quot;report.all.description=Lists all tasks matching the specified criteria\n&quot;
+           &lt;&lt; &quot;report.all.columns=id,project,priority,due,active,age,description\n&quot;
+           &lt;&lt; &quot;report.all.labels=ID,Project,Pri,Due,Active,Age,Description\n&quot;
+           &lt;&lt; &quot;report.all.sort=due+,priority-,project+\n&quot;
+           &lt;&lt; &quot;\n&quot;
+           &lt;&lt; &quot;# task next\n&quot;
+           &lt;&lt; &quot;report.next.description=Lists the most urgent tasks\n&quot;
+           &lt;&lt; &quot;report.next.columns=id,project,priority,due,active,age,description\n&quot;
+           &lt;&lt; &quot;report.next.labels=ID,Project,Pri,Due,Active,Age,Description\n&quot;
+           &lt;&lt; &quot;report.next.sort=due+,priority-,project+\n&quot;
+           &lt;&lt; &quot;report.next.filter=status:pending\n&quot;
+           &lt;&lt; &quot;\n&quot;;
+
+  spit (rc, contents.str ());
+}
 
-  this-&gt;load (rcFile);
+////////////////////////////////////////////////////////////////////////////////
+void Config::createDefaultData (const std::string&amp; data)
+{
+  if (access (data.c_str (), F_OK))
+    mkdir (data.c_str (), S_IRWXU);
+}
 
-  // Get the data.location value from the (potentially newly created) .taskrc
-  // file.
-  dataDir = this-&gt;get (&quot;data.location&quot;, dataDir);
-  if (-1 == access (dataDir.c_str (), F_OK))
-    mkdir (dataDir.c_str (), S_IRWXU);
+////////////////////////////////////////////////////////////////////////////////
+void Config::setDefaults ()
+{
+  set (&quot;report.long.description&quot;,      &quot;Lists all task, all data, matching the specified criteria&quot;);      // TODO i18n
+  set (&quot;report.long.columns&quot;,          &quot;id,project,priority,entry,start,due,recur,age,tags,description&quot;); // TODO i18n
+  set (&quot;report.long.labels&quot;,           &quot;ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description&quot;);    // TODO i18n
+  set (&quot;report.long.sort&quot;,             &quot;due+,priority-,project+&quot;);                                        // TODO i18n
+  set (&quot;report.long.filter&quot;,           &quot;status:pending&quot;);                                                 // TODO i18n
+
+  set (&quot;report.list.description&quot;,      &quot;Lists all tasks matching the specified criteria&quot;);                // TODO i18n
+  set (&quot;report.list.columns&quot;,          &quot;id,project,priority,due,active,age,description&quot;);                 // TODO i18n
+  set (&quot;report.list.labels&quot;,           &quot;ID,Project,Pri,Due,Active,Age,Description&quot;);                      // TODO i18n
+  set (&quot;report.list.sort&quot;,             &quot;due+,priority-,project+&quot;);                                        // TODO i18n
+  set (&quot;report.list.filter&quot;,           &quot;status:pending&quot;);                                                 // TODO i18n
+
+  set (&quot;report.ls.description&quot;,        &quot;Minimal listing of all tasks matching the specified criteria&quot;);   // TODO i18n
+  set (&quot;report.ls.columns&quot;,            &quot;id,project,priority,description&quot;);                                // TODO i18n
+  set (&quot;report.ls.labels&quot;,             &quot;ID,Project,Pri,Description&quot;);                                     // TODO i18n
+  set (&quot;report.ls.sort&quot;,               &quot;priority-,project+&quot;);                                             // TODO i18n
+  set (&quot;report.ls.filter&quot;,             &quot;status:pending&quot;);                                                 // TODO i18n
+
+  set (&quot;report.newest.description&quot;,    &quot;Shows the newest tasks&quot;);                                         // TODO i18n
+  set (&quot;report.newest.columns&quot;,        &quot;id,project,priority,due,active,age,description&quot;);                 // TODO i18n
+  set (&quot;report.newest.labels&quot;,         &quot;ID,Project,Pri,Due,Active,Age,Description&quot;);                      // TODO i18n
+  set (&quot;report.newest.sort&quot;,           &quot;id-&quot;);                                                            // TODO i18n
+  set (&quot;report.newest.filter&quot;,         &quot;status:pending limit:10&quot;);                                        // TODO i18n
+
+  set (&quot;report.oldest.description&quot;,    &quot;Shows the oldest tasks&quot;);                                         // TODO i18n
+  set (&quot;report.oldest.columns&quot;,        &quot;id,project,priority,due,active,age,description&quot;);                 // TODO i18n
+  set (&quot;report.oldest.labels&quot;,         &quot;ID,Project,Pri,Due,Active,Age,Description&quot;);                      // TODO i18n
+  set (&quot;report.oldest.sort&quot;,           &quot;id+&quot;);                                                            // TODO i18n
+  set (&quot;report.oldest.filter&quot;,         &quot;status:pending limit:10&quot;);                                        // TODO i18n
+
+  set (&quot;report.overdue.description&quot;,   &quot;Lists overdue tasks matching the specified criteria&quot;);            // TODO i18n
+  set (&quot;report.overdue.columns&quot;,       &quot;id,project,priority,due,active,age,description&quot;);                 // TODO i18n
+  set (&quot;report.overdue.labels&quot;,        &quot;ID,Project,Pri,Due,Active,Age,Description&quot;);                      // TODO i18n
+  set (&quot;report.overdue.sort&quot;,          &quot;due+,priority-,project+&quot;);                                        // TODO i18n
+  set (&quot;report.overdue.filter&quot;,        &quot;status:pending due.before:today&quot;);                                // TODO i18n
+
+  set (&quot;report.active.description&quot;,    &quot;Lists active tasks matching the specified criteria&quot;);             // TODO i18n
+  set (&quot;report.active.columns&quot;,        &quot;id,project,priority,due,active,age,description&quot;);                 // TODO i18n
+  set (&quot;report.active.labels&quot;,         &quot;ID,Project,Pri,Due,Active,Age,Description&quot;);                      // TODO i18n
+  set (&quot;report.active.sort&quot;,           &quot;due+,priority-,project+&quot;);                                        // TODO i18n
+  set (&quot;report.active.filter&quot;,         &quot;status:pending start.any:&quot;);                                      // TODO i18n
+
+  set (&quot;report.completed.description&quot;, &quot;Lists completed tasks matching the specified criteria&quot;);          // TODO i18n
+  set (&quot;report.completed.columns&quot;,     &quot;end,project,priority,age,description&quot;);                           // TODO i18n
+  set (&quot;report.completed.labels&quot;,      &quot;Complete,Project,Pri,Age,Description&quot;);                           // TODO i18n
+  set (&quot;report.completed.sort&quot;,        &quot;end+,priority-,project+&quot;);                                        // TODO i18n
+  set (&quot;report.completed.filter&quot;,      &quot;status:completed&quot;);                                               // TODO i18n
+
+  set (&quot;report.recurring.description&quot;, &quot;Lists recurring tasks matching the specified criteria&quot;);          // TODO i18n
+  set (&quot;report.recurring.columns&quot;,     &quot;id,project,priority,due,recur,active,age,description&quot;);           // TODO i18n
+  set (&quot;report.recurring.labels&quot;,      &quot;ID,Project,Pri,Due,Recur,Active,Age,Description&quot;);                // TODO i18n
+  set (&quot;report.recurring.sort&quot;,        &quot;due+,priority-,project+&quot;);                                        // TODO i18n
+  set (&quot;report.recurring.filter&quot;,      &quot;status:pending parent.any:&quot;);                                     // TODO i18n
+
+  set (&quot;report.waiting.description&quot;,   &quot;Lists all waiting tasks matching the specified criteria&quot;);        // TODO i18n
+  set (&quot;report.waiting.columns&quot;,       &quot;id,project,priority,wait,age,description&quot;);                       // TODO i18n
+  set (&quot;report.waiting.labels&quot;,        &quot;ID,Project,Pri,Wait,Age,Description&quot;);                            // TODO i18n
+  set (&quot;report.waiting.sort&quot;,          &quot;wait+,priority-,project+&quot;);                                       // TODO i18n
+  set (&quot;report.waiting.filter&quot;,        &quot;status:waiting&quot;);                                                 // TODO i18n
+
+  set (&quot;report.all.description&quot;,       &quot;Lists all tasks matching the specified criteria&quot;);                // TODO i18n
+  set (&quot;report.all.columns&quot;,           &quot;id,project,priority,due,active,age,description&quot;);                 // TODO i18n
+  set (&quot;report.all.labels&quot;,            &quot;ID,Project,Pri,Due,Active,Age,Description&quot;);                      // TODO i18n
+  set (&quot;report.all.sort&quot;,              &quot;due+,priority-,project+&quot;);                                        // TODO i18n
+
+  set (&quot;report.next.description&quot;,      &quot;Lists the most urgent tasks&quot;);                                    // TODO i18n
+  set (&quot;report.next.columns&quot;,          &quot;id,project,priority,due,active,age,description&quot;);                 // TODO i18n
+  set (&quot;report.next.labels&quot;,           &quot;ID,Project,Pri,Due,Active,Age,Description&quot;);                      // TODO i18n
+  set (&quot;report.next.sort&quot;,             &quot;due+,priority-,project+&quot;);                                        // TODO i18n
+  set (&quot;report.next.filter&quot;,           &quot;status:pending&quot;);                                                 // TODO i18n
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -265,19 +363,19 @@ const std::string Config::get (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-bool Config::get (const std::string&amp; key, bool default_value)
+bool Config::get (const std::string&amp; key, const bool default_value)
 {
   if ((*this).find (key) != (*this).end ())
   {
     std::string value = lowerCase ((*this)[key]);
 
-    if (value == &quot;t&quot;      ||
-        value == &quot;true&quot;   ||
-        value == &quot;1&quot;      ||
-        value == &quot;yes&quot;    ||
-        value == &quot;on&quot;     ||
-        value == &quot;enable&quot; ||
-        value == &quot;enabled&quot;)
+    if (value == &quot;t&quot;      ||  // TODO i18n
+        value == &quot;true&quot;   ||  // TODO i18n
+        value == &quot;1&quot;      ||  // no i18n
+        value == &quot;yes&quot;    ||  // TODO i18n
+        value == &quot;on&quot;     ||  // TODO i18n
+        value == &quot;enable&quot; ||  // TODO i18n
+        value == &quot;enabled&quot;)   // TODO i18n
       return true;
 
     return false;</diff>
      <filename>src/Config.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -37,14 +37,19 @@ public:
   Config ();
   Config (const std::string&amp;);
 
+  Config (const Config&amp;);
+  Config&amp; operator= (const Config&amp;);
+
   bool load (const std::string&amp;);
-  void createDefault (const std::string&amp;);
+  void createDefaultRC (const std::string&amp;, const std::string&amp;);
+  void createDefaultData (const std::string&amp;);
+  void setDefaults ();
 
   const std::string get (const char*);
   const std::string get (const char*, const char*);
   const std::string get (const std::string&amp;);
   const std::string get (const std::string&amp;, const std::string&amp;);
-  bool get (const std::string&amp;, bool);
+  bool get (const std::string&amp;, const bool);
   int get (const std::string&amp;, const int);
   double get (const std::string&amp;, const double);
   void set (const std::string&amp;, const int);</diff>
      <filename>src/Config.h</filename>
    </modified>
    <modified>
      <diff>@@ -25,11 +25,13 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;iostream&gt;
+#include &lt;sstream&gt;
 #include &lt;time.h&gt;
 #include &lt;assert.h&gt;
 #include &lt;stdlib.h&gt;
-#include &quot;task.h&quot;
 #include &quot;Date.h&quot;
+#include &quot;text.h&quot;
+#include &quot;util.h&quot;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Defaults to &quot;now&quot;.
@@ -63,6 +65,10 @@ Date::Date (const std::string&amp; mdy, const std::string&amp; format /* = &quot;m/d/Y&quot; */)
   int day   = 0;
   int year  = 0;
 
+  // Perhaps it is an epoch date, in string form?
+  if (isEpoch (mdy))
+    return;
+
   // Before parsing according to &quot;format&quot;, perhaps this is a relative date?
   if (isRelativeDate (mdy))
     return;
@@ -81,8 +87,8 @@ Date::Date (const std::string&amp; mdy, const std::string&amp; format /* = &quot;m/d/Y&quot; */)
         throw std::string (&quot;\&quot;&quot;) + mdy + &quot;\&quot; is not a valid date.&quot;;
       }
 
-      if (i + 1 &lt; mdy.length ()                                         &amp;&amp;
-          (mdy[i + 0] == '0' || mdy[i + 0] == '1')                      &amp;&amp;
+      if (i + 1 &lt; mdy.length ()                    &amp;&amp;
+          (mdy[i + 0] == '0' || mdy[i + 0] == '1') &amp;&amp;
           ::isdigit (mdy[i + 1]))
       {
         month = ::atoi (mdy.substr (i, 2).c_str ());
@@ -118,7 +124,7 @@ Date::Date (const std::string&amp; mdy, const std::string&amp; format /* = &quot;m/d/Y&quot; */)
 
     // Double digit.
     case 'y':
-      if (i + 1 &gt;= mdy.length () ||
+      if (i + 1 &gt;= mdy.length ()   ||
           ! ::isdigit (mdy[i + 0]) ||
           ! ::isdigit (mdy[i + 1]))
       {
@@ -179,6 +185,9 @@ Date::Date (const std::string&amp; mdy, const std::string&amp; format /* = &quot;m/d/Y&quot; */)
     }
   }
 
+  if (i &lt; mdy.length ())
+    throw std::string (&quot;\&quot;&quot;) + mdy + &quot;\&quot; is not a valid date in &quot; + format + &quot; format.&quot;;
+
   if (!valid (month, day, year))
     throw std::string (&quot;\&quot;&quot;) + mdy + &quot;\&quot; is not a valid date.&quot;;
 
@@ -209,6 +218,14 @@ time_t Date::toEpoch ()
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+std::string Date::toEpochString ()
+{
+  std::stringstream epoch;
+  epoch &lt;&lt; mT;
+  return epoch.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
 void Date::toEpoch (time_t&amp; epoch)
 {
   epoch = mT;
@@ -254,6 +271,22 @@ const std::string Date::toString (const std::string&amp; format /*= &quot;m/d/Y&quot; */) cons
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+bool Date::valid (const std::string&amp; input, const std::string&amp; format)
+{
+  try
+  {
+    Date test (input, format);
+  }
+
+  catch (...)
+  {
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 bool Date::valid (const int m, const int d, const int y)
 {
   // Check that the year is valid.
@@ -277,9 +310,11 @@ bool Date::leapYear (int year)
 {
   bool ly = false;
 
-       if (!(year % 4))   ly = true;
-  else if (!(year % 400)) ly = true;
-  else if (!(year % 100)) ly = false;
+  // (year % 4 == 0) &amp;&amp; (year % 100 !=0)  OR
+  // (year % 400 == 0)
+  // are leapyears
+
+  if (((!(year % 4)) &amp;&amp; (year % 100)) || (!(year % 400))) ly = true;
 
   return ly;
 }
@@ -355,6 +390,28 @@ std::string Date::dayName (int dow)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+int Date::weekOfYear (int weekStart) const
+{
+  struct tm* t = localtime (&amp;mT);
+  char   weekStr[3];
+
+  if (weekStart == 0)
+    strftime(weekStr, sizeof(weekStr), &quot;%U&quot;, t);
+  else if (weekStart == 1)
+    strftime(weekStr, sizeof(weekStr), &quot;%V&quot;, t);
+  else
+    throw std::string (&quot;The 'weekstart' configuration variable may &quot;
+                       &quot;only contain 'Sunday' or 'Monday'.&quot;);
+
+  int weekNumber = ::atoi (weekStr);
+
+  if (weekStart == 0)
+    weekNumber += 1;
+
+  return weekNumber;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 int Date::dayOfWeek () const
 {
   struct tm* t = localtime (&amp;mT);
@@ -491,6 +548,19 @@ time_t Date::operator- (const Date&amp; rhs)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+bool Date::isEpoch (const std::string&amp; input)
+{
+  if (digitsOnly (input) &amp;&amp;
+      input.length () &gt; 8)
+  {
+    mT = (time_t) ::atoi (input.c_str ());
+    return true;
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // If the input string looks like a relative date, determine that date, set mT
 // and return true.
 //</diff>
      <filename>src/Date.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -44,8 +44,10 @@ public:
 
   void toEpoch (time_t&amp;);
   time_t toEpoch ();
+  std::string toEpochString ();
   void toMDY (int&amp;, int&amp;, int&amp;);
   const std::string toString (const std::string&amp; format = &quot;m/d/Y&quot;) const;
+  static bool valid (const std::string&amp;, const std::string&amp; format = &quot;m/d/Y&quot;);
   static bool valid (const int, const int, const int);
 
   static bool leapYear (int);
@@ -53,11 +55,13 @@ public:
   static std::string monthName (int);
   static void dayName (int, std::string&amp;);
   static std::string dayName (int);
+  static int weekOfYear (const std::string&amp;);
   static int dayOfWeek (const std::string&amp;);
 
   int month () const;
   int day () const;
   int year () const;
+  int weekOfYear (int) const;
   int dayOfWeek () const;
 
   bool operator== (const Date&amp;);
@@ -77,6 +81,7 @@ public:
   time_t operator- (const Date&amp;);
 
 private:
+  bool isEpoch (const std::string&amp;);
   bool isRelativeDate (const std::string&amp;);
 
 protected:</diff>
      <filename>src/Date.h</filename>
    </modified>
    <modified>
      <diff>@@ -306,7 +306,7 @@ Grid::Cell::operator char () const
 {
   switch (mType)
   {
-  case CELL_BOOL:   return mBool ? 'Y' : 'N';
+  case CELL_BOOL:   return mBool ? 'Y' : 'N'; // TODO i18n
   case CELL_CHAR:   return mChar;
   case CELL_INT:    return (char) mInt;
   case CELL_FLOAT:  return (char) (int) mFloat;
@@ -368,7 +368,7 @@ Grid::Cell::operator std::string () const
 
   switch (mType)
   {
-  case CELL_BOOL:   return mBool ? &quot;true&quot; : &quot;false&quot;;
+  case CELL_BOOL:   return mBool ? &quot;true&quot; : &quot;false&quot;; // TODO i18n
   case CELL_CHAR:   sprintf (s, &quot;%c&quot;, mChar);
                     return std::string (s);
   case CELL_INT:    sprintf (s, &quot;%d&quot;, mInt);</diff>
      <filename>src/Grid.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -45,6 +45,9 @@ public:
     Cell (const double);
     Cell (const std::string&amp;);
 
+    Cell (const Cell&amp;);
+    Cell&amp; operator= (const Cell&amp;);
+
     operator bool () const;
     operator char () const;
     operator int () const;
@@ -72,6 +75,9 @@ public:
   Grid ();
   ~Grid ();
 
+  Grid (const Grid&amp;);
+  Grid&amp; operator= (const Grid&amp;);
+
   void add (const unsigned int, const unsigned int, const bool);
   void add (const unsigned int, const unsigned int, const char);
   void add (const unsigned int, const unsigned int, const int);</diff>
      <filename>src/Grid.h</filename>
    </modified>
    <modified>
      <diff>@@ -1,2 +1,11 @@
 bin_PROGRAMS = task
-task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp Timer.cpp color.cpp parse.cpp task.cpp command.cpp edit.cpp report.cpp util.cpp text.cpp rules.cpp import.cpp Config.h Date.h T.h TDB.h Table.h Grid.h Timer.h color.h task.h
+task_SOURCES = Att.cpp Cmd.cpp Config.cpp Context.cpp Date.cpp Duration.cpp   \
+               Filter.cpp Grid.cpp Keymap.cpp Location.cpp Nibbler.cpp        \
+               Record.cpp Sequence.cpp StringTable.cpp Subst.cpp Task.cpp     \
+               TDB.cpp Table.cpp Timer.cpp Permission.cpp color.cpp edit.cpp  \
+               command.cpp import.cpp interactive.cpp recur.cpp report.cpp    \
+               custom.cpp rules.cpp main.cpp text.cpp util.cpp                \
+               Att.h Cmd.h Config.h Context.h Date.h Duration.h Filter.h      \
+               Grid.h Keymap.h Location.h Nibbler.h Record.h Sequence.h       \
+               StringTable.h Subst.h Task.h TDB.h Table.h Timer.h             \
+               Permission.h color.h i18n.h main.h text.h util.h</diff>
      <filename>src/Makefile.am</filename>
    </modified>
    <modified>
      <diff>@@ -24,439 +24,755 @@
 //     USA
 //
 ////////////////////////////////////////////////////////////////////////////////
+
 #include &lt;iostream&gt;
-#include &lt;fstream&gt;
 #include &lt;sstream&gt;
-#include &lt;sys/file.h&gt;
 #include &lt;unistd.h&gt;
 #include &lt;string.h&gt;
-
-#include &quot;task.h&quot;
+#include &lt;stdio.h&gt;
+#include &lt;sys/file.h&gt;
+#include &lt;stdlib.h&gt;
+#include &quot;text.h&quot;
+#include &quot;util.h&quot;
 #include &quot;TDB.h&quot;
+#include &quot;Table.h&quot;
+#include &quot;Timer.h&quot;
+#include &quot;color.h&quot;
+#include &quot;main.h&quot;
+
+extern Context context;
 
 ////////////////////////////////////////////////////////////////////////////////
+//  The ctor/dtor do nothing.
+//  The lock/unlock methods hold the file open.
+//  There should be only one commit.
+//
+//  +- TDB::TDB
+//  |
+//  |  +- TDB::lock
+//  |  |    open
+//  |  |    [lock]
+//  |  |
+//  |  |  +- TDB::load (Filter)
+//  |  |  |    read all
+//  |  |  |    apply filter
+//  |  |  |    return subset
+//  |  |  |
+//  |  |  +- TDB::add (T)
+//  |  |  |
+//  |  |  +- TDB::update (T)
+//  |  |  |
+//  |  |  +- TDB::commit
+//  |  |  |   write all
+//  |  |  |
+//  |  |  +- TDB::undo
+//  |  |
+//  |  +- TDB::unlock
+//  |       [unlock]
+//  |       close
+//  |
+//  +- TDB::~TDB
+//       [TDB::unlock]
+//
 TDB::TDB ()
-: mPendingFile (&quot;&quot;)
-, mCompletedFile (&quot;&quot;)
+: mLock (true)
+, mAllOpenAndLocked (false)
 , mId (1)
-, mNoLock (false)
 {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 TDB::~TDB ()
 {
+  if (mAllOpenAndLocked)
+    unlock ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-void TDB::dataDirectory (const std::string&amp; directory)
+void TDB::clear ()
 {
-  if (! access (expandPath (directory).c_str (), F_OK))
-  {
-    mPendingFile   = directory + &quot;/pending.data&quot;;
-    mCompletedFile = directory + &quot;/completed.data&quot;;
-  }
-  else
-  {
-    std::string error = &quot;Directory '&quot;;
-    error += directory;
-    error += &quot;' does not exist, or is not readable and writable.&quot;;
-    throw error;
-  }
+  mLocations.clear ();
+  mLock = true;
+
+  if (mAllOpenAndLocked)
+    unlock ();
+
+  mAllOpenAndLocked = false;
+  mId = 1;
+  mPending.clear ();
+  mNew.clear ();
+  mModified.clear ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Combine allPendingT with allCompletedT.
-// Note: this method is O(N1) + O(N2), where N2 is not bounded.
-bool TDB::allT (std::vector &lt;T&gt;&amp; all)
+void TDB::location (const std::string&amp; path)
 {
-  all.clear ();
+  if (access (expandPath (path).c_str (), F_OK))
+    throw std::string (&quot;Data location '&quot;) +
+          path +
+          &quot;' does not exist, or is not readable and writable.&quot;;
 
-  // Retrieve all the pending records.
-  std::vector &lt;T&gt; allp;
-  if (allPendingT (allp))
-  {
-    std::vector &lt;T&gt;::iterator i;
-    for (i = allp.begin (); i != allp.end (); ++i)
-      all.push_back (*i);
+  mLocations.push_back (Location (path));
+}
 
-    // Retrieve all the completed records.
-    std::vector &lt;T&gt; allc;
-    if (allCompletedT (allc))
-    {
-      for (i = allc.begin (); i != allc.end (); ++i)
-        all.push_back (*i);
+////////////////////////////////////////////////////////////////////////////////
+void TDB::lock (bool lockFile /* = true */)
+{
+  mLock = lockFile;
 
-      return true;
-    }
+  mPending.clear ();
+  mNew.clear ();
+  mPending.clear ();
+
+  foreach (location, mLocations)
+  {
+    location-&gt;pending   = openAndLock (location-&gt;path + &quot;/pending.data&quot;);
+    location-&gt;completed = openAndLock (location-&gt;path + &quot;/completed.data&quot;);
+    location-&gt;undo      = openAndLock (location-&gt;path + &quot;/undo.data&quot;);
   }
 
-  return false;
+  mAllOpenAndLocked = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Only accesses to the pending file result in Tasks that have assigned ids.
-bool TDB::pendingT (std::vector &lt;T&gt;&amp; all)
+void TDB::unlock ()
 {
-  all.clear ();
-
-  std::vector &lt;std::string&gt; lines;
-  if (readLockedFile (mPendingFile, lines))
+  if (mAllOpenAndLocked)
   {
-    mId = 1;
+    mPending.clear ();
+    mNew.clear ();
+    mModified.clear ();
 
-    int line = 1;
-    std::vector &lt;std::string&gt;::iterator it;
-    for (it = lines.begin (); it != lines.end (); ++it)
+    foreach (location, mLocations)
     {
-      try
-      {
-        T t (*it);
-        t.setId (mId++);
-        if (t.getStatus () == T::pending)
-          all.push_back (t);
-      }
-
-      catch (std::string&amp; e)
-      {
-        std::stringstream more;
-        more &lt;&lt; &quot;  Line &quot; &lt;&lt; line &lt;&lt; &quot;, in &quot; &lt;&lt; &quot;pending.data&quot;;
+      fflush (location-&gt;pending);
+      fclose (location-&gt;pending);
+      location-&gt;pending = NULL;
 
-        throw e + more.str ();
-      }
+      fflush (location-&gt;completed);
+      fclose (location-&gt;completed);
+      location-&gt;completed = NULL;
 
-      ++line;
+      fflush (location-&gt;undo);
+      fclose (location-&gt;undo);
+      location-&gt;completed = NULL;
     }
 
-    return true;
+    mAllOpenAndLocked = false;
   }
-
-  return false;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Only accesses to the pending file result in Tasks that have assigned ids.
-bool TDB::allPendingT (std::vector &lt;T&gt;&amp; all)
+// Returns number of filtered tasks.
+// Note: tasks.clear () is deliberately not called, to allow the combination of
+//       multiple files.
+int TDB::load (std::vector &lt;Task&gt;&amp; tasks, Filter&amp; filter)
 {
-  all.clear ();
-
-  std::vector &lt;std::string&gt; lines;
-  if (readLockedFile (mPendingFile, lines))
+#ifdef FEATURE_TDB_OPT
+  // Special optimization: if the filter contains Att ('status', '', 'pending'),
+  // and no other 'status' filters, then loadCompleted can be skipped.
+  int numberStatusClauses = 0;
+  int numberSimpleStatusClauses = 0;
+  foreach (att, filter)
   {
-    mId = 1;
-
-    int line = 1;
-    std::vector &lt;std::string&gt;::iterator it;
-    for (it = lines.begin (); it != lines.end (); ++it)
+    if (att-&gt;name () == &quot;status&quot;)
     {
-      try
-      {
-        T t (*it);
-        t.setId (mId++);
-        all.push_back (t);
-      }
-
-      catch (std::string&amp; e)
-      {
-        std::stringstream more;
-        more &lt;&lt; &quot;  Line &quot; &lt;&lt; line &lt;&lt; &quot;, in &quot; &lt;&lt; &quot;pending.data&quot;;
+      ++numberStatusClauses;
 
-        throw e + more.str ();
-      }
-
-      ++line;
+      if (att-&gt;mod () == &quot;&quot; &amp;&amp;
+          (att-&gt;value () == &quot;pending&quot; ||
+           att-&gt;value () == &quot;waiting&quot;))
+        ++numberSimpleStatusClauses;
     }
-
-    return true;
   }
+#endif
+
+  loadPending (tasks, filter);
+
+#ifdef FEATURE_TDB_OPT
+  if (numberStatusClauses == 0 ||
+      numberStatusClauses != numberSimpleStatusClauses)
+    loadCompleted (tasks, filter);
+  else
+    context.debug (&quot;load optimization short circuit&quot;);
+#else
+  loadCompleted (tasks, filter);
+#endif
 
-  return false;
+  return tasks.size ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-bool TDB::completedT (std::vector &lt;T&gt;&amp; all) const
+// Returns number of filtered tasks.
+// Note: tasks.clear () is deliberately not called, to allow the combination of
+//       multiple files.
+int TDB::loadPending (std::vector &lt;Task&gt;&amp; tasks, Filter&amp; filter)
 {
-  all.clear ();
+  Timer t (&quot;TDB::loadPending&quot;);
 
-  std::vector &lt;std::string&gt; lines;
-  if (readLockedFile (mCompletedFile, lines))
+  std::string file;
+  int line_number = 1;
+
+  try
   {
-    int line = 1;
-    std::vector &lt;std::string&gt;::iterator it;
-    for (it = lines.begin (); it != lines.end (); ++it)
+    if (mPending.size () == 0)
     {
-      try
+      mId = 1;
+      char line[T_LINE_MAX];
+      foreach (location, mLocations)
       {
-        T t (*it);
-        if (t.getStatus () != T::deleted)
-          all.push_back (t);
-      }
+        line_number = 1;
+        file = location-&gt;path + &quot;/pending.data&quot;;
 
-      catch (std::string&amp; e)
-      {
-        std::stringstream more;
-        more &lt;&lt; &quot;  Line &quot; &lt;&lt; line &lt;&lt; &quot;, in &quot; &lt;&lt; &quot;pending.data&quot;;
+        fseek (location-&gt;pending, 0, SEEK_SET);
+        while (fgets (line, T_LINE_MAX, location-&gt;pending))
+        {
+          int length = ::strlen (line);
+          if (length &gt; 3) // []\n
+          {
+            // TODO Add hidden attribute indicating source?
+            Task task (line);
+            task.id = mId++;
 
-        throw e + more.str ();
+            mPending.push_back (task);
+          }
+
+          ++line_number;
+        }
       }
+    }
 
-      ++line;
+    // Now filter and return.
+    foreach (task, mPending)
+      if (filter.pass (*task))
+        tasks.push_back (*task);
+
+    // Hand back any accumulated additions, if TDB::loadPending is being called
+    // repeatedly.
+    int fakeId = mId;
+    foreach (task, mNew)
+    {
+      task-&gt;id = fakeId++;
+      if (filter.pass (*task))
+        tasks.push_back (*task);
     }
+  }
 
-    return true;
+  catch (std::string&amp; e)
+  {
+    std::stringstream s;
+    s &lt;&lt; &quot; in &quot; &lt;&lt; file &lt;&lt; &quot; at line &quot; &lt;&lt; line_number;
+    throw e + s.str ();
   }
 
-  return false;
+  return tasks.size ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-bool TDB::allCompletedT (std::vector &lt;T&gt;&amp; all) const
+// Returns number of filtered tasks.
+// Note: tasks.clear () is deliberately not called, to allow the combination of
+//       multiple files.
+int TDB::loadCompleted (std::vector &lt;Task&gt;&amp; tasks, Filter&amp; filter)
 {
-  all.clear ();
+  Timer t (&quot;TDB::loadCompleted&quot;);
+
+  std::string file;
+  int line_number;
 
-  std::vector &lt;std::string&gt; lines;
-  if (readLockedFile (mCompletedFile, lines))
+  try
   {
-    int line = 1;
-    std::vector &lt;std::string&gt;::iterator it;
-    for (it = lines.begin (); it != lines.end (); ++it)
+    char line[T_LINE_MAX];
+    foreach (location, mLocations)
     {
-      try
-      {
-        T t (*it);
-        all.push_back (t);
-      }
+      line_number = 1;
+      file = location-&gt;path + &quot;/completed.data&quot;;
 
-      catch (std::string&amp; e)
+      fseek (location-&gt;completed, 0, SEEK_SET);
+      while (fgets (line, T_LINE_MAX, location-&gt;completed))
       {
-        std::stringstream more;
-        more &lt;&lt; &quot;  Line &quot; &lt;&lt; line &lt;&lt; &quot;, in &quot; &lt;&lt; &quot;pending.data&quot;;
+        int length = ::strlen (line);
+        if (length &gt; 3) // []\n
+        {
+          // TODO Add hidden attribute indicating source?
 
-        throw e + more.str ();
-      }
+          if (line[length - 1] == '\n')
+            line[length - 1] = '\0';
+
+          Task task (line);
+          task.id = 0;  // Need a value, just not a valid value.
+
+          if (filter.pass (task))
+            tasks.push_back (task);
+        }
 
-      ++line;
+        ++line_number;
+      }
     }
+  }
 
-    return true;
+  catch (std::string&amp; e)
+  {
+    std::stringstream s;
+    s &lt;&lt; &quot; in &quot; &lt;&lt; file &lt;&lt; &quot; at line &quot; &lt;&lt; line_number;
+    throw e + s.str ();
   }
 
-  return false;
+  return tasks.size ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Note: mLocations[0] is where all tasks are written.
+void TDB::add (const Task&amp; task)
+{
+  mNew.push_back (task);
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void TDB::update (const Task&amp; task)
+{
+  mModified.push_back (task);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-bool TDB::addT (const T&amp; t)
+// Interestingly, only the pending file gets written to.  The completed file is
+// only modified by TDB::gc.
+int TDB::commit ()
 {
-  T task (t);
-  std::vector &lt;std::string&gt; tags;
-  task.getTags (tags);
+  Timer t (&quot;TDB::commit&quot;);
+
+  int quantity = mNew.size () + mModified.size ();
 
-  // +tag or -tag are both considered valid tags to add to a new pending task.
-  // Generating an error here would not be friendly.
-  for (unsigned int i = 0; i &lt; tags.size (); ++i)
+  // This is an optimization.  If there are only new tasks, and none were
+  // modified, simply seek to the end of pending and write.
+  if (mNew.size () &amp;&amp; ! mModified.size ())
   {
-    if (tags[i][0] == '-' || tags[i][0] == '+')
+    fseek (mLocations[0].pending, 0, SEEK_END);
+    foreach (task, mNew)
     {
-      task.removeTag (tags[i]);
-      task.addTag (tags[i].substr (1, std::string::npos));
+      mPending.push_back (*task);
+      fputs (task-&gt;composeF4 ().c_str (), mLocations[0].pending);
     }
+
+    fseek (mLocations[0].undo, 0, SEEK_END);
+    foreach (task, mNew)
+      writeUndo (*task, mLocations[0].undo);
+
+    mNew.clear ();
+    return quantity;
   }
 
-  if (task.getStatus () == T::pending ||
-      task.getStatus () == T::recurring)
+  // The alternative is to potentially rewrite both files.
+  else if (mNew.size () || mModified.size ())
   {
-    return writePending (task);
+    // allPending is a copy of mPending, with all modifications included, and
+    // new tasks appended.
+    std::vector &lt;Task&gt; allPending;
+    allPending = mPending;
+    foreach (task, allPending)
+      foreach (mtask, mModified)
+        if (task-&gt;id == mtask-&gt;id)
+          *task = *mtask;
+
+    foreach (task, mNew)
+      allPending.push_back (*task);
+
+    // Write out all pending.
+    if (fseek (mLocations[0].pending, 0, SEEK_SET) == 0)
+    {
+      if (ftruncate (fileno (mLocations[0].pending), 0))
+        throw std::string (&quot;Failed to truncate pending.data file &quot;);
+
+      foreach (task, allPending)
+        fputs (task-&gt;composeF4 ().c_str (), mLocations[0].pending);
+    }
+
+    // Update the undo log.
+    if (fseek (mLocations[0].undo, 0, SEEK_END) == 0)
+    {
+      foreach (task, mPending)
+        foreach (mtask, mModified)
+          if (task-&gt;id == mtask-&gt;id)
+            writeUndo (*task, *mtask, mLocations[0].undo);
+
+      foreach (task, mNew)
+        writeUndo (*task, mLocations[0].undo);
+    }
+
+    mPending = allPending;
+
+    mModified.clear ();
+    mNew.clear ();
   }
 
-  return writeCompleted (task);
+  return quantity;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-bool TDB::modifyT (const T&amp; t)
+// Scans the pending tasks for any that are completed or deleted, and if so,
+// moves them to the completed.data file.  Returns a count of tasks moved.
+// Now reverts expired waiting tasks to pending.
+int TDB::gc ()
 {
-  T modified (t);
+  Timer t (&quot;TDB::gc&quot;);
+
+  int count = 0;
+  Date now;
+
+  // Set up a second TDB.
+  Filter filter;
+  TDB tdb;
+  tdb.location (mLocations[0].path);
+  tdb.lock ();
 
-  std::vector &lt;T&gt; all;
-  allPendingT (all);
+  std::vector &lt;Task&gt; pending;
+  tdb.loadPending (pending, filter);
 
-  std::vector &lt;T&gt; pending;
+  std::vector &lt;Task&gt; completed;
+  tdb.loadCompleted (completed, filter);
 
-  std::vector &lt;T&gt;::iterator it;
-  for (it = all.begin (); it != all.end (); ++it)
+  // Now move completed and deleted tasks from the pending list to the
+  // completed list.  Isn't garbage collection easy?
+  std::vector &lt;Task&gt; still_pending;
+  foreach (task, pending)
   {
-    if (it-&gt;getId () == t.getId ())
+    std::string st = task-&gt;get (&quot;status&quot;);
+    Task::status s = task-&gt;getStatus ();
+    if (s == Task::completed ||
+        s == Task::deleted)
     {
-      modified.setUUID (it-&gt;getUUID ());
-      pending.push_back (modified);
+      completed.push_back (*task);
+      ++count;
+    }
+    else if (s == Task::waiting)
+    {
+      // Wake up tasks that are waiting.
+      Date wait_date (::atoi (task-&gt;get (&quot;wait&quot;).c_str ()));
+      if (now &gt; wait_date)
+      {
+        task-&gt;setStatus (Task::pending);
+        task-&gt;remove (&quot;wait&quot;);
+        ++count;
+      }
+
+      still_pending.push_back (*task);
     }
     else
-      pending.push_back (*it);
+      still_pending.push_back (*task);
   }
 
-  return overwritePending (pending);
+  pending = still_pending;
+
+  // No commit - all updates performed manually.
+  if (count &gt; 0)
+  {
+    if (fseek (tdb.mLocations[0].pending, 0, SEEK_SET) == 0)
+    {
+      if (ftruncate (fileno (tdb.mLocations[0].pending), 0))
+        throw std::string (&quot;Failed to truncate pending.data file &quot;);
+
+      foreach (task, pending)
+        fputs (task-&gt;composeF4 ().c_str (), tdb.mLocations[0].pending);
+    }
+
+    if (fseek (tdb.mLocations[0].completed, 0, SEEK_SET) == 0)
+    {
+      if (ftruncate (fileno (tdb.mLocations[0].completed), 0))
+        throw std::string (&quot;Failed to truncate completed.data file &quot;);
+
+      foreach (task, completed)
+        fputs (task-&gt;composeF4 ().c_str (), tdb.mLocations[0].completed);
+    }
+  }
+
+  // Close files.
+  tdb.unlock ();
+
+  std::stringstream s;
+  s &lt;&lt; &quot;gc &quot; &lt;&lt; count &lt;&lt; &quot; tasks&quot;;
+  context.debug (s.str ());
+  return count;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-bool TDB::lock (FILE* file) const
+int TDB::nextId ()
 {
-  if (mNoLock)
-    return true;
-
-  return flock (fileno (file), LOCK_EX) ? false : true;
+  return mId++;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-bool TDB::overwritePending (std::vector &lt;T&gt;&amp; all)
+void TDB::undo ()
 {
-  // Write a single task to the pending file
-  FILE* out;
-  if ((out = fopen (mPendingFile.c_str (), &quot;w&quot;)))
-  {
-    int retry = 0;
-    if (!mNoLock)
-      while (flock (fileno (out), LOCK_EX) &amp;&amp; ++retry &lt;= 3)
-        delay (0.1);
+  std::string location = expandPath (context.config.get (&quot;data.location&quot;));
 
-    std::vector &lt;T&gt;::iterator it;
-    for (it = all.begin (); it != all.end (); ++it)
-      fputs (it-&gt;compose ().c_str (), out);
+  std::string undoFile      = location + &quot;/undo.data&quot;;
+  std::string pendingFile   = location + &quot;/pending.data&quot;;
+  std::string completedFile = location + &quot;/completed.data&quot;;
 
-    fclose (out);
-    return true;
-  }
+  // load undo.data
+  std::vector &lt;std::string&gt; u;
+  slurp (undoFile, u);
 
-  return false;
-}
+  if (u.size () &lt; 3)
+    throw std::string (&quot;There are no recorded transactions to undo.&quot;);
 
-////////////////////////////////////////////////////////////////////////////////
-bool TDB::writePending (const T&amp; t)
-{
-  // Write a single task to the pending file
-  FILE* out;
-  if ((out = fopen (mPendingFile.c_str (), &quot;a&quot;)))
-  {
-    int retry = 0;
-    if (!mNoLock)
-      while (flock (fileno (out), LOCK_EX) &amp;&amp; ++retry &lt;= 3)
-        delay (0.1);
+  // pop last tx
+  u.pop_back (); // separator.
 
-    fputs (t.compose ().c_str (), out);
+  std::string current = u.back ().substr (4, std::string::npos);
+  u.pop_back ();
 
-    fclose (out);
-    return true;
+  std::string prior;
+  std::string when;
+  if (u.back ().substr (0, 5) == &quot;time &quot;)
+  {
+    when = u.back ().substr (5, std::string::npos);
+    u.pop_back ();
+    prior = &quot;&quot;;
+  }
+  else
+  {
+    prior = u.back ().substr (4, std::string::npos);
+    u.pop_back ();
+    when = u.back ().substr (5, std::string::npos);
+    u.pop_back ();
   }
 
-  return false;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-bool TDB::writeCompleted (const T&amp; t)
-{
-  // Write a single task to the pending file
-  FILE* out;
-  if ((out = fopen (mCompletedFile.c_str (), &quot;a&quot;)))
+  Date lastChange (::atoi (when.c_str ()));
+  std::cout &lt;&lt; std::endl
+            &lt;&lt; &quot;The last modification was made &quot;
+            &lt;&lt; lastChange.toString ()
+            &lt;&lt; std::endl;
+
+  // Attributes are all there is, so figure the different attribute names
+  // between before and after.
+  Table table;
+  table.setTableWidth (context.getWidth ());
+  table.setTableIntraPadding (2);
+  table.addColumn (&quot; &quot;);
+  table.addColumn (&quot;Prior Values&quot;);
+  table.addColumn (&quot;Current Values&quot;);
+  table.setColumnUnderline (1);
+  table.setColumnUnderline (2);
+  table.setColumnWidth (0, Table::minimum);
+  table.setColumnWidth (1, Table::flexible);
+  table.setColumnWidth (2, Table::flexible);
+
+  Task after (current);
+
+  if (prior != &quot;&quot;)
   {
-    int retry = 0;
-    if (!mNoLock)
-      while (flock (fileno (out), LOCK_EX) &amp;&amp; ++retry &lt;= 3)
-        delay (0.1);
+    Task before (prior);
 
-    fputs (t.compose ().c_str (), out);
+    std::vector &lt;std::string&gt; beforeAtts;
+    foreach (att, before)
+      beforeAtts.push_back (att-&gt;first);
 
-    fclose (out);
-    return true;
-  }
+    std::vector &lt;std::string&gt; afterAtts;
+    foreach (att, after)
+      afterAtts.push_back (att-&gt;first);
 
-  return false;
-}
+    std::vector &lt;std::string&gt; beforeOnly;
+    std::vector &lt;std::string&gt; afterOnly;
+    listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly);
 
-////////////////////////////////////////////////////////////////////////////////
-bool TDB::readLockedFile (
-  const std::string&amp; file,
-  std::vector &lt;std::string&gt;&amp; contents) const
-{
-  contents.clear ();
+    int row;
+    foreach (name, beforeOnly)
+    {
+      row = table.addRow ();
+      table.addCell (row, 0, *name);
+      table.addCell (row, 1, renderAttribute (*name, before.get (*name)));
+      table.setCellFg (row, 1, Text::red);
+    }
 
-  if (! access (file.c_str (), F_OK | R_OK))
-  {
-    FILE* in;
-    if ((in = fopen (file.c_str (), &quot;r&quot;)))
+    foreach (name, before)
     {
-      int retry = 0;
-      if (!mNoLock)
-        while (flock (fileno (in), LOCK_EX) &amp;&amp; ++retry &lt;= 3)
-          delay (0.1);
+      std::string priorValue   = before.get (name-&gt;first);
+      std::string currentValue = after.get  (name-&gt;first);
 
-      char line[T_LINE_MAX];
-      while (fgets (line, T_LINE_MAX, in))
+      if (currentValue != &quot;&quot;)
       {
-        int length = ::strlen (line);
-        if (length &gt; 1)
+        row = table.addRow ();
+        table.addCell (row, 0, name-&gt;first);
+        table.addCell (row, 1, renderAttribute (name-&gt;first, priorValue));
+        table.addCell (row, 2, renderAttribute (name-&gt;first, currentValue));
+
+        if (priorValue != currentValue)
         {
-          line[length - 1] = '\0'; // Kill \n
-          contents.push_back (line);
+          table.setCellFg (row, 1, Text::red);
+          table.setCellFg (row, 2, Text::green);
         }
       }
+    }
 
-      fclose (in);
-      return true;
+    foreach (name, afterOnly)
+    {
+      row = table.addRow ();
+      table.addCell (row, 0, *name);
+      table.addCell (row, 2, renderAttribute (*name, after.get (*name)));
+      table.setCellFg (row, 2, Text::green);
+    }
+  }
+  else
+  {
+    int row;
+    foreach (name, after)
+    {
+      row = table.addRow ();
+      table.addCell (row, 0, name-&gt;first);
+      table.addCell (row, 2, renderAttribute (name-&gt;first, after.get (name-&gt;first)));
+      table.setCellFg (row, 2, Text::green);
     }
   }
 
-  return false;
-}
+  // Confirm.
+  std::cout &lt;&lt; std::endl
+            &lt;&lt; table.render ()
+            &lt;&lt; std::endl;
 
-////////////////////////////////////////////////////////////////////////////////
-// Scans the pending tasks for any that are completed or deleted, and if so,
-// moves them to the completed.data file.  Returns a count of tasks moved.
-int TDB::gc ()
-{
-  int count = 0;
+  if (!confirm (&quot;The undo command is not reversible.  Are you sure you want to undo the last update?&quot;))
+    throw std::string (&quot;No changes made.&quot;);
 
-  // Read everything from the pending file.
-  std::vector &lt;T&gt; all;
-  allPendingT (all);
+  // Extract identifying uuid.
+  std::string uuid;
+  std::string::size_type uuidAtt = current.find (&quot;uuid:\&quot;&quot;);
+  if (uuidAtt != std::string::npos)
+    uuid = current.substr (uuidAtt, 43); // 43 = uuid:&quot;...&quot;
+  else
+    throw std::string (&quot;Cannot locate UUID in task to undo.&quot;);
 
-  // A list of the truly pending tasks.
-  std::vector &lt;T&gt; pending;
+  // load pending.data
+  std::vector &lt;std::string&gt; p;
+  slurp (pendingFile, p);
 
-  std::vector&lt;T&gt;::iterator it;
-  for (it = all.begin (); it != all.end (); ++it)
+  // is 'current' in pending?
+  foreach (task, p)
   {
-    // Some tasks stay in the pending file.
-    if (it-&gt;getStatus () == T::pending ||
-        it-&gt;getStatus () == T::recurring)
+    if (task-&gt;find (uuid) != std::string::npos)
     {
-      pending.push_back (*it);
+      context.debug (&quot;TDB::undo - task found in pending.data&quot;);
+
+      // Either revert if there was a prior state, or remove the task.
+      if (prior != &quot;&quot;)
+      {
+        *task = prior;
+        std::cout &lt;&lt; &quot;Modified task reverted.&quot; &lt;&lt; std::endl;
+      }
+      else
+      {
+        p.erase (task);
+        std::cout &lt;&lt; &quot;Task removed.&quot; &lt;&lt; std::endl;
+      }
+
+      // Rewrite files.
+      spit (pendingFile, p);
+      spit (undoFile, u);
+      return;
     }
+  }
 
-    // Others are transferred to the completed file.
-    else
+  // load completed.data
+  std::vector &lt;std::string&gt; c;
+  slurp (completedFile, c);
+
+  // is 'current' in completed?
+  foreach (task, c)
+  {
+    if (task-&gt;find (uuid) != std::string::npos)
     {
-      writeCompleted (*it);
-      ++count;
+      context.debug (&quot;TDB::undo - task found in completed.data&quot;);
+
+      // If task now belongs back in pending.data
+      if (prior.find (&quot;status:\&quot;pending\&quot;&quot;)   != std::string::npos ||
+          prior.find (&quot;status:\&quot;waiting\&quot;&quot;)   != std::string::npos ||
+          prior.find (&quot;status:\&quot;recurring\&quot;&quot;) != std::string::npos)
+      {
+        c.erase (task);
+        p.push_back (prior);
+        spit (completedFile, c);
+        spit (pendingFile, p);
+        spit (undoFile, u);
+        std::cout &lt;&lt; &quot;Modified task reverted.&quot; &lt;&lt; std::endl;
+        context.debug (&quot;TDB::undo - task belongs in pending.data&quot;);
+      }
+      else
+      {
+        *task = prior;
+        spit (completedFile, c);
+        spit (undoFile, u);
+        std::cout &lt;&lt; &quot;Modified task reverted.&quot; &lt;&lt; std::endl;
+        context.debug (&quot;TDB::undo - task belongs in completed.data&quot;);
+      }
+
+      std::cout &lt;&lt; &quot;Undo complete.&quot; &lt;&lt; std::endl;
+      return;
     }
   }
 
-  // Dump all clean tasks into pending.  But don't bother unless at least one
-  // task was transferred.
-  if (count)
-    overwritePending (pending);
-
-  return count;
+  // Perhaps user hand-edited the data files?
+  // Perhaps the task was in completed.data, which was still in file format 3?
+  std::cout &lt;&lt; &quot;Task with UUID &quot;
+            &lt;&lt; uuid.substr (6, 36)
+            &lt;&lt; &quot; not found in data.&quot;
+            &lt;&lt; std::endl
+            &lt;&lt; &quot;No undo possible.&quot;
+            &lt;&lt; std::endl;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-int TDB::nextId ()
+FILE* TDB::openAndLock (const std::string&amp; file)
 {
-  return mId++;
+  // TODO Need provision here for read-only locations.
+
+  // Check for access.
+  bool exists = access (file.c_str (), F_OK) ? false : true;
+  if (exists)
+    if (access (file.c_str (), R_OK | W_OK))
+      throw std::string (&quot;Task does not have the correct permissions for '&quot;) +
+            file + &quot;'.&quot;;
+
+  // Open the file.
+  FILE* in = fopen (file.c_str (), (exists ? &quot;r+&quot; : &quot;w+&quot;));
+  if (!in)
+    throw std::string (&quot;Could not open '&quot;) + file + &quot;'.&quot;;
+
+  // Lock if desired.  Try three times before failing.
+  int retry = 0;
+  if (mLock)
+    while (flock (fileno (in), LOCK_NB | LOCK_EX) &amp;&amp; ++retry &lt;= 3)
+    {
+      std::cout &lt;&lt; &quot;Waiting for file lock...&quot; &lt;&lt; std::endl;
+      while (flock (fileno (in), LOCK_EX) &amp;&amp; ++retry &lt;= 3)
+        delay (0.2);
+    }
+
+  if (retry &gt; 3)
+    throw std::string (&quot;Could not lock '&quot;) + file + &quot;'.&quot;;
+
+  return in;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-void TDB::noLock ()
+void TDB::writeUndo (const Task&amp; after, FILE* file)
 {
-  mNoLock = true;
+  Timer t (&quot;TDB::writeUndo&quot;);
+
+  fprintf (file,
+           &quot;time %u\nnew %s---\n&quot;,
+           (unsigned int) time (NULL),
+           after.composeF4 ().c_str ());
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+void TDB::writeUndo (const Task&amp; before, const Task&amp; after, FILE* file)
+{
+  Timer t (&quot;TDB::writeUndo&quot;);
 
+  fprintf (file,
+           &quot;time %u\nold %snew %s---\n&quot;,
+           (unsigned int) time (NULL),
+           before.composeF4 ().c_str (),
+           after.composeF4 ().c_str ());
+}
+
+////////////////////////////////////////////////////////////////////////////////</diff>
      <filename>src/TDB.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -27,41 +27,59 @@
 #ifndef INCLUDED_TDB
 #define INCLUDED_TDB
 
+#include &lt;map&gt;
 #include &lt;vector&gt;
 #include &lt;string&gt;
-#include &quot;T.h&quot;
+#include &quot;Location.h&quot;
+#include &quot;Filter.h&quot;
+#include &quot;Task.h&quot;
+
+// Length of longest line.
+#define T_LINE_MAX 32768
 
 class TDB
 {
 public:
-  TDB ();
-  ~TDB ();
+  TDB ();  // Default constructor
+  ~TDB (); // Destructor
+
+  TDB (const TDB&amp;);
+  TDB&amp; operator= (const TDB&amp;);
+
+  void  clear ();
+  void  location (const std::string&amp;);
+
+  void  lock (bool lockFile = true);
+  void  unlock ();
 
-  void dataDirectory    (const std::string&amp;);
-  bool allT             (std::vector &lt;T&gt;&amp;);
-  bool pendingT         (std::vector &lt;T&gt;&amp;);
-  bool allPendingT      (std::vector &lt;T&gt;&amp;);
-  bool completedT       (std::vector &lt;T&gt;&amp;) const;
-  bool allCompletedT    (std::vector &lt;T&gt;&amp;) const;
-  bool addT             (const T&amp;);
-  bool modifyT          (const T&amp;);
-  int gc                ();
-  int nextId            ();
+  int   load (std::vector &lt;Task&gt;&amp;, Filter&amp;);
+  int   loadPending (std::vector &lt;Task&gt;&amp;, Filter&amp;);
+  int   loadCompleted (std::vector &lt;Task&gt;&amp;, Filter&amp;);
 
-  void noLock           ();
+  void  add (const Task&amp;);    // Single task add to pending
+  void  update (const Task&amp;); // Single task update to pending
+  int   commit ();            // Write out all tasks
+  int   gc ();                // Clean up pending
+  int   nextId ();
+  void  undo ();
 
 private:
-  bool lock             (FILE*) const;
-  bool overwritePending (std::vector &lt;T&gt;&amp;);
-  bool writePending     (const T&amp;);
-  bool writeCompleted   (const T&amp;);
-  bool readLockedFile   (const std::string&amp;, std::vector &lt;std::string&gt;&amp;) const;
+  FILE* openAndLock (const std::string&amp;);
+  void writeUndo (const Task&amp;, FILE*);
+  void writeUndo (const Task&amp;, const Task&amp;, FILE*);
 
 private:
-  std::string mPendingFile;
-  std::string mCompletedFile;
+  std::vector &lt;Location&gt; mLocations;
+  bool mLock;
+  bool mAllOpenAndLocked;
   int mId;
-  bool mNoLock;
+
+  std::vector &lt;Task&gt; mPending;   // Contents of pending.data
+
+  std::vector &lt;Task&gt; mNew;       // Uncommitted new tasks
+  std::vector &lt;Task&gt; mModified;  // Uncommitted modified tasks
+
+  // TODO Need cache of raw file contents to preserve comments.
 };
 
 #endif</diff>
      <filename>src/TDB.h</filename>
    </modified>
    <modified>
      <diff>@@ -46,9 +46,12 @@
 #include &lt;iostream&gt;
 #include &lt;string.h&gt;
 #include &lt;assert.h&gt;
-#include &lt;Table.h&gt;
-#include &lt;Date.h&gt;
-#include &lt;task.h&gt;
+#include &quot;Table.h&quot;
+#include &quot;Date.h&quot;
+#include &quot;Duration.h&quot;
+#include &quot;Timer.h&quot;
+#include &quot;text.h&quot;
+#include &quot;util.h&quot;
 
 ////////////////////////////////////////////////////////////////////////////////
 Table::Table ()
@@ -114,11 +117,11 @@ void Table::setTableDashedUnderline ()
 int Table::addColumn (const std::string&amp; col)
 {
   mSpecifiedWidth.push_back (minimum);
-  mMaxDataWidth.push_back (col.length ());
+  mMaxDataWidth.push_back (col == &quot;&quot; ? 1 : col.length ());
   mCalculatedWidth.push_back (0);
   mColumnPadding.push_back (0);
 
-  mColumns.push_back (col);
+  mColumns.push_back (col == &quot;&quot; ? &quot; &quot; : col);
   return mColumns.size () - 1;
 }
 
@@ -989,7 +992,7 @@ void Table::sort (std::vector &lt;int&gt;&amp; order)
               break;
             else if ((std::string)*left != &quot;&quot; &amp;&amp; (std::string)*right == &quot;&quot;)
               SWAP
-            else if (convertDuration ((std::string)*left) &gt; convertDuration ((std::string)*right))
+            else if (Duration ((std::string)*left) &gt; Duration ((std::string)*right))
               SWAP
             break;
 
@@ -998,7 +1001,7 @@ void Table::sort (std::vector &lt;int&gt;&amp; order)
               break;
             else if ((std::string)*left == &quot;&quot; &amp;&amp; (std::string)*right != &quot;&quot;)
               SWAP
-            else if (convertDuration ((std::string)*left) &lt; convertDuration ((std::string)*right))
+            else if (Duration ((std::string)*left) &lt; Duration ((std::string)*right))
               SWAP
             break;
           }
@@ -1038,6 +1041,8 @@ void Table::clean (std::string&amp; value)
 ////////////////////////////////////////////////////////////////////////////////
 const std::string Table::render (int maximum /* = 0 */)
 {
+  Timer t (&quot;Table::render&quot;);
+
   calculateColumnWidths ();
 
   // Print column headers in column order.</diff>
      <filename>src/Table.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -52,6 +52,9 @@ public:
            Table ();
   virtual ~Table ();
 
+           Table (const Table&amp;);
+           Table&amp; operator= (const Table&amp;);
+
            void setTableColor (Text::color, Text::color);
            void setTableFg (Text::color);
            void setTableBg (Text::color);</diff>
      <filename>src/Table.h</filename>
    </modified>
    <modified>
      <diff>@@ -26,7 +26,11 @@
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;iostream&gt;
 #include &lt;iomanip&gt;
-#include &lt;Timer.h&gt;
+#include &lt;sstream&gt;
+#include &quot;Timer.h&quot;
+#include &quot;Context.h&quot;
+
+extern Context context;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Timer starts when the object is constructed.
@@ -37,19 +41,23 @@ Timer::Timer (const std::string&amp; description)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Timer stops when the object is desctructed.
+// Timer stops when the object is destructed.
 Timer::~Timer ()
 {
   struct timeval end;
   ::gettimeofday (&amp;end, NULL);
 
-  std::cout &lt;&lt; &quot;Timer &quot;
-            &lt;&lt; mDescription
-            &lt;&lt; &quot; &quot;
-            &lt;&lt; std::setprecision (6)
-            &lt;&lt; ((end.tv_sec - mStart.tv_sec) +
-               ((end.tv_usec - mStart.tv_usec ) / 1000000.0))
-            &lt;&lt; std::endl;
+  std::stringstream s;
+  s &lt;&lt; &quot;Timer &quot; // No i18n
+    &lt;&lt; mDescription
+    &lt;&lt; &quot; &quot;
+    &lt;&lt; std::setprecision (6)
+    &lt;&lt; std::fixed
+    &lt;&lt; ((end.tv_sec - mStart.tv_sec) + ((end.tv_usec - mStart.tv_usec )
+       / 1000000.0))
+    &lt;&lt; &quot; sec&quot;;
+
+  context.debug (s.str ());
 }
 
 ////////////////////////////////////////////////////////////////////////////////</diff>
      <filename>src/Timer.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -35,6 +35,8 @@ class Timer
 public:
   Timer (const std::string&amp;);
   ~Timer ();
+  Timer (const Timer&amp;);
+  Timer&amp; operator= (const Timer&amp;);
 
 private:
   std::string mDescription;</diff>
      <filename>src/Timer.h</filename>
    </modified>
    <modified>
      <diff>@@ -25,144 +25,108 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;string&gt;
+#include &quot;Context.h&quot;
+#include &quot;text.h&quot;
+#include &quot;util.h&quot;
+#include &quot;i18n.h&quot;
 #include &quot;color.h&quot;
 
+extern Context context;
+
 ////////////////////////////////////////////////////////////////////////////////
 namespace Text
 {
 
-std::string colorName (color c)
+static struct
 {
-  switch (c)
-  {
-  case nocolor:                 return &quot;&quot;;
-  case off:                     return &quot;off&quot;;
-
-  case bold:                    return &quot;bold&quot;;
-  case underline:               return &quot;underline&quot;;
-  case bold_underline:          return &quot;bold_underline&quot;;
-
-  case black:                   return &quot;black&quot;;
-  case red:                     return &quot;red&quot;;
-  case green:                   return &quot;green&quot;;
-  case yellow:                  return &quot;yellow&quot;;
-  case blue:                    return &quot;blue&quot;;
-  case magenta:                 return &quot;magenta&quot;;
-  case cyan:                    return &quot;cyan&quot;;
-  case white:                   return &quot;white&quot;;
-
-  case bold_black:              return &quot;bold_black&quot;;
-  case bold_red:                return &quot;bold_red&quot;;
-  case bold_green:              return &quot;bold_green&quot;;
-  case bold_yellow:             return &quot;bold_yellow&quot;;
-  case bold_blue:               return &quot;bold_blue&quot;;
-  case bold_magenta:            return &quot;bold_magenta&quot;;
-  case bold_cyan:               return &quot;bold_cyan&quot;;
-  case bold_white:              return &quot;bold_white&quot;;
-
-  case underline_black:         return &quot;underline_black&quot;;
-  case underline_red:           return &quot;underline_red&quot;;
-  case underline_green:         return &quot;underline_green&quot;;
-  case underline_yellow:        return &quot;underline_yellow&quot;;
-  case underline_blue:          return &quot;underline_blue&quot;;
-  case underline_magenta:       return &quot;underline_magenta&quot;;
-  case underline_cyan:          return &quot;underline_cyan&quot;;
-  case underline_white:         return &quot;underline_white&quot;;
-
-  case bold_underline_black:    return &quot;bold_underline_black&quot;;
-  case bold_underline_red:      return &quot;bold_underline_red&quot;;
-  case bold_underline_green:    return &quot;bold_underline_green&quot;;
-  case bold_underline_yellow:   return &quot;bold_underline_yellow&quot;;
-  case bold_underline_blue:     return &quot;bold_underline_blue&quot;;
-  case bold_underline_magenta:  return &quot;bold_underline_magenta&quot;;
-  case bold_underline_cyan:     return &quot;bold_underline_cyan&quot;;
-  case bold_underline_white:    return &quot;bold_underline_white&quot;;
-
-  case on_black:                return &quot;on_black&quot;;
-  case on_red:                  return &quot;on_red&quot;;
-  case on_green:                return &quot;on_green&quot;;
-  case on_yellow:               return &quot;on_yellow&quot;;
-  case on_blue:                 return &quot;on_blue&quot;;
-  case on_magenta:              return &quot;on_magenta&quot;;
-  case on_cyan:                 return &quot;on_cyan&quot;;
-  case on_white:                return &quot;on_white&quot;;
-
-  case on_bright_black:         return &quot;on_bright_black&quot;;
-  case on_bright_red:           return &quot;on_bright_red&quot;;
-  case on_bright_green:         return &quot;on_bright_green&quot;;
-  case on_bright_yellow:        return &quot;on_bright_yellow&quot;;
-  case on_bright_blue:          return &quot;on_bright_blue&quot;;
-  case on_bright_magenta:       return &quot;on_bright_magenta&quot;;
-  case on_bright_cyan:          return &quot;on_bright_cyan&quot;;
-  case on_bright_white:         return &quot;on_bright_white&quot;;
+  color id;
+  int string_id;
+  std::string english_name;
+  std::string escape_sequence;
+} allColors[] =
+{
+//  Text::color             i18n.h                   English                   vt220?  xterm?
+  { nocolor,                0,                       &quot;&quot;,                       &quot;&quot;             },
+  { off,                    COLOR_OFF,               &quot;off&quot;,                    &quot;*[0m&quot;        },
+
+  { bold,                   COLOR_BOLD,              &quot;bold&quot;,                   &quot;\033[1m&quot;      },
+  { underline,              COLOR_UL,                &quot;underline&quot;,              &quot;\033[4m&quot;      },
+  { bold_underline,         COLOR_B_UL,              &quot;bold_underline&quot;,         &quot;\033[1;4m&quot;    },
+
+  { black,                  COLOR_BLACK,             &quot;black&quot;,                  &quot;\033[30m&quot;     },
+  { red,                    COLOR_RED,               &quot;red&quot;,                    &quot;\033[31m&quot;     },
+  { green,                  COLOR_GREEN,             &quot;green&quot;,                  &quot;\033[32m&quot;     },
+  { yellow,                 COLOR_YELLOW,            &quot;yellow&quot;,                 &quot;\033[33m&quot;     },
+  { blue,                   COLOR_BLUE,              &quot;blue&quot;,                   &quot;\033[34m&quot;     },
+  { magenta,                COLOR_MAGENTA,           &quot;magenta&quot;,                &quot;\033[35m&quot;     },
+  { cyan,                   COLOR_CYAN,              &quot;cyan&quot;,                   &quot;\033[36m&quot;     },
+  { white,                  COLOR_WHITE,             &quot;white&quot;,                  &quot;\033[37m&quot;     },
+
+  { bold_black,             COLOR_B_BLACK,           &quot;bold_black&quot;,             &quot;\033[90m&quot;     },
+  { bold_red,               COLOR_B_RED,             &quot;bold_red&quot;,               &quot;\033[91m&quot;     },
+  { bold_green,             COLOR_B_GREEN,           &quot;bold_green&quot;,             &quot;\033[92m&quot;     },
+  { bold_yellow,            COLOR_B_YELLOW,          &quot;bold_yellow&quot;,            &quot;\033[93m&quot;     },
+  { bold_blue,              COLOR_B_BLUE,            &quot;bold_blue&quot;,              &quot;\033[94m&quot;     },
+  { bold_magenta,           COLOR_B_MAGENTA,         &quot;bold_magenta&quot;,           &quot;\033[95m&quot;     },
+  { bold_cyan,              COLOR_B_CYAN,            &quot;bold_cyan&quot;,              &quot;\033[96m&quot;     },
+  { bold_white,             COLOR_B_WHITE,           &quot;bold_white&quot;,             &quot;\033[97m&quot;     },
+
+  { underline_black,        COLOR_UL_BLACK,          &quot;underline_black&quot;,        &quot;\033[4;30m&quot;   },
+  { underline_red,          COLOR_UL_RED,            &quot;underline_red&quot;,          &quot;\033[4;31m&quot;   },
+  { underline_green,        COLOR_UL_GREEN,          &quot;underline_green&quot;,        &quot;\033[4;32m&quot;   },
+  { underline_yellow,       COLOR_UL_YELLOW,         &quot;underline_yellow&quot;,       &quot;\033[4;33m&quot;   },
+  { underline_blue,         COLOR_UL_BLUE,           &quot;underline_blue&quot;,         &quot;\033[4;34m&quot;   },
+  { underline_magenta,      COLOR_UL_MAGENTA,        &quot;underline_magenta&quot;,      &quot;\033[4;35m&quot;   },
+  { underline_cyan,         COLOR_UL_CYAN,           &quot;underline_cyan&quot;,         &quot;\033[4;36m&quot;   },
+  { underline_white,        COLOR_UL_WHITE,          &quot;underline_white&quot;,        &quot;\033[4;37m&quot;   },
+
+  { bold_underline_black,   COLOR_B_UL_BLACK,        &quot;bold_underline_black&quot;,   &quot;\033[1;4;30m&quot; },
+  { bold_underline_red,     COLOR_B_UL_RED,          &quot;bold_underline_red&quot;,     &quot;\033[1;4;31m&quot; },
+  { bold_underline_green,   COLOR_B_UL_GREEN,        &quot;bold_underline_green&quot;,   &quot;\033[1;4;32m&quot; },
+  { bold_underline_yellow,  COLOR_B_UL_YELLOW,       &quot;bold_underline_yellow&quot;,  &quot;\033[1;4;33m&quot; },
+  { bold_underline_blue,    COLOR_B_UL_BLUE,         &quot;bold_underline_blue&quot;,    &quot;\033[1;4;34m&quot; },
+  { bold_underline_magenta, COLOR_B_UL_MAGENTA,      &quot;bold_underline_magenta&quot;, &quot;\033[1;4;35m&quot; },
+  { bold_underline_cyan,    COLOR_B_UL_CYAN,         &quot;bold_underline_cyan&quot;,    &quot;\033[1;4;36m&quot; },
+  { bold_underline_white,   COLOR_B_UL_WHITE,        &quot;bold_underline_white&quot;,   &quot;\033[1;4;37m&quot; },
+
+  { on_black,               COLOR_ON_BLACK,          &quot;on_black&quot;,               &quot;\033[40m&quot;     },
+  { on_red,                 COLOR_ON_RED,            &quot;on_red&quot;,                 &quot;\033[41m&quot;     },
+  { on_green,               COLOR_ON_GREEN,          &quot;on_green&quot;,               &quot;\033[42m&quot;     },
+  { on_yellow,              COLOR_ON_YELLOW,         &quot;on_yellow&quot;,              &quot;\033[43m&quot;     },
+  { on_blue,                COLOR_ON_BLUE,           &quot;on_blue&quot;,                &quot;\033[44m&quot;     },
+  { on_magenta,             COLOR_ON_MAGENTA,        &quot;on_magenta&quot;,             &quot;\033[45m&quot;     },
+  { on_cyan,                COLOR_ON_CYAN,           &quot;on_cyan&quot;,                &quot;\033[46m&quot;     },
+  { on_white,               COLOR_ON_WHITE,          &quot;on_white&quot;,               &quot;\033[47m&quot;     },
+
+  { on_bright_black,        COLOR_ON_BRIGHT_BLACK,   &quot;on_bright_black&quot;,        &quot;\033[100m&quot;    },
+  { on_bright_red,          COLOR_ON_BRIGHT_RED,     &quot;on_bright_red&quot;,          &quot;\033[101m&quot;    },
+  { on_bright_green,        COLOR_ON_BRIGHT_GREEN,   &quot;on_bright_green&quot;,        &quot;\033[102m&quot;    },
+  { on_bright_yellow,       COLOR_ON_BRIGHT_YELLOW,  &quot;on_bright_yellow&quot;,       &quot;\033[103m&quot;    },
+  { on_bright_blue,         COLOR_ON_BRIGHT_BLUE,    &quot;on_bright_blue&quot;,         &quot;\033[104m&quot;    },
+  { on_bright_magenta,      COLOR_ON_BRIGHT_MAGENTA, &quot;on_bright_magenta&quot;,      &quot;\033[105m&quot;    },
+  { on_bright_cyan,         COLOR_ON_BRIGHT_CYAN,    &quot;on_bright_cyan&quot;,         &quot;\033[106m&quot;    },
+  { on_bright_white,        COLOR_ON_BRIGHT_WHITE,   &quot;on_bright_white&quot;,        &quot;\033[107m&quot;    },
+};
+
+#define NUM_COLORS (sizeof (allColors) / sizeof (allColors[0]))
 
-  default: throw &quot;Unknown Text::color value&quot;;
-  }
+////////////////////////////////////////////////////////////////////////////////
+std::string colorName (color c)
+{
+  for (unsigned int i = 0; i &lt; NUM_COLORS; ++i)
+    if (allColors[i].id == c)
+      return allColors[i].english_name;
 
+  throw context.stringtable.get (COLOR_UNKNOWN, &quot;Unknown color value&quot;);
   return &quot;&quot;;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 color colorCode (const std::string&amp; c)
 {
-       if (c == &quot;off&quot;)                     return off;
-  else if (c == &quot;bold&quot;)                    return bold;
-  else if (c == &quot;underline&quot;)               return underline;
-  else if (c == &quot;bold_underline&quot;)          return bold_underline;
-
-  else if (c == &quot;black&quot;)                   return black;
-  else if (c == &quot;red&quot;)                     return red;
-  else if (c == &quot;green&quot;)                   return green;
-  else if (c == &quot;yellow&quot;)                  return yellow;
-  else if (c == &quot;blue&quot;)                    return blue;
-  else if (c == &quot;magenta&quot;)                 return magenta;
-  else if (c == &quot;cyan&quot;)                    return cyan;
-  else if (c == &quot;white&quot;)                   return white;
-
-  else if (c == &quot;bold_black&quot;)              return bold_black;
-  else if (c == &quot;bold_red&quot;)                return bold_red;
-  else if (c == &quot;bold_green&quot;)              return bold_green;
-  else if (c == &quot;bold_yellow&quot;)             return bold_yellow;
-  else if (c == &quot;bold_blue&quot;)               return bold_blue;
-  else if (c == &quot;bold_magenta&quot;)            return bold_magenta;
-  else if (c == &quot;bold_cyan&quot;)               return bold_cyan;
-  else if (c == &quot;bold_white&quot;)              return bold_white;
-
-  else if (c == &quot;underline_black&quot;)         return underline_black;
-  else if (c == &quot;underline_red&quot;)           return underline_red;
-  else if (c == &quot;underline_green&quot;)         return underline_green;
-  else if (c == &quot;underline_yellow&quot;)        return underline_yellow;
-  else if (c == &quot;underline_blue&quot;)          return underline_blue;
-  else if (c == &quot;underline_magenta&quot;)       return underline_magenta;
-  else if (c == &quot;underline_cyan&quot;)          return underline_cyan;
-  else if (c == &quot;underline_white&quot;)         return underline_white;
-
-  else if (c == &quot;bold_underline_black&quot;)    return bold_underline_black;
-  else if (c == &quot;bold_underline_red&quot;)      return bold_underline_red;
-  else if (c == &quot;bold_underline_green&quot;)    return bold_underline_green;
-  else if (c == &quot;bold_underline_yellow&quot;)   return bold_underline_yellow;
-  else if (c == &quot;bold_underline_blue&quot;)     return bold_underline_blue;
-  else if (c == &quot;bold_underline_magenta&quot;)  return bold_underline_magenta;
-  else if (c == &quot;bold_underline_cyan&quot;)     return bold_underline_cyan;
-  else if (c == &quot;bold_underline_white&quot;)    return bold_underline_white;
-
-  else if (c == &quot;on_black&quot;)                return on_black;
-  else if (c == &quot;on_red&quot;)                  return on_red;
-  else if (c == &quot;on_green&quot;)                return on_green;
-  else if (c == &quot;on_yellow&quot;)               return on_yellow;
-  else if (c == &quot;on_blue&quot;)                 return on_blue;
-  else if (c == &quot;on_magenta&quot;)              return on_magenta;
-  else if (c == &quot;on_cyan&quot;)                 return on_cyan;
-  else if (c == &quot;on_white&quot;)                return on_white;
-
-  else if (c == &quot;on_bright_black&quot;)         return on_bright_black;
-  else if (c == &quot;on_bright_red&quot;)           return on_bright_red;
-  else if (c == &quot;on_bright_green&quot;)         return on_bright_green;
-  else if (c == &quot;on_bright_yellow&quot;)        return on_bright_yellow;
-  else if (c == &quot;on_bright_blue&quot;)          return on_bright_blue;
-  else if (c == &quot;on_bright_magenta&quot;)       return on_bright_magenta;
-  else if (c == &quot;on_bright_cyan&quot;)          return on_bright_cyan;
-  else if (c == &quot;on_bright_white&quot;)         return on_bright_white;
+  for (unsigned int i = 0; i &lt; NUM_COLORS; ++i)
+    if (context.stringtable.get (allColors[i].string_id, allColors[i].english_name) == c)
+      return allColors[i].id;
 
   return nocolor;
 }
@@ -170,72 +134,11 @@ color colorCode (const std::string&amp; c)
 ////////////////////////////////////////////////////////////////////////////////
 std::string decode (color c)
 {
-  switch (c)
-  {
-  case nocolor:                 return &quot;&quot;;
-  case off:                     return &quot;\033[0m&quot;;
-
-  case bold:                    return &quot;\033[1m&quot;;
-  case underline:               return &quot;\033[4m&quot;;
-  case bold_underline:          return &quot;\033[1;4m&quot;;
-
-  case black:                   return &quot;\033[30m&quot;;
-  case red:                     return &quot;\033[31m&quot;;
-  case green:                   return &quot;\033[32m&quot;;
-  case yellow:                  return &quot;\033[33m&quot;;
-  case blue:                    return &quot;\033[34m&quot;;
-  case magenta:                 return &quot;\033[35m&quot;;
-  case cyan:                    return &quot;\033[36m&quot;;
-  case white:                   return &quot;\033[37m&quot;;
-
-  case bold_black:              return &quot;\033[90m&quot;;
-  case bold_red:                return &quot;\033[91m&quot;;
-  case bold_green:              return &quot;\033[92m&quot;;
-  case bold_yellow:             return &quot;\033[93m&quot;;
-  case bold_blue:               return &quot;\033[94m&quot;;
-  case bold_magenta:            return &quot;\033[95m&quot;;
-  case bold_cyan:               return &quot;\033[96m&quot;;
-  case bold_white:              return &quot;\033[97m&quot;;
-
-  case underline_black:         return &quot;\033[4;30m&quot;;
-  case underline_red:           return &quot;\033[4;31m&quot;;
-  case underline_green:         return &quot;\033[4;32m&quot;;
-  case underline_yellow:        return &quot;\033[4;33m&quot;;
-  case underline_blue:          return &quot;\033[4;34m&quot;;
-  case underline_magenta:       return &quot;\033[4;35m&quot;;
-  case underline_cyan:          return &quot;\033[4;36m&quot;;
-  case underline_white:         return &quot;\033[4;37m&quot;;
-
-  case bold_underline_black:    return &quot;\033[1;4;30m&quot;;
-  case bold_underline_red:      return &quot;\033[1;4;31m&quot;;
-  case bold_underline_green:    return &quot;\033[1;4;32m&quot;;
-  case bold_underline_yellow:   return &quot;\033[1;4;33m&quot;;
-  case bold_underline_blue:     return &quot;\033[1;4;34m&quot;;
-  case bold_underline_magenta:  return &quot;\033[1;4;35m&quot;;
-  case bold_underline_cyan:     return &quot;\033[1;4;36m&quot;;
-  case bold_underline_white:    return &quot;\033[1;4;37m&quot;;
-
-  case on_black:                return &quot;\033[40m&quot;;
-  case on_red:                  return &quot;\033[41m&quot;;
-  case on_green:                return &quot;\033[42m&quot;;
-  case on_yellow:               return &quot;\033[43m&quot;;
-  case on_blue:                 return &quot;\033[44m&quot;;
-  case on_magenta:              return &quot;\033[45m&quot;;
-  case on_cyan:                 return &quot;\033[46m&quot;;
-  case on_white:                return &quot;\033[47m&quot;;
-
-  case on_bright_black:         return &quot;\033[100m&quot;;
-  case on_bright_red:           return &quot;\033[101m&quot;;
-  case on_bright_green:         return &quot;\033[102m&quot;;
-  case on_bright_yellow:        return &quot;\033[103m&quot;;
-  case on_bright_blue:          return &quot;\033[104m&quot;;
-  case on_bright_magenta:       return &quot;\033[105m&quot;;
-  case on_bright_cyan:          return &quot;\033[106m&quot;;
-  case on_bright_white:         return &quot;\033[107m&quot;;
-
-  default: throw &quot;Unknown Text::color value&quot;;
-  }
+  for (unsigned int i = 0; i &lt; NUM_COLORS; ++i)
+    if (allColors[i].id == c)
+      return allColors[i].escape_sequence;
 
+  throw context.stringtable.get (COLOR_UNKNOWN, &quot;Unknown color value&quot;);
   return &quot;&quot;;
 }
 
@@ -262,4 +165,32 @@ std::string colorize ()
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+std::string guessColor (const std::string&amp; name)
+{
+  std::vector &lt;std::string&gt; all;
+  for (unsigned int i = 0; i &lt; NUM_COLORS; ++i)
+    all.push_back (context.stringtable.get (
+                     allColors[i].string_id,
+                     allColors[i].english_name));
+
+  std::vector &lt;std::string&gt; matches;
+  autoComplete (name, all, matches);
+
+  if (matches.size () == 0)
+    throw std::string (&quot;Unrecognized color '&quot;) + name + &quot;'&quot;;
+
+  else if (matches.size () != 1)
+  {
+    std::string error = &quot;Ambiguous color '&quot; + name + &quot;' - could be either of &quot;; // TODO i18n
+
+    std::string combined;
+    join (combined, &quot;, &quot;, matches);
+
+    throw error + combined;
+  }
+
+  return matches[0];
+}
+
+////////////////////////////////////////////////////////////////////////////////
 }</diff>
      <filename>src/color.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -50,6 +50,7 @@ namespace Text
   std::string colorize (color, color, const std::string&amp; string);
   std::string colorize (color, color);
   std::string colorize ();
+  std::string guessColor (const std::string&amp;);
 }
 
 #endif</diff>
      <filename>src/color.h</filename>
    </modified>
    <modified>
      <diff>@@ -24,83 +24,120 @@
 //     USA
 //
 ////////////////////////////////////////////////////////////////////////////////
+
 #include &lt;iostream&gt;
 #include &lt;iomanip&gt;
 #include &lt;sstream&gt;
 #include &lt;fstream&gt;
+#include &lt;algorithm&gt;
 #include &lt;stdio.h&gt;
 #include &lt;unistd.h&gt;
 #include &lt;stdlib.h&gt;
 #include &lt;pwd.h&gt;
 #include &lt;time.h&gt;
 
-#include &quot;task.h&quot;
+#include &quot;Permission.h&quot;
+#include &quot;text.h&quot;
+#include &quot;util.h&quot;
+#include &quot;main.h&quot;
+#include &quot;../auto.h&quot;
 
 #ifdef HAVE_LIBNCURSES
 #include &lt;ncurses.h&gt;
 #endif
 
+extern Context context;
+
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleAdd (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleAdd ()
 {
   std::stringstream out;
 
-  char entryTime[16];
-  sprintf (entryTime, &quot;%u&quot;, (unsigned int) time (NULL));
-  task.setAttribute (&quot;entry&quot;, entryTime);
-
-  std::map &lt;std::string, std::string&gt; atts;
-  task.getAttributes (atts);
-  foreach (i, atts)
-    if (i-&gt;second == &quot;&quot;)
-      task.removeAttribute (i-&gt;first);
+  context.task.set (&quot;uuid&quot;, uuid ());
+  context.task.setEntry ();
 
   // Recurring tasks get a special status.
-  if (task.getAttribute (&quot;due&quot;)   != &quot;&quot; &amp;&amp;
-      task.getAttribute (&quot;recur&quot;) != &quot;&quot;)
+  if (context.task.has (&quot;due&quot;) &amp;&amp;
+      context.task.has (&quot;recur&quot;))
   {
-    task.setStatus (T::recurring);
-    task.setAttribute (&quot;mask&quot;, &quot;&quot;);
+    context.task.setStatus (Task::recurring);
+    context.task.set (&quot;mask&quot;, &quot;&quot;);
   }
+  else if (context.task.has (&quot;wait&quot;))
+    context.task.setStatus (Task::waiting);
+  else
+    context.task.setStatus (Task::pending);
 
   // Override with default.project, if not specified.
-  if (task.getAttribute (&quot;project&quot;) == &quot;&quot;)
-    task.setAttribute (&quot;project&quot;, conf.get (&quot;default.project&quot;, &quot;&quot;));
+  if (context.task.get (&quot;project&quot;) == &quot;&quot;)
+    context.task.set (&quot;project&quot;, context.config.get (&quot;default.project&quot;, &quot;&quot;));
 
   // Override with default.priority, if not specified.
-  if (task.getAttribute (&quot;priority&quot;) == &quot;&quot;)
+  if (context.task.get (&quot;priority&quot;) == &quot;&quot;)
   {
-    std::string defaultPriority = conf.get (&quot;default.priority&quot;, &quot;&quot;);
-    if (validPriority (defaultPriority))
-      task.setAttribute (&quot;priority&quot;, defaultPriority);
+    std::string defaultPriority = context.config.get (&quot;default.priority&quot;, &quot;&quot;);
+    if (Att::validNameValue (&quot;priority&quot;, &quot;&quot;, defaultPriority))
+      context.task.set (&quot;priority&quot;, defaultPriority);
   }
 
-  // Disallow blank descriptions.
-  if (task.getDescription () == &quot;&quot;)
-    throw std::string (&quot;Cannot add a task that is blank, or contains &lt;CR&gt; or &lt;LF&gt; characters.&quot;);
+  // Include tags.
+  foreach (tag, context.tagAdditions)
+    context.task.addTag (*tag);
+
+  // Only valid tasks can be added.
+  context.task.validate ();
 
-  if (!tdb.addT (task))
-    throw std::string (&quot;Could not create new task.&quot;);
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  context.tdb.add (context.task);
+
+#ifdef FEATURE_NEW_ID
+  // All this, just for an id number.
+  std::vector &lt;Task&gt; all;
+  Filter none;
+  context.tdb.loadPending (all, none);
+  out &lt;&lt; &quot;Created task &quot; &lt;&lt; context.tdb.nextId () &lt;&lt; std::endl;
+#endif
+
+  context.tdb.commit ();
+  context.tdb.unlock ();
 
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleProjects (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleProjects ()
 {
   std::stringstream out;
 
-  // Get all the tasks, including deleted ones.
-  std::vector &lt;T&gt; tasks;
-  tdb.pendingT (tasks);
+  context.filter.push_back (Att (&quot;status&quot;, &quot;pending&quot;));
+
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  int quantity = context.tdb.loadPending (tasks, context.filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
 
   // Scan all the tasks for their project name, building a map using project
   // names as keys.
   std::map &lt;std::string, int&gt; unique;
-  for (unsigned int i = 0; i &lt; tasks.size (); ++i)
+  std::map &lt;std::string, int&gt; high;
+  std::map &lt;std::string, int&gt; medium;
+  std::map &lt;std::string, int&gt; low;
+  std::map &lt;std::string, int&gt; none;
+  std::string project;
+  std::string priority;
+  foreach (t, tasks)
   {
-    T task (tasks[i]);
-    unique[task.getAttribute (&quot;project&quot;)] += 1;
+     project = t-&gt;get (&quot;project&quot;);
+    priority = t-&gt;get (&quot;priority&quot;);
+
+    unique[project] += 1;
+
+         if (priority == &quot;H&quot;) high[project]   += 1;
+    else if (priority == &quot;M&quot;) medium[project] += 1;
+    else if (priority == &quot;L&quot;) low[project]    += 1;
+    else                      none[project]   += 1;
   }
 
   if (unique.size ())
@@ -109,28 +146,45 @@ std::string handleProjects (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     Table table;
     table.addColumn (&quot;Project&quot;);
     table.addColumn (&quot;Tasks&quot;);
+    table.addColumn (&quot;Pri:None&quot;);
+    table.addColumn (&quot;Pri:L&quot;);
+    table.addColumn (&quot;Pri:M&quot;);
+    table.addColumn (&quot;Pri:H&quot;);
 
-    if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+    if (context.config.get (&quot;color&quot;, true) ||
+        context.config.get (std::string (&quot;_forcecolor&quot;), false))
     {
       table.setColumnUnderline (0);
       table.setColumnUnderline (1);
+      table.setColumnUnderline (2);
+      table.setColumnUnderline (3);
+      table.setColumnUnderline (4);
+      table.setColumnUnderline (5);
     }
 
     table.setColumnJustification (1, Table::right);
-    table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+    table.setColumnJustification (2, Table::right);
+    table.setColumnJustification (3, Table::right);
+    table.setColumnJustification (4, Table::right);
+    table.setColumnJustification (5, Table::right);
 
     foreach (i, unique)
     {
       int row = table.addRow ();
       table.addCell (row, 0, i-&gt;first);
       table.addCell (row, 1, i-&gt;second);
+      table.addCell (row, 2, none[i-&gt;first]);
+      table.addCell (row, 3, low[i-&gt;first]);
+      table.addCell (row, 4, medium[i-&gt;first]);
+      table.addCell (row, 5, high[i-&gt;first]);
     }
 
-    out &lt;&lt; optionalBlankLine (conf)
+    out &lt;&lt; optionalBlankLine ()
         &lt;&lt; table.render ()
-        &lt;&lt; optionalBlankLine (conf)
+        &lt;&lt; optionalBlankLine ()
         &lt;&lt; unique.size ()
         &lt;&lt; (unique.size () == 1 ? &quot; project&quot; : &quot; projects&quot;)
+        &lt;&lt; &quot; (&quot; &lt;&lt; quantity &lt;&lt; (quantity == 1 ? &quot; task&quot; : &quot; tasks&quot;) &lt;&lt; &quot;)&quot;
         &lt;&lt; std::endl;
   }
   else
@@ -141,38 +195,95 @@ std::string handleProjects (TDB&amp; tdb, T&amp; task, Config&amp; conf)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleTags (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleCompletionProjects ()
 {
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+
+  Filter filter;
+  if (context.config.get (std::string (&quot;complete.all.projects&quot;), false))
+    context.tdb.load (tasks, filter);
+  else
+    context.tdb.loadPending (tasks, filter);
+
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  // Scan all the tasks for their project name, building a map using project
+  // names as keys.
+  std::map &lt;std::string, int&gt; unique;
+  foreach (t, tasks)
+    unique[t-&gt;get (&quot;project&quot;)] = 0;
+
   std::stringstream out;
+  foreach (project, unique)
+    if (project-&gt;first.length ())
+      out &lt;&lt; project-&gt;first &lt;&lt; std::endl;
 
-  // Get all the tasks.
-  std::vector &lt;T&gt; tasks;
-  tdb.pendingT (tasks);
+  return out.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string handleTags ()
+{
+  std::stringstream out;
+
+  context.filter.push_back (Att (&quot;status&quot;, &quot;pending&quot;));
+
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  int quantity = context.tdb.loadPending (tasks, context.filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
 
   // Scan all the tasks for their project name, building a map using project
   // names as keys.
-  std::map &lt;std::string, std::string&gt; unique;
-  for (unsigned int i = 0; i &lt; tasks.size (); ++i)
+  std::map &lt;std::string, int&gt; unique;
+  foreach (t, tasks)
   {
-    T task (tasks[i]);
-
     std::vector &lt;std::string&gt; tags;
-    task.getTags (tags);
+    t-&gt;getTags (tags);
 
-    for (unsigned int t = 0; t &lt; tags.size (); ++t)
-      unique[tags[t]] = &quot;&quot;;
+    foreach (tag, tags)
+      if (unique.find (*tag) != unique.end ())
+        unique[*tag]++;
+      else
+        unique[*tag] = 1;
   }
 
-  // Render a list of tag names from the map.
-  std::cout &lt;&lt; optionalBlankLine (conf);
-  foreach (i, unique)
-    std::cout &lt;&lt; i-&gt;first &lt;&lt; std::endl;
-
   if (unique.size ())
-    out &lt;&lt; optionalBlankLine (conf)
+  {
+    // Render a list of tags names from the map.
+    Table table;
+    table.addColumn (&quot;Tag&quot;);
+    table.addColumn (&quot;Count&quot;);
+
+    if (context.config.get (&quot;color&quot;, true) ||
+        context.config.get (std::string (&quot;_forcecolor&quot;), false))
+    {
+      table.setColumnUnderline (0);
+      table.setColumnUnderline (1);
+    }
+
+    table.setColumnJustification (1, Table::right);
+
+    foreach (i, unique)
+    {
+      int row = table.addRow ();
+      table.addCell (row, 0, i-&gt;first);
+      table.addCell (row, 1, i-&gt;second);
+    }
+
+    out &lt;&lt; optionalBlankLine ()
+        &lt;&lt; table.render ()
+        &lt;&lt; optionalBlankLine ()
         &lt;&lt; unique.size ()
         &lt;&lt; (unique.size () == 1 ? &quot; tag&quot; : &quot; tags&quot;)
+        &lt;&lt; &quot; (&quot; &lt;&lt; quantity &lt;&lt; (quantity == 1 ? &quot; task&quot; : &quot; tasks&quot;) &lt;&lt; &quot;)&quot;
         &lt;&lt; std::endl;
+  }
   else
     out &lt;&lt; &quot;No tags.&quot;
         &lt;&lt; std::endl;
@@ -181,98 +292,109 @@ std::string handleTags (TDB&amp; tdb, T&amp; task, Config&amp; conf)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// If a task is deleted, but is still in the pending file, then it may be
-// undeleted simply by changing it's status.
-std::string handleUndelete (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleCompletionTags ()
 {
-  std::stringstream out;
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
 
-  std::vector &lt;T&gt; all;
-  tdb.allPendingT (all);
-  filterSequence (all, task);
+  Filter filter;
+  if (context.config.get (std::string (&quot;complete.all.tags&quot;), false))
+    context.tdb.load (tasks, filter);
+  else
+    context.tdb.loadPending (tasks, filter);
 
-  foreach (t, all)
-  {
-    if (t-&gt;getStatus () == T::deleted)
-    {
-      if (t-&gt;getAttribute (&quot;recur&quot;) != &quot;&quot;)
-        out &lt;&lt; &quot;Task does not support 'undo' for recurring tasks.\n&quot;;
+  context.tdb.commit ();
+  context.tdb.unlock ();
 
-      t-&gt;setStatus (T::pending);
-      t-&gt;removeAttribute (&quot;end&quot;);
-      tdb.modifyT (*t);
+  // Scan all the tasks for their project name, building a map using project
+  // names as keys.
+  std::map &lt;std::string, int&gt; unique;
+  foreach (t, tasks)
+  {
+    std::vector &lt;std::string&gt; tags;
+    t-&gt;getTags (tags);
 
-      out &lt;&lt; &quot;Task &quot; &lt;&lt; t-&gt;getId () &lt;&lt; &quot; '&quot; &lt;&lt; t-&gt;getDescription () &lt;&lt; &quot;' successfully undeleted.\n&quot;;
-    }
-    else
-    {
-      out &lt;&lt; &quot;Task &quot; &lt;&lt; t-&gt;getId () &lt;&lt; &quot; '&quot; &lt;&lt; t-&gt;getDescription () &lt;&lt; &quot;' is not deleted - therefore cannot be undeleted.\n&quot;;
-    }
+    foreach (tag, tags)
+      unique[*tag] = 0;
   }
 
-  out &lt;&lt; &quot;\n&quot;
-      &lt;&lt; &quot;Please note that tasks can only be reliably undeleted if the undelete &quot;
-      &lt;&lt; &quot;command is run immediately after the errant delete command.&quot;
-      &lt;&lt; std::endl;
+  std::stringstream out;
+  foreach (tag, unique)
+    out &lt;&lt; tag-&gt;first &lt;&lt; std::endl;
 
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// If a task is done, but is still in the pending file, then it may be undone
-// simply by changing it's status.
-std::string handleUndo (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleCompletionCommands ()
 {
+  std::vector &lt;std::string&gt; commands;
+  context.cmd.allCommands (commands);
+  std::sort (commands.begin (), commands.end ());
+
   std::stringstream out;
+  foreach (command, commands)
+    out &lt;&lt; *command &lt;&lt; std::endl;
 
-  std::vector &lt;T&gt; all;
-  tdb.allPendingT (all);
-  filterSequence (all, task);
+  return out.str ();
+}
 
-  foreach (t, all)
-  {
-    if (t-&gt;getStatus () == T::completed)
-    {
-      if (t-&gt;getAttribute (&quot;recur&quot;) != &quot;&quot;)
-        out &lt;&lt; &quot;Task does not support 'undo' for recurring tasks.\n&quot;;
+////////////////////////////////////////////////////////////////////////////////
+std::string handleCompletionConfig ()
+{
+  std::vector &lt;std::string&gt; configs;
+  context.config.all (configs);
+  std::sort (configs.begin (), configs.end ());
 
-      t-&gt;setStatus (T::pending);
-      t-&gt;removeAttribute (&quot;end&quot;);
-      tdb.modifyT (*t);
+  std::stringstream out;
+  foreach (config, configs)
+    out &lt;&lt; *config &lt;&lt; std::endl;
 
-      out &lt;&lt; &quot;Task &quot; &lt;&lt; t-&gt;getId () &lt;&lt; &quot; '&quot; &lt;&lt; t-&gt;getDescription () &lt;&lt; &quot;' successfully undone.&quot; &lt;&lt; std::endl;
-    }
-    else
-    {
-      out &lt;&lt; &quot;Task &quot; &lt;&lt; t-&gt;getId () &lt;&lt; &quot; '&quot; &lt;&lt; t-&gt;getDescription () &lt;&lt; &quot;' is not done - therefore cannot be undone.&quot; &lt;&lt; std::endl;
-    }
-  }
+  return out.str ();
+}
 
-  out &lt;&lt; std::endl
-      &lt;&lt; &quot;Please note that tasks can only be reliably undone if the undo &quot;
-      &lt;&lt; &quot;command is run immediately after the errant done command.&quot;
-      &lt;&lt; std::endl;
+////////////////////////////////////////////////////////////////////////////////
+std::string handleCompletionIDs ()
+{
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  Filter filter;
+  context.tdb.loadPending (tasks, filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  std::vector &lt;int&gt; ids;
+  foreach (task, tasks)
+    if (task-&gt;getStatus () != Task::deleted &amp;&amp;
+        task-&gt;getStatus () != Task::completed)
+      ids.push_back (task-&gt;id);
+
+  std::sort (ids.begin (), ids.end ());
+
+  std::stringstream out;
+  foreach (id, ids)
+    out &lt;&lt; *id &lt;&lt; std::endl;
 
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleVersion (Config&amp; conf)
+void handleUndo ()
 {
-  std::stringstream out;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  context.tdb.undo ();
+  context.tdb.unlock ();
+}
 
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
-  {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
+////////////////////////////////////////////////////////////////////////////////
+std::string handleVersion ()
+{
+  std::stringstream out;
 
   // Create a table for the disclaimer.
+  int width = context.getWidth ();
   Table disclaimer;
   disclaimer.setTableWidth (width);
   disclaimer.addColumn (&quot; &quot;);
@@ -290,79 +412,87 @@ std::string handleVersion (Config&amp; conf)
   link.setColumnWidth (0, Table::flexible);
   link.setColumnJustification (0, Table::left);
   link.addCell (link.addRow (), 0,
-    &quot;See http://www.beckingham.net/task.html for the latest releases and a &quot;
-    &quot;full tutorial.  New releases containing fixes and enhancements are &quot;
-    &quot;made frequently.&quot;);
+    &quot;See http://taskwarrior.org for the latest releases, online documentation &quot;
+    &quot;and lively discussion.  New releases containing fixes and enhancements &quot;
+    &quot;are made frequently.&quot;);
+
+  std::vector &lt;std::string&gt; all;
+  context.config.all (all);
 
   // Create a table for output.
   Table table;
-  table.setTableWidth (width);
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-  table.addColumn (&quot;Config variable&quot;);
-  table.addColumn (&quot;Value&quot;);
-
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+  if (context.config.get (&quot;longversion&quot;, true))
   {
-    table.setColumnUnderline (0);
-    table.setColumnUnderline (1);
-  }
-  else
-    table.setTableDashedUnderline ();
+    table.setTableWidth (width);
+    table.setDateFormat (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+    table.addColumn (&quot;Config variable&quot;);
+    table.addColumn (&quot;Value&quot;);
+
+    if (context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), 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);
-  table.sortOn (0, Table::ascendingCharacter);
+    table.setColumnWidth (0, Table::minimum);
+    table.setColumnWidth (1, Table::flexible);
+    table.setColumnJustification (0, Table::left);
+    table.setColumnJustification (1, Table::left);
+    table.sortOn (0, Table::ascendingCharacter);
 
-  std::vector &lt;std::string&gt; all;
-  conf.all (all);
-  foreach (i, all)
-  {
-    std::string value = conf.get (*i);
-    if (value != &quot;&quot;)
+    foreach (i, all)
     {
-      int row = table.addRow ();
-      table.addCell (row, 0, *i);
-      table.addCell (row, 1, value);
+      std::string value = context.config.get (*i);
+      if (value != &quot;&quot;)
+      {
+        int row = table.addRow ();
+        table.addCell (row, 0, *i);
+        table.addCell (row, 1, value);
+      }
     }
   }
 
   out &lt;&lt; &quot;Copyright (C) 2006 - 2009, P. Beckingham.&quot;
       &lt;&lt; std::endl
-      &lt;&lt; ((conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+      &lt;&lt; ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false))
            ? Text::colorize (Text::bold, Text::nocolor, PACKAGE)
            : PACKAGE)
       &lt;&lt; &quot; &quot;
-      &lt;&lt; ((conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+      &lt;&lt; ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false))
            ? Text::colorize (Text::bold, Text::nocolor, VERSION)
            : VERSION)
       &lt;&lt; std::endl
       &lt;&lt; disclaimer.render ()
       &lt;&lt; std::endl
-      &lt;&lt; table.render ()
+      &lt;&lt; (context.config.get (&quot;longversion&quot;, true) ? table.render () : &quot;&quot;)
       &lt;&lt; link.render ()
       &lt;&lt; std::endl;
 
   // Complain about configuration variables that are not recognized.
   // These are the regular configuration variables.
-  // Note that there is a leading and trailing space.
+  // Note that there is a leading and trailing space, to make searching easier.
   std::string recognized =
-    &quot; blanklines color color.active color.due color.overdue color.pri.H &quot;
+    &quot; blanklines bulk color color.active color.due color.overdue color.pri.H &quot;
     &quot;color.pri.L color.pri.M color.pri.none color.recurring color.tagged &quot;
-    &quot;confirmation curses data.location dateformat default.command &quot;
-    &quot;default.priority defaultwidth due echo.command locking monthsperline nag &quot;
-    &quot;next project shadow.command shadow.file shadow.notify weekstart editor &quot;
-    &quot;import.synonym.id import.synonym.uuid import.synonym.status &quot;
-    &quot;import.synonym.tags import.synonym.entry import.synonym.start &quot;
-    &quot;import.synonym.due import.synonym.recur import.synonym.end &quot;
-    &quot;import.synonym.project import.synonym.priority import.synonym.fg &quot;
-    &quot;import.synonym.bg import.synonym.description &quot;;
+    &quot;color.footnote color.header color.debug confirmation curses data.location &quot;
+    &quot;dateformat debug default.command default.priority defaultwidth due locale &quot;
+    &quot;displayweeknumber echo.command locking monthsperline nag next project &quot;
+    &quot;shadow.command shadow.file shadow.notify weekstart editor import.synonym.id &quot;
+    &quot;import.synonym.uuid longversion complete.all.projects complete.all.tags &quot;
+#ifdef FEATURE_SHELL
+    &quot;shell.prompt &quot;
+#endif
+    &quot;import.synonym.status import.synonym.tags import.synonym.entry &quot;
+    &quot;import.synonym.start import.synonym.due import.synonym.recur &quot;
+    &quot;import.synonym.end import.synonym.project import.synonym.priority &quot;
+    &quot;import.synonym.fg import.synonym.bg import.synonym.description &quot;;
 
   // 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
   // is redirected to a file, or stdout is not a tty.
-  recognized += &quot; _forcecolor&quot;;
+  recognized += &quot;_forcecolor &quot;;
 
   std::vector &lt;std::string&gt; unrecognized;
   foreach (i, all)
@@ -374,10 +504,11 @@ std::string handleVersion (Config&amp; conf)
     {
       // These are special configuration variables, because their name is
       // dynamic.
-      if (i-&gt;find (&quot;color.keyword.&quot;) == std::string::npos &amp;&amp;
-          i-&gt;find (&quot;color.project.&quot;) == std::string::npos &amp;&amp;
-          i-&gt;find (&quot;color.tag.&quot;)     == std::string::npos &amp;&amp;
-          i-&gt;find (&quot;report.&quot;)        == std::string::npos)
+      if (i-&gt;substr (0, 14) != &quot;color.keyword.&quot; &amp;&amp;
+          i-&gt;substr (0, 14) != &quot;color.project.&quot; &amp;&amp;
+          i-&gt;substr (0, 10) != &quot;color.tag.&quot;     &amp;&amp;
+          i-&gt;substr (0,  7) != &quot;report.&quot;        &amp;&amp;
+          i-&gt;substr (0,  6) != &quot;alias.&quot;)
       {
         unrecognized.push_back (*i);
       }
@@ -403,12 +534,12 @@ std::string handleVersion (Config&amp; conf)
         &lt;&lt; std::endl;
   else
   {
-    if (conf.get (&quot;data.location&quot;) == &quot;&quot;)
+    if (context.config.get (&quot;data.location&quot;) == &quot;&quot;)
       out &lt;&lt; &quot;Configuration error: data.location not specified in .taskrc &quot;
              &quot;file.&quot;
           &lt;&lt; std::endl;
 
-    if (access (expandPath (conf.get (&quot;data.location&quot;)).c_str (), X_OK))
+    if (access (expandPath (context.config.get (&quot;data.location&quot;)).c_str (), X_OK))
       out &lt;&lt; &quot;Configuration error: data.location contains a directory name&quot;
              &quot; that doesn't exist, or is unreadable.&quot;
           &lt;&lt; std::endl;
@@ -418,32 +549,38 @@ std::string handleVersion (Config&amp; conf)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleDelete (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleDelete ()
 {
   std::stringstream out;
 
-  std::vector &lt;T&gt; all;
-  tdb.allPendingT (all);
-  filterSequence (all, task);
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  Filter filter;
+  context.tdb.loadPending (tasks, filter);
+
+  // Filter sequence.
+  std::vector &lt;Task&gt; all = tasks;
+  context.filter.applySequence (tasks, context.sequence);
 
   // Determine the end date.
   char endTime[16];
   sprintf (endTime, &quot;%u&quot;, (unsigned int) time (NULL));
 
-  foreach (t, all)
+  foreach (task, tasks)
   {
     std::stringstream question;
     question &lt;&lt; &quot;Permanently delete task &quot;
-             &lt;&lt; t-&gt;getId ()
+             &lt;&lt; task-&gt;id
              &lt;&lt; &quot; '&quot;
-             &lt;&lt; t-&gt;getDescription ()
+             &lt;&lt; task-&gt;get (&quot;description&quot;)
              &lt;&lt; &quot;'?&quot;;
 
-    if (!conf.get (std::string (&quot;confirmation&quot;), false) || confirm (question.str ()))
+    if (!context.config.get (std::string (&quot;confirmation&quot;), false) || confirm (question.str ()))
     {
       // 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-&gt;getAttribute (&quot;parent&quot;);
+      std::string parent = task-&gt;get (&quot;parent&quot;);
       if (parent != &quot;&quot;)
       {
         if (confirm (&quot;This is a recurring task.  Do you want to delete all pending recurrences of this same task?&quot;))
@@ -452,18 +589,18 @@ std::string handleDelete (TDB&amp; tdb, T&amp; task, Config&amp; conf)
           // itself, and delete them.
           foreach (sibling, all)
           {
-            if (sibling-&gt;getAttribute (&quot;parent&quot;) == parent ||
-                sibling-&gt;getUUID ()              == parent)
+            if (sibling-&gt;get (&quot;parent&quot;) == parent ||
+                sibling-&gt;get (&quot;uuid&quot;)   == parent)
             {
-              sibling-&gt;setStatus (T::deleted);
-              sibling-&gt;setAttribute (&quot;end&quot;, endTime);
-              tdb.modifyT (*sibling);
+              sibling-&gt;setStatus (Task::deleted);
+              sibling-&gt;set (&quot;end&quot;, endTime);
+              context.tdb.update (*sibling);
 
-              if (conf.get (&quot;echo.command&quot;, true))
+              if (context.config.get (&quot;echo.command&quot;, true))
                 out &lt;&lt; &quot;Deleting recurring task &quot;
-                    &lt;&lt; sibling-&gt;getId ()
+                    &lt;&lt; sibling-&gt;id
                     &lt;&lt; &quot; '&quot;
-                    &lt;&lt; sibling-&gt;getDescription ()
+                    &lt;&lt; sibling-&gt;get (&quot;description&quot;)
                     &lt;&lt; &quot;'&quot;
                     &lt;&lt; std::endl;
             }
@@ -472,31 +609,31 @@ std::string handleDelete (TDB&amp; tdb, T&amp; task, Config&amp; conf)
         else
         {
           // Update mask in parent.
-          t-&gt;setStatus (T::deleted);
-          updateRecurrenceMask (tdb, all, *t);
+          task-&gt;setStatus (Task::deleted);
+          updateRecurrenceMask (all, *task);
 
-          t-&gt;setAttribute (&quot;end&quot;, endTime);
-          tdb.modifyT (*t);
+          task-&gt;set (&quot;end&quot;, endTime);
+          context.tdb.update (*task);
 
           out &lt;&lt; &quot;Deleting recurring task &quot;
-              &lt;&lt; t-&gt;getId ()
+              &lt;&lt; task-&gt;id
               &lt;&lt; &quot; '&quot;
-              &lt;&lt; t-&gt;getDescription ()
+              &lt;&lt; task-&gt;get (&quot;description&quot;)
               &lt;&lt; &quot;'&quot;
               &lt;&lt; std::endl;
         }
       }
       else
       {
-        t-&gt;setStatus (T::deleted);
-        t-&gt;setAttribute (&quot;end&quot;, endTime);
-        tdb.modifyT (*t);
+        task-&gt;setStatus (Task::deleted);
+        task-&gt;set (&quot;end&quot;, endTime);
+        context.tdb.update (*task);
 
-        if (conf.get (&quot;echo.command&quot;, true))
+        if (context.config.get (&quot;echo.command&quot;, true))
           out &lt;&lt; &quot;Deleting task &quot;
-              &lt;&lt; t-&gt;getId ()
+              &lt;&lt; task-&gt;id
               &lt;&lt; &quot; '&quot;
-              &lt;&lt; t-&gt;getDescription ()
+              &lt;&lt; task-&gt;get (&quot;description&quot;)
               &lt;&lt; &quot;'&quot;
               &lt;&lt; std::endl;
       }
@@ -505,128 +642,188 @@ std::string handleDelete (TDB&amp; tdb, T&amp; task, Config&amp; conf)
       out &lt;&lt; &quot;Task not deleted.&quot; &lt;&lt; std::endl;
   }
 
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleStart (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleStart ()
 {
   std::stringstream out;
 
-  std::vector &lt;T&gt; all;
-  tdb.pendingT (all);
-  filterSequence (all, task);
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  Filter filter;
+  context.tdb.loadPending (tasks, filter);
 
-  foreach (t, all)
+  // Filter sequence.
+  context.filter.applySequence (tasks, context.sequence);
+
+  bool nagged = false;
+  foreach (task, tasks)
   {
-    if (t-&gt;getAttribute (&quot;start&quot;) == &quot;&quot;)
+    if (! task-&gt;has (&quot;start&quot;))
     {
       char startTime[16];
       sprintf (startTime, &quot;%u&quot;, (unsigned int) time (NULL));
-      t-&gt;setAttribute (&quot;start&quot;, startTime);
+      task-&gt;set (&quot;start&quot;, startTime);
 
-      tdb.modifyT (*t);
+      context.tdb.update (*task);
 
-      if (conf.get (&quot;echo.command&quot;, true))
+      if (context.config.get (&quot;echo.command&quot;, true))
         out &lt;&lt; &quot;Started &quot;
-            &lt;&lt; t-&gt;getId ()
+            &lt;&lt; task-&gt;id
             &lt;&lt; &quot; '&quot;
-            &lt;&lt; t-&gt;getDescription ()
+            &lt;&lt; task-&gt;get (&quot;description&quot;)
             &lt;&lt; &quot;'&quot;
             &lt;&lt; std::endl;
-      nag (tdb, task, conf);
+      if (!nagged)
+        nagged = nag (*task);
     }
     else
     {
-      out &lt;&lt; &quot;Task &quot; &lt;&lt; t-&gt;getId () &lt;&lt; &quot; '&quot; &lt;&lt; t-&gt;getDescription () &lt;&lt; &quot;' already started.&quot; &lt;&lt; std::endl;
+      out &lt;&lt; &quot;Task &quot;
+          &lt;&lt; task-&gt;id
+          &lt;&lt; &quot; '&quot;
+          &lt;&lt; task-&gt;get (&quot;description&quot;)
+          &lt;&lt; &quot;' already started.&quot;
+          &lt;&lt; std::endl;
     }
   }
 
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleStop (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleStop ()
 {
   std::stringstream out;
 
-  std::vector &lt;T&gt; all;
-  tdb.pendingT (all);
-  filterSequence (all, task);
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  Filter filter;
+  context.tdb.loadPending (tasks, filter);
+
+  // Filter sequence.
+  context.filter.applySequence (tasks, context.sequence);
 
-  foreach (t, all)
+  foreach (task, tasks)
   {
-    if (t-&gt;getAttribute (&quot;start&quot;) != &quot;&quot;)
+    if (task-&gt;has (&quot;start&quot;))
     {
-      t-&gt;removeAttribute (&quot;start&quot;);
-      tdb.modifyT (*t);
+      task-&gt;remove (&quot;start&quot;);
+      context.tdb.update (*task);
 
-      if (conf.get (&quot;echo.command&quot;, true))
-        out &lt;&lt; &quot;Stopped &quot; &lt;&lt; t-&gt;getId () &lt;&lt; &quot; '&quot; &lt;&lt; t-&gt;getDescription () &lt;&lt; &quot;'&quot; &lt;&lt; std::endl;
+      if (context.config.get (&quot;echo.command&quot;, true))
+        out &lt;&lt; &quot;Stopped &quot;
+            &lt;&lt; task-&gt;id
+            &lt;&lt; &quot; '&quot;
+            &lt;&lt; task-&gt;get (&quot;description&quot;)
+            &lt;&lt; &quot;'&quot;
+            &lt;&lt; std::endl;
     }
     else
     {
-      out &lt;&lt; &quot;Task &quot; &lt;&lt; t-&gt;getId () &lt;&lt; &quot; '&quot; &lt;&lt; t-&gt;getDescription () &lt;&lt; &quot;' not started.&quot; &lt;&lt; std::endl;
+      out &lt;&lt; &quot;Task &quot;
+          &lt;&lt; task-&gt;id
+          &lt;&lt; &quot; '&quot;
+          &lt;&lt; task-&gt;get (&quot;description&quot;)
+          &lt;&lt; &quot;' not started.&quot;
+          &lt;&lt; std::endl;
     }
   }
 
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleDone (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleDone ()
 {
   int count = 0;
   std::stringstream out;
-  std::vector &lt;T&gt; all;
-  tdb.allPendingT (all);
 
-  std::vector &lt;T&gt; filtered = all;
-  filterSequence (filtered, task);
-  foreach (seq, filtered)
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  Filter filter;
+  context.tdb.loadPending (tasks, filter);
+
+  // Filter sequence.
+  std::vector &lt;Task&gt; all = tasks;
+  context.filter.applySequence (tasks, context.sequence);
+
+  Permission permission;
+  if (context.sequence.size () &gt; (size_t) context.config.get (&quot;bulk&quot;, 2))
+    permission.bigSequence ();
+
+  bool nagged = false;
+  foreach (task, tasks)
   {
-    if (seq-&gt;getStatus () == T::pending)
+    if (task-&gt;getStatus () == Task::pending)
     {
-      // Apply deltas.
-      deltaDescription   (*seq, task);
-      deltaTags          (*seq, task);
-      deltaAttributes    (*seq, task);
-      deltaSubstitutions (*seq, task);
+      Task before (*task);
+
+      // Apply other deltas.
+      if (deltaDescription (*task))
+        permission.bigChange ();
+
+      deltaTags (*task);
+      deltaAttributes (*task);
+      deltaSubstitutions (*task);
 
       // Add an end date.
       char entryTime[16];
       sprintf (entryTime, &quot;%u&quot;, (unsigned int) time (NULL));
-      seq-&gt;setAttribute (&quot;end&quot;, entryTime);
+      task-&gt;set (&quot;end&quot;, entryTime);
 
       // Change status.
-      seq-&gt;setStatus (T::completed);
+      task-&gt;setStatus (Task::completed);
 
-      if (!tdb.modifyT (*seq))
-        throw std::string (&quot;Could not mark task as completed.&quot;);
-
-      if (conf.get (&quot;echo.command&quot;, true))
-        out &lt;&lt; &quot;Completed &quot;
-            &lt;&lt; seq-&gt;getId ()
-            &lt;&lt; &quot; '&quot;
-            &lt;&lt; seq-&gt;getDescription ()
-            &lt;&lt; &quot;'&quot;
-            &lt;&lt; std::endl;
+      if (taskDiff (before, *task))
+      {
+        if (permission.confirmed (before, taskDifferences (before, *task) + &quot;Are you sure?&quot;))
+        {
+          context.tdb.update (*task);
+
+          if (context.config.get (&quot;echo.command&quot;, true))
+            out &lt;&lt; &quot;Completed &quot;
+                &lt;&lt; task-&gt;id
+                &lt;&lt; &quot; '&quot;
+                &lt;&lt; task-&gt;get (&quot;description&quot;)
+                &lt;&lt; &quot;'&quot;
+                &lt;&lt; std::endl;
 
-      updateRecurrenceMask (tdb, all, *seq);
-      nag (tdb, *seq, conf);
+          ++count;
+        }
+      }
 
-      ++count;
+      updateRecurrenceMask (all, *task);
+      if (!nagged)
+        nagged = nag (*task);
     }
     else
       out &lt;&lt; &quot;Task &quot;
-          &lt;&lt; seq-&gt;getId ()
+          &lt;&lt; task-&gt;id
           &lt;&lt; &quot; '&quot;
-          &lt;&lt; seq-&gt;getDescription ()
+          &lt;&lt; task-&gt;get (&quot;description&quot;)
           &lt;&lt; &quot;' is not pending&quot;
           &lt;&lt; std::endl;
   }
 
-  if (conf.get (&quot;echo.command&quot;, true))
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  if (context.config.get (&quot;echo.command&quot;, true))
     out &lt;&lt; &quot;Marked &quot;
         &lt;&lt; count
         &lt;&lt; &quot; task&quot;
@@ -638,229 +835,345 @@ std::string handleDone (TDB&amp; tdb, T&amp; task, Config&amp; conf)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleExport (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleExport ()
 {
-  std::stringstream output;
+  std::stringstream out;
 
-  // Use the description as a file name, then clobber the description so the
-  // file name isn't used for filtering.
-  std::string file = trim (task.getDescription ());
-  task.setDescription (&quot;&quot;);
+  // Deliberately no 'id'.
+  out &lt;&lt; &quot;'uuid',&quot;
+      &lt;&lt; &quot;'status',&quot;
+      &lt;&lt; &quot;'tags',&quot;
+      &lt;&lt; &quot;'entry',&quot;
+      &lt;&lt; &quot;'start',&quot;
+      &lt;&lt; &quot;'due',&quot;
+      &lt;&lt; &quot;'recur',&quot;
+      &lt;&lt; &quot;'end',&quot;
+      &lt;&lt; &quot;'project',&quot;
+      &lt;&lt; &quot;'priority',&quot;
+      &lt;&lt; &quot;'fg',&quot;
+      &lt;&lt; &quot;'bg',&quot;
+      &lt;&lt; &quot;'description'&quot;
+      &lt;&lt; &quot;\n&quot;;
 
-  if (file.length () &gt; 0)
+  int count = 0;
+
+  // Get all the tasks.
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  context.tdb.load (tasks, context.filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  foreach (task, tasks)
   {
-    std::ofstream out (file.c_str ());
-    if (out.good ())
+    if (task-&gt;getStatus () != Task::recurring)
     {
-      out &lt;&lt; &quot;'id',&quot;
-          &lt;&lt; &quot;'uuid',&quot;
-          &lt;&lt; &quot;'status',&quot;
-          &lt;&lt; &quot;'tags',&quot;
-          &lt;&lt; &quot;'entry',&quot;
-          &lt;&lt; &quot;'start',&quot;
-          &lt;&lt; &quot;'due',&quot;
-          &lt;&lt; &quot;'recur',&quot;
-          &lt;&lt; &quot;'end',&quot;
-          &lt;&lt; &quot;'project',&quot;
-          &lt;&lt; &quot;'priority',&quot;
-          &lt;&lt; &quot;'fg',&quot;
-          &lt;&lt; &quot;'bg',&quot;
-          &lt;&lt; &quot;'description'&quot;
-          &lt;&lt; &quot;\n&quot;;
-
-      int count = 0;
-      std::vector &lt;T&gt; all;
-      tdb.allPendingT (all);
-      filter (all, task);
-      foreach (t, all)
-      {
-        if (t-&gt;getStatus () != T::recurring &amp;&amp;
-            t-&gt;getStatus () != T::deleted)
-        {
-          out &lt;&lt; t-&gt;composeCSV ().c_str ();
-          ++count;
-        }
-      }
-      out.close ();
-
-      output &lt;&lt; count &lt;&lt; &quot; tasks exported to '&quot; &lt;&lt; file &lt;&lt; &quot;'&quot; &lt;&lt; std::endl;
+      out &lt;&lt; task-&gt;composeCSV ().c_str ();
+      ++count;
     }
-    else
-      throw std::string (&quot;Could not write to export file.&quot;);
   }
-  else
-    throw std::string (&quot;You must specify a file to write to.&quot;);
 
-  return output.str ();
+  return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleModify (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleModify ()
 {
   int count = 0;
   std::stringstream out;
-  std::vector &lt;T&gt; all;
-  tdb.allPendingT (all);
 
-  std::vector &lt;T&gt; filtered = all;
-  filterSequence (filtered, task);
-  foreach (seq, filtered)
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  Filter filter;
+  context.tdb.loadPending (tasks, filter);
+
+  // Filter sequence.
+  std::vector &lt;Task&gt; all = tasks;
+  context.filter.applySequence (tasks, context.sequence);
+
+  Permission permission;
+  if (context.sequence.size () &gt; (size_t) context.config.get (&quot;bulk&quot;, 2))
+    permission.bigSequence ();
+
+  foreach (task, tasks)
   {
     // Perform some logical consistency checks.
-    if (task.getAttribute (&quot;recur&quot;) != &quot;&quot; &amp;&amp;
-        task.getAttribute (&quot;due&quot;)   == &quot;&quot; &amp;&amp;
-        seq-&gt;getAttribute (&quot;due&quot;)   == &quot;&quot;)
+    if (context.task.has (&quot;recur&quot;) &amp;&amp;
+        !context.task.has (&quot;due&quot;)  &amp;&amp;
+        !task-&gt;has (&quot;due&quot;))
       throw std::string (&quot;You cannot specify a recurring task without a due date.&quot;);
 
-    if (task.getAttribute (&quot;until&quot;) != &quot;&quot; &amp;&amp;
-        task.getAttribute (&quot;recur&quot;) == &quot;&quot; &amp;&amp;
-        seq-&gt;getAttribute (&quot;recur&quot;) == &quot;&quot;)
+    if (context.task.has (&quot;until&quot;)  &amp;&amp;
+        !context.task.has (&quot;recur&quot;) &amp;&amp;
+        !task-&gt;has (&quot;recur&quot;))
       throw std::string (&quot;You cannot specify an until date for a non-recurring task.&quot;);
 
     // Make all changes.
     foreach (other, all)
     {
-      if (other-&gt;getId ()               == seq-&gt;getId ()                   || // Self
-          (seq-&gt;getAttribute (&quot;parent&quot;) != &quot;&quot; &amp;&amp;
-           seq-&gt;getAttribute (&quot;parent&quot;) == other-&gt;getAttribute (&quot;parent&quot;)) || // Sibling
-          other-&gt;getUUID ()             == seq-&gt;getAttribute (&quot;parent&quot;))      // Parent
+      if (other-&gt;id             == task-&gt;id               || // Self
+          (task-&gt;has (&quot;parent&quot;) &amp;&amp;
+           task-&gt;get (&quot;parent&quot;) == other-&gt;get (&quot;parent&quot;)) || // Sibling
+          other-&gt;get (&quot;uuid&quot;)   == task-&gt;get (&quot;parent&quot;))     // Parent
       {
+        Task before (*other);
+
         // 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 (deltaDescription (*other))
+        {
+          permission.bigChange ();
+          ++changes;
+        }
 
-        if (changes)
-          tdb.modifyT (*other);
+        changes += deltaTags (*other);
+        changes += deltaAttributes (*other);
+        changes += deltaSubstitutions (*other);
 
-        ++count;
+        if (taskDiff (before, *other))
+        {
+          if (changes &amp;&amp; permission.confirmed (before, taskDifferences (before, *other) + &quot;Are you sure?&quot;))
+          {
+            context.tdb.update (*other);
+            ++count;
+          }
+        }
       }
     }
   }
 
-  if (conf.get (&quot;echo.command&quot;, true))
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  if (context.config.get (&quot;echo.command&quot;, true))
     out &lt;&lt; &quot;Modified &quot; &lt;&lt; count &lt;&lt; &quot; task&quot; &lt;&lt; (count == 1 ? &quot;&quot; : &quot;s&quot;) &lt;&lt; std::endl;
 
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleAppend (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleAppend ()
 {
   int count = 0;
   std::stringstream out;
-  std::vector &lt;T&gt; all;
-  tdb.allPendingT (all);
 
-  std::vector &lt;T&gt; filtered = all;
-  filterSequence (filtered, task);
-  foreach (seq, filtered)
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  Filter filter;
+  context.tdb.loadPending (tasks, filter);
+
+  // Filter sequence.
+  std::vector &lt;Task&gt; all = tasks;
+  context.filter.applySequence (tasks, context.sequence);
+
+  Permission permission;
+  if (context.sequence.size () &gt; (size_t) context.config.get (&quot;bulk&quot;, 2))
+    permission.bigSequence ();
+
+  foreach (task, tasks)
   {
     foreach (other, all)
     {
-      if (other-&gt;getId ()               == seq-&gt;getId ()                   || // Self
-          (seq-&gt;getAttribute (&quot;parent&quot;) != &quot;&quot; &amp;&amp;
-           seq-&gt;getAttribute (&quot;parent&quot;) == other-&gt;getAttribute (&quot;parent&quot;)) || // Sibling
-          other-&gt;getUUID ()             == seq-&gt;getAttribute (&quot;parent&quot;))      // Parent
+      if (other-&gt;id             == task-&gt;id               || // Self
+          (task-&gt;has (&quot;parent&quot;) &amp;&amp;
+           task-&gt;get (&quot;parent&quot;) == other-&gt;get (&quot;parent&quot;)) || // Sibling
+          other-&gt;get (&quot;uuid&quot;)   == task-&gt;get (&quot;parent&quot;))     // Parent
       {
+        Task before (*other);
+
         // 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);
+        changes += deltaAppend (*other);
+        changes += deltaTags (*other);
+        changes += deltaAttributes (*other);
 
-        if (changes)
+        if (taskDiff (before, *other))
         {
-          tdb.modifyT (*other);
+          if (changes &amp;&amp; permission.confirmed (before, taskDifferences (before, *other) + &quot;Are you sure?&quot;))
+          {
+            context.tdb.update (*other);
 
-          if (conf.get (&quot;echo.command&quot;, true))
-            out &lt;&lt; &quot;Appended '&quot;
-                &lt;&lt; task.getDescription ()
-                &lt;&lt; &quot;' to task &quot;
-                &lt;&lt; other-&gt;getId ()
-                &lt;&lt; std::endl;
-        }
+            if (context.config.get (&quot;echo.command&quot;, true))
+              out &lt;&lt; &quot;Appended '&quot;
+                  &lt;&lt; context.task.get (&quot;description&quot;)
+                  &lt;&lt; &quot;' to task &quot;
+                  &lt;&lt; other-&gt;id
+                  &lt;&lt; std::endl;
 
-        ++count;
+            ++count;
+          }
+        }
       }
     }
   }
 
-  if (conf.get (&quot;echo.command&quot;, true))
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  if (context.config.get (&quot;echo.command&quot;, true))
     out &lt;&lt; &quot;Appended &quot; &lt;&lt; count &lt;&lt; &quot; task&quot; &lt;&lt; (count == 1 ? &quot;&quot; : &quot;s&quot;) &lt;&lt; std::endl;
 
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleDuplicate (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleDuplicate ()
 {
-  int count = 0;
   std::stringstream out;
-  std::vector &lt;T&gt; all;
-  tdb.allPendingT (all);
+  int count = 0;
 
-  std::vector &lt;T&gt; filtered = all;
-  filterSequence (filtered, task);
-  foreach (seq, filtered)
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  Filter filter;
+  context.tdb.loadPending (tasks, filter);
+
+  // Filter sequence.
+  context.filter.applySequence (tasks, context.sequence);
+
+  foreach (task, tasks)
   {
-    if (seq-&gt;getStatus () != T::recurring &amp;&amp; seq-&gt;getAttribute (&quot;parent&quot;) == &quot;&quot;)
+    Task dup (*task);
+    dup.set (&quot;uuid&quot;, uuid ());  // Needs a new UUID.
+    dup.setStatus (Task::pending);
+    dup.remove (&quot;start&quot;);   // Does not inherit start date.
+    dup.remove (&quot;end&quot;);     // Does not inherit end date.
+
+    // Recurring tasks are duplicated and downgraded to regular tasks.
+    if (task-&gt;getStatus () == Task::recurring)
     {
-      T dup (*seq);
-      dup.setUUID (uuid ());  // Needs a new UUID.
+      dup.remove (&quot;parent&quot;);
+      dup.remove (&quot;recur&quot;);
+      dup.remove (&quot;until&quot;);
+      dup.remove (&quot;imak&quot;);
+      dup.remove (&quot;imask&quot;);
+
+      out &lt;&lt; &quot;Note: task &quot;
+          &lt;&lt; task-&gt;id
+          &lt;&lt; &quot; was a recurring task.  The new task is not.&quot;
+          &lt;&lt; std::endl;
+    }
 
-      // Apply deltas.
-      deltaDescription   (dup, task);
-      deltaTags          (dup, task);
-      deltaAttributes    (dup, task);
-      deltaSubstitutions (dup, task);
+    // Apply deltas.
+    deltaDescription (dup);
+    deltaTags (dup);
+    deltaAttributes (dup);
+    deltaSubstitutions (dup);
 
-      // A New task needs a new entry time.
-      char entryTime[16];
-      sprintf (entryTime, &quot;%u&quot;, (unsigned int) time (NULL));
-      dup.setAttribute (&quot;entry&quot;, entryTime);
+    // A New task needs a new entry time.
+    char entryTime[16];
+    sprintf (entryTime, &quot;%u&quot;, (unsigned int) time (NULL));
+    dup.set (&quot;entry&quot;, entryTime);
 
-      if (!tdb.addT (dup))
-        throw std::string (&quot;Could not create new task.&quot;);
+    context.tdb.add (dup);
 
-      if (conf.get (&quot;echo.command&quot;, true))
-        out &lt;&lt; &quot;Duplicated &quot;
-            &lt;&lt; seq-&gt;getId ()
-            &lt;&lt; &quot; '&quot;
-            &lt;&lt; seq-&gt;getDescription ()
-            &lt;&lt; &quot;'&quot;
-            &lt;&lt; std::endl;
-      ++count;
-    }
-    else
-      out &lt;&lt; &quot;Task &quot;
-          &lt;&lt; seq-&gt;getId ()
+    if (context.config.get (&quot;echo.command&quot;, true))
+      out &lt;&lt; &quot;Duplicated &quot;
+          &lt;&lt; task-&gt;id
           &lt;&lt; &quot; '&quot;
-          &lt;&lt; seq-&gt;getDescription ()
-          &lt;&lt; &quot;' is a recurring task, and cannot be duplicated.&quot;
+          &lt;&lt; task-&gt;get (&quot;description&quot;)
+          &lt;&lt; &quot;'&quot;
           &lt;&lt; std::endl;
+    ++count;
   }
 
-  if (conf.get (&quot;echo.command&quot;, true))
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  if (context.config.get (&quot;echo.command&quot;, true))
     out &lt;&lt; &quot;Duplicated &quot; &lt;&lt; count &lt;&lt; &quot; task&quot; &lt;&lt; (count == 1 ? &quot;&quot; : &quot;s&quot;) &lt;&lt; std::endl;
 
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleColor (Config&amp; conf)
+#ifdef FEATURE_SHELL
+void handleShell ()
 {
-  std::stringstream out;
+  // Display some kind of welcome message.
+  std::cout &lt;&lt; ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false))
+                 ? Text::colorize (Text::bold, Text::nocolor, PACKAGE_STRING)
+                 : PACKAGE_STRING)
+            &lt;&lt; &quot; shell&quot;
+            &lt;&lt; std::endl
+            &lt;&lt; std::endl
+            &lt;&lt; &quot;Enter any task command (such as 'list'), or hit 'Enter'.&quot;
+            &lt;&lt; std::endl
+            &lt;&lt; &quot;There is no need to include the 'task' command itself.&quot;
+            &lt;&lt; std::endl
+            &lt;&lt; &quot;Enter 'quit' to end the session.&quot;
+            &lt;&lt; std::endl
+            &lt;&lt; std::endl;
+
+  // Preserve any special override arguments, and reapply them for each
+  // shell command.
+  std::vector &lt;std::string&gt; special;
+  foreach (arg, context.args)
+    if (arg-&gt;substr (0, 3) == &quot;rc.&quot; ||
+        arg-&gt;substr (0, 3) == &quot;rc:&quot;)
+      special.push_back (*arg);
 
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+  std::string quit = &quot;quit&quot;; // TODO i18n
+  std::string command;
+  bool keepGoing = true;
+
+  do
   {
-    out &lt;&lt; optionalBlankLine (conf) &lt;&lt; &quot;Foreground&quot; &lt;&lt; std::endl
+    std::cout &lt;&lt; context.config.get (&quot;shell.prompt&quot;, &quot;task&gt;&quot;) &lt;&lt; &quot; &quot;;
+
+    command = &quot;&quot;;
+    std::getline (std::cin, command);
+    command = lowerCase (trim (command));
+
+    if (command.length () &gt; 0               &amp;&amp;
+        command.length () &lt;= quit.length () &amp;&amp;
+        command == quit.substr (0, command.length ()))
+    {
+      keepGoing = false;
+    }
+    else
+    {
+      try
+      {
+        context.clear ();
+
+        std::vector &lt;std::string&gt; args;
+        split (args, command, ' ');
+        foreach (arg, special) context.args.push_back (*arg);
+        foreach (arg, args)    context.args.push_back (*arg);
+
+        context.initialize ();
+        context.run ();
+      }
+
+      catch (std::string&amp; error)
+      {
+        std::cout &lt;&lt; error &lt;&lt; std::endl;
+      }
+
+      catch (...)
+      {
+        std::cerr &lt;&lt; context.stringtable.get (100, &quot;Unknown error.&quot;) &lt;&lt; std::endl;
+      }
+    }
+  }
+  while (keepGoing &amp;&amp; !std::cin.eof ());
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+std::string handleColor ()
+{
+  std::stringstream out;
+  if (context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false))
+  {
+    out &lt;&lt; optionalBlankLine () &lt;&lt; &quot;Foreground&quot; &lt;&lt; std::endl
         &lt;&lt; &quot;           &quot;
                 &lt;&lt; Text::colorize (Text::bold,                   Text::nocolor, &quot;bold&quot;)                   &lt;&lt; &quot;          &quot;
                 &lt;&lt; Text::colorize (Text::underline,              Text::nocolor, &quot;underline&quot;)              &lt;&lt; &quot;          &quot;
@@ -907,86 +1220,97 @@ std::string handleColor (Config&amp; conf)
                 &lt;&lt; Text::colorize (Text::bold_underline_white,   Text::nocolor, &quot;bold_underline_white&quot;)   &lt;&lt; std::endl
 
         &lt;&lt; std::endl &lt;&lt; &quot;Background&quot; &lt;&lt; std::endl
-        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_black,          &quot;on_black&quot;)               &lt;&lt; &quot;    &quot;
-                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_black,   &quot;on_bright_black&quot;)        &lt;&lt; std::endl
+        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_black,          &quot;on_black&quot;)          &lt;&lt; &quot;    &quot;
+                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_black,   &quot;on_bright_black&quot;)   &lt;&lt; std::endl
 
-        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_red,            &quot;on_red&quot;)                 &lt;&lt; &quot;      &quot;
-                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_red,     &quot;on_bright_red&quot;)          &lt;&lt; std::endl
+        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_red,            &quot;on_red&quot;)            &lt;&lt; &quot;      &quot;
+                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_red,     &quot;on_bright_red&quot;)     &lt;&lt; std::endl
 
-        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_green,          &quot;on_green&quot;)               &lt;&lt; &quot;    &quot;
-                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_green,   &quot;on_bright_green&quot;)        &lt;&lt; std::endl
+        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_green,          &quot;on_green&quot;)          &lt;&lt; &quot;    &quot;
+                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_green,   &quot;on_bright_green&quot;)   &lt;&lt; std::endl
 
-        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_yellow,         &quot;on_yellow&quot;)              &lt;&lt; &quot;   &quot;
-                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_yellow,  &quot;on_bright_yellow&quot;)       &lt;&lt; std::endl
+        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_yellow,         &quot;on_yellow&quot;)         &lt;&lt; &quot;   &quot;
+                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_yellow,  &quot;on_bright_yellow&quot;)  &lt;&lt; std::endl
 
-        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_blue,           &quot;on_blue&quot;)                &lt;&lt; &quot;     &quot;
-                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_blue,    &quot;on_bright_blue&quot;)         &lt;&lt; std::endl
+        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_blue,           &quot;on_blue&quot;)           &lt;&lt; &quot;     &quot;
+                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_blue,    &quot;on_bright_blue&quot;)    &lt;&lt; std::endl
 
-        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_magenta,        &quot;on_magenta&quot;)             &lt;&lt; &quot;  &quot;
-                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_magenta, &quot;on_bright_magenta&quot;)      &lt;&lt; std::endl
+        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_magenta,        &quot;on_magenta&quot;)        &lt;&lt; &quot;  &quot;
+                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_magenta, &quot;on_bright_magenta&quot;) &lt;&lt; std::endl
 
-        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_cyan,           &quot;on_cyan&quot;)                &lt;&lt; &quot;     &quot;
-                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_cyan,    &quot;on_bright_cyan&quot;)         &lt;&lt; std::endl
+        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_cyan,           &quot;on_cyan&quot;)           &lt;&lt; &quot;     &quot;
+                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_cyan,    &quot;on_bright_cyan&quot;)    &lt;&lt; std::endl
 
-        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_white,          &quot;on_white&quot;)               &lt;&lt; &quot;    &quot;
-                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_white,   &quot;on_bright_white&quot;)        &lt;&lt; std::endl
+        &lt;&lt; &quot;  &quot; &lt;&lt; Text::colorize (Text::nocolor, Text::on_white,          &quot;on_white&quot;)          &lt;&lt; &quot;    &quot;
+                &lt;&lt; Text::colorize (Text::nocolor, Text::on_bright_white,   &quot;on_bright_white&quot;)   &lt;&lt; std::endl
 
-        &lt;&lt; optionalBlankLine (conf);
+        &lt;&lt; optionalBlankLine ();
   }
   else
   {
-    out &lt;&lt; &quot;Color is currently turned off in your .taskrc file.&quot; &lt;&lt; std::endl;
+    out &lt;&lt; &quot;Color is currently turned off in your .taskrc file.  &quot;
+           &quot;To enable color, create the entry 'color=on'.&quot;
+        &lt;&lt; std::endl;
   }
 
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleAnnotate (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleAnnotate ()
 {
+  if (!context.task.has (&quot;description&quot;))
+    throw std::string (&quot;Cannot apply a blank annotation.&quot;);
+
   std::stringstream out;
-  std::vector &lt;T&gt; all;
-  tdb.pendingT (all);
-  filterSequence (all, task);
 
-  foreach (t, all)
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  Filter filter;
+  context.tdb.loadPending (tasks, filter);
+
+  // Filter sequence.
+  context.filter.applySequence (tasks, context.sequence);
+
+  Permission permission;
+  if (context.sequence.size () &gt; (size_t) context.config.get (&quot;bulk&quot;, 2))
+    permission.bigSequence ();
+
+  foreach (task, tasks)
   {
-    t-&gt;addAnnotation (task.getDescription ());
-    tdb.modifyT (*t);
-
-    if (conf.get (&quot;echo.command&quot;, true))
-      out &lt;&lt; &quot;Annotated &quot;
-          &lt;&lt; t-&gt;getId ()
-          &lt;&lt; &quot; with '&quot;
-          &lt;&lt; t-&gt;getDescription ()
-          &lt;&lt; &quot;'&quot;
-          &lt;&lt; std::endl;
-  }
+    Task before (*task);
+    task-&gt;addAnnotation (context.task.get (&quot;description&quot;));
 
-  return out.str ();
-}
+    if (taskDiff (before, *task))
+    {
+      if (permission.confirmed (before, taskDifferences (before, *task) + &quot;Are you sure?&quot;))
+      {
+        context.tdb.update (*task);
 
-////////////////////////////////////////////////////////////////////////////////
-T findT (int id, const std::vector &lt;T&gt;&amp; all)
-{
-  std::vector &lt;T&gt;::const_iterator it;
-  for (it = all.begin (); it != all.end (); ++it)
-    if (id == it-&gt;getId ())
-      return *it;
+        if (context.config.get (&quot;echo.command&quot;, true))
+          out &lt;&lt; &quot;Annotated &quot;
+              &lt;&lt; task-&gt;id
+              &lt;&lt; &quot; with '&quot;
+              &lt;&lt; context.task.get (&quot;description&quot;)
+              &lt;&lt; &quot;'&quot;
+              &lt;&lt; std::endl;
+      }
+    }
+  }
 
-  return T ();
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-int deltaAppend (T&amp; task, T&amp; delta)
+int deltaAppend (Task&amp; task)
 {
-  if (delta.getDescription () != &quot;&quot;)
+  if (context.task.has (&quot;description&quot;))
   {
-    task.setDescription (
-      task.getDescription () +
-      &quot; &quot; +
-      delta.getDescription ());
-
+    task.set (&quot;description&quot;,
+              task.get (&quot;description&quot;) + &quot; &quot; + context.task.get (&quot;description&quot;));
     return 1;
   }
 
@@ -994,11 +1318,11 @@ int deltaAppend (T&amp; task, T&amp; delta)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-int deltaDescription (T&amp; task, T&amp; delta)
+int deltaDescription (Task&amp; task)
 {
-  if (delta.getDescription () != &quot;&quot;)
+  if (context.task.has (&quot;description&quot;))
   {
-    task.setDescription (delta.getDescription ());
+    task.set (&quot;description&quot;, context.task.get (&quot;description&quot;));
     return 1;
   }
 
@@ -1006,31 +1330,22 @@ int deltaDescription (T&amp; task, T&amp; delta)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-int deltaTags (T&amp; task, T&amp; delta)
+int deltaTags (Task&amp; task)
 {
   int changes = 0;
 
   // Apply or remove tags, if any.
   std::vector &lt;std::string&gt; tags;
-  delta.getTags (tags);
-  for (unsigned int i = 0; i &lt; tags.size (); ++i)
+  context.task.getTags (tags);
+  foreach (tag, tags)
   {
-    if (tags[i][0] == '+')
-      task.addTag (tags[i].substr (1, std::string::npos));
-    else
-      task.addTag (tags[i]);
-
+    task.addTag (*tag);
     ++changes;
   }
 
-  delta.getRemoveTags (tags);
-  for (unsigned int i = 0; i &lt; tags.size (); ++i)
+  foreach (tag, context.tagRemovals)
   {
-    if (tags[i][0] == '-')
-      task.removeTag (tags[i].substr (1, std::string::npos));
-    else
-      task.removeTag (tags[i]);
-
+    task.removeTag (*tag);
     ++changes;
   }
 
@@ -1038,96 +1353,50 @@ int deltaTags (T&amp; task, T&amp; delta)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-int deltaAttributes (T&amp; task, T&amp; delta)
+int deltaAttributes (Task&amp; task)
 {
   int changes = 0;
 
-  std::map &lt;std::string, std::string&gt; attributes;
-  delta.getAttributes (attributes);
-  foreach (i, attributes)
+  foreach (att, context.task)
   {
-    if (i-&gt;second == &quot;&quot;)
-      task.removeAttribute (i-&gt;first);
-    else
-      task.setAttribute (i-&gt;first, i-&gt;second);
+    if (att-&gt;first != &quot;uuid&quot;        &amp;&amp;
+        att-&gt;first != &quot;description&quot; &amp;&amp;
+        att-&gt;first != &quot;tags&quot;)
+    {
+      // Modifying &quot;wait&quot; changes status.
+      if (att-&gt;first == &quot;wait&quot;)
+      {
+        if (att-&gt;second.value () == &quot;&quot;)
+          task.setStatus (Task::pending);
+        else
+          task.setStatus (Task::waiting);
+      }
 
-    ++changes;
+      if (att-&gt;second.value () == &quot;&quot;)
+        task.remove (att-&gt;first);
+      else
+        task.set (att-&gt;first, att-&gt;second.value ());
+
+      ++changes;
+    }
   }
 
   return changes;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-int deltaSubstitutions (T&amp; task, T&amp; delta)
+int deltaSubstitutions (Task&amp; task)
 {
-  int changes = 0;
-  std::string from;
-  std::string to;
-  bool global;
-  delta.getSubstitution (from, to, global);
-
-  if (from != &quot;&quot;)
-  {
-    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 &lt;time_t, std::string&gt; annotations;
-      task.getAnnotations (annotations);
-      std::map &lt;time_t, std::string&gt;::iterator it;
-      for (it = annotations.begin (); it != annotations.end (); ++it)
-      {
-        while ((pattern = it-&gt;second.find (from)) != std::string::npos)
-        {
-          it-&gt;second.replace (pattern, from.length (), to);
-          ++changes;
-        }
-      }
+  std::string description = task.get (&quot;description&quot;);
+  std::vector &lt;Att&gt; annotations;
+  task.getAnnotations (annotations);
 
-      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 &lt;time_t, std::string&gt; annotations;
-        task.getAnnotations (annotations);
+  context.subst.apply (description, annotations);
 
-        std::map &lt;time_t, std::string&gt;::iterator it;
-        for (it = annotations.begin (); it != annotations.end (); ++it)
-        {
-          if ((pattern = it-&gt;second.find (from)) != std::string::npos)
-          {
-            it-&gt;second.replace (pattern, from.length (), to);
-            ++changes;
-            break;
-          }
-        }
+  task.set (&quot;description&quot;, description);
+  task.setAnnotations (annotations);
 
-        task.setAnnotations (annotations);
-      }
-    }
-  }
-  return changes;
+  return 1;
 }
 
 ////////////////////////////////////////////////////////////////////////////////</diff>
      <filename>src/command.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -33,7 +33,13 @@
 #include &lt;stdlib.h&gt;
 #include &lt;limits.h&gt;
 #include &lt;string.h&gt;
-#include &quot;task.h&quot;
+#include &quot;Date.h&quot;
+#include &quot;Duration.h&quot;
+#include &quot;text.h&quot;
+#include &quot;util.h&quot;
+#include &quot;main.h&quot;
+
+extern Context context;
 
 ////////////////////////////////////////////////////////////////////////////////
 static std::string findValue (
@@ -59,7 +65,6 @@ static std::string findValue (
 
 ////////////////////////////////////////////////////////////////////////////////
 static std::string findDate (
-  Config&amp; conf,
   const std::string&amp; text,
   const std::string&amp; name)
 {
@@ -75,7 +80,7 @@ static std::string findDate (
 
       if (value != &quot;&quot;)
       {
-        Date dt (value, conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+        Date dt (value, context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
         char epoch [16];
         sprintf (epoch, &quot;%d&quot;, (int)dt.toEpoch ());
         return std::string (epoch);
@@ -87,37 +92,22 @@ static std::string findDate (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static std::string formatStatus (T&amp; task)
-{
-  switch (task.getStatus ())
-  {
-  case T::pending:   return &quot;Pending&quot;;   break;
-  case T::completed: return &quot;Completed&quot;; break;
-  case T::deleted:   return &quot;Deleted&quot;;   break;
-  case T::recurring: return &quot;Recurring&quot;; break;
-  }
-
-  return &quot;&quot;;
-}
-
-////////////////////////////////////////////////////////////////////////////////
 static std::string formatDate (
-  Config&amp; conf,
-  T&amp; task,
+  Task&amp; task,
   const std::string&amp; attribute)
 {
-  std::string value = task.getAttribute (attribute);
+  std::string value = task.get (attribute);
   if (value.length ())
   {
     Date dt (::atoi (value.c_str ()));
-    value = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+    value = dt.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
   }
 
   return value;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static std::string formatTask (Config&amp; conf, T task)
+static std::string formatTask (Task task)
 {
   std::stringstream before;
   before &lt;&lt; &quot;# The 'task edit &lt;id&gt;' command allows you to modify all aspects of a task&quot; &lt;&lt; std::endl
@@ -137,13 +127,13 @@ static std::string formatTask (Config&amp; conf, T task)
          &lt;&lt; &quot;#&quot;                                                                         &lt;&lt; std::endl
          &lt;&lt; &quot;# Name               Editable details&quot;                                     &lt;&lt; std::endl
          &lt;&lt; &quot;# -----------------  ----------------------------------------------------&quot; &lt;&lt; std::endl
-         &lt;&lt; &quot;# ID:                &quot; &lt;&lt; task.getId ()                                    &lt;&lt; std::endl
-         &lt;&lt; &quot;# UUID:              &quot; &lt;&lt; task.getUUID ()                                  &lt;&lt; std::endl
-         &lt;&lt; &quot;# Status:            &quot; &lt;&lt; formatStatus (task)                              &lt;&lt; std::endl
-         &lt;&lt; &quot;# Mask:              &quot; &lt;&lt; task.getAttribute (&quot;mask&quot;)                       &lt;&lt; std::endl
-         &lt;&lt; &quot;# iMask:             &quot; &lt;&lt; task.getAttribute (&quot;imask&quot;)                      &lt;&lt; std::endl
-         &lt;&lt; &quot;  Project:           &quot; &lt;&lt; task.getAttribute (&quot;project&quot;)                    &lt;&lt; std::endl
-         &lt;&lt; &quot;  Priority:          &quot; &lt;&lt; task.getAttribute (&quot;priority&quot;)                   &lt;&lt; std::endl;
+         &lt;&lt; &quot;# ID:                &quot; &lt;&lt; task.id                                          &lt;&lt; std::endl
+         &lt;&lt; &quot;# UUID:              &quot; &lt;&lt; task.get (&quot;uuid&quot;)                                &lt;&lt; std::endl
+         &lt;&lt; &quot;# Status:            &quot; &lt;&lt; ucFirst (Task::statusToText (task.getStatus ())) &lt;&lt; std::endl
+         &lt;&lt; &quot;# Mask:              &quot; &lt;&lt; task.get (&quot;mask&quot;)                                &lt;&lt; std::endl
+         &lt;&lt; &quot;# iMask:             &quot; &lt;&lt; task.get (&quot;imask&quot;)                               &lt;&lt; std::endl
+         &lt;&lt; &quot;  Project:           &quot; &lt;&lt; task.get (&quot;project&quot;)                             &lt;&lt; std::endl
+         &lt;&lt; &quot;  Priority:          &quot; &lt;&lt; task.get (&quot;priority&quot;)                            &lt;&lt; std::endl;
 
   std::vector &lt;std::string&gt; tags;
   task.getTags (tags);
@@ -153,26 +143,27 @@ static std::string formatTask (Config&amp; conf, T task)
          &lt;&lt; &quot;  Tags:              &quot; &lt;&lt; allTags                                          &lt;&lt; std::endl
          &lt;&lt; &quot;# The description field is allowed to wrap and use multiple lines.  Task&quot;  &lt;&lt; std::endl
          &lt;&lt; &quot;# will combine them.&quot;                                                      &lt;&lt; std::endl
-         &lt;&lt; &quot;  Description:       &quot; &lt;&lt; task.getDescription ()                           &lt;&lt; std::endl
-         &lt;&lt; &quot;  Created:           &quot; &lt;&lt; formatDate (conf, task, &quot;entry&quot;)                 &lt;&lt; std::endl
-         &lt;&lt; &quot;  Started:           &quot; &lt;&lt; formatDate (conf, task, &quot;start&quot;)                 &lt;&lt; std::endl
-         &lt;&lt; &quot;  Ended:             &quot; &lt;&lt; formatDate (conf, task, &quot;end&quot;)                   &lt;&lt; std::endl
-         &lt;&lt; &quot;  Due:               &quot; &lt;&lt; formatDate (conf, task, &quot;due&quot;)                   &lt;&lt; std::endl
-         &lt;&lt; &quot;  Until:             &quot; &lt;&lt; formatDate (conf, task, &quot;until&quot;)                 &lt;&lt; std::endl
-         &lt;&lt; &quot;  Recur:             &quot; &lt;&lt; task.getAttribute (&quot;recur&quot;)                      &lt;&lt; std::endl
-         &lt;&lt; &quot;  Parent:            &quot; &lt;&lt; task.getAttribute (&quot;parent&quot;)                     &lt;&lt; std::endl
-         &lt;&lt; &quot;  Foreground color:  &quot; &lt;&lt; task.getAttribute (&quot;fg&quot;)                         &lt;&lt; std::endl
-         &lt;&lt; &quot;  Background color:  &quot; &lt;&lt; task.getAttribute (&quot;bg&quot;)                         &lt;&lt; std::endl
+         &lt;&lt; &quot;  Description:       &quot; &lt;&lt; task.get (&quot;description&quot;)                         &lt;&lt; std::endl
+         &lt;&lt; &quot;  Created:           &quot; &lt;&lt; formatDate (task, &quot;entry&quot;)                       &lt;&lt; std::endl
+         &lt;&lt; &quot;  Started:           &quot; &lt;&lt; formatDate (task, &quot;start&quot;)                       &lt;&lt; std::endl
+         &lt;&lt; &quot;  Ended:             &quot; &lt;&lt; formatDate (task, &quot;end&quot;)                         &lt;&lt; std::endl
+         &lt;&lt; &quot;  Due:               &quot; &lt;&lt; formatDate (task, &quot;due&quot;)                         &lt;&lt; std::endl
+         &lt;&lt; &quot;  Until:             &quot; &lt;&lt; formatDate (task, &quot;until&quot;)                       &lt;&lt; std::endl
+         &lt;&lt; &quot;  Recur:             &quot; &lt;&lt; task.get (&quot;recur&quot;)                               &lt;&lt; std::endl
+         &lt;&lt; &quot;  Wait until:        &quot; &lt;&lt; task.get (&quot;wait&quot;)                                &lt;&lt; std::endl
+         &lt;&lt; &quot;  Parent:            &quot; &lt;&lt; task.get (&quot;parent&quot;)                              &lt;&lt; std::endl
+         &lt;&lt; &quot;  Foreground color:  &quot; &lt;&lt; task.get (&quot;fg&quot;)                                  &lt;&lt; std::endl
+         &lt;&lt; &quot;  Background color:  &quot; &lt;&lt; task.get (&quot;bg&quot;)                                  &lt;&lt; std::endl
          &lt;&lt; &quot;# Annotations look like this: &lt;date&gt; &lt;text&gt;, and there can be any number&quot;  &lt;&lt; std::endl
          &lt;&lt; &quot;# of them.&quot;                                                                &lt;&lt; std::endl;
 
-  std::map &lt;time_t, std::string&gt; annotations;
+  std::vector &lt;Att&gt; annotations;
   task.getAnnotations (annotations);
   foreach (anno, annotations)
   {
-    Date dt (anno-&gt;first);
-    before &lt;&lt; &quot;  Annotation:        &quot; &lt;&lt; dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;))
-           &lt;&lt; &quot; &quot;                     &lt;&lt; anno-&gt;second                                   &lt;&lt; std::endl;
+    Date dt (::atoi (anno-&gt;name ().substr (11, std::string::npos).c_str ()));
+    before &lt;&lt; &quot;  Annotation:        &quot; &lt;&lt; dt.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;))
+           &lt;&lt; &quot; &quot;                     &lt;&lt; anno-&gt;value ()                                 &lt;&lt; std::endl;
   }
 
   before &lt;&lt; &quot;  Annotation:        &quot;                                                     &lt;&lt; std::endl
@@ -182,40 +173,40 @@ static std::string formatTask (Config&amp; conf, T task)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static void parseTask (Config&amp; conf, T&amp; task, const std::string&amp; after)
+static void parseTask (Task&amp; task, const std::string&amp; after)
 {
   // project
   std::string value = findValue (after, &quot;Project:&quot;);
-  if (task.getAttribute (&quot;project&quot;) != value)
+  if (task.get (&quot;project&quot;) != value)
   {
     if (value != &quot;&quot;)
     {
       std::cout &lt;&lt; &quot;Project modified.&quot; &lt;&lt; std::endl;
-      task.setAttribute (&quot;project&quot;, value);
+      task.set (&quot;project&quot;, value);
     }
     else
     {
       std::cout &lt;&lt; &quot;Project deleted.&quot; &lt;&lt; std::endl;
-      task.removeAttribute (&quot;project&quot;);
+      task.remove (&quot;project&quot;);
     }
   }
 
   // priority
   value = findValue (after, &quot;Priority:&quot;);
-  if (task.getAttribute (&quot;priority&quot;) != value)
+  if (task.get (&quot;priority&quot;) != value)
   {
     if (value != &quot;&quot;)
     {
-      if (validPriority (value))
+      if (Att::validNameValue (&quot;priority&quot;, &quot;&quot;, value))
       {
         std::cout &lt;&lt; &quot;Priority modified.&quot; &lt;&lt; std::endl;
-        task.setAttribute (&quot;priority&quot;, value);
+        task.set (&quot;priority&quot;, value);
       }
     }
     else
     {
       std::cout &lt;&lt; &quot;Priority deleted.&quot; &lt;&lt; std::endl;
-      task.removeAttribute (&quot;priority&quot;);
+      task.remove (&quot;priority&quot;);
     }
   }
 
@@ -223,177 +214,178 @@ static void parseTask (Config&amp; conf, T&amp; task, const std::string&amp; after)
   value = findValue (after, &quot;Tags:&quot;);
   std::vector &lt;std::string&gt; tags;
   split (tags, value, ' ');
-  task.removeTags ();
+  task.remove (&quot;tags&quot;);
   task.addTags (tags);
 
   // description.
   value = findValue (after, &quot;Description: &quot;);
-  if (task.getDescription () != value)
+  if (task.get (&quot;description&quot;) != value)
   {
     if (value != &quot;&quot;)
     {
       std::cout &lt;&lt; &quot;Description modified.&quot; &lt;&lt; std::endl;
-      task.setDescription (value);
+      task.set (&quot;description&quot;, value);
     }
     else
       throw std::string (&quot;Cannot remove description.&quot;);
   }
 
   // entry
-  value = findDate (conf, after, &quot;Created:&quot;);
+  value = findDate (after, &quot;Created:&quot;);
   if (value != &quot;&quot;)
   {
     Date edited (::atoi (value.c_str ()));
 
-    Date original (::atoi (task.getAttribute (&quot;entry&quot;).c_str ()));
+    Date original (::atoi (task.get (&quot;entry&quot;).c_str ()));
     if (!original.sameDay (edited))
     {
       std::cout &lt;&lt; &quot;Creation date modified.&quot; &lt;&lt; std::endl;
-      task.setAttribute (&quot;entry&quot;, value);
+      task.set (&quot;entry&quot;, value);
     }
   }
   else
     throw std::string (&quot;Cannot remove creation date&quot;);
 
   // start
-  value = findDate (conf, after, &quot;Started:&quot;);
+  value = findDate (after, &quot;Started:&quot;);
   if (value != &quot;&quot;)
   {
     Date edited (::atoi (value.c_str ()));
 
-    if (task.getAttribute (&quot;start&quot;) != &quot;&quot;)
+    if (task.get (&quot;start&quot;) != &quot;&quot;)
     {
-      Date original (::atoi (task.getAttribute (&quot;start&quot;).c_str ()));
+      Date original (::atoi (task.get (&quot;start&quot;).c_str ()));
       if (!original.sameDay (edited))
       {
         std::cout &lt;&lt; &quot;Start date modified.&quot; &lt;&lt; std::endl;
-        task.setAttribute (&quot;start&quot;, value);
+        task.set (&quot;start&quot;, value);
       }
     }
     else
     {
       std::cout &lt;&lt; &quot;Start date modified.&quot; &lt;&lt; std::endl;
-      task.setAttribute (&quot;start&quot;, value);
+      task.set (&quot;start&quot;, value);
     }
   }
   else
   {
-    if (task.getAttribute (&quot;start&quot;) != &quot;&quot;)
+    if (task.get (&quot;start&quot;) != &quot;&quot;)
     {
       std::cout &lt;&lt; &quot;Start date removed.&quot; &lt;&lt; std::endl;
-      task.removeAttribute (&quot;start&quot;);
+      task.remove (&quot;start&quot;);
     }
   }
 
   // end
-  value = findDate (conf, after, &quot;Ended:&quot;);
+  value = findDate (after, &quot;Ended:&quot;);
   if (value != &quot;&quot;)
   {
     Date edited (::atoi (value.c_str ()));
 
-    if (task.getAttribute (&quot;end&quot;) != &quot;&quot;)
+    if (task.get (&quot;end&quot;) != &quot;&quot;)
     {
-      Date original (::atoi (task.getAttribute (&quot;end&quot;).c_str ()));
+      Date original (::atoi (task.get (&quot;end&quot;).c_str ()));
       if (!original.sameDay (edited))
       {
         std::cout &lt;&lt; &quot;Done date modified.&quot; &lt;&lt; std::endl;
-        task.setAttribute (&quot;end&quot;, value);
+        task.set (&quot;end&quot;, value);
       }
     }
-    else if (task.getStatus () != T::deleted)
+    else if (task.getStatus () != Task::deleted)
       throw std::string (&quot;Cannot set a done date on a pending task.&quot;);
   }
   else
   {
-    if (task.getAttribute (&quot;end&quot;) != &quot;&quot;)
+    if (task.get (&quot;end&quot;) != &quot;&quot;)
     {
       std::cout &lt;&lt; &quot;Done date removed.&quot; &lt;&lt; std::endl;
-      task.setStatus (T::pending);
-      task.removeAttribute (&quot;end&quot;);
+      task.setStatus (Task::pending);
+      task.remove (&quot;end&quot;);
     }
   }
 
   // due
-  value = findDate (conf, after, &quot;Due:&quot;);
+  value = findDate (after, &quot;Due:&quot;);
   if (value != &quot;&quot;)
   {
     Date edited (::atoi (value.c_str ()));
 
-    if (task.getAttribute (&quot;due&quot;) != &quot;&quot;)
+    if (task.get (&quot;due&quot;) != &quot;&quot;)
     {
-      Date original (::atoi (task.getAttribute (&quot;due&quot;).c_str ()));
+      Date original (::atoi (task.get (&quot;due&quot;).c_str ()));
       if (!original.sameDay (edited))
       {
         std::cout &lt;&lt; &quot;Due date modified.&quot; &lt;&lt; std::endl;
-        task.setAttribute (&quot;due&quot;, value);
+        task.set (&quot;due&quot;, value);
       }
     }
     else
     {
       std::cout &lt;&lt; &quot;Due date modified.&quot; &lt;&lt; std::endl;
-      task.setAttribute (&quot;due&quot;, value);
+      task.set (&quot;due&quot;, value);
     }
   }
   else
   {
-    if (task.getAttribute (&quot;due&quot;) != &quot;&quot;)
+    if (task.get (&quot;due&quot;) != &quot;&quot;)
     {
-      if (task.getStatus () == T::recurring ||
-          task.getAttribute (&quot;parent&quot;) != &quot;&quot;)
+      if (task.getStatus () == Task::recurring ||
+          task.get (&quot;parent&quot;) != &quot;&quot;)
       {
         std::cout &lt;&lt; &quot;Cannot remove a due date from a recurring task.&quot; &lt;&lt; std::endl;
       }
       else
       {
         std::cout &lt;&lt; &quot;Due date removed.&quot; &lt;&lt; std::endl;
-        task.removeAttribute (&quot;due&quot;);
+        task.remove (&quot;due&quot;);
       }
     }
   }
 
   // until
-  value = findDate (conf, after, &quot;Until:&quot;);
+  value = findDate (after, &quot;Until:&quot;);
   if (value != &quot;&quot;)
   {
     Date edited (::atoi (value.c_str ()));
 
-    if (task.getAttribute (&quot;until&quot;) != &quot;&quot;)
+    if (task.get (&quot;until&quot;) != &quot;&quot;)
     {
-      Date original (::atoi (task.getAttribute (&quot;until&quot;).c_str ()));
+      Date original (::atoi (task.get (&quot;until&quot;).c_str ()));
       if (!original.sameDay (edited))
       {
         std::cout &lt;&lt; &quot;Until date modified.&quot; &lt;&lt; std::endl;
-        task.setAttribute (&quot;until&quot;, value);
+        task.set (&quot;until&quot;, value);
       }
     }
     else
     {
       std::cout &lt;&lt; &quot;Until date modified.&quot; &lt;&lt; std::endl;
-      task.setAttribute (&quot;until&quot;, value);
+      task.set (&quot;until&quot;, value);
     }
   }
   else
   {
-    if (task.getAttribute (&quot;until&quot;) != &quot;&quot;)
+    if (task.get (&quot;until&quot;) != &quot;&quot;)
     {
       std::cout &lt;&lt; &quot;Until date removed.&quot; &lt;&lt; std::endl;
-      task.removeAttribute (&quot;until&quot;);
+      task.remove (&quot;until&quot;);
     }
   }
 
   // recur
   value = findValue (after, &quot;Recur:&quot;);
-  if (value != task.getAttribute (&quot;recur&quot;))
+  if (value != task.get (&quot;recur&quot;))
   {
     if (value != &quot;&quot;)
     {
-      if (validDuration (value))
+      Duration d;
+      if (d.valid (value))
       {
         std::cout &lt;&lt; &quot;Recurrence modified.&quot; &lt;&lt; std::endl;
-        if (task.getAttribute (&quot;due&quot;) != &quot;&quot;)
+        if (task.get (&quot;due&quot;) != &quot;&quot;)
         {
-          task.setAttribute (&quot;recur&quot;, value);
-          task.setStatus (T::recurring);
+          task.set (&quot;recur&quot;, value);
+          task.setStatus (Task::recurring);
         }
         else
           throw std::string (&quot;A recurring task must have a due date.&quot;);
@@ -404,64 +396,94 @@ static void parseTask (Config&amp; conf, T&amp; task, const std::string&amp; after)
     else
     {
       std::cout &lt;&lt; &quot;Recurrence removed.&quot; &lt;&lt; std::endl;
-      task.setStatus (T::pending);
-      task.removeAttribute (&quot;recur&quot;);
-      task.removeAttribute (&quot;until&quot;);
-      task.removeAttribute (&quot;mask&quot;);
-      task.removeAttribute (&quot;imask&quot;);
+      task.setStatus (Task::pending);
+      task.remove (&quot;recur&quot;);
+      task.remove (&quot;until&quot;);
+      task.remove (&quot;mask&quot;);
+      task.remove (&quot;imask&quot;);
+    }
+  }
+
+  // wait
+  value = findDate (after, &quot;Wait until:&quot;);
+  if (value != &quot;&quot;)
+  {
+    Date edited (::atoi (value.c_str ()));
+
+    if (task.get (&quot;wait&quot;) != &quot;&quot;)
+    {
+      Date original (::atoi (task.get (&quot;wait&quot;).c_str ()));
+      if (!original.sameDay (edited))
+      {
+        std::cout &lt;&lt; &quot;Wait date modified.&quot; &lt;&lt; std::endl;
+        task.set (&quot;wait&quot;, value);
+      }
+    }
+    else
+    {
+      std::cout &lt;&lt; &quot;Wait date modified.&quot; &lt;&lt; std::endl;
+      task.set (&quot;wait&quot;, value);
+    }
+  }
+  else
+  {
+    if (task.get (&quot;wait&quot;) != &quot;&quot;)
+    {
+      std::cout &lt;&lt; &quot;Wait date removed.&quot; &lt;&lt; std::endl;
+      task.remove (&quot;wait&quot;);
     }
   }
 
   // parent
   value = findValue (after, &quot;Parent:&quot;);
-  if (value != task.getAttribute (&quot;parent&quot;))
+  if (value != task.get (&quot;parent&quot;))
   {
     if (value != &quot;&quot;)
     {
       std::cout &lt;&lt; &quot;Parent UUID modified.&quot; &lt;&lt; std::endl;
-      task.setAttribute (&quot;parent&quot;, value);
+      task.set (&quot;parent&quot;, value);
     }
     else
     {
       std::cout &lt;&lt; &quot;Parent UUID removed.&quot; &lt;&lt; std::endl;
-      task.removeAttribute (&quot;parent&quot;);
+      task.remove (&quot;parent&quot;);
     }
   }
 
   // fg
   value = findValue (after, &quot;Foreground color:&quot;);
-  if (value != task.getAttribute (&quot;fg&quot;))
+  if (value != task.get (&quot;fg&quot;))
   {
     if (value != &quot;&quot;)
     {
       std::cout &lt;&lt; &quot;Foreground color modified.&quot; &lt;&lt; std::endl;
-      task.setAttribute (&quot;fg&quot;, value);
+      task.set (&quot;fg&quot;, value);
     }
     else
     {
       std::cout &lt;&lt; &quot;Foreground color removed.&quot; &lt;&lt; std::endl;
-      task.removeAttribute (&quot;fg&quot;);
+      task.remove (&quot;fg&quot;);
     }
   }
 
   // bg
   value = findValue (after, &quot;Background color:&quot;);
-  if (value != task.getAttribute (&quot;bg&quot;))
+  if (value != task.get (&quot;bg&quot;))
   {
     if (value != &quot;&quot;)
     {
       std::cout &lt;&lt; &quot;Background color modified.&quot; &lt;&lt; std::endl;
-      task.setAttribute (&quot;bg&quot;, value);
+      task.set (&quot;bg&quot;, value);
     }
     else
     {
       std::cout &lt;&lt; &quot;Background color removed.&quot; &lt;&lt; std::endl;
-      task.removeAttribute (&quot;bg&quot;);
+      task.remove (&quot;bg&quot;);
     }
   }
 
   // Annotations
-  std::map &lt;time_t, std::string&gt; annotations;
+  std::vector &lt;Att&gt; annotations;
   std::string::size_type found = 0;
   while ((found = after.find (&quot;Annotation:&quot;, found)) != std::string::npos)
   {
@@ -478,8 +500,10 @@ static void parseTask (Config&amp; conf, T&amp; task, const std::string&amp; after)
       if (gap != std::string::npos)
       {
         Date when (value.substr (0, gap));
+        std::stringstream name;
+        name &lt;&lt; &quot;annotation_&quot; &lt;&lt; when.toEpoch ();
         std::string text = trim (value.substr (gap, std::string::npos), &quot;\t &quot;);
-        annotations[when.toEpoch ()] = text;
+        annotations.push_back (Att (name.str (), text));
       }
     }
   }
@@ -488,100 +512,131 @@ static void parseTask (Config&amp; conf, T&amp; task, const std::string&amp; after)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Introducing the Silver Bullet.  This feature is the catch-all fixative for
-// various other ills.  This is like opening up the hood and going in with a
-// wrench.  To be used sparingly.
-std::string handleEdit (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+void editFile (Task&amp; task)
 {
-  std::stringstream out;
-  std::vector &lt;T&gt; all;
-  tdb.allPendingT (all);
-
-  filterSequence (all, task);
-  foreach (seq, all)
-  {
-    // Check for file permissions.
-    std::string dataLocation = expandPath (conf.get (&quot;data.location&quot;));
-    if (access (dataLocation.c_str (), X_OK))
-      throw std::string (&quot;Your data.location directory is not writable.&quot;);
-
-    // Create a temp file name in data.location.
-    std::stringstream pattern;
-    pattern &lt;&lt; dataLocation &lt;&lt; &quot;/task.&quot; &lt;&lt; seq-&gt;getId () &lt;&lt; &quot;.XXXXXX&quot;;
-    char cpattern [PATH_MAX];
-    strcpy (cpattern, pattern.str ().c_str ());
-    mkstemp (cpattern);
-    char* file = cpattern;
-
-    // Format the contents, T -&gt; text, write to a file.
-    std::string before = formatTask (conf, *seq);
-    spit (file, before);
-
-    // Determine correct editor: .taskrc:editor &gt; $VISUAL &gt; $EDITOR &gt; vi
-    std::string editor = conf.get (&quot;editor&quot;, &quot;&quot;);
-    char* peditor = getenv (&quot;VISUAL&quot;);
-    if (editor == &quot;&quot; &amp;&amp; peditor) editor = std::string (peditor);
-    peditor = getenv (&quot;EDITOR&quot;);
-    if (editor == &quot;&quot; &amp;&amp; peditor) editor = std::string (peditor);
-    if (editor == &quot;&quot;) editor = &quot;vi&quot;;
-
-    // Complete the command line.
-    editor += &quot; &quot;;
-    editor += file;
+  // Check for file permissions.
+  std::string dataLocation = expandPath (context.config.get (&quot;data.location&quot;));
+  if (access (dataLocation.c_str (), X_OK))
+    throw std::string (&quot;Your data.location directory is not writable.&quot;);
+
+  // Create a temp file name in data.location.
+  std::stringstream file;
+  file &lt;&lt; dataLocation &lt;&lt; &quot;/task.&quot; &lt;&lt; getpid () &lt;&lt; &quot;.&quot; &lt;&lt; task.id &lt;&lt; &quot;.task&quot;;
+
+  // Format the contents, T -&gt; text, write to a file.
+  std::string before = formatTask (task);
+  spit (file.str (), before);
+
+  // Determine correct editor: .taskrc:editor &gt; $VISUAL &gt; $EDITOR &gt; vi
+  std::string editor = context.config.get (&quot;editor&quot;, &quot;&quot;);
+  char* peditor = getenv (&quot;VISUAL&quot;);
+  if (editor == &quot;&quot; &amp;&amp; peditor) editor = std::string (peditor);
+  peditor = getenv (&quot;EDITOR&quot;);
+  if (editor == &quot;&quot; &amp;&amp; peditor) editor = std::string (peditor);
+  if (editor == &quot;&quot;) editor = &quot;vi&quot;;
+
+  // Complete the command line.
+  editor += &quot; &quot;;
+  editor += file.str ();
 
 ARE_THESE_REALLY_HARMFUL:
-    // Launch the editor.
-    std::cout &lt;&lt; &quot;Launching '&quot; &lt;&lt; editor &lt;&lt; &quot;' now...&quot; &lt;&lt; std::endl;
-    system (editor.c_str ());
+  // Launch the editor.
+  std::cout &lt;&lt; &quot;Launching '&quot; &lt;&lt; editor &lt;&lt; &quot;' now...&quot; &lt;&lt; std::endl;
+  if (-1 == system (editor.c_str ()))
+    std::cout &lt;&lt; &quot;No editing performed.&quot; &lt;&lt; std::endl;
+  else
     std::cout &lt;&lt; &quot;Editing complete.&quot; &lt;&lt; std::endl;
 
-    // Slurp file.
-    std::string after;
-    slurp (file, after, false);
+  // Slurp file.
+  std::string after;
+  slurp (file.str (), after, false);
 
-    // TODO Remove this debugging code.
-    //spit (&quot;./before&quot;, before);
-    //spit (&quot;./after&quot;,  after);
+  // Update task based on what can be parsed back out of the file, but only
+  // if changes were made.
+  if (before != after)
+  {
+    std::cout &lt;&lt; &quot;Edits were detected.&quot; &lt;&lt; std::endl;
+    std::string problem = &quot;&quot;;
+    bool oops = false;
 
-    // Update seq based on what can be parsed back out of the file, but only
-    // if changes were made.
-    if (before != after)
+    try
     {
-      std::cout &lt;&lt; &quot;Edits were detected.&quot; &lt;&lt; std::endl;
-      std::string problem = &quot;&quot;;
-      bool oops = false;
+      parseTask (task, after);
+    }
 
-      try
-      {
-        parseTask (conf, *seq, after);
-        tdb.modifyT (*seq);
-      }
+    catch (std::string&amp; e)
+    {
+      problem = e;
+      oops = true;
+    }
 
-      catch (std::string&amp; e)
-      {
-        problem = e;
-        oops = true;
-      }
+    if (oops)
+    {
+      std::cout &lt;&lt; &quot;Error: &quot; &lt;&lt; problem &lt;&lt; std::endl;
 
-      if (oops)
-      {
-        std::cout &lt;&lt; &quot;Error: &quot; &lt;&lt; problem &lt;&lt; std::endl;
+      // Preserve the edits.
+      before = after;
+      spit (file.str (), before);
 
-        // Preserve the edits.
-        before = after;
-        spit (file, before);
+      if (confirm (&quot;Task couldn't handle your edits.  Would you like to try again?&quot;))
+        goto ARE_THESE_REALLY_HARMFUL;
+    }
+  }
+  else
+    std::cout &lt;&lt; &quot;No edits were detected.&quot; &lt;&lt; std::endl;
+
+  // Cleanup.
+  unlink (file.str ().c_str ());
+}
 
-        if (confirm (&quot;Task couldn't handle your edits.  Would you like to try again?&quot;))
-          goto ARE_THESE_REALLY_HARMFUL;
+////////////////////////////////////////////////////////////////////////////////
+// Introducing the Silver Bullet.  This feature is the catch-all fixative for
+// various other ills.  This is like opening up the hood and going in with a
+// wrench.  To be used sparingly.
+std::string handleEdit ()
+{
+  std::stringstream out;
+
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  Filter filter;
+  context.tdb.loadPending (tasks, filter);
+
+  // Filter sequence.
+  std::vector &lt;Task&gt; all = tasks;
+  context.filter.applySequence (tasks, context.sequence);
+
+  foreach (task, tasks)
+  {
+    editFile (*task);
+    context.tdb.update (*task);
+/*
+    foreach (other, all)
+    {
+      if (other-&gt;id != task.id) // Don't edit the same task again.
+      {
+        if (task.has (&quot;parent&quot;) &amp;&amp; 
+        if (other is parent of task)
+        {
+          // Transfer everything but mask, imask, uuid, parent.
+        }
+        else if (task is parent of other)
+        {
+          // Transfer everything but mask, imask, uuid, parent.
+        }
+        else if (task and other are siblings)
+        {
+          // Transfer everything but mask, imask, uuid, parent.
+        }
       }
     }
-    else
-      std::cout &lt;&lt; &quot;No edits were detected.&quot; &lt;&lt; std::endl;
-
-    // Cleanup.
-    unlink (file);
+*/
   }
 
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
   return out.str ();
 }
 </diff>
      <filename>src/edit.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -29,7 +29,11 @@
 #include &lt;stdio.h&gt;
 #include &lt;unistd.h&gt;
 #include &quot;Date.h&quot;
-#include &quot;task.h&quot;
+#include &quot;text.h&quot;
+#include &quot;util.h&quot;
+#include &quot;main.h&quot;
+
+extern Context context;
 
 ////////////////////////////////////////////////////////////////////////////////
 enum fileType
@@ -155,33 +159,34 @@ static fileType determineFileType (const std::vector &lt;std::string&gt;&amp; lines)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static void decorateTask (T&amp; task, Config&amp; conf)
+static void decorateTask (Task&amp; task)
 {
   char entryTime[16];
   sprintf (entryTime, &quot;%u&quot;, (unsigned int) time (NULL));
-  task.setAttribute (&quot;entry&quot;, entryTime);
+  task.set (&quot;entry&quot;, entryTime);
+
+  task.setStatus (Task::pending);
 
   // Override with default.project, if not specified.
-  std::string defaultProject = conf.get (&quot;default.project&quot;, &quot;&quot;);
-  if (task.getAttribute (&quot;project&quot;) == &quot;&quot; &amp;&amp; defaultProject  != &quot;&quot;)
-    task.setAttribute (&quot;project&quot;, defaultProject);
+  std::string defaultProject = context.config.get (&quot;default.project&quot;, &quot;&quot;);
+  if (!task.has (&quot;project&quot;) &amp;&amp; defaultProject  != &quot;&quot;)
+    task.set (&quot;project&quot;, defaultProject);
 
   // Override with default.priority, if not specified.
-  std::string defaultPriority = conf.get (&quot;default.priority&quot;, &quot;&quot;);
-  if (task.getAttribute (&quot;priority&quot;) == &quot;&quot; &amp;&amp;
-      defaultPriority                != &quot;&quot; &amp;&amp;
-      validPriority (defaultPriority))
-    task.setAttribute (&quot;priority&quot;, defaultPriority);
+  std::string defaultPriority = context.config.get (&quot;default.priority&quot;, &quot;&quot;);
+  if (!task.has (&quot;priority&quot;) &amp;&amp;
+      defaultPriority != &quot;&quot; &amp;&amp;
+      Att::validNameValue (&quot;priority&quot;, &quot;&quot;, defaultPriority))
+    task.set (&quot;priority&quot;, defaultPriority);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static std::string importTask_1_4_3 (
-  TDB&amp; tdb,
-  Config&amp; conf,
-  const std::vector &lt;std::string&gt;&amp; lines)
+static std::string importTask_1_4_3 (const std::vector &lt;std::string&gt;&amp; lines)
 {
   std::vector &lt;std::string&gt; failed;
 
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+
   std::vector &lt;std::string&gt;::const_iterator it;
   for (it = lines.begin (); it != lines.end (); ++it)
   {
@@ -227,7 +232,7 @@ static std::string importTask_1_4_3 (
         throw &quot;unrecoverable&quot;;
 
       // Build up this task ready for insertion.
-      T task;
+      Task task;
 
       // Handle the 12 fields.
       for (unsigned int f = 0; f &lt; fields.size (); ++f)
@@ -235,14 +240,14 @@ static std::string importTask_1_4_3 (
         switch (f)
         {
         case 0: // 'uuid'
-          task.setUUID (fields[f].substr (1, 36));
+          task.set (&quot;uuid&quot;, fields[f].substr (1, 36));
           break;
 
         case 1: // 'status'
-               if (fields[f] == &quot;'pending'&quot;)   task.setStatus (T::pending);
-          else if (fields[f] == &quot;'recurring'&quot;) task.setStatus (T::recurring);
-          else if (fields[f] == &quot;'deleted'&quot;)   task.setStatus (T::deleted);
-          else if (fields[f] == &quot;'completed'&quot;) task.setStatus (T::completed);
+               if (fields[f] == &quot;'pending'&quot;)   task.setStatus (Task::pending);
+          else if (fields[f] == &quot;'recurring'&quot;) task.setStatus (Task::recurring);
+          else if (fields[f] == &quot;'deleted'&quot;)   task.setStatus (Task::deleted);
+          else if (fields[f] == &quot;'completed'&quot;) task.setStatus (Task::completed);
           break;
 
         case 2: // 'tags'
@@ -257,53 +262,52 @@ static std::string importTask_1_4_3 (
           break;
 
         case 3: // entry
-          task.setAttribute (&quot;entry&quot;, fields[f]);
+          task.set (&quot;entry&quot;, fields[f]);
           break;
 
         case 4: // start
           if (fields[f].length ())
-            task.setAttribute (&quot;start&quot;, fields[f]);
+            task.set (&quot;start&quot;, fields[f]);
           break;
 
         case 5: // due
           if (fields[f].length ())
-            task.setAttribute (&quot;due&quot;, fields[f]);
+            task.set (&quot;due&quot;, fields[f]);
           break;
 
         case 6: // end
           if (fields[f].length ())
-            task.setAttribute (&quot;end&quot;, fields[f]);
+            task.set (&quot;end&quot;, fields[f]);
           break;
 
         case 7: // 'project'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;project&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;project&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 8: // 'priority'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;priority&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;priority&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 9: // 'fg'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;fg&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;fg&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 10: // 'bg'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;bg&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;bg&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 11: // 'description'
           if (fields[f].length () &gt; 2)
-            task.setDescription (fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;description&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
         }
       }
 
-      if (! tdb.addT (task))
-        failed.push_back (*it);
+      context.tdb.add (task);
     }
 
     catch (...)
@@ -312,6 +316,9 @@ static std::string importTask_1_4_3 (
     }
   }
 
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
   std::stringstream out;
   out &lt;&lt; &quot;Imported &quot;
       &lt;&lt; (lines.size () - failed.size () - 1)
@@ -331,13 +338,12 @@ static std::string importTask_1_4_3 (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static std::string importTask_1_5_0 (
-  TDB&amp; tdb,
-  Config&amp; conf,
-  const std::vector &lt;std::string&gt;&amp; lines)
+static std::string importTask_1_5_0 (const std::vector &lt;std::string&gt;&amp; lines)
 {
   std::vector &lt;std::string&gt; failed;
 
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+
   std::vector &lt;std::string&gt;::const_iterator it;
   for (it = lines.begin (); it != lines.end (); ++it)
   {
@@ -383,7 +389,7 @@ static std::string importTask_1_5_0 (
         throw &quot;unrecoverable&quot;;
 
       // Build up this task ready for insertion.
-      T task;
+      Task task;
 
       // Handle the 13 fields.
       for (unsigned int f = 0; f &lt; fields.size (); ++f)
@@ -391,14 +397,14 @@ static std::string importTask_1_5_0 (
         switch (f)
         {
         case 0: // 'uuid'
-          task.setUUID (fields[f].substr (1, 36));
+          task.set (&quot;uuid&quot;, fields[f].substr (1, 36));
           break;
 
         case 1: // 'status'
-               if (fields[f] == &quot;'pending'&quot;)   task.setStatus (T::pending);
-          else if (fields[f] == &quot;'recurring'&quot;) task.setStatus (T::recurring);
-          else if (fields[f] == &quot;'deleted'&quot;)   task.setStatus (T::deleted);
-          else if (fields[f] == &quot;'completed'&quot;) task.setStatus (T::completed);
+               if (fields[f] == &quot;'pending'&quot;)   task.setStatus (Task::pending);
+          else if (fields[f] == &quot;'recurring'&quot;) task.setStatus (Task::recurring);
+          else if (fields[f] == &quot;'deleted'&quot;)   task.setStatus (Task::deleted);
+          else if (fields[f] == &quot;'completed'&quot;) task.setStatus (Task::completed);
           break;
 
         case 2: // 'tags'
@@ -413,58 +419,57 @@ static std::string importTask_1_5_0 (
           break;
 
         case 3: // entry
-          task.setAttribute (&quot;entry&quot;, fields[f]);
+          task.set (&quot;entry&quot;, fields[f]);
           break;
 
         case 4: // start
           if (fields[f].length ())
-            task.setAttribute (&quot;start&quot;, fields[f]);
+            task.set (&quot;start&quot;, fields[f]);
           break;
 
         case 5: // due
           if (fields[f].length ())
-            task.setAttribute (&quot;due&quot;, fields[f]);
+            task.set (&quot;due&quot;, fields[f]);
           break;
 
         case 6: // recur
           if (fields[f].length ())
-            task.setAttribute (&quot;recur&quot;, fields[f]);
+            task.set (&quot;recur&quot;, fields[f]);
           break;
 
         case 7: // end
           if (fields[f].length ())
-            task.setAttribute (&quot;end&quot;, fields[f]);
+            task.set (&quot;end&quot;, fields[f]);
           break;
 
         case 8: // 'project'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;project&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;project&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 9: // 'priority'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;priority&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;priority&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 10: // 'fg'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;fg&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;fg&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 11: // 'bg'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;bg&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;bg&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 12: // 'description'
           if (fields[f].length () &gt; 2)
-            task.setDescription (fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;description&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
         }
       }
 
-      if (! tdb.addT (task))
-        failed.push_back (*it);
+      context.tdb.add (task);
     }
 
     catch (...)
@@ -473,6 +478,9 @@ static std::string importTask_1_5_0 (
     }
   }
 
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
   std::stringstream out;
   out &lt;&lt; &quot;Imported &quot;
       &lt;&lt; (lines.size () - failed.size () - 1)
@@ -492,13 +500,12 @@ static std::string importTask_1_5_0 (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static std::string importTask_1_6_0 (
-  TDB&amp; tdb,
-  Config&amp; conf,
-  const std::vector &lt;std::string&gt;&amp; lines)
+static std::string importTask_1_6_0 (const std::vector &lt;std::string&gt;&amp; lines)
 {
   std::vector &lt;std::string&gt; failed;
 
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+
   std::vector &lt;std::string&gt;::const_iterator it;
   for (it = lines.begin (); it != lines.end (); ++it)
   {
@@ -544,7 +551,7 @@ static std::string importTask_1_6_0 (
         throw &quot;unrecoverable&quot;;
 
       // Build up this task ready for insertion.
-      T task;
+      Task task;
 
       // Handle the 13 fields.
       for (unsigned int f = 0; f &lt; fields.size (); ++f)
@@ -552,14 +559,15 @@ static std::string importTask_1_6_0 (
         switch (f)
         {
         case 0: // 'uuid'
-          task.setUUID (fields[f].substr (1, 36));
+          task.set (&quot;uuid&quot;, fields[f].substr (1, 36));
           break;
 
         case 1: // 'status'
-               if (fields[f] == &quot;'pending'&quot;)   task.setStatus (T::pending);
-          else if (fields[f] == &quot;'recurring'&quot;) task.setStatus (T::recurring);
-          else if (fields[f] == &quot;'deleted'&quot;)   task.setStatus (T::deleted);
-          else if (fields[f] == &quot;'completed'&quot;) task.setStatus (T::completed);
+               if (fields[f] == &quot;'pending'&quot;)   task.setStatus (Task::pending);
+          else if (fields[f] == &quot;'recurring'&quot;) task.setStatus (Task::recurring);
+          else if (fields[f] == &quot;'deleted'&quot;)   task.setStatus (Task::deleted);
+          else if (fields[f] == &quot;'completed'&quot;) task.setStatus (Task::completed);
+          else if (fields[f] == &quot;'waiting'&quot;)   task.setStatus (Task::waiting);
           break;
 
         case 2: // 'tags'
@@ -574,58 +582,57 @@ static std::string importTask_1_6_0 (
           break;
 
         case 3: // entry
-          task.setAttribute (&quot;entry&quot;, fields[f]);
+          task.set (&quot;entry&quot;, fields[f]);
           break;
 
         case 4: // start
           if (fields[f].length ())
-            task.setAttribute (&quot;start&quot;, fields[f]);
+            task.set (&quot;start&quot;, fields[f]);
           break;
 
         case 5: // due
           if (fields[f].length ())
-            task.setAttribute (&quot;due&quot;, fields[f]);
+            task.set (&quot;due&quot;, fields[f]);
           break;
 
         case 6: // recur
           if (fields[f].length ())
-            task.setAttribute (&quot;recur&quot;, fields[f]);
+            task.set (&quot;recur&quot;, fields[f]);
           break;
 
         case 7: // end
           if (fields[f].length ())
-            task.setAttribute (&quot;end&quot;, fields[f]);
+            task.set (&quot;end&quot;, fields[f]);
           break;
 
         case 8: // 'project'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;project&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;project&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 9: // 'priority'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;priority&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;priority&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 10: // 'fg'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;fg&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;fg&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 11: // 'bg'
           if (fields[f].length () &gt; 2)
-            task.setAttribute (&quot;bg&quot;, fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;bg&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
 
         case 12: // 'description'
           if (fields[f].length () &gt; 2)
-            task.setDescription (fields[f].substr (1, fields[f].length () - 2));
+            task.set (&quot;description&quot;, fields[f].substr (1, fields[f].length () - 2));
           break;
         }
       }
 
-      if (! tdb.addT (task))
-        failed.push_back (*it);
+      context.tdb.add (task);
     }
 
     catch (...)
@@ -634,6 +641,9 @@ static std::string importTask_1_6_0 (
     }
   }
 
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
   std::stringstream out;
   out &lt;&lt; &quot;Imported &quot;
       &lt;&lt; (lines.size () - failed.size () - 1)
@@ -653,10 +663,7 @@ static std::string importTask_1_6_0 (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static std::string importTaskCmdLine (
-  TDB&amp; tdb,
-  Config&amp; conf,
-  const std::vector &lt;std::string&gt;&amp; lines)
+static std::string importTaskCmdLine (const std::vector &lt;std::string&gt;&amp; lines)
 {
   std::vector &lt;std::string&gt; failed;
 
@@ -667,17 +674,19 @@ static std::string importTaskCmdLine (
 
     try
     {
-      std::vector &lt;std::string&gt; args;
-      split (args, std::string (&quot;add &quot;) + line, ' ');
-
-      T task;
-      std::string command;
-      parse (args, command, task, conf);
-      handleAdd (tdb, task, conf);
+      context.args.clear ();
+      split (context.args, std::string (&quot;add &quot;) + line, ' ');
+
+      context.task.clear ();
+      context.cmd.command = &quot;&quot;;
+      context.parse ();
+      handleAdd ();
+      context.clearMessages ();
     }
 
     catch (...)
     {
+      context.clearMessages ();
       failed.push_back (line);
     }
   }
@@ -701,20 +710,19 @@ static std::string importTaskCmdLine (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static std::string importTodoSh_2_0 (
-  TDB&amp; tdb,
-  Config&amp; conf,
-  const std::vector &lt;std::string&gt;&amp; lines)
+static std::string importTodoSh_2_0 (const std::vector &lt;std::string&gt;&amp; lines)
 {
   std::vector &lt;std::string&gt; failed;
 
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+
   std::vector &lt;std::string&gt;::const_iterator it;
   for (it = lines.begin (); it != lines.end (); ++it)
   {
     try
     {
-      std::vector &lt;std::string&gt; args;
-      args.push_back (&quot;add&quot;);
+      context.args.clear ();
+      context.args.push_back (&quot;add&quot;);
 
       bool isPending = true;
       Date endDate;
@@ -727,8 +735,8 @@ static std::string importTodoSh_2_0 (
         if (words[w].length () &gt; 1 &amp;&amp;
             words[w][0] == '+')
         {
-          args.push_back (std::string (&quot;project:&quot;) +
-                          words[w].substr (1, std::string::npos));
+          context.args.push_back (std::string (&quot;project:&quot;) +
+                                  words[w].substr (1, std::string::npos));
         }
 
         // Convert &quot;+aaa&quot; to &quot;project:aaa&quot;.
@@ -736,8 +744,8 @@ static std::string importTodoSh_2_0 (
         else if (words[w].length () &gt; 1 &amp;&amp;
                  words[w][0] == '@')
         {
-          args.push_back (std::string (&quot;+&quot;) +
-                          words[w].substr (1, std::string::npos));
+          context.args.push_back (std::string (&quot;+&quot;) +
+                                  words[w].substr (1, std::string::npos));
         }
 
         // Convert &quot;(A)&quot; to &quot;priority:H&quot;.
@@ -747,9 +755,9 @@ static std::string importTodoSh_2_0 (
                  words[w][0] == '('      &amp;&amp;
                  words[w][2] == ')')
         {
-               if (words[w][1] == 'A') args.push_back (&quot;priority:H&quot;);
-          else if (words[w][1] == 'B') args.push_back (&quot;priority:M&quot;);
-          else                         args.push_back (&quot;priority:L&quot;);
+               if (words[w][1] == 'A') context.args.push_back (&quot;priority:H&quot;);
+          else if (words[w][1] == 'B') context.args.push_back (&quot;priority:M&quot;);
+          else                         context.args.push_back (&quot;priority:L&quot;);
         }
 
         // Set status, if completed.
@@ -772,38 +780,42 @@ static std::string importTodoSh_2_0 (
         // Just an ordinary word.
         else
         {
-          args.push_back (words[w]);
+          context.args.push_back (words[w]);
         }
       }
 
-      T task;
-      std::string command;
-      parse (args, command, task, conf);
-      decorateTask (task, conf);
+      context.task.clear ();
+      context.cmd.command = &quot;&quot;;
+      context.parse ();
+      decorateTask (context.task);
 
       if (isPending)
       {
-        task.setStatus (T::pending);
+        context.task.setStatus (Task::pending);
       }
       else
       {
-        task.setStatus (T::completed);
+        context.task.setStatus (Task::completed);
 
         char end[16];
         sprintf (end, &quot;%u&quot;, (unsigned int) endDate.toEpoch ());
-        task.setAttribute (&quot;end&quot;, end);
+        context.task.set (&quot;end&quot;, end);
       }
 
-      if (! tdb.addT (task))
-        failed.push_back (*it);
+      context.tdb.add (context.task);
+      context.clearMessages ();
     }
 
     catch (...)
     {
+      context.clearMessages ();
       failed.push_back (*it);
     }
   }
 
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
   std::stringstream out;
   out &lt;&lt; &quot;Imported &quot;
       &lt;&lt; (lines.size () - failed.size ())
@@ -818,19 +830,17 @@ static std::string importTodoSh_2_0 (
     join (bad, &quot;\n&quot;, failed);
     return out.str () + &quot;\nCould not import:\n\n&quot; + bad;
   }
-
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static std::string importText (
-  TDB&amp; tdb,
-  Config&amp; conf,
-  const std::vector &lt;std::string&gt;&amp; lines)
+static std::string importText (const std::vector &lt;std::string&gt;&amp; lines)
 {
   std::vector &lt;std::string&gt; failed;
   int count = 0;
 
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+
   std::vector &lt;std::string&gt;::const_iterator it;
   for (it = lines.begin (); it != lines.end (); ++it)
   {
@@ -847,25 +857,29 @@ static std::string importText (
       try
       {
         ++count;
-        std::vector &lt;std::string&gt; args;
-        split (args, std::string (&quot;add &quot;) + line, ' ');
+        context.args.clear ();
+        split (context.args, std::string (&quot;add &quot;) + line, ' ');
 
-        T task;
-        std::string command;
-        parse (args, command, task, conf);
-        decorateTask (task, conf);
+        context.task.clear ();
+        context.cmd.command = &quot;&quot;;
+        context.parse ();
+        decorateTask (context.task);
 
-        if (! tdb.addT (task))
-          failed.push_back (*it);
+        context.tdb.add (context.task);
+        context.clearMessages ();
       }
 
       catch (...)
       {
+        context.clearMessages ();
         failed.push_back (line);
       }
     }
   }
 
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
   std::stringstream out;
   out &lt;&lt; &quot;Imported &quot;
       &lt;&lt; count
@@ -885,13 +899,12 @@ static std::string importText (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-static std::string importCSV (
-  TDB&amp; tdb,
-  Config&amp; conf,
-  const std::vector &lt;std::string&gt;&amp; lines)
+static std::string importCSV (const std::vector &lt;std::string&gt;&amp; lines)
 {
   std::vector &lt;std::string&gt; failed;
 
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+
   // Set up mappings.  Assume no fields match.
   std::map &lt;std::string, int&gt; mapping;
   mapping [&quot;id&quot;]          = -1;
@@ -917,7 +930,7 @@ static std::string importCSV (
     std::string name = lowerCase (trim (unquoteText (trim (headings[h]))));
 
     // If there is a mapping for the field, use the value.
-    if (name == conf.get (&quot;import.synonym.id&quot;) ||
+    if (name == context.config.get (&quot;import.synonym.id&quot;) ||
         name == &quot;id&quot; ||
         name == &quot;#&quot; ||
         name == &quot;sequence&quot; ||
@@ -926,7 +939,7 @@ static std::string importCSV (
       mapping[&quot;id&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.uuid&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.uuid&quot;) ||
              name == &quot;uuid&quot; ||
              name == &quot;guid&quot; ||
              name.find (&quot;unique&quot;) != std::string::npos)
@@ -934,7 +947,7 @@ static std::string importCSV (
       mapping[&quot;uuid&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.status&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.status&quot;) ||
              name == &quot;status&quot; ||
              name == &quot;condition&quot; ||
              name == &quot;state&quot;)
@@ -942,7 +955,7 @@ static std::string importCSV (
       mapping[&quot;status&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.tags&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.tags&quot;) ||
              name == &quot;tags&quot; ||
              name.find (&quot;categor&quot;) != std::string::npos ||
              name.find (&quot;tag&quot;) != std::string::npos)
@@ -950,7 +963,7 @@ static std::string importCSV (
       mapping[&quot;tags&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.entry&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.entry&quot;) ||
              name == &quot;entry&quot; ||
              name.find (&quot;added&quot;) != std::string::npos ||
              name.find (&quot;created&quot;) != std::string::npos ||
@@ -959,7 +972,7 @@ static std::string importCSV (
       mapping[&quot;entry&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.start&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.start&quot;) ||
              name == &quot;start&quot; ||
              name.find (&quot;began&quot;) != std::string::npos ||
              name.find (&quot;begun&quot;) != std::string::npos ||
@@ -968,21 +981,21 @@ static std::string importCSV (
       mapping[&quot;start&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.due&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.due&quot;) ||
              name == &quot;due&quot; ||
              name.find (&quot;expected&quot;) != std::string::npos)
     {
       mapping[&quot;due&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.recur&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.recur&quot;) ||
              name == &quot;recur&quot; ||
              name == &quot;frequency&quot;)
     {
       mapping[&quot;recur&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.end&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.end&quot;) ||
              name == &quot;end&quot; ||
              name == &quot;done&quot; ||
              name.find (&quot;complete&quot;) != std::string::npos)
@@ -990,14 +1003,14 @@ static std::string importCSV (
       mapping[&quot;end&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.project&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.project&quot;) ||
              name == &quot;project&quot; ||
              name.find (&quot;proj&quot;) != std::string::npos)
     {
       mapping[&quot;project&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.priority&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.priority&quot;) ||
              name == &quot;priority&quot; ||
              name == &quot;pri&quot; ||
              name.find (&quot;importan&quot;) != std::string::npos)
@@ -1005,7 +1018,7 @@ static std::string importCSV (
       mapping[&quot;priority&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.fg&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.fg&quot;) ||
              name.find (&quot;fg&quot;)         != std::string::npos ||
              name.find (&quot;foreground&quot;) != std::string::npos ||
              name.find (&quot;color&quot;)      != std::string::npos)
@@ -1013,14 +1026,14 @@ static std::string importCSV (
       mapping[&quot;fg&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.bg&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.bg&quot;) ||
              name == &quot;bg&quot; ||
              name.find (&quot;background&quot;) != std::string::npos)
     {
       mapping[&quot;bg&quot;] = (int)h;
     }
 
-    else if (name == conf.get (&quot;import.synonym.description&quot;) ||
+    else if (name == context.config.get (&quot;import.synonym.description&quot;) ||
              name.find (&quot;desc&quot;)   != std::string::npos ||
              name.find (&quot;detail&quot;) != std::string::npos ||
              name.find (&quot;task&quot;)   != std::string::npos ||
@@ -1040,20 +1053,21 @@ static std::string importCSV (
       std::vector &lt;std::string&gt; fields;
       split (fields, *it, ',');
 
-      T task;
+      Task task;
 
       int f;
       if ((f = mapping[&quot;uuid&quot;]) != -1)
-        task.setUUID (lowerCase (unquoteText (trim (fields[f]))));
+        task.set (&quot;uuid&quot;, lowerCase (unquoteText (trim (fields[f]))));
 
+      task.setStatus (Task::pending);
       if ((f = mapping[&quot;status&quot;]) != -1)
       {
         std::string value = lowerCase (unquoteText (trim (fields[f])));
 
-             if (value == &quot;recurring&quot;) task.setStatus (T::recurring);
-        else if (value == &quot;deleted&quot;)   task.setStatus (T::deleted);
-        else if (value == &quot;completed&quot;) task.setStatus (T::completed);
-        else                           task.setStatus (T::pending);
+             if (value == &quot;recurring&quot;) task.setStatus (Task::recurring);
+        else if (value == &quot;deleted&quot;)   task.setStatus (Task::deleted);
+        else if (value == &quot;completed&quot;) task.setStatus (Task::completed);
+        else if (value == &quot;waiting&quot;)   task.setStatus (Task::waiting);
       }
 
       if ((f = mapping[&quot;tags&quot;]) != -1)
@@ -1066,41 +1080,40 @@ static std::string importCSV (
       }
 
       if ((f = mapping[&quot;entry&quot;]) != -1)
-        task.setAttribute (&quot;entry&quot;, lowerCase (unquoteText (trim (fields[f]))));
+        task.set (&quot;entry&quot;, lowerCase (unquoteText (trim (fields[f]))));
 
       if ((f = mapping[&quot;start&quot;]) != -1)
-        task.setAttribute (&quot;start&quot;, lowerCase (unquoteText (trim (fields[f]))));
+        task.set (&quot;start&quot;, lowerCase (unquoteText (trim (fields[f]))));
 
       if ((f = mapping[&quot;due&quot;]) != -1)
-        task.setAttribute (&quot;due&quot;, lowerCase (unquoteText (trim (fields[f]))));
+        task.set (&quot;due&quot;, lowerCase (unquoteText (trim (fields[f]))));
 
       if ((f = mapping[&quot;recur&quot;]) != -1)
-        task.setAttribute (&quot;recur&quot;, lowerCase (unquoteText (trim (fields[f]))));
+        task.set (&quot;recur&quot;, lowerCase (unquoteText (trim (fields[f]))));
 
       if ((f = mapping[&quot;end&quot;]) != -1)
-        task.setAttribute (&quot;end&quot;, lowerCase (unquoteText (trim (fields[f]))));
+        task.set (&quot;end&quot;, lowerCase (unquoteText (trim (fields[f]))));
 
       if ((f = mapping[&quot;project&quot;]) != -1)
-        task.setAttribute (&quot;project&quot;, unquoteText (trim (fields[f])));
+        task.set (&quot;project&quot;, unquoteText (trim (fields[f])));
 
       if ((f = mapping[&quot;priority&quot;]) != -1)
       {
         std::string value = upperCase (unquoteText (trim (fields[f])));
         if (value == &quot;H&quot; || value == &quot;M&quot; || value == &quot;L&quot;)
-          task.setAttribute (&quot;priority&quot;, value);
+          task.set (&quot;priority&quot;, value);
       }
 
       if ((f = mapping[&quot;fg&quot;]) != -1)
-        task.setAttribute (&quot;fg&quot;, lowerCase (unquoteText (trim (fields[f]))));
+        task.set (&quot;fg&quot;, lowerCase (unquoteText (trim (fields[f]))));
 
       if ((f = mapping[&quot;bg&quot;]) != -1)
-        task.setAttribute (&quot;bg&quot;, lowerCase (unquoteText (trim (fields[f]))));
+        task.set (&quot;bg&quot;, lowerCase (unquoteText (trim (fields[f]))));
 
       if ((f = mapping[&quot;description&quot;]) != -1)
-        task.setDescription (unquoteText (trim (fields[f])));
+        task.set (&quot;description&quot;, unquoteText (trim (fields[f])));
 
-      if (! tdb.addT (task))
-        failed.push_back (*it);
+      context.tdb.add (task);
     }
 
     catch (...)
@@ -1109,6 +1122,9 @@ static std::string importCSV (
     }
   }
 
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
   std::stringstream out;
   out &lt;&lt; &quot;Imported &quot;
       &lt;&lt; (lines.size () - failed.size () - 1)
@@ -1128,12 +1144,12 @@ static std::string importCSV (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleImport (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleImport ()
 {
   std::stringstream out;
 
   // Use the description as a file name.
-  std::string file = trim (task.getDescription ());
+  std::string file = trim (context.task.get (&quot;description&quot;));
   if (file.length () &gt; 0)
   {
     // Load the file.
@@ -1169,7 +1185,7 @@ std::string handleImport (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     case task_cmd_line: identifier = &quot;This looks like task command line arguments.&quot;;            break;
     case todo_sh_2_0:   identifier = &quot;This looks like a todo.sh 2.x file.&quot;;                     break;
     case csv:           identifier = &quot;This looks like a CSV file, but not a task export file.&quot;; break;
-    case text:          identifier = &quot;This looks like a text file with one tasks per line.&quot;;    break;
+    case text:          identifier = &quot;This looks like a text file with one task per line.&quot;;     break;
     case not_a_clue:
       throw std::string (&quot;Task cannot determine which type of file this is, &quot;
                          &quot;and cannot proceed.&quot;);
@@ -1183,13 +1199,13 @@ std::string handleImport (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     // Determine which type it might be, then attempt an import.
     switch (type)
     {
-    case task_1_4_3:    out &lt;&lt; importTask_1_4_3  (tdb, conf, lines); break;
-    case task_1_5_0:    out &lt;&lt; importTask_1_5_0  (tdb, conf, lines); break;
-    case task_1_6_0:    out &lt;&lt; importTask_1_6_0  (tdb, conf, lines); break;
-    case task_cmd_line: out &lt;&lt; importTaskCmdLine (tdb, conf, lines); break;
-    case todo_sh_2_0:   out &lt;&lt; importTodoSh_2_0  (tdb, conf, lines); break;
-    case csv:           out &lt;&lt; importCSV         (tdb, conf, lines); break;
-    case text:          out &lt;&lt; importText        (tdb, conf, lines); break;
+    case task_1_4_3:    out &lt;&lt; importTask_1_4_3  (lines); break;
+    case task_1_5_0:    out &lt;&lt; importTask_1_5_0  (lines); break;
+    case task_1_6_0:    out &lt;&lt; importTask_1_6_0  (lines); break;
+    case task_cmd_line: out &lt;&lt; importTaskCmdLine (lines); break;
+    case todo_sh_2_0:   out &lt;&lt; importTodoSh_2_0  (lines); break;
+    case csv:           out &lt;&lt; importCSV         (lines); break;
+    case text:          out &lt;&lt; importText        (lines); break;
     case not_a_clue:    /* to stop the compiler from complaining. */ break;
     }
   }
@@ -1200,4 +1216,3 @@ std::string handleImport (TDB&amp; tdb, T&amp; task, Config&amp; conf)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-</diff>
      <filename>src/import.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -29,508 +29,475 @@
 #include &lt;sstream&gt;
 #include &lt;fstream&gt;
 #include &lt;sys/types.h&gt;
+#include &lt;sys/stat.h&gt;
 #include &lt;stdio.h&gt;
 #include &lt;unistd.h&gt;
 #include &lt;stdlib.h&gt;
 #include &lt;pwd.h&gt;
 #include &lt;time.h&gt;
 
-#include &quot;Config.h&quot;
+#include &quot;Context.h&quot;
 #include &quot;Date.h&quot;
 #include &quot;Table.h&quot;
-#include &quot;TDB.h&quot;
-#include &quot;T.h&quot;
-#include &quot;task.h&quot;
+#include &quot;text.h&quot;
+#include &quot;util.h&quot;
+#include &quot;main.h&quot;
 
 #ifdef HAVE_LIBNCURSES
 #include &lt;ncurses.h&gt;
 #endif
 
+extern Context context;
+
 ////////////////////////////////////////////////////////////////////////////////
-void filterSequence (std::vector&lt;T&gt;&amp; all, T&amp; task)
+std::string shortUsage ()
 {
-  std::vector &lt;int&gt; sequence = task.getAllIds ();
+  Table table;
 
-  std::vector &lt;T&gt; filtered;
-  std::vector &lt;T&gt;::iterator t;
-  for (t = all.begin (); t != all.end (); ++t)
-  {
-    std::vector &lt;int&gt;::iterator s;
-    for (s = sequence.begin (); s != sequence.end (); ++s)
-      if (t-&gt;getId () == *s)
-        filtered.push_back (*t);
-  }
+  table.addColumn (&quot; &quot;);
+  table.addColumn (&quot; &quot;);
+  table.addColumn (&quot; &quot;);
 
-  if (sequence.size () != filtered.size ())
-  {
-    std::vector &lt;int&gt; filteredSequence;
-    std::vector &lt;T&gt;::iterator fs;
-    for (fs = filtered.begin (); fs != filtered.end (); ++fs)
-      filteredSequence.push_back (fs-&gt;getId ());
-
-    std::vector &lt;int&gt; left;
-    std::vector &lt;int&gt; right;
-    listDiff (filteredSequence, sequence, left, right);
-    if (left.size ())
-      throw std::string (&quot;Sequence filtering error - please report this error&quot;);
-
-    if (right.size ())
-    {
-      std::stringstream out;
-      out &lt;&lt; &quot;Task&quot;;
+  table.setColumnJustification (0, Table::left);
+  table.setColumnJustification (1, Table::left);
+  table.setColumnJustification (2, Table::left);
 
-      if (right.size () &gt; 1) out &lt;&lt; &quot;s&quot;;
+  table.setColumnWidth (0, Table::minimum);
+  table.setColumnWidth (1, Table::minimum);
+  table.setColumnWidth (2, Table::flexible);
+  table.setTableWidth (context.getWidth ());
+  table.setDateFormat (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
 
-      std::vector &lt;int&gt;::iterator s;
-      for (s = right.begin (); s != right.end (); ++s)
-        out &lt;&lt; &quot; &quot; &lt;&lt; *s;
+  int row = table.addRow ();
+  table.addCell (row, 0, &quot;Usage:&quot;);
+  table.addCell (row, 1, &quot;task&quot;);
 
-      out &lt;&lt; &quot; not found&quot;;
-      throw out.str ();
-    }
-  }
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task add [tags] [attrs] desc...&quot;);
+  table.addCell (row, 2, &quot;Adds a new task.&quot;);
 
-  all = filtered;
-}
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task append [tags] [attrs] desc...&quot;);
+  table.addCell (row, 2, &quot;Appends more description to an existing task.&quot;);
 
-////////////////////////////////////////////////////////////////////////////////
-void filter (std::vector&lt;T&gt;&amp; all, T&amp; task)
-{
-  std::vector &lt;T&gt; filtered;
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task annotate ID desc...&quot;);
+  table.addCell (row, 2, &quot;Adds an annotation to an existing task.&quot;);
 
-  // Split any description specified into words.
-  std::vector &lt;std::string&gt; descWords;
-  split (descWords, lowerCase (task.getDescription ()), ' ');
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task ID [tags] [attrs] [desc...]&quot;);
+  table.addCell (row, 2, &quot;Modifies the existing task with provided arguments.&quot;);
 
-  // Get all the tags to match against.
-  std::vector &lt;std::string&gt; tagList;
-  task.getTags (tagList);
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task ID /from/to/g&quot;);
+  table.addCell (row, 2, &quot;Performs substitution on the task description and &quot;
+                         &quot;annotations.  The 'g' is optional, and causes &quot;
+                         &quot;substitutions for all matching text, not just the &quot;
+                         &quot;first occurrence.&quot;);
 
-  // Get all the attributes to match against.
-  std::map &lt;std::string, std::string&gt; attrList;
-  task.getAttributes (attrList);
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task edit ID&quot;);
+  table.addCell (row, 2, &quot;Launches an editor to let you modify all aspects of a task directly, therefore it is to be used carefully.&quot;);
 
-  // Iterate over each task, and apply selection criteria.
-  for (unsigned int i = 0; i &lt; all.size (); ++i)
-  {
-    T refTask (all[i]);
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task undo&quot;);
+  table.addCell (row, 2, &quot;Reverts the most recent action.&quot;);
 
-    // Apply description filter.
-    std::string desc = lowerCase (refTask.getDescription ());
-    unsigned int matches = 0;
-    for (unsigned int w = 0; w &lt; descWords.size (); ++w)
-      if (desc.find (descWords[w]) != std::string::npos)
-        ++matches;
+#ifdef FEATURE_SHELL
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task shell&quot;);
+  table.addCell (row, 2, &quot;Launches an interactive shell.&quot;);
+#endif
 
-    if (matches == descWords.size ())
-    {
-      // Apply attribute filter.
-      matches = 0;
-      foreach (a, attrList)
-      {
-        if (a-&gt;first == &quot;project&quot;)
-        {
-          if (a-&gt;second.length () &lt;= refTask.getAttribute (a-&gt;first).length ())
-            if (a-&gt;second == refTask.getAttribute (a-&gt;first).substr (0, a-&gt;second.length ()))
-              ++matches;
-/*
-  TODO Attempt at allowing &quot;pri:!H&quot;, thwarted by a lack of coffee and the
-       validation of &quot;!H&quot; as a priority value.  To be revisited soon.
-          {
-            if (a-&gt;second[0] == '!')  // Inverted search.
-            {
-              if (a-&gt;second.substr (1, std::string::npos) != refTask.getAttribute (a-&gt;first).substr (0, a-&gt;second.length ()))
-                ++matches;
-            }
-            else
-            {
-              if (a-&gt;second == refTask.getAttribute (a-&gt;first).substr (0, a-&gt;second.length ()))
-                ++matches;
-            }
-          }
-*/
-        }
-        else if (a-&gt;second == refTask.getAttribute (a-&gt;first))
-          ++matches;
-/*
-        else
-        {
-          if (a-&gt;second[0] == '!')  // Inverted search.
-          {
-            if (a-&gt;second.substr (1, std::string::npos) != refTask.getAttribute (a-&gt;first))
-              ++matches;
-          }
-          else
-          {
-            if (a-&gt;second == refTask.getAttribute (a-&gt;first))
-              ++matches;
-          }
-        }
-*/
-      }
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task duplicate ID [tags] [attrs] [desc...]&quot;);
+  table.addCell (row, 2, &quot;Duplicates the specified task, and allows modifications.&quot;);
 
-      if (matches == attrList.size ())
-      {
-        // Apply tag filter.
-        matches = 0;
-        for (unsigned int t = 0; t &lt; tagList.size (); ++t)
-          if (refTask.hasTag (tagList[t]))
-            ++matches;
-
-        if (matches == tagList.size ())
-          filtered.push_back (refTask);
-      }
-    }
-  }
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task delete ID&quot;);
+  table.addCell (row, 2, &quot;Deletes the specified task.&quot;);
 
-  all = filtered;
-}
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task info ID&quot;);
+  table.addCell (row, 2, &quot;Shows all data, metadata for specified task.&quot;);
 
-////////////////////////////////////////////////////////////////////////////////
-// Successively apply filters based on the task object built from the command
-// line.  Tasks that match all the specified criteria are listed.
-std::string handleCompleted (TDB&amp; tdb, T&amp; task, Config&amp; conf)
-{
-  std::stringstream out;
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task start ID&quot;);
+  table.addCell (row, 2, &quot;Marks specified task as started.&quot;);
 
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
-  {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task stop ID&quot;);
+  table.addCell (row, 2, &quot;Removes the 'start' time from a task.&quot;);
 
-  // Get the pending tasks.
-  std::vector &lt;T&gt; tasks;
-  tdb.completedT (tasks);
-  filter (tasks, task);
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task done ID [tags] [attrs] [desc...]&quot;);
+  table.addCell (row, 2, &quot;Marks the specified task as completed.&quot;);
 
-  initializeColorRules (conf);
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task projects&quot;);
+  table.addCell (row, 2, &quot;Shows a list of all project names used, and how many tasks are in each.&quot;);
 
-  // Create a table for output.
-  Table table;
-  table.setTableWidth (width);
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-  table.addColumn (&quot;Done&quot;);
-  table.addColumn (&quot;Project&quot;);
-  table.addColumn (&quot;Description&quot;);
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task tags&quot;);
+  table.addCell (row, 2, &quot;Shows a list of all tags used.&quot;);
 
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-  {
-    table.setColumnUnderline (0);
-    table.setColumnUnderline (1);
-    table.setColumnUnderline (2);
-  }
-  else
-    table.setTableDashedUnderline ();
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task summary&quot;);
+  table.addCell (row, 2, &quot;Shows a report of task status by project.&quot;);
 
-  table.setColumnWidth (0, Table::minimum);
-  table.setColumnWidth (1, Table::minimum);
-  table.setColumnWidth (2, Table::flexible);
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task timesheet [weeks]&quot;);
+  table.addCell (row, 2, &quot;Shows a weekly report of tasks completed and started.&quot;);
 
-  table.setColumnJustification (0, Table::right);
-  table.setColumnJustification (1, Table::left);
-  table.setColumnJustification (2, Table::left);
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task history&quot;);
+  table.addCell (row, 2, &quot;Shows a report of task history, by month.&quot;);
 
-  // Note: There is deliberately no sorting.  The original sorting was on the
-  //       end date.  Tasks are appended to completed.data naturally sorted by
-  //       the end date, so that sequence is assumed to remain unchanged, and
-  //       relied upon here.
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task ghistory&quot;);
+  table.addCell (row, 2, &quot;Shows a graphical report of task history, by month.&quot;);
 
-  // Iterate over each task, and apply selection criteria.
-  for (unsigned int i = 0; i &lt; tasks.size (); ++i)
-  {
-    T refTask (tasks[i]);
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task calendar [due|month year|year]&quot;);
+  table.addCell (row, 2, &quot;Shows a calendar, with due tasks marked.&quot;);
 
-    // Now format the matching task.
-    Date end (::atoi (refTask.getAttribute (&quot;end&quot;).c_str ()));
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task stats&quot;);
+  table.addCell (row, 2, &quot;Shows task database statistics.&quot;);
 
-    // All criteria match, so add refTask to the output table.
-    int row = table.addRow ();
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task import&quot;);
+  table.addCell (row, 2, &quot;Imports tasks from a variety of formats.&quot;);
 
-    table.addCell (row, 0, end.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;)));
-    table.addCell (row, 1, refTask.getAttribute (&quot;project&quot;));
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task export&quot;);
+  table.addCell (row, 2, &quot;Lists all tasks in CSV format.&quot;);
 
-    std::string description = refTask.getDescription ();
-    std::string when;
-    std::map &lt;time_t, std::string&gt; annotations;
-    refTask.getAnnotations (annotations);
-    foreach (anno, annotations)
-    {
-      Date dt (anno-&gt;first);
-      when = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-      description += &quot;\n&quot; + when + &quot; &quot; + anno-&gt;second;
-    }
-    table.addCell (row, 2, description);
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task color&quot;);
+  table.addCell (row, 2, &quot;Displays all possible colors.&quot;);
 
-    if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-    {
-      Text::color fg = Text::colorCode (refTask.getAttribute (&quot;fg&quot;));
-      Text::color bg = Text::colorCode (refTask.getAttribute (&quot;bg&quot;));
-      autoColorize (refTask, fg, bg, conf);
-      table.setRowFg (row, fg);
-      table.setRowBg (row, bg);
-    }
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task version&quot;);
+  table.addCell (row, 2, &quot;Shows the task version number.&quot;);
+
+  row = table.addRow ();
+  table.addCell (row, 1, &quot;task help&quot;);
+  table.addCell (row, 2, &quot;Shows the long usage text.&quot;);
+
+  // Add custom reports here...
+  std::vector &lt;std::string&gt; all;
+  context.cmd.allCustomReports (all);
+  foreach (report, all)
+  {
+    std::string command = std::string (&quot;task &quot;) + *report + std::string (&quot; [tags] [attrs] desc...&quot;);
+    std::string description = context.config.get (
+      std::string (&quot;report.&quot;) + *report + &quot;.description&quot;, std::string (&quot;(missing description)&quot;));
+
+    row = table.addRow ();
+    table.addCell (row, 1, command);
+    table.addCell (row, 2, description);
   }
 
-  if (table.rowCount ())
-    out &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; table.render ()
-        &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; table.rowCount ()
-        &lt;&lt; (table.rowCount () == 1 ? &quot; task&quot; : &quot; tasks&quot;)
-        &lt;&lt; std::endl;
-  else
-    out &lt;&lt; &quot;No matches.&quot;
-        &lt;&lt; std::endl;
+  std::stringstream out;
+  out &lt;&lt; table.render ()
+      &lt;&lt; std::endl
+      &lt;&lt; &quot;See http://taskwarrior.org/wiki/taskwarrior/Download for the latest &quot;
+      &lt;&lt; &quot;releases and a full tutorial.  New releases containing fixes and &quot;
+      &lt;&lt; &quot;enhancements are made frequently.  Join in the discussion of task, &quot;
+      &lt;&lt; &quot;present and future, at http://taskwarrior.org&quot;
+      &lt;&lt; std::endl
+      &lt;&lt; std::endl;
 
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Display all information for the given task.
-std::string handleInfo (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string longUsage ()
 {
   std::stringstream out;
+  out &lt;&lt; shortUsage ()
+      &lt;&lt; &quot;ID is the numeric identifier displayed by the 'task list' command. &quot;
+      &lt;&lt; &quot;You can specify multiple IDs for task commands, and multiple tasks &quot;
+      &lt;&lt; &quot;will be affected.  To specify multiple IDs make sure you use one &quot;
+      &lt;&lt; &quot;of these forms:&quot;                                                    &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  task delete 1,2,3&quot;                                                &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  task info 1-3&quot;                                                    &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  task pri:H 1,2-5,19&quot;                                              &lt;&lt; &quot;\n&quot;
+      &lt;&lt;                                                                         &quot;\n&quot;
+      &lt;&lt; &quot;Tags are arbitrary words, any quantity:&quot;                            &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  +tag               The + means add the tag&quot;                       &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  -tag               The - means remove the tag&quot;                    &lt;&lt; &quot;\n&quot;
+      &lt;&lt;                                                                         &quot;\n&quot;
+      &lt;&lt; &quot;Attributes are:&quot;                                                    &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  project:           Project name&quot;                                  &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  priority:          Priority&quot;                                      &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  due:               Due date&quot;                                      &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  recur:             Recurrence frequency&quot;                          &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  until:             Recurrence end date&quot;                           &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  fg:                Foreground color&quot;                              &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  bg:                Background color&quot;                              &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  limit:             Desired number of rows in report&quot;              &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  wait:              Date until task becomes pending&quot;               &lt;&lt; &quot;\n&quot;
+      &lt;&lt;                                                                         &quot;\n&quot;
+      &lt;&lt; &quot;Attribute modifiers improve filters.  Supported modifiers are:&quot;     &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  before     (synonyms under, below)&quot;                               &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  after      (synonyms over, above)&quot;                                &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  none&quot;                                                             &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  any&quot;                                                              &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  is         (synonym equals)&quot;                                      &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  isnt       (synonym not)&quot;                                         &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  has        (synonym contain)&quot;                                     &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  hasnt&quot;                                                            &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  startswith (synonym left)&quot;                                        &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  endswith   (synonym right)&quot;                                       &lt;&lt; &quot;\n&quot;
+      &lt;&lt;                                                                         &quot;\n&quot;
+      &lt;&lt; &quot;  For example:&quot;                                                     &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;    task list due.before:eom priority.not:L&quot;                        &lt;&lt; &quot;\n&quot;
+      &lt;&lt;                                                                         &quot;\n&quot;
+      &lt;&lt; &quot;The default .taskrc file can be overridden with:&quot;                   &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  task rc:&lt;alternate file&gt; ...&quot;                                     &lt;&lt; &quot;\n&quot;
+      &lt;&lt;                                                                         &quot;\n&quot;
+      &lt;&lt; &quot;The values in .taskrc (or alternate) can be overridden with:&quot;       &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  task ... rc.&lt;name&gt;:&lt;value&gt;&quot;                                       &lt;&lt; &quot;\n&quot;
+      &lt;&lt;                                                                         &quot;\n&quot;
+      &lt;&lt; &quot;Any command or attribute name may be abbreviated if still unique:&quot;  &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  task list project:Home&quot;                                           &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  task li       pro:Home&quot;                                           &lt;&lt; &quot;\n&quot;
+      &lt;&lt;                                                                         &quot;\n&quot;
+      &lt;&lt; &quot;Some task descriptions need to be escaped because of the shell:&quot;    &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  task add \&quot;quoted ' quote\&quot;&quot;                                      &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  task add escaped \\' quote&quot;                                       &lt;&lt; &quot;\n&quot;
+      &lt;&lt;                                                                         &quot;\n&quot;
+      &lt;&lt; &quot;The argument -- tells task to treat all other args as description.&quot; &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  task add -- project:Home needs scheduling&quot;                        &lt;&lt; &quot;\n&quot;
+      &lt;&lt;                                                                         &quot;\n&quot;
+      &lt;&lt; &quot;Many characters have special meaning to the shell, including:&quot;      &lt;&lt; &quot;\n&quot;
+      &lt;&lt; &quot;  $ ! ' \&quot; ( ) ; \\ ` * ? { } [ ] &lt; &gt; | &amp; % # ~&quot;                    &lt;&lt; &quot;\n&quot;
+      &lt;&lt; std::endl;
 
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
-  {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
+  return out.str ();
+}
 
+////////////////////////////////////////////////////////////////////////////////
+// Display all information for the given task.
+std::string handleInfo ()
+{
   // Get all the tasks.
-  std::vector &lt;T&gt; tasks;
-  tdb.allPendingT (tasks);
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  context.tdb.loadPending (tasks, context.filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  // Filter sequence.
+  context.filter.applySequence (tasks, context.sequence);
 
   // Find the task.
-  int count = 0;
-  for (unsigned int i = 0; i &lt; tasks.size (); ++i)
+  std::stringstream out;
+  foreach (task, tasks)
   {
-    T refTask (tasks[i]);
+    Table table;
+    table.setTableWidth (context.getWidth ());
+    table.setDateFormat (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+
+    table.addColumn (&quot;Name&quot;);
+    table.addColumn (&quot;Value&quot;);
 
-    if (refTask.getId () == task.getId () || task.sequenceContains (refTask.getId ()))
+    if ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp;
+        context.config.get (std::string (&quot;fontunderline&quot;), &quot;true&quot;))
     {
-      ++count;
+      table.setColumnUnderline (0);
+      table.setColumnUnderline (1);
+    }
+    else
+      table.setTableDashedUnderline ();
 
-      Table table;
-      table.setTableWidth (width);
-      table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+    table.setColumnWidth (0, Table::minimum);
+    table.setColumnWidth (1, Table::flexible);
 
-      table.addColumn (&quot;Name&quot;);
-      table.addColumn (&quot;Value&quot;);
+    table.setColumnJustification (0, Table::left);
+    table.setColumnJustification (1, Table::left);
+    Date now;
 
-      if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-      {
-        table.setColumnUnderline (0);
-        table.setColumnUnderline (1);
-      }
-      else
-        table.setTableDashedUnderline ();
+    int row = table.addRow ();
+    table.addCell (row, 0, &quot;ID&quot;);
+    table.addCell (row, 1, task-&gt;id);
 
-      table.setColumnWidth (0, Table::minimum);
-      table.setColumnWidth (1, Table::flexible);
+    std::string status = ucFirst (Task::statusToText (task-&gt;getStatus ()));
 
-      table.setColumnJustification (0, Table::left);
-      table.setColumnJustification (1, Table::left);
-          Date now;
+    if (task-&gt;has (&quot;parent&quot;))
+      status += &quot; (Recurring)&quot;;
 
-      int row = table.addRow ();
-      table.addCell (row, 0, &quot;ID&quot;);
-      table.addCell (row, 1, refTask.getId ());
+    row = table.addRow ();
+    table.addCell (row, 0, &quot;Status&quot;);
+    table.addCell (row, 1, status);
 
-      std::string status = refTask.getStatus () == T::pending   ? &quot;Pending&quot;
-                         : refTask.getStatus () == T::completed ? &quot;Completed&quot;
-                         : refTask.getStatus () == T::deleted   ? &quot;Deleted&quot;
-                         : refTask.getStatus () == T::recurring ? &quot;Recurring&quot;
-                         : &quot;&quot;;
-      if (refTask.getAttribute (&quot;parent&quot;) != &quot;&quot;)
-        status += &quot; (Recurring)&quot;;
+    row = table.addRow ();
+    table.addCell (row, 0, &quot;Description&quot;);
+    table.addCell (row, 1, getFullDescription (*task));
 
+    if (task-&gt;has (&quot;project&quot;))
+    {
       row = table.addRow ();
-      table.addCell (row, 0, &quot;Status&quot;);
-      table.addCell (row, 1, status);
-
-      std::string description = refTask.getDescription ();
-      std::string when;
-      std::map &lt;time_t, std::string&gt; annotations;
-      refTask.getAnnotations (annotations);
-      foreach (anno, annotations)
-      {
-        Date dt (anno-&gt;first);
-        when = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-        description += &quot;\n&quot; + when + &quot; &quot; + anno-&gt;second;
-      }
+      table.addCell (row, 0, &quot;Project&quot;);
+      table.addCell (row, 1, task-&gt;get (&quot;project&quot;));
+    }
 
+    if (task-&gt;has (&quot;priority&quot;))
+    {
       row = table.addRow ();
-      table.addCell (row, 0, &quot;Description&quot;);
-      table.addCell (row, 1, description);
+      table.addCell (row, 0, &quot;Priority&quot;);
+      table.addCell (row, 1, task-&gt;get (&quot;priority&quot;));
+    }
 
-      if (refTask.getAttribute (&quot;project&quot;) != &quot;&quot;)
+    if (task-&gt;getStatus () == Task::recurring ||
+        task-&gt;has (&quot;parent&quot;))
+    {
+      if (task-&gt;has (&quot;recur&quot;))
       {
         row = table.addRow ();
-        table.addCell (row, 0, &quot;Project&quot;);
-        table.addCell (row, 1, refTask.getAttribute (&quot;project&quot;));
+        table.addCell (row, 0, &quot;Recurrence&quot;);
+        table.addCell (row, 1, task-&gt;get (&quot;recur&quot;));
       }
 
-      if (refTask.getAttribute (&quot;priority&quot;) != &quot;&quot;)
+      if (task-&gt;has (&quot;until&quot;))
       {
         row = table.addRow ();
-        table.addCell (row, 0, &quot;Priority&quot;);
-        table.addCell (row, 1, refTask.getAttribute (&quot;priority&quot;));
+        table.addCell (row, 0, &quot;Recur until&quot;);
+        table.addCell (row, 1, task-&gt;get (&quot;until&quot;));
       }
 
-      if (refTask.getStatus () == T::recurring ||
-          refTask.getAttribute (&quot;parent&quot;) != &quot;&quot;)
+      if (task-&gt;has (&quot;mask&quot;))
       {
-        if (refTask.getAttribute (&quot;recur&quot;) != &quot;&quot;)
-        {
-          row = table.addRow ();
-          table.addCell (row, 0, &quot;Recurrence&quot;);
-          table.addCell (row, 1, refTask.getAttribute (&quot;recur&quot;));
-        }
-
-        if (refTask.getAttribute (&quot;until&quot;) != &quot;&quot;)
-        {
-          row = table.addRow ();
-          table.addCell (row, 0, &quot;Recur until&quot;);
-          table.addCell (row, 1, refTask.getAttribute (&quot;until&quot;));
-        }
-
-        if (refTask.getAttribute (&quot;mask&quot;) != &quot;&quot;)
-        {
-          row = table.addRow ();
-          table.addCell (row, 0, &quot;Mask&quot;);
-          table.addCell (row, 1, refTask.getAttribute (&quot;mask&quot;));
-        }
-
-        if (refTask.getAttribute (&quot;parent&quot;) != &quot;&quot;)
-        {
-          row = table.addRow ();
-          table.addCell (row, 0, &quot;Parent task&quot;);
-          table.addCell (row, 1, refTask.getAttribute (&quot;parent&quot;));
-        }
-
         row = table.addRow ();
-        table.addCell (row, 0, &quot;Mask Index&quot;);
-        table.addCell (row, 1, refTask.getAttribute (&quot;imask&quot;));
+        table.addCell (row, 0, &quot;Mask&quot;);
+        table.addCell (row, 1, task-&gt;get (&quot;mask&quot;));
       }
 
-      // due (colored)
-      bool imminent = false;
-      bool overdue = false;
-      std::string due = refTask.getAttribute (&quot;due&quot;);
-      if (due != &quot;&quot;)
+      if (task-&gt;has (&quot;parent&quot;))
       {
         row = table.addRow ();
-        table.addCell (row, 0, &quot;Due&quot;);
+        table.addCell (row, 0, &quot;Parent task&quot;);
+        table.addCell (row, 1, task-&gt;get (&quot;parent&quot;));
+      }
 
-        Date dt (::atoi (due.c_str ()));
-        due = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-        table.addCell (row, 1, due);
+      row = table.addRow ();
+      table.addCell (row, 0, &quot;Mask Index&quot;);
+      table.addCell (row, 1, task-&gt;get (&quot;imask&quot;));
+    }
 
-        if (due.length ())
-        {
-          overdue = (dt &lt; now) ? true : false;
-          Date nextweek = now + 7 * 86400;
-          imminent = dt &lt; nextweek ? true : false;
+    // due (colored)
+    bool imminent = false;
+    bool overdue = false;
+    if (task-&gt;has (&quot;due&quot;))
+    {
+      row = table.addRow ();
+      table.addCell (row, 0, &quot;Due&quot;);
 
-          if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-          {
-            if (overdue)
-              table.setCellFg (row, 1, Text::colorCode (conf.get (&quot;color.overdue&quot;, &quot;red&quot;)));
-            else if (imminent)
-              table.setCellFg (row, 1, Text::colorCode (conf.get (&quot;color.due&quot;, &quot;yellow&quot;)));
-          }
-        }
-      }
+      Date dt (::atoi (task-&gt;get (&quot;due&quot;).c_str ()));
+      std::string due = getDueDate (*task);
+      table.addCell (row, 1, due);
 
-      // start
-      if (refTask.getAttribute (&quot;start&quot;) != &quot;&quot;)
-      {
-        row = table.addRow ();
-        table.addCell (row, 0, &quot;Start&quot;);
-        Date dt (::atoi (refTask.getAttribute (&quot;start&quot;).c_str ()));
-        table.addCell (row, 1, dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;)));
-      }
+      overdue = (dt &lt; now) ? true : false;
+      Date nextweek = now + 7 * 86400;
+      imminent = dt &lt; nextweek ? true : false;
 
-      // end
-      if (refTask.getAttribute (&quot;end&quot;) != &quot;&quot;)
+      if (context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false))
       {
-        row = table.addRow ();
-        table.addCell (row, 0, &quot;End&quot;);
-        Date dt (::atoi (refTask.getAttribute (&quot;end&quot;).c_str ()));
-        table.addCell (row, 1, dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;)));
+        if (overdue)
+          table.setCellFg (row, 1, Text::colorCode (context.config.get (&quot;color.overdue&quot;, &quot;red&quot;)));
+        else if (imminent)
+          table.setCellFg (row, 1, Text::colorCode (context.config.get (&quot;color.due&quot;, &quot;yellow&quot;)));
       }
+    }
 
-      // tags ...
-      std::vector &lt;std::string&gt; tags;
-      refTask.getTags (tags);
-      if (tags.size ())
-      {
-        std::string allTags;
-        join (allTags, &quot; &quot;, tags);
+    // wait
+    if (task-&gt;has (&quot;wait&quot;))
+    {
+      row = table.addRow ();
+      table.addCell (row, 0, &quot;Waiting until&quot;);
+      Date dt (::atoi (task-&gt;get (&quot;wait&quot;).c_str ()));
+      table.addCell (row, 1, dt.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;)));
+    }
 
-        row = table.addRow ();
-        table.addCell (row, 0, &quot;Tags&quot;);
-        table.addCell (row, 1, allTags);
-      }
+    // start
+    if (task-&gt;has (&quot;start&quot;))
+    {
+      row = table.addRow ();
+      table.addCell (row, 0, &quot;Start&quot;);
+      Date dt (::atoi (task-&gt;get (&quot;start&quot;).c_str ()));
+      table.addCell (row, 1, dt.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;)));
+    }
 
-      // uuid
+    // end
+    if (task-&gt;has (&quot;end&quot;))
+    {
       row = table.addRow ();
-      table.addCell (row, 0, &quot;UUID&quot;);
-      table.addCell (row, 1, refTask.getUUID ());
+      table.addCell (row, 0, &quot;End&quot;);
+      Date dt (::atoi (task-&gt;get (&quot;end&quot;).c_str ()));
+      table.addCell (row, 1, dt.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;)));
+    }
+
+    // tags ...
+    std::vector &lt;std::string&gt; tags;
+    task-&gt;getTags (tags);
+    if (tags.size ())
+    {
+      std::string allTags;
+      join (allTags, &quot; &quot;, tags);
 
-      // entry
       row = table.addRow ();
-      table.addCell (row, 0, &quot;Entered&quot;);
-      Date dt (::atoi (refTask.getAttribute (&quot;entry&quot;).c_str ()));
-      std::string entry = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+      table.addCell (row, 0, &quot;Tags&quot;);
+      table.addCell (row, 1, allTags);
+    }
 
-      std::string age;
-      std::string created = refTask.getAttribute (&quot;entry&quot;);
-      if (created.length ())
-      {
-        Date dt (::atoi (created.c_str ()));
-        formatTimeDeltaDays (age, (time_t) (now - dt));
-      }
+    // uuid
+    row = table.addRow ();
+    table.addCell (row, 0, &quot;UUID&quot;);
+    table.addCell (row, 1, task-&gt;get (&quot;uuid&quot;));
 
-      table.addCell (row, 1, entry + &quot; (&quot; + age + &quot;)&quot;);
+    // entry
+    row = table.addRow ();
+    table.addCell (row, 0, &quot;Entered&quot;);
+    Date dt (::atoi (task-&gt;get (&quot;entry&quot;).c_str ()));
+    std::string entry = dt.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
 
-      // fg
-      std::string color = refTask.getAttribute (&quot;fg&quot;);
-      if (color != &quot;&quot;)
-      {
-        row = table.addRow ();
-        table.addCell (row, 0, &quot;Foreground color&quot;);
-        table.addCell (row, 1, color);
-      }
+    std::string age;
+    std::string created = task-&gt;get (&quot;entry&quot;);
+    if (created.length ())
+    {
+      Date dt (::atoi (created.c_str ()));
+      age = formatSeconds ((time_t) (now - dt));
+    }
 
-      // bg
-      color = refTask.getAttribute (&quot;bg&quot;);
-      if (color != &quot;&quot;)
-      {
-        row = table.addRow ();
-        table.addCell (row, 0, &quot;Background color&quot;);
-        table.addCell (row, 1, color);
-      }
+    table.addCell (row, 1, entry + &quot; (&quot; + age + &quot;)&quot;);
 
-      out &lt;&lt; optionalBlankLine (conf)
-          &lt;&lt; table.render ()
-          &lt;&lt; std::endl;
+    // fg
+    std::string color = task-&gt;get (&quot;fg&quot;);
+    if (color != &quot;&quot;)
+    {
+      row = table.addRow ();
+      table.addCell (row, 0, &quot;Foreground color&quot;);
+      table.addCell (row, 1, color);
+    }
+
+    // bg
+    color = task-&gt;get (&quot;bg&quot;);
+    if (color != &quot;&quot;)
+    {
+      row = table.addRow ();
+      table.addCell (row, 0, &quot;Background color&quot;);
+      table.addCell (row, 1, color);
     }
+
+    out &lt;&lt; optionalBlankLine ()
+        &lt;&lt; table.render ()
+        &lt;&lt; std::endl;
   }
 
-  if (! count)
+  if (! tasks.size ())
     out &lt;&lt; &quot;No matches.&quot; &lt;&lt; std::endl;
 
   return out.str ();
@@ -540,20 +507,21 @@ std::string handleInfo (TDB&amp; tdb, T&amp; task, Config&amp; conf)
 // Project  Remaining  Avg Age  Complete  0%                  100%
 // A               12      13d       55%  XXXXXXXXXXXXX-----------
 // B              109   3d 12h       10%  XXX---------------------
-std::string handleReportSummary (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleReportSummary ()
 {
-  std::stringstream out;
-
-  std::vector &lt;T&gt; tasks;
-  tdb.allT (tasks);
-  handleRecurrence (tdb, tasks);
-  filter (tasks, task);
+  // Scan the pending tasks.
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  context.tdb.load (tasks, context.filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
 
   // Generate unique list of project names from all pending tasks.
   std::map &lt;std::string, bool&gt; allProjects;
-  foreach (t, tasks)
-    if (t-&gt;getStatus () == T::pending)
-      allProjects[t-&gt;getAttribute (&quot;project&quot;)] = false;
+  foreach (task, tasks)
+    if (task-&gt;getStatus () == Task::pending)
+      allProjects[task-&gt;get (&quot;project&quot;)] = false;
 
   // Initialize counts, sum.
   std::map &lt;std::string, int&gt; countPending;
@@ -563,35 +531,36 @@ std::string handleReportSummary (TDB&amp; tdb, T&amp; task, Config&amp; conf)
   time_t now = time (NULL);
 
   // Initialize counters.
-  foreach (i, allProjects)
+  foreach (project, allProjects)
   {
-    countPending   [i-&gt;first] = 0;
-    countCompleted [i-&gt;first] = 0;
-    sumEntry       [i-&gt;first] = 0.0;
-    counter        [i-&gt;first] = 0;
+    countPending   [project-&gt;first] = 0;
+    countCompleted [project-&gt;first] = 0;
+    sumEntry       [project-&gt;first] = 0.0;
+    counter        [project-&gt;first] = 0;
   }
 
   // Count the various tasks.
-  foreach (t, tasks)
+  foreach (task, tasks)
   {
-    std::string project = t-&gt;getAttribute (&quot;project&quot;);
+    std::string project = task-&gt;get (&quot;project&quot;);
     ++counter[project];
 
-    if (t-&gt;getStatus () == T::pending)
+    if (task-&gt;getStatus () == Task::pending ||
+        task-&gt;getStatus () == Task::waiting)
     {
       ++countPending[project];
 
-      time_t entry = ::atoi (t-&gt;getAttribute (&quot;entry&quot;).c_str ());
+      time_t entry = ::atoi (task-&gt;get (&quot;entry&quot;).c_str ());
       if (entry)
         sumEntry[project] = sumEntry[project] + (double) (now - entry);
     }
 
-    else if (t-&gt;getStatus () == T::completed)
+    else if (task-&gt;getStatus () == Task::completed)
     {
       ++countCompleted[project];
 
-      time_t entry = ::atoi (t-&gt;getAttribute (&quot;entry&quot;).c_str ());
-      time_t end   = ::atoi (task.getAttribute (&quot;end&quot;).c_str ());
+      time_t entry = ::atoi (task-&gt;get (&quot;entry&quot;).c_str ());
+      time_t end   = ::atoi (task-&gt;get (&quot;end&quot;).c_str ());
       if (entry &amp;&amp; end)
         sumEntry[project] = sumEntry[project] + (double) (end - entry);
     }
@@ -605,7 +574,8 @@ std::string handleReportSummary (TDB&amp; tdb, T&amp; task, Config&amp; conf)
   table.addColumn (&quot;Complete&quot;);
   table.addColumn (&quot;0%                        100%&quot;);
 
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+  if ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp;
+      context.config.get (std::string (&quot;fontunderline&quot;), &quot;true&quot;))
   {
     table.setColumnUnderline (0);
     table.setColumnUnderline (1);
@@ -620,7 +590,7 @@ std::string handleReportSummary (TDB&amp; tdb, T&amp; task, Config&amp; conf)
   table.setColumnJustification (3, Table::right);
 
   table.sortOn (0, Table::ascendingCharacter);
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+  table.setDateFormat (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
 
   int barWidth = 30;
   foreach (i, allProjects)
@@ -633,7 +603,7 @@ std::string handleReportSummary (TDB&amp; tdb, T&amp; task, Config&amp; conf)
       if (counter[i-&gt;first])
       {
         std::string age;
-        formatTimeDeltaDays (age, (time_t) (sumEntry[i-&gt;first] / counter[i-&gt;first]));
+        age = formatSeconds ((time_t) (sumEntry[i-&gt;first] / counter[i-&gt;first]));
         table.addCell (row, 2, age);
       }
 
@@ -642,7 +612,7 @@ std::string handleReportSummary (TDB&amp; tdb, T&amp; task, Config&amp; conf)
       int completedBar = (c * barWidth) / (c + p);
 
       std::string bar;
-      if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+      if (context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false))
       {
         bar = &quot;\033[42m&quot;;
         for (int b = 0; b &lt; completedBar; ++b)
@@ -670,10 +640,11 @@ std::string handleReportSummary (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     }
   }
 
+  std::stringstream out;
   if (table.rowCount ())
-    out &lt;&lt; optionalBlankLine (conf)
+    out &lt;&lt; optionalBlankLine ()
         &lt;&lt; table.render ()
-        &lt;&lt; optionalBlankLine (conf)
+        &lt;&lt; optionalBlankLine ()
         &lt;&lt; table.rowCount ()
         &lt;&lt; (table.rowCount () == 1 ? &quot; project&quot; : &quot; projects&quot;)
         &lt;&lt; std::endl;
@@ -702,173 +673,59 @@ std::string handleReportSummary (TDB&amp; tdb, T&amp; task, Config&amp; conf)
 //
 // Make the &quot;three&quot; tasks a configurable number
 //
-std::string handleReportNext (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleReportNext ()
 {
-  std::stringstream out;
-
-  // Load all pending.
-  std::vector &lt;T&gt; pending;
-  tdb.allPendingT (pending);
-  handleRecurrence (tdb, pending);
-  filter (pending, task);
-
-  // Restrict to matching subset.
-  std::vector &lt;int&gt; matching;
-  gatherNextTasks (tdb, task, conf, pending, matching);
-
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
-  {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
-
-  // Get the pending tasks.
-  std::vector &lt;T&gt; tasks;
-  tdb.pendingT (tasks);
-  filter (tasks, task);
-
-  initializeColorRules (conf);
-
-  // Create a table for output.
-  Table table;
-  table.setTableWidth (width);
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-  table.addColumn (&quot;ID&quot;);
-  table.addColumn (&quot;Project&quot;);
-  table.addColumn (&quot;Pri&quot;);
-  table.addColumn (&quot;Due&quot;);
-  table.addColumn (&quot;Active&quot;);
-  table.addColumn (&quot;Age&quot;);
-  table.addColumn (&quot;Description&quot;);
-
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-  {
-    table.setColumnUnderline (0);
-    table.setColumnUnderline (1);
-    table.setColumnUnderline (2);
-    table.setColumnUnderline (3);
-    table.setColumnUnderline (4);
-    table.setColumnUnderline (5);
-    table.setColumnUnderline (6);
-  }
-  else
-    table.setTableDashedUnderline ();
-
-  table.setColumnWidth (0, Table::minimum);
-  table.setColumnWidth (1, Table::minimum);
-  table.setColumnWidth (2, Table::minimum);
-  table.setColumnWidth (3, Table::minimum);
-  table.setColumnWidth (4, Table::minimum);
-  table.setColumnWidth (5, Table::minimum);
-  table.setColumnWidth (6, Table::flexible);
-
-  table.setColumnJustification (0, Table::right);
-  table.setColumnJustification (3, Table::right);
-  table.setColumnJustification (5, Table::right);
-
-  table.sortOn (3, Table::ascendingDate);
-  table.sortOn (2, Table::descendingPriority);
-  table.sortOn (1, Table::ascendingCharacter);
+  // Load report configuration.
+  std::string columnList = context.config.get (&quot;report.next.columns&quot;);
+  std::string labelList  = context.config.get (&quot;report.next.labels&quot;);
+  std::string sortList   = context.config.get (&quot;report.next.sort&quot;);
+  std::string filterList = context.config.get (&quot;report.next.filter&quot;);
 
-  // Iterate over each task, and apply selection criteria.
-  foreach (i, matching)
+  std::vector &lt;std::string&gt; filterArgs;
+  split (filterArgs, filterList, ' ');
   {
-    T refTask (pending[*i]);
-    Date now;
-
-    // Now format the matching task.
-    bool imminent = false;
-    bool overdue = false;
-    std::string due = refTask.getAttribute (&quot;due&quot;);
-    if (due.length ())
-    {
-      switch (getDueState (due))
-      {
-      case 2: overdue = true;  break;
-      case 1: imminent = true; break;
-      case 0:
-      default:                 break;
-      }
-
-      Date dt (::atoi (due.c_str ()));
-      due = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-    }
-
-    std::string active;
-    if (refTask.getAttribute (&quot;start&quot;) != &quot;&quot;)
-      active = &quot;*&quot;;
-
-    std::string age;
-    std::string created = refTask.getAttribute (&quot;entry&quot;);
-    if (created.length ())
-    {
-      Date dt (::atoi (created.c_str ()));
-      formatTimeDeltaDays (age, (time_t) (now - dt));
-    }
-
-    // All criteria match, so add refTask to the output table.
-    int row = table.addRow ();
-    table.addCell (row, 0, refTask.getId ());
-    table.addCell (row, 1, refTask.getAttribute (&quot;project&quot;));
-    table.addCell (row, 2, refTask.getAttribute (&quot;priority&quot;));
-    table.addCell (row, 3, due);
-    table.addCell (row, 4, active);
-    table.addCell (row, 5, age);
-
-    std::string description = refTask.getDescription ();
-    std::string when;
-    std::map &lt;time_t, std::string&gt; annotations;
-    refTask.getAnnotations (annotations);
-    foreach (anno, annotations)
-    {
-      Date dt (anno-&gt;first);
-      when = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-      description += &quot;\n&quot; + when + &quot; &quot; + anno-&gt;second;
-    }
+    Cmd cmd (&quot;next&quot;);
+    Task task;
+    Sequence sequence;
+    Subst subst;
+    Filter filter;
+    context.parse (filterArgs, cmd, task, sequence, subst, filter);
 
-    table.addCell (row, 6, description);
+    context.sequence.combine (sequence);
 
-    if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-    {
-      Text::color fg = Text::colorCode (refTask.getAttribute (&quot;fg&quot;));
-      Text::color bg = Text::colorCode (refTask.getAttribute (&quot;bg&quot;));
-      autoColorize (refTask, fg, bg, conf);
-      table.setRowFg (row, fg);
-      table.setRowBg (row, bg);
+    // Allow limit to be overridden by the command line.
+    if (!context.task.has (&quot;limit&quot;) &amp;&amp; task.has (&quot;limit&quot;))
+      context.task.set (&quot;limit&quot;, task.get (&quot;limit&quot;));
 
-      if (fg == Text::nocolor)
-      {
-        if (overdue)
-          table.setCellFg (row, 3, Text::colorCode (conf.get (&quot;color.overdue&quot;, &quot;red&quot;)));
-        else if (imminent)
-          table.setCellFg (row, 3, Text::colorCode (conf.get (&quot;color.due&quot;, &quot;yellow&quot;)));
-      }
-    }
+    foreach (att, filter)
+      context.filter.push_back (*att);
   }
 
-  if (table.rowCount ())
-    out &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; table.render ()
-        &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; table.rowCount ()
-        &lt;&lt; (table.rowCount () == 1 ? &quot; task&quot; : &quot; tasks&quot;)
-        &lt;&lt; std::endl;
-  else
-    out &lt;&lt; &quot;No matches.&quot;
-        &lt;&lt; std::endl;
+  // Get all the tasks.
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  context.tdb.load (tasks, context.filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
 
-  return out.str ();
+  // Restrict to matching subset.
+  gatherNextTasks (tasks);
+
+  return runCustomReport (
+    &quot;next&quot;,
+    columnList,
+    labelList,
+    sortList,
+    filterList,
+    tasks);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Year Month    Added Completed Deleted
 // 2006 November    87        63      14
-// 2006 December    21         6       1
+//      December    21         6       1
+// 2007 January      3        12       0
 time_t monthlyEpoch (const std::string&amp; date)
 {
   // Convert any date in epoch form to m/d/y, then convert back
@@ -887,100 +744,60 @@ time_t monthlyEpoch (const std::string&amp; date)
   return 0;
 }
 
-std::string handleReportHistory (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleReportHistory ()
 {
-  std::stringstream out;
-
-  std::map &lt;time_t, int&gt; groups;
-  std::map &lt;time_t, int&gt; addedGroup;
-  std::map &lt;time_t, int&gt; completedGroup;
-  std::map &lt;time_t, int&gt; deletedGroup;
+  std::map &lt;time_t, int&gt; groups;          // Represents any month with data
+  std::map &lt;time_t, int&gt; addedGroup;      // Additions by month
+  std::map &lt;time_t, int&gt; completedGroup;  // Completions by month
+  std::map &lt;time_t, int&gt; deletedGroup;    // Deletions by month
 
   // Scan the pending tasks.
-  std::vector &lt;T&gt; pending;
-  tdb.allPendingT (pending);
-  handleRecurrence (tdb, pending);
-  filter (pending, task);
-  for (unsigned int i = 0; i &lt; pending.size (); ++i)
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  context.tdb.load (tasks, context.filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  foreach (task, tasks)
   {
-    T task (pending[i]);
-    time_t epoch = monthlyEpoch (task.getAttribute (&quot;entry&quot;));
-    if (epoch)
+    time_t epoch = monthlyEpoch (task-&gt;get (&quot;entry&quot;));
+    groups[epoch] = 0;
+
+    // Every task has an entry date.
+    if (addedGroup.find (epoch) != addedGroup.end ())
+      addedGroup[epoch] = addedGroup[epoch] + 1;
+    else
+      addedGroup[epoch] = 1;
+
+    // All deleted tasks have an end date.
+    if (task-&gt;getStatus () == Task::deleted)
     {
+      epoch = monthlyEpoch (task-&gt;get (&quot;end&quot;));
       groups[epoch] = 0;
 
-      if (addedGroup.find (epoch) != addedGroup.end ())
-        addedGroup[epoch] = addedGroup[epoch] + 1;
+      if (deletedGroup.find (epoch) != deletedGroup.end ())
+        deletedGroup[epoch] = deletedGroup[epoch] + 1;
       else
-        addedGroup[epoch] = 1;
-
-      if (task.getStatus () == T::deleted)
-      {
-        epoch = monthlyEpoch (task.getAttribute (&quot;end&quot;));
-        groups[epoch] = 0;
-
-        if (deletedGroup.find (epoch) != deletedGroup.end ())
-          deletedGroup[epoch] = deletedGroup[epoch] + 1;
-        else
-          deletedGroup[epoch] = 1;
-      }
-      else if (task.getStatus () == T::completed)
-      {
-        epoch = monthlyEpoch (task.getAttribute (&quot;end&quot;));
-        groups[epoch] = 0;
-
-        if (completedGroup.find (epoch) != completedGroup.end ())
-          completedGroup[epoch] = completedGroup[epoch] + 1;
-        else
-          completedGroup[epoch] = 1;
-      }
+        deletedGroup[epoch] = 1;
     }
-  }
 
-  // Scan the completed tasks.
-  std::vector &lt;T&gt; completed;
-  tdb.allCompletedT (completed);
-  filter (completed, task);
-  for (unsigned int i = 0; i &lt; completed.size (); ++i)
-  {
-    T task (completed[i]);
-    time_t epoch = monthlyEpoch (task.getAttribute (&quot;entry&quot;));
-    if (epoch)
+    // All completed tasks have an end date.
+    else if (task-&gt;getStatus () == Task::completed)
     {
+      epoch = monthlyEpoch (task-&gt;get (&quot;end&quot;));
       groups[epoch] = 0;
 
-      if (addedGroup.find (epoch) != addedGroup.end ())
-        addedGroup[epoch] = addedGroup[epoch] + 1;
+      if (completedGroup.find (epoch) != completedGroup.end ())
+        completedGroup[epoch] = completedGroup[epoch] + 1;
       else
-        addedGroup[epoch] = 1;
-
-      epoch = monthlyEpoch (task.getAttribute (&quot;end&quot;));
-      if (task.getStatus () == T::deleted)
-      {
-        epoch = monthlyEpoch (task.getAttribute (&quot;end&quot;));
-        groups[epoch] = 0;
-
-        if (deletedGroup.find (epoch) != deletedGroup.end ())
-          deletedGroup[epoch] = deletedGroup[epoch] + 1;
-        else
-          deletedGroup[epoch] = 1;
-      }
-      else if (task.getStatus () == T::completed)
-      {
-        epoch = monthlyEpoch (task.getAttribute (&quot;end&quot;));
-        groups[epoch] = 0;
-
-        if (completedGroup.find (epoch) != completedGroup.end ())
-          completedGroup[epoch] = completedGroup[epoch] + 1;
-        else
-          completedGroup[epoch] = 1;
-      }
+        completedGroup[epoch] = 1;
     }
   }
 
   // Now build the table.
   Table table;
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+  table.setDateFormat (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
   table.addColumn (&quot;Year&quot;);
   table.addColumn (&quot;Month&quot;);
   table.addColumn (&quot;Added&quot;);
@@ -988,7 +805,8 @@ std::string handleReportHistory (TDB&amp; tdb, T&amp; task, Config&amp; conf)
   table.addColumn (&quot;Deleted&quot;);
   table.addColumn (&quot;Net&quot;);
 
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+  if ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp;
+      context.config.get (std::string (&quot;fontunderline&quot;), &quot;true&quot;))
   {
     table.setColumnUnderline (0);
     table.setColumnUnderline (1);
@@ -1051,7 +869,7 @@ std::string handleReportHistory (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     }
 
     table.addCell (row, 5, net);
-    if ((conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp; net)
+    if ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp; net)
       table.setCellFg (row, 5, net &gt; 0 ? Text::red: Text::green);
   }
 
@@ -1061,15 +879,16 @@ std::string handleReportHistory (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     row = table.addRow ();
 
     table.addCell (row, 1, &quot;Average&quot;);
-    if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false)) table.setRowFg (row, Text::bold);
-    table.addCell (row, 2, totalAdded / (table.rowCount () - 2));
+    if (context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false)) table.setRowFg (row, Text::bold);
+    table.addCell (row, 2, totalAdded     / (table.rowCount () - 2));
     table.addCell (row, 3, totalCompleted / (table.rowCount () - 2));
-    table.addCell (row, 4, totalDeleted / (table.rowCount () - 2));
+    table.addCell (row, 4, totalDeleted   / (table.rowCount () - 2));
     table.addCell (row, 5, (totalAdded - totalCompleted - totalDeleted) / (table.rowCount () - 2));
   }
 
+  std::stringstream out;
   if (table.rowCount ())
-    out &lt;&lt; optionalBlankLine (conf)
+    out &lt;&lt; optionalBlankLine ()
         &lt;&lt; table.render ()
         &lt;&lt; std::endl;
   else
@@ -1079,117 +898,68 @@ std::string handleReportHistory (TDB&amp; tdb, T&amp; task, Config&amp; conf)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleReportGHistory (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleReportGHistory ()
 {
-  std::stringstream out;
+  std::map &lt;time_t, int&gt; groups;          // Represents any month with data
+  std::map &lt;time_t, int&gt; addedGroup;      // Additions by month
+  std::map &lt;time_t, int&gt; completedGroup;  // Completions by month
+  std::map &lt;time_t, int&gt; deletedGroup;    // Deletions by month
 
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
+  // Scan the pending tasks.
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  context.tdb.load (tasks, context.filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
+
+  foreach (task, tasks)
   {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
-  int widthOfBar = width - 15;   // 15 == strlen (&quot;2008 September &quot;)
+    time_t epoch = monthlyEpoch (task-&gt;get (&quot;entry&quot;));
+    groups[epoch] = 0;
 
-  std::map &lt;time_t, int&gt; groups;
-  std::map &lt;time_t, int&gt; addedGroup;
-  std::map &lt;time_t, int&gt; completedGroup;
-  std::map &lt;time_t, int&gt; deletedGroup;
+    // Every task has an entry date.
+    if (addedGroup.find (epoch) != addedGroup.end ())
+      addedGroup[epoch] = addedGroup[epoch] + 1;
+    else
+      addedGroup[epoch] = 1;
 
-  // Scan the pending tasks.
-  std::vector &lt;T&gt; pending;
-  tdb.allPendingT (pending);
-  handleRecurrence (tdb, pending);
-  filter (pending, task);
-  for (unsigned int i = 0; i &lt; pending.size (); ++i)
-  {
-    T task (pending[i]);
-    time_t epoch = monthlyEpoch (task.getAttribute (&quot;entry&quot;));
-    if (epoch)
+    // All deleted tasks have an end date.
+    if (task-&gt;getStatus () == Task::deleted)
     {
+      epoch = monthlyEpoch (task-&gt;get (&quot;end&quot;));
       groups[epoch] = 0;
 
-      if (addedGroup.find (epoch) != addedGroup.end ())
-        addedGroup[epoch] = addedGroup[epoch] + 1;
+      if (deletedGroup.find (epoch) != deletedGroup.end ())
+        deletedGroup[epoch] = deletedGroup[epoch] + 1;
       else
-        addedGroup[epoch] = 1;
-
-      if (task.getStatus () == T::deleted)
-      {
-        epoch = monthlyEpoch (task.getAttribute (&quot;end&quot;));
-        groups[epoch] = 0;
-
-        if (deletedGroup.find (epoch) != deletedGroup.end ())
-          deletedGroup[epoch] = deletedGroup[epoch] + 1;
-        else
-          deletedGroup[epoch] = 1;
-      }
-      else if (task.getStatus () == T::completed)
-      {
-        epoch = monthlyEpoch (task.getAttribute (&quot;end&quot;));
-        groups[epoch] = 0;
-
-        if (completedGroup.find (epoch) != completedGroup.end ())
-          completedGroup[epoch] = completedGroup[epoch] + 1;
-        else
-          completedGroup[epoch] = 1;
-      }
+        deletedGroup[epoch] = 1;
     }
-  }
 
-  // Scan the completed tasks.
-  std::vector &lt;T&gt; completed;
-  tdb.allCompletedT (completed);
-  filter (completed, task);
-  for (unsigned int i = 0; i &lt; completed.size (); ++i)
-  {
-    T task (completed[i]);
-    time_t epoch = monthlyEpoch (task.getAttribute (&quot;entry&quot;));
-    if (epoch)
+    // All completed tasks have an end date.
+    else if (task-&gt;getStatus () == Task::completed)
     {
+      epoch = monthlyEpoch (task-&gt;get (&quot;end&quot;));
       groups[epoch] = 0;
 
-      if (addedGroup.find (epoch) != addedGroup.end ())
-        addedGroup[epoch] = addedGroup[epoch] + 1;
+      if (completedGroup.find (epoch) != completedGroup.end ())
+        completedGroup[epoch] = completedGroup[epoch] + 1;
       else
-        addedGroup[epoch] = 1;
-
-      epoch = monthlyEpoch (task.getAttribute (&quot;end&quot;));
-      if (task.getStatus () == T::deleted)
-      {
-        epoch = monthlyEpoch (task.getAttribute (&quot;end&quot;));
-        groups[epoch] = 0;
-
-        if (deletedGroup.find (epoch) != deletedGroup.end ())
-          deletedGroup[epoch] = deletedGroup[epoch] + 1;
-        else
-          deletedGroup[epoch] = 1;
-      }
-      else if (task.getStatus () == T::completed)
-      {
-        epoch = monthlyEpoch (task.getAttribute (&quot;end&quot;));
-        groups[epoch] = 0;
-
-        if (completedGroup.find (epoch) != completedGroup.end ())
-          completedGroup[epoch] = completedGroup[epoch] + 1;
-        else
-          completedGroup[epoch] = 1;
-      }
+        completedGroup[epoch] = 1;
     }
   }
 
+  int widthOfBar = context.getWidth () - 15;   // 15 == strlen (&quot;2008 September &quot;)
+
   // Now build the table.
   Table table;
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+  table.setDateFormat (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
   table.addColumn (&quot;Year&quot;);
   table.addColumn (&quot;Month&quot;);
   table.addColumn (&quot;Number Added/Completed/Deleted&quot;);
 
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+  if ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp;
+      context.config.get (std::string (&quot;fontunderline&quot;), &quot;true&quot;))
   {
     table.setColumnUnderline (0);
     table.setColumnUnderline (1);
@@ -1245,7 +1015,7 @@ std::string handleReportGHistory (TDB&amp; tdb, T&amp; task, Config&amp; conf)
       unsigned int deletedBar   = (widthOfBar *   deletedGroup[i-&gt;first]) / maxLine;
 
       std::string bar = &quot;&quot;;
-      if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+      if (context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false))
       {
         char number[24];
         std::string aBar = &quot;&quot;;
@@ -1298,20 +1068,21 @@ std::string handleReportGHistory (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     }
   }
 
+  std::stringstream out;
   if (table.rowCount ())
   {
-    out &lt;&lt; optionalBlankLine (conf)
+    out &lt;&lt; optionalBlankLine ()
         &lt;&lt; table.render ()
         &lt;&lt; std::endl;
 
-    if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+    if (context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false))
       out &lt;&lt; &quot;Legend: &quot;
           &lt;&lt; Text::colorize (Text::black, Text::on_red, &quot;added&quot;)
           &lt;&lt; &quot;, &quot;
           &lt;&lt; Text::colorize (Text::black, Text::on_green, &quot;completed&quot;)
           &lt;&lt; &quot;, &quot;
           &lt;&lt; Text::colorize (Text::black, Text::on_yellow, &quot;deleted&quot;)
-          &lt;&lt; optionalBlankLine (conf)
+          &lt;&lt; optionalBlankLine ()
           &lt;&lt; std::endl;
     else
       out &lt;&lt; &quot;Legend: + added, X completed, - deleted&quot; &lt;&lt; std::endl;
@@ -1323,31 +1094,24 @@ std::string handleReportGHistory (TDB&amp; tdb, T&amp; task, Config&amp; conf)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleReportTimesheet (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleReportTimesheet ()
 {
-  std::stringstream out;
-
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
-  {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
+  // Scan the pending tasks.
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  context.tdb.load (tasks, context.filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
 
-  // Get all the tasks.
-  std::vector &lt;T&gt; tasks;
-  tdb.allT (tasks);
-  filter (tasks, task);
+  // Just do this once.
+  int width = context.getWidth ();
 
   // What day of the week does the user consider the first?
-  int weekStart = Date::dayOfWeek (conf.get (&quot;weekstart&quot;, &quot;Monday&quot;));
-  if (weekStart == -1)
-    throw std::string (&quot;The 'weekstart' configuration variable does &quot;
-                       &quot;not contain a day name, such as 'Monday'.&quot;);
+  int weekStart = Date::dayOfWeek (context.config.get (&quot;weekstart&quot;, &quot;Sunday&quot;));
+  if (weekStart != 0 &amp;&amp; weekStart != 1)
+    throw std::string (&quot;The 'weekstart' configuration variable may &quot;
+                       &quot;only contain 'Sunday' or 'Monday'.&quot;);
 
   // Determine the date of the first day of the most recent report.
   Date today;
@@ -1356,22 +1120,26 @@ std::string handleReportTimesheet (TDB&amp; tdb, T&amp; task, Config&amp; conf)
 
   // Roll back to midnight.
   start = Date (start.month (), start.day (), start.year ());
-  Date end = start + (7 * 86400) - 1;
+  Date end = start + (7 * 86400);
 
   // Determine how many reports to run.
   int quantity = 1;
-  std::vector &lt;int&gt; sequence = task.getAllIds ();
-  if (sequence.size () == 1)
-    quantity = sequence[0];
+  if (context.sequence.size () == 1)
+    quantity = context.sequence[0];
 
+  bool color = context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false);
+
+  std::stringstream out;
   for (int week = 0; week &lt; quantity; ++week)
   {
+    Date endString (end);
+    endString -= 86400;
     out &lt;&lt; std::endl
-        &lt;&lt; Text::colorize (Text::bold, Text::nocolor)
-        &lt;&lt; start.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;))
+        &lt;&lt; (color ? Text::colorize (Text::bold, Text::nocolor) : &quot;&quot;)
+        &lt;&lt; start.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;))
         &lt;&lt; &quot; - &quot;
-        &lt;&lt; end.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;))
-        &lt;&lt; Text::colorize ()
+        &lt;&lt; endString.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;))
+        &lt;&lt; (color ? Text::colorize () : &quot;&quot;)
         &lt;&lt; std::endl;
 
     // Render the completed table.
@@ -1382,9 +1150,14 @@ std::string handleReportTimesheet (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     completed.addColumn (&quot;Due&quot;);
     completed.addColumn (&quot;Description&quot;);
 
-    completed.setColumnUnderline (1);
-    completed.setColumnUnderline (2);
-    completed.setColumnUnderline (3);
+    if (color &amp;&amp; context.config.get (std::string (&quot;fontunderline&quot;), &quot;true&quot;))
+    {
+      completed.setColumnUnderline (1);
+      completed.setColumnUnderline (2);
+      completed.setColumnUnderline (3);
+    }
+    else
+      completed.setTableDashedUnderline ();
 
     completed.setColumnWidth (0, Table::minimum);
     completed.setColumnWidth (1, Table::minimum);
@@ -1396,42 +1169,24 @@ std::string handleReportTimesheet (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     completed.setColumnJustification (2, Table::right);
     completed.setColumnJustification (3, Table::left);
 
-    foreach (t, tasks)
+    foreach (task, tasks)
     {
       // If task completed within range.
-      if (t-&gt;getStatus () == T::completed)
+      if (task-&gt;getStatus () == Task::completed)
       {
-        Date compDate (::atoi (t-&gt;getAttribute (&quot;end&quot;).c_str ()));
+        Date compDate (::atoi (task-&gt;get (&quot;end&quot;).c_str ()));
         if (compDate &gt;= start &amp;&amp; compDate &lt; end)
         {
           int row = completed.addRow ();
-          completed.addCell (row, 1, t-&gt;getAttribute (&quot;project&quot;));
-
-          std::string due = t-&gt;getAttribute (&quot;due&quot;);
-          if (due.length ())
-          {
-            Date d (::atoi (due.c_str ()));
-            due = d.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-            completed.addCell (row, 2, due);
-          }
+          completed.addCell (row, 1, task-&gt;get (&quot;project&quot;));
+          completed.addCell (row, 2, getDueDate (*task));
+          completed.addCell (row, 3, getFullDescription (*task));
 
-          std::string description = t-&gt;getDescription ();
-          std::string when;
-          std::map &lt;time_t, std::string&gt; annotations;
-          t-&gt;getAnnotations (annotations);
-          foreach (anno, annotations)
+          if (color)
           {
-            Date dt (anno-&gt;first);
-            when = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-            description += &quot;\n&quot; + when + &quot; &quot; + anno-&gt;second;
-          }
-          completed.addCell (row, 3, description);
-
-          if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-          {
-            Text::color fg = Text::colorCode (t-&gt;getAttribute (&quot;fg&quot;));
-            Text::color bg = Text::colorCode (t-&gt;getAttribute (&quot;bg&quot;));
-            autoColorize (*t, fg, bg, conf);
+            Text::color fg = Text::colorCode (task-&gt;get (&quot;fg&quot;));
+            Text::color bg = Text::colorCode (task-&gt;get (&quot;bg&quot;));
+            autoColorize (*task, fg, bg);
             completed.setRowFg (row, fg);
             completed.setRowBg (row, bg);
           }
@@ -1453,9 +1208,14 @@ std::string handleReportTimesheet (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     started.addColumn (&quot;Due&quot;);
     started.addColumn (&quot;Description&quot;);
 
-    started.setColumnUnderline (1);
-    started.setColumnUnderline (2);
-    started.setColumnUnderline (3);
+    if (color &amp;&amp; context.config.get (std::string (&quot;fontunderline&quot;), &quot;true&quot;))
+    {
+      completed.setColumnUnderline (1);
+      completed.setColumnUnderline (2);
+      completed.setColumnUnderline (3);
+    }
+    else
+      completed.setTableDashedUnderline ();
 
     started.setColumnWidth (0, Table::minimum);
     started.setColumnWidth (1, Table::minimum);
@@ -1466,43 +1226,25 @@ std::string handleReportTimesheet (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     started.setColumnJustification (1, Table::left);
     started.setColumnJustification (2, Table::right);
     started.setColumnJustification (3, Table::left);
-    foreach (t, tasks)
+    foreach (task, tasks)
     {
       // If task started within range, but not completed withing range.
-      if (t-&gt;getStatus () == T::pending &amp;&amp;
-          t-&gt;getAttribute (&quot;start&quot;) != &quot;&quot;)
+      if (task-&gt;getStatus () == Task::pending &amp;&amp;
+          task-&gt;has (&quot;start&quot;))
       {
-        Date startDate (::atoi (t-&gt;getAttribute (&quot;start&quot;).c_str ()));
+        Date startDate (::atoi (task-&gt;get (&quot;start&quot;).c_str ()));
         if (startDate &gt;= start &amp;&amp; startDate &lt; end)
         {
           int row = started.addRow ();
-          started.addCell (row, 1, t-&gt;getAttribute (&quot;project&quot;));
-
-          std::string due = t-&gt;getAttribute (&quot;due&quot;);
-          if (due.length ())
-          {
-            Date d (::atoi (due.c_str ()));
-            due = d.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-            started.addCell (row, 2, due);
-          }
-
-          std::string description = t-&gt;getDescription ();
-          std::string when;
-          std::map &lt;time_t, std::string&gt; annotations;
-          t-&gt;getAnnotations (annotations);
-          foreach (anno, annotations)
-          {
-            Date dt (anno-&gt;first);
-            when = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-            description += &quot;\n&quot; + when + &quot; &quot; + anno-&gt;second;
-          }
-          started.addCell (row, 3, description);
+          started.addCell (row, 1, task-&gt;get (&quot;project&quot;));
+          started.addCell (row, 2, getDueDate (*task));
+          started.addCell (row, 3, getFullDescription (*task));
 
-          if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+          if (color)
           {
-            Text::color fg = Text::colorCode (t-&gt;getAttribute (&quot;fg&quot;));
-            Text::color bg = Text::colorCode (t-&gt;getAttribute (&quot;bg&quot;));
-            autoColorize (*t, fg, bg, conf);
+            Text::color fg = Text::colorCode (task-&gt;get (&quot;fg&quot;));
+            Text::color bg = Text::colorCode (task-&gt;get (&quot;bg&quot;));
+            autoColorize (*task, fg, bg);
             started.setRowFg (row, fg);
             started.setRowBg (row, bg);
           }
@@ -1530,26 +1272,46 @@ std::string renderMonths (
   int firstMonth,
   int firstYear,
   const Date&amp; today,
-  std::vector &lt;T&gt;&amp; all,
-  Config&amp; conf,
+  std::vector &lt;Task&gt;&amp; all,
   int monthsPerLine)
 {
   Table table;
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+  table.setDateFormat (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+
+  // What day of the week does the user consider the first?
+  int weekStart = Date::dayOfWeek (context.config.get (&quot;weekstart&quot;, &quot;Sunday&quot;));
+  if (weekStart != 0 &amp;&amp; weekStart != 1)
+    throw std::string (&quot;The 'weekstart' configuration variable may &quot;
+                       &quot;only contain 'Sunday' or 'Monday'.&quot;);
 
   // Build table for the number of months to be displayed.
   for (int i = 0 ; i &lt; (monthsPerLine * 8); i += 8)
   {
-    table.addColumn (&quot; &quot;);
-    table.addColumn (&quot;Su&quot;);
-    table.addColumn (&quot;Mo&quot;);
-    table.addColumn (&quot;Tu&quot;);
-    table.addColumn (&quot;We&quot;);
-    table.addColumn (&quot;Th&quot;);
-    table.addColumn (&quot;Fr&quot;);
-    table.addColumn (&quot;Sa&quot;);
-
-    if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+    if (weekStart == 1)
+    {
+      table.addColumn (&quot; &quot;);
+      table.addColumn (&quot;Mo&quot;);
+      table.addColumn (&quot;Tu&quot;);
+      table.addColumn (&quot;We&quot;);
+      table.addColumn (&quot;Th&quot;);
+      table.addColumn (&quot;Fr&quot;);
+      table.addColumn (&quot;Sa&quot;);
+      table.addColumn (&quot;Su&quot;);
+    }
+    else
+    {
+      table.addColumn (&quot; &quot;);
+      table.addColumn (&quot;Su&quot;);
+      table.addColumn (&quot;Mo&quot;);
+      table.addColumn (&quot;Tu&quot;);
+      table.addColumn (&quot;We&quot;);
+      table.addColumn (&quot;Th&quot;);
+      table.addColumn (&quot;Fr&quot;);
+      table.addColumn (&quot;Sa&quot;);
+    }
+
+    if ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp;
+        context.config.get (std::string (&quot;fontunderline&quot;), &quot;true&quot;))
     {
       table.setColumnUnderline (i + 1);
       table.setColumnUnderline (i + 2);
@@ -1570,6 +1332,9 @@ std::string renderMonths (
     table.setColumnJustification (i + 5, Table::right);
     table.setColumnJustification (i + 6, Table::right);
     table.setColumnJustification (i + 7, Table::right);
+
+    // This creates a nice gap between the months.
+    table.setColumnWidth (i + 0, 4);
   }
 
   // At most, we need 6 rows.
@@ -1604,44 +1369,61 @@ std::string renderMonths (
   int row = 0;
 
   // Loop through months to be added on this line.
-  for (int c = 0; c &lt; monthsPerLine ; c++)
+  for (int mpl = 0; mpl &lt; monthsPerLine ; mpl++)
   {
     // Reset row counter for subsequent months
-    if (c != 0)
+    if (mpl != 0)
       row = 0;
 
     // Loop through days in month and add to table.
-    for (int d = 1; d &lt;= daysInMonth.at (c); ++d)
+    for (int d = 1; d &lt;= daysInMonth.at (mpl); ++d)
     {
-      Date temp (months.at (c), d, years.at (c));
+      Date temp (months.at (mpl), d, years.at (mpl));
       int dow = temp.dayOfWeek ();
-      int thisCol = dow + 1 + (8 * c);
+      int woy = temp.weekOfYear (weekStart);
+
+      if (context.config.get (&quot;displayweeknumber&quot;, true))
+        table.addCell (row, (8 * mpl), woy);
+
+      // Calculate column id.
+      int thisCol = dow +                       // 0 = Sunday
+                    (weekStart == 1 ? 0 : 1) +  // Offset for weekStart
+                    (8 * mpl);                  // Columns in 1 month
+
+      if (thisCol == (8 * mpl))
+        thisCol += 7;
 
       table.addCell (row, thisCol, d);
 
-      if ((conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp;
+      if ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp;
           today.day ()   == d              &amp;&amp;
-          today.month () == months.at (c)  &amp;&amp;
-          today.year ()  == years.at (c))
+          today.month () == months.at (mpl)  &amp;&amp;
+          today.year ()  == years.at (mpl))
         table.setCellFg (row, thisCol, Text::cyan);
 
-      std::vector &lt;T&gt;::iterator it;
-      for (it = all.begin (); it != all.end (); ++it)
+      foreach (task, all)
       {
-        Date due (::atoi (it-&gt;getAttribute (&quot;due&quot;).c_str ()));
-
-        if ((conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp;
-            due.day ()   == d             &amp;&amp;
-            due.month () == months.at (c) &amp;&amp;
-            due.year ()  == years.at (c))
+        if (task-&gt;getStatus () == Task::pending &amp;&amp;
+            task-&gt;has (&quot;due&quot;))
         {
-          table.setCellFg (row, thisCol, Text::black);
-          table.setCellBg (row, thisCol, due &lt; today ? Text::on_red : Text::on_yellow);
+          Date due (::atoi (task-&gt;get (&quot;due&quot;).c_str ()));
+
+          if ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp;
+              due.day ()   == d               &amp;&amp;
+              due.month () == months.at (mpl) &amp;&amp;
+              due.year ()  == years.at (mpl))
+          {
+            table.setCellFg (row, thisCol, Text::black);
+            table.setCellBg (row, thisCol, due &lt; today ? Text::on_red : Text::on_yellow);
+          }
         }
       }
 
       // Check for end of week, and...
-      if (dow == 6 &amp;&amp; d &lt; daysInMonth.at (c))
+      int eow = 6;
+      if (weekStart == 1)
+        eow = 0;
+      if (dow == eow &amp;&amp; d &lt; daysInMonth.at (mpl))
         row++;
     }
   }
@@ -1650,59 +1432,109 @@ std::string renderMonths (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleReportCalendar (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleReportCalendar ()
 {
-  std::stringstream out;
-
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
-  {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
-
-  // Each month requires 23 text columns width.  See how many will actually
+  // Each month requires 28 text columns width.  See how many will actually
   // fit.  But if a preference is specified, and it fits, use it.
-  int preferredMonthsPerLine = (conf.get (std::string (&quot;monthsperline&quot;), 0));
-  int monthsThatFit = width / 23;
+  int width = context.getWidth ();
+  int preferredMonthsPerLine = (context.config.get (std::string (&quot;monthsperline&quot;), 0));
+  int monthsThatFit = width / 26;
 
   int monthsPerLine = monthsThatFit;
   if (preferredMonthsPerLine != 0 &amp;&amp; preferredMonthsPerLine &lt; monthsThatFit)
     monthsPerLine = preferredMonthsPerLine;
 
-  // Load all the pending tasks.
-  std::vector &lt;T&gt; pending;
-  tdb.allPendingT (pending);
-  handleRecurrence (tdb, pending);
-  filter (pending, task);
-
-  // Find the oldest pending due date.
-  Date oldest;
-  Date newest;
-  std::vector &lt;T&gt;::iterator it;
-  for (it = pending.begin (); it != pending.end (); ++it)
-  {
-    if (it-&gt;getAttribute (&quot;due&quot;) != &quot;&quot;)
-    {
-      Date d (::atoi (it-&gt;getAttribute (&quot;due&quot;).c_str ()));
+  // Get all the tasks.
+  std::vector &lt;Task&gt; tasks;
+  Filter filter;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  context.tdb.loadPending (tasks, filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
 
-      if (d &lt; oldest) oldest = d;
-      if (d &gt; newest) newest = d;
+  Date today;
+  bool getpendingdate = false;
+  int monthsToDisplay = 1;
+  int mFrom = today.month ();
+  int yFrom = today.year ();
+  int mTo;
+  int yTo;
+
+  // Determine what to do
+  int numberOfArgs = context.args.size();
+
+  if (numberOfArgs == 1 ) {
+    // task cal
+    monthsToDisplay = monthsPerLine;
+    mFrom = today.month();
+    yFrom = today.year();
+  }
+  else if (numberOfArgs == 2 ) {
+    if (context.args[1] == &quot;y&quot;) {
+      // task cal y
+      monthsToDisplay = 12;
+      mFrom = today.month();
+      yFrom = today.year();
     }
+    else if (context.args[1] == &quot;due&quot;) {
+      // task cal due
+      monthsToDisplay = monthsPerLine;
+      getpendingdate = true;
+    }
+    else {
+      // task cal 2010
+      monthsToDisplay = 12;
+      mFrom = 1;
+      yFrom = ::atoi( context.args[1].data());
+    }
+  }
+  else if (numberOfArgs == 3 ) {
+    if (context.args[2] == &quot;y&quot;) {
+      // task cal due y
+      monthsToDisplay = 12;
+      getpendingdate = true;
+    }
+    else {
+      // task cal 8 2010
+      monthsToDisplay = monthsPerLine;
+      mFrom = ::atoi( context.args[1].data());
+      yFrom = ::atoi( context.args[2].data());
+    }
+  }
+  else if (numberOfArgs == 4 ) {
+    // task cal 8 2010 y
+    monthsToDisplay = 12;
+    mFrom = ::atoi( context.args[1].data());
+    yFrom = ::atoi( context.args[2].data());
   }
 
-  // Iterate from oldest due month, year to newest month, year.
-  Date today;
-  int mFrom = oldest.month ();
-  int yFrom = oldest.year ();
+  if (getpendingdate == true) {
+    // Find the oldest pending due date.
+    Date oldest (12,31,2037);
+    foreach (task, tasks)
+    {
+      if (task-&gt;getStatus () == Task::pending)
+      {
+        if (task-&gt;has (&quot;due&quot;))
+        {
+          Date d (::atoi (task-&gt;get (&quot;due&quot;).c_str ()));
+          if (d &lt; oldest) oldest = d;
+        }
+      }
+    }
+    mFrom = oldest.month();
+    yFrom = oldest.year();
+  }
 
-  int mTo = newest.month ();
-  int yTo = newest.year ();
+  mTo = mFrom + monthsToDisplay - 1;
+  yTo = yFrom;
+  if (mTo &gt; 12) {
+    mTo -=12;
+    yTo++;
+  }
 
+  std::stringstream out;
   out &lt;&lt; std::endl;
   std::string output;
 
@@ -1715,14 +1547,31 @@ std::string handleReportCalendar (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     for (int i = 0 ; i &lt; monthsPerLine ; i++)
     {
       std::string month = Date::monthName (nextM);
-      int left = (18 - month.length ()) / 2 + 1;
-      int right = 18 - left - month.length ();
 
-      out &lt;&lt; std::setw (left) &lt;&lt; ' '
+      //    12345678901234567890123456 = 26 chars wide
+      //                ^^             = center
+      //    &lt;-------&gt;                  = 13 - (month.length / 2) + 1
+      //                      &lt;------&gt; = 26 - above
+      //   +--------------------------+
+      //   |         July 2009        |
+      //   |     Mo Tu We Th Fr Sa Su |
+      //   |  27        1  2  3  4  5 |
+      //   |  28  6  7  8  9 10 11 12 |
+      //   |  29 13 14 15 16 17 18 19 |
+      //   |  30 20 21 22 23 24 25 26 |
+      //   |  31 27 28 29 30 31       |
+      //   +--------------------------+
+
+      int totalWidth = 26;
+      int labelWidth = month.length () + 5;  // 5 = &quot; 2009&quot;
+      int leftGap = (totalWidth / 2) - (labelWidth / 2);
+      int rightGap = totalWidth - leftGap - labelWidth;
+
+      out &lt;&lt; std::setw (leftGap) &lt;&lt; ' '
           &lt;&lt; month
           &lt;&lt; ' '
           &lt;&lt; nextY
-          &lt;&lt; std::setw (right) &lt;&lt; ' ';
+          &lt;&lt; std::setw (rightGap) &lt;&lt; ' ';
 
       if (++nextM &gt; 12)
       {
@@ -1732,8 +1581,8 @@ std::string handleReportCalendar (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     }
 
     out &lt;&lt; std::endl
-        &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; renderMonths (mFrom, yFrom, today, pending, conf, monthsPerLine)
+        &lt;&lt; optionalBlankLine ()
+        &lt;&lt; renderMonths (mFrom, yFrom, today, tasks, monthsPerLine)
         &lt;&lt; std::endl;
 
     mFrom += monthsPerLine;
@@ -1744,7 +1593,7 @@ std::string handleReportCalendar (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     }
   }
 
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+  if (context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false))
     out &lt;&lt; &quot;Legend: &quot;
         &lt;&lt; Text::colorize (Text::cyan, Text::nocolor, &quot;today&quot;)
         &lt;&lt; &quot;, &quot;
@@ -1752,288 +1601,48 @@ std::string handleReportCalendar (TDB&amp; tdb, T&amp; task, Config&amp; conf)
         &lt;&lt; &quot;, &quot;
         &lt;&lt; Text::colorize (Text::black, Text::on_red, &quot;overdue&quot;)
         &lt;&lt; &quot;.&quot;
-        &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; std::endl;
-
-  return out.str ();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string handleReportActive (TDB&amp; tdb, T&amp; task, Config&amp; conf)
-{
-  std::stringstream out;
-
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
-  {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
-
-  // Get all the tasks.
-  std::vector &lt;T&gt; tasks;
-  tdb.pendingT (tasks);
-  filter (tasks, task);
-
-  initializeColorRules (conf);
-
-  // Create a table for output.
-  Table table;
-  table.setTableWidth (width);
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-  table.addColumn (&quot;ID&quot;);
-  table.addColumn (&quot;Project&quot;);
-  table.addColumn (&quot;Pri&quot;);
-  table.addColumn (&quot;Due&quot;);
-  table.addColumn (&quot;Description&quot;);
-
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-  {
-    table.setColumnUnderline (0);
-    table.setColumnUnderline (1);
-    table.setColumnUnderline (2);
-    table.setColumnUnderline (3);
-    table.setColumnUnderline (4);
-  }
-  else
-    table.setTableDashedUnderline ();
-
-  table.setColumnWidth (0, Table::minimum);
-  table.setColumnWidth (1, Table::minimum);
-  table.setColumnWidth (2, Table::minimum);
-  table.setColumnWidth (3, Table::minimum);
-  table.setColumnWidth (4, Table::flexible);
-
-  table.setColumnJustification (0, Table::right);
-  table.setColumnJustification (3, Table::right);
-
-  table.sortOn (3, Table::ascendingDate);
-  table.sortOn (2, Table::descendingPriority);
-  table.sortOn (1, Table::ascendingCharacter);
-
-  // Iterate over each task, and apply selection criteria.
-  for (unsigned int i = 0; i &lt; tasks.size (); ++i)
-  {
-    T refTask (tasks[i]);
-    if (refTask.getAttribute (&quot;start&quot;) != &quot;&quot;)
-    {
-      Date now;
-      bool imminent = false;
-      bool overdue = false;
-      std::string due = refTask.getAttribute (&quot;due&quot;);
-      if (due.length ())
-      {
-        switch (getDueState (due))
-        {
-        case 2: overdue = true;  break;
-        case 1: imminent = true; break;
-        case 0:
-        default:                 break;
-        }
-
-        Date dt (::atoi (due.c_str ()));
-        due = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-      }
-
-      // All criteria match, so add refTask to the output table.
-      int row = table.addRow ();
-      table.addCell (row, 0, refTask.getId ());
-      table.addCell (row, 1, refTask.getAttribute (&quot;project&quot;));
-      table.addCell (row, 2, refTask.getAttribute (&quot;priority&quot;));
-      table.addCell (row, 3, due);
-
-      std::string description = refTask.getDescription ();
-      std::string when;
-      std::map &lt;time_t, std::string&gt; annotations;
-      refTask.getAnnotations (annotations);
-      foreach (anno, annotations)
-      {
-        Date dt (anno-&gt;first);
-        when = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-        description += &quot;\n&quot; + when + &quot; &quot; + anno-&gt;second;
-      }
-
-      table.addCell (row, 4, description);
-
-      if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-      {
-        Text::color fg = Text::colorCode (refTask.getAttribute (&quot;fg&quot;));
-        Text::color bg = Text::colorCode (refTask.getAttribute (&quot;bg&quot;));
-        autoColorize (refTask, fg, bg, conf);
-        table.setRowFg (row, fg);
-        table.setRowBg (row, bg);
-
-        if (fg == Text::nocolor)
-        {
-          if (overdue)
-            table.setCellFg (row, 3, Text::colorCode (conf.get (&quot;color.overdue&quot;, &quot;red&quot;)));
-          else if (imminent)
-            table.setCellFg (row, 3, Text::colorCode (conf.get (&quot;color.due&quot;, &quot;yellow&quot;)));
-        }
-      }
-    }
-  }
-
-  if (table.rowCount ())
-    out &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; table.render ()
-        &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; table.rowCount ()
-        &lt;&lt; (table.rowCount () == 1 ? &quot; task&quot; : &quot; tasks&quot;)
+        &lt;&lt; optionalBlankLine ()
         &lt;&lt; std::endl;
-  else
-    out &lt;&lt; &quot;No active tasks.&quot; &lt;&lt; std::endl;
 
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string handleReportOverdue (TDB&amp; tdb, T&amp; task, Config&amp; conf)
+std::string handleReportStats ()
 {
   std::stringstream out;
 
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
-  {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
-
-  // Get all the tasks.
-  std::vector &lt;T&gt; tasks;
-  tdb.pendingT (tasks);
-  filter (tasks, task);
+  // Go get the file sizes.
+  size_t dataSize = 0;
 
-  initializeColorRules (conf);
+  struct stat s;
+  std::string location = expandPath (context.config.get (&quot;data.location&quot;));
+  std::string file = location + &quot;/pending.data&quot;;
+  if (!stat (file.c_str (), &amp;s))
+    dataSize += s.st_size;
 
-  // Create a table for output.
-  Table table;
-  table.setTableWidth (width);
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-  table.addColumn (&quot;ID&quot;);
-  table.addColumn (&quot;Project&quot;);
-  table.addColumn (&quot;Pri&quot;);
-  table.addColumn (&quot;Due&quot;);
-  table.addColumn (&quot;Description&quot;);
-
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-  {
-    table.setColumnUnderline (0);
-    table.setColumnUnderline (1);
-    table.setColumnUnderline (2);
-    table.setColumnUnderline (3);
-    table.setColumnUnderline (4);
-  }
-  else
-    table.setTableDashedUnderline ();
+  file = location + &quot;/completed.data&quot;;
+  if (!stat (file.c_str (), &amp;s))
+    dataSize += s.st_size;
 
-  table.setColumnWidth (0, Table::minimum);
-  table.setColumnWidth (1, Table::minimum);
-  table.setColumnWidth (2, Table::minimum);
-  table.setColumnWidth (3, Table::minimum);
-  table.setColumnWidth (4, Table::flexible);
+  file = location + &quot;/undo.data&quot;;
+  if (!stat (file.c_str (), &amp;s))
+    dataSize += s.st_size;
 
-  table.setColumnJustification (0, Table::right);
-  table.setColumnJustification (3, Table::right);
-
-  table.sortOn (3, Table::ascendingDate);
-  table.sortOn (2, Table::descendingPriority);
-  table.sortOn (1, Table::ascendingCharacter);
-
-  Date now;
-
-  // Iterate over each task, and apply selection criteria.
-  for (unsigned int i = 0; i &lt; tasks.size (); ++i)
-  {
-    T refTask (tasks[i]);
-    std::string due;
-    if ((due = refTask.getAttribute (&quot;due&quot;)) != &quot;&quot;)
-    {
-      if (due.length ())
-      {
-        Date dt (::atoi (due.c_str ()));
-        due = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-
-        // If overdue.
-        if (dt &lt; now)
-        {
-          // All criteria match, so add refTask to the output table.
-          int row = table.addRow ();
-          table.addCell (row, 0, refTask.getId ());
-          table.addCell (row, 1, refTask.getAttribute (&quot;project&quot;));
-          table.addCell (row, 2, refTask.getAttribute (&quot;priority&quot;));
-          table.addCell (row, 3, due);
-
-          std::string description = refTask.getDescription ();
-          std::string when;
-          std::map &lt;time_t, std::string&gt; annotations;
-          refTask.getAnnotations (annotations);
-          foreach (anno, annotations)
-          {
-            Date dt (anno-&gt;first);
-            when = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-            description += &quot;\n&quot; + when + &quot; &quot; + anno-&gt;second;
-          }
-
-          table.addCell (row, 4, description);
-
-          if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-          {
-            Text::color fg = Text::colorCode (refTask.getAttribute (&quot;fg&quot;));
-            Text::color bg = Text::colorCode (refTask.getAttribute (&quot;bg&quot;));
-            autoColorize (refTask, fg, bg, conf);
-            table.setRowFg (row, fg);
-            table.setRowBg (row, bg);
-
-            if (fg == Text::nocolor)
-              table.setCellFg (row, 3, Text::red);
-          }
-        }
-      }
-    }
-  }
-
-  if (table.rowCount ())
-    out &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; table.render ()
-        &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; table.rowCount ()
-        &lt;&lt; (table.rowCount () == 1 ? &quot; task&quot; : &quot; tasks&quot;)
-        &lt;&lt; std::endl;
-  else
-    out &lt;&lt; &quot;No overdue tasks.&quot; &lt;&lt; std::endl;
-
-  return out.str ();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-std::string handleReportStats (TDB&amp; tdb, T&amp; task, Config&amp; conf)
-{
-  std::stringstream out;
-
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
-  {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
+  std::vector &lt;std::string&gt; undo;
+  slurp (file, undo, false);
+  int undoCount = 0;
+  foreach (tx, undo)
+    if (tx-&gt;substr (0, 3) == &quot;---&quot;)
+      ++undoCount;
 
   // Get all the tasks.
-  std::vector &lt;T&gt; tasks;
-  tdb.allT (tasks);
-  filter (tasks, task);
+  std::vector &lt;Task&gt; tasks;
+  context.tdb.lock (context.config.get (&quot;locking&quot;, true));
+  handleRecurrence ();
+  context.tdb.load (tasks, context.filter);
+  context.tdb.commit ();
+  context.tdb.unlock ();
 
   Date now;
   time_t earliest   = time (NULL);
@@ -2042,6 +1651,7 @@ std::string handleReportStats (TDB&amp; tdb, T&amp; task, Config&amp; conf)
   int deletedT      = 0;
   int pendingT      = 0;
   int completedT    = 0;
+  int waitingT      = 0;
   int taggedT       = 0;
   int annotationsT  = 0;
   int recurringT    = 0;
@@ -2050,31 +1660,34 @@ std::string handleReportStats (TDB&amp; tdb, T&amp; task, Config&amp; conf)
   std::map &lt;std::string, int&gt; allTags;
   std::map &lt;std::string, int&gt; allProjects;
 
-  std::vector &lt;T&gt;::iterator it;
+  std::vector &lt;Task&gt;::iterator it;
   for (it = tasks.begin (); it != tasks.end (); ++it)
   {
     ++totalT;
-    if (it-&gt;getStatus () == T::deleted)   ++deletedT;
-    if (it-&gt;getStatus () == T::pending)   ++pendingT;
-    if (it-&gt;getStatus () == T::completed) ++completedT;
-    if (it-&gt;getStatus () == T::recurring) ++recurringT;
+    if (it-&gt;getStatus () == Task::deleted)   ++deletedT;
+    if (it-&gt;getStatus () == Task::pending)   ++pendingT;
+    if (it-&gt;getStatus () == Task::completed) ++completedT;
+    if (it-&gt;getStatus () == Task::recurring) ++recurringT;
+    if (it-&gt;getStatus () == Task::waiting)   ++waitingT;
 
-    time_t entry = ::atoi (it-&gt;getAttribute (&quot;entry&quot;).c_str ());
+    time_t entry = ::atoi (it-&gt;get (&quot;entry&quot;).c_str ());
     if (entry &lt; earliest) earliest = entry;
     if (entry &gt; latest)   latest   = entry;
 
-    if (it-&gt;getStatus () == T::completed)
+    if (it-&gt;getStatus () == Task::completed)
     {
-      time_t end = ::atoi (it-&gt;getAttribute (&quot;end&quot;).c_str ());
+      time_t end = ::atoi (it-&gt;get (&quot;end&quot;).c_str ());
       daysPending += (end - entry) / 86400.0;
     }
 
-    if (it-&gt;getStatus () == T::pending)
+    if (it-&gt;getStatus () == Task::pending)
       daysPending += (now - entry) / 86400.0;
 
-    descLength += it-&gt;getDescription ().length ();
+    descLength += it-&gt;get (&quot;description&quot;).length ();
 
-    annotationsT += it-&gt;getAnnotationCount ();
+    std::vector &lt;Att&gt; annotations;
+    it-&gt;getAnnotations (annotations);
+    annotationsT += annotations.size ();
 
     std::vector &lt;std::string&gt; tags;
     it-&gt;getTags (tags);
@@ -2083,19 +1696,21 @@ std::string handleReportStats (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     foreach (t, tags)
       allTags[*t] = 0;
 
-    std::string project = it-&gt;getAttribute (&quot;project&quot;);
+    std::string project = it-&gt;get (&quot;project&quot;);
     if (project != &quot;&quot;)
       allProjects[project] = 0;
   }
 
   // Create a table for output.
   Table table;
-  table.setTableWidth (width);
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+  table.setTableWidth (context.getWidth ());
+  table.setTableIntraPadding (2);
+  table.setDateFormat (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
   table.addColumn (&quot;Category&quot;);
   table.addColumn (&quot;Data&quot;);
 
-  if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
+  if ((context.config.get (&quot;color&quot;, true) || context.config.get (std::string (&quot;_forcecolor&quot;), false)) &amp;&amp;
+      context.config.get (std::string (&quot;fontunderline&quot;), &quot;true&quot;))
   {
     table.setColumnUnderline (0);
     table.setColumnUnderline (1);
@@ -2114,6 +1729,10 @@ std::string handleReportStats (TDB&amp; tdb, T&amp; task, Config&amp; conf)
   table.addCell (row, 1, pendingT);
 
   row = table.addRow ();
+  table.addCell (row, 0, &quot;Waiting&quot;);
+  table.addCell (row, 1, waitingT);
+
+  row = table.addRow ();
   table.addCell (row, 0, &quot;Recurring&quot;);
   table.addCell (row, 1, recurringT);
 
@@ -2141,6 +1760,14 @@ std::string handleReportStats (TDB&amp; tdb, T&amp; task, Config&amp; conf)
   table.addCell (row, 0, &quot;Projects&quot;);
   table.addCell (row, 1, (int)allProjects.size ());
 
+  row = table.addRow ();
+  table.addCell (row, 0, &quot;Data size&quot;);
+  table.addCell (row, 1, formatBytes (dataSize));
+
+  row = table.addRow ();
+  table.addCell (row, 0, &quot;Undo transactions&quot;);
+  table.addCell (row, 1, undoCount);
+
   if (totalT)
   {
     row = table.addRow ();
@@ -2156,12 +1783,12 @@ std::string handleReportStats (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     Date e (earliest);
     row = table.addRow ();
     table.addCell (row, 0, &quot;Oldest task&quot;);
-    table.addCell (row, 1, e.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;)));
+    table.addCell (row, 1, e.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;)));
 
     Date l (latest);
     row = table.addRow ();
     table.addCell (row, 0, &quot;Newest task&quot;);
-    table.addCell (row, 1, l.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;)));
+    table.addCell (row, 1, l.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;)));
 
     row = table.addRow ();
     table.addCell (row, 0, &quot;Task used for&quot;);
@@ -2205,667 +1832,211 @@ std::string handleReportStats (TDB&amp; tdb, T&amp; task, Config&amp; conf)
     table.addCell (row, 1, value.str ());
   }
 
-  out &lt;&lt; optionalBlankLine (conf)
+  out &lt;&lt; optionalBlankLine ()
       &lt;&lt; table.render ()
-      &lt;&lt; optionalBlankLine (conf);
+      &lt;&lt; optionalBlankLine ();
 
   return out.str ();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-void gatherNextTasks (
-  const TDB&amp; tdb,
-  T&amp; task,
-  Config&amp; conf,
-  std::vector &lt;T&gt;&amp; pending,
-  std::vector &lt;int&gt;&amp; all)
+void gatherNextTasks (std::vector &lt;Task&gt;&amp; tasks)
 {
   // For counting tasks by project.
   std::map &lt;std::string, int&gt; countByProject;
   std::map &lt;int, bool&gt; matching;
-
+  std::vector &lt;Task&gt; filtered;
   Date now;
 
-  // How many items per project?  Default 3.
-  int limit = conf.get (&quot;next&quot;, 3);
+  // How many items per project?  Default 2.
+  int limit = context.config.get (&quot;next&quot;, 2);
 
   // due:&lt; 1wk, pri:*
-  for (unsigned int i = 0; i &lt; pending.size (); ++i)
+  foreach (task, tasks)
   {
-    if (pending[i].getStatus () == T::pending)
+    if (task-&gt;has (&quot;due&quot;))
     {
-      std::string due = pending[i].getAttribute (&quot;due&quot;);
-      if (due != &quot;&quot;)
+      Date d (::atoi (task-&gt;get (&quot;due&quot;).c_str ()));
+      if (d &lt; now + (7 * 24 * 60 * 60)) // if due:&lt; 1wk
       {
-        Date d (::atoi (due.c_str ()));
-        if (d &lt; now + (7 * 24 * 60 * 60)) // if due:&lt; 1wk
+        std::string project = task-&gt;get (&quot;project&quot;);
+        if (countByProject[project] &lt; limit &amp;&amp; matching.find (task-&gt;id) == matching.end ())
         {
-          std::string project = pending[i].getAttribute (&quot;project&quot;);
-          if (countByProject[project] &lt; limit &amp;&amp; matching.find (i) == matching.end ())
-          {
-            ++countByProject[project];
-            matching[i] = true;
-          }
+          ++countByProject[project];
+          matching[task-&gt;id] = true;
+          filtered.push_back (*task);
         }
       }
     }
   }
 
   // due:*, pri:H
-  for (unsigned int i = 0; i &lt; pending.size (); ++i)
+  foreach (task, tasks)
   {
-    if (pending[i].getStatus () == T::pending)
+    if (task-&gt;has (&quot;due&quot;))
     {
-      std::string due = pending[i].getAttribute (&quot;due&quot;);
-      if (due != &quot;&quot;)
-      {
-        std::string priority = pending[i].getAttribute (&quot;priority&quot;);
-        if (priority == &quot;H&quot;)
-        {
-          std::string project = pending[i].getAttribute (&quot;project&quot;);
-          if (countByProject[project] &lt; limit &amp;&amp; matching.find (i) == matching.end ())
-          {
-            ++countByProject[project];
-            matching[i] = true;
-          }
-        }
-      }
-    }
-  }
-
-  // pri:H
-  for (unsigned int i = 0; i &lt; pending.size (); ++i)
-  {
-    if (pending[i].getStatus () == T::pending)
-    {
-      std::string priority = pending[i].getAttribute (&quot;priority&quot;);
+      std::string priority = task-&gt;get (&quot;priority&quot;);
       if (priority == &quot;H&quot;)
       {
-        std::string project = pending[i].getAttribute (&quot;project&quot;);
-        if (countByProject[project] &lt; limit &amp;&amp; matching.find (i) == matching.end ())
+        std::string project = task-&gt;get (&quot;project&quot;);
+        if (countByProject[project] &lt; limit &amp;&amp; matching.find (task-&gt;id) == matching.end ())
         {
           ++countByProject[project];
-          matching[i] = true;
+          matching[task-&gt;id] = true;
+          filtered.push_back (*task);
         }
       }
     }
   }
 
-  // due:*, pri:M
-  for (unsigned int i = 0; i &lt; pending.size (); ++i)
+  // pri:H
+  foreach (task, tasks)
   {
-    if (pending[i].getStatus () == T::pending)
+    std::string priority = task-&gt;get (&quot;priority&quot;);
+    if (priority == &quot;H&quot;)
     {
-      std::string due = pending[i].getAttribute (&quot;due&quot;);
-      if (due != &quot;&quot;)
+      std::string project = task-&gt;get (&quot;project&quot;);
+      if (countByProject[project] &lt; limit &amp;&amp; matching.find (task-&gt;id) == matching.end ())
       {
-        std::string priority = pending[i].getAttribute (&quot;priority&quot;);
-        if (priority == &quot;M&quot;)
-        {
-          std::string project = pending[i].getAttribute (&quot;project&quot;);
-          if (countByProject[project] &lt; limit &amp;&amp; matching.find (i) == matching.end ())
-          {
-            ++countByProject[project];
-            matching[i] = true;
-          }
-        }
+        ++countByProject[project];
+        matching[task-&gt;id] = true;
+        filtered.push_back (*task);
       }
     }
   }
 
-  // pri:M
-  for (unsigned int i = 0; i &lt; pending.size (); ++i)
+  // due:*, pri:M
+  foreach (task, tasks)
   {
-    if (pending[i].getStatus () == T::pending)
+    if (task-&gt;has (&quot;due&quot;))
     {
-      std::string priority = pending[i].getAttribute (&quot;priority&quot;);
+      std::string priority = task-&gt;get (&quot;priority&quot;);
       if (priority == &quot;M&quot;)
       {
-        std::string project = pending[i].getAttribute (&quot;project&quot;);
-        if (countByProject[project] &lt; limit &amp;&amp; matching.find (i) == matching.end ())
+        std::string project = task-&gt;get (&quot;project&quot;);
+        if (countByProject[project] &lt; limit &amp;&amp; matching.find (task-&gt;id) == matching.end ())
         {
           ++countByProject[project];
-          matching[i] = true;
+          matching[task-&gt;id] = true;
+          filtered.push_back (*task);
         }
       }
     }
   }
 
-  // due:*, pri:L
-  for (unsigned int i = 0; i &lt; pending.size (); ++i)
+  // pri:M
+  foreach (task, tasks)
   {
-    if (pending[i].getStatus () == T::pending)
+    std::string priority = task-&gt;get (&quot;priority&quot;);
+    if (priority == &quot;M&quot;)
     {
-      std::string due = pending[i].getAttribute (&quot;due&quot;);
-      if (due != &quot;&quot;)
+      std::string project = task-&gt;get (&quot;project&quot;);
+      if (countByProject[project] &lt; limit &amp;&amp; matching.find (task-&gt;id) == matching.end ())
       {
-        std::string priority = pending[i].getAttribute (&quot;priority&quot;);
-        if (priority == &quot;L&quot;)
-        {
-          std::string project = pending[i].getAttribute (&quot;project&quot;);
-          if (countByProject[project] &lt; limit &amp;&amp; matching.find (i) == matching.end ())
-          {
-            ++countByProject[project];
-            matching[i] = true;
-          }
-        }
+        ++countByProject[project];
+        matching[task-&gt;id] = true;
+        filtered.push_back (*task);
       }
     }
   }
 
-  // pri:L
-  for (unsigned int i = 0; i &lt; pending.size (); ++i)
+  // due:*, pri:L
+  foreach (task, tasks)
   {
-    if (pending[i].getStatus () == T::pending)
+    if (task-&gt;has (&quot;due&quot;))
     {
-      std::string priority = pending[i].getAttribute (&quot;priority&quot;);
+      std::string priority = task-&gt;get (&quot;priority&quot;);
       if (priority == &quot;L&quot;)
       {
-        std::string project = pending[i].getAttribute (&quot;project&quot;);
-        if (countByProject[project] &lt; limit &amp;&amp; matching.find (i) == matching.end ())
+        std::string project = task-&gt;get (&quot;project&quot;);
+        if (countByProject[project] &lt; limit &amp;&amp; matching.find (task-&gt;id) == matching.end ())
         {
           ++countByProject[project];
-          matching[i] = true;
+          matching[task-&gt;id] = true;
+          filtered.push_back (*task);
         }
       }
     }
   }
 
-  // due:, pri:
-  for (unsigned int i = 0; i &lt; pending.size (); ++i)
+  // pri:L
+  foreach (task, tasks)
   {
-    if (pending[i].getStatus () == T::pending)
+    std::string priority = task-&gt;get (&quot;priority&quot;);
+    if (priority == &quot;L&quot;)
     {
-      std::string due = pending[i].getAttribute (&quot;due&quot;);
-      if (due == &quot;&quot;)
+      std::string project = task-&gt;get (&quot;project&quot;);
+      if (countByProject[project] &lt; limit &amp;&amp; matching.find (task-&gt;id) == matching.end ())
       {
-        std::string priority = pending[i].getAttribute (&quot;priority&quot;);
-        if (priority == &quot;&quot;)
-        {
-          std::string project = pending[i].getAttribute (&quot;project&quot;);
-          if (countByProject[project] &lt; limit &amp;&amp; matching.find (i) == matching.end ())
-          {
-            ++countByProject[project];
-            matching[i] = true;
-          }
-        }
+        ++countByProject[project];
+        matching[task-&gt;id] = true;
+        filtered.push_back (*task);
       }
     }
   }
 
-  // Convert map to vector.
-  foreach (i, matching)
-    all.push_back (i-&gt;first);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// This report will eventually become the one report that many others morph into
-// via the .taskrc file.
-std::string handleCustomReport (
-  TDB&amp; tdb,
-  T&amp; task,
-  Config&amp; conf,
-  const std::string&amp; report)
-{
-  // Determine window size, and set table accordingly.
-  int width = conf.get (&quot;defaultwidth&quot;, (int) 80);
-#ifdef HAVE_LIBNCURSES
-  if (conf.get (&quot;curses&quot;, true))
-  {
-    WINDOW* w = initscr ();
-    width = w-&gt;_maxx + 1;
-    endwin ();
-  }
-#endif
-
-  // Load report configuration.
-  std::string columnList = conf.get (&quot;report.&quot; + report + &quot;.columns&quot;);
-  std::vector &lt;std::string&gt; columns;
-  split (columns, columnList, ',');
-  validReportColumns (columns);
-
-  std::string labelList = conf.get (&quot;report.&quot; + report + &quot;.labels&quot;);
-  std::vector &lt;std::string&gt; labels;
-  split (labels, labelList, ',');
-
-  if (columns.size () != labels.size () &amp;&amp; labels.size () != 0)
-    throw std::string (&quot;There are a different number of columns than labels &quot;) +
-          &quot;for report '&quot; + report + &quot;'.  Please correct this.&quot;;
-
-  std::map &lt;std::string, std::string&gt; columnLabels;
-  if (labels.size ())
-    for (unsigned int i = 0; i &lt; columns.size (); ++i)
-      columnLabels[columns[i]] = labels[i];
-
-  std::string sortList   = conf.get (&quot;report.&quot; + report + &quot;.sort&quot;);
-  std::vector &lt;std::string&gt; sortOrder;
-  split (sortOrder, sortList, ',');
-  validSortColumns (columns, sortOrder);
-
-  std::string filterList = conf.get (&quot;report.&quot; + report + &quot;.filter&quot;);
-  std::vector &lt;std::string&gt; filterArgs;
-  split (filterArgs, filterList, ' ');
-
-  // Load all pending tasks.
-  std::vector &lt;T&gt; tasks;
-  tdb.allPendingT (tasks);
-  handleRecurrence (tdb, tasks);
-
-  // Apply filters.
-  {
-    std::string ignore;
-    T filterTask;
-    parse (filterArgs, ignore, filterTask, conf);
-
-    filter (tasks, filterTask);  // Filter from custom report
-    filter (tasks, task);        // Filter from command line
-  }
-
-  // Initialize colorization for subsequent auto colorization.
-  initializeColorRules (conf);
-
-  Table table;
-  table.setTableWidth (width);
-  table.setDateFormat (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-
-  for (unsigned int i = 0; i &lt; tasks.size (); ++i)
-    table.addRow ();
-
-  int columnCount = 0;
-  int dueColumn = -1;
-  foreach (col, columns)
+  // due:, pri:
+  foreach (task, tasks)
   {
-    // Add each column individually.
-    if (*col == &quot;id&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;ID&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::right);
-
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-        table.addCell (row, columnCount, tasks[row].getId ());
-    }
-
-    else if (*col == &quot;uuid&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;UUID&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::left);
-
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-        table.addCell (row, columnCount, tasks[row].getUUID ());
-    }
-
-    else if (*col == &quot;project&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Project&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::left);
-
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-        table.addCell (row, columnCount, tasks[row].getAttribute (&quot;project&quot;));
-    }
-
-    else if (*col == &quot;priority&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Pri&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::left);
-
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-        table.addCell (row, columnCount, tasks[row].getAttribute (&quot;priority&quot;));
-    }
-
-    else if (*col == &quot;entry&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Added&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::right);
-
-      std::string entered;
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-      {
-        entered = tasks[row].getAttribute (&quot;entry&quot;);
-        if (entered.length ())
-        {
-          Date dt (::atoi (entered.c_str ()));
-          entered = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-          table.addCell (row, columnCount, entered);
-        }
-      }
-    }
-
-    else if (*col == &quot;start&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Started&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::right);
-
-      std::string started;
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-      {
-        started = tasks[row].getAttribute (&quot;start&quot;);
-        if (started.length ())
-        {
-          Date dt (::atoi (started.c_str ()));
-          started = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-          table.addCell (row, columnCount, started);
-        }
-      }
-    }
-
-    else if (*col == &quot;due&quot;)
+    if (task-&gt;has (&quot;due&quot;))
     {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Due&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::right);
-
-      std::string due;
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
+      std::string priority = task-&gt;get (&quot;priority&quot;);
+      if (priority == &quot;&quot;)
       {
-        due = tasks[row].getAttribute (&quot;due&quot;);
-        if (due.length ())
+        std::string project = task-&gt;get (&quot;project&quot;);
+        if (countByProject[project] &lt; limit &amp;&amp; matching.find (task-&gt;id) == matching.end ())
         {
-          Date dt (::atoi (due.c_str ()));
-          due = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-          table.addCell (row, columnCount, due);
-        }
-      }
-
-      dueColumn = columnCount;
-    }
-
-    else if (*col == &quot;age&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Age&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::right);
-
-      std::string created;
-      std::string age;
-      Date now;
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-      {
-        created = tasks[row].getAttribute (&quot;entry&quot;);
-        if (created.length ())
-        {
-          Date dt (::atoi (created.c_str ()));
-          formatTimeDeltaDays (age, (time_t) (now - dt));
-          table.addCell (row, columnCount, age);
-        }
-      }
-    }
-
-    else if (*col == &quot;active&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Active&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::left);
-
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-        if (tasks[row].getAttribute (&quot;start&quot;) != &quot;&quot;)
-          table.addCell (row, columnCount, &quot;*&quot;);
-    }
-
-    else if (*col == &quot;tags&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Tags&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::left);
-
-      std::vector &lt;std::string&gt; all;
-      std::string tags;
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-      {
-        tasks[row].getTags (all);
-        join (tags, &quot; &quot;, all);
-        table.addCell (row, columnCount, tags);
-      }
-    }
-
-    else if (*col == &quot;description_only&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Description&quot;);
-      table.setColumnWidth (columnCount, Table::flexible);
-      table.setColumnJustification (columnCount, Table::left);
-
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-        table.addCell (row, columnCount, tasks[row].getDescription ());
-    }
-
-    else if (*col == &quot;description&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Description&quot;);
-      table.setColumnWidth (columnCount, Table::flexible);
-      table.setColumnJustification (columnCount, Table::left);
-
-      std::string description;
-      std::string when;
-      for (unsigned int row = 0; row &lt; tasks.size(); ++row)
-      {
-        description = tasks[row].getDescription ();
-        std::map &lt;time_t, std::string&gt; annotations;
-        tasks[row].getAnnotations (annotations);
-        foreach (anno, annotations)
-        {
-          Date dt (anno-&gt;first);
-          when = dt.toString (conf.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
-          description += &quot;\n&quot; + when + &quot; &quot; + anno-&gt;second;
+          ++countByProject[project];
+          matching[task-&gt;id] = true;
+          filtered.push_back (*task);
         }
-
-        table.addCell (row, columnCount, description);
       }
     }
-
-    else if (*col == &quot;recur&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;Recur&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::right);
-
-      for (unsigned int row = 0; row &lt; tasks.size (); ++row)
-        table.addCell (row, columnCount, tasks[row].getAttribute (&quot;recur&quot;));
-    }
-
-    else if (*col == &quot;recurrence_indicator&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;R&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::right);
-
-      for (unsigned int row = 0; row &lt; tasks.size (); ++row)
-        table.addCell (row, columnCount,
-                       tasks[row].getAttribute (&quot;recur&quot;) != &quot;&quot; ? &quot;R&quot; : &quot;&quot;);
-    }
-
-    else if (*col == &quot;tag_indicator&quot;)
-    {
-      table.addColumn (columnLabels[*col] != &quot;&quot; ? columnLabels[*col] : &quot;T&quot;);
-      table.setColumnWidth (columnCount, Table::minimum);
-      table.setColumnJustification (columnCount, Table::right);
-
-      for (unsigned int row = 0; row &lt; tasks.size (); ++row)
-        table.addCell (row, columnCount,
-                       tasks[row].getTagCount () ? &quot;+&quot; : &quot;&quot;);
-    }
-
-    // Common to all columns.
-    // Add underline.
-    if (conf.get (std::string (&quot;color&quot;), true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-      table.setColumnUnderline (columnCount);
-    else
-      table.setTableDashedUnderline ();
-
-    ++columnCount;
-  }
-
-  // Dynamically add sort criteria.
-  // Build a map of column names -&gt; index.
-  std::map &lt;std::string, unsigned int&gt; columnIndex;
-  for (unsigned int c = 0; c &lt; columns.size (); ++c)
-    columnIndex[columns[c]] = c;
-
-  foreach (sortColumn, sortOrder)
-  {
-    // Separate column and direction.
-    std::string column = sortColumn-&gt;substr (0, sortColumn-&gt;length () - 1);
-    char direction = (*sortColumn)[sortColumn-&gt;length () - 1];
-
-    if (column == &quot;id&quot;)
-      table.sortOn (columnIndex[column],
-                    (direction == '+' ?
-                      Table::ascendingNumeric :
-                      Table::descendingNumeric));
-
-    else if (column == &quot;priority&quot;)
-      table.sortOn (columnIndex[column],
-                    (direction == '+' ?
-                      Table::ascendingPriority :
-                      Table::descendingPriority));
-
-    else if (column == &quot;entry&quot; || column == &quot;start&quot; || column == &quot;due&quot;)
-      table.sortOn (columnIndex[column],
-                    (direction == '+' ?
-                      Table::ascendingDate :
-                      Table::descendingDate));
-
-    else if (column == &quot;recur&quot;)
-      table.sortOn (columnIndex[column],
-                    (direction == '+' ?
-                      Table::ascendingPeriod :
-                      Table::descendingPeriod));
-
-    else
-      table.sortOn (columnIndex[column],
-                    (direction == '+' ?
-                      Table::ascendingCharacter :
-                      Table::descendingCharacter));
   }
 
-  // Now auto colorize all rows.
-  std::string due;
-  bool imminent;
-  bool overdue;
-  for (unsigned int row = 0; row &lt; tasks.size (); ++row)
+  // Filler.
+  foreach (task, tasks)
   {
-    imminent = false;
-    overdue = false;
-    due = tasks[row].getAttribute (&quot;due&quot;);
-    if (due.length ())
+    std::string project = task-&gt;get (&quot;project&quot;);
+    if (countByProject[project] &lt; limit &amp;&amp; matching.find (task-&gt;id) == matching.end ())
     {
-      switch (getDueState (due))
-      {
-      case 2: overdue = true;  break;
-      case 1: imminent = true; break;
-      case 0:
-      default:                 break;
-      }
-    }
-
-    if (conf.get (&quot;color&quot;, true) || conf.get (std::string (&quot;_forcecolor&quot;), false))
-    {
-      Text::color fg = Text::colorCode (tasks[row].getAttribute (&quot;fg&quot;));
-      Text::color bg = Text::colorCode (tasks[row].getAttribute (&quot;bg&quot;));
-      autoColorize (tasks[row], fg, bg, conf);
-      table.setRowFg (row, fg);
-      table.setRowBg (row, bg);
-
-      if (fg == Text::nocolor)
-      {
-        if (dueColumn != -1)
-        {
-          if (overdue)
-            table.setCellFg (row, columnCount, Text::colorCode (conf.get (&quot;color.overdue&quot;, &quot;red&quot;)));
-          else if (imminent)
-            table.setCellFg (row, columnCount, Text::colorCode (conf.get (&quot;color.due&quot;, &quot;yellow&quot;)));
-        }
-      }
+      ++countByProject[project];
+      matching[task-&gt;id] = true;
+      filtered.push_back (*task);
     }
   }
 
-  // Limit the number of rows according to the report definition.
-  int maximum = conf.get (std::string (&quot;report.&quot;) + report + &quot;.limit&quot;, (int)0);
-
-  // If the custom report has a defined limit, then allow an override, which
-  // will show up as a single ID sequence.
-  if (conf.get (std::string (&quot;report.&quot;) + report + &quot;.limit&quot;, (int)0) != 0)
-  {
-    std::vector &lt;int&gt; sequence = task.getAllIds ();
-    if (sequence.size () == 1)
-      maximum = sequence[0];
-  }
-
-  std::stringstream out;
-  if (table.rowCount ())
-    out &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; table.render (maximum)
-        &lt;&lt; optionalBlankLine (conf)
-        &lt;&lt; table.rowCount ()
-        &lt;&lt; (table.rowCount () == 1 ? &quot; task&quot; : &quot; tasks&quot;)
-        &lt;&lt; std::endl;
-  else
-    out &lt;&lt; &quot;No matches.&quot;
-        &lt;&lt; std::endl;
-
-  return out.str ();
+  tasks = filtered;
 }
 
-////////////////////////////////////////////////////////////////////////////////
-void validReportColumns (const std::vector &lt;std::string&gt;&amp; columns)
+///////////////////////////////////////////////////////////////////////////////
+std::string getFullDescription (Task&amp; task)
 {
-  std::vector &lt;std::string&gt; bad;
-
-  std::vector &lt;std::string&gt;::const_iterator it;
-  for (it = columns.begin (); it != columns.end (); ++it)
-    if (*it != &quot;id&quot;                   &amp;&amp;
-        *it != &quot;uuid&quot;                 &amp;&amp;
-        *it != &quot;project&quot;              &amp;&amp;
-        *it != &quot;priority&quot;             &amp;&amp;
-        *it != &quot;entry&quot;                &amp;&amp;
-        *it != &quot;start&quot;                &amp;&amp;
-        *it != &quot;due&quot;                  &amp;&amp;
-        *it != &quot;age&quot;                  &amp;&amp;
-        *it != &quot;active&quot;               &amp;&amp;
-        *it != &quot;tags&quot;                 &amp;&amp;
-        *it != &quot;recur&quot;                &amp;&amp;
-        *it != &quot;recurrence_indicator&quot; &amp;&amp;
-        *it != &quot;tag_indicator&quot;        &amp;&amp;
-        *it != &quot;description_only&quot;     &amp;&amp;
-        *it != &quot;description&quot;)
-      bad.push_back (*it);
-
-  if (bad.size ())
+  std::string desc = task.get (&quot;description&quot;);
+
+  std::vector &lt;Att&gt; annotations;
+  task.getAnnotations (annotations);
+  foreach (anno, annotations)
   {
-    std::string error;
-    join (error, &quot;, &quot;, bad);
-    throw std::string (&quot;Unrecognized column name: &quot;) + error;
+    Date dt (::atoi (anno-&gt;name ().substr (11, std::string::npos).c_str ()));
+    std::string when = dt.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+    desc += &quot;\n&quot; + when + &quot; &quot; + anno-&gt;value ();
   }
+
+  return desc;
 }
 
-////////////////////////////////////////////////////////////////////////////////
-void validSortColumns (
-  const std::vector &lt;std::string&gt;&amp; columns,
-  const std::vector &lt;std::string&gt;&amp; sortColumns)
+///////////////////////////////////////////////////////////////////////////////
+std::string getDueDate (Task&amp; task)
 {
-  std::vector &lt;std::string&gt; bad;
-  std::vector &lt;std::string&gt;::const_iterator sc;
-  for (sc = sortColumns.begin (); sc != sortColumns.end (); ++sc)
+  std::string due = task.get (&quot;due&quot;);
+  if (due.length ())
   {
-    std::vector &lt;std::string&gt;::const_iterator co;
-    for (co = columns.begin (); co != columns.end (); ++co)
-      if (sc-&gt;substr (0, sc-&gt;length () - 1) == *co)
-        break;
-
-    if (co == columns.end ())
-      bad.push_back (*sc);
+    Date d (::atoi (due.c_str ()));
+    due = d.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
   }
 
-  if (bad.size ())
-  {
-    std::string error;
-    join (error, &quot;, &quot;, bad);
-    throw std::string (&quot;Sort column is not part of the report: &quot;) + error;
-  }
+  return due;
 }
 
-////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////</diff>
      <filename>src/report.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -26,11 +26,14 @@
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;iostream&gt;
 #include &lt;stdlib.h&gt;
-#include &quot;Config.h&quot;
+#include &quot;Context.h&quot;
 #include &quot;Table.h&quot;
 #include &quot;Date.h&quot;
-#include &quot;T.h&quot;
-#include &quot;task.h&quot;
+#include &quot;text.h&quot;
+#include &quot;util.h&quot;
+#include &quot;main.h&quot;
+
+extern Context context;
 
 static std::map &lt;std::string, Text::color&gt; gsFg;
 static std::map &lt;std::string, Text::color&gt; gsBg;
@@ -62,17 +65,17 @@ static void parseColorRule (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-void initializeColorRules (Config&amp; conf)
+void initializeColorRules ()
 {
   std::vector &lt;std::string&gt; ruleNames;
-  conf.all (ruleNames);
+  context.config.all (ruleNames);
   foreach (it, ruleNames)
   {
     if (it-&gt;substr (0, 6) == &quot;color.&quot;)
     {
       Text::color fg;
       Text::color bg;
-      parseColorRule (conf.get (*it), fg, bg);
+      parseColorRule (context.config.get (*it), fg, bg);
       gsFg[*it] = fg;
       gsBg[*it] = bg;
     }
@@ -81,10 +84,9 @@ void initializeColorRules (Config&amp; conf)
 
 ////////////////////////////////////////////////////////////////////////////////
 void autoColorize (
-  T&amp; task,
+  Task&amp; task,
   Text::color&amp; fg,
-  Text::color&amp; bg,
-  Config&amp; conf)
+  Text::color&amp; bg)
 {
   // Note: fg, bg already contain colors specifically assigned via command.
   // Note: These rules form a hierarchy - the last rule is King.
@@ -93,9 +95,7 @@ void autoColorize (
   if (gsFg[&quot;color.tagged&quot;] != Text::nocolor ||
       gsBg[&quot;color.tagged&quot;] != Text::nocolor)
   {
-    std::vector &lt;std::string&gt; tags;
-    task.getTags (tags);
-    if (tags.size ())
+    if (task.getTagCount ())
     {
       fg = gsFg[&quot;color.tagged&quot;];
       bg = gsBg[&quot;color.tagged&quot;];
@@ -106,7 +106,7 @@ void autoColorize (
   if (gsFg[&quot;color.pri.L&quot;] != Text::nocolor ||
       gsBg[&quot;color.pri.L&quot;] != Text::nocolor)
   {
-    if (task.getAttribute (&quot;priority&quot;) == &quot;L&quot;)
+    if (task.get (&quot;priority&quot;) == &quot;L&quot;)
     {
       fg = gsFg[&quot;color.pri.L&quot;];
       bg = gsBg[&quot;color.pri.L&quot;];
@@ -117,7 +117,7 @@ void autoColorize (
   if (gsFg[&quot;color.pri.M&quot;] != Text::nocolor ||
       gsBg[&quot;color.pri.M&quot;] != Text::nocolor)
   {
-    if (task.getAttribute (&quot;priority&quot;) == &quot;M&quot;)
+    if (task.get (&quot;priority&quot;) == &quot;M&quot;)
     {
       fg = gsFg[&quot;color.pri.M&quot;];
       bg = gsBg[&quot;color.pri.M&quot;];
@@ -128,7 +128,7 @@ void autoColorize (
   if (gsFg[&quot;color.pri.H&quot;] != Text::nocolor ||
       gsBg[&quot;color.pri.H&quot;] != Text::nocolor)
   {
-    if (task.getAttribute (&quot;priority&quot;) == &quot;H&quot;)
+    if (task.get (&quot;priority&quot;) == &quot;H&quot;)
     {
       fg = gsFg[&quot;color.pri.H&quot;];
       bg = gsBg[&quot;color.pri.H&quot;];
@@ -139,7 +139,7 @@ void autoColorize (
   if (gsFg[&quot;color.pri.none&quot;] != Text::nocolor ||
       gsBg[&quot;color.pri.none&quot;] != Text::nocolor)
   {
-    if (task.getAttribute (&quot;priority&quot;) == &quot;&quot;)
+    if (task.get (&quot;priority&quot;) == &quot;&quot;)
     {
       fg = gsFg[&quot;color.pri.none&quot;];
       bg = gsBg[&quot;color.pri.none&quot;];
@@ -150,7 +150,7 @@ void autoColorize (
   if (gsFg[&quot;color.active&quot;] != Text::nocolor ||
       gsBg[&quot;color.active&quot;] != Text::nocolor)
   {
-    if (task.getAttribute (&quot;start&quot;) != &quot;&quot;)
+    if (task.has (&quot;start&quot;))
     {
       fg = gsFg[&quot;color.active&quot;];
       bg = gsBg[&quot;color.active&quot;];
@@ -178,7 +178,7 @@ void autoColorize (
     if (it-&gt;first.substr (0, 14) == &quot;color.project.&quot;)
     {
       std::string value = it-&gt;first.substr (14, std::string::npos);
-      if (task.getAttribute (&quot;project&quot;) == value)
+      if (task.get (&quot;project&quot;) == value)
       {
         fg = gsFg[it-&gt;first];
         bg = gsBg[it-&gt;first];
@@ -192,7 +192,7 @@ void autoColorize (
     if (it-&gt;first.substr (0, 14) == &quot;color.keyword.&quot;)
     {
       std::string value = lowerCase (it-&gt;first.substr (14, std::string::npos));
-      std::string desc  = lowerCase (task.getDescription ());
+      std::string desc  = lowerCase (task.get (&quot;description&quot;));
       if (desc.find (value) != std::string::npos)
       {
         fg = gsFg[it-&gt;first];
@@ -202,25 +202,24 @@ void autoColorize (
   }
 
   // Colorization of the due and overdue.
-  std::string due = task.getAttribute (&quot;due&quot;);
-  if (due != &quot;&quot;)
+  if (task.has (&quot;due&quot;))
   {
-    Date dueDate (::atoi (due.c_str ()));
-    Date now;
-    Date then (now + conf.get (&quot;due&quot;, 7) * 86400);
-
-    // Overdue
-    if (dueDate &lt; now)
+    std::string due = task.get (&quot;due&quot;);
+    switch (getDueState (due))
     {
+    case 1: // imminent
+      fg = gsFg[&quot;color.due&quot;];
+      bg = gsBg[&quot;color.due&quot;];
+      break;
+
+    case 2: // overdue
       fg = gsFg[&quot;color.overdue&quot;];
       bg = gsBg[&quot;color.overdue&quot;];
-    }
+      break;
 
-    // Imminent
-    else if (dueDate &lt; then)
-    {
-      fg = gsFg[&quot;color.due&quot;];
-      bg = gsBg[&quot;color.due&quot;];
+    case 0: // not due at all
+    default:
+      break;
     }
   }
 
@@ -228,7 +227,7 @@ void autoColorize (
   if (gsFg[&quot;color.recurring&quot;] != Text::nocolor ||
       gsBg[&quot;color.recurring&quot;] != Text::nocolor)
   {
-    if (task.getAttribute (&quot;recur&quot;) != &quot;&quot;)
+    if (task.has (&quot;recur&quot;))
     {
       fg = gsFg[&quot;color.recurring&quot;];
       bg = gsBg[&quot;color.recurring&quot;];
@@ -237,4 +236,64 @@ void autoColorize (
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+std::string colorizeHeader (const std::string&amp; input)
+{
+  if (gsFg[&quot;color.header&quot;] != Text::nocolor ||
+      gsBg[&quot;color.header&quot;] != Text::nocolor)
+  {
+    return Text::colorize (
+           gsFg[&quot;color.header&quot;],
+           gsBg[&quot;color.header&quot;],
+           input);
+  }
+
+  return input;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string colorizeMessage (const std::string&amp; input)
+{
+  if (gsFg[&quot;color.message&quot;] != Text::nocolor ||
+      gsBg[&quot;color.message&quot;] != Text::nocolor)
+  {
+    return Text::colorize (
+           gsFg[&quot;color.message&quot;],
+           gsBg[&quot;color.message&quot;],
+           input);
+  }
+
+  return input;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string colorizeFootnote (const std::string&amp; input)
+{
+  if (gsFg[&quot;color.footnote&quot;] != Text::nocolor ||
+      gsBg[&quot;color.footnote&quot;] != Text::nocolor)
+  {
+    return Text::colorize (
+           gsFg[&quot;color.footnote&quot;],
+           gsBg[&quot;color.footnote&quot;],
+           input);
+  }
+
+  return input;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string colorizeDebug (const std::string&amp; input)
+{
+  if (gsFg[&quot;color.debug&quot;] != Text::nocolor ||
+      gsBg[&quot;color.debug&quot;] != Text::nocolor)
+  {
+    return Text::colorize (
+           gsFg[&quot;color.debug&quot;],
+           gsBg[&quot;color.debug&quot;],
+           input);
+  }
+
+  return input;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 </diff>
      <filename>src/rules.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -5,4 +5,15 @@ date.t
 duration.t
 text.t
 autocomplete.t
-parse.t
+seq.t
+att.t
+record.t
+stringtable.t
+nibbler.t
+subst.t
+filt.t
+cmd.t
+config.t
+util.t
+color.t
+*.log</diff>
      <filename>src/tests/.gitignore</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,14 @@
-PROJECT = t.t tdb.t date.t duration.t t.benchmark.t text.t autocomplete.t \
-          parse.t
+PROJECT = t.t tdb.t date.t duration.t t.benchmark.t text.t autocomplete.t      \
+          config.t seq.t att.t stringtable.t record.t nibbler.t subst.t filt.t \
+          cmd.t util.t color.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
+LFLAGS  = -L/usr/local/lib -lncurses
+OBJECTS = ../TDB.o ../Task.o ../text.o ../Date.o ../Table.o ../Duration.o      \
+          ../util.o ../Config.o ../Sequence.o ../Att.o ../Cmd.o ../Record.o    \
+          ../StringTable.o ../Subst.o ../Nibbler.o ../Location.o ../Filter.o   \
+          ../Context.o ../Keymap.o ../command.o ../interactive.o ../report.o   \
+          ../Grid.o ../color.o ../rules.o ../recur.o ../custom.o ../import.o   \
+          ../edit.o ../Timer.o ../Permission.o
 
 all: $(PROJECT)
 
@@ -39,6 +45,36 @@ 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
+seq.t: seq.t.o $(OBJECTS) test.o
+	g++ seq.t.o $(OBJECTS) test.o $(LFLAGS) -o seq.t
+
+record.t: record.t.o $(OBJECTS) test.o
+	g++ record.t.o $(OBJECTS) test.o $(LFLAGS) -o record.t
+
+att.t: att.t.o $(OBJECTS) test.o
+	g++ att.t.o $(OBJECTS) test.o $(LFLAGS) -o att.t
+
+stringtable.t: stringtable.t.o $(OBJECTS) test.o
+	g++ stringtable.t.o $(OBJECTS) test.o $(LFLAGS) -o stringtable.t
+
+subst.t: subst.t.o $(OBJECTS) test.o
+	g++ subst.t.o $(OBJECTS) test.o $(LFLAGS) -o subst.t
+
+nibbler.t: nibbler.t.o $(OBJECTS) test.o
+	g++ nibbler.t.o $(OBJECTS) test.o $(LFLAGS) -o nibbler.t
+
+filt.t: filt.t.o $(OBJECTS) test.o
+	g++ filt.t.o $(OBJECTS) test.o $(LFLAGS) -o filt.t
+
+cmd.t: cmd.t.o $(OBJECTS) test.o
+	g++ cmd.t.o $(OBJECTS) test.o $(LFLAGS) -o cmd.t
+
+config.t: config.t.o $(OBJECTS) test.o
+	g++ config.t.o $(OBJECTS) test.o $(LFLAGS) -o config.t
+
+util.t: util.t.o $(OBJECTS) test.o
+	g++ util.t.o $(OBJECTS) test.o $(LFLAGS) -o util.t
+
+color.t: color.t.o $(OBJECTS) test.o
+	g++ color.t.o $(OBJECTS) test.o $(LFLAGS) -o color.t
 </diff>
      <filename>src/tests/Makefile</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 22;
+use Test::More tests =&gt; 23;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'abbrev.rc')
@@ -92,6 +92,9 @@ like ($output, qr/ABSOLUTELY NO WARRANTY/, 'v');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'abbrev.rc';
 ok (!-r 'abbrev.rc', 'Removed abbrev.rc');
 </diff>
      <filename>src/tests/abbreviation.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 14;
+use Test::More tests =&gt; 13;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'add.rc')
@@ -39,32 +39,34 @@ if (open my $fh, '&gt;', 'add.rc')
 }
 
 # Test the add command.
-my $output = qx{../task rc:add.rc add This is a test; ../task rc:add.rc info 1};
+qx{../task rc:add.rc add This is a test};
+my $output = qx{../task rc:add.rc info 1};
 like ($output, qr/ID\s+1\n/, 'add ID');
 like ($output, qr/Description\s+This is a test\n/, 'add ID');
 like ($output, qr/Status\s+Pending\n/, 'add Pending');
 like ($output, qr/UUID\s+[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\n/, 'add UUID');
 
 # Test the /// modifier.
-$output = qx{../task rc:add.rc 1 /test/TEST/; ../task rc:add.rc 1 &quot;/is //&quot;; ../task rc:add.rc info 1};
+qx{../task rc:add.rc 1 /test/TEST/};
+qx{../task rc:add.rc 1 &quot;/is //&quot;};
+$output = qx{../task rc:add.rc info 1};
 like ($output, qr/ID\s+1\n/, 'add ID');
 like ($output, qr/Status\s+Pending\n/, 'add Pending');
 like ($output, qr/Description\s+This a TEST\n/, 'add ID');
 
 # Test delete.
-$output = qx{../task rc:add.rc delete 1; ../task rc:add.rc info 1};
+qx{../task rc:add.rc delete 1};
+$output = qx{../task rc:add.rc info 1};
 like ($output, qr/ID\s+1\n/, 'add ID');
 like ($output, qr/Status\s+Deleted\n/, 'add Deleted');
 
-# Test undelete.
-$output = qx{../task rc:add.rc undelete 1; ../task rc:add.rc info 1};
-like ($output, qr/ID\s+1\n/, 'add ID');
-like ($output, qr/Status\s+Pending\n/, 'add Pending');
-
 # Cleanup.
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'add.rc';
 ok (!-r 'add.rc', 'Removed add.rc');
 </diff>
      <filename>src/tests/add.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 8;
+use Test::More tests =&gt; 9;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'annotate.rc')
@@ -67,6 +67,9 @@ like ($output, qr/2 tasks/, 'count');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'annotate.rc';
 ok (!-r 'annotate.rc', 'Removed annotate.rc');
 </diff>
      <filename>src/tests/annotate.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 4;
+use Test::More tests =&gt; 5;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'append.rc')
@@ -48,6 +48,9 @@ like ($output, qr/Description\s+foo\sbar\n/, 'append worked');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'append.rc';
 ok (!-r 'append.rc', 'Removed append.rc');
 </diff>
      <filename>src/tests/append.t</filename>
    </modified>
    <modified>
      <diff>@@ -26,7 +26,10 @@
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;iostream&gt;
 #include &lt;test.h&gt;
-#include &lt;../task.h&gt;
+#include &lt;util.h&gt;
+#include &lt;main.h&gt;
+
+Context context;
 
 ////////////////////////////////////////////////////////////////////////////////
 int main (int argc, char** argv)</diff>
      <filename>src/tests/autocomplete.t.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 7;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'basic.rc')
@@ -40,14 +40,13 @@ if (open my $fh, '&gt;', 'basic.rc')
 
 # Test the usage command.
 my $output = qx{../task rc:basic.rc};
-like ($output, qr/Usage: task/, 'usage');
-like ($output, qr/http:\/\/www\.beckingham\.net\/task\.html/, 'usage - url');
+like ($output, qr/You must specify a command, or a task ID to modify/, 'missing command and ID');
 
 # Test the version command.
 $output = qx{../task rc:basic.rc version};
 like ($output, qr/task \d+\.\d+\.\d+/, 'version - task version number');
 like ($output, qr/ABSOLUTELY NO WARRANTY/, 'version - warranty');
-like ($output, qr/http:\/\/www\.beckingham\.net\/task\.html/, 'version - url');
+like ($output, qr/http:\/\/taskwarrior\.org/, 'version - url');
 
 # Cleanup.
 unlink 'basic.rc';</diff>
      <filename>src/tests/basic.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 4;
+use Test::More tests =&gt; 5;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'bench.rc')
@@ -96,6 +96,9 @@ ok (!-r 'pending.data', 'Removed pending.data');
 unlink 'completed.data';
 ok (!-r 'completed.data', 'Removed completed.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'bench.rc';
 ok (!-r 'bench.rc', 'Removed bench.rc');
 </diff>
      <filename>src/tests/benchmark.t</filename>
    </modified>
    <modified>
      <diff>@@ -38,3 +38,16 @@
     ok 3 - Removed completed.data
     ok 4 - Removed bench.rc
 
+6/18/2009
+  1.8.0:
+    1..4
+    ok 1 - Created bench.rc
+    # start=1245372501
+    # 1000 tasks added in 4 seconds
+    # 600 tasks altered in 45 seconds
+    # stop=1245372747
+    # total=246
+    ok 2 - Removed pending.data
+    ok 3 - Removed completed.data
+    ok 4 - Removed bench.rc
+</diff>
      <filename>src/tests/benchmark.txt</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 13;
+use Test::More tests =&gt; 14;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'annual.rc')
@@ -57,21 +57,24 @@ if (open my $fh, '&gt;', 'annual.rc')
 
 qx{../task rc:annual.rc add foo due:1/1/2000 recur:annual until:1/1/2009};
 my $output = qx{../task rc:annual.rc list};
-like ($output, qr/2\s+1\/1\/2000\s+- foo/, 'synthetic 1 no creep');
-like ($output, qr/3\s+1\/1\/2001\s+- foo/, 'synthetic 2 no creep');
-like ($output, qr/4\s+1\/1\/2002\s+- foo/, 'synthetic 3 no creep');
-like ($output, qr/5\s+1\/1\/2003\s+- foo/, 'synthetic 4 no creep');
-like ($output, qr/6\s+1\/1\/2004\s+- foo/, 'synthetic 5 no creep');
-like ($output, qr/7\s+1\/1\/2005\s+- foo/, 'synthetic 6 no creep');
-like ($output, qr/8\s+1\/1\/2006\s+- foo/, 'synthetic 7 no creep');
-like ($output, qr/9\s+1\/1\/2007\s+- foo/, 'synthetic 8 no creep');
-like ($output, qr/10\s+1\/1\/2008\s+- foo/, 'synthetic 9 no creep');
-like ($output, qr/11\s+1\/1\/2009\s+- foo/, 'synthetic 10 no creep');
+like ($output, qr/2\s+1\/1\/2000\s+-\s+foo/,  'synthetic 1 no creep');
+like ($output, qr/3\s+1\/1\/2001\s+-\s+foo/,  'synthetic 2 no creep');
+like ($output, qr/4\s+1\/1\/2002\s+-\s+foo/,  'synthetic 3 no creep');
+like ($output, qr/5\s+1\/1\/2003\s+-\s+foo/,  'synthetic 4 no creep');
+like ($output, qr/6\s+1\/1\/2004\s+-\s+foo/,  'synthetic 5 no creep');
+like ($output, qr/7\s+1\/1\/2005\s+-\s+foo/,  'synthetic 6 no creep');
+like ($output, qr/8\s+1\/1\/2006\s+-\s+foo/,  'synthetic 7 no creep');
+like ($output, qr/9\s+1\/1\/2007\s+-\s+foo/,  'synthetic 8 no creep');
+like ($output, qr/10\s+1\/1\/2008\s+-\s+foo/, 'synthetic 9 no creep');
+like ($output, qr/11\s+1\/1\/2009\s+-\s+foo/, 'synthetic 10 no creep');
 
 # Cleanup.
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'annual.rc';
 ok (!-r 'annual.rc', 'Removed annual.rc');
 </diff>
      <filename>src/tests/bug.annual.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,12 +28,13 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'bug_concat.rc')
 {
-  print $fh &quot;data.location=.\n&quot;;
+  print $fh &quot;data.location=.\n&quot;,
+            &quot;confirmation=no\n&quot;;
   close $fh;
   ok (-r 'bug_concat.rc', 'Created bug_concat.rc');
 }
@@ -70,6 +71,9 @@ like ($output, qr/Description\s+aaa bbb:ccc ddd\n/, 'properly concatenated');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'bug_concat.rc';
 ok (!-r 'bug_concat.rc', 'Removed bug_concat.rc');
 </diff>
      <filename>src/tests/bug.concat.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'hang.rc')
@@ -76,6 +76,9 @@ ok (!-r 'shadow.txt', 'Removed shadow.txt');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'hang.rc';
 ok (!-r 'hang.rc', 'Removed hang.rc');
 </diff>
      <filename>src/tests/bug.hang.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 41;
+use Test::More tests =&gt; 42;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'period.rc')
@@ -75,61 +75,61 @@ Confirmed:
 =cut
 
 my $output = qx{../task rc:period.rc add daily due:tomorrow recur:daily};
-like ($output, qr/^$/, 'recur:daily');
+unlike ($output, qr/was not recignized/, 'recur:daily');
 
 $output = qx{../task rc:period.rc add day due:tomorrow recur:day};
-like ($output, qr/^$/, 'recur:day');
+unlike ($output, qr/was not recignized/, 'recur:day');
 
 $output = qx{../task rc:period.rc add weekly due:tomorrow recur:weekly};
-like ($output, qr/^$/, 'recur:weekly');
+unlike ($output, qr/was not recignized/, 'recur:weekly');
 
 $output = qx{../task rc:period.rc add sennight due:tomorrow recur:sennight};
-like ($output, qr/^$/, 'recur:sennight');
+unlike ($output, qr/was not recignized/, 'recur:sennight');
 
 $output = qx{../task rc:period.rc add biweekly due:tomorrow recur:biweekly};
-like ($output, qr/^$/, 'recur:biweekly');
+unlike ($output, qr/was not recignized/, 'recur:biweekly');
 
 $output = qx{../task rc:period.rc add fortnight due:tomorrow recur:fortnight};
-like ($output, qr/^$/, 'recur:fortnight');
+unlike ($output, qr/was not recignized/, 'recur:fortnight');
 
 $output = qx{../task rc:period.rc add monthly due:tomorrow recur:monthly};
-like ($output, qr/^$/, 'recur:monthly');
+unlike ($output, qr/was not recignized/, 'recur:monthly');
 
 $output = qx{../task rc:period.rc add quarterly due:tomorrow recur:quarterly};
-like ($output, qr/^$/, 'recur:quarterly');
+unlike ($output, qr/was not recignized/, 'recur:quarterly');
 
 $output = qx{../task rc:period.rc add semiannual due:tomorrow recur:semiannual};
-like ($output, qr/^$/, 'recur:semiannual');
+unlike ($output, qr/was not recignized/, 'recur:semiannual');
 
 $output = qx{../task rc:period.rc add bimonthly due:tomorrow recur:bimonthly};
-like ($output, qr/^$/, 'recur:bimonthly');
+unlike ($output, qr/was not recignized/, 'recur:bimonthly');
 
 $output = qx{../task rc:period.rc add biannual due:tomorrow recur:biannual};
-like ($output, qr/^$/, 'recur:biannual');
+unlike ($output, qr/was not recignized/, 'recur:biannual');
 
 $output = qx{../task rc:period.rc add biyearly due:tomorrow recur:biyearly};
-like ($output, qr/^$/, 'recur:biyearly');
+unlike ($output, qr/was not recignized/, 'recur:biyearly');
 
 $output = qx{../task rc:period.rc add annual due:tomorrow recur:annual};
-like ($output, qr/^$/, 'recur:annual');
+unlike ($output, qr/was not recignized/, 'recur:annual');
 
 $output = qx{../task rc:period.rc add yearly due:tomorrow recur:yearly};
-like ($output, qr/^$/, 'recur:yearly');
+unlike ($output, qr/was not recignized/, 'recur:yearly');
 
 $output = qx{../task rc:period.rc add 2d due:tomorrow recur:2d};
-like ($output, qr/^$/, 'recur:2m');
+unlike ($output, qr/was not recignized/, 'recur:2m');
 
 $output = qx{../task rc:period.rc add 2w due:tomorrow recur:2w};
-like ($output, qr/^$/, 'recur:2q');
+unlike ($output, qr/was not recignized/, 'recur:2q');
 
 $output = qx{../task rc:period.rc add 2m due:tomorrow recur:2m};
-like ($output, qr/^$/, 'recur:2d');
+unlike ($output, qr/was not recignized/, 'recur:2d');
 
 $output = qx{../task rc:period.rc add 2q due:tomorrow recur:2q};
-like ($output, qr/^$/, 'recur:2w');
+unlike ($output, qr/was not recignized/, 'recur:2w');
 
 $output = qx{../task rc:period.rc add 2y due:tomorrow recur:2y};
-like ($output, qr/^$/, 'recur:2y');
+unlike ($output, qr/was not recignized/, 'recur:2y');
 
 # Verify that the recurring task instances get created.  One of each.
 $output = qx{../task rc:period.rc list};
@@ -157,6 +157,9 @@ like ($output, qr/\b2y\b/,         'verify 2y');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'period.rc';
 ok (!-r 'period.rc', 'Removed period.rc');
 </diff>
      <filename>src/tests/bug.period.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'bug_sort.rc')
@@ -54,6 +54,9 @@ like ($output, qr/three.*one.*two/msi, 'list did not hang after pri:H on 1');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'bug_sort.rc';
 ok (!-r 'bug_sort.rc', 'Removed bug_sort.rc');
 </diff>
      <filename>src/tests/bug.sort.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'summary.rc')
@@ -60,6 +60,9 @@ ok (!-r 'pending.data', 'Removed pending.data');
 unlink 'completed.data';
 ok (!-r 'completed.data', 'Removed completed.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'summary.rc';
 ok (!-r 'summary.rc', 'Removed summary.rc');
 </diff>
      <filename>src/tests/bug.summary.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'color.rc')
@@ -53,6 +53,9 @@ like ($output, qr/ \033\[31m        .* red     .* \033\[0m /x, 'color.active');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'color.rc';
 ok (!-r 'color.rc', 'Removed color.rc');
 </diff>
      <filename>src/tests/color.active.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'color.rc')
@@ -51,6 +51,9 @@ unlike ($output, qr/\033\[0m/,  'color.disable - no color reset');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'color.rc';
 ok (!-r 'color.rc', 'Removed color.rc');
 </diff>
      <filename>src/tests/color.disable.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'color.rc')
@@ -52,6 +52,9 @@ like ($output, qr/ \033\[31m        .* red .* \033\[0m/x, 'color.due');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'color.rc';
 ok (!-r 'color.rc', 'Removed color.rc');
 </diff>
      <filename>src/tests/color.due.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'color.rc')
@@ -55,6 +55,9 @@ like ($output, qr/ \033\[32m        .* green   .* \033\[0m      /x, 'color.keywo
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'color.rc';
 ok (!-r 'color.rc', 'Removed color.rc');
 </diff>
      <filename>src/tests/color.keyword.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'color.rc')
@@ -52,6 +52,9 @@ like ($output, qr/ \033\[31m        .* red .* \033\[0m/x, 'color.overdue');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'color.rc';
 ok (!-r 'color.rc', 'Removed color.rc');
 </diff>
      <filename>src/tests/color.overdue.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 7;
+use Test::More tests =&gt; 8;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'color.rc')
@@ -59,6 +59,9 @@ like ($output, qr/ \033\[33m .* yellow .* \033\[0m /x, 'color.pri.none');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'color.rc';
 ok (!-r 'color.rc', 'Removed color.rc');
 </diff>
      <filename>src/tests/color.pri.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'color.rc')
@@ -52,6 +52,9 @@ like ($output, qr/ \033\[31m        .* red     .* \033\[0m /x, 'color.project.re
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'color.rc';
 ok (!-r 'color.rc', 'Removed color.rc');
 </diff>
      <filename>src/tests/color.project.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'color.rc')
@@ -52,6 +52,9 @@ like ($output, qr/ \033\[31m        .* red     .* \033\[0m      /x, 'color.recur
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'color.rc';
 ok (!-r 'color.rc', 'Removed color.rc');
 </diff>
      <filename>src/tests/color.recurring.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'color.rc')
@@ -55,6 +55,9 @@ like ($output, qr/ \033\[32m        .* green   .* \033\[0m /x, 'color.tag.green'
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'color.rc';
 ok (!-r 'color.rc', 'Removed color.rc');
 </diff>
      <filename>src/tests/color.tag.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'color.rc')
@@ -52,6 +52,9 @@ like ($output, qr/ \033\[31m        .* red     .* \033\[0m /x, 'color.tagged');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'color.rc';
 ok (!-r 'color.rc', 'Removed color.rc');
 </diff>
      <filename>src/tests/color.tagged.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'completed.rc')
@@ -57,6 +57,9 @@ ok (!-r 'pending.data', 'Removed pending.data');
 unlink 'completed.data';
 ok (!-r 'completed.data', 'Removed completed.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'completed.rc';
 ok (!-r 'completed.rc', 'Removed completed.rc');
 </diff>
      <filename>src/tests/completed.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'obsolete.rc')
@@ -50,6 +50,9 @@ like ($output, qr/  foo\n/, 'unsupported configuration variable');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'obsolete.rc';
 ok (!-r 'obsolete.rc', 'Removed obsolete.rc');
 </diff>
      <filename>src/tests/config.obsolete.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 26;
+use Test::More tests =&gt; 27;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'confirm.rc')
@@ -99,6 +99,9 @@ like ($output, qr/(Permanently delete task 7 'foo'\? \(y\/n\)) \1 \1/, 'confirma
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'response.txt';
 ok (!-r 'response.txt', 'Removed response.txt');
 </diff>
      <filename>src/tests/confirmation.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 4;
+use Test::More tests =&gt; 5;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'custom.rc')
@@ -50,6 +50,9 @@ like ($output, qr/Unrecognized column name: foo\n/, 'custom report spotted inval
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'custom.rc';
 ok (!-r 'custom.rc', 'Removed custom.rc');
 </diff>
      <filename>src/tests/custom.columns.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'custom.rc')
@@ -54,6 +54,9 @@ unlike ($output, qr/2\s+R/, 'No recurrence indicator t2');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'custom.rc';
 ok (!-r 'custom.rc', 'Removed custom.rc');
 </diff>
      <filename>src/tests/custom.recur_ind.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'custom.rc')
@@ -56,6 +56,9 @@ unlike ($output, qr/two/, 'custom filter excluded');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'custom.rc';
 ok (!-r 'custom.rc', 'Removed custom.rc');
 </diff>
      <filename>src/tests/custom.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'custom.rc')
@@ -54,6 +54,9 @@ unlike ($output, qr/2\s+\+/, 'No tag indicator t2');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'custom.rc';
 ok (!-r 'custom.rc', 'Removed custom.rc');
 </diff>
      <filename>src/tests/custom.tag_ind.t</filename>
    </modified>
    <modified>
      <diff>@@ -25,13 +25,16 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;iostream&gt;
+#include &lt;Context.h&gt;
 #include &lt;Date.h&gt;
 #include &lt;test.h&gt;
 
+Context context;
+
 ////////////////////////////////////////////////////////////////////////////////
 int main (int argc, char** argv)
 {
-  UnitTest t (100);
+  UnitTest t (102);
 
   try
   {
@@ -72,11 +75,14 @@ int main (int argc, char** argv)
     t.ok    (Date::valid (2, 29, 2008), &quot;valid: 2/29/2008&quot;);
     t.notok (Date::valid (2, 29, 2007), &quot;invalid: 2/29/2007&quot;);
 
+    t.ok    (Date::valid (&quot;2/29/2008&quot;), &quot;valid: 2/29/2008&quot;);
+    t.notok (Date::valid (&quot;2/29/2007&quot;), &quot;invalid: 2/29/2007&quot;);
+
     // Leap year.
     t.ok    (Date::leapYear (2008), &quot;2008 is a leap year&quot;);
     t.notok (Date::leapYear (2007), &quot;2007 is not a leap year&quot;);
     t.ok    (Date::leapYear (2000), &quot;2000 is a leap year&quot;);
-    t.ok    (Date::leapYear (1900), &quot;1900 is a leap year&quot;);
+    t.notok (Date::leapYear (1900), &quot;1900 is not a leap year&quot;);
 
     // Days in month.
     t.is (Date::daysInMonth (2, 2008), 29, &quot;29 days in February 2008&quot;);
@@ -132,8 +138,8 @@ int main (int argc, char** argv)
 
     Date epoch (9, 8, 2001);
     t.ok ((int)epoch.toEpoch () &lt; 1000000000, &quot;9/8/2001 &lt; 1,000,000,000&quot;);
-    epoch += 86400;
-    t.ok ((int)epoch.toEpoch () &gt; 1000000000, &quot;9/9/2001 &gt; 1,000,000,000&quot;);
+    epoch += 172800;
+    t.ok ((int)epoch.toEpoch () &gt; 1000000000, &quot;9/10/2001 &gt; 1,000,000,000&quot;);
 
     Date fromEpoch (epoch.toEpoch ());
     t.is (fromEpoch.toString (), epoch.toString (), &quot;ctor (time_t)&quot;);</diff>
      <filename>src/tests/date.t.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 8;
+use Test::More tests =&gt; 9;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'date1.rc')
@@ -62,6 +62,9 @@ like ($output, qr/\b12\/1\/09\b/, 'date format m/d/y parsed');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'date1.rc';
 ok (!-r 'date1.rc', 'Removed date1.rc');
 </diff>
      <filename>src/tests/dateformat.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,13 +28,13 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 16;
+use Test::More tests =&gt; 17;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'default.rc')
 {
   print $fh &quot;data.location=.\n&quot;,
-            &quot;default.command=list\n&quot;,
+            &quot;default.command=rc:default.rc list\n&quot;,
             &quot;default.project=PROJECT\n&quot;,
             &quot;default.priority=M\n&quot;;
   close $fh;
@@ -76,6 +76,9 @@ like ($output, qr/1 PROJECT L .+ priority specified/, 'default command worked');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'default.rc';
 ok (!-r 'default.rc', 'Removed default.rc');
 </diff>
      <filename>src/tests/default.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,39 +28,36 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 16;
+use Test::More tests =&gt; 17;
 
 # Create the rc file.
-if (open my $fh, '&gt;', 'undelete.rc')
+if (open my $fh, '&gt;', 'delete.rc')
 {
   print $fh &quot;data.location=.\n&quot;,
             &quot;echo.command=no\n&quot;;
   close $fh;
-  ok (-r 'undelete.rc', 'Created undelete.rc');
+  ok (-r 'delete.rc', 'Created delete.rc');
 }
 
 # Add a task, delete it, undelete it.
-my $output = qx{../task rc:undelete.rc add one; ../task rc:undelete.rc info 1};
+my $output = qx{../task rc:delete.rc add one; ../task rc:delete.rc info 1};
 ok (-r 'pending.data', 'pending.data created');
 like ($output, qr/Status\s+Pending\n/, 'Pending');
 
-$output = qx{../task rc:undelete.rc delete 1; ../task rc:undelete.rc info 1};
+$output = qx{../task rc:delete.rc delete 1; ../task rc:delete.rc info 1};
 like ($output, qr/Status\s+Deleted\n/, 'Deleted');
-ok (! -r 'completed.data', 'completed.data not created');
+ok (-r 'completed.data', 'completed.data created');
 
-$output = qx{../task rc:undelete.rc undelete 1; ../task rc:undelete.rc info 1};
+$output = qx{echo 'y' | ../task rc:delete.rc undo; ../task rc:delete.rc info 1};
 like ($output, qr/Status\s+Pending\n/, 'Pending');
-ok (! -r 'completed.data', 'completed.data not created');
-
-$output = qx{../task rc:undelete.rc delete 1; ../task rc:undelete.rc list};
-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/Task 1 not found/, 'Task 1 not found');
+$output = qx{../task rc:delete.rc delete 1; ../task rc:delete.rc list};
+like ($output, qr/No matches./, 'No matches');
+ok (-r 'completed.data', 'completed.data created');
 
-$output = qx{../task rc:undelete.rc info 1};
-like ($output, qr/No matches./, 'no matches');
+$output = qx{../task rc:delete.rc info 1};
+like ($output, qr/Task 1 not found/, 'No matches');
 
 # Cleanup.
 ok (-r 'pending.data', 'Need to remove pending.data');
@@ -71,8 +68,12 @@ ok (-r 'completed.data', 'Need to remove completed.data');
 unlink 'completed.data';
 ok (!-r 'completed.data', 'Removed completed.data');
 
-unlink 'undelete.rc';
-ok (!-r 'undelete.rc', 'Removed undelete.rc');
+ok (-r 'undo.data', 'Need to remove undo.data');
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
+unlink 'delete.rc';
+ok (!-r 'delete.rc', 'Removed delete.rc');
 
 exit 0;
 </diff>
      <filename>src/tests/delete.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'due.rc')
@@ -60,6 +60,9 @@ like ($output, qr/\s+$almost\s+/, 'two not marked due');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'due.rc';
 ok (!-r 'due.rc', 'Removed due.rc');
 </diff>
      <filename>src/tests/due.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 11;
+use Test::More tests =&gt; 12;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'dup.rc')
@@ -59,6 +59,9 @@ like ($output, qr/Tags\s+tag/,        'duplicate added tag');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'dup.rc';
 ok (!-r 'dup.rc', 'Removed dup.rc');
 </diff>
      <filename>src/tests/duplicate.t</filename>
    </modified>
    <modified>
      <diff>@@ -25,40 +25,71 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;iostream&gt;
-#include &lt;Date.h&gt;
+#include &lt;Context.h&gt;
+#include &lt;Duration.h&gt;
 #include &lt;test.h&gt;
-#include &lt;../task.h&gt;
+
+Context context;
 
 ////////////////////////////////////////////////////////////////////////////////
 //   daily, day, Nd
-//   weekly, Nw, sennight, biweekly, fortnight
+//   weekly, 1w, sennight, biweekly, fortnight
 //   monthly, bimonthly, Nm, semimonthly
 //   1st 2nd 3rd 4th .. 31st
-//   quarterly, Nq
-//   biannual, biyearly, annual, semiannual, yearly, Ny
+//   quarterly, 1q
+//   biannual, biyearly, annual, semiannual, yearly, 1y
+
+int convertDuration (const std::string&amp; input)
+{
+  try { Duration d (input); return (int) d; }
+  catch (...) {}
+  return 0;
+}
+
 int main (int argc, char** argv)
 {
-  UnitTest t (17);
+  UnitTest t (35);
+
+  Duration d;
+  t.ok (d.valid (&quot;daily&quot;),     &quot;duration daily = 1&quot;);
+  t.ok (d.valid (&quot;weekdays&quot;),  &quot;duration weekdays = 1&quot;);
+  t.ok (d.valid (&quot;day&quot;),       &quot;duration day = 1&quot;);
+  t.ok (d.valid (&quot;0d&quot;),        &quot;duration 0d = 0&quot;);
+  t.ok (d.valid (&quot;1d&quot;),        &quot;duration 1d = 1&quot;);
+  t.ok (d.valid (&quot;7d&quot;),        &quot;duration 7d = 7&quot;);
+  t.ok (d.valid (&quot;10d&quot;),       &quot;duration 10d = 10&quot;);
+  t.ok (d.valid (&quot;100d&quot;),      &quot;duration 100d = 100&quot;);
+
+  t.ok (d.valid (&quot;weekly&quot;),    &quot;duration weekly = 7&quot;);
+  t.ok (d.valid (&quot;sennight&quot;),  &quot;duration sennight = 7&quot;);
+  t.ok (d.valid (&quot;biweekly&quot;),  &quot;duration biweekly = 14&quot;);
+  t.ok (d.valid (&quot;fortnight&quot;), &quot;duration fortnight = 14&quot;);
+  t.ok (d.valid (&quot;0w&quot;),        &quot;duration 0w = 0&quot;);
+  t.ok (d.valid (&quot;1w&quot;),        &quot;duration 1w = 7&quot;);
+  t.ok (d.valid (&quot;7w&quot;),        &quot;duration 7w = 49&quot;);
+  t.ok (d.valid (&quot;10w&quot;),       &quot;duration 10w = 70&quot;);
+  t.ok (d.valid (&quot;100w&quot;),      &quot;duration 100w = 700&quot;);
+
+  t.notok (d.valid (&quot;woof&quot;),   &quot;duration woof = fail&quot;);
 
-  std::string d;
-  d = &quot;daily&quot;;     t.is (convertDuration (d),   1, &quot;duration daily = 1&quot;);
-  d = &quot;weekdays&quot;;  t.is (convertDuration (d),   1, &quot;duration weekdays = 1&quot;);
-  d = &quot;day&quot;;       t.is (convertDuration (d),   1, &quot;duration day = 1&quot;);
-  d = &quot;0d&quot;;        t.is (convertDuration (d),   0, &quot;duration 0d = 0&quot;);
-  d = &quot;1d&quot;;        t.is (convertDuration (d),   1, &quot;duration 1d = 1&quot;);
-  d = &quot;7d&quot;;        t.is (convertDuration (d),   7, &quot;duration 7d = 7&quot;);
-  d = &quot;10d&quot;;       t.is (convertDuration (d),  10, &quot;duration 10d = 10&quot;);
-  d = &quot;100d&quot;;      t.is (convertDuration (d), 100, &quot;duration 100d = 100&quot;);
+  t.is (convertDuration (&quot;daily&quot;),       1, &quot;duration daily = 1&quot;);
+  t.is (convertDuration (&quot;weekdays&quot;),    1, &quot;duration weekdays = 1&quot;);
+  t.is (convertDuration (&quot;day&quot;),         1, &quot;duration day = 1&quot;);
+  t.is (convertDuration (&quot;0d&quot;),          0, &quot;duration 0d = 0&quot;);
+  t.is (convertDuration (&quot;1d&quot;),          1, &quot;duration 1d = 1&quot;);
+  t.is (convertDuration (&quot;7d&quot;),          7, &quot;duration 7d = 7&quot;);
+  t.is (convertDuration (&quot;10d&quot;),        10, &quot;duration 10d = 10&quot;);
+  t.is (convertDuration (&quot;100d&quot;),      100, &quot;duration 100d = 100&quot;);
 
-  d = &quot;weekly&quot;;    t.is (convertDuration (d),   7, &quot;duration weekly = 7&quot;);
-  d = &quot;sennight&quot;;  t.is (convertDuration (d),   7, &quot;duration sennight = 7&quot;);
-  d = &quot;biweekly&quot;;  t.is (convertDuration (d),  14, &quot;duration biweekly = 14&quot;);
-  d = &quot;fortnight&quot;; t.is (convertDuration (d),  14, &quot;duration fortnight = 14&quot;);
-  d = &quot;0w&quot;;        t.is (convertDuration (d),   0, &quot;duration 0w = 0&quot;);
-  d = &quot;1w&quot;;        t.is (convertDuration (d),   7, &quot;duration 1w = 7&quot;);
-  d = &quot;7w&quot;;        t.is (convertDuration (d),  49, &quot;duration 7w = 49&quot;);
-  d = &quot;10w&quot;;       t.is (convertDuration (d),  70, &quot;duration 10w = 70&quot;);
-  d = &quot;100w&quot;;      t.is (convertDuration (d), 700, &quot;duration 100w = 700&quot;);
+  t.is (convertDuration (&quot;weekly&quot;),      7, &quot;duration weekly = 7&quot;);
+  t.is (convertDuration (&quot;sennight&quot;),    7, &quot;duration sennight = 7&quot;);
+  t.is (convertDuration (&quot;biweekly&quot;),   14, &quot;duration biweekly = 14&quot;);
+  t.is (convertDuration (&quot;fortnight&quot;),  14, &quot;duration fortnight = 14&quot;);
+  t.is (convertDuration (&quot;0w&quot;),          0, &quot;duration 0w = 0&quot;);
+  t.is (convertDuration (&quot;1w&quot;),          7, &quot;duration 1w = 7&quot;);
+  t.is (convertDuration (&quot;7w&quot;),         49, &quot;duration 7w = 49&quot;);
+  t.is (convertDuration (&quot;10w&quot;),        70, &quot;duration 10w = 70&quot;);
+  t.is (convertDuration (&quot;100w&quot;),      700, &quot;duration 100w = 700&quot;);
 
   return 0;
 }</diff>
      <filename>src/tests/duration.t.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 11;
+use Test::More tests =&gt; 12;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'enp.rc')
@@ -57,6 +57,9 @@ like ($output, qr/Tags\s+tag/,            'en passant 2 description change');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'enp.rc';
 ok (!-r 'enp.rc', 'Removed enp.rc');
 </diff>
      <filename>src/tests/enpassant.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 7;
+use Test::More tests =&gt; 8;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'export.rc')
@@ -41,16 +41,21 @@ if (open my $fh, '&gt;', 'export.rc')
 # Add two tasks, export, examine result.
 qx{../task rc:export.rc add priority:H project:A one};
 qx{../task rc:export.rc add +tag1 +tag2 two};
-qx{../task rc:export.rc export ./export.txt};
+qx{../task rc:export.rc export &gt; ./export.txt};
 
 my @lines;
 if (open my $fh, '&lt;', './export.txt')
 {
-  @lines = &lt;$fh&gt;;
+  while (my $line = &lt;$fh&gt;)
+  {
+    next unless $line =~ /^['0-9]/;
+    push @lines, $line;
+  }
+
   close $fh;
 }
 
-my $line1 = qr/'id','uuid','status','tags','entry','start','due','recur','end','project','priority','fg','bg','description'\n/;
+my $line1 = qr/'uuid','status','tags','entry','start','due','recur','end','project','priority','fg','bg','description'\n/;
 my $line2 = qr/'.{8}-.{4}-.{4}-.{4}-.{12}','pending','',\d+,,,,,'A','H',,,'one'\n/;
 my $line3 = qr/'.{8}-.{4}-.{4}-.{4}-.{12}','pending','tag1 tag2',\d+,,,,,,,,,'two'\n/;
 
@@ -65,6 +70,9 @@ ok (!-r 'export.txt', 'Removed export.txt');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'export.rc';
 ok (!-r 'export.rc', 'Removed export.rc');
 </diff>
      <filename>src/tests/export.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 108;
+use Test::More tests =&gt; 130;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'filter.rc')
@@ -110,6 +110,33 @@ like   ($output, qr/five/,  'g5');
 unlike ($output, qr/six/,   'g6');
 unlike ($output, qr/seven/, 'g7');
 
+$output = qx{../task rc:filter.rc list -tag};
+unlike ($output, qr/one/,   'g1');
+like   ($output, qr/two/,   'g2');
+like   ($output, qr/three/, 'g3');
+like   ($output, qr/four/,  'g4');
+unlike ($output, qr/five/,  'g5');
+like   ($output, qr/six/,   'g6');
+like   ($output, qr/seven/, 'g7');
+
+$output = qx{../task rc:filter.rc list -missing};
+like   ($output, qr/one/,   'g1');
+like   ($output, qr/two/,   'g2');
+like   ($output, qr/three/, 'g3');
+like   ($output, qr/four/,  'g4');
+like   ($output, qr/five/,  'g5');
+like   ($output, qr/six/,   'g6');
+like   ($output, qr/seven/, 'g7');
+
+$output = qx{../task rc:filter.rc list +tag -tag};
+unlike ($output, qr/one/,   'g1');
+unlike ($output, qr/two/,   'g2');
+unlike ($output, qr/three/, 'g3');
+unlike ($output, qr/four/,  'g4');
+unlike ($output, qr/five/,  'g5');
+unlike ($output, qr/six/,   'g6');
+unlike ($output, qr/seven/, 'g7');
+
 $output = qx{../task rc:filter.rc list project:A priority:H};
 like   ($output, qr/one/,   'h1');
 like   ($output, qr/two/,   'h2');
@@ -186,6 +213,9 @@ unlike ($output, qr/seven/, 'n7');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'filter.rc';
 ok (!-r 'filter.rc', 'Removed filter.rc');
 </diff>
      <filename>src/tests/filter.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 8;
+use Test::More tests =&gt; 9;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'import.rc')
@@ -50,7 +50,7 @@ if (open my $fh, '&gt;', 'import.txt')
 }
 
 my $output = qx{../task rc:import.rc import import.txt};
-is ($output, &quot;Imported 2 tasks successfully, with 0 errors.\n&quot;, 'no errors');
+like ($output, qr/Imported 2 tasks successfully, with 0 errors./, 'no errors');
 
 $output = qx{../task rc:import.rc list};
 like ($output, qr/1.+A.+M.+foo bar/,  't1');
@@ -63,6 +63,9 @@ ok (!-r 'import.txt', 'Removed import.txt');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'import.rc';
 ok (!-r 'import.rc', 'Removed import.rc');
 </diff>
      <filename>src/tests/import.143.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 8;
+use Test::More tests =&gt; 9;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'import.rc')
@@ -50,7 +50,7 @@ if (open my $fh, '&gt;', 'import.txt')
 }
 
 my $output = qx{../task rc:import.rc import import.txt};
-is ($output, &quot;Imported 2 tasks successfully, with 0 errors.\n&quot;, 'no errors');
+like ($output, qr/Imported 2 tasks successfully, with 0 errors./, 'no errors');
 
 $output = qx{../task rc:import.rc list};
 like ($output, qr/1.+A.+M.+foo bar/,  't1');
@@ -63,6 +63,9 @@ ok (!-r 'import.txt', 'Removed import.txt');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'import.rc';
 ok (!-r 'import.rc', 'Removed import.rc');
 </diff>
      <filename>src/tests/import.150.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 8;
+use Test::More tests =&gt; 9;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'import.rc')
@@ -50,7 +50,7 @@ if (open my $fh, '&gt;', 'import.txt')
 }
 
 my $output = qx{../task rc:import.rc import import.txt};
-is ($output, &quot;Imported 2 tasks successfully, with 0 errors.\n&quot;, 'no errors');
+like ($output, qr/Imported 2 tasks successfully, with 0 errors./, 'no errors');
 
 $output = qx{../task rc:import.rc list};
 like ($output, qr/1.+A.+M.+foo bar/,  't1');
@@ -63,6 +63,9 @@ ok (!-r 'import.txt', 'Removed import.txt');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'import.rc';
 ok (!-r 'import.rc', 'Removed import.rc');
 </diff>
      <filename>src/tests/import.160.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 8;
+use Test::More tests =&gt; 9;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'import.rc')
@@ -49,7 +49,7 @@ if (open my $fh, '&gt;', 'import.txt')
 }
 
 my $output = qx{../task rc:import.rc import import.txt};
-is ($output, &quot;Imported 2 tasks successfully, with 0 errors.\n&quot;, 'no errors');
+like ($output, qr/Imported 2 tasks successfully, with 0 errors./, 'no errors');
 
 $output = qx{../task rc:import.rc list};
 like ($output, qr/1.+A.+H.+This is a test/, 't1');
@@ -62,6 +62,9 @@ ok (!-r 'import.txt', 'Removed import.txt');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'import.rc';
 ok (!-r 'import.rc', 'Removed import.rc');
 </diff>
      <filename>src/tests/import.cmd.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 8;
+use Test::More tests =&gt; 9;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'import.rc')
@@ -50,7 +50,7 @@ if (open my $fh, '&gt;', 'import.txt')
 }
 
 my $output = qx{../task rc:import.rc import import.txt};
-is ($output, &quot;Imported 2 tasks successfully, with 0 errors.\n&quot;, 'no errors');
+like ($output, qr/Imported 2 tasks successfully, with 0 errors./, 'no errors');
 
 $output = qx{../task rc:import.rc list};
 like ($output, qr/1.+H.+this is a test/, 't1');
@@ -63,6 +63,9 @@ ok (!-r 'import.txt', 'Removed import.txt');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'import.rc';
 ok (!-r 'import.rc', 'Removed import.rc');
 </diff>
      <filename>src/tests/import.csv.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 10;
+use Test::More tests =&gt; 11;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'import.rc')
@@ -50,7 +50,7 @@ if (open my $fh, '&gt;', 'import.txt')
 }
 
 my $output = qx{../task rc:import.rc import import.txt};
-is ($output, &quot;Imported 3 tasks successfully, with 0 errors.\n&quot;, 'no errors');
+like ($output, qr/Imported 3 tasks successfully, with 0 errors./, 'no errors');
 
 $output = qx{../task rc:import.rc list};
 like ($output, qr/1.+project.+This is a test/, 't1');
@@ -69,6 +69,9 @@ ok (!-r 'pending.data', 'Removed pending.data');
 unlink 'completed.data';
 ok (!-r 'completed.data', 'Removed completed.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'import.rc';
 ok (!-r 'import.rc', 'Removed import.rc');
 </diff>
      <filename>src/tests/import.todo.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 9;
+use Test::More tests =&gt; 10;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'import.rc')
@@ -50,7 +50,7 @@ if (open my $fh, '&gt;', 'import.txt')
 }
 
 my $output = qx{../task rc:import.rc import import.txt};
-is ($output, &quot;Imported 3 tasks successfully, with 0 errors.\n&quot;, 'no errors');
+like ($output, qr/Imported 3 tasks successfully, with 0 errors./, 'no errors');
 
 $output = qx{../task rc:import.rc list};
 like ($output, qr/1.+Get milk, bread/, 't1');
@@ -64,6 +64,9 @@ ok (!-r 'import.txt', 'Removed import.txt');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'import.rc';
 ok (!-r 'import.rc', 'Removed import.rc');
 </diff>
      <filename>src/tests/import.txt.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 8;
+use Test::More tests =&gt; 9;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'custom.rc')
@@ -59,6 +59,9 @@ unlike ($output, qr/two/,         'custom filter excluded');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'custom.rc';
 ok (!-r 'custom.rc', 'Removed custom.rc');
 </diff>
      <filename>src/tests/label.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 9;
+use Test::More tests =&gt; 10;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'nag.rc')
@@ -58,6 +58,9 @@ unlike (qx{../task rc:nag.rc do 1}, qr/NAG/, 'do due:yesterday -&gt; no nag');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'nag.rc';
 ok (!-r 'nag.rc', 'Removed nag.rc');
 </diff>
      <filename>src/tests/nag.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'next.rc')
@@ -54,6 +54,9 @@ like ($output, qr/\s3\sB\s+H\s+-\sBH\n/, 'BH shown');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'next.rc';
 ok (!-r 'next.rc', 'Removed next.rc');
 </diff>
      <filename>src/tests/next.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 55;
+use Test::More tests =&gt; 56;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'oldest.rc')
@@ -78,7 +78,7 @@ like   ($output, qr/nine/,   'oldest: nine');
 like   ($output, qr/ten/,    'oldest: ten');
 unlike ($output, qr/eleven/, 'no: eleven');
 
-$output = qx{../task rc:oldest.rc oldest 3};
+$output = qx{../task rc:oldest.rc oldest limit:3};
 like   ($output, qr/one/,    'oldest: one');
 like   ($output, qr/two/,    'oldest: two');
 like   ($output, qr/three/,  'oldest: three');
@@ -104,7 +104,7 @@ like   ($output, qr/nine/,   'newest: nine');
 like   ($output, qr/ten/,    'newest: ten');
 like   ($output, qr/eleven/, 'newest: eleven');
 
-$output = qx{../task rc:oldest.rc newest 3};
+$output = qx{../task rc:oldest.rc newest limit:3};
 unlike ($output, qr/one/,    'no: one');
 unlike ($output, qr/two/,    'no: two');
 unlike ($output, qr/three/,  'no: three');
@@ -121,6 +121,9 @@ like   ($output, qr/eleven/, 'newest: eleven');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'oldest.rc';
 ok (!-r 'oldest.rc', 'Removed oldest.rc');
 </diff>
      <filename>src/tests/oldest.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'due.rc')
@@ -53,6 +53,9 @@ unlike ($output, qr/three/, 'overdue: task 3 does not show up');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'due.rc';
 ok (!-r 'due.rc', 'Removed due.rc');
 </diff>
      <filename>src/tests/overdue.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 5;
+use Test::More tests =&gt; 6;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'recur.rc')
@@ -57,6 +57,9 @@ like ($output, qr/second .* third .* first/msx, 'weekly 3d daily');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'recur.rc';
 ok (!-r 'recur.rc', 'Removed recur.rc');
 </diff>
      <filename>src/tests/recur.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 7;
+use Test::More tests =&gt; 8;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'recur.rc')
@@ -57,6 +57,9 @@ ok (!-r 'pending.data', 'Removed pending.data');
 unlink 'completed.data';
 ok (!-r 'completed.data', 'Removed completed.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'recur.rc';
 ok (!-r 'recur.rc', 'Removed recur.rc');
 </diff>
      <filename>src/tests/recur.weekdays.t</filename>
    </modified>
    <modified>
      <diff>@@ -9,3 +9,26 @@ done
 
 date &gt;&gt; all.log
 
+START=`head -1 all.log`
+END=`tail -1 all.log`
+OS=`uname`
+
+case $OS in
+  Darwin)
+    STARTEPOCH=`date -j -f &quot;%a %b %d %T %Z %Y&quot; &quot;${START}&quot; &quot;+%s&quot;`
+    ENDEPOCH=`date -j -f &quot;%a %b %d %T %Z %Y&quot; &quot;${END}&quot; &quot;+%s&quot;`
+    ;;
+  Linux)
+    STARTEPOCH=`date &quot;+%s&quot; -d &quot;${START}&quot;`
+    ENDEPOCH=`date &quot;+%s&quot; -d &quot;${END}&quot;`
+    ;;
+esac
+
+RUNTIME=$(($ENDEPOCH - $STARTEPOCH))
+
+echo -n 'Pass: '
+grep ^ok all.log | wc -l
+echo -n 'Fail: '
+grep ^not all.log | wc -l
+echo -n 'Runtime: '
+echo $RUNTIME</diff>
      <filename>src/tests/run_all</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 27;
+use Test::More tests =&gt; 28;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'seq.rc')
@@ -46,7 +46,8 @@ my $output = qx{../task rc:seq.rc info 1};
 like ($output, qr/Status\s+Completed/, 'sequence do 1');
 $output = qx{../task rc:seq.rc info 2};
 like ($output, qr/Status\s+Completed/, 'sequence do 2');
-qx{../task rc:seq.rc undo 1,2};
+qx{echo 'y'|../task rc:seq.rc undo};
+qx{echo 'y'|../task rc:seq.rc undo};
 $output = qx{../task rc:seq.rc info 1};
 like ($output, qr/Status\s+Pending/, 'sequence undo 1');
 $output = qx{../task rc:seq.rc info 2};
@@ -58,11 +59,12 @@ $output = qx{../task rc:seq.rc info 1};
 like ($output, qr/Status\s+Deleted/, 'sequence delete 1');
 $output = qx{../task rc:seq.rc info 2};
 like ($output, qr/Status\s+Deleted/, 'sequence delete 2');
-qx{../task rc:seq.rc undelete 1,2};
+qx{echo 'y'|../task rc:seq.rc undo};
+qx{echo 'y'|../task rc:seq.rc undo};
 $output = qx{../task rc:seq.rc info 1};
-like ($output, qr/Status\s+Pending/, 'sequence undelete 1');
+like ($output, qr/Status\s+Pending/, 'sequence undo 1');
 $output = qx{../task rc:seq.rc info 2};
-like ($output, qr/Status\s+Pending/, 'sequence undelete 2');
+like ($output, qr/Status\s+Pending/, 'sequence undo 2');
 
 # Test sequences in start/stop
 qx{../task rc:seq.rc start 1,2};
@@ -118,6 +120,9 @@ like ($output, qr/\d+\/\d+\/\d+ note/, 'sequence annotate 2');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'seq.rc';
 ok (!-r 'seq.rc', 'Removed seq.rc');
 </diff>
      <filename>src/tests/sequence.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,14 +28,14 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 21;
+use Test::More tests =&gt; 22;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'shadow.rc')
 {
   print $fh &quot;data.location=.\n&quot;,
             &quot;shadow.file=./shadow.txt\n&quot;,
-            &quot;shadow.command=stats\n&quot;,
+            &quot;shadow.command=rc:shadow.rc stats\n&quot;,
             &quot;shadow.notify=on\n&quot;;
   close $fh;
   ok (-r 'shadow.rc', 'Created shadow.rc');
@@ -51,7 +51,7 @@ $output = qx{../task rc:shadow.rc delete 1};
 like ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file updated on delete');
 
 $output = qx{../task rc:shadow.rc list};
-like ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file updated on list');
+unlike ($output, qr/\[Shadow file '\.\/shadow\.txt' updated\]/, 'shadow file not updated on list');
 
 # Inspect the shadow file.
 my $file = slurp ('./shadow.txt');
@@ -75,6 +75,9 @@ ok (!-r 'pending.data', 'Removed pending.data');
 unlink 'completed.data';
 ok (!-r 'completed.data', 'Removed completed.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'shadow.rc';
 ok (!-r 'shadow.rc', 'Removed shadow.rc');
 </diff>
      <filename>src/tests/shadow.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 12;
+use Test::More tests =&gt; 14;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'start.rc')
@@ -66,6 +66,10 @@ ok (-r 'pending.data', 'Need to remove pending.data');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+ok (-r 'undo.data', 'Need to remove undo.data');
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'start.rc';
 ok (!-r 'start.rc', 'Removed start.rc');
 </diff>
      <filename>src/tests/start.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 11;
+use Test::More tests =&gt; 12;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'sp.rc')
@@ -60,12 +60,15 @@ $output = qx{../task rc:sp.rc list project:abc};
 like ($output, qr/\babc\s*$/m, 'abc,ab,a,b | a -&gt; abc');
 
 $output = qx{../task rc:sp.rc list project:abcd};
-like ($output, qr/^No matches.$/, 'abc,ab,a,b | abcd -&gt; nul');
+like ($output, qr/No matches./, 'abc,ab,a,b | abcd -&gt; nul');
 
 # Cleanup.
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'sp.rc';
 ok (!-r 'sp.rc', 'Removed sp.rc');
 </diff>
      <filename>src/tests/subproject.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 9;
+use Test::More tests =&gt; 10;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'subst.rc')
@@ -72,6 +72,9 @@ like ($output, qr/aaa  ccc/, 'word deletion in description');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'subst.rc';
 ok (!-r 'subst.rc', 'Removed subst.rc');
 </diff>
      <filename>src/tests/substitute.t</filename>
    </modified>
    <modified>
      <diff>@@ -25,20 +25,30 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;sys/time.h&gt;
-#include &quot;../T.h&quot;
-#include &quot;../task.h&quot;
+#include &quot;Task.h&quot;
+#include &quot;main.h&quot;
 #include &quot;test.h&quot;
 
+Context context;
+
 ////////////////////////////////////////////////////////////////////////////////
 int main (int argc, char** argv)
 {
   UnitTest test (1);
 
-  std::string sample = &quot;d346065c-7ef6-49af-ae77-19c1825807f5 &quot;
-                       &quot;- &quot;
-                       &quot;[bug performance solaris linux osx] &quot;
-                       &quot;[due:1236142800 entry:1236177552 priority:H project:task-1.5.0 start:1236231761] &quot;
-                       &quot;Profile task and identify performance bottlenecks&quot;;
+  // FF4 parsing is being tested.  Performance of legacy format parsing is
+  // immaterial.
+  std::string sample = &quot;[&quot;
+                       &quot;uuid:\&quot;d346065c-7ef6-49af-ae77-19c1825807f5\&quot; &quot;
+                       &quot;status:\&quot;pending\&quot; &quot;
+                       &quot;tags:\&quot;bug,performance,solaris,linux,osx\&quot; &quot;
+                       &quot;due:\&quot;1236142800\&quot; &quot;
+                       &quot;entry:\&quot;1236177552\&quot; &quot;
+                       &quot;priority:\&quot;H\&quot; &quot;
+                       &quot;project:\&quot;task-1.5.0\&quot; &quot;
+                       &quot;start:\&quot;1236231761\&quot; &quot;
+                       &quot;description:\&quot;Profile task and identify performance bottlenecks\&quot;&quot;
+                       &quot;]&quot;;
 
   // Start clock
   test.diag (&quot;start&quot;);
@@ -47,7 +57,7 @@ int main (int argc, char** argv)
 
   for (int i = 0; i &lt; 1000000; i++)
   {
-    T t (sample);
+    Task t (sample);
   }
 
   // End clock</diff>
      <filename>src/tests/t.benchmark.t.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -24,70 +24,141 @@
 //     USA
 //
 ////////////////////////////////////////////////////////////////////////////////
-#include &quot;../T.h&quot;
-#include &quot;../task.h&quot;
+#include &quot;main.h&quot;
 #include &quot;test.h&quot;
 
+Context context;
+
 ////////////////////////////////////////////////////////////////////////////////
 int main (int argc, char** argv)
 {
-  UnitTest test (10);
-
-  T t;
-  std::string s = t.compose ();
-  test.is ((int)s.length (), 49, &quot;T::T (); T::compose ()&quot;);
-  test.diag (s);
-
-  t.setStatus (T::completed);
-  s = t.compose ();
-  test.is (s[37], '+', &quot;T::setStatus (completed)&quot;);
-  test.diag (s);
-
-  t.setStatus (T::deleted);
-  s = t.compose ();
-  test.is (s[37], 'X', &quot;T::setStatus (deleted)&quot;);
-  test.diag (s);
-
-  t.setStatus (T::recurring);
-  s = t.compose ();
-  test.is (s[37], 'r', &quot;T::setStatus (recurring)&quot;);
-  test.diag (s);
-
-  std::string format3 = &quot;00000000-0000-0000-0000-000000000000 - [] [] [] Sample\n&quot;;
-
-  // Format 1 Round trip test.
-  std::string sample = &quot;[] [] Sample&quot;;
-  T tf1;
-  tf1.parse (sample);
-  test.is (tf1.compose ().substr (36, std::string::npos),
-           format3.substr (36, std::string::npos),
-           &quot;T::parse format 1 -&gt; T::compose round trip&quot;);
-
-  // Format 2 Round trip test.
-  sample = &quot;00000000-0000-0000-0000-000000000000 - [] [] Sample&quot;;
-  T tf2;
-  tf2.parse (sample);
-  test.is (tf2.compose (), format3, &quot;T::parse format 2 -&gt; T::compose round trip&quot;);
-
-  // Format 3 Round trip test.
-  sample = &quot;00000000-0000-0000-0000-000000000000 - [] [] [] Sample&quot;;
-  T tf3;
-  tf3.parse (sample);
-  test.is (tf3.compose (), format3, &quot;T::parse format 3 -&gt; T::compose round trip&quot;);
-
-  // b10b3236-70d8-47bb-840a-b4c430758fb6 - [foo] [bar:baz] [1237865996:'woof'] sample\n
-  // ....:....|....:....|....:....|....:....|....:....|....:....|....:....|....:....|....:....|
-  // ^                                   ^                             ^
-  // 0                                   36                            66
-  t.setStatus (T::pending);
-  t.addTag (&quot;foo&quot;);
-  t.setAttribute (&quot;bar&quot;, &quot;baz&quot;);
-  t.addAnnotation (&quot;woof&quot;);
-  t.setDescription (&quot;sample&quot;);
-  std::string format = t.compose ();
-  test.is (format.substr (36, 20), &quot; - [foo] [bar:baz] [&quot;, &quot;compose tag, attribute&quot;);
-  test.is (format.substr (66, 16), &quot;:\&quot;woof\&quot;] sample\n&quot;,  &quot;compose annotation&quot;);
-  test.is (t.getAnnotationCount (), 1,                     &quot;annotation count&quot;);
+  UnitTest test (37);
+
+  test.is ((int)Task::textToStatus (&quot;pending&quot;),   (int)Task::pending,   &quot;textToStatus pending&quot;);
+  test.is ((int)Task::textToStatus (&quot;completed&quot;), (int)Task::completed, &quot;textToStatus completed&quot;);
+  test.is ((int)Task::textToStatus (&quot;deleted&quot;),   (int)Task::deleted,   &quot;textToStatus deleted&quot;);
+  test.is ((int)Task::textToStatus (&quot;recurring&quot;), (int)Task::recurring, &quot;textToStatus recurring&quot;);
+
+  test.is (Task::statusToText (Task::pending),   &quot;pending&quot;,   &quot;statusToText pending&quot;);
+  test.is (Task::statusToText (Task::completed), &quot;completed&quot;, &quot;statusToText completed&quot;);
+  test.is (Task::statusToText (Task::deleted),   &quot;deleted&quot;,   &quot;statusToText deleted&quot;);
+  test.is (Task::statusToText (Task::recurring), &quot;recurring&quot;, &quot;statusToText recurring&quot;);
+
+  // Round-trip testing.
+  Task t3;
+  t3.set (&quot;name&quot;, &quot;value&quot;);
+  std::string before = t3.composeF4 ();
+  t3.parse (before);
+  std::string after = t3.composeF4 ();
+  t3.parse (after);
+  after = t3.composeF4 ();
+  t3.parse (after);
+  after = t3.composeF4 ();
+  test.is (before, after, &quot;Task::composeF4 -&gt; parse round trip 4 iterations&quot;);
+
+  // Legacy Format 1
+  //   [tags] [attributes] description\n
+  //   X [tags] [attributes] description\n
+  std::string sample = &quot;[tag1 tag2] [att1:value1 att2:value2] Description&quot;;
+  sample = &quot;X &quot;
+           &quot;[one two] &quot;
+           &quot;[att1:value1 att2:value2] &quot;
+           &quot;Description&quot;;
+  bool good = true;
+  try { Task ff1 (sample); } catch (...) { good = false; }
+  test.notok (good, &quot;Support for ff1 removed&quot;);
+
+  // Legacy Format 2
+  //   uuid status [tags] [attributes] description\n
+  sample = &quot;00000000-0000-0000-0000-000000000000 &quot;
+           &quot;- &quot;
+           &quot;[tag1 tag2] &quot;
+           &quot;[att1:value1 att2:value2] &quot;
+           &quot;Description&quot;;
+  Task ff2 (sample);
+  std::string value = ff2.get (&quot;uuid&quot;);
+  test.is (value, &quot;00000000-0000-0000-0000-000000000000&quot;, &quot;ff2 uuid&quot;);
+  value = ff2.get (&quot;status&quot;);
+  test.is (value, &quot;pending&quot;, &quot;ff2 status&quot;);
+  test.ok (ff2.hasTag (&quot;tag1&quot;), &quot;ff2 tag1&quot;);
+  test.ok (ff2.hasTag (&quot;tag2&quot;), &quot;ff2 tag2&quot;);
+  test.is (ff2.getTagCount (), 2, &quot;ff2 # tags&quot;);
+  value = ff2.get (&quot;att1&quot;);
+  test.is (value, &quot;value1&quot;, &quot;ff2 att1&quot;);
+  value = ff2.get (&quot;att2&quot;);
+  test.is (value, &quot;value2&quot;, &quot;ff2 att2&quot;);
+  value = ff2.get (&quot;description&quot;);
+  test.is (value, &quot;Description&quot;, &quot;ff2 description&quot;);
+
+  // Legacy Format 3
+  //   uuid status [tags] [attributes] [annotations] description\n
+  sample = &quot;00000000-0000-0000-0000-000000000000 &quot;
+           &quot;- &quot;
+           &quot;[tag1 tag2] &quot;
+           &quot;[att1:value1 att2:value2] &quot;
+           &quot;[123:ann1 456:ann2] Description&quot;;
+  Task ff3 (sample);
+  value = ff2.get (&quot;uuid&quot;);
+  test.is (value, &quot;00000000-0000-0000-0000-000000000000&quot;, &quot;ff3 uuid&quot;);
+  value = ff2.get (&quot;status&quot;);
+  test.is (value, &quot;pending&quot;, &quot;ff3 status&quot;);
+  test.ok (ff2.hasTag (&quot;tag1&quot;), &quot;ff3 tag1&quot;);
+  test.ok (ff2.hasTag (&quot;tag2&quot;), &quot;ff3 tag2&quot;);
+  test.is (ff2.getTagCount (), 2, &quot;ff3 # tags&quot;);
+  value = ff3.get (&quot;att1&quot;);
+  test.is (value, &quot;value1&quot;, &quot;ff3 att1&quot;);
+  value = ff3.get (&quot;att2&quot;);
+  test.is (value, &quot;value2&quot;, &quot;ff3 att2&quot;);
+  value = ff3.get (&quot;description&quot;);
+  test.is (value, &quot;Description&quot;, &quot;ff3 description&quot;);
+
+  // Current Format 4
+  //   [name:&quot;value&quot; ...]\n
+  sample = &quot;[&quot;
+           &quot;uuid:\&quot;00000000-0000-0000-0000-000000000000\&quot; &quot;
+           &quot;status:\&quot;P\&quot; &quot;
+           &quot;tags:\&quot;tag1&amp;commaltag2\&quot; &quot;
+           &quot;att1:\&quot;value1\&quot; &quot;
+           &quot;att2:\&quot;value2\&quot; &quot;
+           &quot;description:\&quot;Description\&quot;&quot;
+           &quot;]&quot;;
+  Task ff4 (sample);
+  value = ff2.get (&quot;uuid&quot;);
+  test.is (value, &quot;00000000-0000-0000-0000-000000000000&quot;, &quot;ff4 uuid&quot;);
+  value = ff2.get (&quot;status&quot;);
+  test.is (value, &quot;pending&quot;, &quot;ff4 status&quot;);
+  test.ok (ff2.hasTag (&quot;tag1&quot;), &quot;ff4 tag1&quot;);
+  test.ok (ff2.hasTag (&quot;tag2&quot;), &quot;ff4 tag2&quot;);
+  test.is (ff2.getTagCount (), 2, &quot;ff4 # tags&quot;);
+  value = ff4.get (&quot;att1&quot;);
+  test.is (value, &quot;value1&quot;, &quot;ff4 att1&quot;);
+  value = ff4.get (&quot;att2&quot;);
+  test.is (value, &quot;value2&quot;, &quot;ff4 att2&quot;);
+  value = ff4.get (&quot;description&quot;);
+  test.is (value, &quot;Description&quot;, &quot;ff4 description&quot;);
+
+/*
+
+TODO Task::composeCSV
+TODO Task::id
+TODO Task::*Status
+TODO Task::*Tag*
+TODO Task::*Annotation*
+
+*/
+
+  // Task::operator==
+  Task left (&quot;[one:1 two:2 three:3]&quot;);
+  Task right (left);
+  test.ok (left == right, &quot;left == right -&gt; true&quot;);
+  left.set (&quot;one&quot;, &quot;1.0&quot;);
+  test.notok (left == right, &quot;left == right -&gt; false&quot;);
+
+  // Task::validate
+  Task bad (&quot;[entry:1000000001 start:1000000000]&quot;);
+  good = true;
+  try { bad.validate (); } catch (...) { good = false; }
+  test.notok (good, &quot;Task::validate entry &lt;= start&quot;);
 
   return 0;
 }</diff>
      <filename>src/tests/t.t.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 9;
+use Test::More tests =&gt; 10;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'tag.rc')
@@ -39,20 +39,20 @@ if (open my $fh, '&gt;', 'tag.rc')
 }
 
 # Add task with tags.
-my $output = qx{../task rc:tag.rc add +1 This +2 is a test +3; ../task rc:tag.rc info 1};
-like ($output, qr/^Tags\s+1 2 3\n/m, 'tags found');
+my $output = qx{../task rc:tag.rc add +one This +two is a test +three; ../task rc:tag.rc info 1};
+like ($output, qr/^Tags\s+one two three\n/m, 'tags found');
 
 # Remove tags.
-$output = qx{../task rc:tag.rc 1 -3 -2 -1; ../task rc:tag.rc info 1};
-unlike ($output, qr/^Tags/m, '-3 -2 -1 tag removed');
+$output = qx{../task rc:tag.rc 1 -three -two -one; ../task rc:tag.rc info 1};
+unlike ($output, qr/^Tags/m, '-three -two -one tag removed');
 
 # Add tags.
-$output = qx{../task rc:tag.rc 1 +4 +5 +6; ../task rc:tag.rc info 1};
-like ($output, qr/^Tags\s+4 5 6\n/m, 'tags found');
+$output = qx{../task rc:tag.rc 1 +four +five +six; ../task rc:tag.rc info 1};
+like ($output, qr/^Tags\s+four five six\n/m, 'tags found');
 
 # Remove tags.
-$output = qx{../task rc:tag.rc 1 -4 -5 -6; ../task rc:tag.rc info 1};
-unlike ($output, qr/^Tags/m, '-4 -5 -6 tag removed');
+$output = qx{../task rc:tag.rc 1 -four -five -six; ../task rc:tag.rc info 1};
+unlike ($output, qr/^Tags/m, '-four -five -six tag removed');
 
 # Add and remove tags.
 $output = qx{../task rc:tag.rc 1 +duplicate -duplicate; ../task rc:tag.rc info 1};
@@ -66,6 +66,9 @@ unlike ($output, qr/^Tags/m, '-missing NOP');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'tag.rc';
 ok (!-r 'tag.rc', 'Removed tag.rc');
 </diff>
      <filename>src/tests/tag.t</filename>
    </modified>
    <modified>
      <diff>@@ -27,90 +27,117 @@
 #include &lt;iostream&gt;
 #include &lt;unistd.h&gt;
 
-#include &quot;../TDB.h&quot;
-#include &quot;../task.h&quot;
+#include &quot;main.h&quot;
 #include &quot;test.h&quot;
 
+Context context;
+
+////////////////////////////////////////////////////////////////////////////////
+void get (std::vector &lt;Task&gt;&amp; pending, std::vector &lt;Task&gt;&amp; completed)
+{
+  TDB tdb;
+  tdb.location (&quot;.&quot;);
+  tdb.lock ();
+  tdb.loadPending   (pending,   context.filter);
+  tdb.loadCompleted (completed, context.filter);
+  tdb.unlock ();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 int main (int argc, char** argv)
 {
-  UnitTest t (38);
+  UnitTest t (22);
 
   try
   {
     // Remove any residual test file.
     unlink (&quot;./pending.data&quot;);
     unlink (&quot;./completed.data&quot;);
+    unlink (&quot;./undo.data&quot;);
 
     // Try reading an empty database.
+    Filter filter;
+    std::vector &lt;Task&gt; all;
+    std::vector &lt;Task&gt; pending;
+    std::vector &lt;Task&gt; completed;
+    get (pending, completed);
+    t.ok (pending.size () == 0, &quot;TDB Read empty pending&quot;);
+    t.ok (completed.size () == 0, &quot;TDB Read empty completed&quot;);
+
+    // Add without commit.
     TDB tdb;
-    tdb.dataDirectory (&quot;.&quot;);
-    std::vector &lt;T&gt; all;
-    t.ok (!tdb.pendingT (all), &quot;TDB::pendingT read empty db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-    t.ok (!tdb.allPendingT (all), &quot;TDB::allPendingT read empty db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-    t.ok (!tdb.completedT (all), &quot;TDB::completedT read empty db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-    t.ok (!tdb.allCompletedT (all), &quot;TDB::allCompletedT read empty db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-
-    // Add a new task.
-    T t1;
-    t1.setId (1);
-    t1.setStatus (T::pending);
-    t1.setAttribute (&quot;project&quot;, &quot;p1&quot;);
-    t1.setDescription (&quot;task 1&quot;);
-    t.diag (t1.compose ());
-    t.ok (tdb.addT (t1), &quot;TDB::addT t1&quot;);
-
-    // Verify as above.
-    t.ok (tdb.pendingT (all), &quot;TDB::pendingT read db&quot;);
-    t.is ((int) all.size (), 1, &quot;empty db&quot;);
-    t.ok (tdb.allPendingT (all), &quot;TDB::allPendingT read db&quot;);
-    t.is ((int) all.size (), 1, &quot;empty db&quot;);
-    t.ok (!tdb.completedT (all), &quot;TDB::completedT read empty db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-    t.ok (!tdb.allCompletedT (all), &quot;TDB::allCompletedT read empty db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-
-    // TODO Modify task.
-
-    // Complete task.
-    t1.setStatus (T::completed);
-    t.ok (tdb.modifyT (t1), &quot;TDB::modifyT (completed) t1&quot;);;
-    t.ok (tdb.pendingT (all), &quot;TDB::pendingT read db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-    t.ok (tdb.allPendingT (all), &quot;TDB::allPendingT read db&quot;);
-    t.is ((int) all.size (), 1, &quot;empty db&quot;);
-    t.ok (!tdb.completedT (all), &quot;TDB::completedT read empty db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-    t.ok (!tdb.allCompletedT (all), &quot;TDB::allCompletedT read empty db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-
-    t.is (tdb.gc (), 1, &quot;TDB::gc&quot;);
-    t.ok (tdb.pendingT (all), &quot;TDB::pendingT read empty db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-    t.ok (tdb.allPendingT (all), &quot;TDB::allPendingT read empty db&quot;);
-    t.is ((int) all.size (), 0, &quot;empty db&quot;);
-    t.ok (tdb.completedT (all), &quot;TDB::completedT read db&quot;);
-    t.is ((int) all.size (), 1, &quot;empty db&quot;);
-    t.ok (tdb.allCompletedT (all), &quot;TDB::allCompletedT read db&quot;);
-    t.is ((int) all.size (), 1, &quot;empty db&quot;);
-
-    // Add a new task.
-    T t2;
-    t2.setId (1);
-    t2.setAttribute (&quot;project&quot;, &quot;p2&quot;);
-    t2.setDescription (&quot;task 2&quot;);
-    t.ok (tdb.addT (t2), &quot;TDB::addT t2&quot;);
-
-    // Delete task.
-    t2.setStatus (T::deleted);
-    t.ok (tdb.modifyT (t2), &quot;TDB::modifyT (deleted) t2&quot;);
-
-    // GC the files.
-    t.is (tdb.gc (), 1, &quot;1 &lt;- TDB::gc&quot;);
+    tdb.location (&quot;.&quot;);
+    tdb.lock ();
+    Task task (&quot;[name:\&quot;value\&quot;]&quot;);
+    tdb.add (task);
+    tdb.unlock ();
+
+    pending.clear ();
+    completed.clear ();
+    get (pending, completed);
+    t.ok (pending.size () == 0, &quot;TDB add -&gt; no commit -&gt; empty&quot;);
+    t.ok (completed.size () == 0, &quot;TDB add -&gt; no commit -&gt; empty&quot;);
+
+    // Add with commit.
+    tdb.lock ();
+    tdb.add (task);
+    tdb.commit ();
+    tdb.unlock ();
+
+    get (pending, completed);
+    t.ok (pending.size () == 1, &quot;TDB add -&gt; commit -&gt; saved&quot;);
+    t.is (pending[0].get (&quot;name&quot;), &quot;value&quot;, &quot;TDB load name=value&quot;);
+    t.is (pending[0].id, 1, &quot;TDB load verification id=1&quot;);
+    t.ok (completed.size () == 0, &quot;TDB add -&gt; commit -&gt; saved&quot;);
+
+    // Update with commit.
+    tdb.lock ();
+    pending.clear ();
+    completed.clear ();
+    tdb.load (all, context.filter);
+    all[0].set (&quot;name&quot;, &quot;value2&quot;);
+    tdb.update (all[0]);
+    tdb.commit ();
+    tdb.unlock ();
+
+    pending.clear ();
+    completed.clear ();
+    get (pending, completed);
+    t.ok (all.size () == 1, &quot;TDB update -&gt; commit -&gt; saved&quot;);
+    t.is (all[0].get (&quot;name&quot;), &quot;value2&quot;, &quot;TDB load name=value2&quot;);
+    t.is (all[0].id, 1, &quot;TDB load verification id=1&quot;);
+
+    // GC.
+    tdb.lock ();
+    all.clear ();
+    tdb.loadPending (all, context.filter);
+    all[0].setStatus (Task::completed);
+    tdb.update (all[0]);
+    Task t2 (&quot;[foo:\&quot;bar\&quot; status:\&quot;pending\&quot;]&quot;);
+    tdb.add (t2);
+    tdb.commit ();
+    tdb.unlock ();
+
+    pending.clear ();
+    completed.clear ();
+    get (pending, completed);
+    t.is (pending.size (), (size_t)2,               &quot;TDB before gc pending #2&quot;);
+    t.is (pending[0].id, 1,                         &quot;TDB before gc pending id 1&quot;);
+    t.is (pending[0].getStatus (), Task::completed, &quot;TDB before gc pending status completed&quot;);
+    t.is (pending[1].id, 2,                         &quot;TDB before gc pending id 2&quot;);
+    t.is (pending[1].getStatus (), Task::pending,   &quot;TDB before gc pending status pending&quot;);
+    t.is (completed.size (), (size_t)0,             &quot;TDB before gc completed 0&quot;);
+
+    tdb.gc ();
+
+    pending.clear ();
+    completed.clear ();
+    get (pending, completed);
+    t.is (pending.size (), (size_t)1,                 &quot;TDB after gc pending #1&quot;);
+    t.is (pending[0].id, 1,                           &quot;TDB after gc pending id 2&quot;);
+    t.is (pending[0].getStatus (), Task::pending,     &quot;TDB after gc pending status pending&quot;);
+    t.is (completed.size (), (size_t)1,               &quot;TDB after gc completed #1&quot;);
+    t.is (completed[0].getStatus (), Task::completed, &quot;TDB after gc completed status completed&quot;);
   }
 
   catch (std::string&amp; error)
@@ -127,6 +154,7 @@ int main (int argc, char** argv)
 
   unlink (&quot;./pending.data&quot;);
   unlink (&quot;./completed.data&quot;);
+  unlink (&quot;./undo.data&quot;);
 
   return 0;
 }</diff>
      <filename>src/tests/tdb.t.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -26,8 +26,10 @@
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;iostream&gt;
 #include &lt;iomanip&gt;
-#include &lt;string&gt;
-#include &lt;task.h&gt;
+#include &lt;string.h&gt;
+#include &lt;main.h&gt;
+#include &lt;util.h&gt;
+#include &lt;text.h&gt;
 #include &quot;test.h&quot;
 
 ///////////////////////////////////////////////////////////////////////////////</diff>
      <filename>src/tests/test.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -25,13 +25,16 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;iostream&gt;
-#include &quot;task.h&quot;
+#include &quot;main.h&quot;
+#include &quot;text.h&quot;
 #include &quot;test.h&quot;
 
+Context context;
+
 ////////////////////////////////////////////////////////////////////////////////
 int main (int argc, char** argv)
 {
-  UnitTest t (94);
+  UnitTest t (109);
 
   // void wrapText (std::vector &lt;std::string&gt;&amp; lines, const std::string&amp; text, const int width)
   std::string text = &quot;This is a test of the line wrapping code.&quot;;
@@ -227,6 +230,27 @@ int main (int argc, char** argv)
   t.is (upperCase (&quot;&quot;),            &quot;&quot;,            &quot;upperCase '' -&gt; ''&quot;);
   t.is (upperCase (&quot;pre01_:POST&quot;), &quot;PRE01_:POST&quot;, &quot;upperCase 'pre01_:POST' -&gt; 'PRE01_:POST'&quot;);
 
+  // bool digitsOnly (const std::string&amp;);
+  t.ok    (digitsOnly (&quot;&quot;),                       &quot;digitsOnly '' -&gt; true&quot;);
+  t.ok    (digitsOnly (&quot;0&quot;),                      &quot;digitsOnly '0' -&gt; true&quot;);
+  t.ok    (digitsOnly (&quot;123&quot;),                    &quot;digitsOnly '123' -&gt; true&quot;);
+  t.notok (digitsOnly (&quot;12fa&quot;),                   &quot;digitsOnly '12fa' -&gt; false&quot;);
+
+  // bool noSpaces (const std::string&amp;);
+  t.ok    (noSpaces (&quot;&quot;),                         &quot;noSpaces '' -&gt; true&quot;);
+  t.ok    (noSpaces (&quot;a&quot;),                        &quot;noSpaces 'a' -&gt; true&quot;);
+  t.ok    (noSpaces (&quot;abc&quot;),                      &quot;noSpaces 'abc' -&gt; true&quot;);
+  t.notok (noSpaces (&quot; &quot;),                        &quot;noSpaces ' ' -&gt; false&quot;);
+  t.notok (noSpaces (&quot;ab cd&quot;),                    &quot;noSpaces 'ab cd' -&gt; false&quot;);
+
+  // bool noVerticalSpace (const std::string&amp;);
+  t.ok    (noVerticalSpace (&quot;&quot;),                  &quot;noVerticalSpace '' -&gt; true&quot;);
+  t.ok    (noVerticalSpace (&quot;a&quot;),                 &quot;noVerticalSpace 'a' -&gt; true&quot;);
+  t.ok    (noVerticalSpace (&quot;abc&quot;),               &quot;noVerticalSpace 'abc' -&gt; true&quot;);
+  t.notok (noVerticalSpace (&quot;a\nb&quot;),              &quot;noVerticalSpace 'a\\nb' -&gt; false&quot;);
+  t.notok (noVerticalSpace (&quot;a\rb&quot;),              &quot;noVerticalSpace 'a\\rb' -&gt; false&quot;);
+  t.notok (noVerticalSpace (&quot;a\fb&quot;),              &quot;noVerticalSpace 'a\\fb' -&gt; false&quot;);
+
   return 0;
 }
 </diff>
      <filename>src/tests/text.t.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -45,18 +45,14 @@ ok (-r 'pending.data', 'pending.data created');
 like ($output, qr/Status\s+Pending\n/, 'Pending');
 
 $output = qx{../task rc:undo.rc do 1; ../task rc:undo.rc info 1};
-ok (! -r 'completed.data', 'completed.data not created');
+ok (-r 'completed.data', 'completed.data created');
 like ($output, qr/Status\s+Completed\n/, 'Completed');
 
-$output = qx{../task rc:undo.rc undo 1; ../task rc:undo.rc info 1};
-ok (! -r 'completed.data', 'completed.data not created');
+$output = qx{echo 'y'|../task rc:undo.rc undo; ../task rc:undo.rc info 1};
+ok (-r 'completed.data', 'completed.data created');
 like ($output, qr/Status\s+Pending\n/, 'Pending');
 
 $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 1 not found');
 like ($output, qr/No matches/, 'No matches');
 
 # Cleanup.
@@ -68,6 +64,10 @@ ok (-r 'completed.data', 'Need to remove completed.data');
 unlink 'completed.data';
 ok (!-r 'completed.data', 'Removed completed.data');
 
+ok (-r 'undo.data', 'Need to remove undo.data');
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'undo.rc';
 ok (!-r 'undo.rc', 'Removed undo.rc');
 </diff>
      <filename>src/tests/undo.t</filename>
    </modified>
    <modified>
      <diff>@@ -28,7 +28,7 @@
 
 use strict;
 use warnings;
-use Test::More tests =&gt; 6;
+use Test::More tests =&gt; 7;
 
 # Create the rc file.
 if (open my $fh, '&gt;', 'utf8.rc')
@@ -73,6 +73,9 @@ like ($output, qr/utf8 in tag/, 'utf8 in tag works');
 unlink 'pending.data';
 ok (!-r 'pending.data', 'Removed pending.data');
 
+unlink 'undo.data';
+ok (!-r 'undo.data', 'Removed undo.data');
+
 unlink 'utf8.rc';
 ok (!-r 'utf8.rc', 'Removed utf8.rc');
 </diff>
      <filename>src/tests/utf8.t</filename>
    </modified>
    <modified>
      <diff>@@ -27,7 +27,12 @@
 #include &lt;iostream&gt;
 #include &lt;vector&gt;
 #include &lt;string&gt;
-#include &quot;task.h&quot;
+#include &lt;ctype.h&gt;
+#include &quot;Context.h&quot;
+#include &quot;util.h&quot;
+#include &quot;text.h&quot;
+
+extern Context context;
 
 static const char* newline = &quot;\n&quot;;
 static const char* noline  = &quot;&quot;;
@@ -292,12 +297,84 @@ std::string upperCase (const std::string&amp; input)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-const char* optionalBlankLine (Config&amp; conf)
+std::string ucFirst (const std::string&amp; input)
+{
+  std::string output = input;
+
+  if (output.length () &gt; 0)
+    output[0] = ::toupper (output[0]);
+
+  return output;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+const char* optionalBlankLine ()
 {
-  if (conf.get (&quot;blanklines&quot;, true) == true)
+  if (context.config.get (&quot;blanklines&quot;, true) == true) // no i18n
     return newline;
 
   return noline;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+void guess (
+  const std::string&amp; type,
+  std::vector&lt;std::string&gt;&amp; options,
+  std::string&amp; candidate)
+{
+  std::vector &lt;std::string&gt; matches;
+  autoComplete (candidate, options, matches);
+  if (1 == matches.size ())
+    candidate = matches[0];
+
+  else if (0 == matches.size ())
+    candidate = &quot;&quot;;
+
+  else
+  {
+    std::string error = &quot;Ambiguous &quot;; // TODO i18n
+    error += type;
+    error += &quot; '&quot;;
+    error += candidate;
+    error += &quot;' - could be either of &quot;; // TODO i18n
+    for (size_t i = 0; i &lt; matches.size (); ++i)
+    {
+      if (i)
+        error += &quot;, &quot;;
+      error += matches[i];
+    }
+
+    throw error;
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool digitsOnly (const std::string&amp; input)
+{
+  for (size_t i = 0; i &lt; input.length (); ++i)
+    if (!::isdigit (input[i]))
+      return false;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool noSpaces (const std::string&amp; input)
+{
+  for (size_t i = 0; i &lt; input.length (); ++i)
+    if (::isspace (input[i]))
+      return false;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool noVerticalSpace (const std::string&amp; input)
+{
+  if (input.find_first_of (&quot;\n\r\f&quot;) != std::string::npos)
+    return false;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////</diff>
      <filename>src/text.cpp</filename>
    </modified>
    <modified>
      <diff>@@ -26,6 +26,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 #include &lt;iostream&gt;
 #include &lt;fstream&gt;
+#include &lt;sstream&gt;
 #include &lt;vector&gt;
 #include &lt;string&gt;
 #include &lt;sys/types.h&gt;
@@ -36,15 +37,20 @@
 #include &lt;string.h&gt;
 #include &lt;pwd.h&gt;
 #include &lt;errno.h&gt;
+
 #include &quot;Date.h&quot;
-#include &quot;Table.h&quot;
-#include &quot;task.h&quot;
+#include &quot;text.h&quot;
+#include &quot;main.h&quot;
+#include &quot;i18n.h&quot;
+#include &quot;util.h&quot;
 #include &quot;../auto.h&quot;
 
+extern Context context;
+
 ////////////////////////////////////////////////////////////////////////////////
 // Uses std::getline, because std::cin eats leading whitespace, and that means
 // that if a newline is entered, std::cin eats it and never returns from the
-// &quot;std::cin &gt;&gt; answer;&quot; line, but it does disply the newline.  This way, with
+// &quot;std::cin &gt;&gt; answer;&quot; line, but it does display the newline.  This way, with
 // std::getline, the newline can be detected, and the prompt re-written.
 bool confirm (const std::string&amp; question)
 {
@@ -52,18 +58,55 @@ bool confirm (const std::string&amp; question)
 
   do
   {
-    std::cout &lt;&lt; question &lt;&lt; &quot; (y/n) &quot;;
+    std::cout &lt;&lt; question
+              &lt;&lt; &quot; &quot;
+              &lt;&lt; context.stringtable.get (CONFIRM_YES_NO, &quot;(y/n)&quot;)
+              &lt;&lt; &quot; &quot;;
+
     std::getline (std::cin, answer);
     answer = lowerCase (trim (answer));
-    if (answer == &quot;\n&quot;) std::cout &lt;&lt; &quot;newline\n&quot;;
   }
-  while (answer != &quot;y&quot;   &amp;&amp;
-         answer != &quot;ye&quot;  &amp;&amp;
-         answer != &quot;yes&quot; &amp;&amp;
-         answer != &quot;n&quot;   &amp;&amp;
-         answer != &quot;no&quot;);
+  while (answer != &quot;y&quot;   &amp;&amp; // TODO i18n
+         answer != &quot;ye&quot;  &amp;&amp; // TODO i18n
+         answer != &quot;yes&quot; &amp;&amp; // TODO i18n
+         answer != &quot;n&quot;   &amp;&amp; // TODO i18n
+         answer != &quot;no&quot;);   // TODO i18n
 
-  return (answer == &quot;y&quot; || answer == &quot;ye&quot; || answer == &quot;yes&quot;) ? true : false;
+  return (answer == &quot;y&quot; || answer == &quot;ye&quot; || answer == &quot;yes&quot;) ? true : false;   // TODO i18n
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 0 = no
+// 1 = yes
+// 2 = all
+int confirm3 (const std::string&amp; question)
+{
+  std::vector &lt;std::string&gt; options;
+  options.push_back (&quot;yes&quot;);
+  options.push_back (&quot;no&quot;);
+  options.push_back (&quot;all&quot;);
+
+  std::string answer;
+  std::vector &lt;std::string&gt; matches;
+
+  do
+  {
+    std::cout &lt;&lt; question
+              &lt;&lt; &quot; (&quot;
+              &lt;&lt; options[0] &lt;&lt; &quot;/&quot;
+              &lt;&lt; options[1] &lt;&lt; &quot;/&quot;
+              &lt;&lt; options[2]
+              &lt;&lt; &quot;) &quot;;
+
+    std::getline (std::cin, answer);
+    answer = trim (answer);
+    autoComplete (answer, options, matches);
+  }
+  while (matches.size () != 1);
+
+       if (matches[0] == &quot;yes&quot;) return 1;
+  else if (matches[0] == &quot;all&quot;) return 2;
+  else                          return 0;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -78,121 +121,102 @@ void delay (float f)
 
 ////////////////////////////////////////////////////////////////////////////////
 // Convert a quantity in seconds to a more readable format.
-// Long version:
-//   0-59        S seconds
-//   60-3599     M minutes, S seconds
-//   3600-86399  H hours, M minutes, S seconds
-//   86400-      D days, H hours, M minutes, S seconds
-// Short version:
-//   0-59        S seconds
-//   60-3599     M minutes, S seconds
-//   3600-86399  H hours, M minutes, S seconds
-//
-void formatTimeDeltaDays (std::string&amp; output, time_t delta)
+std::string formatSeconds (time_t delta)
 {
   char formatted[24];
   float days = (float) delta / 86400.0;
 
   if (days &gt; 365)
-    sprintf (formatted, &quot;%.1f yrs&quot;, (days / 365.2422));
+    sprintf (formatted, &quot;%.1f yrs&quot;, (days / 365.2422));   // TODO i18n
   else if (days &gt; 84)
-    sprintf (formatted, &quot;%1d mth%s&quot;,
+    sprintf (formatted, &quot;%1d mth%s&quot;,   // TODO i18n
                         (int) (days / 30.6),
-                        ((int) (days / 30.6) == 1 ? &quot;&quot; : &quot;s&quot;));
+                        ((int) (days / 30.6) == 1 ? &quot;&quot; : &quot;s&quot;));   // TODO i18n
   else if (days &gt; 13)
-    sprintf (formatted, &quot;%d wk%s&quot;,
+    sprintf (formatted, &quot;%d wk%s&quot;,   // TODO i18n
                         (int) (days / 7.0),
-                        ((int) (days / 7.0) == 1 ? &quot;&quot; : &quot;s&quot;));
-  else if (days &gt; 5.0)
-    sprintf (formatted, &quot;%d day%s&quot;,
-                        (int) days,
-                        ((int) days == 1 ? &quot;&quot; : &quot;s&quot;));
+                        ((int) (days / 7.0) == 1 ? &quot;&quot; : &quot;s&quot;));   // TODO i18n
   else if (days &gt; 1.0)
-    sprintf (formatted, &quot;%.1f days&quot;, days);
+    sprintf (formatted, &quot;%d day%s&quot;,   // TODO i18n
+                        (int) days,
+                        ((int) days == 1 ? &quot;&quot; : &quot;s&quot;));   // TODO i18n
   else if (days * 24 &gt; 1.0)
-    sprintf (formatted, &quot;%d hr%s&quot;,
+    sprintf (formatted, &quot;%d hr%s&quot;,   // TODO i18n
                         (int) (days * 24.0),
-                        ((int) (days * 24.0) == 1 ? &quot;&quot; : &quot;s&quot;));
+                        ((int) (days * 24) == 1 ? &quot;&quot; : &quot;s&quot;));   // TODO i18n
   else if (days * 24 * 60 &gt; 1)
-    sprintf (formatted, &quot;%d min%s&quot;,
+    sprintf (formatted, &quot;%d min%s&quot;,   // TODO i18n
                         (int) (days * 24 * 60),
-                        ((int) (days * 24 * 60) == 1 ? &quot;&quot; : &quot;s&quot;));
+                        ((int) (days * 24 * 60) == 1 ? &quot;&quot; : &quot;s&quot;));   // TODO i18n
   else if (days * 24 * 60 * 60 &gt; 1)
-    sprintf (formatted, &quot;%d sec%s&quot;,
+    sprintf (formatted, &quot;%d sec%s&quot;,   // TODO i18n
                         (int) (days * 24 * 60 * 60),
-                        ((int) (days * 24 * 60 * 60) == 1 ? &quot;&quot; : &quot;s&quot;));
+                        ((int) (days * 24 * 60 * 60) == 1 ? &quot;&quot; : &quot;s&quot;));   // TODO i18n
   else
-    strcpy (formatted, &quot;-&quot;);
+    strcpy (formatted, &quot;-&quot;); // no i18n
 
-  output = formatted;
+  return std::string (formatted);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-std::string formatSeconds (time_t delta)
+// Convert a quantity in seconds to a more readable format.
+std::string formatSecondsCompact (time_t delta)
 {
   char formatted[24];
   float days = (float) delta / 86400.0;
 
-  if (days &gt; 365)
-    sprintf (formatted, &quot;%.1f yrs&quot;, (days / 365.2422));
-  else if (days &gt; 84)
-    sprintf (formatted, &quot;%1d mth%s&quot;,
-                        (int) (days / 30.6),
-                        ((int) (days / 30.6) == 1 ? &quot;&quot; : &quot;s&quot;));
-  else if (days &gt; 13)
-    sprintf (formatted, &quot;%d wk%s&quot;,
-                        (int) (days / 7.0),
-                        ((int) (days / 7.0) == 1 ? &quot;&quot; : &quot;s&quot;));
-  else if (days &gt; 5.0)
-    sprintf (formatted, &quot;%d day%s&quot;,
-                        (int) days,
-                        ((int) days == 1 ? &quot;&quot; : &quot;s&quot;));
-  else if (days &gt; 1.0)
-    sprintf (formatted, &quot;%.1f days&quot;, days);
-  else if (days * 24 &gt; 1.0)
-    sprintf (formatted, &quot;%d hr%s&quot;,
-                        (int) (days * 24.0),
-                        ((int) (days * 24) == 1 ? &quot;&quot; : &quot;s&quot;));
-  else if (days * 24 * 60 &gt; 1)
-    sprintf (formatted, &quot;%d min%s&quot;,
-                        (int) (days * 24 * 60),
-                        ((int) (days * 24 * 60) == 1 ? &quot;&quot; : &quot;s&quot;));
-  else if (days * 24 * 60 * 60 &gt; 1)
-    sprintf (formatted, &quot;%d sec%s&quot;,
-                        (int) (days * 24 * 60 * 60),
-                        ((int) (days * 24 * 60 * 60) == 1 ? &quot;&quot; : &quot;s&quot;));
+  if (days &gt; 365)                sprintf (formatted, &quot;%.1fy&quot;, (days / 365.2422));         // TODO i18n
+  else if (days &gt; 84)            sprintf (formatted, &quot;%1dmo&quot;, (int) (days / 30.6));       // TODO i18n
+  else if (days &gt; 13)            sprintf (formatted, &quot;%dwk&quot;, (int) (days / 7.0));         // TODO i18n
+  else if (days &gt; 1.0)           sprintf (formatted, &quot;%dd&quot;, (int) days);                  // TODO i18n
+  else if (days * 24 &gt; 1.0)      sprintf (formatted, &quot;%dh&quot;, (int) (days * 24.0));         // TODO i18n
+  else if (days * 24 * 60 &gt; 1)   sprintf (formatted, &quot;%dm&quot;, (int) (days * 24 * 60));      // TODO i18n
+  else if (days * 24 * 3600 &gt; 1) sprintf (formatted, &quot;%ds&quot;, (int) (days * 24 * 60 * 60)); // TODO i18n
   else
-    strcpy (formatted, &quot;-&quot;);
+    strcpy (formatted, &quot;-&quot;); // no i18n
 
   return std::string (formatted);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// Convert a quantity in seconds to a more readable format.
+std::string formatBytes (size_t bytes)
+{
+  char formatted[24];
+
+       if (bytes &gt;=  995000000) sprintf (formatted, &quot;%.1f GiB&quot;, (bytes / 1000000000.0));
+  else if (bytes &gt;=     995000) sprintf (formatted, &quot;%.1f MiB&quot;, (bytes /    1000000.0));
+  else if (bytes &gt;=        995) sprintf (formatted, &quot;%.1f KiB&quot;, (bytes /       1000.0));
+  else                          sprintf (formatted, &quot;%d B&quot;, (int)bytes                );
+
+  return commify (formatted);
+}
+
+////////////////////////////////////////////////////////////////////////////////
 int autoComplete (
   const std::string&amp; partial,
   const std::vector&lt;std::string&gt;&amp; list,
   std::vector&lt;std::string&gt;&amp; matches)
 {
-  matches.erase (matches.begin (), matches.end ());
+  matches.clear ();
 
   // Handle trivial case. 
   unsigned int length = partial.length ();
   if (length)
   {
-    for (unsigned int i = 0; i &lt; list.size (); ++i)
+    foreach (item, list)
     {
-      // Special case where there is an exact match.
-      if (partial == list[i])
+      if (partial == *item)
       {
-        matches.erase (matches.begin (), matches.end ());
-        matches.push_back (list[i]);
+        matches.clear ();
+        matches.push_back (*item);
         return 1;
       }
 
       // Maintain a list of partial matches.
-      if (length &lt;= list[i].length () &amp;&amp;
-          ! strncmp (partial.c_str (), list[i].c_str (), length))
-        matches.push_back (list[i]);
+      if (length &lt;= item-&gt;length () &amp;&amp;
+          partial == item-&gt;substr (0, length))
+        matches.push_back (*item);
     }
   }
 
@@ -219,12 +243,10 @@ const std::string uuid ()
 
 ////////////////////////////////////////////////////////////////////////////////
 #else
-#warning &quot;Using custom UUID generator&quot;
-
 #include &lt;stdlib.h&gt;
 static char randomHexDigit ()
 {
-  static char digits[] = &quot;0123456789abcdef&quot;;
+  static char digits[] = &quot;0123456789abcdef&quot;; // no i18n
 #ifdef HAVE_RANDOM
   // random is better than rand.
   return digits[random () % 16];
@@ -281,74 +303,7 @@ const std::string uuid ()
 #endif
 
 ////////////////////////////////////////////////////////////////////////////////
-// Recognize the following constructs, and return the number of days represented
-int convertDuration (const std::string&amp; input)
-{
-  std::string lower_input = lowerCase (input);
-  Date today;
-
-  std::vector &lt;std::string&gt; supported;
-  supported.push_back (&quot;daily&quot;);
-  supported.push_back (&quot;day&quot;);
-  supported.push_back (&quot;weekly&quot;);
-  supported.push_back (&quot;weekdays&quot;);
-  supported.push_back (&quot;sennight&quot;);
-  supported.push_back (&quot;biweekly&quot;);
-  supported.push_back (&quot;fortnight&quot;);
-  supported.push_back (&quot;monthly&quot;);
-  supported.push_back (&quot;bimonthly&quot;);
-  supported.push_back (&quot;quarterly&quot;);
-  supported.push_back (&quot;biannual&quot;);
-  supported.push_back (&quot;biyearly&quot;);
-  supported.push_back (&quot;annual&quot;);
-  supported.push_back (&quot;semiannual&quot;);
-  supported.push_back (&quot;yearly&quot;);
-
-  std::vector &lt;std::string&gt; matches;
-  if (autoComplete (lower_input, supported, matches) == 1)
-  {
-    std::string found = matches[0];
-
-         if (found == &quot;daily&quot;    || found == &quot;day&quot;)       return 1;
-    else if (found == &quot;weekdays&quot;)                         return 1;
-    else if (found == &quot;weekly&quot;   || found == &quot;sennight&quot;)  return 7;
-    else if (found == &quot;biweekly&quot; || found == &quot;fortnight&quot;) return 14;
-    else if (found == &quot;monthly&quot;)                          return 30;
-    else if (found == &quot;bimonthly&quot;)                        return 61;
-    else if (found == &quot;quarterly&quot;)                        return 91;
-    else if (found == &quot;semiannual&quot;)                       return 183;
-    else if (found == &quot;yearly&quot;   || found == &quot;annual&quot;)    return 365;
-    else if (found == &quot;biannual&quot; || found == &quot;biyearly&quot;)  return 730;
-  }
-
-  // Support \d+ d|w|m|q|y
-  else
-  {
-    // Verify all digits followed by d, w, m, q, or y.
-    unsigned int length = lower_input.length ();
-    for (unsigned int i = 0; i &lt; length; ++i)
-    {
-      if (! isdigit (lower_input[i]) &amp;&amp;
-          i == length - 1)
-      {
-        int number = ::atoi (lower_input.substr (0, i).c_str ());
-
-        switch (lower_input[length - 1])
-        {
-        case 'd': return number *   1; break;
-        case 'w': return number *   7; break;
-        case 'm': return number *  30; break;
-        case 'q': return number *  91; break;
-        case 'y': return number * 365; break;
-        }
-      }
-    }
-  }
-
-  return 0; // Error.
-}
-
-////////////////////////////////////////////////////////////////////////////////
+// no i18n
 std::string expandPath (const std::string&amp; in)
 {
   std::string copy = in;
@@ -468,7 +423,122 @@ void spit (const std::string&amp; file, const std::string&amp; contents)
     out.close ();
   }
   else
-    throw std::string (&quot;Could not write file '&quot;) + file + &quot;'&quot;;
+    throw std::string (&quot;Could not write file '&quot;) + file + &quot;'&quot;; // TODO i18n
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void spit (
+  const std::string&amp; file,
+  const std::vector &lt;std::string&gt;&amp; lines,
+  bool addNewlines /* = true */)
+{
+  std::ofstream out (file.c_str ());
+  if (out.good ())
+  {
+    foreach (line, lines)
+    {
+      out &lt;&lt; *line;
+
+      if (addNewlines)
+        out &lt;&lt; &quot;\n&quot;;
+    }
+
+    out.close ();
+  }
+  else
+    throw std::string (&quot;Could not write file '&quot;) + file + &quot;'&quot;; // TODO i18n
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool taskDiff (const Task&amp; before, const Task&amp; after)
+{
+  // Attributes are all there is, so figure the different attribute names
+  // between before and after.
+  std::vector &lt;std::string&gt; beforeAtts;
+  foreach (att, before)
+    beforeAtts.push_back (att-&gt;first);
+
+  std::vector &lt;std::string&gt; afterAtts;
+  foreach (att, after)
+    afterAtts.push_back (att-&gt;first);
+
+  std::vector &lt;std::string&gt; beforeOnly;
+  std::vector &lt;std::string&gt; afterOnly;
+  listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly);
+
+  if (beforeOnly.size () ||
+      afterOnly.size ())
+    return true;
+
+  foreach (name, beforeAtts)
+    if (*name              != &quot;uuid&quot; &amp;&amp;
+        before.get (*name) != after.get (*name))
+      return true;
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string taskDifferences (const Task&amp; before, const Task&amp; after)
+{
+  // Attributes are all there is, so figure the different attribute names
+  // between before and after.
+  std::vector &lt;std::string&gt; beforeAtts;
+  foreach (att, before)
+    beforeAtts.push_back (att-&gt;first);
+
+  std::vector &lt;std::string&gt; afterAtts;
+  foreach (att, after)
+    afterAtts.push_back (att-&gt;first);
+
+  std::vector &lt;std::string&gt; beforeOnly;
+  std::vector &lt;std::string&gt; afterOnly;
+  listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly);
+
+  // Now start generating a description of the differences.
+  std::stringstream out;
+  foreach (name, beforeOnly)
+    out &lt;&lt; &quot;  - &quot;
+        &lt;&lt; *name
+        &lt;&lt; &quot; was deleted\n&quot;;
+
+  foreach (name, afterOnly)
+    out &lt;&lt; &quot;  - &quot;
+        &lt;&lt; *name
+        &lt;&lt; &quot; was set to '&quot;
+        &lt;&lt; renderAttribute (*name, after.get (*name))
+        &lt;&lt; &quot;'\n&quot;;
+
+  foreach (name, beforeAtts)
+    if (*name              != &quot;uuid&quot; &amp;&amp;
+        after.get (*name)  != &quot;&quot;     &amp;&amp;
+        before.get (*name) != after.get (*name))
+      out &lt;&lt; &quot;  - &quot;
+          &lt;&lt; *name
+          &lt;&lt; &quot; was changed from '&quot;
+          &lt;&lt; renderAttribute (*name, before.get (*name))
+          &lt;&lt; &quot;' to '&quot;
+          &lt;&lt; renderAttribute (*name, after.get (*name))
+          &lt;&lt; &quot;'\n&quot;;
+
+  // Shouldn't just say nothing.
+  if (out.str ().length () == 0)
+    out &lt;&lt; &quot;  - No changes were made\n&quot;;
+
+  return out.str ();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+std::string renderAttribute (const std::string&amp; name, const std::string&amp; value)
+{
+  Att a;
+  if (a.type (name) == &quot;date&quot;)
+  {
+    Date d ((time_t)::atoi (value.c_str ()));
+    return d.toString (context.config.get (&quot;dateformat&quot;, &quot;m/d/Y&quot;));
+  }
+
+  return value;
 }
 
 ////////////////////////////////////////////////////////////////////////////////</diff>
      <filename>src/util.cpp</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>DEVELOPERS</filename>
    </removed>
    <removed>
      <filename>Makefile.in</filename>
    </removed>
    <removed>
      <filename>binary/COPYING.txt</filename>
    </removed>
    <removed>
      <filename>binary/README.txt</filename>
    </removed>
    <removed>
      <filename>checklist.txt</filename>
    </removed>
    <removed>
      <filename>doc/man1/task.1</filename>
    </removed>
    <removed>
      <filename>doc/man5/taskrc.5</filename>
    </removed>
    <removed>
      <filename>grammar.bnf</filename>
    </removed>
    <removed>
      <filename>html/30second.html</filename>
    </removed>
    <removed>
      <filename>html/advanced.html</filename>
    </removed>
    <removed>
      <filename>html/color.html</filename>
    </removed>
    <removed>
      <filename>html/config.html</filename>
    </removed>
    <removed>
      <filename>html/custom.html</filename>
    </removed>
    <removed>
      <filename>html/date.html</filename>
    </removed>
    <removed>
      <filename>html/faq.html</filename>
    </removed>
    <removed>
      <filename>html/filter.html</filename>
    </removed>
    <removed>
      <filename>html/git.html</filename>
    </removed>
    <removed>
      <filename>html/images/color.png</filename>
    </removed>
    <removed>
      <filename>html/import.html</filename>
    </removed>
    <removed>
      <filename>html/links.html</filename>
    </removed>
    <removed>
      <filename>html/recur.html</filename>
    </removed>
    <removed>
      <filename>html/sequence.html</filename>
    </removed>
    <removed>
      <filename>html/setup.html</filename>
    </removed>
    <removed>
      <filename>html/shadow.html</filename>
    </removed>
    <removed>
      <filename>html/shell.html</filename>
    </removed>
    <removed>
      <filename>html/simple.html</filename>
    </removed>
    <removed>
      <filename>html/tab_completion.html</filename>
    </removed>
    <removed>
      <filename>html/task.css</filename>
    </removed>
    <removed>
      <filename>html/task.html</filename>
    </removed>
    <removed>
      <filename>html/versions.html</filename>
    </removed>
    <removed>
      <filename>script.txt</filename>
    </removed>
    <removed>
      <filename>src/Makefile.in</filename>
    </removed>
    <removed>
      <filename>src/T.cpp</filename>
    </removed>
    <removed>
      <filename>src/T.h</filename>
    </removed>
    <removed>
      <filename>src/parse.cpp</filename>
    </removed>
    <removed>
      <filename>src/task.cpp</filename>
    </removed>
    <removed>
      <filename>src/task.h</filename>
    </removed>
    <removed>
      <filename>src/tests/parse.t.cpp</filename>
    </removed>
    <removed>
      <filename>task.pmdoc/01task-contents.xml</filename>
    </removed>
    <removed>
      <filename>task.pmdoc/01task.xml</filename>
    </removed>
    <removed>
      <filename>task.pmdoc/index.xml</filename>
    </removed>
    <removed>
      <filename>task_completion.sh</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>1422a15cbc470cff590bf06daad20d01fe1b05ef</id>
    </parent>
    <parent>
      <id>14977ef317bd004dae2f2c313e806af9f2a2140c</id>
    </parent>
  </parents>
  <author>
    <name>Paul Beckingham</name>
    <email>paul@beckingham.net</email>
  </author>
  <url>http://github.com/pbeckingham/task/commit/90d53245c3c57422ff5003eefe04fea2c24bbd02</url>
  <id>90d53245c3c57422ff5003eefe04fea2c24bbd02</id>
  <committed-date>2009-07-21T16:15:54-07:00</committed-date>
  <authored-date>2009-07-21T16:15:54-07:00</authored-date>
  <message>Merge branch '1.8.0'

Conflicts:
	.gitignore
	AUTHORS
	ChangeLog
	DEVELOPERS
	Makefile.am
	NEWS
	README
	configure.ac
	doc/man/task.1
	doc/man/taskrc.5
	src/T.cpp
	src/T.h
	src/TDB.cpp
	src/TDB.h
	src/command.cpp
	src/edit.cpp
	src/import.cpp
	src/parse.cpp
	src/report.cpp
	src/rules.cpp
	src/task.cpp
	src/task.h
	task_completion.sh</message>
  <tree>114a6ecbfe18251f366f7c651e0a4eda0d1e79ec</tree>
  <committer>
    <name>Paul Beckingham</name>
    <email>paul@beckingham.net</email>
  </committer>
</commit>
