- Added regex support to substirutions.
- Fixed bug that prevented 1.9.4 from shipping with regexes.  If the
  description is "aXXaaXXa", and the substitution is /XX/.../ then the
  first substitutions changes the length of the string to "a...aaXXa"
  and therefore invalidates the index for the second match, and makes
  this change: "a...a...Xa".  The fix is to keep a running 'skew' count
  of the difference in 'from' and 'to' length, to adjust the match
  indexes.
- Moved the helper deltaSubstitutions function into the Task object,
  which makes more sense.
- Cleaned up output composition for CmdAdd.
- Eliminated #ifdef FEATURE_REGEX.  They are here to stay.
This commit is contained in:
Paul Beckingham 2011-06-25 16:33:55 -04:00
parent 622e9c5e1e
commit b58438bdd4
11 changed files with 152 additions and 166 deletions

3
NEWS
View file

@ -48,10 +48,13 @@ New configuration options in taskwarrior 2.0.0
- New 'expressions' enables/disables command line expression support.
- New 'json.array' determines whether 'query' command output is enclosed by
'[...]'.
- New 'regex' control determines whether substitutions use Regular Expressions
or simple text patterns.
Newly deprecated features in taskwarrior 2.0.0
- The 'next' configuration variable has been removed.
- Use of 'fg:' and 'bg:' attributes are deprecated.
---

View file

