# HG changeset patch # User František Kučera # Date 1536451200 -7200 # Node ID 911ec74cce33e36f59ff7bf7a2b3a0a4f38578be # Parent d13b0b5969aabc89e1fd0d9d35742e96b7a3a099 tabular handler, based on the prototype code diff -r d13b0b5969aa -r 911ec74cce33 nbproject/configurations.xml --- a/nbproject/configurations.xml Sat Sep 08 19:44:51 2018 +0200 +++ b/nbproject/configurations.xml Sun Sep 09 02:00:00 2018 +0200 @@ -4,6 +4,7 @@ + src/TabularPrefetchingHandler.h + + @@ -76,6 +79,8 @@ + + diff -r d13b0b5969aa -r 911ec74cce33 nbproject/project.xml --- a/nbproject/project.xml Sat Sep 08 19:44:51 2018 +0200 +++ b/nbproject/project.xml Sun Sep 09 02:00:00 2018 +0200 @@ -6,7 +6,7 @@ relpipe-out-tabular.cpp cpp - + h UTF-8 diff -r d13b0b5969aa -r 911ec74cce33 relpipe-out-tabular.cpp --- a/relpipe-out-tabular.cpp Sat Sep 08 19:44:51 2018 +0200 +++ b/relpipe-out-tabular.cpp Sun Sep 09 02:00:00 2018 +0200 @@ -7,25 +7,11 @@ #include #include +#include "src/TabularPrefetchingHandler.h" using namespace relpipe::cli; using namespace relpipe::reader; - -class DemoHandler : public handlers::RelationalReaderStringHadler { - - void startRelation(string_t name, std::vector > attributes) override { - std::wcout << L"start relation: " << name << std::endl << std::flush; - for (int i = 0; i < attributes.size(); i++) { - std::wcout << L"\tcolumn: " << attributes[i].first << L" / " << (int) attributes[i].second << std::endl << std::flush; - } - } - - void attribute(const string_t& value) override { - std::wcout << L"attribute: " << value << std::endl << std::flush; - } - - -}; +using namespace relpipe::out::tabular; int main(int argc, char** argv) { CLI cli(argc, argv); @@ -42,7 +28,7 @@ // } std::shared_ptr reader(Factory::create(std::cin)); - DemoHandler handler; + TabularPrefetchingHandler handler(std::cout); reader->addHandler(&handler); reader->process(); diff -r d13b0b5969aa -r 911ec74cce33 src/TabularPrefetchingHandler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/TabularPrefetchingHandler.h Sun Sep 09 02:00:00 2018 +0200 @@ -0,0 +1,187 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace relpipe { +namespace out { +namespace tabular { + +using namespace relpipe::reader; + +class TabularPrefetchingHandler : public handlers::RelationalReaderStringHadler { +private: + std::wstring_convert> convertor; // TODO: support also other encodings. + const char* INDENTATION = " "; + const char* ESC_BRIGHT = "\u001b[1m"; + const char* ESC_RED = "\u001b[31m"; + const char* ESC_GREEN = "\u001b[32m"; + const char* ESC_YELLOW = "\u001b[33m"; + const char* ESC_CYAN = "\u001b[36m"; + const char* ESC_RESET = "\u001b[0m"; + + const char* ESC_HEADER = ESC_BRIGHT; + const char* ESC_BORDER = ESC_GREEN; + const char* ESC_VALUE = ESC_CYAN; + const char* ESC_REPLACEMENT = ESC_RED; + + const char* INDENT = " "; // table indent from the left + + std::ostream &output; + + std::vector columnTypes; + std::vector columnTypeCodes; + std::vector columnNames; + std::vector columnWidths; + std::vector values; // all values are saved here and processed at the end of the relation + integer_t columnCount = 0; + + const string_t colorizeReplacement(const string_t &replacement, const char* valueColor) { + return convertor.from_bytes(ESC_RESET) + convertor.from_bytes(ESC_REPLACEMENT) + replacement + convertor.from_bytes(ESC_RESET) + convertor.from_bytes(valueColor); + } + + /** + * Sanitizes whitespace that could broke table layout. + * + * TODO: sanitize also escape sequences and emoji (resp. properly measure their width) + * + * @param value original value + * @param color value foreground color + * @return value with replaced whitespaces + */ + const string_t formatValue(const string_t &value, const char* color) { + std::wstringstream result; + + result << convertor.from_bytes(color); + + for (auto & ch : value) { + switch (ch) { + case L'\n': result << colorizeReplacement(L"↲", color); + break; + case L'\r': result << colorizeReplacement(L"⏎", color); + break; + case L'\t': result << colorizeReplacement(L"↹", color); + break; + case L' ': result << colorizeReplacement(L"⎵", color); + break; + default: result << ch; + } + } + + result << convertor.from_bytes(ESC_RESET); + + return result.str(); + } + + void printHorizontalLine(const string_t &left, const string_t &middle, const string_t &right) { + const string_t bar = L"─"; + // TODO: support also ASCII nostalgia: + // border = border.replaceAll("─", "-"); + // border = border.replaceAll("│", "|"); + // border = border.replaceAll("[╭┬╮├┼┤╰┴╯]", "+"); + + output << INDENT << ESC_BORDER; + output << convertor.to_bytes(left); + for (size_t c = 0; c < columnCount; c++) { + integer_t width = columnWidths[c]; + for (integer_t w = 0; w < (width + 2); w++) output << convertor.to_bytes(bar); // 2 = left and right padding of the value + if (c < (columnCount - 1)) output << convertor.to_bytes(middle); + } + output << convertor.to_bytes(right); + output << ESC_RESET << std::endl; + + } + + void printCachedData() { + // Compute column widths and paddings: + vector paddings(columnCount); + for (size_t i = 0; i < columnCount; i++) { + string_t typeCode = columnTypeCodes[i]; + string_t columnName = columnNames[i]; + integer_t minWidth = columnName.size() + typeCode.size() + 3; // 3 = " ()" in "columnName (typeCode)" + columnWidths[i] = max(columnWidths[i], minWidth); + paddings[i] = columnWidths[i] - minWidth; + } + + printHorizontalLine(L"╭", L"┬", L"╮"); + + // Print column headers: + output << INDENT << ESC_BORDER << "│" << ESC_RESET; + for (size_t i = 0; i < columnCount; i++) { + output << " " << convertor.to_bytes(formatValue(columnNames[i], ESC_HEADER)); + for (integer_t p = 0; p < paddings[i]; p++) { + output << " "; + } + output << " (" << convertor.to_bytes(columnTypeCodes[i]) << ")"; + output << ESC_BORDER << " │" << ESC_RESET; + } + output << std::endl; + printHorizontalLine(L"├", L"┼", L"┤"); + + // Print particular rows: + for (size_t i = 0; i < values.size(); i++) { + integer_t columnIndex = i % columnCount; + if (columnIndex == 0) output << INDENT << ESC_BORDER << "│" << ESC_RESET; + string_t stringValue = values[i]; + integer_t padding = columnWidths[columnIndex] - stringValue.size(); + boolean_t alignRight = columnTypes[columnIndex] == TypeId::BOOLEAN || columnTypes[columnIndex] == TypeId::INTEGER; + + if (alignRight) for (integer_t p = 0; p < padding; p++) output << " "; + output << " " << convertor.to_bytes(formatValue(stringValue, ESC_VALUE)); + if (!alignRight) for (integer_t p = 0; p < padding; p++) output << " "; + + output << ESC_BORDER << " │" << ESC_RESET; + if (columnIndex == (columnCount - 1)) output << std::endl; + } + printHorizontalLine(L"╰", L"┴", L"╯"); + integer_t recordCount = values.size() / columnCount; + output << ESC_YELLOW << "Record count: " << ESC_RESET << recordCount << std::endl; + + values.clear(); + } + +public: + + TabularPrefetchingHandler(std::ostream& output) : output(output) { + } + + void startRelation(string_t name, std::vector > attributes) override { + if (columnCount) printCachedData(); + + output << ESC_RED << convertor.to_bytes(name) << ":" << ESC_RESET << endl; + columnCount = attributes.size(); + columnTypes.resize(columnCount); + columnTypeCodes.resize(columnCount); + columnNames.resize(columnCount); + columnWidths.resize(columnCount, 0); + for (int i = 0; i < attributes.size(); i++) { + columnNames[i] = attributes[i].first; + columnTypes[i] = attributes[i].second; + columnTypeCodes[i] = L"TODO"; // TODO: type codes/names + } + } + + void attribute(const string_t& value) override { + integer_t i = values.size() % columnCount; + values.push_back(value); + columnWidths[i] = max(columnWidths[i], value.length()); + } + + void endOfPipe() { + if (columnCount) printCachedData(); + } + +}; + +} +} +}