diff --git a/src/Task.cpp b/src/Task.cpp index b5d22ed63..10ae25f0c 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -51,6 +51,8 @@ #ifdef PRODUCT_TASKWARRIOR #include +#include + #define APPROACHING_INFINITY 1000 // Close enough. This isn't rocket surgery. extern Context context; @@ -1734,3 +1736,228 @@ float Task::urgency_blocking () const } //////////////////////////////////////////////////////////////////////////////// +void Task::modify ( + const A3& arguments, + std::string& description) +{ + // Coalesce arguments together into sets to be processed as a batch. + unsigned int pos = 0; + Arg arg; + while ((*this).next_mod_group (arguments, arg, pos)) + { + // Attributes are essentially name:value pairs, and correspond directly + // to stored attributes. + if (arg._category == Arg::cat_attr) + { + std::string name; + std::string value; + A3::extract_attr (arg._raw, name, value); + if (A3::is_attribute (name, name)) // Canonicalize + { + //std::cout << "# Task::modify_task name='" << name << "' value='" << value << "'\n"; + + // Get the column info. + Column* column = context.columns[name]; + + if (value == "") + { + (*this).remove (name); + } + else + { + // Dependencies are used as IDs. + if (name == "depends") + { + // Parse IDs + std::vector deps; + split (deps, value, ','); + + // Apply or remove dendencies in turn. + std::vector ::iterator i; + for (i = deps.begin (); i != deps.end (); i++) + { + bool removal = false; + std::string& dep = *i; + + if (dep[0] == '-') + { + removal = true; + dep = i->substr(1, std::string::npos); + } + + std::vector ids; + // Crude UUID check + if (dep.length () == 36) + { + int id = context.tdb2.pending.id (dep); + ids.push_back (id); + } + else + A3::extract_id (dep, ids); + + std::vector ::iterator id; + for (id = ids.begin (); id != ids.end(); id++) + if (removal) + (*this).removeDependency (*id); + else + (*this).addDependency (*id); + } + } + + // Priorities are converted to upper case. + else if (name == "priority") + { + (*this).set (name, upperCase (value)); + } + + // Dates are special, maybe. + else if (column->type () == "date") + { + // All values must be eval'd first. + A3 value_tokens; + value_tokens.capture (value); + value_tokens = value_tokens.postfix (value_tokens.tokenize (value_tokens)); + + E9 e (value_tokens); + std::string result = e.evalExpression ((*this)); + context.debug (std::string ("Eval '") + value + "' --> '" + result + "'"); + + // If the date value is less than 5 years, it is a duration, not a + // date, therefore add 'now'. + long l = (long) strtod (result.c_str (), NULL); + if (labs (l) < 5 * 365 * 86400) + { + Duration dur (value); + Date now; + now += l; + (*this).set (name, now.toEpochString ()); + } + else + { + Date d (result, context.config.get ("dateformat")); + (*this).set (name, d.toEpochString ()); + } + } + + // Durations too. + else if (name == "recur" || + column->type () == "duration") + { + // All values must be eval'd first, in this case, just to catch errors. + A3 value_tokens; + value_tokens.capture (value); + value_tokens = value_tokens.postfix (value_tokens.tokenize (value_tokens)); + + E9 e (value_tokens); + std::string result = e.evalExpression ((*this)); + context.debug (std::string ("Eval '") + value + "' --> '" + result + "'"); + + Duration d (value); + + // Deliberately storing the 'raw' value, which is necessary for + // durations like 'weekday'.. + (*this).set (name, name == "recur" ? value : result); + } + + // Need handling for numeric types, used by UDAs. + else if (column->type () == "numeric") + { + A3 value_tokens; + value_tokens.capture (value); + value_tokens = value_tokens.postfix (value_tokens.tokenize (value_tokens)); + + E9 e (value_tokens); + std::string result = e.evalExpression ((*this)); + context.debug (std::string ("Eval '") + value + "' --> '" + result + "'"); + + Nibbler n (result); + double d; + if (n.getNumber (d) && + n.depleted ()) + (*this).set (name, result); + else + throw format (STRING_UDA_NUMERIC, result); + } + + // By default, just add/remove it. + else + { + if (column->validate (value)) + (*this).set (name, value); + else + throw format (STRING_INVALID_MOD, name, value); + } + + // Warn about deprecated/obsolete attribute usage. + legacyAttributeCheck (name); + } + } + else + throw format (STRING_CMD_ADD_BAD_ATTRIBUTE, name); + } + + // Tags need special handling because they are essentially a vector stored + // in a single string, therefore Task::{add,remove}Tag must be called as + // appropriate. + else if (arg._category == Arg::cat_tag) + { + char type; + std::string value; + A3::extract_tag (arg._raw, type, value); + + if (type == '+') + { + (*this).addTag (value); + feedback_special_tags ((*this), value); + } + else + (*this).removeTag (value); + } + + // Substitutions. + else if (arg._category == Arg::cat_subst) + { + std::string from; + std::string to; + bool global; + A3::extract_subst (arg._raw, from, to, global); + (*this).substitute (from, to, global); + } + + // Anything else is essentially downgraded to 'word' and considered part of + // the description. + else + { + if (description.length ()) + description += " "; + + description += arg._raw; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Special processing for modifications. +bool Task::next_mod_group (const A3& input, Arg& arg, unsigned int& pos) +{ + if (pos < input.size ()) + { + arg = input[pos++]; + + if (arg._raw == "depends") + { + while (pos < input.size () && + (input[pos]._category == Arg::cat_op || + input[pos]._type == Arg::type_number)) + { + arg._raw += input[pos++]._raw; + } + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/src/Task.h b/src/Task.h index 826adb671..5fcb8525b 100644 --- a/src/Task.h +++ b/src/Task.h @@ -32,6 +32,7 @@ #include #include #include +#include class Task : public std::map { @@ -143,6 +144,9 @@ public: float urgency_c () const; float urgency (); + void modify (const A3&, std::string&); + bool next_mod_group (const A3&, Arg&, unsigned int&); + private: int determineVersion (const std::string&); void parseJSON (const std::string&); diff --git a/src/commands/Command.cpp b/src/commands/Command.cpp index 574040a6e..0cc07d0e0 100644 --- a/src/commands/Command.cpp +++ b/src/commands/Command.cpp @@ -460,200 +460,8 @@ void Command::modify_task ( const A3& arguments, std::string& description) { - // Coalesce arguments together into sets to be processed as a batch. - unsigned int pos = 0; - Arg arg; - while (next_mod_group (arguments, arg, pos)) - { - // Attributes are essentially name:value pairs, and correspond directly - // to stored attributes. - if (arg._category == Arg::cat_attr) - { - std::string name; - std::string value; - A3::extract_attr (arg._raw, name, value); - if (A3::is_attribute (name, name)) // Canonicalize - { - //std::cout << "# Command::modify_task name='" << name << "' value='" << value << "'\n"; - - // Get the column info. - Column* column = context.columns[name]; - - if (value == "") - { - task.remove (name); - } - else - { - // Dependencies are used as IDs. - if (name == "depends") - { - // Parse IDs - std::vector deps; - split (deps, value, ','); - - // Apply or remove dendencies in turn. - std::vector ::iterator i; - for (i = deps.begin (); i != deps.end (); i++) - { - bool removal = false; - std::string& dep = *i; - - if (dep[0] == '-') - { - removal = true; - dep = i->substr(1, std::string::npos); - } - - std::vector ids; - // Crude UUID check - if (dep.length () == 36) - { - int id = context.tdb2.pending.id (dep); - ids.push_back (id); - } - else - A3::extract_id (dep, ids); - - std::vector ::iterator id; - for (id = ids.begin (); id != ids.end(); id++) - if (removal) - task.removeDependency (*id); - else - task.addDependency (*id); - } - } - - // Priorities are converted to upper case. - else if (name == "priority") - { - task.set (name, upperCase (value)); - } - - // Dates are special, maybe. - else if (column->type () == "date") - { - // All values must be eval'd first. - A3 value_tokens; - value_tokens.capture (value); - value_tokens = value_tokens.postfix (value_tokens.tokenize (value_tokens)); - - E9 e (value_tokens); - std::string result = e.evalExpression (task); - context.debug (std::string ("Eval '") + value + "' --> '" + result + "'"); - - // If the date value is less than 5 years, it is a duration, not a - // date, therefore add 'now'. - long l = (long) strtod (result.c_str (), NULL); - if (labs (l) < 5 * 365 * 86400) - { - Duration dur (value); - Date now; - now += l; - task.set (name, now.toEpochString ()); - } - else - { - Date d (result, context.config.get ("dateformat")); - task.set (name, d.toEpochString ()); - } - } - - // Durations too. - else if (name == "recur" || - column->type () == "duration") - { - // All values must be eval'd first, in this case, just to catch errors. - A3 value_tokens; - value_tokens.capture (value); - value_tokens = value_tokens.postfix (value_tokens.tokenize (value_tokens)); - - E9 e (value_tokens); - std::string result = e.evalExpression (task); - context.debug (std::string ("Eval '") + value + "' --> '" + result + "'"); - - Duration d (value); - - // Deliberately storing the 'raw' value, which is necessary for - // durations like 'weekday'.. - task.set (name, name == "recur" ? value : result); - } - - // Need handling for numeric types, used by UDAs. - else if (column->type () == "numeric") - { - A3 value_tokens; - value_tokens.capture (value); - value_tokens = value_tokens.postfix (value_tokens.tokenize (value_tokens)); - - E9 e (value_tokens); - std::string result = e.evalExpression (task); - context.debug (std::string ("Eval '") + value + "' --> '" + result + "'"); - - Nibbler n (result); - double d; - if (n.getNumber (d) && - n.depleted ()) - task.set (name, result); - else - throw format (STRING_UDA_NUMERIC, result); - } - - // By default, just add/remove it. - else - { - if (column->validate (value)) - task.set (name, value); - else - throw format (STRING_INVALID_MOD, name, value); - } - - // Warn about deprecated/obsolete attribute usage. - legacyAttributeCheck (name); - } - } - else - throw format (STRING_CMD_ADD_BAD_ATTRIBUTE, name); - } - - // Tags need special handling because they are essentially a vector stored - // in a single string, therefore Task::{add,remove}Tag must be called as - // appropriate. - else if (arg._category == Arg::cat_tag) - { - char type; - std::string value; - A3::extract_tag (arg._raw, type, value); - - if (type == '+') - { - task.addTag (value); - feedback_special_tags (task, value); - } - else - task.removeTag (value); - } - - // Substitutions. - else if (arg._category == Arg::cat_subst) - { - std::string from; - std::string to; - bool global; - A3::extract_subst (arg._raw, from, to, global); - task.substitute (from, to, global); - } - - // Anything else is essentially downgraded to 'word' and considered part of - // the description. - else - { - if (description.length ()) - description += " "; - - description += arg._raw; - } - } + // Utilize Task::modify + task.modify(arguments, description); } //////////////////////////////////////////////////////////////////////////////// @@ -737,27 +545,3 @@ bool Command::permission ( } //////////////////////////////////////////////////////////////////////////////// -// Special processing for modifications. -bool Command::next_mod_group (const A3& input, Arg& arg, unsigned int& pos) -{ - if (pos < input.size ()) - { - arg = input[pos++]; - - if (arg._raw == "depends") - { - while (pos < input.size () && - (input[pos]._category == Arg::cat_op || - input[pos]._type == Arg::type_number)) - { - arg._raw += input[pos++]._raw; - } - } - - return true; - } - - return false; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/Command.h b/src/commands/Command.h index 1c822d2fe..2e3453d48 100644 --- a/src/commands/Command.h +++ b/src/commands/Command.h @@ -66,8 +66,6 @@ protected: void safety (); bool permission (const Task&, const std::string&, unsigned int); - bool next_mod_group (const A3&, Arg&, unsigned int&); - protected: std::string _keyword; std::string _usage;