diff --git a/ChangeLog b/ChangeLog index 4656eec5e..af78b2b89 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,8 @@ is a set of IDs. This allows commands like "task delete 1 2 5-10,12". + Fixed bug in the ghistory report, which caused it to only show a new month if a task was added during that month. + + New command "duplicate" which allows an existing task to be duplicated, + and also have modifications applied (thanks to David J Patrick). ------ old releases ------------------------------ diff --git a/checklist.txt b/checklist.txt index 5cec663c1..7da5a57a2 100644 --- a/checklist.txt +++ b/checklist.txt @@ -1,3 +1,21 @@ +Adding New Command Checklist +---------------------------- + +- Create new handler in command.cpp or report.cpp. +- Add prototype to task.h. +- Add call to appropriate section in task.cpp. +- Add usage info in task.cpp. +- Add command name to list in parse.cpp. +- Add unit tests in src/tests. +- Add new configuration details to html/config.html. +- Add command details to html/advanced.html. +- Add description to ChangeLog, with attribution if the idea + came from a single user, but not if it came from multiple + users. +- Add description to html/task.html. + + + Release Checklist ----------------- diff --git a/html/advanced.html b/html/advanced.html index b4abe30b0..356bb23cd 100644 --- a/html/advanced.html +++ b/html/advanced.html @@ -120,6 +120,13 @@ ID Project Pri Due Active Age Description variable.

+ % task duplicate 1 /foo/bar/g +tag priority:H +

+ This duplicates task 1, then applies the modifications specified, + which change all "foo" to "bar" in the description and annotations, + adds the tag "tag", and sets the priority to "H". +

+ % task delete <id>

