Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] - Support of multi line cells in SimpleTable #529

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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