mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-30 22:43:24 +02:00
Migrated core task modification code to the Task object, leaving wrappers behind for legacy usage.
This commit is contained in:
parent
1072852ca8
commit
55a8b157b1
4 changed files with 233 additions and 220 deletions
227
src/Task.cpp
227
src/Task.cpp
|
@ -51,6 +51,8 @@
|
||||||
#ifdef PRODUCT_TASKWARRIOR
|
#ifdef PRODUCT_TASKWARRIOR
|
||||||
#include <main.h>
|
#include <main.h>
|
||||||
|
|
||||||
|
#include <E9.h>
|
||||||
|
|
||||||
#define APPROACHING_INFINITY 1000 // Close enough. This isn't rocket surgery.
|
#define APPROACHING_INFINITY 1000 // Close enough. This isn't rocket surgery.
|
||||||
|
|
||||||
extern Context context;
|
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 <std::string> deps;
|
||||||
|
split (deps, value, ',');
|
||||||
|
|
||||||
|
// Apply or remove dendencies in turn.
|
||||||
|
std::vector <std::string>::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 <int> 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 <int>::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
|
@ -32,6 +32,7 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <A3.h>
|
||||||
|
|
||||||
class Task : public std::map <std::string, std::string>
|
class Task : public std::map <std::string, std::string>
|
||||||
{
|
{
|
||||||
|
@ -143,6 +144,9 @@ public:
|
||||||
float urgency_c () const;
|
float urgency_c () const;
|
||||||
float urgency ();
|
float urgency ();
|
||||||
|
|
||||||
|
void modify (const A3&, std::string&);
|
||||||
|
bool next_mod_group (const A3&, Arg&, unsigned int&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int determineVersion (const std::string&);
|
int determineVersion (const std::string&);
|
||||||
void parseJSON (const std::string&);
|
void parseJSON (const std::string&);
|
||||||
|
|
|
@ -460,200 +460,8 @@ void Command::modify_task (
|
||||||
const A3& arguments,
|
const A3& arguments,
|
||||||
std::string& description)
|
std::string& description)
|
||||||
{
|
{
|
||||||
// Coalesce arguments together into sets to be processed as a batch.
|
// Utilize Task::modify
|
||||||
unsigned int pos = 0;
|
task.modify(arguments, description);
|
||||||
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 <std::string> deps;
|
|
||||||
split (deps, value, ',');
|
|
||||||
|
|
||||||
// Apply or remove dendencies in turn.
|
|
||||||
std::vector <std::string>::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 <int> 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 <int>::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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
|
@ -66,8 +66,6 @@ protected:
|
||||||
void safety ();
|
void safety ();
|
||||||
bool permission (const Task&, const std::string&, unsigned int);
|
bool permission (const Task&, const std::string&, unsigned int);
|
||||||
|
|
||||||
bool next_mod_group (const A3&, Arg&, unsigned int&);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string _keyword;
|
std::string _keyword;
|
||||||
std::string _usage;
|
std::string _usage;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue