diff --git a/ChangeLog b/ChangeLog index f8d7aa5bc..16d8c4c32 100644 --- a/ChangeLog +++ b/ChangeLog @@ -40,6 +40,8 @@ date, because of an assumption of 365 days per year, which failed to consider leap years (thanks to T. Charles Yun). + Annotations can now be modified with the substitution commands /from/to/. + + Substitutions can now be made global with /from/to/g and all occurrences + of "from" will be replaced with "to". ------ old releases ------------------------------ diff --git a/html/advanced.html b/html/advanced.html index 49c2d5b29..0db9467c5 100644 --- a/html/advanced.html +++ b/html/advanced.html @@ -289,7 +289,7 @@ ID Project Pri Description set via the "newest" configuration variable.

- % task /from/to/ + % task <id> /from/to/

If a task has been entered with a typo, it can be easily corrected by this command. For example: @@ -309,14 +309,22 @@ ID Project Pri Description ...

- This command makes single corrections to a task description. + This command makes a single correction to the first occurrence of + "from" in a task description.

- If a task is annotated, the annotation can be modified using + If a task is annotated, the annotation can also be modified using this command.

+ % task <id> /from/to/g +

+ The "g" modifier to the substitution command causes every occurrence + of "from" to be replaced with "to", in both the description and any + annotations. +

+ % task tags

This command will generate a list of all the tags that are currently diff --git a/html/task.html b/html/task.html index 0fbfbee90..b0a146a36 100644 --- a/html/task.html +++ b/html/task.html @@ -145,6 +145,8 @@ date, because of an assumption of 365 days per year, which failed to consider leap years (thanks to T. Charles Yun).

  • Annotations can now be modified with the substitution commands /from/to/. +
  • Substitutions can now be made global with /from/to/g and all occurrences + of "from" will be replaced with "to".

    diff --git a/src/T.cpp b/src/T.cpp index 117a46d80..3ec41edc1 100644 --- a/src/T.cpp +++ b/src/T.cpp @@ -40,6 +40,9 @@ T::T () mTags.clear (); mAttributes.clear (); mDescription = ""; + mFrom = ""; + mTo = ""; + mGlobal = false; mAnnotations.clear (); } @@ -240,17 +243,25 @@ void T::removeAttribute (const std::string& name) } //////////////////////////////////////////////////////////////////////////////// -void T::getSubstitution (std::string& from, std::string& to) const +void T::getSubstitution ( + std::string& from, + std::string& to, + bool& global) const { - from = mFrom; - to = mTo; + from = mFrom; + to = mTo; + global = mGlobal; } //////////////////////////////////////////////////////////////////////////////// -void T::setSubstitution (const std::string& from, const std::string& to) +void T::setSubstitution ( + const std::string& from, + const std::string& to, + bool global) { - mFrom = from; - mTo = to; + mFrom = from; + mTo = to; + mGlobal = global; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/T.h b/src/T.h index 35966ebb7..14004a9c3 100644 --- a/src/T.h +++ b/src/T.h @@ -58,8 +58,8 @@ public: void setDescription (const std::string& description) { mDescription = description; } int getAnnotationCount () const { return mAnnotations.size (); } - void getSubstitution (std::string&, std::string&) const; - void setSubstitution (const std::string&, const std::string&); + void getSubstitution (std::string&, std::string&, bool&) const; + void setSubstitution (const std::string&, const std::string&, bool); bool hasTag (const std::string&) const; @@ -101,6 +101,7 @@ private: std::map mAttributes; std::string mFrom; std::string mTo; + bool mGlobal; std::map mAnnotations; }; diff --git a/src/command.cpp b/src/command.cpp index 3912f057e..16282bdc9 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -756,40 +756,67 @@ std::string handleModify (TDB& tdb, T& task, Config& conf) std::string from; std::string to; - task.getSubstitution (from, to); + bool global; + task.getSubstitution (from, to, global); if (from != "") { std::string description = original.getDescription (); - size_t pattern = description.find (from); - if (pattern != std::string::npos) + size_t pattern; + + if (global) { - description = description.substr (0, pattern) + - to + - description.substr (pattern + from.length (), std::string::npos); + // Perform all subs on description. + while ((pattern = description.find (from)) != std::string::npos) + { + description.replace (pattern, from.length (), to); + ++changes; + } + original.setDescription (description); - ++changes; - } - else - { + + // Perform all subs on annotations. std::map annotations; original.getAnnotations (annotations); - std::map ::iterator it; for (it = annotations.begin (); it != annotations.end (); ++it) { - size_t pattern = it->second.find (from); - if (pattern != std::string::npos) + while ((pattern = it->second.find (from)) != std::string::npos) { - it->second = it->second.substr (0, pattern) + - to + - it->second.substr (pattern + from.length (), std::string::npos); + it->second.replace (pattern, from.length (), to); ++changes; - break; } } - if (changes) + original.setAnnotations (annotations); + } + else + { + // Perform first description substitution. + if ((pattern = description.find (from)) != std::string::npos) + { + description.replace (pattern, from.length (), to); + original.setDescription (description); + ++changes; + } + // Failing that, perform the first annotation substitution. + else + { + std::map annotations; + original.getAnnotations (annotations); + + std::map ::iterator it; + for (it = annotations.begin (); it != annotations.end (); ++it) + { + if ((pattern = it->second.find (from)) != std::string::npos) + { + it->second.replace (pattern, from.length (), to); + ++changes; + break; + } + } + original.setAnnotations (annotations); + } } } @@ -879,23 +906,6 @@ std::string handleAppend (TDB& tdb, T& task, Config& conf) ++changes; } - std::string from; - std::string to; - task.getSubstitution (from, to); - if (from != "") - { - std::string description = original.getDescription (); - size_t pattern = description.find (from); - if (pattern != std::string::npos) - { - description = description.substr (0, pattern) + - to + - description.substr (pattern + from.length (), std::string::npos); - original.setDescription (description); - ++changes; - } - } - if (changes) { tdb.modifyT (original); diff --git a/src/parse.cpp b/src/parse.cpp index 110877aab..f4778307e 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -348,7 +348,8 @@ static bool validCommand (std::string& input) static bool validSubstitution ( std::string& input, std::string& from, - std::string& to) + std::string& to, + bool& global) { size_t first = input.find ('/'); if (first != std::string::npos) @@ -362,10 +363,17 @@ static bool validSubstitution ( if (first == 0 && first < second && second < third && - third == input.length () - 1) + (third == input.length () - 1 || + third == input.length () - 2)) { from = input.substr (first + 1, second - first - 1); to = input.substr (second + 1, third - second - 1); + + global = false; + if (third == input.length () - 2 && + input.find ('g', third + 1) != std::string::npos) + global = true; + return true; } } @@ -411,6 +419,7 @@ void parse ( size_t colon; // Pointer to colon in argument. std::string from; std::string to; + bool global; // An id is the first argument found that contains all digits. if (lowerCase (command) != "add" && // "add" doesn't require an ID @@ -451,9 +460,9 @@ void parse ( } // Substitution of description text. - else if (validSubstitution (arg, from, to)) + else if (validSubstitution (arg, from, to, global)) { - task.setSubstitution (from, to); + task.setSubstitution (from, to, global); } // Command. diff --git a/src/tests/substitute.t b/src/tests/substitute.t index 5b5c68007..8b4823ba8 100755 --- a/src/tests/substitute.t +++ b/src/tests/substitute.t @@ -28,7 +28,7 @@ use strict; use warnings; -use Test::More tests => 5; +use Test::More tests => 7; # Create the rc file. if (open my $fh, '>', 'subst.rc') @@ -39,16 +39,24 @@ if (open my $fh, '>', 'subst.rc') } # Test the substitution command. -qx{../task rc:subst.rc add foo}; +qx{../task rc:subst.rc add foo foo foo}; qx{../task rc:subst.rc 1 /foo/FOO/}; my $output = qx{../task rc:subst.rc info 1}; -like ($output, qr/FOO/, 'substitution in description'); +like ($output, qr/FOO foo foo/, 'substitution in description'); + +qx{../task rc:subst.rc 1 /foo/FOO/g}; +my $output = qx{../task rc:subst.rc info 1}; +like ($output, qr/FOO FOO FOO/, 'global substitution in description'); # Test the substitution command on annotations. -qx{../task rc:subst.rc annotate 1 bar}; +qx{../task rc:subst.rc annotate 1 bar bar bar}; qx{../task rc:subst.rc 1 /bar/BAR/}; $output = qx{../task rc:subst.rc info 1}; -like ($output, qr/BAR/, 'substitution in annotation'); +like ($output, qr/BAR bar bar/, 'substitution in annotation'); + +qx{../task rc:subst.rc 1 /bar/BAR/g}; +$output = qx{../task rc:subst.rc info 1}; +like ($output, qr/BAR BAR BAR/, 'global substitution in annotation'); # Cleanup. unlink 'pending.data';