This is a follow-up from my previous post, which can be found here. A readme for more detailed information can be found here. It is a library to create console based tables where you can insert, remove and update entries. An example could look like this:
+---------------+-------------+-----------------+-----------------+---------------------+
| Country | Capital | Population | Area | Currency |
+---------------+-------------+-----------------+-----------------+---------------------+
| Australia | NEW ENTRY | 24,877,800 | 7,692,024 km2 | Australian Dollar |
| China | Beijing | 1,403,500,365 | 9,596,961 km2 | Yuan |
| France | Paris | 67,201,000 | 640,679 km2 | Euro |
| Germany | Berlin | 82,800,000 | 357,168 km2 | Euro |
| Iceland | Reykjavik | 348,580 | 102,775 km2 | Icelandic Krona |
| Netherlands | Amsterdam | 17,200,671 | 41,543 km2 | Euro |
+---------------+-------------+-----------------+-----------------+---------------------+
I got very good feedback last time and tried to improve the code as best as I could. I wonder if I can fine-tune it any further and if there are still any bad habits that can be avoided.
ConsoleTable.cpp
#include "ConsoleTable.h"
ConsoleTable::ConsoleTable(std::initializer_list<std::string> headers) {
this->headers = {headers};
for (std::string column : headers) {
widths.push_back(column.length());
}
}
void ConsoleTable::setPadding(unsigned int n) {
this->padding = n;
}
void ConsoleTable::setStyle(unsigned int n) {
switch (n) {
case 0 :
style = BasicStyle;
break;
case 1 :
style = LineStyle;
break;
case 2 :
style = DoubleLineStyle;
break;
case 3 :
style = InvisibleStyle;
break;
default :
style = BasicStyle;
break;
}
}
bool ConsoleTable::addRow(std::initializer_list<std::string> row) {
if (row.size() > widths.size()) {
throw std::invalid_argument{"Appended row size must be same as header size"};
}
std::vector<std::string> r = std::vector<std::string>{row};
rows.push_back(r);
for (int i = 0; i < r.size(); ++i) {
widths[i] = std::max(r[i].size(), widths[i]);
}
return true;
}
bool ConsoleTable::removeRow(unsigned int index) {
if (index > rows.size())
return false;
rows.erase(rows.begin() + index);
return true;
}
ConsoleTable &ConsoleTable::operator+=(std::initializer_list<std::string> row) {
if (row.size() > widths.size()) {
throw std::invalid_argument{"Appended row size must be same as header size"};
}
addRow(row);
return *this;
}
ConsoleTable &ConsoleTable::operator-=(unsigned int rowIndex) {
if (rows.size() < rowIndex)
throw std::out_of_range{"Row index out of range."};
removeRow(rowIndex);
}
std::string ConsoleTable::getLine(RowType rowType) const {
std::stringstream line;
line << rowType.left;
for (int i = 0; i < widths.size(); ++i) {
for (int j = 0; j < (widths[i] + padding + padding); ++j) {
line << style.horizontal;
}
line << (i == widths.size() - 1 ? rowType.right : rowType.intersect);
}
return line.str() + "\n";
}
std::string ConsoleTable::getHeaders(Headers headers) const {
std::stringstream line;
line << style.vertical;
for (int i = 0; i < headers.size(); ++i) {
std::string text = headers[i];
line << space * padding + text + space * (widths[i] - text.length()) + space * padding;
line << style.vertical;
}
line << "\n";
return line.str();
}
std::string ConsoleTable::getRows(Rows rows) const {
std::stringstream line;
for (int i = 0; i < rows.size(); ++i) {
line << style.vertical;
for (int j = 0; j < rows[i].size(); ++j) {
std::string text = rows[i][j];
line << space * padding + text + space * (widths[j] - text.length()) + space * padding;
line << style.vertical;
}
line << "\n";
}
return line.str();
}
std::ostream &operator<<(std::ostream &out, const ConsoleTable &consoleTable) {
out << consoleTable.getLine(consoleTable.style.top);
out << consoleTable.getHeaders(consoleTable.headers);
out << consoleTable.getLine(consoleTable.style.middle);
out << consoleTable.getRows(consoleTable.rows);
out << consoleTable.getLine(consoleTable.style.bottom);
return out;
}
bool ConsoleTable::sort(bool ascending) {
if (ascending)
std::sort(rows.begin(), rows.end(), std::less<std::vector<std::string>>());
else
std::sort(rows.begin(), rows.end(), std::greater<std::vector<std::string>>());
return true;
}
void ConsoleTable::updateRow(unsigned int row, unsigned int header, std::string data) {
if (row > rows.size() - 1)
throw std::out_of_range{"Row index out of range."};
if (header > headers.size() - 1)
throw std::out_of_range{"Header index out of range."};
rows[row][header] = data;
}
void ConsoleTable::updateHeader(unsigned int header, std::string text) {
if (header > headers.size())
throw std::out_of_range{"Header index out of range."};
headers[header] = text;
}
std::string operator*(const std::string &other, int repeats) {
std::string ret;
ret.reserve(other.size() * repeats);
for (; repeats; --repeats)
ret.append(other);
return ret;
}
ConsoleTable.h
#ifndef CONSOLETABLE_CONSOLETABLE_H
#define CONSOLETABLE_CONSOLETABLE_H
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <memory>
#include <algorithm>
typedef std::vector<std::string> Headers;
typedef std::vector<std::vector<std::string>> Rows;
typedef std::vector<std::size_t> Widths;
class ConsoleTable {
public:
/// Initialize a new ConsoleTable
/// \param headers Stringlist of the tables headers
ConsoleTable(std::initializer_list<std::string> headers);
/// Sets the distance from the text to the cell border
/// \param n Spaces between the text and the cell border
void setPadding(unsigned int n);
/// Sets the style of the table, default is 0
/// n = 0 : Basic table style
/// n = 1 : Single lined table style
/// n = 2 : Double lined table style
/// n = 3 : Invisivle table lines style
/// \param n The table style number
void setStyle(unsigned int n);
/// Sorts the table rows based on the first column
/// \param ascending Should table be sorted ascending or descending
/// \return True if sorting was successful, otherwise false
bool sort(bool ascending);
/// Adds a new row to the table
/// \param row A list of strings to add as row
/// \return True if the value was added successfully, otherwise false
bool addRow(std::initializer_list<std::string> row);
/// Removes a row from the table by the row index
/// \param index The index of the row that should be removed
/// \return True if the row was removed successfully, otherwise false
bool removeRow(unsigned int index);
/// Update an existing table cell with new data
/// \param row The index of the row that needs to be updated
/// \param header The index of the column that needs to be updated
/// \param data The new data that should be assigned to teh cell
void updateRow(unsigned int row, unsigned int header, std::string data);
/// Update a header with new text
/// \param header Index of the header that should be updated
/// \param text The new teext of the new header
void updateHeader(unsigned int header, std::string text);
/// Operator of the addRow() function
/// \param row A list of strings to add as row
/// \return this
ConsoleTable &operator+=(std::initializer_list<std::string> row);
/// Operator of the removeRow() function
/// \param rowIndex The index of the row that should be removed
/// \return this
ConsoleTable &operator-=(unsigned int rowIndex);
/// Holds all header strings of the table
Headers headers;
/// Holds all rows of the table
Rows rows;
/// Holds the size of widest string of each column of the table
Widths widths;
private:
struct RowType {
std::string left;
std::string intersect;
std::string right;
};
struct TableStyle {
std::string horizontal;
std::string vertical;
RowType top;
RowType middle;
RowType bottom;
};
TableStyle BasicStyle = {"-", "|", {"+", "+", "+"}, {"+", "+", "+"}, {"+", "+", "+"}};
TableStyle LineStyle = {"━", "┃", {"┏", "┳", "┓"}, {"┣", "╋", "┫"}, {"┗", "┻", "┛"}};
TableStyle DoubleLineStyle = {"═", "║", {"╔", "╦", "╗"}, {"╠", "╬", "╣"}, {"╚", "╩", "╝"}};
TableStyle InvisibleStyle = {" ", " ", {" ", " ", " "}, {" ", " ", " "}, {" ", " ", " "}};
TableStyle style = BasicStyle;
std::string space = " ";
/// The distance between the cell text and the cell border
unsigned int padding = 1;
/// Returns a formatted horizontal separation line for the table
/// \param rowType The type of the row (top, middle, bottom)
/// \return The formatted row string
std::string getLine(RowType rowType) const;
/// Returns a formatted header string
/// \param headers The Headers-object that holds the header strings
/// \return The formatted header string
std::string getHeaders(Headers headers) const;
/// Returns a formmatted row string
/// \param rows The Rows-object that holds all rows of the table
/// \return A formatted string of all rows in the table
std::string getRows(Rows rows) const;
/// Writes the entire table with all its contents in the output stream
/// This can be used to display the table using the std::cout function
/// \param out The output stream the table should be written to
/// \param consoleTable The ConsoleTable-object
/// \return Output stream with the formatted table string
friend std::ostream &operator<<(std::ostream &out, const ConsoleTable &consoleTable);
};
/// Repeats a given string n times
/// \param other The string to repeat
/// \param repeats The amount the string should be repeated
/// \return The repeated string
std::string operator*(const std::string &other, int repeats);
#endif //CONSOLETABLE_CONSOLETABLE_H
To create an example table like shown above, this is how you can use the lib:
#include <iostream>
#include "ConsoleTable.h"
int main() {
ConsoleTable table{"Country", "Capital", "Population", "Area", "Currency"};
table.setPadding(2);
table.setStyle(3);
table += {"Germany", "Berlin", "82,800,000", "357,168 km2", "Euro"};
table += {"France", "Paris", "67,201,000", "640,679 km2 ", "Euro"};
table += {"South Korea", "Seoul", "51,446,201", "100,210 km2 ", "South Korean Won"};
table += {"Australia", "Canberra", "24,877,800", "7,692,024 km2", "Australian Dollar"};
table += {"China", "Beijing", "1,403,500,365", "9,596,961 km2", "Yuan"};
table += {"Iceland", "Reykjavik", "348,580", "102,775 km2", "Icelandic Krona"};
table += {"Netherlands", "Amsterdam", "17,200,671", "41,543 km2", "Euro"};
table.updateRow(3, 1, "NEW ENTRY");
// Remove some entries
table -= 2;
table -= 1;
table -= 0;
table.sort(true);
std::cout << table;
return 0;
}
std::cout
or a file etc. I just found the name console table most fitting :) \$\endgroup\$switch
andcase
will be on one line. \$\endgroup\$