tabular handler, based on the prototype code v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sun, 09 Sep 2018 02:00:00 +0200
branchv_0
changeset 5 911ec74cce33
parent 4 d13b0b5969aa
child 6 fc1e746e26a5
tabular handler, based on the prototype code
nbproject/configurations.xml
nbproject/project.xml
relpipe-out-tabular.cpp
src/TabularPrefetchingHandler.h
--- 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 @@
     <logicalFolder name="HeaderFiles"
                    displayName="Header Files"
                    projectFiles="true">
+      <itemPath>src/TabularPrefetchingHandler.h</itemPath>
     </logicalFolder>
     <logicalFolder name="ResourceFiles"
                    displayName="Resource Files"
@@ -47,6 +48,8 @@
       </compileType>
       <item path="relpipe-out-tabular.cpp" ex="false" tool="1" flavor2="0">
       </item>
+      <item path="src/TabularPrefetchingHandler.h" ex="false" tool="3" flavor2="0">
+      </item>
     </conf>
     <conf name="Release" type="1">
       <toolsSet>
@@ -76,6 +79,8 @@
       </compileType>
       <item path="relpipe-out-tabular.cpp" ex="false" tool="1" flavor2="0">
       </item>
+      <item path="src/TabularPrefetchingHandler.h" ex="false" tool="3" flavor2="0">
+      </item>
     </conf>
   </confs>
 </configurationDescriptor>
--- 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 @@
             <name>relpipe-out-tabular.cpp</name>
             <c-extensions/>
             <cpp-extensions>cpp</cpp-extensions>
-            <header-extensions/>
+            <header-extensions>h</header-extensions>
             <sourceEncoding>UTF-8</sourceEncoding>
             <make-dep-projects/>
             <sourceRootList/>
--- 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 <relpipe/reader/RelationalReader.h>
 #include <relpipe/reader/RelpipeReaderException.h>
 
+#include "src/TabularPrefetchingHandler.h"
 
 using namespace relpipe::cli;
 using namespace relpipe::reader;
-
-class DemoHandler : public handlers::RelationalReaderStringHadler {
-
-	void startRelation(string_t name, std::vector<std::pair<string_t, TypeId> > 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<RelationalReader> reader(Factory::create(std::cin));
-		DemoHandler handler;
+		TabularPrefetchingHandler handler(std::cout);
 		reader->addHandler(&handler);
 		reader->process();
 
--- /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();
+	}
+
+};
+
+}
+}
+}