mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-27 00:57:19 +02:00
Regexes
- 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:
parent
622e9c5e1e
commit
b58438bdd4
11 changed files with 152 additions and 166 deletions
3
NEWS
3
NEWS
|
@ -48,10 +48,13 @@ New configuration options in taskwarrior 2.0.0
|
||||||
- New 'expressions' enables/disables command line expression support.
|
- New 'expressions' enables/disables command line expression support.
|
||||||
- New 'json.array' determines whether 'query' command output is enclosed by
|
- 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
|
Newly deprecated features in taskwarrior 2.0.0
|
||||||
|
|
||||||
- The 'next' configuration variable has been removed.
|
- The 'next' configuration variable has been removed.
|
||||||
|
- Use of 'fg:' and 'bg:' attributes are deprecated.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -786,9 +786,9 @@ bool Arguments::is_subst (const std::string& input)
|
||||||
std::string from;
|
std::string from;
|
||||||
std::string to;
|
std::string to;
|
||||||
Nibbler n (input);
|
Nibbler n (input);
|
||||||
if (n.skip ('/') &&
|
if (n.skip ('/') &&
|
||||||
n.getUntil ('/', from) &&
|
n.getUntil ('/', from) &&
|
||||||
n.skip ('/') &&
|
n.skip ('/') &&
|
||||||
n.getUntil ('/', to) &&
|
n.getUntil ('/', to) &&
|
||||||
n.skip ('/'))
|
n.skip ('/'))
|
||||||
{
|
{
|
||||||
|
|
11
src/RX.cpp
11
src/RX.cpp
|
@ -31,8 +31,7 @@
|
||||||
|
|
||||||
#define L10N // Localization complete.
|
#define L10N // Localization complete.
|
||||||
|
|
||||||
//#define _POSIX_C_SOURCE 1
|
//#define _POSIX_C_SOURCE 1 // Forgot why this is here. Moving on...
|
||||||
#define MAX_MATCHES 64
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
RX::RX ()
|
RX::RX ()
|
||||||
|
@ -126,8 +125,8 @@ bool RX::match (
|
||||||
if (!_compiled)
|
if (!_compiled)
|
||||||
compile ();
|
compile ();
|
||||||
|
|
||||||
regmatch_t rm[MAX_MATCHES];
|
regmatch_t rm[RX_MAX_MATCHES];
|
||||||
if (regexec (&_regex, in.c_str (), MAX_MATCHES, rm, 0) == 0)
|
if (regexec (&_regex, in.c_str (), RX_MAX_MATCHES, rm, 0) == 0)
|
||||||
{
|
{
|
||||||
for (unsigned int i = 1; i < 1 + _regex.re_nsub; ++i)
|
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));
|
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)
|
if (!_compiled)
|
||||||
compile ();
|
compile ();
|
||||||
|
|
||||||
regmatch_t rm[MAX_MATCHES];
|
regmatch_t rm[RX_MAX_MATCHES];
|
||||||
if (regexec (&_regex, in.c_str (), MAX_MATCHES, rm, 0) == 0)
|
if (regexec (&_regex, in.c_str (), RX_MAX_MATCHES, rm, 0) == 0)
|
||||||
{
|
{
|
||||||
for (unsigned int i = 1; i < 1 + _regex.re_nsub; ++i)
|
for (unsigned int i = 1; i < 1 + _regex.re_nsub; ++i)
|
||||||
{
|
{
|
||||||
|
|
2
src/RX.h
2
src/RX.h
|
@ -33,6 +33,8 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <regex.h>
|
#include <regex.h>
|
||||||
|
|
||||||
|
#define RX_MAX_MATCHES 64
|
||||||
|
|
||||||
class RX
|
class RX
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
124
src/Task.cpp
124
src/Task.cpp
|
@ -34,10 +34,14 @@
|
||||||
#include <Duration.h>
|
#include <Duration.h>
|
||||||
#include <Task.h>
|
#include <Task.h>
|
||||||
#include <JSON.h>
|
#include <JSON.h>
|
||||||
|
#include <RX.h>
|
||||||
#include <text.h>
|
#include <text.h>
|
||||||
#include <util.h>
|
#include <util.h>
|
||||||
|
#include <i18n.h>
|
||||||
#include <main.h>
|
#include <main.h>
|
||||||
|
|
||||||
|
#define APPROACHING_INFINITY 1000 // Close enough. This isn't rocket surgery.
|
||||||
|
|
||||||
extern Context context;
|
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
|
void Task::validate () const
|
||||||
{
|
{
|
||||||
|
|
|
@ -81,6 +81,8 @@ public:
|
||||||
void getDependencies (std::vector <int>&) const;
|
void getDependencies (std::vector <int>&) const;
|
||||||
void getDependencies (std::vector <std::string>&) const;
|
void getDependencies (std::vector <std::string>&) const;
|
||||||
|
|
||||||
|
void substitute (const std::string&, const std::string&, bool);
|
||||||
|
|
||||||
void validate () const;
|
void validate () const;
|
||||||
|
|
||||||
float urgency ();
|
float urgency ();
|
||||||
|
|
|
@ -27,8 +27,6 @@
|
||||||
|
|
||||||
#define L10N // Localization complete.
|
#define L10N // Localization complete.
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <Context.h>
|
#include <Context.h>
|
||||||
#include <text.h>
|
#include <text.h>
|
||||||
#include <i18n.h>
|
#include <i18n.h>
|
||||||
|
@ -59,7 +57,7 @@ int CmdAdd::execute (std::string& output)
|
||||||
std::vector <Task> all;
|
std::vector <Task> all;
|
||||||
context.tdb.loadPending (all);
|
context.tdb.loadPending (all);
|
||||||
|
|
||||||
// Every task needs a UUID.
|
// Every new task needs a UUID.
|
||||||
Task task;
|
Task task;
|
||||||
task.set ("uuid", uuid ());
|
task.set ("uuid", uuid ());
|
||||||
|
|
||||||
|
@ -73,19 +71,15 @@ int CmdAdd::execute (std::string& output)
|
||||||
|
|
||||||
context.tdb.add (task);
|
context.tdb.add (task);
|
||||||
|
|
||||||
std::stringstream out;
|
|
||||||
// TODO This should be a call in to feedback.cpp.
|
// TODO This should be a call in to feedback.cpp.
|
||||||
#ifdef FEATURE_NEW_ID
|
#ifdef FEATURE_NEW_ID
|
||||||
out << format (STRING_CMD_ADD_FEEDBACK, context.tdb.nextId ())
|
output = format (STRING_CMD_ADD_FEEDBACK, context.tdb.nextId ()) + "\n";
|
||||||
<< "\n";
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
context.footnote (onProjectChange (task));
|
context.footnote (onProjectChange (task));
|
||||||
|
|
||||||
context.tdb.commit ();
|
context.tdb.commit ();
|
||||||
context.tdb.unlock ();
|
context.tdb.unlock ();
|
||||||
|
|
||||||
output = out.str ();
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -343,6 +343,16 @@ void Command::modify_task (Task& task, Arguments& arguments)
|
||||||
description += arg->first;
|
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
|
// Any additional argument types are indicative of a failure in
|
||||||
// Arguments::extract_modifications.
|
// Arguments::extract_modifications.
|
||||||
else
|
else
|
||||||
|
|
|
@ -234,6 +234,7 @@
|
||||||
#define STRING_TAGS_NO_COMMAS "Tags are not permitted to contain commas."
|
#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_TRIVIAL_INPUT "You must specify a command, or a task ID to modify."
|
||||||
#define STRING_ASSUME_INFO "No command - assuming 'info'"
|
#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
|
// Feedback
|
||||||
#define STRING_FEEDBACK_NO_MATCH "No matches."
|
#define STRING_FEEDBACK_NO_MATCH "No matches."
|
||||||
|
|
146
src/helpers.cpp
146
src/helpers.cpp
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
|
@ -29,8 +29,6 @@
|
||||||
#define L10N // Localization complete.
|
#define L10N // Localization complete.
|
||||||
|
|
||||||
#define FEATURE_NEW_ID 1 // Echoes back new id.
|
#define FEATURE_NEW_ID 1 // Echoes back new id.
|
||||||
//#define FEATURE_REGEX 1 // Enables regexes for attribute modifiers,
|
|
||||||
// // subst, general search.
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -59,7 +57,6 @@ int deltaPrepend (Task&);
|
||||||
int deltaDescription (Task&);
|
int deltaDescription (Task&);
|
||||||
int deltaTags (Task&);
|
int deltaTags (Task&);
|
||||||
int deltaAttributes (Task&);
|
int deltaAttributes (Task&);
|
||||||
int deltaSubstitutions (Task&);
|
|
||||||
|
|
||||||
// rules.cpp
|
// rules.cpp
|
||||||
void initializeColorRules ();
|
void initializeColorRules ();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue