From 5f4563af2f12feb24374a5bf41d356881db11ac0 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 29 Mar 2009 15:34:35 -0400 Subject: [PATCH] File Import - Implemented import from task 1.4.3, 1.5.0, 1.6.0, plain text, todo.sh and task command line files. - Implemented unit tests to cover the above. --- ChangeLog | 2 + html/task.html | 2 + src/import.cpp | 791 +++++++++++++++++++++++++-- src/import/{line.txt => cmdline.txt} | 0 src/import/task-1.4.3.csv | 1 + src/import/task-1.5.0.csv | 1 + src/import/task-1.6.0.csv | 3 + src/import/text.txt | 4 + src/import/todo.txt | 2 + src/parse.cpp | 2 +- src/tests/import.143.t | 70 +++ src/tests/import.150.t | 70 +++ src/tests/import.160.t | 70 +++ src/tests/import.cmd.t | 69 +++ src/tests/import.todo.t | 76 +++ src/tests/import.txt.t | 71 +++ 16 files changed, 1180 insertions(+), 54 deletions(-) rename src/import/{line.txt => cmdline.txt} (100%) create mode 100644 src/import/task-1.6.0.csv create mode 100644 src/import/text.txt create mode 100755 src/tests/import.143.t create mode 100755 src/tests/import.150.t create mode 100755 src/tests/import.160.t create mode 100755 src/tests/import.cmd.t create mode 100755 src/tests/import.todo.t create mode 100755 src/tests/import.txt.t diff --git a/ChangeLog b/ChangeLog index 803ab37ce..fd129ba7b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -24,6 +24,8 @@ export files from versions 1.4.3 and earlier, versions 1.5.0 and later, todo.sh 2.x, CSV, plain text and task command line. See online docs for full details. + + Export was including 'id' in the column header even though it was not + included in the data. ------ old releases ------------------------------ diff --git a/html/task.html b/html/task.html index ce46b4a00..2d010b7a5 100644 --- a/html/task.html +++ b/html/task.html @@ -121,6 +121,8 @@ export files from versions 1.4.3 and earlier, versions 1.5.0 and later, todo.sh 2.x, CSV, plain text and task command line. See online docs for full details. +
  • Export was including 'id' in the column header even though it was not + included in the data.

    diff --git a/src/import.cpp b/src/import.cpp index 0dc2ba4a8..94879404e 100644 --- a/src/import.cpp +++ b/src/import.cpp @@ -26,6 +26,7 @@ //////////////////////////////////////////////////////////////////////////////// #include #include +#include "Date.h" #include "task.h" //////////////////////////////////////////////////////////////////////////////// @@ -34,6 +35,7 @@ enum fileType not_a_clue, task_1_4_3, task_1_5_0, + task_1_6_0, task_cmd_line, todo_sh_2_0, csv, @@ -55,6 +57,10 @@ static fileType determineFileType (const std::vector & lines) lines[1][38] == ',' && lines[1][39] == '\'') { + if (lines[0] == "'uuid','status','tags','entry','start','due','recur'," + "'end','project','priority','fg','bg','description'") + return task_1_6_0; + if (lines[0] == "'id','uuid','status','tags','entry','start','due','recur'," "'end','project','priority','fg','bg','description'") return task_1_5_0; @@ -64,7 +70,26 @@ static fileType determineFileType (const std::vector & lines) return task_1_4_3; } - // TODO task_cmd_line + // A task command line might include a priority or project. + for (unsigned int i = 0; i < lines.size (); ++i) + { + std::vector words; + split (words, lines[i], ' '); + + for (unsigned int w = 0; w < words.size (); ++w) + if (words[w].substr (0, 9) == "priority:" || + words[w].substr (0, 8) == "priorit:" || + words[w].substr (0, 7) == "priori:" || + words[w].substr (0, 6) == "prior:" || + words[w].substr (0, 5) == "prio:" || + words[w].substr (0, 4) == "pri:" || + words[w].substr (0, 8) == "project:" || + words[w].substr (0, 7) == "projec:" || + words[w].substr (0, 6) == "proje:" || + words[w].substr (0, 5) == "proj:" || + words[w].substr (0, 4) == "pro:") + return task_cmd_line; + } // x 2009-03-25 Walk the dog +project @context // This is a test +project @context @@ -111,7 +136,6 @@ static fileType determineFileType (const std::vector & lines) for (unsigned int i = 0; i < lines.size (); ++i) { if (lines[i].length () > 10 && - lines[i][0] != '#' && lines[i].find (",") == std::string::npos) { commas_on_every_line = false; @@ -121,32 +145,187 @@ static fileType determineFileType (const std::vector & lines) if (commas_on_every_line) return csv; - // TODO text, possibly with commas. - for (unsigned int i = 0; i < lines.size (); ++i) - { - // TODO priority:{H,M,L} - // TODO priorit:{H,M,L} - // TODO priori:{H,M,L} - // TODO prior:{H,M,L} - // TODO prio:{H,M,L} - // TODO pri:{H,M,L} - // TODO project:.+ - // TODO projec:.+ - // TODO proje:.+ - // TODO proj:.+ - // TODO pro:.+ - } + // Looks like 'text' is the default case, if there is any data at all. + if (lines.size () > 1) + return text; return not_a_clue; } +//////////////////////////////////////////////////////////////////////////////// +static void decorateTask (T& task, Config& conf) +{ + char entryTime[16]; + sprintf (entryTime, "%u", (unsigned int) time (NULL)); + task.setAttribute ("entry", entryTime); + + // Override with default.project, if not specified. + std::string defaultProject = conf.get ("default.project", ""); + if (task.getAttribute ("project") == "" && defaultProject != "") + task.setAttribute ("project", defaultProject); + + // Override with default.priority, if not specified. + std::string defaultPriority = conf.get ("default.priority", ""); + if (task.getAttribute ("priority") == "" && + defaultPriority != "" && + validPriority (defaultPriority)) + task.setAttribute ("priority", defaultPriority); +} + //////////////////////////////////////////////////////////////////////////////// static std::string importTask_1_4_3 ( TDB& tdb, Config& conf, const std::vector & lines) { - return "task 1.4.3\n"; + std::vector failed; + + std::vector ::const_iterator it; + for (it = lines.begin (); it != lines.end (); ++it) + { + try + { + // Skip the first line, if it is a columns header line. + if (it->substr (0, 5) == "'id',") + continue; + + std::vector fields; + split (fields, *it, ','); + + // If there is an unexpected number of fields, something is wrong. Perhaps + // an embedded comma, in which case there are (at least) two fields that + // need to be concatenated. + if (fields.size () > 12) + { + int safety = 10; // Shouldn't be more than 10 commas in a description/project. + + do + { + std::vector modified; + for (unsigned int f = 0; f < fields.size (); ++f) + { + if (fields[f][0] != '\'' && + fields[f][fields[f].length () - 1] == '\'') + { + modified[modified.size () - 1] += "," + fields[f]; + } + + else + modified.push_back (fields[f]); + } + fields = modified; + + if (safety-- <= 0) + throw "unrecoverable"; + } + while (fields.size () > 12); + } + + if (fields.size () < 12) + throw "unrecoverable"; + + // Build up this task ready for insertion. + T task; + + // Handle the 12 fields. + for (unsigned int f = 0; f < fields.size (); ++f) + { + switch (f) + { + case 0: // 'uuid' + task.setUUID (fields[f].substr (1, 36)); + break; + + case 1: // 'status' + if (fields[f] == "'pending'") task.setStatus (T::pending); + else if (fields[f] == "'recurring'") task.setStatus (T::recurring); + else if (fields[f] == "'deleted'") task.setStatus (T::deleted); + else if (fields[f] == "'completed'") task.setStatus (T::completed); + break; + + case 2: // 'tags' + if (fields[f].length () > 2) + { + std::string tokens = fields[f].substr (1, fields[f].length () - 2); + std::vector tags; + split (tags, tokens, ' '); + for (unsigned int i = 0; i > tags.size (); ++i) + task.addTag (tags[i]); + } + break; + + case 3: // entry + task.setAttribute ("entry", fields[f]); + break; + + case 4: // start + if (fields[f].length ()) + task.setAttribute ("start", fields[f]); + break; + + case 5: // due + if (fields[f].length ()) + task.setAttribute ("due", fields[f]); + break; + + case 6: // end + if (fields[f].length ()) + task.setAttribute ("end", fields[f]); + break; + + case 7: // 'project' + if (fields[f].length () > 2) + task.setAttribute ("project", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 8: // 'priority' + if (fields[f].length () > 2) + task.setAttribute ("priority", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 9: // 'fg' + if (fields[f].length () > 2) + task.setAttribute ("fg", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 10: // 'bg' + if (fields[f].length () > 2) + task.setAttribute ("bg", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 11: // 'description' + if (fields[f].length () > 2) + task.setDescription (fields[f].substr (1, fields[f].length () - 2)); + break; + } + } + + if (! tdb.addT (task)) + failed.push_back (*it); + } + + catch (...) + { + failed.push_back (*it); + } + } + + std::stringstream out; + out << "Imported " + << (lines.size () - failed.size () - 1) + << " tasks successfully, with " + << failed.size () + << " errors." + << std::endl; + + if (failed.size ()) + { + std::string bad; + join (bad, "\n", failed); + return out.str () + "\nCould not import:\n\n" + bad; + } + + return out.str (); } //////////////////////////////////////////////////////////////////////////////// @@ -155,7 +334,320 @@ static std::string importTask_1_5_0 ( Config& conf, const std::vector & lines) { - return "task 1.5.0\n"; + std::vector failed; + + std::vector ::const_iterator it; + for (it = lines.begin (); it != lines.end (); ++it) + { + try + { + // Skip the first line, if it is a columns header line. + if (it->substr (0, 5) == "'id',") + continue; + + std::vector fields; + split (fields, *it, ','); + + // If there is an unexpected number of fields, something is wrong. Perhaps + // an embedded comma, in which case there are (at least) two fields that + // need to be concatenated. + if (fields.size () > 13) + { + int safety = 10; // Shouldn't be more than 10 commas in a description/project. + + do + { + std::vector modified; + for (unsigned int f = 0; f < fields.size (); ++f) + { + if (fields[f][0] != '\'' && + fields[f][fields[f].length () - 1] == '\'') + { + modified[modified.size () - 1] += "," + fields[f]; + } + + else + modified.push_back (fields[f]); + } + fields = modified; + + if (safety-- <= 0) + throw "unrecoverable"; + } + while (fields.size () > 13); + } + + if (fields.size () < 13) + throw "unrecoverable"; + + // Build up this task ready for insertion. + T task; + + // Handle the 13 fields. + for (unsigned int f = 0; f < fields.size (); ++f) + { + switch (f) + { + case 0: // 'uuid' + task.setUUID (fields[f].substr (1, 36)); + break; + + case 1: // 'status' + if (fields[f] == "'pending'") task.setStatus (T::pending); + else if (fields[f] == "'recurring'") task.setStatus (T::recurring); + else if (fields[f] == "'deleted'") task.setStatus (T::deleted); + else if (fields[f] == "'completed'") task.setStatus (T::completed); + break; + + case 2: // 'tags' + if (fields[f].length () > 2) + { + std::string tokens = fields[f].substr (1, fields[f].length () - 2); + std::vector tags; + split (tags, tokens, ' '); + for (unsigned int i = 0; i > tags.size (); ++i) + task.addTag (tags[i]); + } + break; + + case 3: // entry + task.setAttribute ("entry", fields[f]); + break; + + case 4: // start + if (fields[f].length ()) + task.setAttribute ("start", fields[f]); + break; + + case 5: // due + if (fields[f].length ()) + task.setAttribute ("due", fields[f]); + break; + + case 6: // recur + if (fields[f].length ()) + task.setAttribute ("recur", fields[f]); + break; + + case 7: // end + if (fields[f].length ()) + task.setAttribute ("end", fields[f]); + break; + + case 8: // 'project' + if (fields[f].length () > 2) + task.setAttribute ("project", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 9: // 'priority' + if (fields[f].length () > 2) + task.setAttribute ("priority", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 10: // 'fg' + if (fields[f].length () > 2) + task.setAttribute ("fg", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 11: // 'bg' + if (fields[f].length () > 2) + task.setAttribute ("bg", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 12: // 'description' + if (fields[f].length () > 2) + task.setDescription (fields[f].substr (1, fields[f].length () - 2)); + break; + } + } + + if (! tdb.addT (task)) + failed.push_back (*it); + } + + catch (...) + { + failed.push_back (*it); + } + } + + std::stringstream out; + out << "Imported " + << (lines.size () - failed.size () - 1) + << " tasks successfully, with " + << failed.size () + << " errors." + << std::endl; + + if (failed.size ()) + { + std::string bad; + join (bad, "\n", failed); + return out.str () + "\nCould not import:\n\n" + bad; + } + + return out.str (); +} + +//////////////////////////////////////////////////////////////////////////////// +static std::string importTask_1_6_0 ( + TDB& tdb, + Config& conf, + const std::vector & lines) +{ + std::vector failed; + + std::vector ::const_iterator it; + for (it = lines.begin (); it != lines.end (); ++it) + { + try + { + // Skip the first line, if it is a columns header line. + if (it->substr (0, 7) == "'uuid',") + continue; + + std::vector fields; + split (fields, *it, ','); + + // If there is an unexpected number of fields, something is wrong. Perhaps + // an embedded comma, in which case there are (at least) two fields that + // need to be concatenated. + if (fields.size () > 13) + { + int safety = 10; // Shouldn't be more than 10 commas in a description/project. + + do + { + std::vector modified; + for (unsigned int f = 0; f < fields.size (); ++f) + { + if (fields[f][0] != '\'' && + fields[f][fields[f].length () - 1] == '\'') + { + modified[modified.size () - 1] += "," + fields[f]; + } + + else + modified.push_back (fields[f]); + } + fields = modified; + + if (safety-- <= 0) + throw "unrecoverable"; + } + while (fields.size () > 13); + } + + if (fields.size () < 13) + throw "unrecoverable"; + + // Build up this task ready for insertion. + T task; + + // Handle the 13 fields. + for (unsigned int f = 0; f < fields.size (); ++f) + { + switch (f) + { + case 0: // 'uuid' + task.setUUID (fields[f].substr (1, 36)); + break; + + case 1: // 'status' + if (fields[f] == "'pending'") task.setStatus (T::pending); + else if (fields[f] == "'recurring'") task.setStatus (T::recurring); + else if (fields[f] == "'deleted'") task.setStatus (T::deleted); + else if (fields[f] == "'completed'") task.setStatus (T::completed); + break; + + case 2: // 'tags' + if (fields[f].length () > 2) + { + std::string tokens = fields[f].substr (1, fields[f].length () - 2); + std::vector tags; + split (tags, tokens, ' '); + for (unsigned int i = 0; i > tags.size (); ++i) + task.addTag (tags[i]); + } + break; + + case 3: // entry + task.setAttribute ("entry", fields[f]); + break; + + case 4: // start + if (fields[f].length ()) + task.setAttribute ("start", fields[f]); + break; + + case 5: // due + if (fields[f].length ()) + task.setAttribute ("due", fields[f]); + break; + + case 6: // recur + if (fields[f].length ()) + task.setAttribute ("recur", fields[f]); + break; + + case 7: // end + if (fields[f].length ()) + task.setAttribute ("end", fields[f]); + break; + + case 8: // 'project' + if (fields[f].length () > 2) + task.setAttribute ("project", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 9: // 'priority' + if (fields[f].length () > 2) + task.setAttribute ("priority", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 10: // 'fg' + if (fields[f].length () > 2) + task.setAttribute ("fg", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 11: // 'bg' + if (fields[f].length () > 2) + task.setAttribute ("bg", fields[f].substr (1, fields[f].length () - 2)); + break; + + case 12: // 'description' + if (fields[f].length () > 2) + task.setDescription (fields[f].substr (1, fields[f].length () - 2)); + break; + } + } + + if (! tdb.addT (task)) + failed.push_back (*it); + } + + catch (...) + { + failed.push_back (*it); + } + } + + std::stringstream out; + out << "Imported " + << (lines.size () - failed.size () - 1) + << " tasks successfully, with " + << failed.size () + << " errors." + << std::endl; + + if (failed.size ()) + { + std::string bad; + join (bad, "\n", failed); + return out.str () + "\nCould not import:\n\n" + bad; + } + + return out.str (); } //////////////////////////////////////////////////////////////////////////////// @@ -163,43 +655,28 @@ static std::string importTaskCmdLine ( TDB& tdb, Config& conf, const std::vector & lines) -{ - return "task command line\n"; -} - -//////////////////////////////////////////////////////////////////////////////// -static std::string importTodoSh_2_0 ( - TDB& tdb, - Config& conf, - const std::vector & lines) -{ - return "todo.sh 2.0\n"; -} - -//////////////////////////////////////////////////////////////////////////////// -static std::string importText ( - TDB& tdb, - Config& conf, - const std::vector & lines) { std::vector failed; - for (unsigned int i = 0; i < lines.size (); ++i) + std::vector ::const_iterator it; + for (it = lines.begin (); it != lines.end (); ++it) { - std::string line = lines[i]; + std::string line = *it; - // Strip comments - std::string::size_type pound = line.find ("#"); - if (pound != std::string::npos) - line = line.substr (0, pound); - - // Skip blank lines - if (line.length () > 0) + try { + std::vector args; + split (args, std::string ("add ") + line, ' '); + T task; - task.parse (std::string ("\"") + lines[i] + "\""); - if (! tdb.addT (task)) - failed.push_back (lines[i]); + std::string command; + parse (args, command, task, conf); + handleAdd (tdb, task, conf); + } + + catch (...) + { + failed.push_back (line); } } @@ -211,9 +688,198 @@ static std::string importText ( << " errors." << std::endl; - std::string bad; - join (bad, "\n", failed); - return out.str () + "\n" + bad; + if (failed.size ()) + { + std::string bad; + join (bad, "\n", failed); + return out.str () + "\nCould not import:\n\n" + bad; + } + + return out.str (); +} + +//////////////////////////////////////////////////////////////////////////////// +static std::string importTodoSh_2_0 ( + TDB& tdb, + Config& conf, + const std::vector & lines) +{ + std::vector failed; + + std::vector ::const_iterator it; + for (it = lines.begin (); it != lines.end (); ++it) + { + try + { + std::vector args; + args.push_back ("add"); + + bool isPending = true; + Date endDate; + + std::vector words; + split (words, *it, ' '); + + for (unsigned int w = 0; w < words.size (); ++w) + { + if (words[w].length () > 1 && + words[w][0] == '+') + { + args.push_back (std::string ("project:") + + words[w].substr (1, std::string::npos)); + } + + // Convert "+aaa" to "project:aaa". + // Convert "@aaa" to "+aaa". + else if (words[w].length () > 1 && + words[w][0] == '@') + { + args.push_back (std::string ("+") + + words[w].substr (1, std::string::npos)); + } + + // Convert "(A)" to "priority:H". + // Convert "(B)" to "priority:M". + // Convert "(?)" to "priority:L". + else if (words[w].length () == 3 && + words[w][0] == '(' && + words[w][2] == ')') + { + if (words[w][1] == 'A') args.push_back ("priority:H"); + else if (words[w][1] == 'B') args.push_back ("priority:M"); + else args.push_back ("priority:L"); + } + + // Set status, if completed. + else if (w == 0 && + words[w] == "x") + { + isPending = false; + } + + // Set status, and add an end date, if completed. + else if (! isPending && + w == 1 && + words[w].length () == 10 && + words[w][4] == '-' && + words[w][7] == '-') + { + endDate = Date (words[w], "Y-M-D"); + } + + // Just an ordinary word. + else + { + args.push_back (words[w]); + } + } + + T task; + std::string command; + parse (args, command, task, conf); + decorateTask (task, conf); + + if (isPending) + { + task.setStatus (T::pending); + } + else + { + task.setStatus (T::completed); + + char end[16]; + sprintf (end, "%u", (unsigned int) endDate.toEpoch ()); + task.setAttribute ("end", end); + } + + if (! tdb.addT (task)) + failed.push_back (*it); + } + + catch (...) + { + failed.push_back (*it); + } + } + + std::stringstream out; + out << "Imported " + << (lines.size () - failed.size ()) + << " tasks successfully, with " + << failed.size () + << " errors." + << std::endl; + + if (failed.size ()) + { + std::string bad; + join (bad, "\n", failed); + return out.str () + "\nCould not import:\n\n" + bad; + } + + return out.str (); +} + +//////////////////////////////////////////////////////////////////////////////// +static std::string importText ( + TDB& tdb, + Config& conf, + const std::vector & lines) +{ + std::vector failed; + int count = 0; + + std::vector ::const_iterator it; + for (it = lines.begin (); it != lines.end (); ++it) + { + std::string line = *it; + + // Strip comments + std::string::size_type pound = line.find ("#"); + if (pound != std::string::npos) + line = line.substr (0, pound); + + // Skip blank lines + if (line.length () > 0) + { + try + { + ++count; + std::vector args; + split (args, std::string ("add ") + line, ' '); + + T task; + std::string command; + parse (args, command, task, conf); + decorateTask (task, conf); + + if (! tdb.addT (task)) + failed.push_back (*it); + } + + catch (...) + { + failed.push_back (line); + } + } + } + + std::stringstream out; + out << "Imported " + << count + << " tasks successfully, with " + << failed.size () + << " errors." + << std::endl; + + if (failed.size ()) + { + std::string bad; + join (bad, "\n", failed); + return out.str () + "\nCould not import:\n\n" + bad; + } + + return out.str (); } //////////////////////////////////////////////////////////////////////////////// @@ -237,8 +903,26 @@ std::string handleImport (TDB& tdb, T& task, Config& conf) if (file.length () > 0) { // Load the file. + std::vector all; + slurp (file, all, true); + std::vector lines; - slurp (file, lines, true); + std::vector ::iterator it; + for (it = all.begin (); it != all.end (); ++it) + { + std::string line = *it; + + // Strip comments + std::string::size_type pound = line.find ("#"); + if (pound != std::string::npos) + line = line.substr (0, pound); + + trim (line); + + // Skip blank lines + if (line.length () > 0) + lines.push_back (line); + } // Take a guess at the file type. fileType type = determineFileType (lines); @@ -250,6 +934,7 @@ std::string handleImport (TDB& tdb, T& task, Config& conf) { case task_1_4_3: out << importTask_1_4_3 (tdb, conf, lines); break; case task_1_5_0: out << importTask_1_5_0 (tdb, conf, lines); break; + case task_1_6_0: out << importTask_1_6_0 (tdb, conf, lines); break; case task_cmd_line: out << importTaskCmdLine (tdb, conf, lines); break; case todo_sh_2_0: out << importTodoSh_2_0 (tdb, conf, lines); break; case csv: out << importCSV (tdb, conf, lines); break; diff --git a/src/import/line.txt b/src/import/cmdline.txt similarity index 100% rename from src/import/line.txt rename to src/import/cmdline.txt diff --git a/src/import/task-1.4.3.csv b/src/import/task-1.4.3.csv index b917e0df9..9c60aaf0d 100644 --- a/src/import/task-1.4.3.csv +++ b/src/import/task-1.4.3.csv @@ -1,2 +1,3 @@ 'id','status','tags','entry','start','due','end','project','priority','fg','bg','description' '545629d2-24a3-4a32-8894-57e708b98354','pending','',1238037900,,,,'A','M',,,'foo bar ' +'545629d2-24a3-4a32-8894-57e708b98354','pending','',1238037900,,,,'A','M',,,'foo, bar ' diff --git a/src/import/task-1.5.0.csv b/src/import/task-1.5.0.csv index 08c900aa9..6e4eff714 100644 --- a/src/import/task-1.5.0.csv +++ b/src/import/task-1.5.0.csv @@ -1,2 +1,3 @@ 'id','uuid','status','tags','entry','start','due','recur','end','project','priority','fg','bg','description' '7f7a4191-c2f2-487f-8855-7a1eb378c267','pending','',1238037947,,,,,'A','M',,,'foo bar' +'7f7a4191-c2f2-487f-8855-7a1eb378c267','pending','',1238037947,,,,,'A','M',,,'foo, bar' diff --git a/src/import/task-1.6.0.csv b/src/import/task-1.6.0.csv new file mode 100644 index 000000000..55e9f9c62 --- /dev/null +++ b/src/import/task-1.6.0.csv @@ -0,0 +1,3 @@ +'uuid','status','tags','entry','start','due','recur','end','project','priority','fg','bg','description' +'7f7a4191-c2f2-487f-8855-7a1eb378c267','pending','',1238037947,,,,,'A','M',,,'foo bar' +'7f7a4191-c2f2-487f-8855-7a1eb378c267','pending','',1238037947,,,,,'A','M',,,'foo, bar' diff --git a/src/import/text.txt b/src/import/text.txt new file mode 100644 index 000000000..57bae5055 --- /dev/null +++ b/src/import/text.txt @@ -0,0 +1,4 @@ +Get milk, bread +Order cake +Clean house + diff --git a/src/import/todo.txt b/src/import/todo.txt index 8177b0c40..cceab0b35 100644 --- a/src/import/todo.txt +++ b/src/import/todo.txt @@ -1,2 +1,4 @@ x 2009-03-25 Walk the dog +project @context This is a test +project @context +(A) A prioritized task + diff --git a/src/parse.cpp b/src/parse.cpp index e372f2352..e6923c589 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -235,7 +235,7 @@ bool validDate (std::string& date, Config& conf) { Date test (date, conf.get ("dateformat", "m/d/Y")); - char epoch[12]; + char epoch[16]; sprintf (epoch, "%d", (int) test.toEpoch ()); date = epoch; diff --git a/src/tests/import.143.t b/src/tests/import.143.t new file mode 100755 index 000000000..632d5814c --- /dev/null +++ b/src/tests/import.143.t @@ -0,0 +1,70 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, Paul Beckingham. +## All rights reserved. +## +## This program is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free Software +## Foundation; either version 2 of the License, or (at your option) any later +## version. +## +## This program is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +## details. +## +## You should have received a copy of the GNU General Public License along with +## this program; if not, write to the +## +## Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, +## Boston, MA +## 02110-1301 +## USA +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 8; + +# Create the rc file. +if (open my $fh, '>', 'import.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'import.rc', 'Created import.rc'); +} + +# Create import file. +if (open my $fh, '>', 'import.txt') +{ + print $fh "'id','status','tags','entry','start','due','end','project','priority','fg','bg','description'\n", + "'7f7a4191-c2f2-487f-8855-7a1eb378c267','pending','',1238037947,,,,'A','M',,,'foo bar'\n", + "'7f7a4191-c2f2-487f-8855-7a1eb378c267','pending','',1238037947,,,,'A','M',,,'foo, bar'\n", + "\n"; + close $fh; + ok (-r 'import.txt', 'Created sample import data'); +} + +my $output = qx{../task rc:import.rc import import.txt}; +is ($output, "Imported 2 tasks successfully, with 0 errors.\n", 'no errors'); + +$output = qx{../task rc:import.rc list}; +like ($output, qr/1.+A.+M.+foo bar/, 't1'); +like ($output, qr/2.+A.+M.+foo, bar/, 't2'); + +# Cleanup. +unlink 'import.txt'; +ok (!-r 'import.txt', 'Removed import.txt'); + +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'import.rc'; +ok (!-r 'import.rc', 'Removed import.rc'); + +exit 0; + diff --git a/src/tests/import.150.t b/src/tests/import.150.t new file mode 100755 index 000000000..4344ed7f1 --- /dev/null +++ b/src/tests/import.150.t @@ -0,0 +1,70 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, Paul Beckingham. +## All rights reserved. +## +## This program is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free Software +## Foundation; either version 2 of the License, or (at your option) any later +## version. +## +## This program is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +## details. +## +## You should have received a copy of the GNU General Public License along with +## this program; if not, write to the +## +## Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, +## Boston, MA +## 02110-1301 +## USA +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 8; + +# Create the rc file. +if (open my $fh, '>', 'import.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'import.rc', 'Created import.rc'); +} + +# Create import file. +if (open my $fh, '>', 'import.txt') +{ + print $fh "'id','uuid','status','tags','entry','start','due','recur','end','project','priority','fg','bg','description'\n", + "'7f7a4191-c2f2-487f-8855-7a1eb378c267','pending','',1238037947,,,,,'A','M',,,'foo bar'\n", + "'7f7a4191-c2f2-487f-8855-7a1eb378c267','pending','',1238037947,,,,,'A','M',,,'foo, bar'\n", + "\n"; + close $fh; + ok (-r 'import.txt', 'Created sample import data'); +} + +my $output = qx{../task rc:import.rc import import.txt}; +is ($output, "Imported 2 tasks successfully, with 0 errors.\n", 'no errors'); + +$output = qx{../task rc:import.rc list}; +like ($output, qr/1.+A.+M.+foo bar/, 't1'); +like ($output, qr/2.+A.+M.+foo, bar/, 't2'); + +# Cleanup. +unlink 'import.txt'; +ok (!-r 'import.txt', 'Removed import.txt'); + +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'import.rc'; +ok (!-r 'import.rc', 'Removed import.rc'); + +exit 0; + diff --git a/src/tests/import.160.t b/src/tests/import.160.t new file mode 100755 index 000000000..33c34e2af --- /dev/null +++ b/src/tests/import.160.t @@ -0,0 +1,70 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, Paul Beckingham. +## All rights reserved. +## +## This program is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free Software +## Foundation; either version 2 of the License, or (at your option) any later +## version. +## +## This program is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +## details. +## +## You should have received a copy of the GNU General Public License along with +## this program; if not, write to the +## +## Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, +## Boston, MA +## 02110-1301 +## USA +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 8; + +# Create the rc file. +if (open my $fh, '>', 'import.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'import.rc', 'Created import.rc'); +} + +# Create import file. +if (open my $fh, '>', 'import.txt') +{ + print $fh "'uuid','status','tags','entry','start','due','recur','end','project','priority','fg','bg','description'\n", + "'7f7a4191-c2f2-487f-8855-7a1eb378c267','pending','',1238037947,,,,,'A','M',,,'foo bar'\n", + "'7f7a4191-c2f2-487f-8855-7a1eb378c267','pending','',1238037947,,,,,'A','M',,,'foo, bar'\n", + "\n"; + close $fh; + ok (-r 'import.txt', 'Created sample import data'); +} + +my $output = qx{../task rc:import.rc import import.txt}; +is ($output, "Imported 2 tasks successfully, with 0 errors.\n", 'no errors'); + +$output = qx{../task rc:import.rc list}; +like ($output, qr/1.+A.+M.+foo bar/, 't1'); +like ($output, qr/2.+A.+M.+foo, bar/, 't2'); + +# Cleanup. +unlink 'import.txt'; +ok (!-r 'import.txt', 'Removed import.txt'); + +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'import.rc'; +ok (!-r 'import.rc', 'Removed import.rc'); + +exit 0; + diff --git a/src/tests/import.cmd.t b/src/tests/import.cmd.t new file mode 100755 index 000000000..220241eeb --- /dev/null +++ b/src/tests/import.cmd.t @@ -0,0 +1,69 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, Paul Beckingham. +## All rights reserved. +## +## This program is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free Software +## Foundation; either version 2 of the License, or (at your option) any later +## version. +## +## This program is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +## details. +## +## You should have received a copy of the GNU General Public License along with +## this program; if not, write to the +## +## Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, +## Boston, MA +## 02110-1301 +## USA +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 8; + +# Create the rc file. +if (open my $fh, '>', 'import.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'import.rc', 'Created import.rc'); +} + +# Create import file. +if (open my $fh, '>', 'import.txt') +{ + print $fh "This is a test priority:H project:A\n", + "Another task\n", + "\n"; + close $fh; + ok (-r 'import.txt', 'Created sample import data'); +} + +my $output = qx{../task rc:import.rc import import.txt}; +is ($output, "Imported 2 tasks successfully, with 0 errors.\n", 'no errors'); + +$output = qx{../task rc:import.rc list}; +like ($output, qr/1.+A.+H.+This is a test/, 't1'); +like ($output, qr/2.+Another task/, 't2'); + +# Cleanup. +unlink 'import.txt'; +ok (!-r 'import.txt', 'Removed import.txt'); + +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'import.rc'; +ok (!-r 'import.rc', 'Removed import.rc'); + +exit 0; + diff --git a/src/tests/import.todo.t b/src/tests/import.todo.t new file mode 100755 index 000000000..e8bb578f9 --- /dev/null +++ b/src/tests/import.todo.t @@ -0,0 +1,76 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, Paul Beckingham. +## All rights reserved. +## +## This program is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free Software +## Foundation; either version 2 of the License, or (at your option) any later +## version. +## +## This program is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +## details. +## +## You should have received a copy of the GNU General Public License along with +## this program; if not, write to the +## +## Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, +## Boston, MA +## 02110-1301 +## USA +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 10; + +# Create the rc file. +if (open my $fh, '>', 'import.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'import.rc', 'Created import.rc'); +} + +# Create import file. +if (open my $fh, '>', 'import.txt') +{ + print $fh "x 2010-03-25 Walk the dog +project \@context\n", + "This is a test +project \@context\n", + "(A) A prioritized task\n", + "\n"; + close $fh; + ok (-r 'import.txt', 'Created sample import data'); +} + +my $output = qx{../task rc:import.rc import import.txt}; +is ($output, "Imported 3 tasks successfully, with 0 errors.\n", 'no errors'); + +$output = qx{../task rc:import.rc list}; +like ($output, qr/1.+project.+This is a test/, 't1'); +like ($output, qr/2.+H.+A prioritized task/, 't2'); + +$output = qx{../task rc:import.rc completed}; +like ($output, qr/3\/25\/2009.+Walk the dog/, 't3'); + +# Cleanup. +unlink 'import.txt'; +ok (!-r 'import.txt', 'Removed import.txt'); + +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'completed.data'; +ok (!-r 'completed.data', 'Removed completed.data'); + +unlink 'import.rc'; +ok (!-r 'import.rc', 'Removed import.rc'); + +exit 0; + diff --git a/src/tests/import.txt.t b/src/tests/import.txt.t new file mode 100755 index 000000000..65f3e0b69 --- /dev/null +++ b/src/tests/import.txt.t @@ -0,0 +1,71 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2009, Paul Beckingham. +## All rights reserved. +## +## This program is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free Software +## Foundation; either version 2 of the License, or (at your option) any later +## version. +## +## This program is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +## details. +## +## You should have received a copy of the GNU General Public License along with +## this program; if not, write to the +## +## Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, +## Boston, MA +## 02110-1301 +## USA +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 9; + +# Create the rc file. +if (open my $fh, '>', 'import.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'import.rc', 'Created import.rc'); +} + +# Create import file. +if (open my $fh, '>', 'import.txt') +{ + print $fh "Get milk, bread\n", + "Order cake\n", + "Clean house\n", + "\n"; + close $fh; + ok (-r 'import.txt', 'Created sample import data'); +} + +my $output = qx{../task rc:import.rc import import.txt}; +is ($output, "Imported 3 tasks successfully, with 0 errors.\n", 'no errors'); + +$output = qx{../task rc:import.rc list}; +like ($output, qr/1.+Get milk, bread/, 't1'); +like ($output, qr/2.+Order cake/, 't2'); +like ($output, qr/3.+Clean house/, 't3'); + +# Cleanup. +unlink 'import.txt'; +ok (!-r 'import.txt', 'Removed import.txt'); + +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'import.rc'; +ok (!-r 'import.rc', 'Removed import.rc'); + +exit 0; +