/* * Created by Brett on 26/01/23. * Licensed under GNU General Public License V3.0 * See LICENSE file for license detail */ #ifndef BLT_TESTS_FORMAT_H #define BLT_TESTS_FORMAT_H #include #include #include #include #include #include #include #include "memory.h" #include "vector.h" #include namespace blt::string { template static inline std::string withGrouping(T t, size_t group = 3) { // TODO: all this + make it faster static_assert(std::is_arithmetic_v && "Must be arithmetic type!"); auto str = std::to_string(t); std::string ret; ret.reserve(str.size()); blt::size_t count = 0; auto start_pos = static_cast(str.size() - 1); for (auto i = start_pos; i >= 0; i--) { if (str[i] == '.') { start_pos = i - 1; break; } } for (auto i = static_cast(str.size() - 1); i > start_pos; i--) ret += str[i]; for (auto i = start_pos; i >= 0; i--) { ret += str[i]; if (count++ % (group) == group - 1 && i != 0) ret += ','; } std::reverse(ret.begin(), ret.end()); return ret; } } namespace blt { class byte_convert_t { public: enum class byte_t : blt::u64 { Bytes = 1, Kilobyte = 1024, Megabyte = 1024 * 1024, Gigabyte = 1024 * 1024 * 1024, }; explicit byte_convert_t(blt::u64 bytes): bytes(bytes) {} byte_convert_t(blt::u64 bytes, byte_t convert_type): bytes(bytes), type(convert_type) { converted = static_cast(bytes) / static_cast(static_cast(type)); } byte_convert_t& convert_to_nearest_type() { if (bytes > 1073741824) { // gigabyte type = byte_t::Gigabyte; } else if (bytes > 1048576) { // megabyte type = byte_t::Megabyte; } else if (bytes > 1024) { // kilobyte type = byte_t::Kilobyte; } else { type = byte_t::Bytes; } converted = static_cast(bytes) / static_cast(static_cast(type)); return *this; } [[nodiscard]] std::string_view type_string() const { switch (type) { case byte_t::Bytes: return "b"; case byte_t::Kilobyte: return "KiB"; case byte_t::Megabyte: return "MiB"; case byte_t::Gigabyte: return "GiB"; } return "NotPossible!"; } [[nodiscard]] double getConverted() const { return converted; } template typename round_function = round_up_t> [[nodiscard]] double getConvertedRound() const { round_function convert{}; return convert(converted); } template typename round_function = round_up_t> [[nodiscard]] std::string to_pretty_string() const { auto str = string::withGrouping(getConvertedRound(), 3); str += type_string(); return str; } [[nodiscard]] blt::u64 getBytes() const { return bytes; } [[nodiscard]] byte_t getType() const { return type; } private: blt::u64 bytes = 0; byte_t type = byte_t::Bytes; double converted = 0; }; } namespace blt::string { // negative decimal places will not round. template static inline std::string fromBytes(blt::u64 bytes) { byte_convert_t convert(bytes); convert.convert_to_nearest_type(); return std::to_string(convert.getConvertedRound()) + convert.type_string(); } // TODO: update table formatter to use these! /** * creates a line starting/ending with ending char filled between with spacing char * @param totalLength total length to generate * @param endingChar beginning and ending char to use * @param spacingChar char to use for spacing * @return a generated line string eg: +--------+ */ std::string createLine(size_t totalLength, char endingChar, char spacingChar); /** * Create a padding string using length and spacing char * @param length length of string to generate * @param spacing char to use to generate padding * @return a padding string */ std::string createPadding(size_t length, char spacing = ' '); // TODO template the padding functions: /** * Ensure that string str has expected length, pad after the string otherwise. * @param str string to pad * @param expectedLength expected length of the string. * @return a space padded string */ static inline std::string postPadWithSpaces(const std::string& str, size_t expectedLength) { auto currentSize = (int) (str.length() - 1); if ((int) expectedLength - currentSize <= 0) return str; auto paddedString = str; paddedString += createPadding(expectedLength - currentSize); return paddedString; } /** * Ensure that string str has expected length, pad before the string otherwise. * @param str string to pad * @param expectedLength expected length of the string. * @return a space padded string */ static inline std::string prePadWithSpaces(const std::string& str, size_t expectedLength) { auto currentSize = str.length() - 1; auto paddedString = std::string(); paddedString += createPadding(expectedLength - currentSize); paddedString += str; return paddedString; } struct utf8_string { char* characters; unsigned int size; }; // taken from java, adapted for c++. static inline utf8_string createUTFString(const std::string& str) { const auto strlen = (unsigned int) str.size(); unsigned int utflen = strlen; for (unsigned int i = 0; i < strlen; i++) { unsigned char c = str[i]; if (c >= 0x80 || c == 0) utflen += 1; } if (utflen > 65535 || /* overflow */ utflen < strlen) throw "UTF Error"; utf8_string chars{}; chars.size = utflen + 2; chars.characters = new char[chars.size]; int count = 0; chars.characters[count++] = (char) ((utflen >> 8) & 0xFF); chars.characters[count++] = (char) ((utflen >> 0) & 0xFF); unsigned int i = 0; for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII int c = (unsigned char) str[i]; if (c >= 0x80 || c == 0) break; chars.characters[count++] = (char) c; } for (; i < strlen; i++) { int c = (unsigned char) str[i]; if (c < 0x80 && c != 0) { chars.characters[count++] = (char) c; } else if (c >= 0x800) { chars.characters[count++] = (char) (0xE0 | ((c >> 12) & 0x0F)); chars.characters[count++] = (char) (0x80 | ((c >> 6) & 0x3F)); chars.characters[count++] = (char) (0x80 | ((c >> 0) & 0x3F)); } else { chars.characters[count++] = (char) (0xC0 | ((c >> 6) & 0x1F)); chars.characters[count++] = (char) (0x80 | ((c >> 0) & 0x3F)); } } return chars; } static inline std::string getStringFromUTF8(const utf8_string& str) { int utflen = (int) str.size; int c, char2, char3; int count = 0; int chararr_count = 0; auto chararr = new char[utflen + 1]; while (count < utflen) { c = (int) str.characters[count] & 0xff; if (c > 127) break; count++; chararr[chararr_count++] = (char) c; } while (count < utflen) { c = (int) str.characters[count] & 0xff; switch (c >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: /* 0xxxxxxx*/ count++; chararr[chararr_count++] = (char) c; break; case 12: case 13: /* 110x xxxx 10xx xxxx*/ count += 2; if (count > utflen) throw "malformed input: partial character at end"; char2 = (int) str.characters[count - 1]; if ((char2 & 0xC0) != 0x80) throw "malformed input around byte " + std::to_string(count); chararr[chararr_count++] = (char) (((c & 0x1F) << 6) | (char2 & 0x3F)); break; case 14: /* 1110 xxxx 10xx xxxx 10xx xxxx */ count += 3; if (count > utflen) throw "malformed input: partial character at end"; char2 = (int) str.characters[count - 2]; char3 = (int) str.characters[count - 1]; if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) throw "malformed input around byte " + std::to_string(count - 1); chararr[chararr_count++] = (char) (((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0)); break; default: /* 10xx xxxx, 1111 xxxx */ throw "malformed input around byte " + std::to_string(count); break; } } chararr[utflen] = '\0'; std::string strs{chararr}; delete[] chararr; return strs; } struct TableColumn { std::string columnName; size_t maxColumnLength = 0; explicit TableColumn(std::string columnName): columnName(std::move(columnName)) {} }; struct TableRow { std::vector rowValues; }; class TableFormatter { private: std::string m_tableName; int m_columnPadding; int m_maxColumnWidth; std::vector columns; std::vector rows; std::string generateTopSeparator(size_t size); std::string generateColumnHeader(); std::string generateSeparator(size_t size); void updateMaxColumnLengths(); [[nodiscard]] inline size_t columnSize(const TableColumn& column) const { return column.columnName.size() + m_columnPadding * 2; } public: explicit TableFormatter(std::string tableName = "", int columnPadding = 2, int maxColumnWidth = 500): m_tableName(std::move(tableName)), m_columnPadding(columnPadding), m_maxColumnWidth(maxColumnWidth) {} inline void addColumn(const TableColumn& column) { columns.push_back(column); } inline void addColumn(std::string column) { columns.emplace_back(std::move(column)); } inline void addRow(TableRow row) { if (row.rowValues.size() > columns.size()) throw "Cannot insert more rows than columns!\n"; // ensure every row populates every column. This is important as the table generator assumes that all rows are complete! if (row.rowValues.size() < columns.size()) for (auto i = row.rowValues.size(); i < columns.size(); i++) row.rowValues.emplace_back(" "); rows.push_back(std::move(row)); } inline void addRow(const std::initializer_list& values) { TableRow row; for (const auto& value : values) row.rowValues.push_back(value); addRow(row); } std::vector createTable(bool top = false, bool bottom = false); }; static inline constexpr size_t MAX_CHILDREN = 16; /** * Structure which provides variables for the internal spacing of ASCII objects */ struct ascii_padding_format { size_t horizontalPadding = 1; size_t verticalPadding = 1; }; struct tree_format { ascii_padding_format boxFormat; int verticalPadding; int horizontalPadding; // should we remove preceding spaces? bool collapse = true; tree_format(): boxFormat{}, verticalPadding(1), horizontalPadding(4) {} }; struct tree_node { std::string data; std::string title; blt::static_vector children; }; class ascii_data { private: char* data_; size_t width_ = 0; size_t height_ = 0; size_t size_ = 0; public: ascii_data(size_t width, size_t height): data_(new char[width * height]), width_(width), height_(height), size_(width * height) { // he he he memset(data_, ' ', width * height); } ascii_data(const ascii_data& copy) = delete; ascii_data(ascii_data&& move) noexcept { data_ = move.data_; width_ = move.width_; height_ = move.height_; size_ = move.size_; } ascii_data& operator=(const ascii_data& copy) = delete; ascii_data& operator=(ascii_data&& move) noexcept { delete[] data_; data_ = move.data_; width_ = move.width_; height_ = move.height_; size_ = move.size_; return *this; }; inline char& at(size_t x, size_t y) { return data_[x * height_ + y]; } inline char* data() { return data_; } std::vector toVec() { std::vector vec; for (size_t j = 0; j < height(); j++) { std::string line; line.reserve(width()); for (size_t i = 0; i < width(); i++) { line += at(i, j); } vec.push_back(std::move(line)); } return vec; } [[nodiscard]] char* data() const { return data_; } [[nodiscard]] inline size_t width() const { return width_; } [[nodiscard]] inline size_t height() const { return height_; } [[nodiscard]] inline size_t size() const { return size_; } ~ascii_data() { delete[] data_; } }; class ascii_object { protected: size_t width_; size_t height_; public: ascii_object(size_t width, size_t height): width_(width), height_(height) {} /** * @return Internal width of the ascii object. This does not include the bordering box */ [[nodiscard]] inline size_t width() const { return width_; } /** * @return Internal height of the ascii object. This does not include the border. */ [[nodiscard]] inline size_t height() const { return height_; } /** * @return full height of the ascii box, includes the expected border around the box */ [[nodiscard]] inline size_t full_height() const { return height_ + 2; } /** * @return full width of the ascii box, includes the expected border around the box. */ [[nodiscard]] inline size_t full_width() const { return width_ + 2; } }; class ascii_box : public ascii_object { public: std::string_view data; const ascii_padding_format& format; public: explicit ascii_box(std::string_view data, const ascii_padding_format& format = {}): ascii_object(data.length() + (format.horizontalPadding * 2), 1 + (format.verticalPadding * 2)), data(data), format(format) {} }; class ascii_titled_box : public ascii_object { public: std::string_view title; std::string_view data; const ascii_padding_format& format; public: ascii_titled_box(std::string_view title, std::string_view data, const ascii_padding_format& format = {}): ascii_object(std::max(data.length(), title.length()) + (format.horizontalPadding * 2), 3 + (format.verticalPadding * 2)), title(title), data(data), format(format) {} }; typedef std::variant box_type; class ascii_boxes { private: std::vector boxes_; size_t width_ = 1; size_t height_ = 0; enum class STORED_TYPE { NONE, BOX, BOX_WITH_TITLE, BOTH }; STORED_TYPE type = STORED_TYPE::NONE; public: ascii_boxes() = default; ascii_boxes(std::initializer_list boxes) { for (const auto& b : boxes) push_back(b); } void push_back(box_type&& box); inline void push_back(const box_type& box) { push_back(box_type(box)); } inline std::vector& boxes() { return boxes_; } [[nodiscard]] inline size_t width() const { return width_; } [[nodiscard]] inline size_t height() const { return height_; } [[nodiscard]] inline bool is_mixed() const { return type == STORED_TYPE::BOTH; } }; typedef std::variant box_container; ascii_data constructBox(const box_container& box, bool normalize_mixed_types = true); class BinaryTreeFormatter { public: // data classes struct Node { std::string data; Node* left = nullptr; Node* right = nullptr; explicit Node(std::string data): data(std::move(data)) {} Node* with(Node* l, Node* r = nullptr) { left = l; right = r; return this; } ~Node() { delete left; delete right; } }; private: tree_format format; Node* root = nullptr; public: explicit BinaryTreeFormatter(std::string rootData, const tree_format& format = {}): format(format), root(new Node(std::move(rootData))) {} std::vector generateBox(Node* node) const; inline Node* getRoot() { return root; } std::vector construct(); BinaryTreeFormatter() { delete root; } }; } #endif //BLT_TESTS_FORMAT_H