diff --git a/ChangeLog b/ChangeLog index 2733fad11..3e2be8a59 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 2.4.5 () - +- TW-1440 "task import" from STDIN (thanks to Renato Alves). - TW-1572 Better urgency inheritance (thanks to Jens Erat). ------ current release --------------------------- diff --git a/doc/man/task.1.in b/doc/man/task.1.in index 5e53c7593..5ef0bd27a 100644 --- a/doc/man/task.1.in +++ b/doc/man/task.1.in @@ -345,13 +345,15 @@ In general, this is not the recommended method of modifying tasks, but is provided for exceptional circumstances. Use carefully. .TP -.B task import [ ...] +.B task import [ ...] Imports tasks in the JSON format. The standard task release comes with a few example scripts, such as: import-todo.sh.pl import-yaml.pl +If no file or "-" is specified, import tasks from STDIN. + .TP .B task log Adds a new task that is already completed, to the task list. diff --git a/src/commands/CmdImport.cpp b/src/commands/CmdImport.cpp index cb226d8f3..de6e71a58 100644 --- a/src/commands/CmdImport.cpp +++ b/src/commands/CmdImport.cpp @@ -41,7 +41,7 @@ extern Context context; CmdImport::CmdImport () { _keyword = "import"; - _usage = "task import [ ...]"; + _usage = "task import [ ...]"; _description = STRING_CMD_IMPORT_USAGE; _read_only = false; _displays_id = false; @@ -53,45 +53,38 @@ int CmdImport::execute (std::string& output) int rc = 0; int count = 0; - // Use the description as a file name. + // Get filenames from command line arguments. std::vector words = context.cli.getWords (); - if (! words.size ()) - throw std::string (STRING_CMD_IMPORT_NOFILE); - - for (auto& word : words) + if (! words.size () || (words.size () == 1 && words[0] == "-")) { - File incoming (word); - if (! incoming.exists ()) - throw format (STRING_CMD_IMPORT_MISSING, word); - - std::cout << format (STRING_CMD_IMPORT_FILE, word) << "\n"; - - // Load the file. + // No files or only "-" specified, import tasks from STDIN. std::vector lines; - incoming.read (lines); + std::string line; - for (auto& line : lines) + std::cout << format (STRING_CMD_IMPORT_FILE, "STDIN") << "\n"; + + while (std::getline (std::cin, line)) + lines.push_back (line); + + if (lines.size () > 0) + count = import (lines); + } + else + { + // Import tasks from all specified files. + for (auto& word : words) { - std::string object = trimLeft ( - trimRight ( - trim (line), - "]"), - "["); + File incoming (word); + if (! incoming.exists ()) + throw format (STRING_CMD_IMPORT_MISSING, word); - // Skip blanks. May be caused by the trim calls above. - if (! object.length ()) - continue; + std::cout << format (STRING_CMD_IMPORT_FILE, word) << "\n"; - // Parse the whole thing. - Task task (object); - context.tdb2.add (task); + // Load the file. + std::vector lines; + incoming.read (lines); - ++count; - std::cout << " " - << task.get ("uuid") - << " " - << task.get ("description") - << "\n"; + count += import (lines); } } @@ -100,3 +93,34 @@ int CmdImport::execute (std::string& output) } //////////////////////////////////////////////////////////////////////////////// +int CmdImport::import (std::vector & lines) +{ + int count = 0; + for (auto& line : lines) + { + std::string object = trimLeft ( + trimRight ( + trim (line), + "]"), + "["); + + // Skip blanks. May be caused by the trim calls above. + if (! object.length ()) + continue; + + // Parse the whole thing. + Task task (object); + context.tdb2.add (task); + + ++count; + std::cout << " " + << task.get ("uuid") + << " " + << task.get ("description") + << "\n"; + } + + return count; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdImport.h b/src/commands/CmdImport.h index 72438e6e2..278fa0e2e 100644 --- a/src/commands/CmdImport.h +++ b/src/commands/CmdImport.h @@ -35,6 +35,7 @@ class CmdImport : public Command public: CmdImport (); int execute (std::string&); + int import (std::vector & lines); }; /* diff --git a/src/l10n/deu-DEU.h b/src/l10n/deu-DEU.h index b4fbdd662..2e33b07dd 100644 --- a/src/l10n/deu-DEU.h +++ b/src/l10n/deu-DEU.h @@ -449,7 +449,6 @@ #define STRING_CMD_IMPORT_USAGE "Importiert eine JSON-Datei" #define STRING_CMD_IMPORT_SUMMARY "{1} Aufgabe importiert." -#define STRING_CMD_IMPORT_NOFILE "Sie müssen eine zu importierende Datei angeben." #define STRING_CMD_IMPORT_FILE "Importiere '{1}'" #define STRING_CMD_IMPORT_MISSING "Datei '{1}' nicht gefunden." #define STRING_TASK_NO_DESC "Kommentar fehlt Beschreibung: {1}" diff --git a/src/l10n/eng-USA.h b/src/l10n/eng-USA.h index 3f9ec9784..f0e15cdad 100644 --- a/src/l10n/eng-USA.h +++ b/src/l10n/eng-USA.h @@ -449,7 +449,6 @@ #define STRING_CMD_IMPORT_USAGE "Imports JSON files" #define STRING_CMD_IMPORT_SUMMARY "Imported {1} tasks." -#define STRING_CMD_IMPORT_NOFILE "You must specify a file to import." #define STRING_CMD_IMPORT_FILE "Importing '{1}'" #define STRING_CMD_IMPORT_MISSING "File '{1}' not found." #define STRING_TASK_NO_DESC "Annotation is missing a description: {1}" diff --git a/src/l10n/epo-RUS.h b/src/l10n/epo-RUS.h index 1bbb32a8c..1aa4a64f4 100644 --- a/src/l10n/epo-RUS.h +++ b/src/l10n/epo-RUS.h @@ -449,7 +449,6 @@ #define STRING_CMD_IMPORT_USAGE "Importas JSON-dosierojn" #define STRING_CMD_IMPORT_SUMMARY "Importis {1} taskojn." -#define STRING_CMD_IMPORT_NOFILE "Vi devas specifi kiu dosieron import." #define STRING_CMD_IMPORT_FILE "Importanta '{1}'" #define STRING_CMD_IMPORT_MISSING "File '{1}' not found." #define STRING_TASK_NO_DESC "Komento havas mankon de priskribo: {1}" diff --git a/src/l10n/esp-ESP.h b/src/l10n/esp-ESP.h index 8c702ae5e..1ec86747a 100644 --- a/src/l10n/esp-ESP.h +++ b/src/l10n/esp-ESP.h @@ -457,7 +457,6 @@ #define STRING_CMD_IMPORT_USAGE "Importa archivos JSON" #define STRING_CMD_IMPORT_SUMMARY "Importadas {1} tareas." -#define STRING_CMD_IMPORT_NOFILE "Debe especificar un archivo a importar." #define STRING_CMD_IMPORT_FILE "Importando '{1}'" #define STRING_CMD_IMPORT_MISSING "Archivo '{1}' no encontrado." #define STRING_TASK_NO_DESC "La anotación carece de descripción: {1}" diff --git a/src/l10n/fra-FRA.h b/src/l10n/fra-FRA.h index 07540fc46..7495ffb8c 100644 --- a/src/l10n/fra-FRA.h +++ b/src/l10n/fra-FRA.h @@ -449,7 +449,6 @@ #define STRING_CMD_IMPORT_USAGE "Imports JSON files" #define STRING_CMD_IMPORT_SUMMARY "Imported {1} tasks." -#define STRING_CMD_IMPORT_NOFILE "You must specify a file to import." #define STRING_CMD_IMPORT_FILE "Importing '{1}'" #define STRING_CMD_IMPORT_MISSING "File '{1}' not found." #define STRING_TASK_NO_DESC "Annotation is missing a description: {1}" diff --git a/src/l10n/ita-ITA.h b/src/l10n/ita-ITA.h index 1f45ce915..0008652f0 100644 --- a/src/l10n/ita-ITA.h +++ b/src/l10n/ita-ITA.h @@ -448,7 +448,6 @@ #define STRING_CMD_IMPORT_USAGE "Importa file JSON" #define STRING_CMD_IMPORT_SUMMARY "Importati {1} task." -#define STRING_CMD_IMPORT_NOFILE "Specificare il file da importare." #define STRING_CMD_IMPORT_FILE "Importazione di '{1}'" #define STRING_CMD_IMPORT_MISSING "File '{1}' not found." #define STRING_TASK_NO_DESC "Annotazione senza descrizione: {1}" diff --git a/src/l10n/jpn-JPN.h b/src/l10n/jpn-JPN.h index 672694fe4..f5dd4c6fc 100644 --- a/src/l10n/jpn-JPN.h +++ b/src/l10n/jpn-JPN.h @@ -449,7 +449,6 @@ #define STRING_CMD_IMPORT_USAGE "JSON ファイルをインポート" #define STRING_CMD_IMPORT_SUMMARY "{1} task をインポートしました。" -#define STRING_CMD_IMPORT_NOFILE "インポートするファイルを指定してください。" #define STRING_CMD_IMPORT_FILE "'{1}' をインポート中" #define STRING_CMD_IMPORT_MISSING "ファイル '{1}' が見つかりません。" #define STRING_TASK_NO_DESC "Annotation is missing a description: {1}" diff --git a/src/l10n/pol-POL.h b/src/l10n/pol-POL.h index 928c35e20..634e40604 100644 --- a/src/l10n/pol-POL.h +++ b/src/l10n/pol-POL.h @@ -449,7 +449,6 @@ #define STRING_CMD_IMPORT_USAGE "Importuje pliki JSON" #define STRING_CMD_IMPORT_SUMMARY "Zaimportowano {1} zadań." -#define STRING_CMD_IMPORT_NOFILE "Musisz podać plik do zaimportowania." #define STRING_CMD_IMPORT_FILE "Importowanie '{1}'" #define STRING_CMD_IMPORT_MISSING "File '{1}' not found." #define STRING_TASK_NO_DESC "Komentarz nie posiada treści: {1}" diff --git a/src/l10n/por-PRT.h b/src/l10n/por-PRT.h index be2e1c438..47496bb7e 100644 --- a/src/l10n/por-PRT.h +++ b/src/l10n/por-PRT.h @@ -449,7 +449,6 @@ #define STRING_CMD_IMPORT_USAGE "Importa ficheiros JSON" #define STRING_CMD_IMPORT_SUMMARY "Importadas {1} tarefas." -#define STRING_CMD_IMPORT_NOFILE "Necessita especificar o ficheiro a importar." #define STRING_CMD_IMPORT_FILE "A importar '{1}'" #define STRING_CMD_IMPORT_MISSING "File '{1}' not found." #define STRING_TASK_NO_DESC "Descrição da anotação em falta: {1}" diff --git a/test/import_stdin.t b/test/import_stdin.t new file mode 100755 index 000000000..d33f2933c --- /dev/null +++ b/test/import_stdin.t @@ -0,0 +1,63 @@ +#!/usr/bin/env python2.7 +# -*- coding: utf-8 -*- +############################################################################### +# +# Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# http://www.opensource.org/licenses/mit-license.php +# +############################################################################### + +import sys +import os +import unittest +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase + + +class TestImportSTDIN(TestCase): + def setUp(self): + self.t = Task() + self.t.config("report.foo.description", "foo") + self.t.config("report.foo.columns", "id,description,tags") + self.t.config("report.foo.sort", "id+") + + def test_import_stdin(self): + """When no files specified, import from STDIN""" + json_data = """ + {"uuid":"00000000-0000-0000-0000-000000000000","description":"zero","project":"A","status":"pending","entry":"1234567889"} + {"uuid":"11111111-1111-1111-1111-111111111111","description":"one","project":"B","status":"pending","entry":"1234567889"} + {"uuid":"22222222-2222-2222-2222-222222222222","description":"two","status":"completed","entry":"1234524689","end":"1234524690"}""" # borrowed from Perl import test + code, out, err = self.t("import", input=json_data) + self.assertRegexpMatches(err, "Imported 3 tasks.", "import all tasks") + code, out, err = self.t("foo") + self.assertRegexpMatches(out, "1\s+zero", "first task present") + self.assertRegexpMatches(out, "2\s+one", "second task present") + self.assertRegexpMatches(out, "-\s+two", "third task present") + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4