Custom Reports

- Integrated new View in place of Table for all custom reports.
- Implemented legacy field mapping for columns and sort fields.
- Implemented rc.indent.report.
- Implemented rc.row.padding.
- Implemented rc.column.padding.
- Implemented rc.color.label.
- Modified default rc.indent.annotation from 1 to 2.
- Implemented urgency value caching.
- Implemented View truncation by line and row.
- Columns now know which report thy belong to, so they can use the
  rc.report.<report>.dateformat override.
- Assorted bugs remain.
This commit is contained in:
Paul Beckingham 2011-05-08 09:29:55 -04:00
parent 67a43d6099
commit 041bcfdf21
18 changed files with 346 additions and 798 deletions

View file

@ -12,6 +12,7 @@
+ Corrected sorting to use std::stable_sort instead of std::sort, which is not + Corrected sorting to use std::stable_sort instead of std::sort, which is not
guaranteed stable (thanks to Stefan Hacker). guaranteed stable (thanks to Stefan Hacker).
+ Enhanced diagnostics command. + Enhanced diagnostics command.
+ Performance enhancements.
+ The old 'curses' configuration variable is now replaced by 'detection', and + The old 'curses' configuration variable is now replaced by 'detection', and
has the same meaning - whether or not to auto-detect terminal size. has the same meaning - whether or not to auto-detect terminal size.
+ Added Czech Republic holiday files (thanks to Tomas Cech). + Added Czech Republic holiday files (thanks to Tomas Cech).
@ -26,6 +27,7 @@
+ New 'dependency.indicator' configuration variable for the depends.indicator + New 'dependency.indicator' configuration variable for the depends.indicator
report field. report field.
+ New 'indent.annotation' for the 'description.default' field format. + New 'indent.annotation' for the 'description.default' field format.
+ New 'color.label' for colorizing the report column labels.
# Tracked Features, sorted by ID. # Tracked Features, sorted by ID.
+ Added feature #330, which supports the 'inverse' color attribute. + Added feature #330, which supports the 'inverse' color attribute.

4
NEWS
View file

@ -11,6 +11,9 @@ New Features in taskwarrior 2.0.0
- Project names may now contain spaces. - Project names may now contain spaces.
- New export-html.pl script. - New export-html.pl script.
- Now supports the 'inverse' color attribute. - Now supports the 'inverse' color attribute.
- Reports may now be sorted by columns that are not displayed (example: ID,
project, due date and description sorted by urgency).
- Performance enhancements.
Please refer to the ChangeLog file for full details. There are too many to Please refer to the ChangeLog file for full details. There are too many to
list here. list here.
@ -31,6 +34,7 @@ New configuration options in taskwarrior 2.0.0
- Two new solarized color themes. - Two new solarized color themes.
- New 'dependency.indicator' for the 'depends.indicator' report field format. - New 'dependency.indicator' for the 'depends.indicator' report field format.
- New 'indent.annotation' for the description.default field format. - New 'indent.annotation' for the description.default field format.
- New 'color.label' for report column labels.
Newly deprecated features in taskwarrior 2.0.0 Newly deprecated features in taskwarrior 2.0.0

View file

@ -212,9 +212,21 @@ and a "+" sign will be added if there are any annotations present. The default
value is "full". value is "full".
.TP .TP
.B indent.annotation=1 .B indent.annotation=2
Controls the number of spaces to indent annotations when shown beneath the Controls the number of spaces to indent annotations when shown beneath the
description field. The default value is "1". description field. The default value is "2".
.TP
.B indent.report=0
Controls the indentation of the entire report output. Default is "0".
.TP
.B row.padding=0
Controls left and right padding around each row of the report output. Default is "0".
.TP
.B column.padding=0
Controls padding between columns of the report output. Default is "1".
.TP .TP
.B next=2 .B next=2
@ -744,6 +756,11 @@ Color of holidays in calendar.
Color of weeknumbers in calendar. Color of weeknumbers in calendar.
.RE .RE
.TP
.B color.label=
Colors the report labels. Defaults to not use color.
.RE
.TP .TP
.B color.alternate=on rgb253 .B color.alternate=on rgb253
Color of alternate tasks. Color of alternate tasks.

View file

@ -73,7 +73,10 @@ std::string Config::defaults =
"confirmation=yes # Confirmation on delete, big changes\n" "confirmation=yes # Confirmation on delete, big changes\n"
"echo.command=yes # Details on command just run\n" "echo.command=yes # Details on command just run\n"
"annotations=full # Level of verbosity for annotations: full, sparse or none\n" "annotations=full # Level of verbosity for annotations: full, sparse or none\n"
"indent.annotation=1 # Indent spaces for annotations\n" "indent.annotation=2 # Indent spaces for annotations\n"
"indent.report=0 # Indent spaces for whole report\n"
"row.padding=0 # Left and right padding for each row of report\n"
"column.padding=1 # Spaces between each column in a report\n"
"next=2 # How many tasks per project in next report\n" "next=2 # How many tasks per project in next report\n"
"bulk=2 # > 2 tasks considered 'a lot', for confirmation\n" "bulk=2 # > 2 tasks considered 'a lot', for confirmation\n"
"nag=You have more urgent tasks. # Nag message to keep you honest\n" // TODO "nag=You have more urgent tasks. # Nag message to keep you honest\n" // TODO

