mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-24 08:56:43 +02:00
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:
parent
67a43d6099
commit
041bcfdf21
18 changed files with 346 additions and 798 deletions
|
@ -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
4
NEWS
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
44
src/Task.cpp
44
src/Task.cpp
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -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&);
|
||||||
|
|
27
src/View.cpp
27
src/View.cpp
|
@ -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)
|
||||||
|
{
|
||||||
|
if (row_color.nontrivial ())
|
||||||
|
out += row_color.colorize (intra);
|
||||||
|
else
|
||||||
out += (odd ? intra_odd : intra_even);
|
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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ("")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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&);
|
||||||
|
@ -49,6 +49,7 @@ public:
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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 "
|
||||||
|
|
904
src/custom.cpp
904
src/custom.cpp
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue