taskwarrior/src/Table.cpp
Paul Beckingham c35a764019 Custom Reports - oldest, newest
- Added support for the "report.X.limit" configuration variable, to
  restrict the number of rows a report generates.
- Added support for Table::render (limit) to limit the number of rows
  that are rendered.
- Removed "oldest" and "newest" report code.
- Added "oldest" and "newest" custom report details to Config.cpp
- Updated various documentation.
2009-03-12 22:34:45 -04:00

1088 lines
30 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
// Copyright 2006 - 2009, 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
//
//
//
// Attributes Table Row Column Cell
// ----------------------------------------------------
// foreground color Y Y Y Y
// background color Y Y Y Y
// padding Y Y Y Y
// wrap Y Y Y Y
// width Y - Y Y
// height - Y - Y
// justification Y Y Y Y
//
// Precedence
// If attributes conflict, the precedence order is:
// cell
// row
// column
// table
//
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <string.h>
#include <assert.h>
#include <Table.h>
#include <Date.h>
#include <task.h>
////////////////////////////////////////////////////////////////////////////////
Table::Table ()
: mRows (0)
, mIntraPadding (1)
, mDashedUnderline (false)
, mTablePadding (0)
, mTableWidth (0)
, mSuppressWS (false)
{
}
////////////////////////////////////////////////////////////////////////////////
Table::~Table ()
{
}
////////////////////////////////////////////////////////////////////////////////
void Table::setTableColor (Text::color fg, Text::color bg)
{
mFg["table"] = Text::colorName (fg);
mBg["table"] = Text::colorName (bg);
}
////////////////////////////////////////////////////////////////////////////////
void Table::setTableFg (Text::color c)
{
mFg["table"] = Text::colorName (c);
}
////////////////////////////////////////////////////////////////////////////////
void Table::setTableBg (Text::color c)
{
mBg["table"] = Text::colorName (c);
}
////////////////////////////////////////////////////////////////////////////////
void Table::setTablePadding (int padding)
{
mTablePadding = padding;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setTableIntraPadding (int padding)
{
mIntraPadding = padding;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setTableWidth (int width)
{
assert (width > 0);
mTableWidth = width;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setTableDashedUnderline ()
{
mDashedUnderline = true;
}
////////////////////////////////////////////////////////////////////////////////
int Table::addColumn (const std::string& col)
{
mSpecifiedWidth.push_back (minimum);
mMaxDataWidth.push_back (col.length ());
mCalculatedWidth.push_back (0);
mColumnPadding.push_back (0);
mColumns.push_back (col);
return mColumns.size () - 1;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setColumnColor (int column, Text::color fg, Text::color bg)
{
char id[12];
sprintf (id, "col:%d", column);
mFg[id] = Text::colorName (fg);
mBg[id] = Text::colorName (bg);
}
////////////////////////////////////////////////////////////////////////////////
void Table::setColumnFg (int column, Text::color c)
{
char id[12];
sprintf (id, "col:%d", column);
mFg[id] = Text::colorName (c);
}
////////////////////////////////////////////////////////////////////////////////
void Table::setColumnBg (int column, Text::color c)
{
char id[12];
sprintf (id, "col:%d", column);
mBg[id] = Text::colorName (c);
}
////////////////////////////////////////////////////////////////////////////////
void Table::setColumnUnderline (int column)
{
char id[12];
sprintf (id, "col:%d", column);
mUnderline[id] = Text::underline;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setColumnPadding (int column, int padding)
{
mColumnPadding[column] = padding;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setColumnWidth (int column, int width)
{
assert (width > 0);
mSpecifiedWidth[column] = width;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setColumnWidth (int column, sizing s)
{
mSpecifiedWidth[column] = (int) s;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setColumnCommify (int column)
{
mCommify[column] = true;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setColumnJustification (int column, just j)
{
mJustification[column] = j;
}
////////////////////////////////////////////////////////////////////////////////
void Table::sortOn (int column, order o)
{
mSortColumns.push_back (column);
mSortOrder[column] = o;
}
////////////////////////////////////////////////////////////////////////////////
int Table::addRow ()
{
return mRows++;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setRowColor (const int row, const Text::color fg, const Text::color bg)
{
char id[12];
sprintf (id, "row:%d", row);
mFg[id] = Text::colorName (fg);
mBg[id] = Text::colorName (bg);
}
////////////////////////////////////////////////////////////////////////////////
void Table::setRowFg (const int row, const Text::color c)
{
char id[12];
sprintf (id, "row:%d", row);
mFg[id] = Text::colorName (c);
}
////////////////////////////////////////////////////////////////////////////////
void Table::setRowBg (const int row, const Text::color c)
{
char id[12];
sprintf (id, "row:%d", row);
mBg[id] = Text::colorName (c);
}
////////////////////////////////////////////////////////////////////////////////
void Table::addCell (const int row, const int col, const std::string& data)
{
int length = 0;
if (mSuppressWS)
{
std::string data2;
if (mCommify.find (col) != mCommify.end ())
data2 = commify (data);
else
data2 = data;
clean (data2);
length = data2.length ();
mData.add (row, col, data2);
}
else
{
if (mCommify.find (col) != mCommify.end ())
mData.add (row, col, commify (data));
else
mData.add (row, col, data);
length = data.length ();
}
// Automatically maintain max width.
mMaxDataWidth[col] = max (mMaxDataWidth[col], length);
}
////////////////////////////////////////////////////////////////////////////////
void Table::addCell (const int row, const int col, const char data)
{
mData.add (row, col, data);
// Automatically maintain max width.
mMaxDataWidth[col] = max (mMaxDataWidth[col], 1);
}
////////////////////////////////////////////////////////////////////////////////
void Table::addCell (const int row, const int col, const int data)
{
char value[12];
sprintf (value, "%d", data);
if (mCommify.find (col) != mCommify.end ())
mData.add (row, col, commify (value));
else
mData.add (row, col, value);
// Automatically maintain max width.
mMaxDataWidth[col] = max (mMaxDataWidth[col], (signed) ::strlen (value));
}
////////////////////////////////////////////////////////////////////////////////
void Table::addCell (const int row, const int col, const float data)
{
char value[24];
sprintf (value, "%.2f", data);
if (mCommify.find (col) != mCommify.end ())
mData.add (row, col, commify (value));
else
mData.add (row, col, value);
// Automatically maintain max width.
mMaxDataWidth[col] = max (mMaxDataWidth[col], (signed) ::strlen (value));
}
////////////////////////////////////////////////////////////////////////////////
void Table::addCell (const int row, const int col, const double data)
{
char value[24];
sprintf (value, "%.6f", data);
if (mCommify.find (col) != mCommify.end ())
mData.add (row, col, commify (value));
else
mData.add (row, col, value);
// Automatically maintain max width.
mMaxDataWidth[col] = max (mMaxDataWidth[col], (signed) ::strlen (value));
}
////////////////////////////////////////////////////////////////////////////////
void Table::setCellColor (const int row, const int col, const Text::color fg, const Text::color bg)
{
char id[24];
sprintf (id, "cell:%d,%d", row, col);
mFg[id] = Text::colorName (fg);
mBg[id] = Text::colorName (bg);
}
////////////////////////////////////////////////////////////////////////////////
void Table::setCellFg (const int row, const int col, const Text::color c)
{
char id[24];
sprintf (id, "cell:%d,%d", row, col);
mFg[id] = Text::colorName (c);
}
////////////////////////////////////////////////////////////////////////////////
void Table::setCellBg (const int row, const int col, const Text::color c)
{
char id[24];
sprintf (id, "cell:%d,%d", row, col);
mBg[id] = Text::colorName (c);
}
////////////////////////////////////////////////////////////////////////////////
std::string Table::getCell (const int row, const int col)
{
Grid::Cell* c = mData.byRow (row, col);
if (c)
return (std::string) *c;
return "";
}
////////////////////////////////////////////////////////////////////////////////
Text::color Table::getFg (const int row, const int col)
{
char idCell[24];
sprintf (idCell, "cell:%d,%d", row, col);
if (mFg.find (idCell) != mFg.end ())
return Text::colorCode (mFg[idCell]);
char idRow[12];
sprintf (idRow, "row:%d", row);
if (mFg.find (idRow) != mFg.end ())
return Text::colorCode (mFg[idRow]);
char idCol[12];
sprintf (idCol, "col:%d", col);
if (mFg.find (idCol) != mFg.end ())
return Text::colorCode (mFg[idCol]);
if (mFg.find ("table") != mFg.end ())
return Text::colorCode (mFg["table"]);
return Text::nocolor;
}
////////////////////////////////////////////////////////////////////////////////
Text::color Table::getHeaderFg (int col)
{
char idCol[12];
sprintf (idCol, "col:%d", col);
return mFg.find (idCol) != mFg.end () ? Text::colorCode (mFg[idCol])
: mFg.find ("table") != mFg.end () ? Text::colorCode (mFg["table"])
: Text::nocolor;
}
////////////////////////////////////////////////////////////////////////////////
Text::color Table::getBg (const int row, const int col)
{
char idCell[24];
sprintf (idCell, "cell:%d,%d", row, col);
if (mBg.find (idCell) != mBg.end ())
return Text::colorCode (mBg[idCell]);
char idRow[12];
sprintf (idRow, "row:%d", row);
if (mBg.find (idRow) != mBg.end ())
return Text::colorCode (mBg[idRow]);
char idCol[12];
sprintf (idCol, "col:%d", col);
if (mBg.find (idCol) != mBg.end ())
return Text::colorCode (mBg[idCol]);
if (mBg.find ("table") != mBg.end ())
return Text::colorCode (mBg["table"]);
return Text::nocolor;
}
////////////////////////////////////////////////////////////////////////////////
Text::color Table::getHeaderBg (int col)
{
char idCol[12];
sprintf (idCol, "col:%d", col);
return mBg.find (idCol) != mBg.end () ? Text::colorCode (mBg[idCol])
: mBg.find ("table") != mBg.end () ? Text::colorCode (mBg["table"])
: Text::nocolor;
}
////////////////////////////////////////////////////////////////////////////////
Text::color Table::getHeaderUnderline (int col)
{
char idCol[12];
sprintf (idCol, "col:%d", col);
return mUnderline.find (idCol) != mUnderline.end () ? Text::underline
: Text::nocolor;
}
////////////////////////////////////////////////////////////////////////////////
int Table::getIntraPadding ()
{
return mIntraPadding;
}
////////////////////////////////////////////////////////////////////////////////
int Table::getPadding (int col)
{
return max (mColumnPadding[col], mTablePadding);
}
////////////////////////////////////////////////////////////////////////////////
// Using mSpecifiedWidth, mMaxDataWidth, generate mCalculatedWidth.
void Table::calculateColumnWidths ()
{
// Ideal case: either no table width is specified, or everything fits without
// wrapping into mTableWidth.
std::vector <int> ideal = mMaxDataWidth;
int width = 0;
int countFlexible = 0;
for (size_t c = 0; c < mColumns.size (); ++c)
{
if (mSpecifiedWidth[c] == flexible)
++countFlexible;
else if (mSpecifiedWidth[c] > 0)
ideal[c] = mSpecifiedWidth[c];
width += mColumnPadding[c] +
ideal[c] +
mColumnPadding[c] +
(c > 0 ? mIntraPadding : 0);
}
if (!mTableWidth || width < mTableWidth)
{
mCalculatedWidth = ideal;
return;
}
// Try again, with available space divided among the flexible columns.
if (countFlexible)
{
ideal = mMaxDataWidth;
width = 0;
for (size_t c = 0; c < mColumns.size (); ++c)
{
if (mSpecifiedWidth[c] > 0)
ideal[c] = mSpecifiedWidth[c];
else if (mSpecifiedWidth[c] == flexible)
{
ideal[c] = 0;
}
width += mColumnPadding[c] +
ideal[c] +
mColumnPadding[c] +
(c > 0 ? mIntraPadding : 0);
}
int available = mTableWidth - width;
if (width < mTableWidth) // if there is room to wiggle in
{
int shared = available / countFlexible;
int remainder = available % countFlexible;
int lastFlexible = mColumns.size () - 1;
for (size_t c = 0; c < mColumns.size (); ++c)
{
if (mSpecifiedWidth[c] == flexible)
{
lastFlexible = c;
ideal[c] += shared;
}
}
// Remainder goes to last column.
ideal[lastFlexible] += remainder;
mCalculatedWidth = ideal;
return;
}
else
{
// std::cout << "# insufficient room, considering only flexible columns." << std::endl;
// The fallback position is to assume no width was specificed, and just
// calculate widths accordingly.
mTableWidth = 0;
calculateColumnWidths ();
return;
}
}
// Try again, treating minimum columns as flexible.
// std::cout << "# no flexible columns. Now what?" << std::endl;
}
////////////////////////////////////////////////////////////////////////////////
Table::just Table::getJustification (const int row, const int col)
{
if (mJustification.find (col) != mJustification.end ())
return mJustification[col];
return left;
}
////////////////////////////////////////////////////////////////////////////////
Table::just Table::getHeaderJustification (int col)
{
return mJustification.find (col) != mJustification.end () ? mJustification[col]
: left;
}
////////////////////////////////////////////////////////////////////////////////
// data One Data to be rendered
// width 8 Max data width for column/specified width
// padding 1 Extra padding around data
// intraPadding 0 Extra padding between columns only
// justification right Alignment withing padding
//
// Returns:
// " One "
// One data
// ^ ^ padding
// ^ intraPadding
// ^^^^^^^^ width
// ^ ^ fg/bg
//
const std::string Table::formatHeader (
int col,
int width,
int padding)
{
assert (width > 0);
Text::color fg = getHeaderFg (col);
Text::color bg = getHeaderBg (col);
std::string data = mColumns[col];
Text::color decoration = getHeaderUnderline (col);
std::string pad = "";
std::string intraPad = "";
std::string preJust = "";
std::string attrOn = "";
std::string attrOff = "";
std::string postJust = "";
for (int i = 0; i < padding; ++i)
pad += " ";
// Place the value within the available space - justify.
int gap = width - data.length ();
for (int i = 0; i < gap; ++i)
postJust += " ";
if (col < (signed) mColumns.size () - 1)
for (int i = 0; i < getIntraPadding (); ++i)
intraPad += " ";
return Text::colorize (
fg, bg,
Text::colorize (
decoration, Text::nocolor,
pad + preJust + data + postJust + pad) + intraPad);
}
////////////////////////////////////////////////////////////////////////////////
// data One Data to be rendered
// width 8 Max data width for column/specified width
// padding 1 Extra padding around data
// intraPadding 0 Extra padding between columns only
// justification right Alignment withing padding
//
// Returns:
// "------- "
// ------- data
// ^ ^ padding
// ^ intraPadding
// ^^^^^^^^ width
// ^ ^ fg/bg
//
const std::string Table::formatHeaderDashedUnderline (
int col,
int width,
int padding)
{
assert (width > 0);
Text::color fg = getHeaderFg (col);
Text::color bg = getHeaderBg (col);
Text::color decoration = getHeaderUnderline (col);
std::string data = "";
for (int i = 0; i < width; ++i)
data += '-';
std::string pad = "";
std::string intraPad = "";
std::string attrOn = "";
std::string attrOff = "";
for (int i = 0; i < padding; ++i)
pad += " ";
// Place the value within the available space - justify.
if (col < (signed) mColumns.size () - 1)
for (int i = 0; i < getIntraPadding (); ++i)
intraPad += " ";
return Text::colorize (
fg, bg,
Text::colorize (
decoration, Text::nocolor,
pad + data + pad) + intraPad);
}
////////////////////////////////////////////////////////////////////////////////
void Table::formatCell (
const int row,
const int col,
const int width,
const int padding,
std::vector <std::string>& lines,
std::string& blank)
{
assert (width > 0);
Text::color fg = getFg (row, col);
Text::color bg = getBg (row, col);
just justification = getJustification (row, col);
std::string data = getCell (row, col);
std::string pad = "";
std::string intraPad = "";
for (int i = 0; i < padding; ++i)
pad += " ";
if (col < (signed) mColumns.size () - 1)
for (int i = 0; i < getIntraPadding (); ++i)
intraPad += " ";
// Break the text into chunks of width characters.
std::string preJust;
std::string postJust;
std::vector <std::string> chunks;
wrapText (chunks, data, width);
for (size_t chunk = 0; chunk < chunks.size (); ++chunk)
{
// Place the data within the available space - justify.
int gap = width - chunks[chunk].length ();
preJust = "";
postJust = "";
if (justification == left)
for (int i = 0; i < gap; ++i)
postJust += " ";
else if (justification == right)
for (int i = 0; i < gap; ++i)
preJust += " ";
else if (justification == center)
{
for (int i = 0; i < gap / 2; ++i)
preJust += " ";
for (size_t i = 0; i < gap - preJust.length (); ++i)
postJust += " ";
}
lines.push_back (
Text::colorize (fg, bg, pad + preJust + chunks[chunk] + postJust + pad + intraPad));
}
// The blank is used to vertically pad cells that have blank lines.
pad = "";
for (int i = 0; i < width; ++i)
pad += " ";
blank = Text::colorize (fg, bg, pad + intraPad);
}
////////////////////////////////////////////////////////////////////////////////
void Table::suppressWS ()
{
mSuppressWS = true;
}
////////////////////////////////////////////////////////////////////////////////
void Table::setDateFormat (const std::string& dateFormat)
{
mDateFormat = dateFormat;
}
////////////////////////////////////////////////////////////////////////////////
int Table::rowCount ()
{
return mRows;
}
////////////////////////////////////////////////////////////////////////////////
int Table::columnCount ()
{
return mColumns.size ();
}
////////////////////////////////////////////////////////////////////////////////
// Removes extraneous output characters, such as:
// - removal of redundant color codes:
// ^[[31mName^[[0m ^[[31mValue^[[0m -> ^[[31mName Value^[[0m
//
// This method is a work in progress.
void Table::optimize (std::string& output) const
{
// int start = output.length ();
/*
Well, how about that!
The benchmark.t unit test adds a 1000 tasks, fiddles with some of them, then
runs a series of reports. The results are timed, and look like this:
1000 tasks added in 3 seconds
600 tasks altered in 32 seconds
'task ls' in 26 seconds
'task list' in 17 seconds
'task list pri:H' in 19 seconds
'task list +tag' in 0 seconds
'task list project_A' in 0 seconds
'task long' in 29 seconds
'task completed' in 2 seconds
'task history' in 0 seconds
'task ghistory' in 0 seconds
This performance is terrible. To identify the worst offender, Various Timer
objects were added in Table::render, assuming that table sorting is the major
bottleneck. But no, it is Table::optimize that is the problem. After
commenting out this method, the results are now:
1000 tasks added in 3 seconds
600 tasks altered in 29 seconds
'task ls' in 0 seconds
'task list' in 0 seconds
'task list pri:H' in 1 seconds
'task list +tag' in 0 seconds
'task list project_A' in 0 seconds
'task long' in 0 seconds
'task completed' in 0 seconds
'task history' in 0 seconds
'task ghistory' in 0 seconds
Much better.
*/
// std::cout << int ((100 * (start - output.length ()) / start))
// << "%" << std::endl;
}
////////////////////////////////////////////////////////////////////////////////
// Combsort11, with O(n log n) average, O(n log n) worst case performance.
//
// function combsort11(array input)
// gap := input.size
//
// loop until gap <= 1 and swaps = 0
// if gap > 1
// gap := gap / 1.3
// if gap = 10 or gap = 9
// gap := 11
// end if
// end if
//
// i := 0
// swaps := 0
//
// loop until i + gap >= input.size
// if input[i] > input[i+gap]
// swap(input[i], input[i+gap])
// swaps := 1
// end if
// i := i + 1
// end loop
//
// end loop
// end function
#define SWAP \
{ \
int temp = order[r]; \
order[r] = order[r + gap]; \
order[r + gap] = temp; \
swaps = 1; \
}
void Table::sort (std::vector <int>& order)
{
int gap = order.size ();
int swaps = 1;
while (gap > 1 || swaps > 0)
{
if (gap > 1)
{
gap = (int) ((float)gap / 1.3);
if (gap == 10 or gap == 9)
gap = 11;
}
int r = 0;
swaps = 0;
while (r + gap < (int) order.size ())
{
bool keepScanning = true;
for (size_t c = 0; keepScanning && c < mSortColumns.size (); ++c)
{
keepScanning = false;
Grid::Cell* left = mData.byRow (order[r], mSortColumns[c]);
Grid::Cell* right = mData.byRow (order[r + gap], mSortColumns[c]);
// Data takes precedence over missing data.
if (left == NULL && right != NULL)
{
SWAP
break;
}
// No data - try comparing the next column.
else if (left == NULL && right == NULL)
{
keepScanning = true;
}
// Identical data - try comparing the next column.
else if (left && right && *left == *right)
{
keepScanning = true;
}
// Differing data - do a proper comparison.
else if (left && right && *left != *right)
{
switch (mSortOrder[mSortColumns[c]])
{
case ascendingNumeric:
if ((float)*left > (float)*right)
SWAP
break;
case descendingNumeric:
if ((float)*left < (float)*right)
SWAP
break;
case ascendingCharacter:
if ((std::string)*left > (std::string)*right)
SWAP
break;
case descendingCharacter:
if ((std::string)*left < (std::string)*right)
SWAP
break;
case ascendingDate:
{
if ((std::string)*left != "" && (std::string)*right == "")
break;
else if ((std::string)*left == "" && (std::string)*right != "")
SWAP
else
{
Date dl ((std::string)*left, mDateFormat);
Date dr ((std::string)*right, mDateFormat);
if (dl > dr)
SWAP
}
}
break;
case descendingDate:
{
if ((std::string)*left != "" && (std::string)*right == "")
break;
else if ((std::string)*left == "" && (std::string)*right != "")
SWAP
else
{
Date dl ((std::string)*left, mDateFormat);
Date dr ((std::string)*right, mDateFormat);
if (dl < dr)
SWAP
}
}
break;
case ascendingPriority:
if (((std::string)*left == "" && (std::string)*right != "") ||
((std::string)*left == "M" && (std::string)*right == "L") ||
((std::string)*left == "H" && ((std::string)*right == "L" || (std::string)*right == "M")))
SWAP
break;
case descendingPriority:
if (((std::string)*left == "" && (std::string)*right != "") ||
((std::string)*left == "L" && ((std::string)*right == "M" || (std::string)*right == "H")) ||
((std::string)*left == "M" && (std::string)*right == "H"))
SWAP
break;
case ascendingPeriod:
if ((std::string)*left == "" && (std::string)*right != "")
break;
else if ((std::string)*left != "" && (std::string)*right == "")
SWAP
else if (convertDuration ((std::string)*left) > convertDuration ((std::string)*right))
SWAP
break;
case descendingPeriod:
if ((std::string)*left != "" && (std::string)*right == "")
break;
else if ((std::string)*left == "" && (std::string)*right != "")
SWAP
else if (convertDuration ((std::string)*left) < convertDuration ((std::string)*right))
SWAP
break;
}
}
}
++r;
}
}
}
////////////////////////////////////////////////////////////////////////////////
void Table::clean (std::string& value)
{
size_t start = 0;
size_t pos;
while ((pos = value.find ('\t', start)) != std::string::npos)
{
value.replace (pos, 1, " ");
start = pos; // Not pos + 1, because we have a destructive operation, and
// this is ultimately safer.
}
while ((pos = value.find ('\r', start)) != std::string::npos)
{
value.replace (pos, 1, " ");
start = pos;
}
while ((pos = value.find ('\n', start)) != std::string::npos)
{
value.replace (pos, 1, " ");
start = pos;
}
}
////////////////////////////////////////////////////////////////////////////////
const std::string Table::render (int maximum /* = 0 */)
{
calculateColumnWidths ();
// Print column headers in column order.
std::string output;
std::string underline;
for (size_t col = 0; col < mColumns.size (); ++col)
{
output += formatHeader (
col,
mCalculatedWidth[col],
mColumnPadding[col]);
if (mDashedUnderline)
underline += formatHeaderDashedUnderline (
col,
mCalculatedWidth[col],
mColumnPadding[col]);
}
output += "\n";
if (underline.length ())
output += underline + "\n";
// Determine row order, according to sort options.
std::vector <int> order;
for (int row = 0; row < mRows; ++row)
order.push_back (row);
// Only sort if necessary.
if (mSortColumns.size ())
sort (order);
// If a non-zero maximum is specified, then it limits the number of rows of
// the table that are rendered.
int limit = mRows;
if (maximum != 0)
limit = maximum;
// Print all rows.
for (int row = 0; row < limit; ++row)
{
std::vector <std::vector <std::string> > columns;
std::vector <std::string> blanks;
size_t maxHeight = 0;
for (size_t col = 0; col < mColumns.size (); ++col)
{
std::vector <std::string> lines;
std::string blank;
formatCell (
order[row],
col,
mCalculatedWidth[col],
mColumnPadding[col],
lines,
blank);
columns.push_back (lines);
blanks.push_back (blank);
maxHeight = max (maxHeight, columns[col].size ());
}
if (maxHeight)
{
for (size_t lines = 0; lines < maxHeight; ++lines)
{
for (size_t col = 0; col < mColumns.size (); ++col)
if (lines < columns[col].size ())
output += columns[col][lines];
else
output += blanks[col];
// Trim right.
output.erase (output.find_last_not_of (" ") + 1);
output += "\n";
}
}
else
{
// Trim right.
output.erase (output.find_last_not_of (" ") + 1);
output += "\n";
}
}
return output;
}
////////////////////////////////////////////////////////////////////////////////