mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-19 19:03:07 +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 '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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -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 ('/'))
|
||||
{
|
||||
|
|
11
src/RX.cpp
11
src/RX.cpp
|
@ -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)
|
||||
{
|
||||
|
|
2
src/RX.h
2
src/RX.h
|
@ -33,6 +33,8 @@
|
|||
#include <vector>
|
||||
#include <regex.h>
|
||||
|
||||
#define RX_MAX_MATCHES 64
|
||||
|
||||
class RX
|
||||
{
|
||||
public:
|
||||
|
|
124
src/Task.cpp
124
src/Task.cpp
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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 ();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
|
|
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 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 ();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue