Enhancements - T2::legacyParse

- T2 can now parse all supported legacy formats (ff2, ff3) as well as
  ff4.
- Added tag and attribute support to T2.
- Added T2 unit tests for all formats.
This commit is contained in:
Paul Beckingham 2009-06-07 22:17:11 -04:00
parent 72e3f76ed9
commit 9d48faa759
9 changed files with 246 additions and 56 deletions

View file

@ -25,6 +25,7 @@
112 Cannot substitute an empty string
113 Unrecognized character(s) at end of substitution
114 Malformed substitution
115 Tags are not permitted to contain commas
# 2xx Commands - must be sequential
200 active

View file

@ -377,6 +377,10 @@ std::cout << "# parse tag removal '" << *arg << "'" << std::endl;
if (foundSequence)
foundSomethingAfterSequence = true;
if (arg->find (',') != std::string::npos)
throw stringtable.get (TAGS_NO_COMMA,
"Tags are not permitted to contain commas.");
tagRemovals.push_back (arg->substr (1, std::string::npos));
}

View file

@ -82,6 +82,8 @@ std::string Record::composeF4 ()
//
void Record::parse (const std::string& input)
{
clear ();
Nibbler n (input);
std::string line;
if (n.skip ('[') &&

View file

@ -629,7 +629,7 @@ int T::determineVersion (const std::string& line)
// Version 4 looks like:
//
// [name:"value" ...]
// [name:"value" ...]\n
//
// Scan for [, ] and :".
if (line[0] == '[' &&

View file

@ -26,7 +26,6 @@
////////////////////////////////////////////////////////////////////////////////
#include <sstream>
#include <string>
#include <algorithm>
#include "Nibbler.h"
#include "T2.h"
@ -47,7 +46,7 @@ T2::T2 (const std::string& input)
{
try
{
parse (input);
Record::parse (input);
}
catch (std::string& e)
@ -74,8 +73,57 @@ T2::~T2 ()
{
}
////////////////////////////////////////////////////////////////////////////////
T2::status T2::textToStatus (const std::string& input)
{
if (input == "pending") return pending;
else if (input == "completed") return completed;
else if (input == "deleted") return deleted;
else if (input == "recurring") return recurring;
return pending;
}
////////////////////////////////////////////////////////////////////////////////
std::string T2::statusToText (T2::status s)
{
if (s == pending) return "pending";
else if (s == completed) return "completed";
else if (s == deleted) return "deleted";
else if (s == recurring) return "recurring";
return "pending";
}
////////////////////////////////////////////////////////////////////////////////
T2::status T2::getStatus ()
{
return textToStatus (get ("status"));
}
////////////////////////////////////////////////////////////////////////////////
void T2::setSatus (T2::status status)
{
set ("status", statusToText (status));
}
////////////////////////////////////////////////////////////////////////////////
void T2::parse (const std::string& line)
{
try
{
Record::parse (line);
}
catch (std::string& e)
{
legacyParse (line);
}
}
////////////////////////////////////////////////////////////////////////////////
// Support FF2, FF3.
// Thankfully FF1 is no longer supported.
void T2::legacyParse (const std::string& line)
{
switch (determineVersion (line))
@ -88,17 +136,18 @@ void T2::legacyParse (const std::string& line)
// File format version 2, from 2008.1.1 - 2009.3.23
case 2:
/*
{
if (line.length () > 46) // ^.{36} . \[\] \[\] \n
{
mUUID = line.substr (0, 36);
set ("uuid", line.substr (0, 36));
mStatus = line[37] == '+' ? completed
T2::status status = line[37] == '+' ? completed
: line[37] == 'X' ? deleted
: line[37] == 'r' ? recurring
: pending;
set ("status", statusToText (status));
size_t openTagBracket = line.find ("[");
size_t closeTagBracket = line.find ("]", openTagBracket);
if (openTagBracket != std::string::npos &&
@ -111,8 +160,9 @@ void T2::legacyParse (const std::string& line)
{
std::string tags = line.substr (
openTagBracket + 1, closeTagBracket - openTagBracket - 1);
std::vector <std::string> rawTags;
split (mTags, tags, ' ');
std::vector <std::string> tagSet;
split (tagSet, tags, ' ');
addTags (tagSet);
std::string attributes = line.substr (
openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
@ -123,10 +173,10 @@ void T2::legacyParse (const std::string& line)
std::vector <std::string> pair;
split (pair, pairs[i], ':');
if (pair.size () == 2)
mAttributes[pair[0]] = pair[1];
set (pair[0], pair[1]);
}
mDescription = line.substr (closeAttrBracket + 2, std::string::npos);
set ("description", line.substr (closeAttrBracket + 2, std::string::npos));
}
else
throw std::string ("Missing attribute brackets");
@ -137,24 +187,24 @@ void T2::legacyParse (const std::string& line)
else
throw std::string ("Line too short");
mAnnotations.clear ();
removeAnnotations ();
}
*/
break;
// File format version 3, from 2009.3.23
case 3:
/*
{
if (line.length () > 49) // ^.{36} . \[\] \[\] \[\] \n
{
mUUID = line.substr (0, 36);
set ("uuid", line.substr (0, 36));
mStatus = line[37] == '+' ? completed
T2::status status = line[37] == '+' ? completed
: line[37] == 'X' ? deleted
: line[37] == 'r' ? recurring
: pending;
set ("status", statusToText (status));
size_t openTagBracket = line.find ("[");
size_t closeTagBracket = line.find ("]", openTagBracket);
if (openTagBracket != std::string::npos &&
@ -172,8 +222,9 @@ void T2::legacyParse (const std::string& line)
{
std::string tags = line.substr (
openTagBracket + 1, closeTagBracket - openTagBracket - 1);
std::vector <std::string> rawTags;
split (mTags, tags, ' ');
std::vector <std::string> tagSet;
split (tagSet, tags, ' ');
addTags (tagSet);
std::string attributes = line.substr (
openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
@ -185,9 +236,10 @@ void T2::legacyParse (const std::string& line)
std::vector <std::string> pair;
split (pair, pairs[i], ':');
if (pair.size () == 2)
mAttributes[pair[0]] = pair[1];
set (pair[0], pair[1]);
}
/*
// Extract and split the annotations, which are of the form:
// 1234:"..." 5678:"..."
std::string annotations = line.substr (
@ -225,8 +277,9 @@ void T2::legacyParse (const std::string& line)
mAnnotations[::atoi (name.c_str ())] = value;
}
}
*/
mDescription = line.substr (closeAnnoBracket + 2, std::string::npos);
set ("description", line.substr (closeAnnoBracket + 2, std::string::npos));
}
else
throw std::string ("Missing annotation brackets.");
@ -240,7 +293,6 @@ void T2::legacyParse (const std::string& line)
else
throw std::string ("Line too short.");
}
*/
break;
default:
@ -271,10 +323,7 @@ void T2::getAnnotations (std::vector <Att>& annotations) const
void T2::setAnnotations (const std::vector <Att>& annotations)
{
// Erase old annotations.
Record::iterator i;
for (i = this->begin (); i != this->end (); ++i)
if (i->first.substr (0, 11) == "annotation_")
this->erase (i);
removeAnnotations ();
std::vector <Att>::const_iterator ci;
for (ci = annotations.begin (); ci != annotations.end (); ++ci)
@ -293,6 +342,37 @@ void T2::addAnnotation (const std::string& description)
(*this)[s.str ()] = Att (s.str (), description);
}
////////////////////////////////////////////////////////////////////////////////
void T2::removeAnnotations ()
{
// Erase old annotations.
Record::iterator i;
for (i = this->begin (); i != this->end (); ++i)
if (i->first.substr (0, 11) == "annotation_")
this->erase (i);
}
////////////////////////////////////////////////////////////////////////////////
int T2::getTagCount ()
{
std::vector <std::string> tags;
split (tags, get ("tags"), ',');
return (int) tags.size ();
}
////////////////////////////////////////////////////////////////////////////////
bool T2::hasTag (const std::string& tag)
{
std::vector <std::string> tags;
split (tags, get ("tags"), ',');
if (std::find (tags.begin (), tags.end (), tag) != tags.end ())
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
void T2::addTag (const std::string& tag)
{
@ -308,6 +388,22 @@ void T2::addTag (const std::string& tag)
}
}
////////////////////////////////////////////////////////////////////////////////
void T2::addTags (const std::vector <std::string>& tags)
{
remove ("tags");
std::vector <std::string>::const_iterator it;
for (it = tags.begin (); it != tags.end (); ++it)
addTag (*it);
}
////////////////////////////////////////////////////////////////////////////////
void T2::getTags (std::vector<std::string>& tags)
{
split (tags, get ("tags"), ',');
}
////////////////////////////////////////////////////////////////////////////////
void T2::removeTag (const std::string& tag)
{

View file

@ -40,6 +40,7 @@ public:
T2& operator= (const T2&); // Assignment operator
~T2 (); // Destructor
void parse (const std::string&);
std::string composeCSV ();
// Status values.
@ -53,28 +54,23 @@ public:
int id () const { return sequence.size () ? sequence[0] : 0; }
void id (int anotherId) { sequence.push_back (anotherId); }
/*
status getStatus () const { return mStatus; }
void setStatus (status s) { mStatus = s; }
static status textToStatus (const std::string&);
static std::string statusToText (status);
bool hasTag (const std::string&) const;
void getRemoveTags (std::vector<std::string>&); // SPECIAL
void addRemoveTag (const std::string&); // SPECIAL
int getTagCount () const;
void getTags (std::vector<std::string>&) const;
*/
status getStatus ();
void setSatus (status);
int getTagCount ();
bool hasTag (const std::string&);
void addTag (const std::string&);
/*
void addTags (const std::vector <std::string>&);
*/
void getTags (std::vector<std::string>&);
void removeTag (const std::string&);
/*
void removeTags ();
*/
void getAnnotations (std::vector <Att>&) const;
void setAnnotations (const std::vector <Att>&);
void addAnnotation (const std::string&);
void removeAnnotations ();
bool valid () const;

View file

@ -188,6 +188,9 @@ int TDB2::load (std::vector <T2>& tasks, Filter& filter)
++line_number;
}
// TODO If the filter contains Status:x where x is not deleted or
// completed, then this can be skipped.
line_number = 1;
file = location->path + "/completed.data";
while (fgets (line, T_LINE_MAX, location->completed))
@ -243,6 +246,9 @@ void TDB2::update (T2& before, T2& after)
// TODO writes all, including comments
int TDB2::commit ()
{
// TODO Two passes: first the pending file.
// then the compelted file.
throw std::string ("unimplemented TDB2::commit");
}

View file

@ -61,6 +61,8 @@
#define SUBST_BAD_CHARS 113
#define SUBST_MALFORMED 114
#define TAGS_NO_COMMA 115
// 2xx Commands
#define CMD_ACTIVE 200
#define CMD_ADD 201

View file

@ -32,27 +32,110 @@ Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest test (3);
UnitTest test (34);
T2 t;
t.addTag ("tag1");
t.addTag ("tag2");
test.is (t.composeF4 (), "[tags:\"tag1&comma;tag2\" uuid:\"...\"]", "T2::addTag");
test.is ((int)T2::textToStatus ("pending"), (int)T2::pending, "textToStatus pending");
test.is ((int)T2::textToStatus ("completed"), (int)T2::completed, "textToStatus completed");
test.is ((int)T2::textToStatus ("deleted"), (int)T2::deleted, "textToStatus deleted");
test.is ((int)T2::textToStatus ("recurring"), (int)T2::recurring, "textToStatus recurring");
T2 t2 (t.composeF4 ());
test.is (t2.composeF4 (), "[tags:\"tag1&comma;tag2\" uuid:\"...\"]", "T2::composeF4 -> parse round trip");
test.is (T2::statusToText (T2::pending), "pending", "statusToText pending");
test.is (T2::statusToText (T2::completed), "completed", "statusToText completed");
test.is (T2::statusToText (T2::deleted), "deleted", "statusToText deleted");
test.is (T2::statusToText (T2::recurring), "recurring", "statusToText recurring");
// Round-trip testing.
T2 t3;
std::string before = t3.composeF4 ();
/*
t3 (t3.composeF4 ());
t3 (t3.composeF4 ());
t3 (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, "T2::composeF4 -> parse round trip 4 iterations");
// Legacy Format 1
// [tags] [attributes] description\n
// X [tags] [attributes] description\n
std::string sample = "[tag1 tag2] [att1:value1 att2:value2] Description";
sample = "X "
"[one two] "
"[att1:value1 att2:value2] "
"Description";
bool good = true;
try { T2 ff1 (sample); } catch (...) { good = false; }
test.notok (good, "Support for ff1 removed");
// Legacy Format 2
// uuid status [tags] [attributes] description\n
sample = "00000000-0000-0000-0000-000000000000 "
"- "
"[tag1 tag2] "
"[att1:value1 att2:value2] "
"Description";
T2 ff2 (sample);
std::string value = ff2.get ("uuid");
test.is (value, "00000000-0000-0000-0000-000000000000", "ff2 uuid");
value = ff2.get ("status");
test.is (value, "pending", "ff2 status");
test.ok (ff2.hasTag ("tag1"), "ff2 tag1");
test.ok (ff2.hasTag ("tag2"), "ff2 tag2");
test.is (ff2.getTagCount (), 2, "ff2 # tags");
value = ff2.get ("att1");
test.is (value, "value1", "ff2 att1");
value = ff2.get ("att2");
test.is (value, "value2", "ff2 att2");
value = ff2.get ("description");
test.is (value, "Description", "ff2 description");
// Legacy Format 3
// uuid status [tags] [attributes] [annotations] description\n
sample = "00000000-0000-0000-0000-000000000000 "
"- "
"[tag1 tag2] "
"[att1:value1 att2:value2] "
"[123:ann1 456:ann2] Description";
T2 ff3 (sample);
value = ff2.get ("uuid");
test.is (value, "00000000-0000-0000-0000-000000000000", "ff3 uuid");
value = ff2.get ("status");
test.is (value, "pending", "ff3 status");
test.ok (ff2.hasTag ("tag1"), "ff3 tag1");
test.ok (ff2.hasTag ("tag2"), "ff3 tag2");
test.is (ff2.getTagCount (), 2, "ff3 # tags");
value = ff3.get ("att1");
test.is (value, "value1", "ff3 att1");
value = ff3.get ("att2");
test.is (value, "value2", "ff3 att2");
value = ff3.get ("description");
test.is (value, "Description", "ff3 description");
// Current Format 4
// [name:"value" ...]\n
sample = "["
"uuid:\"00000000-0000-0000-0000-000000000000\" "
"status:\"P\" "
"tags:\"tag1&commaltag2\" "
"att1:\"value1\" "
"att2:\"value2\" "
"description:\"Description\""
"]";
T2 ff4 (sample);
value = ff2.get ("uuid");
test.is (value, "00000000-0000-0000-0000-000000000000", "ff4 uuid");
value = ff2.get ("status");
test.is (value, "pending", "ff4 status");
test.ok (ff2.hasTag ("tag1"), "ff4 tag1");
test.ok (ff2.hasTag ("tag2"), "ff4 tag2");
test.is (ff2.getTagCount (), 2, "ff4 # tags");
value = ff4.get ("att1");
test.is (value, "value1", "ff4 att1");
value = ff4.get ("att2");
test.is (value, "value2", "ff4 att2");
value = ff4.get ("description");
test.is (value, "Description", "ff4 description");
/*
T2::composeCSV