Journal Feature

- Added change log display to the 'info' command, controlled by the
  'journal.info' configuration setting.
This commit is contained in:
Paul Beckingham 2010-11-27 16:38:36 -05:00
parent 31cf470cc8
commit 4c3354fa50
8 changed files with 219 additions and 16 deletions

View file

@ -2,11 +2,13 @@
------ current release ---------------------------
1.9.4 ()
+ Added burndown charts - burndown.daily, burndown.weekly, burndown.monthly,
that use color.burndown.pending, color.burndown.started and
color.burndown.done colors.
+ Added highlighting for the show command that indicates which values differ
+ Added burndown charts - 'burndown.daily', 'burndown.weekly',
'burndown.monthly', that use 'color.burndown.pending', 'color.burndown.started'
and 'color.burndown.done' colors.
+ Added highlighting for the 'show' command that indicates which values differ
from the defaults.
+ Added change log display to the 'info' command, controlled by the
'journal.info' configuration setting.
+ Added feature #158, regular expression support for filters and substitutions.
+ Added feature #247, providing infinite width reports when redirecting output
to a file, by setting defaultwidth to 0.

3
NEWS
View file

@ -7,6 +7,7 @@ New Features in taskwarrior 1.9.4
- Regular expression support in filters and substitutions.
- Added highlighting for the show command that indicates which values differ
from the defaults.
- Added change log display to the 'info' command.
Please refer to the ChangeLog file for full details. There are too many to
list here.
@ -29,6 +30,8 @@ New configuration options in taskwarrior 1.9.4
- regex=on enables regular expression searches for filters (task list a...e
matches 'above') and substitutions (task <id> /a...e/over/ changes 'above'
to 'over'). Default is off, as this is an advanced feature.
- journal.info controls whether a change log for each task is displayed by
the 'info' command.
Newly deprecated features in taskwarrior 1.9.4

View file

@ -474,7 +474,7 @@ turned off by setting the variable to none. The default value is "none".
.B journal.time=no
May be yes or no, and determines whether the 'start' and 'stop' commands should
record an annotation when being executed. The default value is "no". The text of
the corresponding annotations is controlled by
the corresponding annotations is controlled by:
.TP journal.time.start.annotation=Started task
The text of the annotation that is recorded when executing the start command and
@ -484,6 +484,10 @@ having set journal.time.
The text of the annotation that is recorded when executing the stop command and
having set journal.time.
.TP journal.info=on
When enabled, this setting causes a change log of each task to be displayed by
the 'info' command. Default value is "on".
.SS Holidays
Holidays are entered either directly in the .taskrc file or via an include file
that is specified in .taskrc. For each holiday the name and the date is

View file

@ -103,6 +103,7 @@ std::string Config::defaults =
"journal.time=no # Record start/stop commands as annotation\n"
"journal.time.start.annotation=Started task # Annotation description for the start journal entry\n"
"journal.time.stop.annotation=Stopped task # Annotation description for the stop journal entry\n"
"journal.info=on # Display task journal with info command\n"
"\n"
"# Dependency controls\n"
"dependency.reminder=on # Nags on dependency chain violations\n"

View file

@ -907,7 +907,7 @@ int handleShow (std::string& outs)
"default.command default.priority default.project defaultwidth due "
"dependency.confirmation dependency.reminder locale displayweeknumber "
"export.ical.class echo.command fontunderline locking monthsperline nag "
"next journal.time journal.time.start.annotation "
"next journal.time journal.time.start.annotation journal.info "
"journal.time.stop.annotation project shadow.command shadow.file "
"shadow.notify weekstart editor import.synonym.id import.synonym.uuid "
"complete.all.projects complete.all.tags search.case.sensitive hooks "

View file

