Skip to content

Commit

Permalink
[Feature] - Support of multi line cells in SimpleTable
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanel committed Mar 11, 2024
1 parent 1d1bddb commit 218365d
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 91 deletions.
18 changes: 10 additions & 8 deletions src/engine/src/queryresultprinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ json ToJson(CoincenterCommandType commandType, json &&in, json &&out) {
in.emplace("req", CoincenterCommandTypeToString(commandType));

json ret;

ret.emplace("in", std::move(in));
ret.emplace("out", std::move(out));

Expand Down Expand Up @@ -1154,7 +1155,7 @@ void QueryResultPrinter::printWithdrawFees(const MonetaryAmountByCurrencySetPerE
for (const auto &[e, withdrawFees] : withdrawFeesPerExchange) {
auto it = withdrawFees.find(cur);
if (it == withdrawFees.end()) {
row.emplace_back();
row.emplace_back("");
} else {
row.emplace_back(it->str());
}
Expand Down Expand Up @@ -1314,16 +1315,17 @@ void QueryResultPrinter::printDustSweeper(
switch (_apiOutputType) {
case ApiOutputType::kFormattedTable: {
SimpleTable simpleTable("Exchange", "Account", "Trades", "Final Amount");

simpleTable.reserve(1U + tradedAmountsVectorWithFinalAmountPerExchange.size());
for (const auto &[exchangePtr, tradedAmountsVectorWithFinalAmount] :
tradedAmountsVectorWithFinalAmountPerExchange) {
string tradesStr;
for (const auto &tradedAmounts : tradedAmountsVectorWithFinalAmount.tradedAmountsVector) {
if (!tradesStr.empty()) {
tradesStr.append(", ");
}
tradesStr.append(tradedAmounts.str());
SimpleTable::Cell tradesCell;
const auto &tradedAmountsVector = tradedAmountsVectorWithFinalAmount.tradedAmountsVector;
tradesCell.reserve(tradedAmountsVector.size());
for (const auto &tradedAmounts : tradedAmountsVector) {
tradesCell.emplace_back(tradedAmounts.str());
}
simpleTable.emplace_back(exchangePtr->name(), exchangePtr->keyName(), std::move(tradesStr),
simpleTable.emplace_back(exchangePtr->name(), exchangePtr->keyName(), std::move(tradesCell),
tradedAmountsVectorWithFinalAmount.finalAmount.str());
}
printTable(simpleTable);
Expand Down
17 changes: 9 additions & 8 deletions src/engine/test/queryresultprinter_private_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#include "exchangename.hpp"
#include "exchangeprivateapitypes.hpp"
#include "monetaryamount.hpp"
#include "order.hpp"
#include "ordersconstraints.hpp"
#include "priceoptions.hpp"
#include "priceoptionsdef.hpp"
Expand Down Expand Up @@ -1660,13 +1659,15 @@ TEST_F(QueryResultPrinterDustSweeperTest, FormattedTable) {
basicQueryResultPrinter(ApiOutputType::kFormattedTable)
.printDustSweeper(tradedAmountsVectorWithFinalAmountPerExchange, cur);
static constexpr std::string_view kExpected = R"(
+----------+-----------+-------------------------------------------------------+--------------+
| Exchange | Account | Trades | Final Amount |
+----------+-----------+-------------------------------------------------------+--------------+
| binance | testuser1 | 98.47 ETH -> 0.00005 BTC | 0 ETH |
| huobi | testuser1 | | 1.56 ETH |
| huobi | testuser2 | 0.45609 EUR -> 98.47 ETH, 1509.45 ETH -> 0.000612 BTC | 0 ETH |
+----------+-----------+-------------------------------------------------------+--------------+
+----------+-----------+-----------------------------+--------------+
| Exchange | Account | Trades | Final Amount |
+----------+-----------+-----------------------------+--------------+
| binance | testuser1 | 98.47 ETH -> 0.00005 BTC | 0 ETH |
| huobi | testuser1 | | 1.56 ETH |
|~~~~~~~~~~|~~~~~~~~~~~|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~~~~~~~~~~~~~~|
| huobi | testuser2 | 0.45609 EUR -> 98.47 ETH | 0 ETH |
| | | 1509.45 ETH -> 0.000612 BTC | |
+----------+-----------+-----------------------------+--------------+
)";
expectStr(kExpected);
}
Expand Down
158 changes: 124 additions & 34 deletions src/tech/include/simpletable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,24 @@ namespace cct {
/// Simple, lightweight and fast table with dynamic number of columns.
/// No checks are made about the number of columns for each Row, it's up to client's responsibility to make sure they
/// match.
/// Multi line rows are *not* supported.
/// The SimpleTable is made up of 'Row's, themselves made up of 'Cell's, themselves made of 'CellLine's.
/// SimpleTable, Row, Cell behave like standard C++ vector-like containers, with support of most common methods.
/// The first Row is constructed like any other, but it will print an additional line separator to appear like a header.
/// No line separator will be placed between two single line only cells.
/// However, multi line Rows ('Row' containing at least a 'Cell' with several 'CellLine's) will have line separators
/// before and after them.
/// It is also possible to force a line separator between any row in the print. For that, you can insert the special Row
/// Row::kDivider at the desired place in the SimpleTable.
/// See the unit test to have an overview of its usage and the look and feel of the print.
class SimpleTable {
public:
class Row;
class Cell;

/// Cell in a SimpleTable.
/// Cell in a SimpleTable on a single line.
/// Can currently hold only 4 types of values: a string, a string_view, a int64_t and a bool.
class Cell {
class CellLine {
public:
using IntegralType = int64_t;
#ifdef CCT_MSVC
// folly::string does not support operator<< correctly with alignments with MSVC. Hence we use std::string
// in SimpleTable to guarantee correct alignment of formatted table. Referenced in this issue:
Expand All @@ -40,55 +48,130 @@ class SimpleTable {
#else
using string_type = string;
#endif
using value_type = std::variant<string_type, std::string_view, IntegralType, bool>;
using value_type = std::variant<std::string_view, string_type, int64_t, uint64_t, bool>;
using size_type = uint32_t;

explicit Cell(std::string_view sv = std::string_view()) : _data(sv) {}
CellLine() noexcept = default;

explicit CellLine(std::string_view sv) : _data(sv) {}

explicit Cell(const char *cstr) : _data(std::string_view(cstr)) {}
explicit CellLine(const char *cstr) : _data(std::string_view(cstr)) {}

#ifdef CCT_MSVC
explicit Cell(const string &v) : _data(std::string(v.data(), v.size())) {}
explicit CellLine(const string &v) : _data(string_type(v.data(), v.size())) {}
#else
explicit Cell(const string_type &str) : _data(str) {}
explicit CellLine(const string_type &str) : _data(str) {}

explicit Cell(string_type &&str) : _data(std::move(str)) {}
explicit CellLine(string_type &&str) : _data(std::move(str)) {}
#endif

explicit Cell(std::integral auto val) : _data(val) {}
explicit CellLine(std::integral auto val) : _data(val) {}

size_type size() const noexcept;
// Number of chars of this single line cell value.
size_type width() const noexcept;

void swap(Cell &rhs) noexcept { _data.swap(rhs._data); }
void swap(CellLine &rhs) noexcept { _data.swap(rhs._data); }

using trivially_relocatable = is_trivially_relocatable<string_type>::type;

bool operator==(const Cell &) const noexcept = default;
std::strong_ordering operator<=>(const CellLine &) const noexcept = default;

friend std::ostream &operator<<(std::ostream &os, const CellLine &singleLineCell);

private:
value_type _data;
};

class Cell {
public:
using value_type = CellLine;
using size_type = uint32_t;

private:
using CellLineVector = SmallVector<value_type, 1>;

public:
using iterator = CellLineVector::iterator;
using const_iterator = CellLineVector::const_iterator;

Cell() noexcept = default;

/// Implicit constructor of a Cell from a CellLine.
Cell(CellLine singleLineCell) { _singleLineCells.push_back(std::move(singleLineCell)); }

/// Creates a new Row with given list of cells.
template <class... Args>
explicit Cell(Args &&...singleLineCells) {
([&](auto &&input) { _singleLineCells.emplace_back(std::forward<decltype(input)>(input)); }(
std::forward<Args>(singleLineCells)),
...);
}

iterator begin() noexcept { return _singleLineCells.begin(); }
const_iterator begin() const noexcept { return _singleLineCells.begin(); }

iterator end() noexcept { return _singleLineCells.end(); }
const_iterator end() const noexcept { return _singleLineCells.end(); }

value_type &front() { return _singleLineCells.front(); }
const value_type &front() const { return _singleLineCells.front(); }

value_type &back() { return _singleLineCells.back(); }
const value_type &back() const { return _singleLineCells.back(); }

void push_back(const value_type &cell) { _singleLineCells.push_back(cell); }
void push_back(value_type &&cell) { _singleLineCells.push_back(std::move(cell)); }

template <class... Args>
value_type &emplace_back(Args &&...args) {
return _singleLineCells.emplace_back(std::forward<Args &&>(args)...);
}

size_type size() const noexcept { return _singleLineCells.size(); }

size_type width() const noexcept;

value_type &operator[](size_type cellPos) { return _singleLineCells[cellPos]; }
const value_type &operator[](size_type cellPos) const { return _singleLineCells[cellPos]; }

void reserve(size_type sz) { _singleLineCells.reserve(sz); }

void swap(Cell &rhs) noexcept { _singleLineCells.swap(rhs._singleLineCells); }

using trivially_relocatable = is_trivially_relocatable<CellLineVector>::type;

std::strong_ordering operator<=>(const Cell &) const noexcept = default;

private:
friend class Row;

void print(std::ostream &os, size_type maxCellWidth) const;
void print(std::ostream &os, size_type linePos, size_type maxCellWidth) const;

value_type _data;
CellLineVector _singleLineCells;
};

/// Row in a SimpleTable.
class Row {
public:
using value_type = Cell;
using size_type = uint32_t;
using iterator = vector<value_type>::iterator;
using const_iterator = vector<value_type>::const_iterator;

private:
using CellVector = vector<value_type>;

public:
using iterator = CellVector::iterator;
using const_iterator = CellVector::const_iterator;

static const Row kDivider;

Row() noexcept = default;

/// Creates a new Row with given list of cells.
template <class... Args>
explicit Row(Args &&...args) {
// Usage of C++17 fold expressions to make it possible to set a Row directly from a variadic input arguments
([&](auto &&input) { _cells.emplace_back(std::forward<decltype(input)>(input)); }(std::forward<Args>(args)), ...);
explicit Row(Args &&...cells) {
([&](auto &&input) { _cells.emplace_back(std::forward<decltype(input)>(input)); }(std::forward<Args>(cells)),
...);
}

iterator begin() noexcept { return _cells.begin(); }
Expand All @@ -103,8 +186,8 @@ class SimpleTable {
value_type &back() { return _cells.back(); }
const value_type &back() const { return _cells.back(); }

void push_back(const Cell &cell) { _cells.push_back(cell); }
void push_back(Cell &&cell) { _cells.push_back(std::move(cell)); }
void push_back(const value_type &cell) { _cells.push_back(cell); }
void push_back(value_type &&cell) { _cells.push_back(std::move(cell)); }

template <class... Args>
value_type &emplace_back(Args &&...args) {
Expand All @@ -113,30 +196,38 @@ class SimpleTable {

size_type size() const noexcept { return _cells.size(); }

bool isMultiLine() const noexcept;

bool isDivider() const noexcept { return _cells.empty(); }

void reserve(size_type sz) { _cells.reserve(sz); }

value_type &operator[](size_type cellPos) { return _cells[cellPos]; }
const value_type &operator[](size_type cellPos) const { return _cells[cellPos]; }

using trivially_relocatable = is_trivially_relocatable<vector<value_type>>::type;
void swap(Row &rhs) noexcept { _cells.swap(rhs._cells); }

bool operator==(const Row &) const noexcept = default;
using trivially_relocatable = is_trivially_relocatable<CellVector>::type;

std::strong_ordering operator<=>(const Row &) const noexcept = default;

private:
friend class SimpleTable;
friend std::ostream &operator<<(std::ostream &, const SimpleTable &);

void print(std::ostream &os, std::span<const uint16_t> maxWidthPerColumn) const;

vector<value_type> _cells;
CellVector _cells;
};

using value_type = Row;
using size_type = uint32_t;
using iterator = vector<Row>::iterator;
using const_iterator = vector<Row>::const_iterator;

private:
using RowVector = vector<value_type>;

public:
using iterator = RowVector::iterator;
using const_iterator = RowVector::const_iterator;

SimpleTable() noexcept = default;

Expand Down Expand Up @@ -165,21 +256,20 @@ class SimpleTable {
size_type size() const noexcept { return _rows.size(); }
bool empty() const noexcept { return _rows.empty(); }

value_type &operator[](size_type rowPos) { return _rows[rowPos]; }
const value_type &operator[](size_type rowPos) const { return _rows[rowPos]; }

void reserve(size_type sz) { _rows.reserve(sz); }

friend std::ostream &operator<<(std::ostream &os, const SimpleTable &t);
friend std::ostream &operator<<(std::ostream &os, const SimpleTable &table);

using trivially_relocatable = is_trivially_relocatable<vector<Row>>::type;
using trivially_relocatable = is_trivially_relocatable<RowVector>::type;

private:
using MaxWidthPerColumnVector = SmallVector<uint16_t, 8>;

static Cell::string_type ComputeLineSep(std::span<const uint16_t> maxWidthPerColumnVector);

MaxWidthPerColumnVector computeMaxWidthPerColumn() const;

vector<Row> _rows;
RowVector _rows;
};
} // namespace cct
Loading

0 comments on commit 218365d

Please sign in to comment.