#include "tableprint.hpp" #include #include #include #include #include #include #include #include #include enum kTextColors { kTextColor_Default, kTextColor_Red, kTextColor_Green, kTextColor_Yellow, kTextColor_Blue, kTextColor_Magenta, kTextColor_Cyan, kTextColor_White, kTextColor_DarkGrey, kTextColor_DarkYellow, kTextColor_LightGrey }; struct coloredText { std::string text; kTextColors color; }; const std::map kReplacements = { {":tick:", {"+", kTextColor_Green}}, {":cross:", {"x", kTextColor_Red}}, {":warning:", {"!", kTextColor_Yellow}}, {":info:", {"i", kTextColor_Blue}}, {":check:", {"+", kTextColor_Green}}, {":x:", {"x", kTextColor_Red}}, {":error:", {"!", kTextColor_Red}}, {":question:", {"?", kTextColor_DarkGrey}}, {":greytick:", {"+", kTextColor_LightGrey}}, {":greycross:", {"x", kTextColor_LightGrey}} }; // Helper function to get ANSI color code std::string get_color_code(kTextColors color) { switch (color) { case kTextColor_Red: return "\033[1;31m"; case kTextColor_Green: return "\033[1;32m"; case kTextColor_Yellow: return "\033[1;33m"; case kTextColor_Blue: return "\033[1;34m"; case kTextColor_Magenta: return "\033[1;35m"; case kTextColor_Cyan: return "\033[1;36m"; case kTextColor_White: return "\033[1;37m"; case kTextColor_DarkGrey: return "\033[90m"; case kTextColor_DarkYellow: return "\033[38;5;142m"; case kTextColor_LightGrey: return "\033[38;5;250m"; default: return "\033[0m"; } } int get_codepoints(const std::string& str) { int num_code_points = 0; for (char byte: str) { if((byte & 0xC0) != 0x80) { num_code_points++; } } return num_code_points; } int get_visible_length(const std::string& str) { int length = get_codepoints(str); size_t pos = 0; while (pos < str.length()) { for (const auto& [key, value] : kReplacements) { if (str.compare(pos, key.length(), key) == 0) { length = length - get_codepoints(key) + get_codepoints(value.text); pos += key.length(); break; } } pos++; } return length; } std::string process_cell(std::string str,std::string rowcolor) { std::string result; size_t pos = 0; while (pos < str.length()) { bool found_replacement = false; for (const auto& [key, value] : kReplacements) { if (str.compare(pos, key.length(), key) == 0) { found_replacement = true; result += get_color_code(value.color) + value.text + rowcolor; pos += key.length(); break; } } if (!found_replacement) { result += str[pos]; pos++; } } return result; } std::string width_print_centered(std::string str,int width, std::string rowcolor) { size_t padding = (width - get_visible_length(str)); size_t lpad = padding/2; size_t rpad = padding - lpad; std::ostringstream oss; oss << rowcolor << std::string(lpad, ' ') << process_cell(str, rowcolor) << std::string(rpad, ' ') << "\033[0m"; // std::cout << "str = "<1 ? 1 : 0); size_t rpad = padding - lpad; std::ostringstream oss; oss << rowcolor << std::string(lpad, ' ') << process_cell(str, rowcolor)<< std::string(rpad, ' ') << "\033[0m"; return oss.str(); } tableprint::tableprint(const std::string title, bool compact) : title(title), mCompact(compact) { // Set locale for wide character support std::setlocale(LC_ALL, ""); } tableprint::~tableprint() {} void tableprint::set_title(const std::string title) { this->title = title; } void tableprint::add_row(const std::vector& row) { std::vector trimmed_row; for (const auto& cell : row) { // Trim whitespace from start and end auto start = cell.find_first_not_of(" \t"); auto end = cell.find_last_not_of(" \t"); if (start == std::string::npos) { trimmed_row.push_back(""); } else { trimmed_row.push_back(cell.substr(start, end - start + 1)); } } rows.push_back(trimmed_row); } void tableprint::print() { if (rows.empty()) return; // Calculate column widths based on header text std::vector col_widths(rows[0].size(), 0); for (size_t i = 0; i < rows[0].size(); ++i) { col_widths[i] = rows[0][i].length(); } // Adjust widths for any cells with replacements for (size_t row_idx = 1; row_idx < rows.size(); ++row_idx) { const auto& row = rows[row_idx]; for (size_t i = 0; i < row.size(); ++i) { int l = get_visible_length(row[i]); if (l > col_widths[i]) col_widths[i] = l; } } // Debug output // std::cerr << "Column widths: "; // for (size_t width : col_widths) { // std::cerr << width << " "; // } // std::cerr << std::endl; // Calculate total table width size_t total_width = 0; for (size_t width : col_widths) { total_width += width + 2; // +2 for padding } total_width += col_widths.size() - 1; // Add space for vertical borders // Print title if it exists if (!title.empty()) { std::cout << "\033[90m"; // Dark grey color for borders std::cout << "+"; for (size_t i = 0; i < rows[0].size(); ++i) { std::cout << std::string(col_widths[i] + 2, '-'); if (i < rows[0].size() - 1) std::cout << "-"; } std::cout << "+" << std::endl; std::cout << "|"; // White color for title size_t title_width = 0; for (size_t width : col_widths) { title_width += width + 2; // +2 for padding } title_width += col_widths.size() - 1; // Add space for vertical borders std::cout << width_print_centered(title,title_width,"\033[1;37m"); std::cout << "\033[90m|" << std::endl; // Use └─┴─┘ for bottom of title box to connect with table std::cout << "+"; for (size_t i = 0; i < rows[0].size(); ++i) { std::cout << std::string(col_widths[i] + 2, '-'); if (i < rows[0].size() - 1) std::cout << "-"; } std::cout << "+" << std::endl; } else { // Print top border if no title std::cout << "\033[90m"; // Dark grey color for borders std::cout << "+"; for (size_t i = 0; i < rows[0].size(); ++i) { std::cout << std::string(col_widths[i] + 2, '-'); if (i < rows[0].size() - 1) std::cout << "+"; } std::cout << "+" << std::endl; } // Print header std::cout << "|"; for (size_t i = 0; i < rows[0].size(); ++i) { std::cout << width_print_centered(rows[0][i],col_widths[i]+2,"\033[1;36m"); if (i < rows[0].size() - 1) { std::cout << "\033[90m|\033[1;36m"; // Border color then back to cyan } else { std::cout << "\033[90m|"; // Just border color for last column } } std::cout << std::endl; // Print header separator if (!mCompact) { std::cout << "+"; for (size_t i = 0; i < rows[0].size(); ++i) { for (size_t j = 0; j < col_widths[i] + 2; ++j) { std::cout << "-"; } if (i < rows[0].size() - 1) std::cout << "+"; } std::cout << "+" << std::endl; } // Print rows for (size_t row_idx = 1; row_idx < rows.size(); ++row_idx) { const auto& row = rows[row_idx]; std::cout << "|"; for (size_t i = 0; i < row.size(); ++i) { // Set the appropriate color for the row std::string rowcolor = (row_idx % 2 == 1) ? "\033[38;5;142m" : "\033[38;5;250m"; std::cout << width_print_left(row[i],col_widths[i]+2,rowcolor); std::cout << "\033[90m" << "|"; } std::cout << std::endl; // Print row separator if not the last row if (row_idx < rows.size() - 1 && !mCompact) { std::cout << "+"; for (size_t i = 0; i < rows[0].size(); ++i) { for (size_t j = 0; j < col_widths[i] + 2; ++j) { std::cout << "-"; } if (i < rows[0].size() - 1) std::cout << "+"; } std::cout << "+" << std::endl; } } // Print bottom border std::cout << "+"; for (size_t i = 0; i < rows[0].size(); ++i) { for (size_t j = 0; j < col_widths[i] + 2; ++j) { std::cout << "-"; } if (i < rows[0].size() - 1) std::cout << "+"; } std::cout << "+" << "\033[0m" << std::endl; // Reset color }