From d52630ac5cd01bdad0915a33dd8b74ef342950aa Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Tue, 19 Aug 2025 20:53:51 +0800 Subject: [PATCH] feat: add tabulate but no test. --- src/CMakeLists.txt | 2 + src/yycc/carton/tabulate.cpp | 192 ++++++++++++++++++++++++++++ src/yycc/carton/tabulate.hpp | 198 +++++++++++++++++++++++++++++ testbench/CMakeLists.txt | 4 +- testbench/yycc/carton/tabulate.cpp | 13 ++ 5 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 src/yycc/carton/tabulate.cpp create mode 100644 src/yycc/carton/tabulate.hpp create mode 100644 testbench/yycc/carton/tabulate.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa0a549..0d19433 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,7 @@ PRIVATE yycc/carton/pycodec.cpp yycc/carton/termcolor.cpp yycc/carton/wcwidth.cpp + yycc/carton/tabulate.cpp ) target_sources(YYCCommonplace PUBLIC @@ -71,6 +72,7 @@ FILES yycc/carton/pycodec.hpp yycc/carton/termcolor.hpp yycc/carton/wcwidth.hpp + yycc/carton/tabulate.hpp ) # Setup header infomations target_include_directories(YYCCommonplace diff --git a/src/yycc/carton/tabulate.cpp b/src/yycc/carton/tabulate.cpp new file mode 100644 index 0000000..238b2cf --- /dev/null +++ b/src/yycc/carton/tabulate.cpp @@ -0,0 +1,192 @@ +#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 diff --git a/src/yycc/carton/tabulate.hpp b/src/yycc/carton/tabulate.hpp new file mode 100644 index 0000000..bd0481e --- /dev/null +++ b/src/yycc/carton/tabulate.hpp @@ -0,0 +1,198 @@ +#pragma once +#include "../macro/class_copy_move.hpp" +#include +#include +#include +#include +#include + +namespace yycc::carton::tabulate { + + /** + * @private + * @brief Assistant class recording column width. + */ + class TabulateWidth { + public: + /** + * @brief Create width recorder with given column count. + * @param[in] n Column count. + */ + TabulateWidth(size_t n); + ~TabulateWidth(); + YYCC_DEFAULT_COPY_MOVE(TabulateWidth) + + public: + /** + * @brief Get column count. + * @return Column count. + */ + size_t get_column_count() const; + /** + * @brief Get column width of given index. + * @param[in] column_index Column index for fetching width. + * @return Column width of given index. + */ + size_t get_column_width(size_t column_index) const; + /** + * @brief Update column width of given index with given value. + * @details The width of column will be updated to the maximum between given value and old value. + * @param[in] column_index Column index for updating width. + * @param[in] new_size New width value. + */ + void update_column_width(size_t column_index, size_t new_size); + /** + * @brief Clear all width data + * @details All width data will be reset to zero. + */ + void clear(); + + private: + std::vector widths; + }; + + /** + * @private + * @brief Assistant class holding table cell data. + * @details + * This class holds the data of table cell. + * Also make a cache for the width this cell's text occupied in console, + * to avoid duplicated calculation for occupied width. + */ + class TabulateCell { + public: + /** + * @brief Build cell with given text. + * @param[in] text Data of cell. + */ + TabulateCell(const std::u8string_view& text); + ~TabulateCell(); + YYCC_DEFAULT_COPY_MOVE(TabulateCell) + + public: + /** + * @brief Get the text of cell. + * @return The text of cell. + */ + const std::u8string& get_text() const; + /** + * @brief Get width this cell's text occupied in console. + * @return The width this cell occupied. + */ + size_t get_text_width() const; + + private: + /// @brief The data of cell. + std::u8string text; + /// @brief The width cache of this data occupied in console. + size_t text_width; + }; + + /** + * @private + * @brief The type representing one row of data in table. + */ + using Row = std::vector; + /** + * @private + * @brief The type representing row collection in table. + */ + using Rows = std::vector; + + /** + * @brief Main class of Tabulate + */ + class Tabulate { + public: + /** + * @brief Create Tabulate class with given column count. + * @details + * In default, the separator bar of table is 3 dash. + * Header and separator bar are also shown in default. + * @param[in] n Column count of table. + */ + Tabulate(size_t n); + ~Tabulate(); + YYCC_DELETE_COPY_MOVE(Tabulate) + + public: + /** + * @brief Print table into given stream. + * @details In default, stream is \c stdout. + * @param[in] dst The stream printed into. + */ + void print(std::ostream& dst = std::cout) const; + + /** + * @brief Get the column count of table. + * @return Column count of table. + */ + size_t get_column_count() const; + /** + * @brief Change whether show table header when printing. + * @param[in] show_header True for showing, otherwise false. + */ + void show_header(bool show_header); + /** + * @brief Change whether show separator bar when printing. + * @param[in] show_bar True for showing, otherwise false. + */ + void show_bar(bool show_bar); + /** + * @brief Modify the prefix string of table. + * @details + * The prefix string of table is the string + * which will be printed before each lines of output. + * It is usually used for indent. + * @param[in] prefix The prefix string. + */ + void set_prefix(const std::u8string_view& prefix); + /** + * @brief Modify the header of table. + * @param[in] hdr An initializer list holding header texts one by one. + * @exception std::invalid_argument The size of given header is mismatch with column count. + */ + void set_header(std::initializer_list hdr); + /** + * @brief Modify separator bar string of table. + * @param[in] bar New separator bar string. + */ + void set_bar(const std::u8string_view& bar); + /** + * @brief Add one data row into table. + * @param[in] row An initializer list holding row texts one by one. + * @exception std::invalid_argument The size of given header is mismatch with column count. + */ + void add_row(std::initializer_list row); + /** + * @brief Clear all data rows of table. + * @details Table header and separator bar will not be changed. + */ + void clear(); + + private: + + /// @brief The column count of table. + size_t n; + + /// @brief Whether showing table header. + bool header_display; + /// @brief Whether showing table separator bar between header and data. + bool bar_display; + /// @brief The prefix string presented in each lines of output table. + std::u8string prefix_string; + + /// @brief Width recorder for header. + TabulateWidth header_widths; + /// @brief Width recorder for data. + TabulateWidth rows_widths; + + /// @brief The header of table. + Row header; + /// @brief The separator bar of table. + TabulateCell bar; + /// @brief The data of table. + Rows rows; + }; + +} diff --git a/testbench/CMakeLists.txt b/testbench/CMakeLists.txt index a55e3b3..fa797eb 100644 --- a/testbench/CMakeLists.txt +++ b/testbench/CMakeLists.txt @@ -3,9 +3,10 @@ add_executable(YYCCTestbench "") # Setup testbench sources target_sources(YYCCTestbench PRIVATE + main.cpp + shared/literals.cpp - main.cpp yycc/macro/version_cmp.cpp yycc/macro/os_detector.cpp yycc/macro/compiler_detector.cpp @@ -35,6 +36,7 @@ PRIVATE yycc/carton/pycodec.cpp yycc/carton/termcolor.cpp yycc/carton/wcwidth.cpp + yycc/carton/tabulate.cpp ) target_sources(YYCCTestbench PRIVATE diff --git a/testbench/yycc/carton/tabulate.cpp b/testbench/yycc/carton/tabulate.cpp new file mode 100644 index 0000000..33596bd --- /dev/null +++ b/testbench/yycc/carton/tabulate.cpp @@ -0,0 +1,13 @@ +#include +#include +#include + +#define TABULATE ::yycc::carton::tabulate + +namespace yycctest::carton::tabulate { + + TEST(CartonTabulate, Main) { + + } + +}