From 576af3fd5f9b88c957f7a2414df29cd9bcab3df5 Mon Sep 17 00:00:00 2001 From: Thomas Lauf Date: Thu, 7 Nov 2024 14:03:53 +0100 Subject: [PATCH] Add the sub-command `range` to the `modify` command. Additionally, this also makes the syntax of the `modify` command more flexible. Before, the sub-command had to follow right after the `modify` command, now one can also write it with the id between command and sub-command: ``` $ timew modify @1 start 09:30 ``` The `range` sub-command modifies both start and end time of the interval. It expects a range as parameter, e.g. ``` $ timew modify @3 range 08:15 - 13:37 ``` Closes #627 Signed-off-by: Thomas Lauf --- ChangeLog | 1 + doc/man1/timew-modify.1.adoc | 41 ++++++++++---- src/CLI.cpp | 26 +++++++++ src/CLI.h | 2 + src/commands/CMakeLists.txt | 3 + src/commands/CmdAnnotate.cpp | 2 +- src/commands/CmdChart.cpp | 10 ++-- src/commands/CmdConfig.cpp | 2 +- src/commands/CmdContinue.cpp | 2 +- src/commands/CmdDelete.cpp | 2 +- src/commands/CmdExport.cpp | 2 +- src/commands/CmdFill.cpp | 2 +- src/commands/CmdGaps.cpp | 2 +- src/commands/CmdGet.cpp | 2 +- src/commands/CmdHelp.cpp | 3 +- src/commands/CmdJoin.cpp | 2 +- src/commands/CmdLengthen.cpp | 2 +- src/commands/CmdModify.cpp | 90 ++++-------------------------- src/commands/CmdModifyEnd.cpp | 97 ++++++++++++++++++++++++++++++++ src/commands/CmdModifyRange.cpp | 99 +++++++++++++++++++++++++++++++++ src/commands/CmdModifyStart.cpp | 93 +++++++++++++++++++++++++++++++ src/commands/CmdMove.cpp | 2 +- src/commands/CmdReport.cpp | 2 +- src/commands/CmdResize.cpp | 2 +- src/commands/CmdRetag.cpp | 2 +- src/commands/CmdShorten.cpp | 2 +- src/commands/CmdSplit.cpp | 2 +- src/commands/CmdStart.cpp | 2 +- src/commands/CmdStop.cpp | 2 +- src/commands/CmdSummary.cpp | 2 +- src/commands/CmdTag.cpp | 2 +- src/commands/CmdTags.cpp | 2 +- src/commands/CmdTrack.cpp | 2 +- src/commands/CmdUntag.cpp | 2 +- src/commands/commands.h | 73 ++++++++++++------------ src/init.cpp | 2 +- src/timew.h | 2 +- test/modify.t | 38 +++++++++++-- 38 files changed, 467 insertions(+), 159 deletions(-) create mode 100644 src/commands/CmdModifyEnd.cpp create mode 100644 src/commands/CmdModifyRange.cpp create mode 100644 src/commands/CmdModifyStart.cpp diff --git a/ChangeLog b/ChangeLog index 54a940b5..c33f4a2b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ +- #658 Add sub-command 'range' to command 'modify' - #620 Fix installation of man pages from tarball - #600 Add retag command to internal help (thanks to Stefan Herold) diff --git a/doc/man1/timew-modify.1.adoc b/doc/man1/timew-modify.1.adoc index 29093c74..1fe3b6c4 100644 --- a/doc/man1/timew-modify.1.adoc +++ b/doc/man1/timew-modify.1.adoc @@ -1,26 +1,47 @@ = timew-modify(1) == NAME -timew-modify - change start or end date of an interval +timew-modify - change the range of an interval == SYNOPSIS [verse] *timew modify* (*start*|*end*) __ __ +*timew modify* range __ __ == DESCRIPTION -The 'modify' command is used to change the start or end date of an interval. -Using the 'summary' command, and specifying the ':ids' hint shows interval IDs. -Using the right ID, you can identify an interval to modify. +The 'modify' command is used to change range of an interval. + +Using the 'start' or 'end' subcommand, one can either specify a new start or end date respectively, or with the 'range' subcommand, change the complete range. +The interval to be modified is specified via its id. + +If the resulting interval overlaps with an existing interval, the command will return an error. +One can the ':adjust' hint to force an overwrite in this case. + +See **timew-summary**(1) on how to retrieve the interval id. == EXAMPLES -For example, show the IDs: +*Modify the start date of an interval*:: ++ + $ timew modify start @3 2020-12-28T17:00 ++ +This sets the start of interval '@3' to '17:00' of date '2020-12-28'. +If this datetime is after the end of the interval, the command will return an error. - $ timew summary :week :ids - -Then having selected '@3' as the interval you wish to modify: - - $ timew modify end @3 2020-12-28T17:00:00 +*Modify the end date of an interval*:: ++ +If the interval to be modified has the same date as today, it can be omitted: ++ + $ timew modify end @3 18:00 ++ +Similar to when modifying the interval start, the end datetime has to be after the start datetime. +*Modify the range of an interval*:: ++ +Instead of modifying start and end separately, those can be combined into a single call of the 'range' subcommand: ++ + $ timew modify range @3 2020-12-28T17:00 - 2020-12-28T18:00 ++ +As in the examples above, the date portion can be omitted, if the date of the interval is today. == SEE ALSO **timew-lengthen**(1), diff --git a/src/CLI.cpp b/src/CLI.cpp index 0846ab5b..3a113e3f 100644 --- a/src/CLI.cpp +++ b/src/CLI.cpp @@ -293,6 +293,32 @@ std::vector CLI::getWords () const return words; } +//////////////////////////////////////////////////////////////////////////////// +// Find and mark sub-command, return empty string if not found +std::string CLI::findSubCommand(const std::set& subCommands) { + for (auto &a: _args) { + if (a.hasTag("BINARY") && + a.hasTag("CMD") && + a.hasTag("CONFIG") && + a.hasTag("HINT")) { + continue; + } + if (subCommands.find(a.attribute("raw")) != subCommands.end()) { + a.tag ("CMD"); + return a.attribute("raw"); + } + } + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +// Find and mark sub-command, return given default if not found +std::string CLI::getSubCommand(const std::set& subCommands, const std::string& defaultCommand) { + const auto& subCommand = findSubCommand (subCommands); + + return subCommand.empty () ? defaultCommand : subCommand; +} + //////////////////////////////////////////////////////////////////////////////// // Search for 'value' in _entities category, return canonicalized value. bool CLI::canonicalize ( diff --git a/src/CLI.h b/src/CLI.h index 6ee4d5a0..8b74bdbc 100644 --- a/src/CLI.h +++ b/src/CLI.h @@ -69,6 +69,8 @@ public: std::string getCommand () const; bool getComplementaryHint (const std::string&, bool) const; bool getHint(const std::string&, bool) const; + std::string findSubCommand(const std::set&); + std::string getSubCommand(const std::set&, const std::string&); std::set getIds () const; std::set getTags () const; std::string getAnnotation() const; diff --git a/src/commands/CMakeLists.txt b/src/commands/CMakeLists.txt index aaff9ca7..9f0ced41 100644 --- a/src/commands/CMakeLists.txt +++ b/src/commands/CMakeLists.txt @@ -33,6 +33,9 @@ set (commands_SRCS CmdAnnotate.cpp CmdJoin.cpp CmdLengthen.cpp CmdModify.cpp + CmdModifyEnd.cpp + CmdModifyRange.cpp + CmdModifyStart.cpp CmdMove.cpp CmdReport.cpp CmdResize.cpp diff --git a/src/commands/CmdAnnotate.cpp b/src/commands/CmdAnnotate.cpp index 55d503e5..0aaa6713 100644 --- a/src/commands/CmdAnnotate.cpp +++ b/src/commands/CmdAnnotate.cpp @@ -34,7 +34,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdAnnotate ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdChart.cpp b/src/commands/CmdChart.cpp index 7d26936d..3b3b817a 100644 --- a/src/commands/CmdChart.cpp +++ b/src/commands/CmdChart.cpp @@ -36,13 +36,13 @@ #include #include -int renderChart (const std::string&, const CLI&, Rules&, Database&); +int renderChart (const std::string&, CLI&, Rules&, Database&); std::map createHolidayMap (Rules&, Range&); //////////////////////////////////////////////////////////////////////////////// int CmdChartDay ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database) { @@ -51,7 +51,7 @@ int CmdChartDay ( //////////////////////////////////////////////////////////////////////////////// int CmdChartWeek ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database) { @@ -60,7 +60,7 @@ int CmdChartWeek ( //////////////////////////////////////////////////////////////////////////////// int CmdChartMonth ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database) { @@ -70,7 +70,7 @@ int CmdChartMonth ( //////////////////////////////////////////////////////////////////////////////// int renderChart ( const std::string& type, - const CLI& cli, + CLI& cli, Rules& rules, Database& database) { diff --git a/src/commands/CmdConfig.cpp b/src/commands/CmdConfig.cpp index 9725003c..dd8d7146 100644 --- a/src/commands/CmdConfig.cpp +++ b/src/commands/CmdConfig.cpp @@ -35,7 +35,7 @@ // timew config name Remove name // timew config Show all config int CmdConfig ( - const CLI& cli, + CLI& cli, Rules& rules, Journal& journal) { diff --git a/src/commands/CmdContinue.cpp b/src/commands/CmdContinue.cpp index e743aae8..e5a87520 100644 --- a/src/commands/CmdContinue.cpp +++ b/src/commands/CmdContinue.cpp @@ -36,7 +36,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdContinue ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdDelete.cpp b/src/commands/CmdDelete.cpp index c90fb246..86fcb86c 100644 --- a/src/commands/CmdDelete.cpp +++ b/src/commands/CmdDelete.cpp @@ -32,7 +32,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdDelete ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdExport.cpp b/src/commands/CmdExport.cpp index 0832e700..d466f8d4 100644 --- a/src/commands/CmdExport.cpp +++ b/src/commands/CmdExport.cpp @@ -34,7 +34,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdExport ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database) { diff --git a/src/commands/CmdFill.cpp b/src/commands/CmdFill.cpp index 8395ea8b..4933f572 100644 --- a/src/commands/CmdFill.cpp +++ b/src/commands/CmdFill.cpp @@ -33,7 +33,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdFill ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdGaps.cpp b/src/commands/CmdGaps.cpp index 4234f4cb..5280229b 100644 --- a/src/commands/CmdGaps.cpp +++ b/src/commands/CmdGaps.cpp @@ -32,7 +32,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdGaps ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database) { diff --git a/src/commands/CmdGet.cpp b/src/commands/CmdGet.cpp index 51266a9d..8bc25c5e 100644 --- a/src/commands/CmdGet.cpp +++ b/src/commands/CmdGet.cpp @@ -33,7 +33,7 @@ //////////////////////////////////////////////////////////////////////////////// // Іdentify DOM references in cli, provide space-separated results. int CmdGet ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database) { diff --git a/src/commands/CmdHelp.cpp b/src/commands/CmdHelp.cpp index 608dc6c0..82395b33 100644 --- a/src/commands/CmdHelp.cpp +++ b/src/commands/CmdHelp.cpp @@ -52,6 +52,7 @@ int CmdHelpUsage (const Extensions& extensions) << " timew join @ @\n" << " timew lengthen @ [@ ...] \n" << " timew modify (start|end) @ \n" + << " timew modify range @ \n" << " timew month [] [ ...]\n" << " timew move @ \n" << " timew [report] [] [ ...]\n" @@ -113,7 +114,7 @@ int CmdHelpUsage (const Extensions& extensions) } int CmdHelp ( - const CLI& cli, + CLI& cli, const Extensions& extensions) { auto words = cli.getWords (); diff --git a/src/commands/CmdJoin.cpp b/src/commands/CmdJoin.cpp index 8277c6b0..1a2d531c 100644 --- a/src/commands/CmdJoin.cpp +++ b/src/commands/CmdJoin.cpp @@ -33,7 +33,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdJoin ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdLengthen.cpp b/src/commands/CmdLengthen.cpp index 7a1b606a..3348a9b9 100644 --- a/src/commands/CmdLengthen.cpp +++ b/src/commands/CmdLengthen.cpp @@ -33,7 +33,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdLengthen ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdModify.cpp b/src/commands/CmdModify.cpp index fa64a1b7..7cc5efe4 100644 --- a/src/commands/CmdModify.cpp +++ b/src/commands/CmdModify.cpp @@ -24,105 +24,37 @@ // //////////////////////////////////////////////////////////////////////////////// -#include -#include #include #include -#include //////////////////////////////////////////////////////////////////////////////// int CmdModify ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) { - const bool verbose = rules.getBoolean ("verbose"); + const auto subCommand = cli.findSubCommand (std::set {"start", "end", "range"}); - auto words = cli.getWords (); - - enum {MODIFY_START, MODIFY_END} op = MODIFY_START; - - if (words.empty()) + if (subCommand.empty()) { - throw std::string ("Must specify start|end command to modify. See 'timew help modify'."); + throw std::string ("Must specify start|end|range command to modify. See 'timew help modify'."); } - if (words.at (0) == "start") + if (subCommand == "start") { - op = MODIFY_START; + return CmdModifyStart (cli, rules, database, journal); } - else if (words.at (0) == "end") + if (subCommand == "end") { - op = MODIFY_END; + return CmdModifyEnd (cli, rules, database, journal); } - else + if (subCommand == "range") { - throw format ("Must specify start|end command to modify. See 'timew help modify'.", words.at (0)); + return CmdModifyRange (cli, rules, database, journal); } - auto ids = cli.getIds (); - - if (ids.empty ()) - { - throw std::string ("ID must be specified. See 'timew help modify'."); - } - - if (ids.size () > 1) - { - throw std::string ("Only one ID may be specified. See 'timew help modify'."); - } - - auto range = cli.getRange ({0, 0}); - - int id = *ids.begin(); - - flattenDatabase (database, rules); - auto filtering = IntervalFilterAllWithIds (ids); - auto intervals = getTracked (database, rules, filtering); - - if (intervals.empty()) - { - throw format ("ID '@{1}' does not correspond to any tracking.", id); - } - - assert (intervals.size () == 1); - if (! range.is_started ()) - { - throw std::string ("No updated time specified. See 'timew help modify'."); - } - - const Interval interval = intervals.at (0); - Interval modified {interval}; - switch (op) - { - case MODIFY_START: - modified.start = range.start; - break; - - case MODIFY_END: - if (interval.is_open ()) - { - throw format ("Cannot modify end of open interval @{1}.", id); - } - modified.end = range.start; - break; - } - - if (! modified.is_open () && (modified.start > modified.end)) - { - throw format ("Cannot modify interval @{1} where start is after end.", id); - } - - journal.startTransaction (); - - database.deleteInterval (interval); - validate (cli, rules, database, modified); - database.addInterval (modified, verbose); - - journal.endTransaction(); - - return 0; + throw format ("Command 'modify' has no subcommand '{1}' defined!", subCommand); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdModifyEnd.cpp b/src/commands/CmdModifyEnd.cpp new file mode 100644 index 00000000..873b74c0 --- /dev/null +++ b/src/commands/CmdModifyEnd.cpp @@ -0,0 +1,97 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024, Thomas Lauf, 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +int CmdModifyEnd ( + CLI& cli, + Rules& rules, + Database& database, + Journal& journal) +{ + const bool verbose = rules.getBoolean ("verbose"); + + auto ids = cli.getIds (); + + if (ids.empty ()) + { + throw std::string ("ID must be specified. See 'timew help modify'."); + } + + if (ids.size () > 1) + { + throw std::string ("Only one ID may be specified. See 'timew help modify'."); + } + + auto range = cli.getRange ({0, 0}); + + int id = *ids.begin(); + + flattenDatabase (database, rules); + auto filtering = IntervalFilterAllWithIds (ids); + auto intervals = getTracked (database, rules, filtering); + + if (intervals.empty()) + { + throw format ("ID '@{1}' does not correspond to any tracking.", id); + } + + assert (intervals.size () == 1); + if (! range.is_started ()) + { + throw std::string ("No updated time specified. See 'timew help modify'."); + } + + const Interval interval = intervals.at (0); + Interval modified {interval}; + if (interval.is_open ()) + { + throw format ("Cannot modify end of open interval @{1}.", id); + } + modified.end = range.start; + + if (! modified.is_open () && (modified.start > modified.end)) + { + throw format ("Cannot modify interval @{1} where start is after end.", id); + } + + journal.startTransaction (); + + database.deleteInterval (interval); + validate (cli, rules, database, modified); + database.addInterval (modified, verbose); + + journal.endTransaction(); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdModifyRange.cpp b/src/commands/CmdModifyRange.cpp new file mode 100644 index 00000000..60de344b --- /dev/null +++ b/src/commands/CmdModifyRange.cpp @@ -0,0 +1,99 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024, Thomas Lauf, 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +int CmdModifyRange ( + CLI& cli, + Rules& rules, + Database& database, + Journal& journal) +{ + const bool verbose = rules.getBoolean ("verbose"); + + auto ids = cli.getIds (); + + if (ids.empty ()) + { + throw std::string ("ID must be specified. See 'timew help modify'."); + } + + if (ids.size () > 1) + { + throw std::string ("Only one ID may be specified. See 'timew help modify'."); + } + + auto range = cli.getRange ({0, 0}); + + if (!range.is_open() && range.start > range.end) + { + throw format ("Invalid range where start is after end given! {1}", range.dump ()); + } + + int id = *ids.begin(); + + flattenDatabase (database, rules); + auto filtering = IntervalFilterAllWithIds (ids); + auto intervals = getTracked (database, rules, filtering); + + if (intervals.empty()) + { + throw format ("ID '@{1}' does not correspond to any tracking.", id); + } + + assert (intervals.size () == 1); + if (! range.is_started ()) + { + throw std::string ("No updated time specified. See 'timew help modify'."); + } + + const Interval interval = intervals.at (0); + Interval modified {interval}; + modified.start = range.start; + modified.end = range.end; + + if (! modified.is_open () && (modified.start > modified.end)) + { + throw format ("Cannot modify interval @{1} where start is after end.", id); + } + + journal.startTransaction (); + + database.deleteInterval (interval); + validate (cli, rules, database, modified); + database.addInterval (modified, verbose); + + journal.endTransaction(); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdModifyStart.cpp b/src/commands/CmdModifyStart.cpp new file mode 100644 index 00000000..e532f186 --- /dev/null +++ b/src/commands/CmdModifyStart.cpp @@ -0,0 +1,93 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024, Thomas Lauf, 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +int CmdModifyStart ( + CLI& cli, + Rules& rules, + Database& database, + Journal& journal) +{ + const bool verbose = rules.getBoolean ("verbose"); + + auto ids = cli.getIds (); + + if (ids.empty ()) + { + throw std::string ("ID must be specified. See 'timew help modify'."); + } + + if (ids.size () > 1) + { + throw std::string ("Only one ID may be specified. See 'timew help modify'."); + } + + auto range = cli.getRange ({0, 0}); + + int id = *ids.begin(); + + flattenDatabase (database, rules); + auto filtering = IntervalFilterAllWithIds (ids); + auto intervals = getTracked (database, rules, filtering); + + if (intervals.empty()) + { + throw format ("ID '@{1}' does not correspond to any tracking.", id); + } + + assert (intervals.size () == 1); + if (! range.is_started ()) + { + throw std::string ("No updated time specified. See 'timew help modify'."); + } + + const Interval interval = intervals.at (0); + Interval modified {interval}; + modified.start = range.start; + + if (! modified.is_open () && (modified.start > modified.end)) + { + throw format ("Cannot modify interval @{1} where start is after end.", id); + } + + journal.startTransaction (); + + database.deleteInterval (interval); + validate (cli, rules, database, modified); + database.addInterval (modified, verbose); + + journal.endTransaction(); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdMove.cpp b/src/commands/CmdMove.cpp index dc2d9f0f..1af49d4c 100644 --- a/src/commands/CmdMove.cpp +++ b/src/commands/CmdMove.cpp @@ -33,7 +33,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdMove ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdReport.cpp b/src/commands/CmdReport.cpp index 93a3d20a..3438d6c5 100644 --- a/src/commands/CmdReport.cpp +++ b/src/commands/CmdReport.cpp @@ -107,7 +107,7 @@ std::string getScriptName (const std::string& script_path) //////////////////////////////////////////////////////////////////////////////// int CmdReport ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, const Extensions& extensions) diff --git a/src/commands/CmdResize.cpp b/src/commands/CmdResize.cpp index 97ab7469..8877fbf0 100644 --- a/src/commands/CmdResize.cpp +++ b/src/commands/CmdResize.cpp @@ -33,7 +33,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdResize ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdRetag.cpp b/src/commands/CmdRetag.cpp index c07a3c1e..ac1a4974 100644 --- a/src/commands/CmdRetag.cpp +++ b/src/commands/CmdRetag.cpp @@ -34,7 +34,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdRetag ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdShorten.cpp b/src/commands/CmdShorten.cpp index 32e00f00..30a40085 100644 --- a/src/commands/CmdShorten.cpp +++ b/src/commands/CmdShorten.cpp @@ -33,7 +33,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdShorten ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdSplit.cpp b/src/commands/CmdSplit.cpp index a7148bb8..30273b72 100644 --- a/src/commands/CmdSplit.cpp +++ b/src/commands/CmdSplit.cpp @@ -33,7 +33,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdSplit ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdStart.cpp b/src/commands/CmdStart.cpp index ed2220e5..219efbed 100644 --- a/src/commands/CmdStart.cpp +++ b/src/commands/CmdStart.cpp @@ -30,7 +30,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdStart ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdStop.cpp b/src/commands/CmdStop.cpp index 32d1086f..feee25d5 100644 --- a/src/commands/CmdStop.cpp +++ b/src/commands/CmdStop.cpp @@ -42,7 +42,7 @@ template T setIntersect ( //////////////////////////////////////////////////////////////////////////////// int CmdStop ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdSummary.cpp b/src/commands/CmdSummary.cpp index 7529f7c0..250f37a5 100644 --- a/src/commands/CmdSummary.cpp +++ b/src/commands/CmdSummary.cpp @@ -42,7 +42,7 @@ std::string renderHolidays (const std::map &); //////////////////////////////////////////////////////////////////////////////// int CmdSummary ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database) { diff --git a/src/commands/CmdTag.cpp b/src/commands/CmdTag.cpp index deff4154..1982363d 100644 --- a/src/commands/CmdTag.cpp +++ b/src/commands/CmdTag.cpp @@ -34,7 +34,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdTag ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdTags.cpp b/src/commands/CmdTags.cpp index afb7f861..20d2a2bc 100644 --- a/src/commands/CmdTags.cpp +++ b/src/commands/CmdTags.cpp @@ -38,7 +38,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdTags ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database) { diff --git a/src/commands/CmdTrack.cpp b/src/commands/CmdTrack.cpp index b4a7be31..2d5e8a48 100644 --- a/src/commands/CmdTrack.cpp +++ b/src/commands/CmdTrack.cpp @@ -30,7 +30,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdTrack ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/CmdUntag.cpp b/src/commands/CmdUntag.cpp index 5f4051a3..29b989ba 100644 --- a/src/commands/CmdUntag.cpp +++ b/src/commands/CmdUntag.cpp @@ -34,7 +34,7 @@ //////////////////////////////////////////////////////////////////////////////// int CmdUntag ( - const CLI& cli, + CLI& cli, Rules& rules, Database& database, Journal& journal) diff --git a/src/commands/commands.h b/src/commands/commands.h index 6dfb23f9..3f4a7f17 100644 --- a/src/commands/commands.h +++ b/src/commands/commands.h @@ -33,41 +33,44 @@ #include #include -int CmdAnnotate (const CLI&, Rules&, Database&, Journal& ); -int CmdCancel ( Rules&, Database&, Journal& ); -int CmdConfig (const CLI&, Rules&, Journal& ); -int CmdContinue (const CLI&, Rules&, Database&, Journal& ); -int CmdDefault ( Rules&, Database& ); -int CmdDelete (const CLI&, Rules&, Database&, Journal& ); -int CmdDiagnostics ( Rules&, Database&, const Extensions&); -int CmdExport (const CLI&, Rules&, Database& ); -int CmdExtensions ( const Extensions&); -int CmdFill (const CLI&, Rules&, Database&, Journal& ); -int CmdGaps (const CLI&, Rules&, Database& ); -int CmdGet (const CLI&, Rules&, Database& ); -int CmdHelpUsage ( const Extensions&); -int CmdHelp (const CLI&, const Extensions&); -int CmdJoin (const CLI&, Rules&, Database&, Journal& ); -int CmdLengthen (const CLI&, Rules&, Database&, Journal& ); -int CmdModify (const CLI&, Rules&, Database&, Journal& ); -int CmdMove (const CLI&, Rules&, Database&, Journal& ); -int CmdReport (const CLI&, Rules&, Database&, const Extensions&); -int CmdResize (const CLI&, Rules&, Database&, Journal& ); -int CmdRetag (const CLI&, Rules&, Database&, Journal& ); -int CmdShorten (const CLI&, Rules&, Database&, Journal& ); -int CmdShow ( Rules& ); -int CmdSplit (const CLI&, Rules&, Database&, Journal& ); -int CmdStart (const CLI&, Rules&, Database&, Journal& ); -int CmdStop (const CLI&, Rules&, Database&, Journal& ); -int CmdTag (const CLI&, Rules&, Database&, Journal& ); -int CmdTags (const CLI&, Rules&, Database& ); -int CmdTrack (const CLI&, Rules&, Database&, Journal& ); -int CmdUndo ( Rules&, Database&, Journal& ); -int CmdUntag (const CLI&, Rules&, Database&, Journal& ); +int CmdAnnotate (CLI&, Rules&, Database&, Journal& ); +int CmdCancel ( Rules&, Database&, Journal& ); +int CmdConfig (CLI&, Rules&, Journal& ); +int CmdContinue (CLI&, Rules&, Database&, Journal& ); +int CmdDefault ( Rules&, Database& ); +int CmdDelete (CLI&, Rules&, Database&, Journal& ); +int CmdDiagnostics ( Rules&, Database&, const Extensions&); +int CmdExport (CLI&, Rules&, Database& ); +int CmdExtensions ( const Extensions&); +int CmdFill (CLI&, Rules&, Database&, Journal& ); +int CmdGaps (CLI&, Rules&, Database& ); +int CmdGet (CLI&, Rules&, Database& ); +int CmdHelpUsage ( const Extensions&); +int CmdHelp (CLI&, const Extensions&); +int CmdJoin (CLI&, Rules&, Database&, Journal& ); +int CmdLengthen (CLI&, Rules&, Database&, Journal& ); +int CmdModify (CLI&, Rules&, Database&, Journal& ); +int CmdModifyEnd (CLI&, Rules&, Database&, Journal& ); +int CmdModifyRange (CLI&, Rules&, Database&, Journal& ); +int CmdModifyStart (CLI&, Rules&, Database&, Journal& ); +int CmdMove (CLI&, Rules&, Database&, Journal& ); +int CmdReport (CLI&, Rules&, Database&, const Extensions&); +int CmdResize (CLI&, Rules&, Database&, Journal& ); +int CmdRetag (CLI&, Rules&, Database&, Journal& ); +int CmdShorten (CLI&, Rules&, Database&, Journal& ); +int CmdShow ( Rules& ); +int CmdSplit (CLI&, Rules&, Database&, Journal& ); +int CmdStart (CLI&, Rules&, Database&, Journal& ); +int CmdStop (CLI&, Rules&, Database&, Journal& ); +int CmdTag (CLI&, Rules&, Database&, Journal& ); +int CmdTags (CLI&, Rules&, Database& ); +int CmdTrack (CLI&, Rules&, Database&, Journal& ); +int CmdUndo ( Rules&, Database&, Journal& ); +int CmdUntag (CLI&, Rules&, Database&, Journal& ); -int CmdChartDay (const CLI&, Rules&, Database& ); -int CmdChartWeek (const CLI&, Rules&, Database& ); -int CmdChartMonth (const CLI&, Rules&, Database& ); -int CmdSummary (const CLI&, Rules&, Database& ); +int CmdChartDay (CLI&, Rules&, Database& ); +int CmdChartWeek (CLI&, Rules&, Database& ); +int CmdChartMonth (CLI&, Rules&, Database& ); +int CmdSummary (CLI&, Rules&, Database& ); #endif diff --git a/src/init.cpp b/src/init.cpp index 2d2fa6db..788d50af 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -200,7 +200,7 @@ void initializeExtensions ( //////////////////////////////////////////////////////////////////////////////// int dispatchCommand ( - const CLI& cli, + CLI& cli, Database& database, Journal& journal, Rules& rules, diff --git a/src/timew.h b/src/timew.h index 15a35a56..88764157 100644 --- a/src/timew.h +++ b/src/timew.h @@ -65,7 +65,7 @@ bool lightweightVersionCheck (int, const char**); void initializeEntities (CLI&); void initializeDataJournalAndRules (const CLI&, Database&, Journal&, Rules&); void initializeExtensions (CLI&, const Rules&, Extensions&); -int dispatchCommand (const CLI&, Database&, Journal&, Rules&, const Extensions&); +int dispatchCommand (CLI&, Database&, Journal&, Rules&, const Extensions&); // helper.cpp Color summaryIntervalColor (const Rules&, const std::set &); diff --git a/test/modify.t b/test/modify.t index dab96d9b..fda2ffa5 100755 --- a/test/modify.t +++ b/test/modify.t @@ -2,7 +2,7 @@ ############################################################################### # -# Copyright 2018 - 2022, Thomas Lauf, Paul Beckingham, Federico Hernandez. +# Copyright 2018 - 2022, 2024 Thomas Lauf, 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 @@ -65,14 +65,14 @@ class TestModify(TestCase): expectedStart="{:%Y%m%dT%H%M%S}Z".format(one_hour_before_utc)) def test_modify_invalid_subcommand(self): - """Modify without (start|stop) subcommand""" + """Modify with invalid subcommand""" now_utc = datetime.now(timezone.utc) one_hour_before_utc = now_utc - timedelta(hours=1) self.t("start {:%Y-%m-%dT%H:%M:%S}Z".format(one_hour_before_utc)) self.t("stop") - code, out, err = self.t.runError("modify @1 {:%Y-%m-%dT%H:%M:%S}Z".format(now_utc)) - self.assertIn("Must specify start|end command to modify", err) + code, out, err = self.t.runError("modify @1 bogus {:%Y-%m-%dT%H:%M:%S}Z".format(now_utc)) + self.assertIn("Must specify start|end|range command to modify", err) def test_modify_no_end_time(self): """Modify without a time to stop at""" @@ -281,6 +281,36 @@ class TestModify(TestCase): code, out, err = self.t.runError("modify start @2") self.assertIn("ID '@2' does not correspond to any tracking.", err) + def test_modify_range(self): + """Call modify with range subcommand""" + now = datetime.now().replace(second=0, microsecond=0, minute=0) + now_utc = now.replace(tzinfo=tz.tzlocal()).astimezone(timezone.utc).replace(second=0, microsecond=0, minute=0) + two_hours_before_utc = now_utc - timedelta(hours=2) + three_hours_before_utc = now_utc - timedelta(hours=3) + four_hours_before_utc = now_utc - timedelta(hours=4) + + self.t("track from {:%Y-%m-%dT%H:%M:%S}Z for 30min bar".format(four_hours_before_utc)) + + self.t("modify @1 range {:%Y-%m-%dT%H:%M:%S}Z - {:%Y-%m-%dT%H:%M:%S}Z".format(three_hours_before_utc, two_hours_before_utc)) + + j = self.t.export() + + self.assertEqual(len(j), 1) + self.assertClosedInterval(j[0], + expectedStart=three_hours_before_utc, + expectedEnd=two_hours_before_utc, + expectedTags=['bar']) + + def test_modify_range_with_point_in_time(self): + """Call modify range with a point in time is an error""" + now = datetime.now().replace(second=0, microsecond=0, minute=0) + now_utc = now.replace(tzinfo=tz.tzlocal()).astimezone(timezone.utc).replace(second=0, microsecond=0, minute=0) + three_hours_before_utc = now_utc - timedelta(hours=3) + four_hours_before_utc = now_utc - timedelta(hours=4) + + self.t("track from {:%Y-%m-%dT%H:%M:%S}Z for 30min bar".format(four_hours_before_utc)) + + self.t("modify @1 range {:%Y-%m-%dT%H:%M:%S}Z".format(three_hours_before_utc)) if __name__ == "__main__": from simpletap import TAPTestRunner