#include "tabulate.hpp" #include "wcwidth.hpp" #include "../num/safe_op.hpp" #include "../string/reinterpret.hpp" #include #include #define WCWIDTH ::yycc::carton::wcwidth #define REINTERPRET ::yycc::string::reinterpret #define SAFEOP ::yycc::num::safe_op namespace yycc::carton::tabulate { #pragma region Tabulate Width TabulateWidth::TabulateWidth(size_t n) : widths(n, 0u) {} TabulateWidth::~TabulateWidth() {} size_t TabulateWidth::get_column_count() const { return widths.size(); } size_t TabulateWidth::get_column_width(size_t column_index) const { return widths.at(column_index); } void TabulateWidth::update_column_width(size_t column_index, size_t new_size) { auto& width = widths.at(column_index); width = std::max(width, new_size); } void TabulateWidth::clear() { std::fill(widths.begin(), widths.end(), 0u); } #pragma endregion #pragma region Tabulate Cell TabulateCell::TabulateCell(const std::u8string_view& text) : text(text), text_width(WCWIDTH::wcswidth(text).value_or(0u)) {} TabulateCell::~TabulateCell() {} const std::u8string& TabulateCell::get_text() const { return text; } size_t TabulateCell::get_text_width() const { return text_width; } #pragma endregion #pragma region Tabulate /// @brief Default separator literal for Tabulate. static constexpr char8_t SEPARATOR_BAR[] = u8"---"; /// @brief A stupid size_t ZERO literal to trigger template type deduce for std::views::iota. static constexpr size_t ZERO = 0; Tabulate::Tabulate(size_t n) : n(n), header_display(true), bar_display(true), prefix_string(), rows_widths(n), header_widths(n), header(n, TabulateCell(u8"")), bar(SEPARATOR_BAR), rows() {} Tabulate::~Tabulate() {} void Tabulate::print(std::ostream& dst) const { // Get column count auto n = this->get_column_count(); // Create width recorder for final printing // according to whether we show table header and separator bar. auto widths = this->rows_widths; if (this->header_display) { for (auto index : std::views::iota(ZERO, n)) { widths.update_column_width(index, this->header_widths.get_column_width(index)); } } if (this->bar_display) { auto bar_width = this->bar.get_text_width(); for (auto index : std::views::iota(ZERO, n)) { widths.update_column_width(index, bar_width); } } // Get the maximum space char count to build a string filled with spaces, // for the convenient about following printing. size_t max_space = 1; for (auto index : std::views::iota(ZERO, n)) { max_space = std::max(max_space, widths.get_column_width(index)); } std::u8string spaces(max_space, u8' '); std::u8string_view spaces_view(spaces); // Print table // Define a convenient macro #define CVT(data) REINTERPRET::as_ordinary_view(data) // Show header if (this->header_display) { dst << CVT(this->prefix_string); for (const auto [index, item] : std::views::enumerate(header)) { auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), item.get_text_width()); dst << CVT(item.get_text()) << CVT(spaces_view.substr(0, diff)) << " "; } dst << std::endl; } // Show bar if (this->bar_display) { dst << CVT(this->prefix_string); auto bar_width = this->bar.get_text_width(); for (auto index : std::views::iota(ZERO, n)) { auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), bar_width); dst << CVT(this->bar.get_text()) << CVT(spaces_view.substr(0, diff)) << " "; } dst << std::endl; } // Show data for (const auto& row : this->rows) { dst << CVT(this->prefix_string); for (const auto [index, item] : std::views::enumerate(row)) { auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), item.get_text_width()); dst << CVT(item.get_text()) << CVT(spaces_view.substr(0, diff)) << " "; } dst << std::endl; } // Undef macro #undef CVT } size_t Tabulate::get_column_count() const { return this->n; } void Tabulate::show_header(bool show_header) { this->header_display = show_header; } void Tabulate::show_bar(bool show_bar) { this->bar_display = show_bar; } void Tabulate::set_prefix(const std::u8string_view& prefix) { this->prefix_string = prefix; } void Tabulate::set_header(std::initializer_list hdr) { // Check data size. if (hdr.size() != get_column_count()) { throw std::invalid_argument("the size of given header is not equal to column count"); } // Change header data and update header width recorder. header.clear(); header_widths.clear(); for (const auto [index, item] : std::views::enumerate(hdr)) { auto cell = header.emplace_back(item); header_widths.update_column_width(index, cell.get_text_width()); } } void Tabulate::set_bar(const std::u8string_view& bar) { this->bar = bar; } void Tabulate::add_row(std::initializer_list row) { // Check data size. if (row.size() != get_column_count()) { throw std::invalid_argument("the size of given row is not equal to column count"); } // Prepare inserted row, and update data width recorder. Row inserted_row; inserted_row.reserve(row.size()); for (const auto [index, item] : std::views::enumerate(row)) { auto cell = inserted_row.emplace_back(item); rows_widths.update_column_width(index, cell.get_text_width()); } // Insert row rows.emplace_back(std::move(inserted_row)); } void Tabulate::clear() { // Clear data and data width recorder. rows.clear(); rows_widths.clear(); } #pragma endregion } // namespace yycc::carton::tabulate