//////////////////////////////////////////////////////////////////////////////// // 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 // //////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include "Date.h" #include "text.h" #include "util.h" #include "main.h" extern Context context; //////////////////////////////////////////////////////////////////////////////// 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, text }; static fileType determineFileType (const std::vector & lines) { // '7f7a4191-c2f2-487f-8855-7a1eb378c267',' ... // ....:....|....:....|....:....|....:....| // 1 10 20 30 40 if (lines.size () > 1 && lines[1][0] == '\'' && lines[1][9] == '-' && lines[1][14] == '-' && lines[1][19] == '-' && lines[1][24] == '-' && lines[1][37] == '\'' && 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; if (lines[0] == "'id','status','tags','entry','start','due','end','project'," "'priority','fg','bg','description'") return task_1_4_3; } // 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 for (unsigned int i = 0; i < lines.size (); ++i) { // All done tasks begin with "x YYYY-MM-DD". if (lines[i].length () > 12) { if ( lines[i][0] == 'x' && lines[i][1] == ' ' && ::isdigit (lines[i][2]) && ::isdigit (lines[i][3]) && ::isdigit (lines[i][4]) && ::isdigit (lines[i][5]) && lines[i][6] == '-' && ::isdigit (lines[i][7]) && ::isdigit (lines[i][8]) && lines[i][9] == '-' && ::isdigit (lines[i][10]) && ::isdigit (lines[i][11])) return todo_sh_2_0; } std::vector words; split (words, lines[i], ' '); for (unsigned int w = 0; w < words.size (); ++w) { // +project if (words[w].length () > 1 && words[w][0] == '+' && ::isalnum (words[w][1])) return todo_sh_2_0; // @context if (words[w].length () > 1 && words[w][0] == '@' && ::isalnum (words[w][1])) return todo_sh_2_0; } } // CSV - commas on every non-comment, non-trivial line. bool commas_on_every_line = true; for (unsigned int i = 0; i < lines.size (); ++i) { if (lines[i].length () > 10 && lines[i].find (",") == std::string::npos) { commas_on_every_line = false; break; } } if (commas_on_every_line) return csv; // 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 (Task& task) { char entryTime[16]; sprintf (entryTime, "%u", (unsigned int) time (NULL)); task.set ("entry", entryTime); task.setStatus (Task::pending); // Override with default.project, if not specified. std::string defaultProject = context.config.get ("default.project", ""); if (!task.has ("project") && defaultProject != "") task.set ("project", defaultProject); // Override with default.priority, if not specified. std::string defaultPriority = context.config.get ("default.priority", ""); if (!task.has ("priority") && defaultPriority != "" && Att::validNameValue ("priority", "", defaultPriority)) task.set ("priority", defaultPriority); } //////////////////////////////////////////////////////////////////////////////// static std::string importTask_1_4_3 (const std::vector & lines) { std::vector failed; context.tdb.lock (context.config.get ("locking", true)); 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. Task task; // Handle the 12 fields. for (unsigned int f = 0; f < fields.size (); ++f) { switch (f) { case 0: // 'uuid' task.set ("uuid", fields[f].substr (1, 36)); break; case 1: // 'status' if (fields[f] == "'pending'") task.setStatus (Task::pending); else if (fields[f] == "'recurring'") task.setStatus (Task::recurring); else if (fields[f] == "'deleted'") task.setStatus (Task::deleted); else if (fields[f] == "'completed'") task.setStatus (Task::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.set ("entry", fields[f]); break; case 4: // start if (fields[f].length ()) task.set ("start", fields[f]); break; case 5: // due if (fields[f].length ()) task.set ("due", fields[f]); break; case 6: // end if (fields[f].length ()) task.set ("end", fields[f]); break; case 7: // 'project' if (fields[f].length () > 2) task.set ("project", fields[f].substr (1, fields[f].length () - 2)); break; case 8: // 'priority' if (fields[f].length () > 2) task.set ("priority", fields[f].substr (1, fields[f].length () - 2)); break; case 9: // 'fg' if (fields[f].length () > 2) task.set ("fg", fields[f].substr (1, fields[f].length () - 2)); break; case 10: // 'bg' if (fields[f].length () > 2) task.set ("bg", fields[f].substr (1, fields[f].length () - 2)); break; case 11: // 'description' if (fields[f].length () > 2) task.set ("description", fields[f].substr (1, fields[f].length () - 2)); break; } } context.tdb.add (task); } catch (...) { failed.push_back (*it); } } context.tdb.commit (); context.tdb.unlock (); 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_5_0 (const std::vector & lines) { std::vector failed; context.tdb.lock (context.config.get ("locking", true)); 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. Task task; // Handle the 13 fields. for (unsigned int f = 0; f < fields.size (); ++f) { switch (f) { case 0: // 'uuid' task.set ("uuid", fields[f].substr (1, 36)); break; case 1: // 'status' if (fields[f] == "'pending'") task.setStatus (Task::pending); else if (fields[f] == "'recurring'") task.setStatus (Task::recurring); else if (fields[f] == "'deleted'") task.setStatus (Task::deleted); else if (fields[f] == "'completed'") task.setStatus (Task::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.set ("entry", fields[f]); break; case 4: // start if (fields[f].length ()) task.set ("start", fields[f]); break; case 5: // due if (fields[f].length ()) task.set ("due", fields[f]); break; case 6: // recur if (fields[f].length ()) task.set ("recur", fields[f]); break; case 7: // end if (fields[f].length ()) task.set ("end", fields[f]); break; case 8: // 'project' if (fields[f].length () > 2) task.set ("project", fields[f].substr (1, fields[f].length () - 2)); break; case 9: // 'priority' if (fields[f].length () > 2) task.set ("priority", fields[f].substr (1, fields[f].length () - 2)); break; case 10: // 'fg' if (fields[f].length () > 2) task.set ("fg", fields[f].substr (1, fields[f].length () - 2)); break; case 11: // 'bg' if (fields[f].length () > 2) task.set ("bg", fields[f].substr (1, fields[f].length () - 2)); break; case 12: // 'description' if (fields[f].length () > 2) task.set ("description", fields[f].substr (1, fields[f].length () - 2)); break; } } context.tdb.add (task); } catch (...) { failed.push_back (*it); } } context.tdb.commit (); context.tdb.unlock (); 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 (const std::vector & lines) { std::vector failed; context.tdb.lock (context.config.get ("locking", true)); 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. Task task; // Handle the 13 fields. for (unsigned int f = 0; f < fields.size (); ++f) { switch (f) { case 0: // 'uuid' task.set ("uuid", fields[f].substr (1, 36)); break; case 1: // 'status' if (fields[f] == "'pending'") task.setStatus (Task::pending); else if (fields[f] == "'recurring'") task.setStatus (Task::recurring); else if (fields[f] == "'deleted'") task.setStatus (Task::deleted); else if (fields[f] == "'completed'") task.setStatus (Task::completed); else if (fields[f] == "'waiting'") task.setStatus (Task::waiting); 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.set ("entry", fields[f]); break; case 4: // start if (fields[f].length ()) task.set ("start", fields[f]); break; case 5: // due if (fields[f].length ()) task.set ("due", fields[f]); break; case 6: // recur if (fields[f].length ()) task.set ("recur", fields[f]); break; case 7: // end if (fields[f].length ()) task.set ("end", fields[f]); break; case 8: // 'project' if (fields[f].length () > 2) task.set ("project", fields[f].substr (1, fields[f].length () - 2)); break; case 9: // 'priority' if (fields[f].length () > 2) task.set ("priority", fields[f].substr (1, fields[f].length () - 2)); break; case 10: // 'fg' if (fields[f].length () > 2) task.set ("fg", fields[f].substr (1, fields[f].length () - 2)); break; case 11: // 'bg' if (fields[f].length () > 2) task.set ("bg", fields[f].substr (1, fields[f].length () - 2)); break; case 12: // 'description' if (fields[f].length () > 2) task.set ("description", fields[f].substr (1, fields[f].length () - 2)); break; } } context.tdb.add (task); } catch (...) { failed.push_back (*it); } } context.tdb.commit (); context.tdb.unlock (); 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 importTaskCmdLine (const std::vector & lines) { std::vector failed; std::vector ::const_iterator it; for (it = lines.begin (); it != lines.end (); ++it) { std::string line = *it; try { context.args.clear (); split (context.args, std::string ("add ") + line, ' '); context.task.clear (); context.cmd.command = ""; context.parse (); handleAdd (); context.clearMessages (); } catch (...) { context.clearMessages (); failed.push_back (line); } } 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 importTodoSh_2_0 (const std::vector & lines) { std::vector failed; context.tdb.lock (context.config.get ("locking", true)); std::vector ::const_iterator it; for (it = lines.begin (); it != lines.end (); ++it) { try { context.args.clear (); context.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] == '+') { context.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] == '@') { context.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') context.args.push_back ("priority:H"); else if (words[w][1] == 'B') context.args.push_back ("priority:M"); else context.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 { context.args.push_back (words[w]); } } context.task.clear (); context.cmd.command = ""; context.parse (); decorateTask (context.task); if (isPending) { context.task.setStatus (Task::pending); } else { context.task.setStatus (Task::completed); char end[16]; sprintf (end, "%u", (unsigned int) endDate.toEpoch ()); context.task.set ("end", end); } context.tdb.add (context.task); context.clearMessages (); } catch (...) { context.clearMessages (); failed.push_back (*it); } } context.tdb.commit (); context.tdb.unlock (); 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 (const std::vector & lines) { std::vector failed; int count = 0; context.tdb.lock (context.config.get ("locking", true)); 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; context.args.clear (); split (context.args, std::string ("add ") + line, ' '); context.task.clear (); context.cmd.command = ""; context.parse (); decorateTask (context.task); context.tdb.add (context.task); context.clearMessages (); } catch (...) { context.clearMessages (); failed.push_back (line); } } } context.tdb.commit (); context.tdb.unlock (); 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 (); } //////////////////////////////////////////////////////////////////////////////// static std::string importCSV (const std::vector & lines) { std::vector failed; context.tdb.lock (context.config.get ("locking", true)); // Set up mappings. Assume no fields match. std::map mapping; mapping ["id"] = -1; mapping ["uuid"] = -1; mapping ["status"] = -1; mapping ["tags"] = -1; mapping ["entry"] = -1; mapping ["start"] = -1; mapping ["due"] = -1; mapping ["recur"] = -1; mapping ["end"] = -1; mapping ["project"] = -1; mapping ["priority"] = -1; mapping ["fg"] = -1; mapping ["bg"] = -1; mapping ["description"] = -1; std::vector headings; split (headings, lines[0], ','); for (unsigned int h = 0; h < headings.size (); ++h) { std::string name = lowerCase (trim (unquoteText (trim (headings[h])))); // If there is a mapping for the field, use the value. if (name == context.config.get ("import.synonym.id") || name == "id" || name == "#" || name == "sequence" || name.find ("num") != std::string::npos) { mapping["id"] = (int)h; } else if (name == context.config.get ("import.synonym.uuid") || name == "uuid" || name == "guid" || name.find ("unique") != std::string::npos) { mapping["uuid"] = (int)h; } else if (name == context.config.get ("import.synonym.status") || name == "status" || name == "condition" || name == "state") { mapping["status"] = (int)h; } else if (name == context.config.get ("import.synonym.tags") || name == "tags" || name.find ("categor") != std::string::npos || name.find ("tag") != std::string::npos) { mapping["tags"] = (int)h; } else if (name == context.config.get ("import.synonym.entry") || name == "entry" || name.find ("added") != std::string::npos || name.find ("created") != std::string::npos || name.find ("entered") != std::string::npos) { mapping["entry"] = (int)h; } else if (name == context.config.get ("import.synonym.start") || name == "start" || name.find ("began") != std::string::npos || name.find ("begun") != std::string::npos || name.find ("started") != std::string::npos) { mapping["start"] = (int)h; } else if (name == context.config.get ("import.synonym.due") || name == "due" || name.find ("expected") != std::string::npos) { mapping["due"] = (int)h; } else if (name == context.config.get ("import.synonym.recur") || name == "recur" || name == "frequency") { mapping["recur"] = (int)h; } else if (name == context.config.get ("import.synonym.end") || name == "end" || name == "done" || name.find ("complete") != std::string::npos) { mapping["end"] = (int)h; } else if (name == context.config.get ("import.synonym.project") || name == "project" || name.find ("proj") != std::string::npos) { mapping["project"] = (int)h; } else if (name == context.config.get ("import.synonym.priority") || name == "priority" || name == "pri" || name.find ("importan") != std::string::npos) { mapping["priority"] = (int)h; } else if (name == context.config.get ("import.synonym.fg") || name.find ("fg") != std::string::npos || name.find ("foreground") != std::string::npos || name.find ("color") != std::string::npos) { mapping["fg"] = (int)h; } else if (name == context.config.get ("import.synonym.bg") || name == "bg" || name.find ("background") != std::string::npos) { mapping["bg"] = (int)h; } else if (name == context.config.get ("import.synonym.description") || name.find ("desc") != std::string::npos || name.find ("detail") != std::string::npos || name.find ("task") != std::string::npos || name.find ("what") != std::string::npos) { mapping["description"] = (int)h; } } // TODO Dump mappings and ask for confirmation? std::vector ::const_iterator it = lines.begin (); for (++it; it != lines.end (); ++it) { try { std::vector fields; split (fields, *it, ','); Task task; int f; if ((f = mapping["uuid"]) != -1) task.set ("uuid", lowerCase (unquoteText (trim (fields[f])))); task.setStatus (Task::pending); if ((f = mapping["status"]) != -1) { std::string value = lowerCase (unquoteText (trim (fields[f]))); if (value == "recurring") task.setStatus (Task::recurring); else if (value == "deleted") task.setStatus (Task::deleted); else if (value == "completed") task.setStatus (Task::completed); else if (value == "waiting") task.setStatus (Task::waiting); } if ((f = mapping["tags"]) != -1) { std::string value = unquoteText (trim (fields[f])); std::vector tags; split (tags, value, ' '); for (unsigned int i = 0; i < tags.size (); ++i) task.addTag (tags[i]); } if ((f = mapping["entry"]) != -1) task.set ("entry", lowerCase (unquoteText (trim (fields[f])))); if ((f = mapping["start"]) != -1) task.set ("start", lowerCase (unquoteText (trim (fields[f])))); if ((f = mapping["due"]) != -1) task.set ("due", lowerCase (unquoteText (trim (fields[f])))); if ((f = mapping["recur"]) != -1) task.set ("recur", lowerCase (unquoteText (trim (fields[f])))); if ((f = mapping["end"]) != -1) task.set ("end", lowerCase (unquoteText (trim (fields[f])))); if ((f = mapping["project"]) != -1) task.set ("project", unquoteText (trim (fields[f]))); if ((f = mapping["priority"]) != -1) { std::string value = upperCase (unquoteText (trim (fields[f]))); if (value == "H" || value == "M" || value == "L") task.set ("priority", value); } if ((f = mapping["fg"]) != -1) task.set ("fg", lowerCase (unquoteText (trim (fields[f])))); if ((f = mapping["bg"]) != -1) task.set ("bg", lowerCase (unquoteText (trim (fields[f])))); if ((f = mapping["description"]) != -1) task.set ("description", unquoteText (trim (fields[f]))); context.tdb.add (task); } catch (...) { failed.push_back (*it); } } context.tdb.commit (); context.tdb.unlock (); 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 (); } //////////////////////////////////////////////////////////////////////////////// std::string handleImport () { std::stringstream out; // Use the description as a file name. std::string file = trim (context.task.get ("description")); if (file.length () > 0) { // Load the file. std::vector all; slurp (file, all, true); std::vector lines; 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); std::string identifier; switch (type) { case task_1_4_3: identifier = "This looks like an older task export file."; break; case task_1_5_0: identifier = "This looks like a recent task export file."; break; case task_1_6_0: identifier = "This looks like a current task export file."; break; case task_cmd_line: identifier = "This looks like task command line arguments."; break; case todo_sh_2_0: identifier = "This looks like a todo.sh 2.x file."; break; case csv: identifier = "This looks like a CSV file, but not a task export file."; break; case text: identifier = "This looks like a text file with one task per line."; break; case not_a_clue: throw std::string ("Task cannot determine which type of file this is, " "and cannot proceed."); } // For tty users, confirm the import, as it is destructive. if (isatty (fileno (stdout))) if (! confirm (identifier + " Okay to proceed?")) throw std::string ("Task will not import any data."); // Determine which type it might be, then attempt an import. switch (type) { case task_1_4_3: out << importTask_1_4_3 (lines); break; case task_1_5_0: out << importTask_1_5_0 (lines); break; case task_1_6_0: out << importTask_1_6_0 (lines); break; case task_cmd_line: out << importTaskCmdLine (lines); break; case todo_sh_2_0: out << importTodoSh_2_0 (lines); break; case csv: out << importCSV (lines); break; case text: out << importText (lines); break; case not_a_clue: /* to stop the compiler from complaining. */ break; } } else throw std::string ("You must specify a file to import."); return out.str (); } ////////////////////////////////////////////////////////////////////////////////