From db7b2dd9fea5afac9aee882225c07a6538099f59 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Wed, 25 Mar 2009 02:05:50 -0400 Subject: [PATCH 1/7] File Import - Implemented scaffolding for new import command. --- ChangeLog | 2 + html/import.html | 91 ++++++++++++++++++++++++++++++++++++++++++++ html/task.html | 3 ++ src/Makefile.am | 2 +- src/Makefile.in | 5 ++- src/import.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++++++++ src/parse.cpp | 1 + src/task.cpp | 5 +++ src/task.h | 3 ++ 9 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 html/import.html create mode 100644 src/import.cpp diff --git a/ChangeLog b/ChangeLog index c3d281d33..d0bb8b228 100644 --- a/ChangeLog +++ b/ChangeLog @@ -20,6 +20,8 @@ frequency, a due date, and an optional until date. + When a recurring task is modified, all other instances of the recurring task are also modified. + + Task can now import tasks from a variety of data formats. See online + docs for full details. ------ old releases ------------------------------ diff --git a/html/import.html b/html/import.html new file mode 100644 index 000000000..aee543927 --- /dev/null +++ b/html/import.html @@ -0,0 +1,91 @@ + + + + Data Import + + + + + +
+ + + + + + +
+ + +
+
+
+
+

Data Import

+
+
+ +
+
+
+

