diff --git a/src/Cmd.cpp b/src/Cmd.cpp index 6414a6593..86c7d00d3 100644 --- a/src/Cmd.cpp +++ b/src/Cmd.cpp @@ -135,6 +135,7 @@ void Cmd::load () commands.push_back ("_config"); commands.push_back ("_version"); commands.push_back ("_merge"); + commands.push_back ("_urgency"); commands.push_back ("export.csv"); commands.push_back ("export.ical"); commands.push_back ("export.yaml"); @@ -232,6 +233,7 @@ bool Cmd::isReadOnlyCommand () command == "_ids" || command == "_config" || command == "_version" || + command == "_urgency" || command == "export.csv" || command == "export.ical" || command == "export.yaml" || diff --git a/src/Config.cpp b/src/Config.cpp index 3ad0ef921..70f52d238 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -101,6 +101,21 @@ std::string Config::defaults = "journal.time.start.annotation=Started task # Annotation description for the start journal entry\n" "journal.time.stop.annotation=Stopped task # Annotation description for the stop journal entry\n" "\n" + "# Urgency Coefficients\n" + "urgency.next.coefficient=10.0 # Urgency coefficients for 'next' special tag\n" + "urgency.blocking.coefficient=9.0 # Urgency coefficients for blocking tasks\n" + "urgency.blocked.coefficient=8.0 # Urgency coefficients for blocked tasks\n" + "urgency.due.coefficient=7.0 # Urgency coefficients for due dates\n" + "urgency.priority.coefficient=6.0 # Urgency coefficients for priorities\n" + "urgency.waiting.coefficient=5.0 # Urgency coefficients for waiting status\n" + "urgency.active.coefficient=4.0 # Urgency coefficients for active tasks\n" + "urgency.project.coefficient=3.0 # Urgency coefficients for projects\n" + "urgency.tags.coefficient=2.0 # Urgency coefficients for tags\n" + "urgency.annotations.coefficient=1.0 # Urgency coefficients for annotations\n" + "\n" + "#urgency.user.project.foo.coefficient=5.0 # Urgency coefficients for 'foo' project\n" + "#urgency.user.tag.foo.coefficient=5.0 # Urgency coefficients for 'foo' tag\n" + "\n" "# Color controls.\n" "color=on # Enable color\n" #ifdef LINUX diff --git a/src/Context.cpp b/src/Context.cpp index fecf8fed7..a32d47701 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -249,6 +249,7 @@ int Context::dispatch (std::string &out) else if (cmd.command == "_ids") { rc = handleCompletionIDs (out); } else if (cmd.command == "_config") { rc = handleCompletionConfig (out); } else if (cmd.command == "_version") { rc = handleCompletionVersion (out); } + else if (cmd.command == "_urgency") { rc = handleUrgency (out); } else if (cmd.command == "" && sequence.size ()) { rc = handleModify (out); } diff --git a/src/Task.cpp b/src/Task.cpp index ccbbcd6c4..307352301 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -34,6 +34,7 @@ #include "Task.h" #include "text.h" #include "util.h" +#include "main.h" extern Context context; @@ -738,3 +739,171 @@ int Task::determineVersion (const std::string& line) } //////////////////////////////////////////////////////////////////////////////// +// Urgency is defined as a polynomial, the value of which is calculated in this +// function, according to: +// +// U = A.t + B.t + C.t ... +// a b c +// +// U = urgency +// A = coefficient for term a +// t sub a = numeric scale from 0 -> 1, with 1 being the highest +// urgency, derived from one task attribute and mapped +// to the numeric scale +// +// See rfc31-urgency.txt for full details. +// +float Task::urgency () +{ + float urgency = 0.0; + + // urgency.priority.coefficient + float coefficient = (float) context.config.getReal ("urgency.priority.coefficient"); + float term; + + std::string value = get ("priority"); + if (value == "H") term = 1.0; + else if (value == "M") term = 0.65; + else if (value == "L") term = 0.3; + else term = 0.0; + + urgency += term * coefficient; + + // urgency.project.coefficient + coefficient = (float) context.config.getReal ("urgency.project.coefficient"); + + value = get ("project"); + if (value != "") term = 1.0; + else term = 0.0; + + urgency += term * coefficient; + + // urgency.active.coefficient + coefficient = (float) context.config.getReal ("urgency.active.coefficient"); + + value = get ("start"); + if (value != "") term = 1.0; + else term = 0.0; + + urgency += term * coefficient; + + // urgency.waiting.coefficient + coefficient = (float) context.config.getReal ("urgency.waiting.coefficient"); + + value = get ("status"); + if (value == "pending") term = 1.0; + else if (value == "waiting") term = 0.0; + + urgency += term * coefficient; + + // urgency.blocked.coefficient + coefficient = (float) context.config.getReal ("urgency.blocked.coefficient"); + + value = get ("depends"); + if (value != "") term = 0.0; + else term = 1.0; + + urgency += term * coefficient; + + // urgency.annotations.coefficient + coefficient = (float) context.config.getReal ("urgency.annotations.coefficient"); + + std::vector annos; + getAnnotations (annos); + if (annos.size () >= 3) term = 1.0; + else if (annos.size () == 2) term = 0.9; + else if (annos.size () == 1) term = 0.8; + else term = 0.0; + + urgency += term * coefficient; + + // urgency.tags.coefficient + coefficient = (float) context.config.getReal ("urgency.tags.coefficient"); + + int count = getTagCount (); + if (count >= 3) term = 1.0; + else if (count == 2) term = 0.9; + else if (count == 1) term = 0.8; + else term = 0.0; + + urgency += term * coefficient; + + // urgency.next.coefficient + coefficient = (float) context.config.getReal ("urgency.next.coefficient"); + + if (hasTag ("next")) term = 1.0; + else term = 0.0; + + urgency += term * coefficient; + + // urgency.due.coefficient + // days overdue, capped at 7 -> 0.8 - 1.0 + // due today -> 0.7 + // days until due, capped at 14 -> 0.4 - 0.6 + // has due date -> 0.3 + // no due date -> 0.0 + coefficient = (float) context.config.getReal ("urgency.due.coefficient"); + if (has ("due")) + { + Date now; + Date due (get ("due")); + int days_overdue = (now - due) / 86400; + + if (days_overdue > 7) term = 1.0; + else if (days_overdue > 0) term = (0.2 * days_overdue / 7.0) + 0.8; + else if (due.sameDay (now)) term = 0.7; + else if (days_overdue < -14) term = 0.4; + else if (days_overdue < 0) term = (0.2 * days_overdue / 14.0) + 0.6; + else term = 0.3; + } + else + term = 0.0; + + urgency += term * coefficient; + + // Tag- and project-specific coefficients. + std::vector all; + context.config.all (all); + + foreach (var, all) + { + if (var->substr (0, 13) == "urgency.user.") + { + // urgency.user.project..coefficient + std::string::size_type end = std::string::npos; + if (var->substr (13, 8) == "project." && + (end = var->find (".coefficient")) != std::string::npos) + { + std::string project = var->substr (21, end - 21); + coefficient = (float) context.config.getReal (*var); + + if (get ("project").find (project) == 0) + urgency += coefficient; + } + + // urgency.user.tag..coefficient + if (var->substr (13, 4) == "tag." && + (end = var->find (".coefficient")) != std::string::npos) + { + std::string tag = var->substr (17, end - 17); + coefficient = (float) context.config.getReal (*var); + + if (hasTag (tag)) + urgency += coefficient; + } + } + } + + // urgency.blocking.coefficient + coefficient = (float) context.config.getReal ("urgency.blocking.coefficient"); + + if (dependencyIsBlocking (*this)) term = 1.0; + else term = 0.0; + + urgency += term * coefficient; + + // Return the sum of all terms. + return urgency; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Task.h b/src/Task.h index 4ff53405e..40b06c391 100644 --- a/src/Task.h +++ b/src/Task.h @@ -81,6 +81,8 @@ public: void validate () const; + float urgency (); + private: int determineVersion (const std::string&); void legacyParse (const std::string&); diff --git a/src/command.cpp b/src/command.cpp index a8d99d592..a04902eed 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -515,6 +515,36 @@ int handleCompletionVersion (std::string &outs) return 0; } +//////////////////////////////////////////////////////////////////////////////// +// Temporary command to display urgency for a task. +int handleUrgency (std::string &outs) +{ + // Get all the tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.loadPending (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); + + // Filter sequence. + context.filter.applySequence (tasks, context.sequence); + + // Find the task(s). + std::stringstream out; + foreach (task, tasks) + { + out << "task " + << task->id + << " urgency " + << task->urgency () + << "\n"; + } + + outs = out.str (); + return 0; +} + //////////////////////////////////////////////////////////////////////////////// int handleCompletionIDs (std::string &outs) { @@ -715,7 +745,12 @@ int handleShow (std::string &outs) "import.synonym.status import.synonym.tags import.synonym.entry " "import.synonym.start import.synonym.due import.synonym.recur " "import.synonym.end import.synonym.project import.synonym.priority " - "import.synonym.fg import.synonym.bg import.synonym.description "; + "import.synonym.fg import.synonym.bg import.synonym.description " + + "urgency.next.coefficient urgency.blocking.coefficient urgency.blocked.coefficient " + "urgency.due.coefficient urgency.priority.coefficient urgency.waiting.coefficient " + "urgency.active.coefficient urgency.project.coefficient urgency.tags.coefficient " + "urgency.annotations.coefficient "; // This configuration variable is supported, but not documented. It exists // so that unit tests can force color to be on even when the output from task @@ -732,13 +767,15 @@ int handleShow (std::string &outs) { // These are special configuration variables, because their name is // dynamic. - if (i->substr (0, 14) != "color.keyword." && - i->substr (0, 14) != "color.project." && - i->substr (0, 10) != "color.tag." && - i->substr (0, 8) != "holiday." && - i->substr (0, 7) != "report." && - i->substr (0, 6) != "alias." && - i->substr (0, 5) != "hook.") + if (i->substr (0, 14) != "color.keyword." && + i->substr (0, 14) != "color.project." && + i->substr (0, 10) != "color.tag." && + i->substr (0, 8) != "holiday." && + i->substr (0, 7) != "report." && + i->substr (0, 6) != "alias." && + i->substr (0, 5) != "hook." && + i->substr (0, 21) != "urgency.user.project." && + i->substr (0, 17) != "urgency.user.tag.") { unrecognized.push_back (*i); } diff --git a/src/main.h b/src/main.h index b594afdb5..338390be1 100644 --- a/src/main.h +++ b/src/main.h @@ -69,6 +69,7 @@ int handleCompletionCommands (std::string &); int handleCompletionIDs (std::string &); int handleCompletionConfig (std::string &); int handleCompletionVersion (std::string &); +int handleUrgency (std::string &); int handleVersion (std::string &); int handleConfig (std::string &); int handleShow (std::string &); diff --git a/src/tests/t.t.cpp b/src/tests/t.t.cpp index 497f54c0f..a1dd01dba 100644 --- a/src/tests/t.t.cpp +++ b/src/tests/t.t.cpp @@ -153,6 +153,8 @@ TODO Task::removeDependency TODO Task::getDependencies TODO Task::getDependencies +TODO Task::urgency + */ // Task::operator==