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';