View file

@ -42,6 +42,8 @@ extern Context context;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
Task::Task () Task::Task ()
: id (0) : id (0)
, urgency_value (0.0)
, recalc_urgency (true)
{ {
} }
@ -121,6 +123,8 @@ void Task::setEntry ()
char entryTime[16]; char entryTime[16];
sprintf (entryTime, "%u", (unsigned int) time (NULL)); sprintf (entryTime, "%u", (unsigned int) time (NULL));
set ("entry", entryTime); // No i18n set ("entry", entryTime); // No i18n
recalc_urgency = true;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -133,6 +137,8 @@ Task::status Task::getStatus () const
void Task::setStatus (Task::status status) void Task::setStatus (Task::status status)
{ {
set ("status", statusToText (status)); // No i18n set ("status", statusToText (status)); // No i18n
recalc_urgency = true;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -551,6 +557,8 @@ void Task::setAnnotations (const std::vector <Att>& annotations)
std::vector <Att>::const_iterator ci; std::vector <Att>::const_iterator ci;
for (ci = annotations.begin (); ci != annotations.end (); ++ci) for (ci = annotations.begin (); ci != annotations.end (); ++ci)
(*this)[ci->name ()] = *ci; (*this)[ci->name ()] = *ci;
recalc_urgency = true;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -885,13 +893,14 @@ int Task::determineVersion (const std::string& line)
// //
// See rfc31-urgency.txt for full details. // See rfc31-urgency.txt for full details.
// //
double Task::urgency () float Task::urgency ()
{ {
double urgency = 0.0; if (! recalc_urgency)
return urgency_value;
// urgency.priority.coefficient // urgency.priority.coefficient
double coefficient = context.config.getReal ("urgency.priority.coefficient"); float coefficient = context.config.getReal ("urgency.priority.coefficient");
double term; float term;
std::string value = get ("priority"); std::string value = get ("priority");
if (value == "H") term = 1.0; if (value == "H") term = 1.0;
@ -899,7 +908,7 @@ double Task::urgency ()
else if (value == "L") term = 0.3; else if (value == "L") term = 0.3;
else term = 0.0; else term = 0.0;
urgency += term * coefficient; urgency_value += term * coefficient;
// urgency.project.coefficient // urgency.project.coefficient
coefficient = context.config.getReal ("urgency.project.coefficient"); coefficient = context.config.getReal ("urgency.project.coefficient");
@ -908,7 +917,7 @@ double Task::urgency ()
if (value != "") term = 1.0; if (value != "") term = 1.0;
else term = 0.0; else term = 0.0;
urgency += term * coefficient; urgency_value += term * coefficient;
// urgency.active.coefficient // urgency.active.coefficient
coefficient = context.config.getReal ("urgency.active.coefficient"); coefficient = context.config.getReal ("urgency.active.coefficient");
@ -917,7 +926,7 @@ double Task::urgency ()
if (value != "") term = 1.0; if (value != "") term = 1.0;
else term = 0.0; else term = 0.0;
urgency += term * coefficient; urgency_value += term * coefficient;
// urgency.waiting.coefficient // urgency.waiting.coefficient
coefficient = context.config.getReal ("urgency.waiting.coefficient"); coefficient = context.config.getReal ("urgency.waiting.coefficient");
@ -926,7 +935,7 @@ double Task::urgency ()
if (value == "pending") term = 1.0; if (value == "pending") term = 1.0;
else if (value == "waiting") term = 0.0; else if (value == "waiting") term = 0.0;
urgency += term * coefficient; urgency_value += term * coefficient;
// urgency.blocked.coefficient // urgency.blocked.coefficient
coefficient = context.config.getReal ("urgency.blocked.coefficient"); coefficient = context.config.getReal ("urgency.blocked.coefficient");
@ -935,7 +944,7 @@ double Task::urgency ()
if (value != "") term = 1.0; if (value != "") term = 1.0;
else term = 0.0; else term = 0.0;
urgency += term * coefficient; urgency_value += term * coefficient;
// urgency.annotations.coefficient // urgency.annotations.coefficient
coefficient = context.config.getReal ("urgency.annotations.coefficient"); coefficient = context.config.getReal ("urgency.annotations.coefficient");
@ -947,7 +956,7 @@ double Task::urgency ()
else if (annos.size () == 1) term = 0.8; else if (annos.size () == 1) term = 0.8;
else term = 0.0; else term = 0.0;
urgency += term * coefficient; urgency_value += term * coefficient;
// urgency.tags.coefficient // urgency.tags.coefficient
coefficient = context.config.getReal ("urgency.tags.coefficient"); coefficient = context.config.getReal ("urgency.tags.coefficient");
@ -958,7 +967,7 @@ double Task::urgency ()
else if (count == 1) term = 0.8; else if (count == 1) term = 0.8;
else term = 0.0; else term = 0.0;
urgency += term * coefficient; urgency_value += term * coefficient;
// urgency.next.coefficient // urgency.next.coefficient
coefficient = context.config.getReal ("urgency.next.coefficient"); coefficient = context.config.getReal ("urgency.next.coefficient");
@ -966,7 +975,7 @@ double Task::urgency ()
if (hasTag ("next")) term = 1.0; if (hasTag ("next")) term = 1.0;
else term = 0.0; else term = 0.0;
urgency += term * coefficient; urgency_value += term * coefficient;
// urgency.due.coefficient // urgency.due.coefficient
// overdue days 7 -> 1.0 // overdue days 7 -> 1.0
@ -1025,7 +1034,7 @@ double Task::urgency ()
else else
term = 0.0; term = 0.0;
urgency += term * coefficient; urgency_value += term * coefficient;
// Tag- and project-specific coefficients. // Tag- and project-specific coefficients.
std::vector <std::string> all; std::vector <std::string> all;
@ -1044,7 +1053,7 @@ double Task::urgency ()
coefficient = context.config.getReal (*var); coefficient = context.config.getReal (*var);
if (get ("project").find (project) == 0) if (get ("project").find (project) == 0)
urgency += coefficient; urgency_value += coefficient;
} }
// urgency.user.tag.<tag>.coefficient // urgency.user.tag.<tag>.coefficient
@ -1055,7 +1064,7 @@ double Task::urgency ()
coefficient = context.config.getReal (*var); coefficient = context.config.getReal (*var);
if (hasTag (tag)) if (hasTag (tag))
urgency += coefficient; urgency_value += coefficient;
} }
} }
} }
@ -1066,10 +1075,11 @@ double Task::urgency ()
if (dependencyIsBlocking (*this)) term = 1.0; if (dependencyIsBlocking (*this)) term = 1.0;
else term = 0.0; else term = 0.0;
urgency += term * coefficient; urgency_value += term * coefficient;
// Return the sum of all terms. // Return the sum of all terms.
return urgency; recalc_urgency = false;
return urgency_value;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View file

