From 6778e3b87cc9a6f5d195a2c80e5b499e3d94558b Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 27 Jan 2019 02:09:50 +0100 Subject: Add binds to emoji parsing, refactor --- .gdb_history | 6 ++ include/dchat/Cache.hpp | 13 +-- include/dchat/IncomingMessage.hpp | 22 ++++ include/dchat/MessageComposer.hpp | 32 ------ include/dchat/OutgoingMessage.hpp | 33 ++++++ include/dchat/Range.hpp | 15 +++ include/dchat/Storage.hpp | 24 +++++ src/Cache.cpp | 94 +---------------- src/IncomingMessage.cpp | 160 +++++++++++++++++++++++++++++ src/MessageComposer.cpp | 160 ----------------------------- src/OutgoingMessage.cpp | 136 +++++++++++++++++++++++++ src/Room.cpp | 4 +- src/Storage.cpp | 105 +++++++++++++++++++ tests/main.cpp | 206 ++++++++++++++++++++++++++++++++------ 14 files changed, 681 insertions(+), 329 deletions(-) create mode 100644 .gdb_history create mode 100644 include/dchat/IncomingMessage.hpp delete mode 100644 include/dchat/MessageComposer.hpp create mode 100644 include/dchat/OutgoingMessage.hpp create mode 100644 include/dchat/Range.hpp create mode 100644 include/dchat/Storage.hpp create mode 100644 src/IncomingMessage.cpp delete mode 100644 src/MessageComposer.cpp create mode 100644 src/OutgoingMessage.cpp create mode 100644 src/Storage.cpp diff --git a/.gdb_history b/.gdb_history new file mode 100644 index 0000000..1ef1965 --- /dev/null +++ b/.gdb_history @@ -0,0 +1,6 @@ +start +b src/OutgoingMessage.cpp:55 +info b +quit +start +b src/OutgoingMessage.cpp:55 diff --git a/include/dchat/Cache.hpp b/include/dchat/Cache.hpp index 4d37b54..8d3f28e 100644 --- a/include/dchat/Cache.hpp +++ b/include/dchat/Cache.hpp @@ -59,12 +59,11 @@ namespace dchat i64 lastAccessed; }; - using LoadBindsCallbackFunc = std::function; // @fileContent contains data allocated with new[], deallocate it with delete[] fileContent.data; // Returned gif should be allocated with @new using CreateGifFunc = std::function; using CreateStaticImageFunc = std::function; - + class Cache { DISABLE_COPY(Cache) @@ -74,16 +73,6 @@ namespace dchat Cache(CreateGifFunc createGifFunc, CreateStaticImageFunc createStaticImageFunc); ~Cache(); - // Creates directory if it doesn't exist (recursively). Throws boost exception on failure - static boost::filesystem::path getDchatDir(); - - // Creates directory if it doesn't exist (recursively). Throws boost exception on failure - static boost::filesystem::path getImagesDir(); - - // @callbackFunc can't be nullptr - static void loadBindsFromFile(LoadBindsCallbackFunc callbackFunc); - static void replaceBindsInFile(const std::unordered_map &binds); - // Get cached content or download it. // Default download file limit is 12MB // Returns ContentByUrlResult describing texture status. diff --git a/include/dchat/IncomingMessage.hpp b/include/dchat/IncomingMessage.hpp new file mode 100644 index 0000000..b20e4d1 --- /dev/null +++ b/include/dchat/IncomingMessage.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "types.hpp" +#include "Range.hpp" +#include + +namespace dchat +{ + struct IncomingMessagePart + { + enum class Type + { + TEXT, + EMOJI + }; + + Type type; + Range textRange; + }; + + void parseIncomingMessage(const char *text, usize size, std::function callbackFunc); +} \ No newline at end of file diff --git a/include/dchat/MessageComposer.hpp b/include/dchat/MessageComposer.hpp deleted file mode 100644 index b4b55c2..0000000 --- a/include/dchat/MessageComposer.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "types.hpp" -#include - -namespace dchat -{ - struct Range - { - int start; - int end; - - int length() const - { - return end - start; - } - }; - - struct MessagePart - { - enum class Type - { - TEXT, - EMOJI - }; - - Type type; - Range textRange; - }; - - void compose(const char *text, usize size, std::function callbackFunc); -} \ No newline at end of file diff --git a/include/dchat/OutgoingMessage.hpp b/include/dchat/OutgoingMessage.hpp new file mode 100644 index 0000000..c3e8e84 --- /dev/null +++ b/include/dchat/OutgoingMessage.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "types.hpp" +#include "Range.hpp" +#include +#include +#include + +namespace dchat +{ + struct OutgoingMessagePart + { + enum class Type + { + TEXT, + EMOJI + }; + + Type type; + union + { + Range textRange; + const std::string *emoji; + }; + + OutgoingMessagePart(Type _type, Range _textRange) : type(_type), textRange(_textRange) {} + OutgoingMessagePart(Type _type, const std::string *_emoji) : type(_type), emoji(_emoji) {} + }; + + using EmojiBindMap = std::unordered_map; + + void parseOutgoingMessage(const char *text, usize size, const EmojiBindMap &emojiBindMap, std::function callbackFunc); +} \ No newline at end of file diff --git a/include/dchat/Range.hpp b/include/dchat/Range.hpp new file mode 100644 index 0000000..a3a9581 --- /dev/null +++ b/include/dchat/Range.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace dchat +{ + struct Range + { + int start; + int end; + + int length() const + { + return end - start; + } + }; +} \ No newline at end of file diff --git a/include/dchat/Storage.hpp b/include/dchat/Storage.hpp new file mode 100644 index 0000000..4f3dfa2 --- /dev/null +++ b/include/dchat/Storage.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include + +namespace dchat +{ + // Throws runtime exception on failure + boost::filesystem::path getHomeDir(); + + // Creates directory if it doesn't exist (recursively). Throws boost exception on failure + boost::filesystem::path getDchatDir(); + + // Creates directory if it doesn't exist (recursively). Throws boost exception on failure + boost::filesystem::path getImagesDir(); + + using LoadBindsCallbackFunc = std::function; + + // @callbackFunc can't be nullptr + void loadBindsFromFile(LoadBindsCallbackFunc callbackFunc); + void replaceBindsInFile(const std::unordered_map &binds); +} \ No newline at end of file diff --git a/src/Cache.cpp b/src/Cache.cpp index 84bee97..0d5dd05 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -2,6 +2,7 @@ #include "../include/env.hpp" #include "../include/dchat/FileUtil.hpp" #include "../include/dchat/Gif.hpp" +#include "../include/dchat/Storage.hpp" #include #include #include @@ -11,7 +12,6 @@ #include #if OS_FAMILY == OS_FAMILY_POSIX -#include #define toNativeString(str) str #else #include @@ -32,98 +32,6 @@ using namespace TinyProcessLib; namespace dchat { const i64 CONTENT_NOT_VISIBLE_AGE_MS = 30000; // Delete content from cache after a specified amount of time if the content is not visible on the screen - - static boost::filesystem::path getHomeDir() - { - #if OS_FAMILY == OS_FAMILY_POSIX - const char *homeDir = getenv("HOME"); - if(!homeDir) - { - passwd *pw = getpwuid(getuid()); - homeDir = pw->pw_dir; - } - return boost::filesystem::path(homeDir); - #elif OS_FAMILY == OS_FAMILY_WINDOWS - BOOL ret; - HANDLE hToken; - std::wstring homeDir; - DWORD homeDirLen = MAX_PATH; - homeDir.resize(homeDirLen); - - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken)) - throw std::runtime_error("Failed to open process token"); - - if (!GetUserProfileDirectory(hToken, &homeDir[0], &homeDirLen)) - { - CloseHandle(hToken); - throw std::runtime_error("Failed to get home directory"); - } - - CloseHandle(hToken); - homeDir.resize(wcslen(homeDir.c_str())); - return boost::filesystem::path(homeDir); - #endif - } - - boost::filesystem::path Cache::getDchatDir() - { - boost::filesystem::path dchatHomeDir = getHomeDir() / ".local" / "share" / "dchat"; - boost::filesystem::create_directories(dchatHomeDir); - return dchatHomeDir; - } - - boost::filesystem::path Cache::getImagesDir() - { - boost::filesystem::path imagesDir = getDchatDir() / "images"; - boost::filesystem::create_directories(imagesDir); - return imagesDir; - } - - void Cache::loadBindsFromFile(LoadBindsCallbackFunc callbackFunc) - { - assert(callbackFunc); - StringView fileContent; - try - { - fileContent = getFileContent(getDchatDir() / "binds"); - sibs::SafeDeserializer deserializer((const u8*)fileContent.data, fileContent.size); - - while(!deserializer.empty()) - { - u8 keySize = deserializer.extract(); - string key; - key.resize(keySize); - deserializer.extract((u8*)&key[0], keySize); - - u8 valueSize = deserializer.extract(); - string value; - value.resize(valueSize); - deserializer.extract((u8*)&value[0], valueSize); - - callbackFunc(key, value); - } - } - catch(FileException &e) - { - fprintf(stderr, "Failed to read binds from file, reason: %s\n", e.what()); - } - - delete[] fileContent.data; - } - - void Cache::replaceBindsInFile(const unordered_map &binds) - { - sibs::SafeSerializer serializer; - for(auto &it : binds) - { - serializer.add((u8)it.first.size()); - serializer.add((const u8*)it.first.data(), it.first.size()); - - serializer.add((u8)it.second.size()); - serializer.add((const u8*)it.second.data(), it.second.size()); - } - fileReplace(getDchatDir() / "binds", StringView((const char*)serializer.getBuffer().data(), serializer.getBuffer().size())); - } static bool downscaleImage(const boost::filesystem::path &filepath, void *data, const int size, const int newWidth, const int newHeight) { diff --git a/src/IncomingMessage.cpp b/src/IncomingMessage.cpp new file mode 100644 index 0000000..e003a22 --- /dev/null +++ b/src/IncomingMessage.cpp @@ -0,0 +1,160 @@ +#include "../include/dchat/IncomingMessage.hpp" +#include +#include + +namespace dchat +{ + enum class Token + { + NONE, + END_OF_FILE, + + TEXT, + TYPE + }; + + struct Tokenizer + { + Tokenizer(const char *_text, usize _size) + { + assert(_text); + text = _text; + size = _size; + index = 0; + identifierRange = { 0, 0 }; + typeRange = { 0, 0 }; + typeDataRange = { 0, 0 }; + } + + enum class EnclosedType + { + TEXT, + DATA + }; + + EnclosedType parseEnclosedData(char endSymbol) + { + bool foundEndOfType = false; + + while(index < size) + { + char c = getChar(); + ++index; + if(c == endSymbol) + { + foundEndOfType = true; + break; + } + } + + if(!foundEndOfType) + return EnclosedType::TEXT; + + return EnclosedType::DATA; + } + + Token next() + { + if(index >= size) + return Token::END_OF_FILE; + + char c = getChar(); + if(c == '[') + { + usize start = index; + ++index; + if(parseEnclosedData(']') == EnclosedType::TEXT) + { + identifierRange.start = start; + identifierRange.end = index; + return Token::TEXT; + } + + if(index == size || getChar() != '(') + { + identifierRange.start = start; + identifierRange.end = index; + return Token::TEXT; + } + + typeRange.start = start + 1; + typeRange.end = index - 1; + typeDataRange.start = index + 1; + + ++index; + switch(parseEnclosedData(')')) + { + case EnclosedType::TEXT: + { + identifierRange.start = start; + identifierRange.end = index; + return Token::TEXT; + } + case EnclosedType::DATA: + { + typeDataRange.end = index - 1; + return Token::TYPE; + } + } + } + else + { + identifierRange.start = index; + ++index; + while(index < size) + { + c = getChar(); + if(c == '[') + break; + ++index; + } + identifierRange.end = index; + return Token::TEXT; + } + + assert(false); + return Token::NONE; + } + + char getChar() const + { + assert(index < size); + return text[index]; + } + + const char *text; + usize size; + usize index; + + Range identifierRange; + Range typeRange; + Range typeDataRange; + }; + + void parseIncomingMessage(const char *text, usize size, std::function callbackFunc) + { + Tokenizer tokenizer(text, size); + Token token = tokenizer.next(); + while(token != Token::END_OF_FILE) + { + if(token == Token::TEXT) + { + callbackFunc(IncomingMessagePart { IncomingMessagePart::Type::TEXT, tokenizer.identifierRange }); + token = tokenizer.next(); + } + else if(token == Token::TYPE) + { + if(tokenizer.typeRange.length() == 5 && memcmp(text + tokenizer.typeRange.start, "emoji", 5) == 0) + { + callbackFunc(IncomingMessagePart { IncomingMessagePart::Type::EMOJI, tokenizer.typeDataRange }); + } + else + { + Range typeToTextRange = { tokenizer.typeRange.start - 1, tokenizer.typeDataRange.end + 1 }; + callbackFunc(IncomingMessagePart{ IncomingMessagePart::Type::TEXT, typeToTextRange }); + } + token = tokenizer.next(); + } + } + } +} \ No newline at end of file diff --git a/src/MessageComposer.cpp b/src/MessageComposer.cpp deleted file mode 100644 index 71fa62e..0000000 --- a/src/MessageComposer.cpp +++ /dev/null @@ -1,160 +0,0 @@ -#include "../include/dchat/MessageComposer.hpp" -#include -#include - -namespace dchat -{ - enum class Token - { - NONE, - END_OF_FILE, - - TEXT, - TYPE - }; - - struct Tokenizer - { - Tokenizer(const char *_text, usize _size) - { - assert(_text); - text = _text; - size = _size; - index = 0; - identifierRange = { 0, 0 }; - typeRange = { 0, 0 }; - typeDataRange = { 0, 0 }; - } - - enum class EnclosedType - { - TEXT, - DATA - }; - - EnclosedType parseEnclosedData(char endSymbol) - { - bool foundEndOfType = false; - - while(index < size) - { - char c = getChar(); - ++index; - if(c == endSymbol) - { - foundEndOfType = true; - break; - } - } - - if(!foundEndOfType) - return EnclosedType::TEXT; - - return EnclosedType::DATA; - } - - Token next() - { - if(index >= size) - return Token::END_OF_FILE; - - char c = getChar(); - if(c == '[') - { - usize start = index; - ++index; - if(parseEnclosedData(']') == EnclosedType::TEXT) - { - identifierRange.start = start; - identifierRange.end = index; - return Token::TEXT; - } - - if(index == size || getChar() != '(') - { - identifierRange.start = start; - identifierRange.end = index; - return Token::TEXT; - } - - typeRange.start = start + 1; - typeRange.end = index - 1; - typeDataRange.start = index + 1; - - ++index; - switch(parseEnclosedData(')')) - { - case EnclosedType::TEXT: - { - identifierRange.start = start; - identifierRange.end = index; - return Token::TEXT; - } - case EnclosedType::DATA: - { - typeDataRange.end = index - 1; - return Token::TYPE; - } - } - } - else - { - identifierRange.start = index; - ++index; - while(index < size) - { - c = getChar(); - if(c == '[') - break; - ++index; - } - identifierRange.end = index; - return Token::TEXT; - } - - assert(false); - return Token::NONE; - } - - char getChar() const - { - assert(index < size); - return text[index]; - } - - const char *text; - usize size; - usize index; - - Range identifierRange; - Range typeRange; - Range typeDataRange; - }; - - void compose(const char *text, usize size, std::function callbackFunc) - { - Tokenizer tokenizer(text, size); - Token token = tokenizer.next(); - while(token != Token::END_OF_FILE) - { - if(token == Token::TEXT) - { - callbackFunc(MessagePart { MessagePart::Type::TEXT, tokenizer.identifierRange }); - token = tokenizer.next(); - } - else if(token == Token::TYPE) - { - if(tokenizer.typeRange.length() == 5 && memcmp(text + tokenizer.typeRange.start, "emoji", 5) == 0) - { - callbackFunc(MessagePart { MessagePart::Type::EMOJI, tokenizer.typeDataRange }); - } - else - { - Range typeToTextRange = { tokenizer.typeRange.start - 1, tokenizer.typeDataRange.end + 1 }; - callbackFunc(MessagePart{ MessagePart::Type::TEXT, typeToTextRange }); - } - token = tokenizer.next(); - } - } - } -} \ No newline at end of file diff --git a/src/OutgoingMessage.cpp b/src/OutgoingMessage.cpp new file mode 100644 index 0000000..e774940 --- /dev/null +++ b/src/OutgoingMessage.cpp @@ -0,0 +1,136 @@ +#include "../include/dchat/OutgoingMessage.hpp" +#include + +namespace dchat +{ + namespace OutgoingMessage + { + enum class Token + { + NONE, + END_OF_FILE, + + TEXT, + BIND + }; + + struct Tokenizer + { + Tokenizer(const char *_text, usize _size, const EmojiBindMap *_emojiBindMap) + { + assert(_text); + assert(_emojiBindMap); + text = _text; + size = _size; + index = 0; + emoji = nullptr; + emojiBindMap = _emojiBindMap; + textRange = { 0, 0 }; + } + + // returns -1 if character can't be found in the string + usize find(const char *str, usize size, char c) const + { + for(usize i = 0; i < size; ++i) + { + if(str[i] == c) + return i; + } + return -1; + } + + // Return nullptr if bind is not found + const std::string* getBindEmoji(const char *text, usize size) const + { + // TODO: unecessary conversion to string, convert emojibindmap to const char* and uses custom hasher + auto it = emojiBindMap->find(std::string(text, size)); + if(it != emojiBindMap->end()) + return &it->second; + return nullptr; + } + + Token next() + { + if(index >= size) + return Token::END_OF_FILE; + + char c = getChar(); + usize start = index; + ++index; + if(c == ':') + { + while(index < size) + { + c = getChar(); + ++index; + if(c == ':') + { + usize end = index - 1; + const std::string *_emoji = getBindEmoji(text + start + 1, end - (start + 1)); + if(_emoji) + { + emoji = _emoji; + return Token::BIND; + } + break; + } + } + + textRange.start = start; + textRange.end = index; + return Token::TEXT; + } + else + { + while(index < size) + { + c = getChar(); + if(c == ':') + break; + ++index; + } + + textRange.start = start; + textRange.end = index; + return Token::TEXT; + } + + assert(false); + return Token::NONE; + } + + char getChar() const + { + assert(index < size); + return text[index]; + } + + const char *text; + usize size; + usize index; + const std::string *emoji; + const EmojiBindMap *emojiBindMap; + + Range textRange; + }; + } + + void parseOutgoingMessage(const char *text, usize size, const EmojiBindMap &emojiBindMap, std::function callbackFunc) + { + OutgoingMessage::Tokenizer tokenizer(text, size, &emojiBindMap); + OutgoingMessage::Token token = tokenizer.next(); + while(token != OutgoingMessage::Token::END_OF_FILE) + { + if(token == OutgoingMessage::Token::TEXT) + { + callbackFunc(OutgoingMessagePart { OutgoingMessagePart::Type::TEXT, tokenizer.textRange }); + token = tokenizer.next(); + } + else if(token == OutgoingMessage::Token::BIND) + { + callbackFunc(OutgoingMessagePart { OutgoingMessagePart::Type::EMOJI, tokenizer.emoji }); + token = tokenizer.next(); + } + } + } +} \ No newline at end of file diff --git a/src/Room.cpp b/src/Room.cpp index c7135da..b526833 100644 --- a/src/Room.cpp +++ b/src/Room.cpp @@ -1,5 +1,5 @@ #include "../include/dchat/Room.hpp" -#include "../include/dchat/Cache.hpp" +#include "../include/dchat/Storage.hpp" #include "../include/dchat/FileUtil.hpp" #include "../include/dchat/RoomDataType.hpp" #include @@ -203,7 +203,7 @@ namespace dchat databaseCallbackFuncs.createNodeCallbackFunc = std::bind(&Rooms::createNodeCallbackFunc, this, std::placeholders::_1); databaseCallbackFuncs.addNodeCallbackFunc = std::bind(&Rooms::addNodeCallbackFunc, this, std::placeholders::_1); databaseCallbackFuncs.addUserCallbackFunc = std::bind(&Rooms::addUserCallbackFunc, this, std::placeholders::_1); - database = odhtdb::Database::connect(address, port, Cache::getDchatDir(), databaseCallbackFuncs).get(); + database = odhtdb::Database::connect(address, port, getDchatDir(), databaseCallbackFuncs).get(); } void Rooms::createNodeCallbackFunc(const odhtdb::DatabaseCreateNodeRequest &request) diff --git a/src/Storage.cpp b/src/Storage.cpp new file mode 100644 index 0000000..a0cc891 --- /dev/null +++ b/src/Storage.cpp @@ -0,0 +1,105 @@ +#include "../include/dchat/Storage.hpp" +#include "../include/dchat/env.hpp" +#include "../include/dchat/FileUtil.hpp" +#include +#include +#include + +#if OS_FAMILY == OS_FAMILY_POSIX +#include +#endif + +namespace dchat +{ + boost::filesystem::path getHomeDir() + { + #if OS_FAMILY == OS_FAMILY_POSIX + const char *homeDir = getenv("HOME"); + if(!homeDir) + { + passwd *pw = getpwuid(getuid()); + homeDir = pw->pw_dir; + } + return boost::filesystem::path(homeDir); + #elif OS_FAMILY == OS_FAMILY_WINDOWS + BOOL ret; + HANDLE hToken; + std::wstring homeDir; + DWORD homeDirLen = MAX_PATH; + homeDir.resize(homeDirLen); + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken)) + throw std::runtime_error("Failed to open process token"); + + if (!GetUserProfileDirectory(hToken, &homeDir[0], &homeDirLen)) + { + CloseHandle(hToken); + throw std::runtime_error("Failed to get home directory"); + } + + CloseHandle(hToken); + homeDir.resize(wcslen(homeDir.c_str())); + return boost::filesystem::path(homeDir); + #endif + } + + boost::filesystem::path getDchatDir() + { + boost::filesystem::path dchatHomeDir = getHomeDir() / ".local" / "share" / "dchat"; + boost::filesystem::create_directories(dchatHomeDir); + return dchatHomeDir; + } + + boost::filesystem::path getImagesDir() + { + boost::filesystem::path imagesDir = getDchatDir() / "images"; + boost::filesystem::create_directories(imagesDir); + return imagesDir; + } + + void loadBindsFromFile(LoadBindsCallbackFunc callbackFunc) + { + assert(callbackFunc); + StringView fileContent; + try + { + fileContent = getFileContent(getDchatDir() / "binds"); + sibs::SafeDeserializer deserializer((const u8*)fileContent.data, fileContent.size); + + while(!deserializer.empty()) + { + u8 keySize = deserializer.extract(); + std::string key; + key.resize(keySize); + deserializer.extract((u8*)&key[0], keySize); + + u8 valueSize = deserializer.extract(); + std::string value; + value.resize(valueSize); + deserializer.extract((u8*)&value[0], valueSize); + + callbackFunc(key, value); + } + } + catch(FileException &e) + { + fprintf(stderr, "Failed to read binds from file, reason: %s\n", e.what()); + } + + delete[] fileContent.data; + } + + void replaceBindsInFile(const std::unordered_map &binds) + { + sibs::SafeSerializer serializer; + for(auto &it : binds) + { + serializer.add((u8)it.first.size()); + serializer.add((const u8*)it.first.data(), it.first.size()); + + serializer.add((u8)it.second.size()); + serializer.add((const u8*)it.second.data(), it.second.size()); + } + fileReplace(getDchatDir() / "binds", StringView((const char*)serializer.getBuffer().data(), serializer.getBuffer().size())); + } +} \ No newline at end of file diff --git a/tests/main.cpp b/tests/main.cpp index 9f33a05..64a0523 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include #include @@ -14,48 +15,48 @@ static void requireEqualValues(int a, int b, const char *file, int line) } #define REQUIRE_EQUAL(a, b) do { requireEqualValues((a), (b), __FILE__, __LINE__); } while(0) -int main(int argc, char **argv) +static void testIncomingMessage() { - std::vector expect = + std::vector expect = { - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, - dchat::MessagePart { dchat::MessagePart::Type::EMOJI, dchat::Range{ 12, 16 } }, - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 17, 21 } } + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::EMOJI, dchat::Range{ 12, 16 } }, + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 17, 21 } } }; - std::vector expectText = + std::vector expectText = { "abc ", "cool", " def" }; - int index = 0; + dchat::usize index = 0; const char *str = "abc [emoji](cool) def"; dchat::usize strSize = strlen(str); - auto compareFunc = [&str, &index, &expect, &expectText](dchat::MessagePart messagePart) + auto compareFunc = [&str, &index, &expect, &expectText](dchat::IncomingMessagePart incomingMessagePart) { REQUIRE(index < expect.size()); - REQUIRE_EQUAL((int)messagePart.type, (int)expect[index].type); - REQUIRE_EQUAL(messagePart.textRange.start, expect[index].textRange.start); - REQUIRE_EQUAL(messagePart.textRange.end, expect[index].textRange.end); - int length = messagePart.textRange.end - messagePart.textRange.start; - if(strncmp(str + messagePart.textRange.start, expectText[index], length) != 0) + REQUIRE_EQUAL((int)incomingMessagePart.type, (int)expect[index].type); + REQUIRE_EQUAL(incomingMessagePart.textRange.start, expect[index].textRange.start); + REQUIRE_EQUAL(incomingMessagePart.textRange.end, expect[index].textRange.end); + int length = incomingMessagePart.textRange.end - incomingMessagePart.textRange.start; + if(length != expectText[index].size() || strncmp(str + incomingMessagePart.textRange.start, expectText[index].c_str(), length) != 0) { - fprintf(stderr, "Assert failed: |%.*s| == |%.*s|\n", length, str + messagePart.textRange.start, length, expectText[index]); + fprintf(stderr, "Assert failed: |%.*s| == |%.*s|\n", length, str + incomingMessagePart.textRange.start, expectText[index].size(), expectText[index].c_str()); exit(EXIT_FAILURE); } ++index; }; - dchat::compose(str, strSize, compareFunc); + dchat::parseIncomingMessage(str, strSize, compareFunc); { expect = { - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 4, 20 } } + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 4, 20 } } }; expectText = @@ -67,15 +68,15 @@ int main(int argc, char **argv) index = 0; str = "abc [emoji](cool def"; strSize = strlen(str); - dchat::compose(str, strSize, compareFunc); + dchat::parseIncomingMessage(str, strSize, compareFunc); } { expect = { - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 4, 11 } }, - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 11, 22 } } + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 4, 11 } }, + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 11, 22 } } }; expectText = @@ -88,15 +89,15 @@ int main(int argc, char **argv) index = 0; str = "abc [emoji] (cool def)"; strSize = strlen(str); - dchat::compose(str, strSize, compareFunc); + dchat::parseIncomingMessage(str, strSize, compareFunc); } { expect = { - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 4, 20 } }, - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 20, 24 } } + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 4, 20 } }, + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 20, 24 } } }; expectText = @@ -109,14 +110,14 @@ int main(int argc, char **argv) index = 0; str = "abc [notemoji](cool) def"; strSize = strlen(str); - dchat::compose(str, strSize, compareFunc); + dchat::parseIncomingMessage(str, strSize, compareFunc); } { expect = { - dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 0, 12 } }, - dchat::MessagePart { dchat::MessagePart::Type::EMOJI, dchat::Range{ 20, 71 } } + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::TEXT, dchat::Range{ 0, 12 } }, + dchat::IncomingMessagePart { dchat::IncomingMessagePart::Type::EMOJI, dchat::Range{ 20, 71 } } }; expectText = @@ -128,8 +129,153 @@ int main(int argc, char **argv) index = 0; str = "Hello world [emoji](https://discordemoji.com/assets/emoji/PeepoHide.png)"; strSize = strlen(str); - dchat::compose(str, strSize, compareFunc); + dchat::parseIncomingMessage(str, strSize, compareFunc); + } +} + +static void testOutgoingMessage() +{ + const dchat::EmojiBindMap binds = { + { "thinking", "https://thinking" }, + { "sad", "https://sad" } + }; + + std::vector expect; + std::vector expectText; + + dchat::usize index = 0; + const char *str = nullptr; + dchat::usize size = 0; + auto callbackFunc = [&expect, &expectText, &index, &str](dchat::OutgoingMessagePart outgoingMessagePart) + { + REQUIRE(index < expect.size()); + REQUIRE_EQUAL((int)outgoingMessagePart.type, (int)expect[index].type); + switch(outgoingMessagePart.type) + { + case dchat::OutgoingMessagePart::Type::TEXT: + { + REQUIRE_EQUAL(outgoingMessagePart.textRange.start, expect[index].textRange.start); + REQUIRE_EQUAL(outgoingMessagePart.textRange.end, expect[index].textRange.end); + int length = outgoingMessagePart.textRange.end - outgoingMessagePart.textRange.start; + if(length != expectText[index].size() || strncmp(str + outgoingMessagePart.textRange.start, expectText[index].c_str(), length) != 0) + { + fprintf(stderr, "Assert failed: |%.*s| == |%.*s|\n", length, str + outgoingMessagePart.textRange.start, expectText[index].size(), expectText[index].c_str()); + exit(EXIT_FAILURE); + } + break; + } + case dchat::OutgoingMessagePart::Type::EMOJI: + { + if(*outgoingMessagePart.emoji != expectText[index]) + { + fprintf(stderr, "Assert failed: |%s| == |%s|\n", outgoingMessagePart.emoji->c_str(), expectText[index].c_str()); + exit(EXIT_FAILURE); + } + break; + } + } + ++index; + }; + + { + expect = + { + dchat::OutgoingMessagePart { dchat::OutgoingMessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, + dchat::OutgoingMessagePart { dchat::OutgoingMessagePart::Type::EMOJI, nullptr }, + dchat::OutgoingMessagePart { dchat::OutgoingMessagePart::Type::TEXT, dchat::Range{ 14, 18 } } + }; + + expectText = + { + "aaa ", + "https://thinking", + " bbb" + }; + + index = 0; + str = "aaa :thinking: bbb"; + size = strlen(str); + dchat::parseOutgoingMessage(str, size, binds, callbackFunc); + } + + { + expect = + { + dchat::OutgoingMessagePart { dchat::OutgoingMessagePart::Type::EMOJI, nullptr }, + dchat::OutgoingMessagePart { dchat::OutgoingMessagePart::Type::TEXT, dchat::Range{ 5, 9 } } + }; + + expectText = + { + "https://sad", + " bbb" + }; + + index = 0; + str = ":sad: bbb"; + size = strlen(str); + dchat::parseOutgoingMessage(str, size, binds, callbackFunc); + } + + { + expect = + { + dchat::OutgoingMessagePart { dchat::OutgoingMessagePart::Type::EMOJI, nullptr }, + }; + + expectText = + { + "https://sad", + }; + + index = 0; + str = ":sad:"; + size = strlen(str); + dchat::parseOutgoingMessage(str, size, binds, callbackFunc); + } + + { + expect = + { + dchat::OutgoingMessagePart { dchat::OutgoingMessagePart::Type::TEXT, dchat::Range{ 0, 9 } } + }; + + expectText = + { + ":nothing:", + }; + + index = 0; + str = ":nothing:"; + size = strlen(str); + dchat::parseOutgoingMessage(str, size, binds, callbackFunc); } + { + expect = + { + dchat::OutgoingMessagePart { dchat::OutgoingMessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, + dchat::OutgoingMessagePart { dchat::OutgoingMessagePart::Type::TEXT, dchat::Range{ 4, 13 } }, + dchat::OutgoingMessagePart { dchat::OutgoingMessagePart::Type::TEXT, dchat::Range{ 13, 17 } } + }; + + expectText = + { + "aaa ", + ":nothing:", + " bbb" + }; + + index = 0; + str = "aaa :nothing: bbb"; + size = strlen(str); + dchat::parseOutgoingMessage(str, size, binds, callbackFunc); + } +} + +int main(int argc, char **argv) +{ + testIncomingMessage(); + testOutgoingMessage(); return 0; } \ No newline at end of file -- cgit v1.2.3