--- /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 <string>
+#include <vector>
+#include <iostream>
+#include <sstream>
+#include <locale>
+#include <codecvt>
+#include <regex>
+
+#include <relpipe/reader/typedefs.h>
+#include <relpipe/reader/TypeId.h>
+#include <relpipe/reader/handlers/RelationalReaderStringHandler.h>
+
+namespace relpipe {
+namespace out {
+namespace tabular {
+
+using namespace relpipe::reader;
+
+class TabularPrefetchingHandler : public handlers::RelationalReaderStringHadler {
+private:
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> 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<TypeId> columnTypes;
+ std::vector<string_t> columnTypeCodes;
+ std::vector<string_t> columnNames;
+ std::vector<integer_t> columnWidths;
+ std::vector<string_t> 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<integer_t> 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<std::pair<string_t, TypeId> > 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();
+ }
+
+};
+
+}
+}
+}