@ -786,9 +786,9 @@ bool Arguments::is_subst (const std::string& input)
std::string from;
std::string to;
Nibbler n (input);
if (n.skip ('/') &&
n.getUntil ('/', from) &&
n.skip ('/') &&
if (n.skip ('/') &&
n.getUntil ('/', from) &&
n.skip ('/') &&
n.getUntil ('/', to) &&
n.skip ('/'))
{

View file

@ -31,8 +31,7 @@
#define L10N // Localization complete.
//#define _POSIX_C_SOURCE 1
#define MAX_MATCHES 64
//#define _POSIX_C_SOURCE 1 // Forgot why this is here. Moving on...
////////////////////////////////////////////////////////////////////////////////
RX::RX ()
@ -126,8 +125,8 @@ bool RX::match (
if (!_compiled)
compile ();
regmatch_t rm[MAX_MATCHES];
if (regexec (&_regex, in.c_str (), MAX_MATCHES, rm, 0) == 0)
regmatch_t rm[RX_MAX_MATCHES];
if (regexec (&_regex, in.c_str (), RX_MAX_MATCHES, rm, 0) == 0)
{
for (unsigned int i = 1; i < 1 + _regex.re_nsub; ++i)
matches.push_back (in.substr (rm[i].rm_so, rm[i].rm_eo - rm[i].rm_so));
@ -147,8 +146,8 @@ bool RX::match (
if (!_compiled)
compile ();
regmatch_t rm[MAX_MATCHES];
if (regexec (&_regex, in.c_str (), MAX_MATCHES, rm, 0) == 0)
regmatch_t rm[RX_MAX_MATCHES];
if (regexec (&_regex, in.c_str (), RX_MAX_MATCHES, rm, 0) == 0)
{
for (unsigned int i = 1; i < 1 + _regex.re_nsub; ++i)
{

View file

@ -33,6 +33,8 @@
#include <vector>
#include <regex.h>
#define RX_MAX_MATCHES 64
class RX
{
public:

View file

@ -34,10 +34,14 @@
#include <Duration.h>
#include <Task.h>
#include <JSON.h>
#include <RX.h>
#include <text.h>
#include <util.h>
#include <i18n.h>
#include <main.h>
#define APPROACHING_INFINITY 1000 // Close enough. This isn't rocket surgery.
extern Context context;
////////////////////////////////////////////////////////////////////////////////
@ -753,6 +757,126 @@ void Task::removeTag (const std::string& tag)
}
}
////////////////////////////////////////////////////////////////////////////////
void Task::substitute (
const std::string& from,
const std::string& to,
bool global)
{
// Get the data to modify.
std::string description = get ("description");
std::vector <Att> annotations;
getAnnotations (annotations);
bool sensitive = context.config.getBoolean ("search.case.sensitive");
// Count the changes, so we know whether to proceed to annotations, after
// modifying description.
int changes = 0;
// Regex support is optional.
if (context.config.getBoolean ("regex"))
{
// Insert capturing parentheses, if necessary.
std::string pattern;
if (from.find ('(') != std::string::npos)
pattern = from;
else
pattern = "(" + from + ")";
// Create the regex.
RX rx (pattern, sensitive);
std::vector <int> start;
std::vector <int> end;
// Perform all subs on description.
if (rx.match (start, end, description))
{
int skew = 0;
int limit = global ? (int) start.size () : 1;
for (int i = 0; i < limit && i < RX_MAX_MATCHES; ++i)
{
description.replace (start[i + skew], end[i] - start[i], to);
skew += to.length () - (end[i] - start[i]);
++changes;
}
}
if (changes == 0 || global)
{
// Perform all subs on annotations.
std::vector <Att>::iterator it;
for (it = annotations.begin (); it != annotations.end (); ++it)
{
std::string annotation = it->value ();
start.clear ();
end.clear ();
if (rx.match (start, end, annotation))
{
int skew = 0;
int limit = global ? (int) start.size () : 1;
for (int i = 0; i < limit && i < RX_MAX_MATCHES; ++i)
{
annotation.replace (start[i + skew], end[i] - start[i], to);
skew += to.length () - (end[i] - start[i]);
it->value (annotation);
++changes;
}
}
}
}
}
else
{
// Perform all subs on description.
int counter = 0;
std::string::size_type pos = 0;
while ((pos = ::find (description, from, pos, sensitive)) != std::string::npos)
{
description.replace (pos, from.length (), to);
pos += to.length ();
++changes;
if (! global)
break;
if (++counter > APPROACHING_INFINITY)
throw format (STRING_INFINITE_LOOP, APPROACHING_INFINITY);
}
if (changes == 0 || global)
{
// Perform all subs on annotations.
counter = 0;
std::vector <Att>::iterator i;
for (i = annotations.begin (); i != annotations.end (); ++i)
{
pos = 0;
std::string annotation = i->value ();
while ((pos = ::find (annotation, from, pos, sensitive)) != std::string::npos)
{
annotation.replace (pos, from.length (), to);
pos += to.length ();
++changes;
i->value (annotation);
if (! global)
break;
if (++counter > APPROACHING_INFINITY)
throw format (STRING_INFINITE_LOOP, APPROACHING_INFINITY);
}
}
}
}
set ("description", description);
setAnnotations (annotations);
}
////////////////////////////////////////////////////////////////////////////////
void Task::validate () const
{

View file

@ -81,6 +81,8 @@ public:
void getDependencies (std::vector <int>&) const;
void getDependencies (std::vector <std::string>&) const;
void substitute (const std::string&, const std::string&, bool);
void validate () const;
float urgency ();

View file

@ -27,8 +27,6 @@
#define L10N // Localization complete.
#include <sstream>
#include <stdlib.h>
#include <Context.h>
#include <text.h>
#include <i18n.h>
@ -59,7 +57,7 @@ int CmdAdd::execute (std::string& output)
std::vector <Task> all;
context.tdb.loadPending (all);
// Every task needs a UUID.
// Every new task needs a UUID.
Task task;
task.set ("uuid", uuid ());
@ -73,19 +71,15 @@ int CmdAdd::execute (std::string& output)
context.tdb.add (task);
std::stringstream out;
// TODO This should be a call in to feedback.cpp.
#ifdef FEATURE_NEW_ID
out << format (STRING_CMD_ADD_FEEDBACK, context.tdb.nextId ())
<< "\n";
output = format (STRING_CMD_ADD_FEEDBACK, context.tdb.nextId ()) + "\n";
#endif
context.footnote (onProjectChange (task));
context.tdb.commit ();
context.tdb.unlock ();
output = out.str ();
return rc;
}

View file

@ -343,6 +343,16 @@ void Command::modify_task (Task& task, Arguments& arguments)
description += arg->first;
}
// Substitutions.
else if (arg->second == "subst")
{
std::string from;
std::string to;
bool global;
Arguments::extract_subst (arg->first, from, to, global);
task.substitute (from, to, global);
}
// Any additional argument types are indicative of a failure in
// Arguments::extract_modifications.
else

View file

@ -234,6 +234,7 @@
#define STRING_TAGS_NO_COMMAS "Tags are not permitted to contain commas."
#define STRING_TRIVIAL_INPUT "You must specify a command, or a task ID to modify."
#define STRING_ASSUME_INFO "No command - assuming 'info'"
#define STRING_INFINITE_LOOP "Terminated substitution because more than {1} changes were made - infinite loop protection."
// Feedback
#define STRING_FEEDBACK_NO_MATCH "No matches."

View file

@ -358,149 +358,3 @@ int deltaAttributes (Task& task)
}
////////////////////////////////////////////////////////////////////////////////
int deltaSubstitutions (Task& task)
{
/*
std::string description = task.get ("description");
std::vector <Att> annotations;
task.getAnnotations (annotations);
apply_subst (description, annotations);
task.set ("description", description);
task.setAnnotations (annotations);
*/
return 1;
}
////////////////////////////////////////////////////////////////////////////////
/*
void apply_subst (
std::string& description,
std::vector <Att>& annotations) const
{
std::string::size_type pattern;
bool sensitive = context.config.getBoolean ("search.case.sensitive");
if (mFrom != "")
{
#ifdef FEATURE_REGEX
if (context.config.getBoolean ("regex"))
{
// Insert capturing parentheses, if necessary.
std::string pattern;
if (mFrom.find ('(') != std::string::npos)
pattern = mFrom;
else
pattern = "(" + mFrom + ")";
std::vector <int> start;
std::vector <int> end;
// Perform all subs on description.
int counter = 0;
if (regexMatch (start, end, description, pattern, sensitive))
{
for (unsigned int i = 0; i < start.size (); ++i)
{
description.replace (start[i], end[i] - start[i], mTo);
if (!mGlobal)
break;
if (++counter > 1000)
throw ("Terminated substitution because more than a thousand changes were made - infinite loop protection.");
}
}
// Perform all subs on annotations.
counter = 0;
std::vector <Att>::iterator i;
for (i = annotations.begin (); i != annotations.end (); ++i)
{
std::string annotation = i->value ();
start.clear ();
end.clear ();
if (regexMatch (start, end, annotation, pattern, sensitive))
{
for (unsigned int match = 0; match < start.size (); ++match)
{
annotation.replace (start[match], end[match] - start[match], mTo);
i->value (annotation);
if (!mGlobal)
break;
if (++counter > 1000)
throw ("Terminated substitution because more than a thousand changes were made - infinite loop protection.");
}
}
}
}
else
{
#endif
if (mGlobal)
{
// Perform all subs on description.
int counter = 0;
pattern = 0;
while ((pattern = find (description, mFrom, pattern, sensitive)) != std::string::npos)
{
description.replace (pattern, mFrom.length (), mTo);
pattern += mTo.length ();
if (++counter > 1000)
throw ("Terminated substitution because more than a thousand changes were made - infinite loop protection.");
}
// Perform all subs on annotations.
counter = 0;
std::vector <Att>::iterator i;
for (i = annotations.begin (); i != annotations.end (); ++i)
{
pattern = 0;
std::string annotation = i->value ();
while ((pattern = find (annotation, mFrom, pattern, sensitive)) != std::string::npos)
{
annotation.replace (pattern, mFrom.length (), mTo);
pattern += mTo.length ();
i->value (annotation);
if (++counter > 1000)
throw ("Terminated substitution because more than a thousand changes were made - infinite loop protection.");
}
}
}
else
{
// Perform first description substitution.
if ((pattern = find (description, mFrom, sensitive)) != std::string::npos)
description.replace (pattern, mFrom.length (), mTo);
// Failing that, perform the first annotation substitution.
else
{
std::vector <Att>::iterator i;
for (i = annotations.begin (); i != annotations.end (); ++i)
{
std::string annotation = i->value ();
if ((pattern = find (annotation, mFrom, sensitive)) != std::string::npos)
{
annotation.replace (pattern, mFrom.length (), mTo);
i->value (annotation);
break;
}
}
}
}
#ifdef FEATURE_REGEX
}
#endif
}
}
*/
////////////////////////////////////////////////////////////////////////////////

View file

@ -29,8 +29,6 @@
#define L10N // Localization complete.
#define FEATURE_NEW_ID 1 // Echoes back new id.
//#define FEATURE_REGEX 1 // Enables regexes for attribute modifiers,
// // subst, general search.
#include <string>
#include <vector>
@ -59,7 +57,6 @@ int deltaPrepend (Task&);
int deltaDescription (Task&);
int deltaTags (Task&);
int deltaAttributes (Task&);
int deltaSubstitutions (Task&);
// rules.cpp
void initializeColorRules ();