There are two ways of getting rid of tasks - mark them as done, or diff --git a/html/task.html b/html/task.html index ac3e2038e..3bf21201d 100644 --- a/html/task.html +++ b/html/task.html @@ -123,6 +123,8 @@ is a set of IDs. This allows commands like "task delete 1 2 5-10,12".

  • Fixed bug in the ghistory report, which caused it to only show a new month if a task was added during that month. +
  • New command "duplicate" which allows an existing task to be duplicated, + and also have modifications applied (thanks to David J Patrick).

    diff --git a/src/command.cpp b/src/command.cpp index fcc512fb9..de48bb40e 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -727,7 +727,7 @@ std::string handleAppend (TDB& tdb, T& task, Config& conf) if (other->getId () == seq->getId () || // Self (seq->getAttribute ("parent") != "" && seq->getAttribute ("parent") == other->getAttribute ("parent")) || // Sibling - other->getUUID () == seq->getAttribute ("parent")) // Parent + other->getUUID () == seq->getAttribute ("parent")) // Parent { // A non-zero value forces a file write. int changes = 0; @@ -755,7 +755,62 @@ std::string handleAppend (TDB& tdb, T& task, Config& conf) } if (conf.get ("echo.command", true)) - out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl; + out << "Appended " << count << " task" << (count == 1 ? "" : "s") << std::endl; + + return out.str (); +} + +//////////////////////////////////////////////////////////////////////////////// +std::string handleDuplicate (TDB& tdb, T& task, Config& conf) +{ + int count = 0; + std::stringstream out; + std::vector all; + tdb.allPendingT (all); + + std::vector filtered = all; + filterSequence (filtered, task); + foreach (seq, filtered) + { + if (seq->getStatus () != T::recurring && seq->getAttribute ("parent") == "") + { + T dup (*seq); + dup.setUUID (uuid ()); // Needs a new UUID. + + // Apply deltas. + deltaDescription (dup, task); + deltaTags (dup, task); + deltaAttributes (dup, task); + deltaSubstitutions (dup, task); + + // A New task needs a new entry time. + char entryTime[16]; + sprintf (entryTime, "%u", (unsigned int) time (NULL)); + dup.setAttribute ("entry", entryTime); + + if (!tdb.addT (dup)) + throw std::string ("Could not create new task."); + + if (conf.get ("echo.command", true)) + out << "Duplicated " + << seq->getId () + << " '" + << seq->getDescription () + << "'" + << std::endl; + ++count; + } + else + out << "Task " + << seq->getId () + << " '" + << seq->getDescription () + << "' is a recurring task, and cannot be duplicated." + << std::endl; + } + + if (conf.get ("echo.command", true)) + out << "Duplicated " << count << " task" << (count == 1 ? "" : "s") << std::endl; return out.str (); } diff --git a/src/parse.cpp b/src/parse.cpp index e0f35039b..6bb7e224b 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -127,6 +127,7 @@ static const char* commands[] = "completed", "delete", "done", + "duplicate", "export", "help", "history", diff --git a/src/task.cpp b/src/task.cpp index bf725f8a5..49026df94 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -108,6 +108,10 @@ static std::string shortUsage (Config& conf) table.addCell (row, 1, "task ID /from/to/g"); table.addCell (row, 2, "Performs all substitutions on the task description, for fixing mistakes"); + row = table.addRow (); + table.addCell (row, 1, "task duplicate ID [tags] [attrs] [desc...]"); + table.addCell (row, 2, "Duplicates the specified task, and allows modifications"); + row = table.addRow (); table.addCell (row, 1, "task delete ID"); table.addCell (row, 2, "Deletes the specified task"); @@ -861,17 +865,18 @@ std::string runTaskCommand ( else if (command == "help") { out = longUsage ( conf); } // Commands that cause updates. - else if (command == "" && task.getId ()) { cmdMod = true; out = handleModify (tdb, task, conf); } - else if (command == "add") { cmdMod = true; out = handleAdd (tdb, task, conf); } - else if (command == "append") { cmdMod = true; out = handleAppend (tdb, task, conf); } - else if (command == "annotate") { cmdMod = true; out = handleAnnotate (tdb, task, conf); } - else if (command == "done") { cmdMod = true; out = handleDone (tdb, task, conf); } - else if (command == "undelete") { cmdMod = true; out = handleUndelete (tdb, task, conf); } - else if (command == "delete") { cmdMod = true; out = handleDelete (tdb, task, conf); } - 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); } + else if (command == "" && task.getId ()) { cmdMod = true; out = handleModify (tdb, task, conf); } + else if (command == "add") { cmdMod = true; out = handleAdd (tdb, task, conf); } + else if (command == "append") { cmdMod = true; out = handleAppend (tdb, task, conf); } + else if (command == "annotate") { cmdMod = true; out = handleAnnotate (tdb, task, conf); } + else if (command == "done") { cmdMod = true; out = handleDone (tdb, task, conf); } + else if (command == "undelete") { cmdMod = true; out = handleUndelete (tdb, task, conf); } + else if (command == "delete") { cmdMod = true; out = handleDelete (tdb, task, conf); } + 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); } + else if (command == "duplicate") { cmdMod = true; out = handleDuplicate (tdb, task, conf); } // Command that display IDs and therefore need TDB::gc first. else if (command == "completed") { if (gc) gcMod = tdb.gc (); out = handleCompleted (tdb, task, conf); } diff --git a/src/task.h b/src/task.h index 2ecc4f755..bdb9e6090 100644 --- a/src/task.h +++ b/src/task.h @@ -89,6 +89,7 @@ std::string handleStop (TDB&, T&, Config&); std::string handleUndo (TDB&, T&, Config&); std::string handleColor (Config&); std::string handleAnnotate (TDB&, T&, Config&); +std::string handleDuplicate (TDB&, T&, Config&); T findT (int, const std::vector &); int deltaAppend (T&, T&); int deltaDescription (T&, T&); diff --git a/src/tests/duplicate.t b/src/tests/duplicate.t new file mode 100755 index 000000000..d02321e86 --- /dev/null +++ b/src/tests/duplicate.t @@ -0,0 +1,66 @@ +#! /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 => 11; + +# Create the rc file. +if (open my $fh, '>', 'dup.rc') +{ + print $fh "data.location=.\n"; + close $fh; + ok (-r 'dup.rc', 'Created dup.rc'); +} + +# Test the duplicate command. +qx{../task rc:dup.rc add foo}; +qx{../task rc:dup.rc duplicate 1}; +my $output = qx{../task rc:dup.rc info 2}; +like ($output, qr/ID\s+2/, 'duplicate new id'); +like ($output, qr/Status\s+Pending/, 'duplicate same status'); +like ($output, qr/Description\s+foo/, 'duplicate same description'); + +# Test the en passant modification while duplicating. +qx{../task rc:dup.rc duplicate 1 priority:H /foo/FOO/ +tag}; +$output = qx{../task rc:dup.rc info 3}; +like ($output, qr/ID\s+3/, 'duplicate new id'); +like ($output, qr/Status\s+Pending/, 'duplicate same status'); +like ($output, qr/Description\s+FOO/, 'duplicate modified description'); +like ($output, qr/Priority\s+H/, 'duplicate added priority'); +like ($output, qr/Tags\s+tag/, 'duplicate added tag'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'dup.rc'; +ok (!-r 'dup.rc', 'Removed dup.rc'); + +exit 0; +