mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
815 lines
27 KiB
C++
815 lines
27 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
// task - a command line task list manager.
|
|
//
|
|
// Copyright 2006 - 2010, Paul Beckingham.
|
|
// All rights reserved.
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify it under
|
|
// the terms of the GNU General Public License as published by the Free Software
|
|
// Foundation; either version 2 of the License, or (at your option) any later
|
|
// version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
// details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// this program; if not, write to the
|
|
//
|
|
// Free Software Foundation, Inc.,
|
|
// 51 Franklin Street, Fifth Floor,
|
|
// Boston, MA
|
|
// 02110-1301
|
|
// USA
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <sys/types.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <pwd.h>
|
|
#include <time.h>
|
|
|
|
#include "Context.h"
|
|
#include "Date.h"
|
|
#include "Table.h"
|
|
#include "text.h"
|
|
#include "util.h"
|
|
#include "main.h"
|
|
|
|
#ifdef HAVE_LIBNCURSES
|
|
#include <ncurses.h>
|
|
#endif
|
|
|
|
extern Context context;
|
|
static std::vector <std::string> customReports;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// This report will eventually become the one report that many others morph into
|
|
// via the .taskrc file.
|
|
int handleCustomReport (const std::string& report, std::string &outs)
|
|
{
|
|
// Load report configuration.
|
|
std::string columnList = context.config.get ("report." + report + ".columns");
|
|
std::string labelList = context.config.get ("report." + report + ".labels");
|
|
std::string sortList = context.config.get ("report." + report + ".sort");
|
|
std::string filterList = context.config.get ("report." + report + ".filter");
|
|
|
|
std::vector <std::string> filterArgs;
|
|
split (filterArgs, filterList, ' ');
|
|
{
|
|
Cmd cmd (report);
|
|
Task task;
|
|
Sequence sequence;
|
|
Subst subst;
|
|
Filter filter;
|
|
context.parse (filterArgs, cmd, task, sequence, subst, filter);
|
|
|
|
context.sequence.combine (sequence);
|
|
|
|
// Allow limit to be overridden by the command line.
|
|
if (!context.task.has ("limit") && task.has ("limit"))
|
|
context.task.set ("limit", task.get ("limit"));
|
|
|
|
foreach (att, filter)
|
|
context.filter.push_back (*att);
|
|
}
|
|
|
|
// Get all the tasks.
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.getBoolean ("locking"));
|
|
handleRecurrence ();
|
|
context.tdb.load (tasks, context.filter);
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
return runCustomReport (
|
|
report,
|
|
columnList,
|
|
labelList,
|
|
sortList,
|
|
filterList,
|
|
tasks,
|
|
outs);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// This report will eventually become the one report that many others morph into
|
|
// via the .taskrc file.
|
|
int runCustomReport (
|
|
const std::string& report,
|
|
const std::string& columnList,
|
|
const std::string& labelList,
|
|
const std::string& sortList,
|
|
const std::string& filterList,
|
|
std::vector <Task>& tasks,
|
|
std::string &outs)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (context.hooks.trigger ("pre-custom-report-command") &&
|
|
context.hooks.trigger (std::string ("pre-") + report + "-command"))
|
|
{
|
|
// Load report configuration.
|
|
std::vector <std::string> columns;
|
|
split (columns, columnList, ',');
|
|
validReportColumns (columns);
|
|
|
|
std::vector <std::string> labels;
|
|
split (labels, labelList, ',');
|
|
|
|
if (columns.size () != labels.size () && labels.size () != 0)
|
|
throw std::string ("There are a different number of columns than labels ") +
|
|
"for report '" + report + "'.";
|
|
|
|
std::map <std::string, std::string> columnLabels;
|
|
if (labels.size ())
|
|
for (unsigned int i = 0; i < columns.size (); ++i)
|
|
columnLabels[columns[i]] = labels[i];
|
|
|
|
std::vector <std::string> sortOrder;
|
|
split (sortOrder, sortList, ',');
|
|
validSortColumns (columns, sortOrder);
|
|
|
|
std::vector <std::string> filterArgs;
|
|
split (filterArgs, filterList, ' ');
|
|
{
|
|
Cmd cmd (report);
|
|
Task task;
|
|
Sequence sequence;
|
|
Subst subst;
|
|
Filter filter;
|
|
context.parse (filterArgs, cmd, task, sequence, subst, filter);
|
|
|
|
context.sequence.combine (sequence);
|
|
|
|
// Allow limit to be overridden by the command line.
|
|
if (!context.task.has ("limit") && task.has ("limit"))
|
|
context.task.set ("limit", task.get ("limit"));
|
|
|
|
foreach (att, filter)
|
|
context.filter.push_back (*att);
|
|
}
|
|
|
|
// Filter sequence.
|
|
if (context.sequence.size ())
|
|
context.filter.applySequence (tasks, context.sequence);
|
|
|
|
// Initialize colorization for subsequent auto colorization.
|
|
initializeColorRules ();
|
|
|
|
Table table;
|
|
table.setTableWidth (context.getWidth ());
|
|
table.setDateFormat (context.config.get ("dateformat"));
|
|
table.setReportName (report);
|
|
|
|
foreach (task, tasks)
|
|
{
|
|
table.addRow ();
|
|
context.hooks.trigger ("pre-display", *task);
|
|
}
|
|
|
|
int columnCount = 0;
|
|
int dueColumn = -1;
|
|
foreach (col, columns)
|
|
{
|
|
// Add each column individually.
|
|
if (*col == "id")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "ID");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
char s[16];
|
|
std::string value;
|
|
int row = 0;
|
|
foreach (task, tasks)
|
|
{
|
|
if (task->id != 0)
|
|
{
|
|
sprintf (s, "%d", (int) task->id);
|
|
value = s;
|
|
}
|
|
else
|
|
{
|
|
value = "-";
|
|
}
|
|
|
|
context.hooks.trigger ("format-id", "id", value);
|
|
table.addCell (row++, columnCount, value);
|
|
}
|
|
}
|
|
|
|
else if (*col == "uuid")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "UUID");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::left);
|
|
|
|
std::string value;
|
|
int row = 0;
|
|
foreach (task, tasks)
|
|
{
|
|
value = task->get ("uuid");
|
|
context.hooks.trigger ("format-uuid", "uuid", value);
|
|
table.addCell (row++, columnCount, value);
|
|
}
|
|
}
|
|
|
|
else if (*col == "project")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Project");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::left);
|
|
|
|
std::string value;
|
|
int row = 0;
|
|
foreach (task, tasks)
|
|
{
|
|
value = task->get ("project");
|
|
context.hooks.trigger ("format-project", "project", value);
|
|
table.addCell (row++, columnCount, value);
|
|
}
|
|
}
|
|
|
|
else if (*col == "priority")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Pri");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::left);
|
|
|
|
int row = 0;
|
|
foreach (task, tasks)
|
|
{
|
|
std::string value = task->get ("priority");
|
|
context.hooks.trigger ("format-priority", "priority", value);
|
|
table.addCell (row++, columnCount, value);
|
|
}
|
|
}
|
|
|
|
else if (*col == "priority_long")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Pri");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::left);
|
|
|
|
int row = 0;
|
|
std::string pri;
|
|
foreach (task, tasks)
|
|
{
|
|
pri = task->get ("priority");
|
|
|
|
if (pri == "H") pri = "High"; // TODO i18n
|
|
else if (pri == "M") pri = "Medium"; // TODO i18n
|
|
else if (pri == "L") pri = "Low"; // TODO i18n
|
|
|
|
context.hooks.trigger ("format-priority_long", "priority", pri);
|
|
table.addCell (row++, columnCount, pri);
|
|
}
|
|
}
|
|
|
|
else if (*col == "entry")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Added");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
std::string entered;
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
entered = tasks[row].get ("entry");
|
|
if (entered.length ())
|
|
{
|
|
Date dt (::atoi (entered.c_str ()));
|
|
entered = dt.toString (context.config.get ("dateformat"));
|
|
context.hooks.trigger ("format-entry", "entry", entered);
|
|
table.addCell (row, columnCount, entered);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "entry_time")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Added");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
std::string entered;
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
entered = tasks[row].get ("entry");
|
|
if (entered.length ())
|
|
{
|
|
Date dt (::atoi (entered.c_str ()));
|
|
entered = dt.toStringWithTime (context.config.get ("dateformat"));
|
|
context.hooks.trigger ("format-entry_time", "entry_time", entered);
|
|
table.addCell (row, columnCount, entered);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "start")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Started");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
std::string started;
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
started = tasks[row].get ("start");
|
|
if (started.length ())
|
|
{
|
|
Date dt (::atoi (started.c_str ()));
|
|
started = dt.toString (context.config.get ("dateformat"));
|
|
context.hooks.trigger ("format-start", "start", started);
|
|
table.addCell (row, columnCount, started);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "start_time")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Started");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
std::string started;
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
started = tasks[row].get ("start");
|
|
if (started.length ())
|
|
{
|
|
Date dt (::atoi (started.c_str ()));
|
|
started = dt.toStringWithTime (context.config.get ("dateformat"));
|
|
context.hooks.trigger ("format-start_time", "start_time", started);
|
|
table.addCell (row, columnCount, started);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "end")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Completed");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
std::string ended;
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
ended = tasks[row].get ("end");
|
|
if (ended.length ())
|
|
{
|
|
Date dt (::atoi (ended.c_str ()));
|
|
ended = dt.toString (context.config.get ("dateformat"));
|
|
context.hooks.trigger ("format-end", "end", ended);
|
|
table.addCell (row, columnCount, ended);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "end_time")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Completed");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
std::string format = context.config.get ("dateformat");
|
|
|
|
std::string ended;
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
ended = tasks[row].get ("end");
|
|
if (ended.length ())
|
|
{
|
|
Date dt (::atoi (ended.c_str ()));
|
|
ended = dt.toStringWithTime (format);
|
|
context.hooks.trigger ("format-end_time", "end_time", ended);
|
|
table.addCell (row, columnCount, ended);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "due")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Due");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::left);
|
|
|
|
std::string format = context.config.get ("report." + report + ".dateformat");
|
|
if (format == "")
|
|
format = context.config.get ("dateformat.report");
|
|
if (format == "")
|
|
format = context.config.get ("dateformat");
|
|
|
|
int row = 0;
|
|
std::string due;
|
|
foreach (task, tasks)
|
|
{
|
|
std::string value = getDueDate (*task, format);
|
|
context.hooks.trigger ("format-due", "due", value);
|
|
table.addCell (row++, columnCount, value);
|
|
}
|
|
|
|
dueColumn = columnCount;
|
|
}
|
|
|
|
else if (*col == "countdown")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Countdown");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
std::string due;
|
|
std::string countdown;
|
|
Date now;
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
due = tasks[row].get ("due");
|
|
if (due.length ())
|
|
{
|
|
Date dt (::atoi (due.c_str ()));
|
|
time_t cntdwn = (time_t) (now - dt);
|
|
countdown = formatSeconds ( cntdwn < 0 ? cntdwn * -1 : cntdwn );
|
|
if ( cntdwn < 0 )
|
|
countdown = std::string("-") + countdown;
|
|
context.hooks.trigger ("format-countdown", "countdown", countdown);
|
|
table.addCell (row, columnCount, countdown);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "countdown_compact")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Countdown");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
std::string due;
|
|
std::string countdown;
|
|
Date now;
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
due = tasks[row].get ("due");
|
|
if (due.length ())
|
|
{
|
|
Date dt (::atoi (due.c_str ()));
|
|
time_t cntdwn = (time_t) (now - dt);
|
|
countdown = formatSecondsCompact ( cntdwn < 0 ? cntdwn * -1 : cntdwn );
|
|
if ( cntdwn < 0 )
|
|
countdown = std::string("-") + countdown;
|
|
context.hooks.trigger ("format-countdown_compact", "countdown_compact", countdown);
|
|
table.addCell (row, columnCount, countdown);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "age")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Age");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
std::string created;
|
|
std::string age;
|
|
Date now;
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
created = tasks[row].get ("entry");
|
|
if (created.length ())
|
|
{
|
|
Date dt (::atoi (created.c_str ()));
|
|
age = formatSeconds ((time_t) (now - dt));
|
|
context.hooks.trigger ("format-age", "age", age);
|
|
table.addCell (row, columnCount, age);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "age_compact")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Age");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
std::string created;
|
|
std::string age;
|
|
Date now;
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
created = tasks[row].get ("entry");
|
|
if (created.length ())
|
|
{
|
|
Date dt (::atoi (created.c_str ()));
|
|
age = formatSecondsCompact ((time_t) (now - dt));
|
|
context.hooks.trigger ("format-age_compact", "age_compact", age);
|
|
table.addCell (row, columnCount, age);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "active")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Active");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::left);
|
|
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
if (tasks[row].has ("start"))
|
|
table.addCell (row, columnCount, context.config.get ("active.indicator"));
|
|
}
|
|
|
|
else if (*col == "tags")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Tags");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::left);
|
|
|
|
int row = 0;
|
|
std::vector <std::string> all;
|
|
std::string tags;
|
|
foreach (task, tasks)
|
|
{
|
|
task->getTags (all);
|
|
join (tags, " ", all);
|
|
context.hooks.trigger ("format-tags", "tags", tags);
|
|
table.addCell (row++, columnCount, tags);
|
|
}
|
|
}
|
|
|
|
else if (*col == "description_only")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Description");
|
|
table.setColumnWidth (columnCount, Table::flexible);
|
|
table.setColumnJustification (columnCount, Table::left);
|
|
|
|
std::string desc;
|
|
int row = 0;
|
|
foreach (task, tasks)
|
|
{
|
|
desc = task->get ("description");
|
|
context.hooks.trigger ("format-description_only", "description_only", desc);
|
|
table.addCell (row++, columnCount, desc);
|
|
}
|
|
}
|
|
|
|
else if (*col == "description")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Description");
|
|
table.setColumnWidth (columnCount, Table::flexible);
|
|
table.setColumnJustification (columnCount, Table::left);
|
|
|
|
std::string desc;
|
|
int row = 0;
|
|
foreach (task, tasks)
|
|
{
|
|
desc = getFullDescription (*task, report);
|
|
context.hooks.trigger ("format-description", "description", desc);
|
|
table.addCell (row++, columnCount, desc);
|
|
}
|
|
}
|
|
|
|
else if (*col == "recur")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Recur");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
{
|
|
std::string recur = tasks[row].get ("recur");
|
|
if (recur != "")
|
|
{
|
|
context.hooks.trigger ("format-recur", "recur", recur);
|
|
table.addCell (row, columnCount, recur);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (*col == "recurrence_indicator")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "R");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
if (tasks[row].has ("recur"))
|
|
table.addCell (row, columnCount, context.config.get ("recurrence.indicator"));
|
|
}
|
|
|
|
else if (*col == "tag_indicator")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "T");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
for (unsigned int row = 0; row < tasks.size(); ++row)
|
|
if (tasks[row].getTagCount ())
|
|
table.addCell (row, columnCount, context.config.get ("tag.indicator"));
|
|
}
|
|
|
|
else if (*col == "wait")
|
|
{
|
|
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Wait");
|
|
table.setColumnWidth (columnCount, Table::minimum);
|
|
table.setColumnJustification (columnCount, Table::right);
|
|
|
|
int row = 0;
|
|
std::string wait;
|
|
foreach (task, tasks)
|
|
{
|
|
wait = task->get ("wait");
|
|
if (wait != "")
|
|
{
|
|
Date dt (::atoi (wait.c_str ()));
|
|
wait = dt.toString (context.config.get ("dateformat"));
|
|
context.hooks.trigger ("format-wait", "wait", wait);
|
|
table.addCell (row++, columnCount, wait);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Common to all columns.
|
|
// Add underline.
|
|
if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) &&
|
|
context.config.getBoolean ("fontunderline"))
|
|
table.setColumnUnderline (columnCount);
|
|
else
|
|
table.setTableDashedUnderline ();
|
|
|
|
++columnCount;
|
|
}
|
|
|
|
// Dynamically add sort criteria.
|
|
// Build a map of column names -> index.
|
|
std::map <std::string, unsigned int> columnIndex;
|
|
for (unsigned int c = 0; c < columns.size (); ++c)
|
|
columnIndex[columns[c]] = c;
|
|
|
|
foreach (sortColumn, sortOrder)
|
|
{
|
|
// Separate column and direction.
|
|
std::string column = sortColumn->substr (0, sortColumn->length () - 1);
|
|
char direction = (*sortColumn)[sortColumn->length () - 1];
|
|
|
|
// TODO This code should really be using Att::type.
|
|
if (column == "id")
|
|
table.sortOn (columnIndex[column],
|
|
(direction == '+' ?
|
|
Table::ascendingNumeric :
|
|
Table::descendingNumeric));
|
|
|
|
else if (column == "priority")
|
|
table.sortOn (columnIndex[column],
|
|
(direction == '+' ?
|
|
Table::ascendingPriority :
|
|
Table::descendingPriority));
|
|
|
|
else if (column == "entry" || column == "start" || column == "wait" ||
|
|
column == "until" || column == "end")
|
|
table.sortOn (columnIndex[column],
|
|
(direction == '+' ?
|
|
Table::ascendingDate :
|
|
Table::descendingDate));
|
|
|
|
else if (column == "due")
|
|
table.sortOn (columnIndex[column],
|
|
(direction == '+' ?
|
|
Table::ascendingDueDate :
|
|
Table::descendingDueDate));
|
|
|
|
else if (column == "recur")
|
|
table.sortOn (columnIndex[column],
|
|
(direction == '+' ?
|
|
Table::ascendingPeriod :
|
|
Table::descendingPeriod));
|
|
|
|
else
|
|
table.sortOn (columnIndex[column],
|
|
(direction == '+' ?
|
|
Table::ascendingCharacter :
|
|
Table::descendingCharacter));
|
|
}
|
|
|
|
// Now auto colorize all rows.
|
|
if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor"))
|
|
{
|
|
for (unsigned int row = 0; row < tasks.size (); ++row)
|
|
{
|
|
Color c (tasks[row].get ("fg") + " " + tasks[row].get ("bg"));
|
|
autoColorize (tasks[row], c);
|
|
table.setRowColor (row, c);
|
|
}
|
|
}
|
|
|
|
// If an alternating row color is specified, notify the table.
|
|
if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor"))
|
|
{
|
|
Color alternate (context.config.get ("color.alternate"));
|
|
if (alternate.nontrivial ())
|
|
table.setTableAlternateColor (alternate);
|
|
}
|
|
|
|
// Limit the number of rows according to the report definition.
|
|
int maximum = context.config.getInteger (std::string ("report.") + report + ".limit");
|
|
|
|
// If the custom report has a defined limit, then allow a numeric override.
|
|
// This is an integer specified as a filter (limit:10).
|
|
if (context.task.has ("limit"))
|
|
maximum = atoi (context.task.get ("limit").c_str ());
|
|
|
|
std::stringstream out;
|
|
if (table.rowCount ())
|
|
out << optionalBlankLine ()
|
|
<< table.render (maximum)
|
|
<< optionalBlankLine ()
|
|
<< table.rowCount ()
|
|
<< (table.rowCount () == 1 ? " task" : " tasks")
|
|
<< std::endl;
|
|
else {
|
|
out << "No matches."
|
|
<< std::endl;
|
|
rc = 1;
|
|
}
|
|
|
|
outs = out.str ();
|
|
context.hooks.trigger (std::string ("post-") + report + "-command");
|
|
context.hooks.trigger ("post-custom-report-command");
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void validReportColumns (const std::vector <std::string>& columns)
|
|
{
|
|
std::vector <std::string> bad;
|
|
|
|
std::vector <std::string>::const_iterator it;
|
|
for (it = columns.begin (); it != columns.end (); ++it)
|
|
if (*it != "id" &&
|
|
*it != "uuid" &&
|
|
*it != "project" &&
|
|
*it != "priority" &&
|
|
*it != "priority_long" &&
|
|
*it != "entry" &&
|
|
*it != "entry_time" &&
|
|
*it != "start" &&
|
|
*it != "start_time" &&
|
|
*it != "end" &&
|
|
*it != "end_time" &&
|
|
*it != "due" &&
|
|
*it != "countdown" &&
|
|
*it != "countdown_compact" &&
|
|
*it != "age" &&
|
|
*it != "age_compact" &&
|
|
*it != "active" &&
|
|
*it != "tags" &&
|
|
*it != "recur" &&
|
|
*it != "recurrence_indicator" &&
|
|
*it != "tag_indicator" &&
|
|
*it != "description_only" &&
|
|
*it != "description" &&
|
|
*it != "wait")
|
|
bad.push_back (*it);
|
|
|
|
if (bad.size ())
|
|
{
|
|
std::string error;
|
|
join (error, ", ", bad);
|
|
throw std::string ("Unrecognized column name: ") + error;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void validSortColumns (
|
|
const std::vector <std::string>& columns,
|
|
const std::vector <std::string>& sortColumns)
|
|
{
|
|
std::vector <std::string> bad;
|
|
std::vector <std::string>::const_iterator sc;
|
|
for (sc = sortColumns.begin (); sc != sortColumns.end (); ++sc)
|
|
{
|
|
std::vector <std::string>::const_iterator co;
|
|
for (co = columns.begin (); co != columns.end (); ++co)
|
|
if (sc->substr (0, sc->length () - 1) == *co)
|
|
break;
|
|
|
|
if (co == columns.end ())
|
|
bad.push_back (*sc);
|
|
}
|
|
|
|
if (bad.size ())
|
|
{
|
|
std::string error;
|
|
join (error, ", ", bad);
|
|
throw std::string ("Sort column is not part of the report: ") + error;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|