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 112 Cannot substitute an empty string
113 Unrecognized character(s) at end of substitution 113 Unrecognized character(s) at end of substitution
114 Malformed substitution 114 Malformed substitution
115 Tags are not permitted to contain commas
# 2xx Commands - must be sequential # 2xx Commands - must be sequential
200 active 200 active

View file

@ -377,6 +377,10 @@ std::cout << "# parse tag removal '" << *arg << "'" << std::endl;
if (foundSequence) if (foundSequence)
foundSomethingAfterSequence = true; 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)); 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) void Record::parse (const std::string& input)
{ {
clear ();
Nibbler n (input); Nibbler n (input);
std::string line; std::string line;
if (n.skip ('[') && if (n.skip ('[') &&

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -32,27 +32,110 @@ Context context;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv) int main (int argc, char** argv)
{ {
UnitTest test (3); UnitTest test (34);
T2 t; test.is ((int)T2::textToStatus ("pending"), (int)T2::pending, "textToStatus pending");
t.addTag ("tag1"); test.is ((int)T2::textToStatus ("completed"), (int)T2::completed, "textToStatus completed");
t.addTag ("tag2"); test.is ((int)T2::textToStatus ("deleted"), (int)T2::deleted, "textToStatus deleted");
test.is (t.composeF4 (), "[tags:\"tag1&comma;tag2\" uuid:\"...\"]", "T2::addTag"); test.is ((int)T2::textToStatus ("recurring"), (int)T2::recurring, "textToStatus recurring");
T2 t2 (t.composeF4 ()); test.is (T2::statusToText (T2::pending), "pending", "statusToText pending");
test.is (t2.composeF4 (), "[tags:\"tag1&comma;tag2\" uuid:\"...\"]", "T2::composeF4 -> parse round trip"); 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. // Round-trip testing.
T2 t3; T2 t3;
std::string before = t3.composeF4 (); std::string before = t3.composeF4 ();
/* t3.parse (before);
t3 (t3.composeF4 ());
t3 (t3.composeF4 ());
t3 (t3.composeF4 ());
*/
std::string after = t3.composeF4 (); 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"); 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 T2::composeCSV