taskwarrior/src/commands/CmdInfo.cpp
2018-01-15 13:35:38 -05:00

614 lines
20 KiB
C++

////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2018, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// http://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#include <CmdInfo.h>
#include <sstream>
#include <stdlib.h>
#include <math.h>
#include <Context.h>
#include <Filter.h>
#include <Datetime.h>
#include <Duration.h>
#include <main.h>
#include <shared.h>
#include <format.h>
#include <util.h>
#include <i18n.h>
extern Context context;
////////////////////////////////////////////////////////////////////////////////
CmdInfo::CmdInfo ()
{
_keyword = "information";
_usage = "task <filter> information";
_description = STRING_CMD_INFO_USAGE;
_read_only = true;
// This is inaccurate, but it does prevent a GC. While this doesn't make a
// lot of sense, given that the info command shows the ID, it does mimic the
// behavior of versions prior to 2.0, which the test suite relies upon.
//
// Once the test suite is completely modified, this can be corrected.
_displays_id = false;
_needs_gc = false;
_uses_context = false;
_accepts_filter = true;
_accepts_modifications = false;
_accepts_miscellaneous = false;
_category = Command::Category::metadata;
}
////////////////////////////////////////////////////////////////////////////////
int CmdInfo::execute (std::string& output)
{
auto rc = 0;
// Apply filter.
Filter filter;
std::vector <Task> filtered;
filter.subset (filtered);
if (! filtered.size ())
{
context.footnote (STRING_FEEDBACK_NO_MATCH);
rc = 1;
}
// Get the undo data.
std::vector <std::string> undo;
if (context.config.getBoolean ("journal.info"))
undo = context.tdb2.undo.get_lines ();
// Determine the output date format, which uses a hierarchy of definitions.
// rc.dateformat.info
// rc.dateformat
auto dateformat = context.config.get ("dateformat.info");
if (dateformat == "")
dateformat = context.config.get ("dateformat");
auto dateformatanno = context.config.get ("dateformat.annotation");
if (dateformatanno == "")
dateformatanno = dateformat;
// Render each task.
std::stringstream out;
for (auto& task : filtered)
{
Table view;
view.width (context.getWidth ());
if (context.config.getBoolean ("obfuscate"))
view.obfuscate ();
if (context.color ())
view.forceColor ();
view.add (STRING_COLUMN_LABEL_NAME);
view.add (STRING_COLUMN_LABEL_VALUE);
setHeaderUnderline (view);
Datetime now;
// id
auto row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_ID);
view.set (row, 1, (task.id ? format (task.id) : "-"));
std::string status = Lexer::ucFirst (Task::statusToText (task.getStatus ()));
// description
Color c;
autoColorize (task, c);
auto description = task.get ("description");
auto indent = context.config.getInteger ("indent.annotation");
for (auto& anno : task.getAnnotations ())
description += '\n'
+ std::string (indent, ' ')
+ Datetime (anno.first.substr (11)).toString (dateformatanno)
+ ' '
+ anno.second;
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_DESC);
view.set (row, 1, description, c);
// status
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_STATUS);
view.set (row, 1, status);
// project
if (task.has ("project"))
{
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_PROJECT);
view.set (row, 1, task.get ("project"));
}
// dependencies: blocked
{
auto blocked = dependencyGetBlocking (task);
if (blocked.size ())
{
std::stringstream message;
for (auto& block : blocked)
message << block.id << ' ' << block.get ("description") << '\n';
row = view.addRow ();
view.set (row, 0, STRING_CMD_INFO_BLOCKED);
view.set (row, 1, message.str ());
}
}
// dependencies: blocking
{
auto blocking = dependencyGetBlocked (task);
if (blocking.size ())
{
std::stringstream message;
for (auto& block : blocking)
message << block.id << ' ' << block.get ("description") << '\n';
row = view.addRow ();
view.set (row, 0, STRING_CMD_INFO_BLOCKING);
view.set (row, 1, message.str ());
}
}
// recur
if (task.has ("recur"))
{
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_RECUR_L);
view.set (row, 1, task.get ("recur"));
}
// parent
// 2017-01-07: Deprecated in 2.6.0
if (task.has ("parent"))
{
row = view.addRow ();
view.set (row, 0, "Parent task");
view.set (row, 1, task.get ("parent"));
}
// mask
// 2017-01-07: Deprecated in 2.6.0
if (task.has ("mask"))
{
row = view.addRow ();
view.set (row, 0, "Mask");
view.set (row, 1, task.get ("mask"));
}
// imask
// 2017-01-07: Deprecated in 2.6.0
if (task.has ("imask"))
{
row = view.addRow ();
view.set (row, 0, "Mask Index");
view.set (row, 1, task.get ("imask"));
}
// template
if (task.has ("template"))
{
row = view.addRow ();
view.set (row, 0, "Template task");
view.set (row, 1, task.get ("template"));
}
// last
if (task.has ("last"))
{
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_LAST);
view.set (row, 1, task.get ("last"));
}
// rtype
if (task.has ("rtype"))
{
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_RTYPE);
view.set (row, 1, task.get ("rtype"));
}
// entry
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_ENTERED);
Datetime dt (task.get_date ("entry"));
std::string entry = dt.toString (dateformat);
std::string age;
auto created = task.get ("entry");
if (created.length ())
{
Datetime dt (strtol (created.c_str (), NULL, 10));
age = Duration (now - dt).formatVague ();
}
view.set (row, 1, entry + " (" + age + ')');
// wait
if (task.has ("wait"))
{
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_WAITING);
view.set (row, 1, Datetime (task.get_date ("wait")).toString (dateformat));
}
// scheduled
if (task.has ("scheduled"))
{
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_SCHED);
view.set (row, 1, Datetime (task.get_date ("scheduled")).toString (dateformat));
}
// start
if (task.has ("start"))
{
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_START);
view.set (row, 1, Datetime (task.get_date ("start")).toString (dateformat));
}
// due (colored)
if (task.has ("due"))
{
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_DUE);
view.set (row, 1, Datetime (task.get_date ("due")).toString (dateformat));
}
// end
if (task.has ("end"))
{
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_END);
view.set (row, 1, Datetime (task.get_date ("end")).toString (dateformat));
}
// until
if (task.has ("until"))
{
row = view.addRow ();
view.set (row, 0, STRING_CMD_INFO_UNTIL);
view.set (row, 1, Datetime (task.get_date ("until")).toString (dateformat));
}
// modified
if (task.has ("modified"))
{
row = view.addRow ();
view.set (row, 0, STRING_CMD_INFO_MODIFIED);
Datetime mod (task.get_date ("modified"));
std::string age = Duration (now - mod).formatVague ();
view.set (row, 1, mod.toString (dateformat) + " (" + age + ')');
}
// tags ...
auto tags = task.getTags ();
if (tags.size ())
{
auto allTags = join (" ", tags);
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_TAGS);
view.set (row, 1, allTags);
}
// Virtual tags.
{
// Note: This list must match that in Task::hasTag.
// Note: This list must match that in ::feedback_reserved_tags.
std::string virtualTags = "";
if (task.hasTag ("ACTIVE")) virtualTags += "ACTIVE ";
if (task.hasTag ("ANNOTATED")) virtualTags += "ANNOTATED ";
if (task.hasTag ("BLOCKED")) virtualTags += "BLOCKED ";
if (task.hasTag ("BLOCKING")) virtualTags += "BLOCKING ";
if (task.hasTag ("CHILD")) virtualTags += "CHILD "; // 2017-01-07: Deprecated in 2.6.0
if (task.hasTag ("COMPLETED")) virtualTags += "COMPLETED ";
if (task.hasTag ("DELETED")) virtualTags += "DELETED ";
if (task.hasTag ("DUE")) virtualTags += "DUE ";
if (task.hasTag ("DUETODAY")) virtualTags += "DUETODAY ";
if (task.hasTag ("INSTANCE")) virtualTags += "INSTANCE ";
if (task.hasTag ("MONTH")) virtualTags += "MONTH ";
if (task.hasTag ("ORPHAN")) virtualTags += "ORPHAN ";
if (task.hasTag ("OVERDUE")) virtualTags += "OVERDUE ";
if (task.hasTag ("PARENT")) virtualTags += "PARENT "; // 2017-01-07: Deprecated in 2.6.0
if (task.hasTag ("PENDING")) virtualTags += "PENDING ";
if (task.hasTag ("READY")) virtualTags += "READY ";
if (task.hasTag ("SCHEDULED")) virtualTags += "SCHEDULED ";
if (task.hasTag ("TAGGED")) virtualTags += "TAGGED ";
if (task.hasTag ("TEMPLATE")) virtualTags += "TEMPLATE ";
if (task.hasTag ("TODAY")) virtualTags += "TODAY ";
if (task.hasTag ("TOMORROW")) virtualTags += "TOMORROW ";
if (task.hasTag ("UDA")) virtualTags += "UDA ";
if (task.hasTag ("UNBLOCKED")) virtualTags += "UNBLOCKED ";
if (task.hasTag ("UNTIL")) virtualTags += "UNTIL ";
if (task.hasTag ("WAITING")) virtualTags += "WAITING ";
if (task.hasTag ("WEEK")) virtualTags += "WEEK ";
if (task.hasTag ("YEAR")) virtualTags += "YEAR ";
if (task.hasTag ("YESTERDAY")) virtualTags += "YESTERDAY ";
if (task.hasTag ("LATEST")) virtualTags += "LATEST ";
if (task.hasTag ("PROJECT")) virtualTags += "PROJECT ";
if (task.hasTag ("PRIORITY")) virtualTags += "PRIORITY ";
// If you update the above list, update src/commands/CmdInfo.cpp and src/commands/CmdTags.cpp as well.
row = view.addRow ();
view.set (row, 0, STRING_CMD_INFO_VIRTUAL_TAGS);
view.set (row, 1, virtualTags);
}
// uuid
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_UUID);
auto uuid = task.get ("uuid");
view.set (row, 1, uuid);
// Task::urgency
row = view.addRow ();
view.set (row, 0, STRING_COLUMN_LABEL_URGENCY);
view.set (row, 1, format (task.urgency (), 4, 4));
// Show any UDAs
auto all = task.all ();
std::string type;
for (auto& att: all)
{
if (context.columns.find (att) != context.columns.end ())
{
Column* col = context.columns[att];
if (col->is_uda ())
{
auto value = task.get (att);
if (value != "")
{
row = view.addRow ();
view.set (row, 0, col->label ());
if (type == "date")
value = Datetime (value).toString (dateformat);
else if (type == "duration")
{
Duration iso;
std::string::size_type cursor = 0;
if (iso.parse (value, cursor))
value = (std::string) Variant (iso.toTime_t (), Variant::type_duration);
else
value = "PT0S";
}
view.set (row, 1, value);
}
}
}
}
// Show any orphaned UDAs, which are identified by not being represented in
// the context.columns map.
for (auto& att : all)
{
if (att.substr (0, 11) != "annotation_" &&
context.columns.find (att) == context.columns.end ())
{
row = view.addRow ();
view.set (row, 0, '[' + att);
view.set (row, 1, task.get (att) + ']');
}
}
// Create a second table, containing urgency details, if necessary.
Table urgencyDetails;
if (task.urgency () != 0.0)
{
setHeaderUnderline (urgencyDetails);
if (context.color ())
{
Color alternate (context.config.get ("color.alternate"));
urgencyDetails.colorOdd (alternate);
urgencyDetails.intraColorOdd (alternate);
}
if (context.config.getBoolean ("obfuscate"))
urgencyDetails.obfuscate ();
if (context.config.getBoolean ("color"))
view.forceColor ();
urgencyDetails.width (context.getWidth ());
urgencyDetails.add (""); // Attribute
urgencyDetails.add (""); // Value
urgencyDetails.add (""); // *
urgencyDetails.add (""); // Coefficient
urgencyDetails.add (""); // =
urgencyDetails.add (""); // Result
urgencyTerm (urgencyDetails, "project", task.urgency_project (), Task::urgencyProjectCoefficient);
urgencyTerm (urgencyDetails, "active", task.urgency_active (), Task::urgencyActiveCoefficient);
urgencyTerm (urgencyDetails, "scheduled", task.urgency_scheduled (), Task::urgencyScheduledCoefficient);
urgencyTerm (urgencyDetails, "waiting", task.urgency_waiting (), Task::urgencyWaitingCoefficient);
urgencyTerm (urgencyDetails, "blocked", task.urgency_blocked (), Task::urgencyBlockedCoefficient);
urgencyTerm (urgencyDetails, "blocking", task.urgency_blocking (), Task::urgencyBlockingCoefficient);
urgencyTerm (urgencyDetails, "annotations", task.urgency_annotations (), Task::urgencyAnnotationsCoefficient);
urgencyTerm (urgencyDetails, "tags", task.urgency_tags (), Task::urgencyTagsCoefficient);
urgencyTerm (urgencyDetails, "due", task.urgency_due (), Task::urgencyDueCoefficient);
urgencyTerm (urgencyDetails, "age", task.urgency_age (), Task::urgencyAgeCoefficient);
// Tag, Project- and UDA-specific coefficients.
for (auto& var : Task::coefficients)
{
if (var.first.substr (0, 13) == "urgency.user.")
{
// urgency.user.project.<project>.coefficient
auto end = std::string::npos;
if (var.first.substr (13, 8) == "project." &&
(end = var.first.find (".coefficient")) != std::string::npos)
{
auto project = var.first.substr (21, end - 21);
if (task.get ("project").find (project) == 0)
urgencyTerm (urgencyDetails, "PROJECT " + project, 1.0, var.second);
}
// urgency.user.tag.<tag>.coefficient
if (var.first.substr (13, 4) == "tag." &&
(end = var.first.find (".coefficient")) != std::string::npos)
{
auto name = var.first.substr (17, end - 17);
if (task.hasTag (name))
urgencyTerm (urgencyDetails, "TAG " + name, 1.0, var.second);
}
// urgency.user.keyword.<keyword>.coefficient
if (var.first.substr (13, 8) == "keyword." &&
(end = var.first.find (".coefficient")) != std::string::npos)
{
auto keyword = var.first.substr (21, end - 21);
if (task.get ("description").find (keyword) != std::string::npos)
urgencyTerm (urgencyDetails, "KEYWORD " + keyword, 1.0, var.second);
}
}
// urgency.uda.<name>.coefficient
else if (var.first.substr (0, 12) == "urgency.uda.")
{
// urgency.uda.<name>.coefficient
// urgency.uda.<name>.<value>.coefficient
auto end = var.first.find (".coefficient");
if (end != std::string::npos)
{
auto uda = var.first.substr (12, end - 12);
auto dot = uda.find (".");
if (dot == std::string::npos)
{
// urgency.uda.<name>.coefficient
if (task.has (uda))
urgencyTerm (urgencyDetails, std::string ("UDA ") + uda, 1.0, var.second);
}
else
{
// urgency.uda.<name>.<value>.coefficient
if (task.get (uda.substr(0, dot)) == uda.substr(dot+1))
urgencyTerm (urgencyDetails, std::string ("UDA ") + uda, 1.0, var.second);
}
}
}
}
row = urgencyDetails.addRow ();
urgencyDetails.set (row, 5, rightJustify ("------", 6));
row = urgencyDetails.addRow ();
urgencyDetails.set (row, 5, rightJustify (format (task.urgency (), 4, 4), 6));
}
// Create a third table, containing undo log change details.
Table journal;
setHeaderUnderline (journal);
if (context.config.getBoolean ("obfuscate"))
journal.obfuscate ();
if (context.config.getBoolean ("color"))
journal.forceColor ();
journal.width (context.getWidth ());
journal.add (STRING_COLUMN_LABEL_DATE);
journal.add (STRING_CMD_INFO_MODIFICATION);
if (context.config.getBoolean ("journal.info") &&
undo.size () > 3)
{
// Scan the undo data for entries matching this task, without making
// copies.
unsigned int i = 0;
long last_timestamp = 0;
while (i < undo.size ())
{
int when = i++;
int previous = -1;
if (! undo[i].compare (0, 3, "old", 3))
previous = i++;
int current = i++;
i++; // Separator
if (undo[current].find ("uuid:\"" + uuid) != std::string::npos)
{
if (previous != -1)
{
int row = journal.addRow ();
Datetime timestamp (strtol (undo[when].substr (5).c_str (), NULL, 10));
journal.set (row, 0, timestamp.toString (dateformat));
Task before (undo[previous].substr (4));
Task after (undo[current].substr (4));
journal.set (row, 1, taskInfoDifferences (before, after, dateformat, last_timestamp, timestamp.toEpoch()));
}
}
}
}
out << optionalBlankLine ()
<< view.render ()
<< '\n';
if (urgencyDetails.rows () > 0)
out << urgencyDetails.render ()
<< '\n';
if (journal.rows () > 0)
out << journal.render ()
<< '\n';
}
output = out.str ();
return rc;
}
////////////////////////////////////////////////////////////////////////////////
void CmdInfo::urgencyTerm (
Table& view,
const std::string& label,
float measure,
float coefficient) const
{
auto value = measure * coefficient;
if (value != 0.0)
{
auto row = view.addRow ();
view.set (row, 0, " " + label);
view.set (row, 1, rightJustify (format (measure, 5, 3), 6));
view.set (row, 2, "*");
view.set (row, 3, rightJustify (format (coefficient, 4, 2), 4));
view.set (row, 4, "=");
view.set (row, 5, rightJustify (format (value, 5, 3), 6));
}
}
////////////////////////////////////////////////////////////////////////////////