@ -37,15 +37,15 @@
#include <pwd.h>
#include <time.h>
#include "Context.h"
#include "Directory.h"
#include "File.h"
#include "Date.h"
#include "Duration.h"
#include "Table.h"
#include "text.h"
#include "util.h"
#include "main.h"
#include <Context.h>
#include <Directory.h>
#include <File.h>
#include <Date.h>
#include <Duration.h>
#include <Table.h>
#include <text.h>
#include <util.h>
#include <main.h>
#ifdef HAVE_LIBNCURSES
#include <ncurses.h>
@ -396,6 +396,15 @@ int handleInfo (std::string& outs)
// Filter sequence.
context.filter.applySequence (tasks, context.sequence);
// Read the undo file.
std::vector <std::string> undo;
if (context.config.getBoolean ("journal.info"))
{
Directory location (context.config.get ("data.location"));
std::string undoFile = location.data + "/undo.data";
File::read (undoFile, undo);
}
// Find the task.
std::stringstream out;
foreach (task, tasks)
@ -614,7 +623,7 @@ int handleInfo (std::string& outs)
// uuid
row = table.addRow ();
table.addCell (row, 0, "UUID");
value = task->get ("uuid");
std::string uuid = value = task->get ("uuid");
context.hooks.trigger ("format-uuid", "uuid", value);
table.addCell (row, 1, value);
@ -659,17 +668,82 @@ int handleInfo (std::string& outs)
//table.addCell (row, 0, "Urgency");
//table.addCell (row, 1, task->urgency ());
Table journal;
// If an alternating row color is specified, notify the table.
if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor"))
{
Color alternate (context.config.get ("color.alternate"));
if (alternate.nontrivial ())
{
table.setTableAlternateColor (alternate);
journal.setTableAlternateColor (alternate);
}
}
out << optionalBlankLine ()
<< table.render ()
<< "\n";
journal.setTableWidth (context.getWidth ());
journal.setDateFormat (context.config.get ("dateformat"));
journal.addColumn ("Date");
journal.addColumn ("Modification");
if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) &&
context.config.getBoolean ("fontunderline"))
{
journal.setColumnUnderline (0);
journal.setColumnUnderline (1);
}
else
journal.setTableDashedUnderline ();
journal.setColumnWidth (0, Table::minimum);
journal.setColumnWidth (1, Table::flexible);
journal.setColumnJustification (0, Table::left);
journal.setColumnJustification (1, Table::left);
if (context.config.getBoolean ("journal.info") &&
undo.size () > 3)
{
// Scan the undo data for entries matching this task.
std::string when;
std::string previous;
std::string current;
unsigned int i = 0;
while (i < undo.size ())
{
when = undo[i++];
previous = "";
if (undo[i].substr (0, 3) == "old")
previous = undo[i++];
current = undo[i++];
i++; // Separator
if (current.find ("uuid:\"" + uuid) != std::string::npos)
{
if (previous != "")
{
int row = journal.addRow ();
Date timestamp (atoi (when.substr (5).c_str ()));
journal.addCell (row, 0, timestamp.toString (context.config.get ("dateformat")));
Task before (previous.substr (4));
Task after (current.substr (4));
journal.addCell (row, 1, taskInfoDifferences (before, after));
}
}
}
if (journal.rowCount () > 0)
out << journal.render ()
<< "\n";
}
}
if (! tasks.size ())

View file

@ -435,6 +435,124 @@ std::string taskDifferences (const Task& before, const Task& after)
return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
std::string taskInfoDifferences (const Task& before, const Task& after)
{
// Attributes are all there is, so figure the different attribute names
// between before and after.
std::vector <std::string> beforeAtts;
foreach (att, before)
beforeAtts.push_back (att->first);
std::vector <std::string> afterAtts;
foreach (att, after)
afterAtts.push_back (att->first);
std::vector <std::string> beforeOnly;
std::vector <std::string> afterOnly;
listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly);
// Now start generating a description of the differences.
std::stringstream out;
foreach (name, beforeOnly)
{
if (*name == "depends")
{
std::vector <int> deps_before;
before.getDependencies (deps_before);
std::string from;
join (from, ",", deps_before);
out << "dependencies '"
<< from
<< "' deleted\n";
}
else if (name->substr (0, 11) == "annotation_")
{
out << "annotation '"
<< before.get (*name)
<< "' deleted";
}
else
{
out << *name
<< " deleted\n";
}
}
foreach (name, afterOnly)
{
if (*name == "depends")
{
std::vector <int> deps_after;
after.getDependencies (deps_after);
std::string to;
join (to, ",", deps_after);
out << *name
<< " set to '"
<< to
<< "'\n";
}
else if (name->substr (0, 11) == "annotation_")
{
out << "annotation added '"
<< after.get (*name)
<< "'";
}
else
out << *name
<< " set to '"
<< renderAttribute (*name, after.get (*name))
<< "'\n";
}
foreach (name, beforeAtts)
if (*name != "uuid" &&
before.get (*name) != after.get (*name))
{
if (*name == "depends")
{
std::vector <int> deps_before;
before.getDependencies (deps_before);
std::string from;
join (from, ",", deps_before);
std::vector <int> deps_after;
after.getDependencies (deps_after);
std::string to;
join (to, ",", deps_after);
out << *name
<< " changed from '"
<< from
<< "' to '"
<< to
<< "'\n";
}
else if (name->substr (0, 11) == "annotation_")
{
out << "annotation '"
<< after.get (*name)
<< "'";
}
else
out << *name
<< " changed from '"
<< renderAttribute (*name, before.get (*name))
<< "' to '"
<< renderAttribute (*name, after.get (*name))
<< "'\n";
}
// Shouldn't just say nothing.
if (out.str ().length () == 0)
out << "No changes made.\n";
return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
std::string renderAttribute (const std::string& name, const std::string& value)
{

View file

@ -70,6 +70,7 @@ const std::string uuid ();
bool taskDiff (const Task&, const Task&);
std::string taskDifferences (const Task&, const Task&);
std::string taskInfoDifferences (const Task&, const Task&);
std::string renderAttribute (const std::string&, const std::string&);
#endif