+ Copyright 2006-2009, P. Beckingham. All rights reserved. +

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+ +
+ + + + + + + diff --git a/html/task.html b/html/task.html index 0c471c85e..8fdd6d773 100644 --- a/html/task.html +++ b/html/task.html @@ -58,6 +58,7 @@
  • Filters
  • Shadow Files
  • Custom Reports +
  • Data Import

    @@ -116,6 +117,8 @@ frequency, a due date, and an optional until date.

  • When a recurring task is modified, all other instances of the recurring task are also modified. +
  • Task can now import tasks from a variety of data formats. See online + docs for full details.

    diff --git a/src/Makefile.am b/src/Makefile.am index 8203dbb14..c164f7999 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,2 +1,2 @@ bin_PROGRAMS = task -task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp Timer.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h Timer.h color.h task.h +task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp Timer.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp import.cpp Config.h Date.h T.h TDB.h Table.h Grid.h Timer.h color.h task.h diff --git a/src/Makefile.in b/src/Makefile.in index ae8c519e5..0cdac5fab 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -47,7 +47,7 @@ am_task_OBJECTS = Config.$(OBJEXT) Date.$(OBJEXT) T.$(OBJEXT) \ TDB.$(OBJEXT) Table.$(OBJEXT) Grid.$(OBJEXT) Timer.$(OBJEXT) \ color.$(OBJEXT) parse.$(OBJEXT) task.$(OBJEXT) \ command.$(OBJEXT) report.$(OBJEXT) util.$(OBJEXT) \ - text.$(OBJEXT) rules.$(OBJEXT) + text.$(OBJEXT) rules.$(OBJEXT) import.$(OBJEXT) task_OBJECTS = $(am_task_OBJECTS) task_LDADD = $(LDADD) DEFAULT_INCLUDES = -I. -I$(top_builddir)@am__isrc@ @@ -155,7 +155,7 @@ sysconfdir = @sysconfdir@ target_alias = @target_alias@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp Timer.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp Config.h Date.h T.h TDB.h Table.h Grid.h Timer.h color.h task.h +task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp Timer.cpp color.cpp parse.cpp task.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp import.cpp Config.h Date.h T.h TDB.h Table.h Grid.h Timer.h color.h task.h all: all-am .SUFFIXES: @@ -231,6 +231,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Timer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/import.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/report.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rules.Po@am__quote@ diff --git a/src/import.cpp b/src/import.cpp new file mode 100644 index 000000000..50504c3c6 --- /dev/null +++ b/src/import.cpp @@ -0,0 +1,98 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 "task.h" + +//////////////////////////////////////////////////////////////////////////////// +std::string handleImport (TDB& tdb, T& task, Config& conf) +{ + std::stringstream out; + + // Use the description as a file name. + std::string file = trim (task.getDescription ()); + + if (file.length () > 0) + { + out << "Not yet implemented." << std::endl; + } + else + throw std::string ("You must specify a file to import."); + + return out.str (); +} + +//////////////////////////////////////////////////////////////////////////////// +// todo.sh v2.x +// file format: (A) Walk the dog +project @context +// x 2009-03-25 Walk the dog +project @context +// priority: (A) - (Z) +// multiple projects: +project +// multiple contexts: @context + +// task >= 1.5 export +// file format: id,uuid,status,tags,entry,start,due,recur,end,project, +// priority,fg,bg,description\n + +// task < 1.5 export +// file format: id,uuid,status,tags,entry,start,due,project,priority, +// fg,bg,description\n + +// single line text +// file format: foo bar baz + +// CSV +// file format: project,priority,description + +//////////////////////////////////////////////////////////////////////////////// +void determineFileType () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void importTask_1_5 () +{ +} + +void importTask_1_6 () +{ +} + +void importTodoSh_2_0 () +{ +} + +void importSingleLine () +{ +} + +void importCSV () +{ +} + +//////////////////////////////////////////////////////////////////////////////// + diff --git a/src/parse.cpp b/src/parse.cpp index 4dacbc0d2..e372f2352 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -131,6 +131,7 @@ static const char* commands[] = "help", "history", "ghistory", + "import", "info", "next", "overdue", diff --git a/src/task.cpp b/src/task.cpp index ac3ed392b..4ccf29db3 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -172,6 +172,10 @@ static std::string shortUsage (Config& conf) table.addCell (row, 1, "task stats"); table.addCell (row, 2, "Shows task database statistics"); + row = table.addRow (); + table.addCell (row, 1, "task import"); + table.addCell (row, 2, "Imports tasks from a variety of formats"); + row = table.addRow (); table.addCell (row, 1, "task export"); table.addCell (row, 2, "Exports all tasks as a CSV file"); @@ -844,6 +848,7 @@ std::string runTaskCommand ( else if (command == "start") { cmdMod = true; out = handleStart (tdb, task, conf); } else if (command == "stop") { cmdMod = true; out = handleStop (tdb, task, conf); } else if (command == "undo") { cmdMod = true; out = handleUndo (tdb, task, conf); } + else if (command == "import") { cmdMod = true; out = handleImport (tdb, task, conf); } // Command that display IDs and therefore need TDB::gc first. diff --git a/src/task.h b/src/task.h index a2c2c75b7..788ae26ca 100644 --- a/src/task.h +++ b/src/task.h @@ -143,4 +143,7 @@ std::string expandPath (const std::string&); void initializeColorRules (Config&); void autoColorize (T&, Text::color&, Text::color&, Config&); +// import.cpp +std::string handleImport (TDB&, T&, Config&); + //////////////////////////////////////////////////////////////////////////////// From 99dc72f26f27121177ffcd88603c8760f06e361e Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 26 Mar 2009 00:41:15 -0400 Subject: [PATCH 2/7] File Import - Added format identifier code for task 1.4.3, task 1.5.0, todo.sh 2.0 and CSV. - Implemented import for type text. - Implemented util.cpp:slurp function. - Gathered sample input files for import testing, and later, unit tests. --- src/import.cpp | 222 +++++++++++++++++++++++++++++++++----- src/import/freeform.csv | 4 + src/import/line.txt | 3 + src/import/task-1.4.3.csv | 2 + src/import/task-1.5.0.csv | 2 + src/import/todo.txt | 2 + src/task.h | 2 + src/util.cpp | 26 +++++ 8 files changed, 239 insertions(+), 24 deletions(-) create mode 100644 src/import/freeform.csv create mode 100644 src/import/line.txt create mode 100644 src/import/task-1.4.3.csv create mode 100644 src/import/task-1.5.0.csv create mode 100644 src/import/todo.txt diff --git a/src/import.cpp b/src/import.cpp index 50504c3c6..a6ccf26c8 100644 --- a/src/import.cpp +++ b/src/import.cpp @@ -28,24 +28,6 @@ #include #include "task.h" -//////////////////////////////////////////////////////////////////////////////// -std::string handleImport (TDB& tdb, T& task, Config& conf) -{ - std::stringstream out; - - // Use the description as a file name. - std::string file = trim (task.getDescription ()); - - if (file.length () > 0) - { - out << "Not yet implemented." << std::endl; - } - else - throw std::string ("You must specify a file to import."); - - return out.str (); -} - //////////////////////////////////////////////////////////////////////////////// // todo.sh v2.x // file format: (A) Walk the dog +project @context @@ -69,29 +51,221 @@ std::string handleImport (TDB& tdb, T& task, Config& conf) // file format: project,priority,description //////////////////////////////////////////////////////////////////////////////// -void determineFileType () +enum fileType { + not_a_clue, + task_1_4_3, + task_1_5_0, + 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] == "'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; + } + + // 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 line. + bool commas_on_every_line = true; + 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; + break; + } + } + 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:.+ + } + + return not_a_clue; } //////////////////////////////////////////////////////////////////////////////// -void importTask_1_5 () +static std::string importTask_1_4_3 ( + TDB& tdb, + Config& conf, + const std::vector & lines) { + return "task 1.4.3\n"; } -void importTask_1_6 () +//////////////////////////////////////////////////////////////////////////////// +static std::string importTask_1_5_0 ( + TDB& tdb, + Config& conf, + const std::vector & lines) { + return "task 1.5.0\n"; } -void importTodoSh_2_0 () +//////////////////////////////////////////////////////////////////////////////// +static std::string importTodoSh_2_0 ( + TDB& tdb, + Config& conf, + const std::vector & lines) { + return "todo.sh 2.0\n"; } -void importSingleLine () +//////////////////////////////////////////////////////////////////////////////// +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::string line = lines[i]; + + // 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) + { + T task; + task.parse (std::string ("\"") + lines[i] + "\""); + if (! tdb.addT (task)) + failed.push_back (lines[i]); + } + } + + std::stringstream out; + out << "Imported " + << (lines.size () - failed.size ()) + << " tasks successfully, with " + << failed.size () + << " errors." + << std::endl; + + std::string bad; + join (bad, "\n", failed); + return out.str () + "\n" + bad; } -void importCSV () +//////////////////////////////////////////////////////////////////////////////// +static std::string importCSV ( + TDB& tdb, + Config& conf, + const std::vector & lines) { + return "CSV\n"; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string handleImport (TDB& tdb, T& task, Config& conf) +{ + std::stringstream out; + + // Use the description as a file name. + std::string file = trim (task.getDescription ()); + if (file.length () > 0) + { + // Load the file. + std::vector lines; + slurp (file, lines, true); + + // Determine which type it might be, then attempt an import. + switch (determineFileType (lines)) + { + 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 todo_sh_2_0: out << importTodoSh_2_0 (tdb, conf, lines); break; + case csv: out << importCSV (tdb, conf, lines); break; + case text: out << importText (tdb, conf, lines); break; + + case not_a_clue: + out << "?"; + break; + } + } + else + throw std::string ("You must specify a file to import."); + + return out.str (); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/import/freeform.csv b/src/import/freeform.csv new file mode 100644 index 000000000..6d60bb5f6 --- /dev/null +++ b/src/import/freeform.csv @@ -0,0 +1,4 @@ +'id','priority','description' +1,H,'this is a test' +2,,'another task' + diff --git a/src/import/line.txt b/src/import/line.txt new file mode 100644 index 000000000..3ac276232 --- /dev/null +++ b/src/import/line.txt @@ -0,0 +1,3 @@ +This is a test priority:H project:A +Another task + diff --git a/src/import/task-1.4.3.csv b/src/import/task-1.4.3.csv new file mode 100644 index 000000000..b917e0df9 --- /dev/null +++ b/src/import/task-1.4.3.csv @@ -0,0 +1,2 @@ +'id','status','tags','entry','start','due','end','project','priority','fg','bg','description' +'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 new file mode 100644 index 000000000..08c900aa9 --- /dev/null +++ b/src/import/task-1.5.0.csv @@ -0,0 +1,2 @@ +'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' diff --git a/src/import/todo.txt b/src/import/todo.txt new file mode 100644 index 000000000..8177b0c40 --- /dev/null +++ b/src/import/todo.txt @@ -0,0 +1,2 @@ +x 2009-03-25 Walk the dog +project @context +This is a test +project @context diff --git a/src/task.h b/src/task.h index 788ae26ca..2a0b8f59f 100644 --- a/src/task.h +++ b/src/task.h @@ -139,6 +139,8 @@ std::string expandPath (const std::string&); int flock (int, int); #endif +bool slurp (const std::string&, std::vector &, bool trimLines = false); + // rules.cpp void initializeColorRules (Config&); void autoColorize (T&, Text::color&, Text::color&, Config&); diff --git a/src/util.cpp b/src/util.cpp index a506377af..1fe260c89 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -25,6 +25,7 @@ // //////////////////////////////////////////////////////////////////////////////// #include +#include #include #include #include @@ -404,3 +405,28 @@ int flock (int fd, int operation) #endif //////////////////////////////////////////////////////////////////////////////// +bool slurp ( + const std::string& file, + std::vector & contents, + bool trimLines /* = false */) +{ + contents.clear (); + + std::ifstream in (file.c_str ()); + if (in.good ()) + { + std::string line; + while (getline (in, line)) + { + if (trimLines) line = trim (line); + contents.push_back (line); + } + + in.close (); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// From e4f5d6579cd5fe659887a5c860bf1914646fc285 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 26 Mar 2009 00:52:51 -0400 Subject: [PATCH 3/7] File Import - Updated documentation. - Added another recognized format - task command line. --- ChangeLog | 6 ++++-- html/task.html | 6 ++++-- src/import.cpp | 49 +++++++++++++++++++++---------------------------- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/ChangeLog b/ChangeLog index d0bb8b228..803ab37ce 100644 --- a/ChangeLog +++ b/ChangeLog @@ -20,8 +20,10 @@ frequency, a due date, and an optional until date. + When a recurring task is modified, all other instances of the recurring task are also modified. - + Task can now import tasks from a variety of data formats. See online - docs for full details. + + Task can now import tasks from a variety of data formats, including task + 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. ------ old releases ------------------------------ diff --git a/html/task.html b/html/task.html index 8fdd6d773..ce46b4a00 100644 --- a/html/task.html +++ b/html/task.html @@ -117,8 +117,10 @@ frequency, a due date, and an optional until date.

  • When a recurring task is modified, all other instances of the recurring task are also modified. -
  • Task can now import tasks from a variety of data formats. See online - docs for full details. +
  • Task can now import tasks from a variety of data formats, including task + 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.

    diff --git a/src/import.cpp b/src/import.cpp index a6ccf26c8..be3dab21e 100644 --- a/src/import.cpp +++ b/src/import.cpp @@ -28,34 +28,13 @@ #include #include "task.h" -//////////////////////////////////////////////////////////////////////////////// -// todo.sh v2.x -// file format: (A) Walk the dog +project @context -// x 2009-03-25 Walk the dog +project @context -// priority: (A) - (Z) -// multiple projects: +project -// multiple contexts: @context - -// task >= 1.5 export -// file format: id,uuid,status,tags,entry,start,due,recur,end,project, -// priority,fg,bg,description\n - -// task < 1.5 export -// file format: id,uuid,status,tags,entry,start,due,project,priority, -// fg,bg,description\n - -// single line text -// file format: foo bar baz - -// CSV -// file format: project,priority,description - //////////////////////////////////////////////////////////////////////////////// enum fileType { not_a_clue, task_1_4_3, task_1_5_0, + task_cmd_line, todo_sh_2_0, csv, text @@ -85,6 +64,8 @@ static fileType determineFileType (const std::vector & lines) return task_1_4_3; } + // TODO 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) @@ -125,7 +106,7 @@ static fileType determineFileType (const std::vector & lines) } } - // CSV - commas on every line. + // CSV - commas on every non-comment, non-trivial line. bool commas_on_every_line = true; for (unsigned int i = 0; i < lines.size (); ++i) { @@ -177,6 +158,15 @@ static std::string importTask_1_5_0 ( return "task 1.5.0\n"; } +//////////////////////////////////////////////////////////////////////////////// +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, @@ -232,6 +222,8 @@ static std::string importCSV ( Config& conf, const std::vector & lines) { + // TODO Allow any number of fields, but attempt to map into task fields. + // TODO Must have header line to name fields. return "CSV\n"; } @@ -251,11 +243,12 @@ std::string handleImport (TDB& tdb, T& task, Config& conf) // Determine which type it might be, then attempt an import. switch (determineFileType (lines)) { - 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 todo_sh_2_0: out << importTodoSh_2_0 (tdb, conf, lines); break; - case csv: out << importCSV (tdb, conf, lines); break; - case text: out << importText (tdb, conf, lines); break; + 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_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; + case text: out << importText (tdb, conf, lines); break; case not_a_clue: out << "?"; From c1291dc58738393a53089a8861bac15ca57f9442 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 27 Mar 2009 22:02:13 -0400 Subject: [PATCH 4/7] Updated Documentation - Added description of import process. --- html/import.html | 24 ++++++++++++++++++++++++ src/import.cpp | 7 ++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/html/import.html b/html/import.html index aee543927..35e9ec83f 100644 --- a/html/import.html +++ b/html/import.html @@ -34,6 +34,30 @@

    Data Import

    +

    + Tasks can be imported from files with this command: + +

    % task import file
    + + A variety of different file types are recognized by task, namely: + +
      +
    • Tasks exported from task prior to version 1.5.0. +
    • Tasks exported from task version 1.5.0 and later. The file + format changed with 1.5.0. +
    • todo.sh files. +
    • CSV files with a variety of recognized column names. +
    • Plain text files, with one task listed per line. +
    • Task command line format. +
    + + Task makes a good effort to determine which of these formats a + file is, and then imports accordingly. +

    + +

    + It would be wise to backup your task data files before an import. +


    diff --git a/src/import.cpp b/src/import.cpp index be3dab21e..0dc2ba4a8 100644 --- a/src/import.cpp +++ b/src/import.cpp @@ -240,8 +240,13 @@ std::string handleImport (TDB& tdb, T& task, Config& conf) std::vector lines; slurp (file, lines, true); + // Take a guess at the file type. + fileType type = determineFileType (lines); + + // TODO Allow an override. + // Determine which type it might be, then attempt an import. - switch (determineFileType (lines)) + switch (type) { 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; From 5f4563af2f12feb24374a5bf41d356881db11ac0 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 29 Mar 2009 15:34:35 -0400 Subject: [PATCH 5/7] 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; + From 25425614b1d14e4fba44c7e92a99fa305c8f6696 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 29 Mar 2009 17:42:11 -0400 Subject: [PATCH 6/7] File Import - Implemented all remaining import functionality. --- src/import.cpp | 256 ++++++++++++++++++++++++++++++++++++++-- src/task.h | 11 +- src/tests/import.csv.t | 70 +++++++++++ src/tests/import.todo.t | 2 +- src/tests/text.t.cpp | 20 +++- src/text.cpp | 17 ++- 6 files changed, 354 insertions(+), 22 deletions(-) create mode 100755 src/tests/import.csv.t diff --git a/src/import.cpp b/src/import.cpp index 94879404e..ce36f8480 100644 --- a/src/import.cpp +++ b/src/import.cpp @@ -26,6 +26,8 @@ //////////////////////////////////////////////////////////////////////////////// #include #include +#include +#include #include "Date.h" #include "task.h" @@ -249,7 +251,7 @@ static std::string importTask_1_4_3 ( 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) + for (unsigned int i = 0; i < tags.size (); ++i) task.addTag (tags[i]); } break; @@ -405,7 +407,7 @@ static std::string importTask_1_5_0 ( 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) + for (unsigned int i = 0; i < tags.size (); ++i) task.addTag (tags[i]); } break; @@ -566,7 +568,7 @@ static std::string importTask_1_6_0 ( 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) + for (unsigned int i = 0; i < tags.size (); ++i) task.addTag (tags[i]); } break; @@ -888,9 +890,227 @@ static std::string importCSV ( Config& conf, const std::vector & lines) { - // TODO Allow any number of fields, but attempt to map into task fields. - // TODO Must have header line to name fields. - return "CSV\n"; + std::vector failed; + + // 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 == "id" || + name == "#" || + name == "sequence" || + name.find ("num") != std::string::npos) + { + mapping["id"] = (int)h; + } + + else if (name == "uuid" || + name == "guid" || + name.find ("unique") != std::string::npos) + { + mapping["uuid"] = (int)h; + } + + else if (name == "status" || + name == "condition" || + name == "state") + { + mapping["status"] = (int)h; + } + + else if (name == "tags" || + name.find ("categor") != std::string::npos || + name.find ("tag") != std::string::npos) + { + mapping["tags"] = (int)h; + } + + else if (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 == "start" || + name.find ("began") != std::string::npos || + name.find ("begun") != std::string::npos || + name.find ("started") != std::string::npos || + name == "") + { + mapping["start"] = (int)h; + } + + else if (name == "due" || + name.find ("expected") != std::string::npos) + { + mapping["due"] = (int)h; + } + + else if (name == "recur" || + name == "frequency") + { + mapping["recur"] = (int)h; + } + + else if (name == "end" || + name == "done" || + name.find ("complete") != std::string::npos) + { + mapping["end"] = (int)h; + } + + else if (name == "project" || + name.find ("proj") != std::string::npos) + { + mapping["project"] = (int)h; + } + + else if (name == "priority" || + name == "pri" || + name.find ("importan") != std::string::npos) + { + mapping["priority"] = (int)h; + } + + else if (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 == "bg" || + name.find ("background") != std::string::npos) + { + mapping["bg"] = (int)h; + } + + else if (name.find ("desc") != std::string::npos || + name.find ("detail") != 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, ','); + + T task; + + int f; + if ((f = mapping["uuid"]) != -1) + task.setUUID (lowerCase (unquoteText (trim (fields[f])))); + + if ((f = mapping["status"]) != -1) + { + std::string value = lowerCase (unquoteText (trim (fields[f]))); + + if (value == "recurring") task.setStatus (T::recurring); + else if (value == "deleted") task.setStatus (T::deleted); + else if (value == "completed") task.setStatus (T::completed); + else task.setStatus (T::pending); + } + + 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.setAttribute ("entry", lowerCase (unquoteText (trim (fields[f])))); + + if ((f = mapping["start"]) != -1) + task.setAttribute ("start", lowerCase (unquoteText (trim (fields[f])))); + + if ((f = mapping["due"]) != -1) + task.setAttribute ("due", lowerCase (unquoteText (trim (fields[f])))); + + if ((f = mapping["recur"]) != -1) + task.setAttribute ("recur", lowerCase (unquoteText (trim (fields[f])))); + + if ((f = mapping["end"]) != -1) + task.setAttribute ("end", lowerCase (unquoteText (trim (fields[f])))); + + if ((f = mapping["project"]) != -1) + task.setAttribute ("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.setAttribute ("priority", value); + } + + if ((f = mapping["fg"]) != -1) + task.setAttribute ("fg", lowerCase (unquoteText (trim (fields[f])))); + + if ((f = mapping["bg"]) != -1) + task.setAttribute ("bg", lowerCase (unquoteText (trim (fields[f])))); + + if ((f = mapping["description"]) != -1) + task.setDescription (unquoteText (trim (fields[f]))); + + 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 (); } //////////////////////////////////////////////////////////////////////////////// @@ -926,8 +1146,25 @@ std::string handleImport (TDB& tdb, T& task, Config& conf) // 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 tasks per line."; break; + case not_a_clue: + throw std::string ("Task cannot determine which type of file this is, " + "and cannot proceed."); + } - // TODO Allow an override. + // 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) @@ -939,10 +1176,7 @@ std::string handleImport (TDB& tdb, T& task, Config& conf) case todo_sh_2_0: out << importTodoSh_2_0 (tdb, conf, lines); break; case csv: out << importCSV (tdb, conf, lines); break; case text: out << importText (tdb, conf, lines); break; - - case not_a_clue: - out << "?"; - break; + case not_a_clue: /* to stop the compiler from complaining. */ break; } } else diff --git a/src/task.h b/src/task.h index 2a0b8f59f..ab4faa514 100644 --- a/src/task.h +++ b/src/task.h @@ -108,12 +108,12 @@ std::string handleCustomReport (TDB&, T&, Config&, const std::string&); void validReportColumns (const std::vector &); void validSortColumns (const std::vector &, const std::vector &); -// util.cpp -bool confirm (const std::string&); +// text.cpp void wrapText (std::vector &, const std::string&, const int); std::string trimLeft (const std::string& in, const std::string& t = " "); std::string trimRight (const std::string& in, const std::string& t = " "); std::string trim (const std::string& in, const std::string& t = " "); +std::string unquoteText (const std::string&); void extractLine (std::string&, std::string&, int); void split (std::vector&, const std::string&, const char); void split (std::vector&, const std::string&, const std::string&); @@ -121,12 +121,15 @@ void join (std::string&, const std::string&, const std::vector&); std::string commify (const std::string&); std::string lowerCase (const std::string&); std::string upperCase (const std::string&); +const char* optionalBlankLine (Config&); + +// util.cpp +bool confirm (const std::string&); void delay (float); -int autoComplete (const std::string&, const std::vector&, std::vector&); void formatTimeDeltaDays (std::string&, time_t); std::string formatSeconds (time_t); +int autoComplete (const std::string&, const std::vector&, std::vector&); const std::string uuid (); -const char* optionalBlankLine (Config&); int convertDuration (const std::string&); std::string expandPath (const std::string&); diff --git a/src/tests/import.csv.t b/src/tests/import.csv.t new file mode 100755 index 000000000..2d1eb718e --- /dev/null +++ b/src/tests/import.csv.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','priority','description'\n", + "1,H,'this is a test'\n", + "2,,'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.+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 index e8bb578f9..dac3a6cbe 100755 --- a/src/tests/import.todo.t +++ b/src/tests/import.todo.t @@ -41,7 +41,7 @@ if (open my $fh, '>', 'import.rc') # Create import file. if (open my $fh, '>', 'import.txt') { - print $fh "x 2010-03-25 Walk the dog +project \@context\n", + print $fh "x 2009-03-25 Walk the dog +project \@context\n", "This is a test +project \@context\n", "(A) A prioritized task\n", "\n"; diff --git a/src/tests/text.t.cpp b/src/tests/text.t.cpp index 65c3d5e11..d959ea1d4 100644 --- a/src/tests/text.t.cpp +++ b/src/tests/text.t.cpp @@ -31,7 +31,7 @@ //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - UnitTest t (78); + UnitTest t (94); // void wrapText (std::vector & lines, const std::string& text, const int width) std::string text = "This is a test of the line wrapping code."; @@ -183,6 +183,24 @@ int main (int argc, char** argv) t.is (trim (" \t xxx \t "), "\t xxx \t", "trim ' \\t xxx \\t ' -> '\\t xxx \\t'"); t.is (trim (" \t xxx \t ", " \t"), "xxx", "trim ' \\t xxx \\t ' -> 'xxx'"); + // std::string unquoteText (const std::string& text) + t.is (unquoteText (""), "", "unquoteText '' -> ''"); + t.is (unquoteText ("x"), "x", "unquoteText 'x' -> 'x'"); + t.is (unquoteText ("'x"), "'x", "unquoteText ''x' -> ''x'"); + t.is (unquoteText ("x'"), "x'", "unquoteText 'x'' -> 'x''"); + t.is (unquoteText ("\"x"), "\"x", "unquoteText '\"x' -> '\"x'"); + t.is (unquoteText ("x\""), "x\"", "unquoteText 'x\"' -> 'x\"'"); + t.is (unquoteText ("''"), "", "unquoteText '''' -> ''"); + t.is (unquoteText ("'''"), "'", "unquoteText ''''' -> '''"); + t.is (unquoteText ("\"\""), "", "unquoteText '\"\"' -> ''"); + t.is (unquoteText ("\"\"\""), "\"", "unquoteText '\"\"\"' -> '\"'"); + t.is (unquoteText ("''''"), "''", "unquoteText '''''' -> ''''"); + t.is (unquoteText ("\"\"\"\""), "\"\"", "unquoteText '\"\"\"\"' -> '\"\"'"); + t.is (unquoteText ("'\"\"'"), "\"\"", "unquoteText '''\"\"' -> '\"\"'"); + t.is (unquoteText ("\"''\""), "''", "unquoteText '\"''\"' -> ''''"); + t.is (unquoteText ("'x'"), "x", "unquoteText ''x'' -> 'x'"); + t.is (unquoteText ("\"x\""), "x", "unquoteText '\"x\"' -> 'x'"); + // std::string commify (const std::string& data) t.is (commify (""), "", "commify '' -> ''"); t.is (commify ("1"), "1", "commify '1' -> '1'"); diff --git a/src/text.cpp b/src/text.cpp index fced0b0aa..69689a5ac 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -127,12 +127,19 @@ std::string trim (const std::string& in, const std::string& t /*= " "*/) //////////////////////////////////////////////////////////////////////////////// // Remove enclosing balanced quotes. Assumes trimmed text. -void unquoteText (std::string& text) +std::string unquoteText (const std::string& input) { - char quote = text[0]; - if (quote == '\'' || quote == '"') - if (text[text.length () - 1] == quote) - text = text.substr (1, text.length () - 3); + std::string output = input; + + if (output.length () > 1) + { + char quote = output[0]; + if ((quote == '\'' || quote == '"') && + output[output.length () - 1] == quote) + return output.substr (1, output.length () - 2); + } + + return output; } //////////////////////////////////////////////////////////////////////////////// From 567bdd98a413dd93fa0f33476edb8e48c4f24283 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 29 Mar 2009 17:43:21 -0400 Subject: [PATCH 7/7] Code Cleanup - Removed temporary file import samples --- src/import/cmdline.txt | 3 --- src/import/freeform.csv | 4 ---- src/import/task-1.4.3.csv | 3 --- src/import/task-1.5.0.csv | 3 --- src/import/task-1.6.0.csv | 3 --- src/import/text.txt | 4 ---- src/import/todo.txt | 4 ---- 7 files changed, 24 deletions(-) delete mode 100644 src/import/cmdline.txt delete mode 100644 src/import/freeform.csv delete mode 100644 src/import/task-1.4.3.csv delete mode 100644 src/import/task-1.5.0.csv delete mode 100644 src/import/task-1.6.0.csv delete mode 100644 src/import/text.txt delete mode 100644 src/import/todo.txt diff --git a/src/import/cmdline.txt b/src/import/cmdline.txt deleted file mode 100644 index 3ac276232..000000000 --- a/src/import/cmdline.txt +++ /dev/null @@ -1,3 +0,0 @@ -This is a test priority:H project:A -Another task - diff --git a/src/import/freeform.csv b/src/import/freeform.csv deleted file mode 100644 index 6d60bb5f6..000000000 --- a/src/import/freeform.csv +++ /dev/null @@ -1,4 +0,0 @@ -'id','priority','description' -1,H,'this is a test' -2,,'another task' - diff --git a/src/import/task-1.4.3.csv b/src/import/task-1.4.3.csv deleted file mode 100644 index 9c60aaf0d..000000000 --- a/src/import/task-1.4.3.csv +++ /dev/null @@ -1,3 +0,0 @@ -'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 deleted file mode 100644 index 6e4eff714..000000000 --- a/src/import/task-1.5.0.csv +++ /dev/null @@ -1,3 +0,0 @@ -'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 deleted file mode 100644 index 55e9f9c62..000000000 --- a/src/import/task-1.6.0.csv +++ /dev/null @@ -1,3 +0,0 @@ -'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 deleted file mode 100644 index 57bae5055..000000000 --- a/src/import/text.txt +++ /dev/null @@ -1,4 +0,0 @@ -Get milk, bread -Order cake -Clean house - diff --git a/src/import/todo.txt b/src/import/todo.txt deleted file mode 100644 index cceab0b35..000000000 --- a/src/import/todo.txt +++ /dev/null @@ -1,4 +0,0 @@ -x 2009-03-25 Walk the dog +project @context -This is a test +project @context -(A) A prioritized task -