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;
+