@ -52,6 +52,8 @@ public:
// Public data. // Public data.
int id; int id;
float urgency_value;
bool recalc_urgency;
// Series of helper functions. // Series of helper functions.
static status textToStatus (const std::string&); static status textToStatus (const std::string&);
@ -82,7 +84,7 @@ public:
void validate () const; void validate () const;
double urgency (); float urgency ();
private: private:
int determineVersion (const std::string&); int determineVersion (const std::string&);

View file

@ -26,6 +26,7 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include <View.h> #include <View.h>
#include <Timer.h>
#include <text.h> #include <text.h>
#include <utf8.h> #include <utf8.h>
#include <main.h> #include <main.h>
@ -43,8 +44,10 @@ View::View ()
, _extra_padding (0) , _extra_padding (0)
, _extra_odd (0) , _extra_odd (0)
, _extra_even (0) , _extra_even (0)
, _truncate (0) , _truncate_lines (0)
, _truncate_rows (0)
, _lines (0) , _lines (0)
, _rows (0)
{ {
} }
@ -93,6 +96,8 @@ View::View ()
// //
std::string View::render (std::vector <Task>& data, std::vector <int>& sequence) std::string View::render (std::vector <Task>& data, std::vector <int>& sequence)
{ {
Timer timer ("View::render");
// Determine minimal, ideal column widths. // Determine minimal, ideal column widths.
std::vector <int> minimal; std::vector <int> minimal;
std::vector <int> ideal; std::vector <int> ideal;
@ -212,10 +217,14 @@ std::string View::render (std::vector <Task>& data, std::vector <int>& sequence)
} }
out += extra + "\n"; out += extra + "\n";
++_lines;
// Stop if the line limit is exceeded.
if (++_lines >= _truncate_lines && _truncate_lines != 0)
return out;
} }
// Compose, render columns, in sequence. // Compose, render columns, in sequence.
_rows = 0;
std::vector <std::vector <std::string> > cells; std::vector <std::vector <std::string> > cells;
std::vector <int>::iterator s; std::vector <int>::iterator s;
for (int s = 0; s < sequence.size (); ++s) for (int s = 0; s < sequence.size (); ++s)
@ -247,7 +256,12 @@ std::string View::render (std::vector <Task>& data, std::vector <int>& sequence)
for (int c = 0; c < _columns.size (); ++c) for (int c = 0; c < _columns.size (); ++c)
{ {
if (c) if (c)
out += (odd ? intra_odd : intra_even); {
if (row_color.nontrivial ())
out += row_color.colorize (intra);
else
out += (odd ? intra_odd : intra_even);
}
if (i < cells[c].size ()) if (i < cells[c].size ())
out += cells[c][i]; out += cells[c][i];
@ -256,10 +270,17 @@ std::string View::render (std::vector <Task>& data, std::vector <int>& sequence)
} }
out += (odd ? extra_odd : extra_even) + "\n"; out += (odd ? extra_odd : extra_even) + "\n";
++_lines;
// Stop if the line limit is exceeded.
if (++_lines >= _truncate_lines && _truncate_lines != 0)
return out;
} }
cells.clear (); cells.clear ();
// Stop if the row limit is exceeded.
if (++_rows >= _truncate_rows && _truncate_rows != 0)
return out;
} }
return out; return out;

View file

