- Added feature #415, which supports displaying just a single page of tasks,
  by specifying either 'limit:page' to a command, or 'report.xxx.limit:page'
  in a report specification (thanks to T. Charles Yun).
- Modified the 'next' report to only display a page, by default.
This commit is contained in:
Paul Beckingham 2010-06-20 17:32:11 -04:00
parent 2f85941d37
commit 916b8641b3
13 changed files with 239 additions and 28 deletions

View file

@ -344,8 +344,8 @@ bool Att::validNameValue (
else if (name == "limit")
{
if (value == "" || !digitsOnly (value))
throw std::string ("The '") + name + "' attribute must be an integer.";
if (value == "" || (value != "page" && !digitsOnly (value)))
throw std::string ("The '") + name + "' attribute must be an integer, or the value 'page'.";
}
else if (name == "status")

View file

@ -293,7 +293,7 @@ std::string Config::defaults =
"report.next.columns=id,project,priority,due,active,age,description\n"
"report.next.labels=ID,Project,Pri,Due,Active,Age,Description\n"
"report.next.sort=due+,priority-,project+\n"
"report.next.filter=status:pending\n"
"report.next.filter=status:pending limit:page\n"
"#report.next.dateformat=m/d/Y\n"
"#report.next.annotations=full\n"
"\n";

View file

@ -55,6 +55,7 @@ public:
void shadow (); // shadow file update
int getWidth (); // determine terminal width
int getHeight (); // determine terminal height
void header (const std::string&); // Header message sink
void footnote (const std::string&); // Footnote message sink
@ -93,7 +94,6 @@ public:
std::vector <std::string> tagRemovals;
Hooks hooks;
private:
std::vector <std::string> headers;
std::vector <std::string> footnotes;
std::vector <std::string> debugMessages;

View file

@ -1024,10 +1024,13 @@ void Table::clean (std::string& value)
}
////////////////////////////////////////////////////////////////////////////////
const std::string Table::render (int maximum /* = 0 */)
const std::string Table::render (int maxrows /* = 0 */, int maxlines /* = 0 */)
{
Timer t ("Table::render");
// May not exceed maxlines, if non-zero.
int renderedlines = 0;
calculateColumnWidths ();
// Print column headers in column order.
@ -1048,8 +1051,12 @@ const std::string Table::render (int maximum /* = 0 */)
}
output += "\n";
++renderedlines;
if (underline.length ())
{
output += underline + "\n";
++renderedlines;
}
// Determine row order, according to sort options.
std::vector <int> order;
@ -1060,14 +1067,17 @@ const std::string Table::render (int maximum /* = 0 */)
if (mSortColumns.size ())
sort (order);
// If a non-zero maximum is specified, then it limits the number of rows of
// If a non-zero maxrows is specified, then it limits the number of rows of
// the table that are rendered.
int limit = mRows;
if (maximum != 0)
limit = min (maximum, mRows);
int limitrows = mRows;
if (maxrows != 0)
limitrows = min (maxrows, mRows);
// If a non-zero maxlines is specified, then it limits the number of lines
// of output from the table that are rendered.
// Print all rows.
for (int row = 0; row < limit; ++row)
for (int row = 0; row < limitrows; ++row)
{
std::vector <std::vector <std::string> > columns;
std::vector <std::string> blanks;
@ -1105,6 +1115,11 @@ const std::string Table::render (int maximum /* = 0 */)
// Trim right.
output.erase (output.find_last_not_of (" ") + 1);
output += "\n";
++renderedlines;
if (maxlines != 0 && renderedlines >= maxlines)
break;
}
}
else
@ -1113,6 +1128,9 @@ const std::string Table::render (int maximum /* = 0 */)
output.erase (output.find_last_not_of (" ") + 1);
output += "\n";
}
if (maxlines != 0 && renderedlines >= maxlines)
break;
}
optimize (output);

View file

@ -92,7 +92,7 @@ public:
int rowCount ();
int columnCount ();
const std::string render (int maximum = 0);
const std::string render (int maxrows = 0, int maxlines = 0);
private:
std::string getCell (const int, const int);

View file

