diff --git a/NEWS b/NEWS index 96dfb276e..99cb34678 100644 --- a/NEWS +++ b/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. --- diff --git a/src/Arguments.cpp b/src/Arguments.cpp index 231819ae5..1a0e1ee6d 100644 --- a/src/Arguments.cpp +++ b/src/Arguments.cpp @@ -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 ('/')) { diff --git a/src/RX.cpp b/src/RX.cpp index cae119177..967ac3ac2 100644 --- a/src/RX.cpp +++ b/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) { diff --git a/src/RX.h b/src/RX.h index 651029823..f505b1e3b 100644 --- a/src/RX.h +++ b/src/RX.h @@ -33,6 +33,8 @@ #include #include +#define RX_MAX_MATCHES 64 + class RX { public: diff --git a/src/Task.cpp b/src/Task.cpp index aedfae1e4..4fcb5439a 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -34,10 +34,14 @@ #include #include #include +#include #include #include +#include #include +#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 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 start; + std::vector 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 ::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 ::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 { diff --git a/src/Task.h b/src/Task.h index b460fe277..cb7fab1f2 100644 --- a/src/Task.h +++ b/src/Task.h @@ -81,6 +81,8 @@ public: void getDependencies (std::vector &) const; void getDependencies (std::vector &) const; + void substitute (const std::string&, const std::string&, bool); + void validate () const; float urgency (); diff --git a/src/commands/CmdAdd.cpp b/src/commands/CmdAdd.cpp index 0aaad06db..e908596ad 100644 --- a/src/commands/CmdAdd.cpp +++ b/src/commands/CmdAdd.cpp @@ -27,8 +27,6 @@ #define L10N // Localization complete. -#include -#include #include #include #include @@ -59,7 +57,7 @@ int CmdAdd::execute (std::string& output) std::vector 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; } diff --git a/src/commands/Command.cpp b/src/commands/Command.cpp index f2ea8fdd2..b0d0e812b 100644 --- a/src/commands/Command.cpp +++ b/src/commands/Command.cpp @@ -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 diff --git a/src/en-US.h b/src/en-US.h index c8dece2ff..f2bba4bbe 100644 --- a/src/en-US.h +++ b/src/en-US.h @@ -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." diff --git a/src/helpers.cpp b/src/helpers.cpp index 87f82d061..9657a6d6f 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -358,149 +358,3 @@ int deltaAttributes (Task& task) } //////////////////////////////////////////////////////////////////////////////// -int deltaSubstitutions (Task& task) -{ -/* - std::string description = task.get ("description"); - std::vector 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 & 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 start; - std::vector 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 ::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 ::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 ::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 - } -} -*/ - -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/main.h b/src/main.h index ff4495ba1..ebe3347cc 100644 --- a/src/main.h +++ b/src/main.h @@ -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 #include @@ -59,7 +57,6 @@ int deltaPrepend (Task&); int deltaDescription (Task&); int deltaTags (Task&); int deltaAttributes (Task&); -int deltaSubstitutions (Task&); // rules.cpp void initializeColorRules ();