mirror of
https://github.com/GothenburgBitFactory/timewarrior.git
synced 2025-07-27 21:23:26 +02:00
Add import command
The import command lets the user import time tracking data as produced by the export command, i.e. a JSON array of interval objects. Data can be either provided as one or more files, or via stdin. The latter enables synchronization between remote machines via ssh: timew export | ssh <server> 'timew import' Signed-off-by: Thomas Lauf <thomas.lauf@tngtech.com>
This commit is contained in:
parent
d925553116
commit
5276a9f4bd
6 changed files with 230 additions and 0 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
- #676 Add import command
|
||||||
|
(thanks to Shaun Ruffel)
|
||||||
- #677 Extension names starting with 'timew' cause problems
|
- #677 Extension names starting with 'timew' cause problems
|
||||||
(thanks to ftambara)
|
(thanks to ftambara)
|
||||||
- #661 Make display of ids and annotations the default in summary report for new users
|
- #661 Make display of ids and annotations the default in summary report for new users
|
||||||
|
|
57
doc/man1/timew-import.1.adoc
Normal file
57
doc/man1/timew-import.1.adoc
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
= timew-import(1)
|
||||||
|
|
||||||
|
== NAME
|
||||||
|
timew-import - import time-tracking data from files
|
||||||
|
|
||||||
|
== SYNOPSIS
|
||||||
|
[verse]
|
||||||
|
*timew import* [_<file>_**...**]
|
||||||
|
|
||||||
|
== DESCRIPTION
|
||||||
|
Import tracked time from `file`.
|
||||||
|
If no files are specified, the command will read from standard input.
|
||||||
|
|
||||||
|
The data to import has to be in JSON format, as exported by **timew-export**(1), i.e. a single array with interval objects.
|
||||||
|
|
||||||
|
When importing, the intervals are checked for overlaps with existing intervals.
|
||||||
|
If an overlap is found, the import will abort at the first overlap and no more intervals are imported, unless the `:adjust` hint is specified.
|
||||||
|
|
||||||
|
In general, it is recommended to create a backup of your data before importing.
|
||||||
|
|
||||||
|
== HINTS
|
||||||
|
|
||||||
|
**:adjust**::
|
||||||
|
When given, the imported interval will overwrite any existing intervals that it overlaps with.
|
||||||
|
|
||||||
|
== EXAMPLES
|
||||||
|
|
||||||
|
*Import intervals from a file*::
|
||||||
|
+
|
||||||
|
Import intervals from a single file:
|
||||||
|
+
|
||||||
|
[source]
|
||||||
|
----
|
||||||
|
timew import intervals.json
|
||||||
|
----
|
||||||
|
+
|
||||||
|
Any file path that does not start with a `/` is interpreted as relative to the current working directory.
|
||||||
|
|
||||||
|
*Import intervals from multiple files*::
|
||||||
|
+
|
||||||
|
One can also use shell wildcards and absolute paths:
|
||||||
|
+
|
||||||
|
[source]
|
||||||
|
----
|
||||||
|
timew import /path/to/intervals/*.json
|
||||||
|
----
|
||||||
|
|
||||||
|
*Import intervals from standard input*::
|
||||||
|
+
|
||||||
|
Import intervals from stdin:
|
||||||
|
+
|
||||||
|
[source]
|
||||||
|
----
|
||||||
|
timew export | ssh <server> 'timew import'
|
||||||
|
----
|
||||||
|
This is especially useful for synchronizing intervals between different machines.
|
||||||
|
+
|
|
@ -30,6 +30,7 @@ set (commands_SRCS CmdAnnotate.cpp
|
||||||
CmdGaps.cpp
|
CmdGaps.cpp
|
||||||
CmdGet.cpp
|
CmdGet.cpp
|
||||||
CmdHelp.cpp
|
CmdHelp.cpp
|
||||||
|
CmdImport.cpp
|
||||||
CmdJoin.cpp
|
CmdJoin.cpp
|
||||||
CmdLengthen.cpp
|
CmdLengthen.cpp
|
||||||
CmdModify.cpp
|
CmdModify.cpp
|
||||||
|
|
167
src/commands/CmdImport.cpp
Normal file
167
src/commands/CmdImport.cpp
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Copyright 2025, Gothenburg Bit Factory.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// https://www.opensource.org/licenses/mit-license.php
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <commands.h>
|
||||||
|
#include <format.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <timew.h>
|
||||||
|
#include <IntervalFactory.h>
|
||||||
|
#include <JSON.h>
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
std::vector<Interval> parse_content (const std::string& content)
|
||||||
|
{
|
||||||
|
const std::unique_ptr<json::value> json(json::parse (content));
|
||||||
|
|
||||||
|
if (content.empty() || (json == nullptr))
|
||||||
|
{
|
||||||
|
throw std::string ("Contents invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json->type () != json::j_array)
|
||||||
|
{
|
||||||
|
throw std::string ("Expected JSON array for import data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Interval> intervals;
|
||||||
|
|
||||||
|
for (const auto item: dynamic_cast<json::array *> (json.get ())->_data)
|
||||||
|
{
|
||||||
|
Interval new_interval = IntervalFactory::fromJson (item->dump ());
|
||||||
|
intervals.push_back (new_interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return intervals;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
std::string read_input ()
|
||||||
|
{
|
||||||
|
std::string content;
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline (std::cin, line))
|
||||||
|
{
|
||||||
|
content += line;
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
std::vector<Interval> import_file (const std::string& file_name)
|
||||||
|
{
|
||||||
|
Path file_path;
|
||||||
|
|
||||||
|
if (file_name.empty ())
|
||||||
|
{
|
||||||
|
throw format ("Attempted to import from empty file name!", file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_name.at (0) == '/')
|
||||||
|
{
|
||||||
|
file_path = file_name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
file_path = Directory::cwd () + "/" + file_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string content;
|
||||||
|
|
||||||
|
if (const bool exists = file_path.exists (); exists && File::read (file_path, content))
|
||||||
|
{
|
||||||
|
return parse_content (content);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw format ("File {1} does not exist or cannot be read!", file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void import_intervals (
|
||||||
|
const CLI& cli,
|
||||||
|
const Rules& rules,
|
||||||
|
Database& database,
|
||||||
|
Journal& journal,
|
||||||
|
const bool verbose,
|
||||||
|
std::vector<Interval>& intervals)
|
||||||
|
{
|
||||||
|
journal.startTransaction ();
|
||||||
|
for (auto& interval: intervals)
|
||||||
|
{
|
||||||
|
// Add each interval to the database
|
||||||
|
if (validate (cli, rules, database, interval))
|
||||||
|
{
|
||||||
|
database.addInterval (interval, verbose);
|
||||||
|
database.commit ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
journal.endTransaction ();
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
int CmdImport (
|
||||||
|
CLI& cli,
|
||||||
|
Rules& rules,
|
||||||
|
Database &database,
|
||||||
|
Journal &journal)
|
||||||
|
{
|
||||||
|
const bool verbose = rules.getBoolean ("verbose");
|
||||||
|
|
||||||
|
if (const auto fileNames = cli.getWords (); fileNames.empty ())
|
||||||
|
{
|
||||||
|
const auto content = read_input ();
|
||||||
|
auto intervals = parse_content (content);
|
||||||
|
|
||||||
|
import_intervals (cli, rules, database, journal, verbose, intervals);
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
std::cout << "Imported " << intervals.size () << " interval(s)." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const auto& fileName: fileNames)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto intervals = import_file (fileName);
|
||||||
|
|
||||||
|
import_intervals (cli, rules, database, journal, verbose, intervals);
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
std::cout << "Imported " << intervals.size () << " interval(s) from '" << fileName << "'." << std::endl;
|
||||||
|
}
|
||||||
|
} catch (const std::string& error)
|
||||||
|
{
|
||||||
|
throw format ("Error importing '{1}': {2}", fileName, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -47,6 +47,7 @@ int CmdGaps (CLI&, Rules&, Database& );
|
||||||
int CmdGet (CLI&, Rules&, Database& );
|
int CmdGet (CLI&, Rules&, Database& );
|
||||||
int CmdHelpUsage ( const Extensions&);
|
int CmdHelpUsage ( const Extensions&);
|
||||||
int CmdHelp (CLI&, const Extensions&);
|
int CmdHelp (CLI&, const Extensions&);
|
||||||
|
int CmdImport (CLI&, Rules&, Database&, Journal& );
|
||||||
int CmdJoin (CLI&, Rules&, Database&, Journal& );
|
int CmdJoin (CLI&, Rules&, Database&, Journal& );
|
||||||
int CmdLengthen (CLI&, Rules&, Database&, Journal& );
|
int CmdLengthen (CLI&, Rules&, Database&, Journal& );
|
||||||
int CmdModify (CLI&, Rules&, Database&, Journal& );
|
int CmdModify (CLI&, Rules&, Database&, Journal& );
|
||||||
|
|
|
@ -63,6 +63,7 @@ void initializeEntities (CLI& cli)
|
||||||
cli.entity ("command", "help");
|
cli.entity ("command", "help");
|
||||||
cli.entity ("command", "--help");
|
cli.entity ("command", "--help");
|
||||||
cli.entity ("command", "-h");
|
cli.entity ("command", "-h");
|
||||||
|
cli.entity ("command", "import");
|
||||||
cli.entity ("command", "join");
|
cli.entity ("command", "join");
|
||||||
cli.entity ("command", "lengthen");
|
cli.entity ("command", "lengthen");
|
||||||
cli.entity ("command", "modify");
|
cli.entity ("command", "modify");
|
||||||
|
@ -235,6 +236,7 @@ int dispatchCommand (
|
||||||
else if (command == "help" ||
|
else if (command == "help" ||
|
||||||
command == "--help" ||
|
command == "--help" ||
|
||||||
command == "-h") status = CmdHelp (cli, extensions);
|
command == "-h") status = CmdHelp (cli, extensions);
|
||||||
|
else if (command == "import") status = CmdImport (cli, rules, database, journal );
|
||||||
else if (command == "join") status = CmdJoin (cli, rules, database, journal );
|
else if (command == "join") status = CmdJoin (cli, rules, database, journal );
|
||||||
else if (command == "lengthen") status = CmdLengthen (cli, rules, database, journal );
|
else if (command == "lengthen") status = CmdLengthen (cli, rules, database, journal );
|
||||||
else if (command == "modify") status = CmdModify (cli, rules, database, journal );
|
else if (command == "modify") status = CmdModify (cli, rules, database, journal );
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue