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

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