@ -647,7 +647,7 @@ int handleShow (std::string &outs)
"next project shadow.command shadow.file shadow.notify weekstart editor "
"import.synonym.id import.synonym.uuid complete.all.projects "
"complete.all.tags search.case.sensitive hooks active.indicator tag.indicator "
"recurrence.indicator recurrence.limit "
"recurrence.indicator recurrence.limit list.all.projects list.all.tags "
#ifdef FEATURE_SHELL
"shell.prompt "
#endif
@ -735,7 +735,7 @@ int handleShow (std::string &outs)
out << std::endl
<< table.render ()
<< (table.rowCount () == 0 ? "No matching configuration variables\n" : "")
<< (table.rowCount () == 0 ? "No matching configuration variables\n" : "")
<< std::endl;
// Display the unrecognized variables.

View file

@ -715,23 +715,37 @@ int runCustomReport (
table.setTableAlternateColor (alternate);
}
// Limit the number of rows according to the report definition.
int maximum = context.config.getInteger (std::string ("report.") + report + ".limit");
// Report output can be limited by rows or lines.
int maxrows = 0;
int maxlines = 0;
getLimits (report, maxrows, maxlines);
// If the custom report has a defined limit, then allow a numeric override.
// This is an integer specified as a filter (limit:10).
if (context.task.has ("limit"))
maximum = atoi (context.task.get ("limit").c_str ());
// Adjust for fluff in the output.
if (maxlines)
maxlines -= (context.config.getBoolean ("blanklines") ? 2 : 0)
+ 1
+ context.headers.size ()
+ context.footnotes.size ();
std::stringstream out;
if (table.rowCount ())
{
out << optionalBlankLine ()
<< table.render (maximum)
<< table.render (maxrows, maxlines)
<< optionalBlankLine ()
<< table.rowCount ()
<< (table.rowCount () == 1 ? " task" : " tasks")
<< std::endl;
else {
<< (table.rowCount () == 1 ? " task" : " tasks");
if (maxrows)
out << ", " << maxrows << " shown";
if (maxlines)
out << ", truncated to " << maxlines-1 << " lines";
out << std::endl;
}
else
{
out << "No matches."
<< std::endl;
rc = 1;
@ -813,3 +827,44 @@ void validSortColumns (
}
////////////////////////////////////////////////////////////////////////////////
// A value of zero mean unlimited.
// A value of 'page' means however many screen lines there are.
// A value of a positive integer is a row limit.
void getLimits (const std::string& report, int& rows, int& lines)
{
rows = 0;
lines = 0;
int screenheight = 0;
// If a report has a stated limit, use it.
if (report != "")
{
std::string name = "report." + report + ".limit";
if (context.config.get (name) == "page")
lines = screenheight = context.getHeight ();
else
rows = context.config.getInteger (name);
}
// If the custom report has a defined limit, then allow a numeric override.
// This is an integer specified as a filter (limit:10).
if (context.task.has ("limit"))
{
if (context.task.get ("limit") == "page")
{
if (screenheight == 0)
screenheight = context.getHeight ();
rows = 0;
lines = screenheight;
}
else
{
rows = atoi (context.task.get ("limit").c_str ());
lines = 0;
}
}
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -167,3 +167,36 @@ int Context::getWidth ()
}
////////////////////////////////////////////////////////////////////////////////
int Context::getHeight ()
{
// Determine window size, and set table accordingly.
int height = 25; // TODO Is there a better number?
#ifdef HAVE_LIBNCURSES
if (config.getBoolean ("curses"))
{
#ifdef FEATURE_NCURSES_COLS
initscr ();
height = LINES;
#else
WINDOW* w = initscr ();
height = w->_maxy + 1;
#endif
endwin ();
std::stringstream out;
out << "Context::getHeight: ncurses determined height of " << height << " characters";
debug (out.str ());
}
else
debug ("Context::getHeight: ncurses available but disabled.");
#else
std::stringstream out;
out << "Context::getHeight: no ncurses, using height of " << height << " characters";
debug (out.str ());
#endif
return height;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -117,6 +117,7 @@ int runCustomReport (const std::string&, const std::string&,
std::string&);
void validReportColumns (const std::vector <std::string>&);
void validSortColumns (const std::vector <std::string>&, const std::vector <std::string>&);
void getLimits (const std::string&, int&, int&);
// rules.cpp
void initializeColorRules ();

View file

@ -283,7 +283,7 @@ int longUsage (std::string &outs)
<< " until: Recurrence end date" << "\n"
<< " fg: Foreground color" << "\n"
<< " bg: Background color" << "\n"
<< " limit: Desired number of rows in report" << "\n"
<< " limit: Desired number of rows in report, or 'page'" << "\n"
<< " wait: Date until task becomes pending" << "\n"
<< "\n"
<< "Attribute modifiers improve filters. Supported modifiers are:" << "\n"
@ -1994,15 +1994,15 @@ std::string renderMonths (
case 1: // imminent
cellColor.blend (color_due);
break;
case 2: // today
cellColor.blend (color_duetoday);
break;
case 3: // overdue
cellColor.blend (color_overdue);
break;
case 0: // not due at all
default:
break;
@ -2318,6 +2318,7 @@ int handleReportCalendar (std::string &outs)
holTable.addCell (row, 1, holName);
}
}
out << optionalBlankLine ()
<< holTable.render ()
<< std::endl;

96
src/tests/limit.t Executable file
View file

@ -0,0 +1,96 @@
#! /usr/bin/perl
################################################################################
## task - a command line task list manager.
##
## Copyright 2006 - 2010, Paul Beckingham.
## All rights reserved.
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
## Foundation; either version 2 of the License, or (at your option) any later
## version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
## details.
##
## You should have received a copy of the GNU General Public License along with
## this program; if not, write to the
##
## Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor,
## Boston, MA
## 02110-1301
## USA
##
################################################################################
use strict;
use warnings;
use Test::More tests => 8;
# Create the rc file.
if (open my $fh, '>', 'limit.rc')
{
print $fh "data.location=.\n";
close $fh;
ok (-r 'limit.rc', 'Created limit.rc');
}
# Add a large number of tasks (> 25).
qx{../task rc:limit.rc add one};
qx{../task rc:limit.rc add two};
qx{../task rc:limit.rc add three};
qx{../task rc:limit.rc add four};
qx{../task rc:limit.rc add five};
qx{../task rc:limit.rc add six};
qx{../task rc:limit.rc add seven};
qx{../task rc:limit.rc add eight};
qx{../task rc:limit.rc add nine};
qx{../task rc:limit.rc add ten};
qx{../task rc:limit.rc add eleven};
qx{../task rc:limit.rc add twelve};
qx{../task rc:limit.rc add thirteen};
qx{../task rc:limit.rc add fourteen};
qx{../task rc:limit.rc add fifteen};
qx{../task rc:limit.rc add sixteen};
qx{../task rc:limit.rc add seventeen};
qx{../task rc:limit.rc add eighteen};
qx{../task rc:limit.rc add nineteen};
qx{../task rc:limit.rc add twenty};
qx{../task rc:limit.rc add twenty one};
qx{../task rc:limit.rc add twenty two};
qx{../task rc:limit.rc add twenty three};
qx{../task rc:limit.rc add twenty four};
qx{../task rc:limit.rc add twenty five};
qx{../task rc:limit.rc add twenty six};
qx{../task rc:limit.rc add twenty seven};
qx{../task rc:limit.rc add twenty eight};
qx{../task rc:limit.rc add twenty nine};
qx{../task rc:limit.rc add thirty};
my $output = qx{../task rc:limit.rc ls};
like ($output, qr/^30 tasks$/ms, 'unlimited');
$output = qx{../task rc:limit.rc ls limit:0};
like ($output, qr/^30 tasks$/ms, 'limited to 0 - unlimited');
$output = qx{../task rc:limit.rc ls limit:3};
like ($output, qr/^30 tasks, 3 shown$/ms, 'limited to 3');
$output = qx{../task rc:limit.rc ls limit:page};
like ($output, qr/^30 tasks, truncated to 20 lines$/ms, 'limited to page');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'limit.rc';
ok (!-r 'limit.rc', 'Removed limit.rc');
exit 0;