@ -52,8 +52,10 @@ public:
void extraPadding (int padding) { _extra_padding = padding; } void extraPadding (int padding) { _extra_padding = padding; }
void extraColorOdd (Color& c) { _extra_odd = c; } void extraColorOdd (Color& c) { _extra_odd = c; }
void extraColorEven (Color& c) { _extra_even = c; } void extraColorEven (Color& c) { _extra_even = c; }
void truncate (int n) { _truncate = n; } void truncateLines (int n) { _truncate_lines = n; }
void truncateRows (int n) { _truncate_rows = n; }
int lines () { return _lines; } int lines () { return _lines; }
int rows () { return _rows; }
// View rendering. // View rendering.
std::string render (std::vector <Task>&, std::vector <int>&); std::string render (std::vector <Task>&, std::vector <int>&);
@ -71,8 +73,10 @@ private:
int _extra_padding; int _extra_padding;
Color _extra_odd; Color _extra_odd;
Color _extra_even; Color _extra_even;
int _truncate; int _truncate_lines;
int _truncate_rows;
int _lines; int _lines;
int _rows;
}; };
#endif #endif

View file

@ -42,7 +42,6 @@ ColumnDate::ColumnDate ()
_style = "default"; _style = "default";
_label = ""; _label = "";
_attribute = ""; _attribute = "";
_report = "";
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -50,12 +49,6 @@ ColumnDate::~ColumnDate ()
{ {
} }
////////////////////////////////////////////////////////////////////////////////
void ColumnDate::setReport (const std::string& report)
{
_report = report;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Set the minimum and maximum widths for the value. // Set the minimum and maximum widths for the value.
void ColumnDate::measure (Task& task, int& minimum, int& maximum) void ColumnDate::measure (Task& task, int& minimum, int& maximum)
@ -97,7 +90,7 @@ void ColumnDate::measure (Task& task, int& minimum, int& maximum)
else if (_style == "age") else if (_style == "age")
{ {
Date now; Date now;
minimum = maximum = Duration (now - date).format ().length (); minimum = maximum = Duration (now - date).formatCompact ().length ();
} }
else else
throw std::string ("Unrecognized column format '") + _type + "." + _style + "'"; throw std::string ("Unrecognized column format '") + _type + "." + _style + "'";
@ -165,7 +158,7 @@ void ColumnDate::render (
lines.push_back ( lines.push_back (
color.colorize ( color.colorize (
rightJustify ( rightJustify (
Duration (now - date).format (), width))); Duration (now - date).formatCompact (), width)));
} }
else if (_style == "short") else if (_style == "short")
{ {

View file

@ -39,13 +39,11 @@ public:
ColumnDate (); ColumnDate ();
~ColumnDate (); ~ColumnDate ();
void setReport (const std::string&);
virtual void measure (Task&, int&, int&); virtual void measure (Task&, int&, int&);
virtual void render (std::vector <std::string>&, Task&, int, Color&); virtual void render (std::vector <std::string>&, Task&, int, Color&);
protected: protected:
std::string _attribute; std::string _attribute;
std::string _report;
}; };
#endif #endif

View file

@ -52,7 +52,7 @@ extern Context context;
// //
// <type>[.<format>] // <type>[.<format>]
// //
Column* Column::factory (const std::string& name) Column* Column::factory (const std::string& name, const std::string& report)
{ {
// Decompose name into type and style. // Decompose name into type and style.
std::string::size_type dot = name.find ('.'); std::string::size_type dot = name.find ('.');
@ -89,33 +89,8 @@ Column* Column::factory (const std::string& name)
else else
throw std::string ("Unrecognized column type '") + column_name + "'"; throw std::string ("Unrecognized column type '") + column_name + "'";
column->setReport (report);
column->setStyle (column_style); column->setStyle (column_style);
/*
// TODO Load the report column def from config
// TODO Parse column defs
// TODO Create column object
// TODO Column: name
// TODO Column: style
// TODO Column: break
// TODO Color: odd
// TODO Color: even
// TODO Color: intra_odd
// TODO Color: intra_even
// TODO Color: extra_odd
// TODO Color: extra_even
// TODO Color: header
// Terminal width.
view.width (getWidth ());
// TODO Intra padding.
// TODO Extra padding.
// TODO Margin.
// TODO Truncate lines/page.
*/
return column; return column;
} }
@ -124,6 +99,7 @@ Column::Column ()
: _type ("string") : _type ("string")
, _style ("default") , _style ("default")
, _label ("") , _label ("")
, _report ("")
{ {
} }

View file

@ -35,7 +35,7 @@
class Column class Column
{ {
public: public:
static Column* factory (const std::string&); static Column* factory (const std::string&, const std::string&);
Column (); Column ();
Column (const Column&); Column (const Column&);
@ -47,8 +47,9 @@ public:
std::string getLabel () { return _label; } std::string getLabel () { return _label; }
std::string type () const { return _type; } std::string type () const { return _type; }
virtual void setStyle (const std::string& value) { _style = value; } virtual void setStyle (const std::string& value) { _style = value; }
virtual void setLabel (const std::string& value) { _label = value; } virtual void setLabel (const std::string& value) { _label = value; }
virtual void setReport (const std::string& value) { _report = value; }
virtual void measure (Task&, int&, int&) = 0; virtual void measure (Task&, int&, int&) = 0;
virtual void renderHeader (std::vector <std::string>&, int, Color&); virtual void renderHeader (std::vector <std::string>&, int, Color&);
@ -58,6 +59,7 @@ protected:
std::string _type; std::string _type;
std::string _style; std::string _style;
std::string _label; std::string _label;
std::string _report;
}; };
#endif #endif

View file

@ -1102,7 +1102,7 @@ int handleShow (std::string& outs)
"color.calendar.due color.calendar.due.today color.calendar.overdue regex " "color.calendar.due color.calendar.due.today color.calendar.overdue regex "
"color.calendar.weekend color.calendar.holiday color.calendar.weeknumber " "color.calendar.weekend color.calendar.holiday color.calendar.weeknumber "
"color.summary.background color.summary.bar color.history.add " "color.summary.background color.summary.bar color.history.add "
"color.history.done color.history.delete color.undo.before " "color.history.done color.history.delete color.undo.before color.label "
"color.sync.added color.sync.changed color.sync.rejected color.undo.after " "color.sync.added color.sync.changed color.sync.rejected color.undo.after "
"confirmation data.location dateformat dateformat.holiday " "confirmation data.location dateformat dateformat.holiday "
"dateformat.report dateformat.annotation debug default.command default.due " "dateformat.report dateformat.annotation debug default.command default.due "
@ -1116,7 +1116,8 @@ int handleShow (std::string& outs)
"active.indicator tag.indicator recurrence.indicator recurrence.limit " "active.indicator tag.indicator recurrence.indicator recurrence.limit "
"list.all.projects list.all.tags undo.style verbose rule.precedence.color " "list.all.projects list.all.tags undo.style verbose rule.precedence.color "
"merge.autopush merge.default.uri pull.default.uri push.default.uri " "merge.autopush merge.default.uri pull.default.uri push.default.uri "
"xterm.title shell.prompt " "xterm.title shell.prompt indent.annotation indent.report column.spacing "
"row.padding column.padding "
"import.synonym.status import.synonym.tags import.synonym.entry " "import.synonym.status import.synonym.tags import.synonym.entry "
"import.synonym.start import.synonym.due import.synonym.recur " "import.synonym.start import.synonym.due import.synonym.recur "
"import.synonym.end import.synonym.project import.synonym.priority " "import.synonym.end import.synonym.project import.synonym.priority "

View file

@ -35,728 +35,97 @@
#include <pwd.h> #include <pwd.h>
#include <time.h> #include <time.h>
#include "Context.h" #include <Context.h>
#include "Date.h" #include <Date.h>
#include "Duration.h" #include <Duration.h>
#include "Table.h" #include <Table.h>
#include "text.h" #include <View.h>
#include "util.h" #include <text.h>
#include "main.h" #include <util.h>
#include <main.h>
extern Context context; extern Context context;
static std::vector <std::string> customReports;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// This report will eventually become the one report that many others morph into void validateReportColumns (std::vector <std::string>& columns)
// via the .taskrc file.
int handleCustomReport (const std::string& report, std::string& outs)
{ {
int rc = 0; // One-time initialization, on demand.
static std::map <std::string, std::string> legacyMap;
// Load report configuration. if (! legacyMap.size ())
std::string reportColumns = context.config.get ("report." + report + ".columns");
std::string reportLabels = context.config.get ("report." + report + ".labels");
std::string reportSort = context.config.get ("report." + report + ".sort");
std::string reportFilter = context.config.get ("report." + report + ".filter");
std::vector <std::string> columns;
split (columns, reportColumns, ',');
validReportColumns (columns);
std::vector <std::string> labels;
split (labels, reportLabels, ',');
if (columns.size () != labels.size () && labels.size () != 0)
throw std::string ("There are a different number of columns and 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, reportSort, ',');
validSortColumns (columns, sortOrder);
// Apply rc overrides.
std::vector <std::string> filterArgs;
std::vector <std::string> filteredArgs;
split (filterArgs, reportFilter, ' ');
context.applyOverrides (filterArgs, filteredArgs);
{ {
Cmd cmd (report); legacyMap["priority_long"] = "priority.long";
Task task; legacyMap["entry_time"] = "entry";
Sequence sequence; legacyMap["start_time"] = "start";
Subst subst; legacyMap["end_time"] = "end";
Filter filter; legacyMap["countdown"] = "due.countdown";
context.parse (filteredArgs, cmd, task, sequence, subst, filter); legacyMap["countdown_compact"] = "due.countdown";
legacyMap["age"] = "entry.age";
context.sequence.combine (sequence); legacyMap["age_compact"] = "entry.age";
legacyMap["active"] = "start.active";
// Special case: Allow limit to be overridden by the command line. legacyMap["recurrence_indicator"] = "recur.indicator";
if (!context.task.has ("limit") && task.has ("limit")) legacyMap["tag_indicator"] = "tags.indicator";
context.task.set ("limit", task.get ("limit")); legacyMap["description_only"] = "description.desc";
foreach (att, filter)
context.filter.push_back (*att);
} }
// Get all the tasks. std::vector <std::string>::iterator i;
std::vector <Task> tasks; for (i = columns.begin (); i != columns.end (); ++i)
context.tdb.lock (context.config.getBoolean ("locking"));
handleRecurrence ();
context.tdb.load (tasks, context.filter);
context.tdb.commit ();
context.tdb.unlock ();
// Filter sequence.
if (context.sequence.size ())
context.filter.applySequence (tasks, context.sequence);
// Determine the output date format, which uses a hierarchy of definitions.
std::string dateformat = context.config.get ("report." + report + ".dateformat");
if (dateformat == "")
dateformat = context.config.get ("dateformat.report");
if (dateformat == "")
dateformat = context.config.get ("dateformat");
Table table;
table.setTableWidth (context.getWidth ());
table.setDateFormat (dateformat);
table.setReportName (report);
foreach (task, tasks)
table.addRow ();
int columnCount = 0;
int dueColumn = -1;
foreach (col, columns)
{ {
// Add each column individually. // If a legacy column was used, complain about it, but modify it anyway.
if (*col == "id") std::map <std::string, std::string>::iterator found = legacyMap.find (*i);
if (found != legacyMap.end ())
{ {
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "ID"); context.footnote (std::string ("Deprecated report field '")
table.setColumnWidth (columnCount, Table::minimum); + *i
table.setColumnJustification (columnCount, Table::right); + "' used. Please modify this to '"
+ found->second
std::string value; + "'.");
int row = 0; *i = found->second;
foreach (task, tasks)
{
if (task->id != 0)
value = format (task->id);
else
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");
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");
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");
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
table.addCell (row++, columnCount, pri);
}
}
else if (*col == "entry" || *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.toString (dateformat);
table.addCell (row, columnCount, entered);
}
}
}
else if (*col == "start" || *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.toString (dateformat);
table.addCell (row, columnCount, started);
}
}
}
else if (*col == "end" || *col == "end_time")
{
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 (dateformat);
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);
int row = 0;
std::string due;
foreach (task, tasks)
{
std::string value = getDueDate (*task, dateformat);
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 ()));
countdown = Duration (now - dt).format ();
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 ()));
countdown = Duration (now - dt).formatCompact ();
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 = Duration (now - dt).format ();
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 = Duration (now - dt).formatCompact ();
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);
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");
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);
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 != "")
{
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 (dateformat);
table.addCell (row++, columnCount, wait);
}
}
}
else if (*col == "depends")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Deps");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
int row = 0;
std::vector <Task> blocked;
std::vector <int> blocked_ids;
std::string deps;
foreach (task, tasks)
{
dependencyGetBlocking (*task, blocked);
foreach (b, blocked)
blocked_ids.push_back (b->id);
join (deps, ",", blocked_ids);
blocked_ids.clear ();
blocked.clear ();
table.addCell (row++, columnCount, deps);
}
}
else if (*col == "urgency")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Urgency");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
int row = 0;
foreach (task, tasks)
{
std::string value = format (task->urgency (), 4, 3);
table.addCell (row++, columnCount, value);
}
}
else if (*col == "status")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Status");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::left);
int row = 0;
foreach (task, tasks)
{
table.addCell (row++, columnCount, task->statusToText (task->getStatus ()));
}
}
// Common to all columns.
// Add underline.
if (context.color () && 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" || column == "urgency")
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" || column == "entry_time" ||
column == "start_time" || column == "end_time")
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" || column == "age" || column == "age_compact")
table.sortOn (columnIndex[column],
(direction == '+' ?
Table::ascendingPeriod :
Table::descendingPeriod));
else if (column == "countdown" || column == "countdown_compact")
table.sortOn (columnIndex[column],
(direction == '+' ?
Table::descendingPeriod : // Yes, these are flipped.
Table::ascendingPeriod)); // Yes, these are flipped.
else
table.sortOn (columnIndex[column],
(direction == '+' ?
Table::ascendingCharacter :
Table::descendingCharacter));
}
// Now auto colorize all rows.
if (context.color ())
{
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.color ())
{
Color alternate (context.config.get ("color.alternate"));
if (alternate.nontrivial ())
table.setTableAlternateColor (alternate);
}
// How many lines taken up by table header?
int table_header;
if (context.color () && context.config.getBoolean ("fontunderline"))
table_header = 1; // Underlining doesn't use extra line.
else
table_header = 2; // Dashes use an extra line.
// Report output can be limited by rows or lines.
int maxrows = 0;
int maxlines = 0;
getLimits (report, maxrows, maxlines);
// Adjust for fluff in the output.
if (maxlines)
maxlines -= (context.config.getBoolean ("blanklines") ? 1 : 0)
+ table_header
+ context.headers.size ()
+ context.footnotes.size ();
std::stringstream out;
if (table.rowCount ())
{
out << optionalBlankLine ()
<< table.render (maxrows, maxlines)
<< optionalBlankLine ()
<< table.rowCount ()
<< (table.rowCount () == 1 ? " task" : " tasks");
if (maxrows && maxrows < table.rowCount ())
out << ", " << maxrows << " shown";
if (maxlines && maxlines < table.rowCount ())
out << ", truncated to " << maxlines - table_header << " lines";
out << std::endl;
}
else
{
out << "No matches."
<< std::endl;
rc = 1;
}
outs = out.str ();
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" && // TODO Deprecated
*it != "start" &&
*it != "start_time" && // TODO Deprecated
*it != "end" &&
*it != "end_time" && // TODO Deprecated
*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" &&
*it != "depends" &&
*it != "urgency" &&
*it != "status")
bad.push_back (*it);
if (bad.size ())
{
std::string error;
join (error, ", ", bad);
throw std::string ("Unrecognized column name: ") + error + ".";
} }
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void validSortColumns ( void validateSortColumns (std::vector <std::string>& columns)
const std::vector <std::string>& columns,
const std::vector <std::string>& sortColumns)
{ {
std::vector <std::string> bad; // One-time initialization, on demand.
std::vector <std::string>::const_iterator sc; static std::map <std::string, std::string> legacyMap;
for (sc = sortColumns.begin (); sc != sortColumns.end (); ++sc) if (! legacyMap.size ())
{ {
char direction = (*sc)[sc->length () - 1]; legacyMap["priority_long"] = "priority";
if (direction != '-' && direction != '+') legacyMap["entry_time"] = "entry";
throw std::string ("Sort column '") + legacyMap["start_time"] = "start";
*sc + legacyMap["end_time"] = "end";
"' does not have a +/- ascending/descending indicator."; legacyMap["countdown"] = "due";
legacyMap["countdown_compact"] = "due";
std::vector <std::string>::const_iterator co; legacyMap["age"] = "entry";
for (co = columns.begin (); co != columns.end (); ++co) legacyMap["age_compact"] = "entry";
if (sc->substr (0, sc->length () - 1) == *co) legacyMap["active"] = "start";
break; legacyMap["recurrence_indicator"] = "recur";
legacyMap["tag_indicator"] = "tags";
if (co == columns.end ()) legacyMap["description_only"] = "description";
bad.push_back (*sc);
} }
if (bad.size ()) std::vector <std::string>::iterator i;
for (i = columns.begin (); i != columns.end (); ++i)
{ {
std::string error; // If a legacy column was used, complain about it, but modify it anyway.
join (error, ", ", bad); std::map <std::string, std::string>::iterator found = legacyMap.find (*i);
throw std::string ("Sort column is not part of the report: ") + error + "."; if (found != legacyMap.end ())
{
context.footnote (std::string ("Deprecated sort field '")
+ *i
+ "' used. Please modify this to '"
+ found->second
+ "'.");
*i = found->second;
}
} }
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// A value of zero mean unlimited. // A value of zero mean unlimited.
// A value of 'page' means however many screen lines there are. // A value of 'page' means however many screen lines there are.
// A value of a positive integer is a row limit. // A value of a positive integer is a row/task limit.
void getLimits (const std::string& report, int& rows, int& lines) void getLimits (const std::string& report, int& rows, int& lines)
{ {
rows = 0; rows = 0;
@ -795,3 +164,146 @@ void getLimits (const std::string& report, int& rows, int& lines)
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int handleCustomReport (const std::string& report, std::string& outs)
{
int rc = 0;
// Load report configuration.
std::string reportColumns = context.config.get ("report." + report + ".columns");
std::string reportLabels = context.config.get ("report." + report + ".labels");
std::string reportSort = context.config.get ("report." + report + ".sort");
std::string reportFilter = context.config.get ("report." + report + ".filter");
std::vector <std::string> columns;
split (columns, reportColumns, ',');
validateReportColumns (columns);
std::vector <std::string> labels;
split (labels, reportLabels, ',');
if (columns.size () != labels.size () && labels.size () != 0)
throw std::string ("There are a different number of columns and 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, reportSort, ',');
validateSortColumns (sortOrder);
// Apply rc overrides.
std::vector <std::string> filterArgs;
std::vector <std::string> filteredArgs;
split (filterArgs, reportFilter, ' ');
context.applyOverrides (filterArgs, filteredArgs);
{
Cmd cmd (report);
Task task;
Sequence sequence;
Subst subst;
Filter filter;
context.parse (filteredArgs, cmd, task, sequence, subst, filter);
context.sequence.combine (sequence);
// Special case: 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 ();
// Filter sequence.
if (context.sequence.size ())
context.filter.applySequence (tasks, context.sequence);
// Sort the tasks.
std::vector <int> sequence;
for (int i = 0; i < tasks.size (); ++i)
sequence.push_back (i);
sort_tasks (tasks, sequence, reportSort);
// Configure the view.
View view;
view.width (context.getWidth ());
view.leftMargin (context.config.getInteger ("indent.report"));
view.extraPadding (context.config.getInteger ("row.padding"));
view.intraPadding (context.config.getInteger ("column.padding"));
Color label (context.config.get ("color.label"));
view.colorHeader (label);
Color alternate (context.config.get ("color.alternate"));
view.colorOdd (alternate);
view.intraColorOdd (alternate);
// Add the columns.
std::vector <std::string>::iterator it;
for (it = columns.begin (); it != columns.end (); ++it)
view.add (Column::factory (*it, report));
// How many lines taken up by table header?
int table_header;
if (context.color () && context.config.getBoolean ("fontunderline"))
table_header = 1; // Underlining doesn't use extra line.
else
table_header = 2; // Dashes use an extra line.
// Report output can be limited by rows or lines.
int maxrows = 0;
int maxlines = 0;
getLimits (report, maxrows, maxlines);
// Adjust for fluff in the output.
if (maxlines)
maxlines -= (context.config.getBoolean ("blanklines") ? 1 : 0)
+ table_header
+ context.headers.size ()
+ context.footnotes.size ();
std::stringstream out;
if (tasks.size ())
{
view.truncateRows (maxrows);
view.truncateLines (maxlines);
out << optionalBlankLine ()
<< view.render (tasks, sequence)
<< optionalBlankLine ()
<< tasks.size ()
<< (tasks.size () == 1 ? " task" : " tasks");
if (maxrows && maxrows < view.rows ())
out << ", " << maxrows << " shown";
if (maxlines && maxlines < view.rows ())
out << ", truncated to " << maxlines - table_header << " lines";
out << "\n";
}
else
{
out << "No matches."
<< std::endl;
rc = 1;
}
outs = out.str ();
return rc;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -125,8 +125,8 @@ int handleReportGHistoryAnnual (std::string&);
// custom.cpp // custom.cpp
int handleCustomReport (const std::string&, std::string&); int handleCustomReport (const std::string&, std::string&);
void validReportColumns (const std::vector <std::string>&); void validateReportColumns (const std::vector <std::string>&);
void validSortColumns (const std::vector <std::string>&, const std::vector <std::string>&); void validateSortColumns (const std::vector <std::string>&);
void getLimits (const std::string&, int&, int&); void getLimits (const std::string&, int&, int&);
// rules.cpp // rules.cpp

View file

@ -634,10 +634,9 @@ int handleInfo (std::string& outs)
} }
// Task::urgency // Task::urgency
// TODO Enable this later. This was for testing. row = table.addRow ();
//row = table.addRow (); table.addCell (row, 0, "Urgency");
//table.addCell (row, 0, "Urgency"); table.addCell (row, 1, task->urgency ());
//table.addCell (row, 1, task->urgency ());
// Create a second table, containing undo log change details. // Create a second table, containing undo log change details.
Table journal; Table journal;

View file

@ -31,6 +31,7 @@
#include <Context.h> #include <Context.h>
#include <Duration.h> #include <Duration.h>
#include <Task.h> #include <Task.h>
#include <Timer.h>
#include <text.h> #include <text.h>
extern Context context; extern Context context;
@ -45,6 +46,8 @@ void sort_tasks (
std::vector <int>& order, std::vector <int>& order,
const std::string& keys) const std::string& keys)
{ {
Timer t ("Sort");
global_data = &data; global_data = &data;
// Split the key defs. // Split the key defs.

View file

@ -99,33 +99,34 @@ int main (int argc, char** argv)
Color even_color ("on gray0"); Color even_color ("on gray0");
// Create a view. // Create a view.
std::string report = "view.t";
View view; View view;
view.add (Column::factory ("id")); view.add (Column::factory ("id", report));
view.add (Column::factory ("uuid.short")); view.add (Column::factory ("uuid.short", report));
view.add (Column::factory ("project")); view.add (Column::factory ("project", report));
view.add (Column::factory ("priority.long")); view.add (Column::factory ("priority.long", report));
view.add (Column::factory ("tags")); view.add (Column::factory ("tags", report));
// view.add (Column::factory ("tags.indicator")); // view.add (Column::factory ("tags.indicator", report));
view.add (Column::factory ("tags.count")); view.add (Column::factory ("tags.count", report));
view.add (Column::factory ("description")); view.add (Column::factory ("description", report));
// view.add (Column::factory ("description.desc")); // view.add (Column::factory ("description.desc", report));
// view.add (Column::factory ("description.truncated")); // view.add (Column::factory ("description.truncated", report));
// view.add (Column::factory ("description.oneline")); // view.add (Column::factory ("description.oneline", report));
// view.add (Column::factory ("description.count")); // view.add (Column::factory ("description.count", report));
// view.add (Column::factory ("depends")); // view.add (Column::factory ("depends", report));
// view.add (Column::factory ("depends.count")); // view.add (Column::factory ("depends.count", report));
view.add (Column::factory ("depends.indicator")); view.add (Column::factory ("depends.indicator", report));
// view.add (Column::factory ("recur")); // view.add (Column::factory ("recur", report));
view.add (Column::factory ("recur.indicator")); view.add (Column::factory ("recur.indicator", report));
// view.add (Column::factory ("status")); // view.add (Column::factory ("status", report));
view.add (Column::factory ("status.short")); view.add (Column::factory ("status.short", report));
// view.add (Column::factory ("due")); // view.add (Column::factory ("due", report));
// view.add (Column::factory ("due.julian")); // view.add (Column::factory ("due.julian", report));
view.add (Column::factory ("due.countdown")); view.add (Column::factory ("due.countdown", report));
// view.add (Column::factory ("due.epoch")); // view.add (Column::factory ("due.epoch", report));
// view.add (Column::factory ("due.iso")); // view.add (Column::factory ("due.iso", report));
view.add (Column::factory ("start.active")); view.add (Column::factory ("start.active", report));
view.add (Column::factory ("urgency")); view.add (Column::factory ("urgency", report));
view.width (context.getWidth ()); view.width (context.getWidth ());
view.leftMargin (4); view.leftMargin (4);
/* /*