diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b109143..c93a7d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,11 @@ jobs: with: submodules: recursive token: ${{ secrets.PAT_TOKEN }} + - name: Install uuid-dev + run: | + sudo apt -y update; + sudo apt -y upgrade; + sudo apt -y install uuid-dev; - name: building libs run: | cd external; diff --git a/external/libs b/external/libs index 6c2febd..abd76a6 160000 --- a/external/libs +++ b/external/libs @@ -1 +1 @@ -Subproject commit 6c2febd1758e81cb7600b19c9ba4e7d3300b2e10 +Subproject commit abd76a69a024b31962bb06cec50918f4bca75f92 diff --git a/makefile b/makefile index 55003fc..d0ff4f4 100644 --- a/makefile +++ b/makefile @@ -43,7 +43,8 @@ log user inputbuffer office \ chatroom message chatroomserver packet \ agent agentclient agentserver sealedpacket \ chatroomclient interfaceserver interfaceclient command \ -permissions chat +permissions utils operand\ +chat UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) @@ -60,11 +61,16 @@ LIBRARIES = external/bin/openssl/libssl.a external/bin/openssl/libcrypto.a OPENSSL_INCLUDE_PATH = -Iexternal/bin/openssl/include endif -ifneq ($(CONFIG),test) # test +ifeq ($(CONFIG),release) # release +LIBRARIES += \ + external/bin/libs/release/bflibc/libbfc.a \ + external/bin/libs/release/bflibcpp/libbfcpp.a \ + external/bin/libs/release/bfnet/libbfnet.a +else LIBRARIES += \ - external/bin/libs/$(CONFIG)/bflibc/libbfc.a \ - external/bin/libs/$(CONFIG)/bflibcpp/libbfcpp.a \ - external/bin/libs/$(CONFIG)/bfnet/libbfnet.a + external/bin/libs/debug/bflibc/libbfc-debug.a \ + external/bin/libs/debug/bflibcpp/libbfcpp-debug.a \ + external/bin/libs/debug/bfnet/libbfnet-debug.a endif LINKS = -lpthread -lncurses $(BF_LIB_C_FLAGS) -ldl @@ -88,11 +94,7 @@ MAIN_FILE = testbench/tests.cpp BIN_NAME = chat-test #ADDR_SANITIZER = -fsanitize=address FLAGS = $(CPPFLAGS) -DDEBUG -DTESTING -g -Isrc/ $(ADDR_SANITIZER) $(CPPSTD) -Iexternal/bin/libs/debug $(OPENSSL_INCLUDE_PATH) -LIBRARIES += \ - external/bin/libs/debug/bflibc/libbfc-debug.a \ - external/bin/libs/debug/bflibcpp/libbfcpp-debug.a \ - external/bin/libs/debug/bfnet/libbfnet-debug.a \ - external/bin/libs/debug/bftest/libbftest-debug.a +LIBRARIES += external/bin/libs/debug/bftest/libbftest-debug.a endif # ($(CONFIG),...) LIBS_MAKEFILES_PATH:=$(CURDIR)/external/libs/makefiles diff --git a/src/command.cpp b/src/command.cpp index 14fe4dc..128d3cf 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -5,6 +5,7 @@ #include "command.hpp" #include "inputbuffer.hpp" +#include "operand.hpp" #include extern "C" { @@ -32,8 +33,13 @@ Command::~Command() { } -String Command::op() const { - return this->argumentAtIndex(0); +Operand Command::op() const { + String arg = this->argumentAtIndex(0); + if (arg.starts_with(":")) { + arg.remCharAtIndex(0); + } + + return Operand({arg}); } String Command::operator[](int i) const { diff --git a/src/command.hpp b/src/command.hpp index 35aab59..e618f9f 100644 --- a/src/command.hpp +++ b/src/command.hpp @@ -11,6 +11,7 @@ #include class InputBuffer; +class Operand; class Command : public BF::Object { public: @@ -22,7 +23,7 @@ class Command : public BF::Object { * * this is the first word in the buf */ - BF::String op() const; + Operand op() const; BF::String argumentAtIndex(int i) const; diff --git a/src/inputbuffer.cpp b/src/inputbuffer.cpp index 3858e17..e6ee818 100644 --- a/src/inputbuffer.cpp +++ b/src/inputbuffer.cpp @@ -14,18 +14,16 @@ using namespace BF; InputBuffer::InputBuffer() : InputBuffer("") {} InputBuffer::InputBuffer(const char * str) : String(str) { - this->_isready = false; + this->_enterPressed = false; this->_cursorpos = 0; } -InputBuffer::~InputBuffer() { - -} +InputBuffer::~InputBuffer() { } int InputBuffer::addChar(int ch) { switch (ch) { case '\n': - this->_isready = true; + this->_enterPressed = true; break; case KEY_BACKSPACE: case 127: @@ -55,7 +53,7 @@ int InputBuffer::addChar(int ch) { int InputBuffer::reset() { this->_cursorpos = 0; - this->_isready = false; + this->_enterPressed = false; return this->String::clear(); } @@ -63,7 +61,7 @@ size_t InputBuffer::cursorPosition() { return this->_cursorpos; } -bool InputBuffer::isready() { - return this->_isready; +bool InputBuffer::enterPressed() { + return this->_enterPressed; } diff --git a/src/inputbuffer.hpp b/src/inputbuffer.hpp index 3d214a5..a51bdf0 100644 --- a/src/inputbuffer.hpp +++ b/src/inputbuffer.hpp @@ -23,7 +23,7 @@ class InputBuffer : public BF::String { /** * when the buffer is ready to be sent */ - bool isready(); + bool enterPressed(); /** * clears buffer and resets the cursor position @@ -37,7 +37,7 @@ class InputBuffer : public BF::String { private: - bool _isready; + bool _enterPressed; /** * current cursor position diff --git a/src/interface.cpp b/src/interface.cpp index edb1c00..1837d42 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -20,6 +20,8 @@ #include "agentclient.hpp" #include "command.hpp" #include "permissions.hpp" +#include "utils.hpp" +#include "operand.hpp" extern "C" { #include @@ -523,7 +525,11 @@ int Interface::windowUpdateInputWindowText(InputBuffer & userInput) { BFLockLock(&this->_winlock); werase(this->_inputWin); if (this->_errorMessage.length() == 0) { - mvwprintw(this->_inputWin, 0, 0, userInput.cString()); + if (userInput.length() == 0) { + mvwprintw(this->_inputWin, 0, 0, "-- '?' for help --"); + } else { + mvwprintw(this->_inputWin, 0, 0, userInput.cString()); + } } else { mvwprintw(this->_inputWin, 0, 0, this->_errorMessage.cString()); this->_errorMessage.clear(); @@ -604,15 +610,15 @@ Chatroom * _InterfaceGetChatroomAtIndex(int i) { } int Interface::processinputStateLobby(InputBuffer & userInput) { - if (userInput.isready()) { + if (Utils::inputReady(userInput, this->_state)) { Command cmd(userInput); - if (!cmd.op().compareString(INTERFACE_COMMAND_QUIT)) { // quit + if (cmd.op() == OP_QUIT) { // quit Office::quitApplication(this->_user.get()); this->_state = kInterfaceStateQuit; - } else if (!cmd.op().compareString(INTERFACE_COMMAND_HELP)) { // help + } else if (cmd.op() == OP_HELP) { // help this->_returnfromhelpstate = this->_state; this->_state = kInterfaceStateHelp; - } else if (!cmd.op().compareString(INTERFACE_COMMAND_CREATE)) { // create + } else if (cmd.op() == OP_CREATE) { // create if (!Permissions::CanCreateChatroom()) { String errmsg("not permitted: you are not allowd to create a chatroom"); this->setErrorMessage(errmsg); @@ -632,7 +638,7 @@ int Interface::processinputStateLobby(InputBuffer & userInput) { Chatroom * cr = ChatroomServer::create(chatroomname); BFRelease(cr); } - } else if (!cmd.op().compareString(INTERFACE_COMMAND_JOIN)) { // join + } else if (cmd.op() == OP_JOIN) { // join int index = String::toi(cmd[1]) - 1; if ((index >= 0) && (index < Chatroom::getChatroomsCount())) { this->_chatroom = _InterfaceGetChatroomAtIndex(index); @@ -646,7 +652,7 @@ int Interface::processinputStateLobby(InputBuffer & userInput) { } } } else { - String errmsg("unknown command: %s", cmd.op().cString()); + String errmsg("unknown command: '%s'", cmd.op().description().cString()); this->setErrorMessage(errmsg); } userInput.reset(); @@ -656,9 +662,9 @@ int Interface::processinputStateLobby(InputBuffer & userInput) { } int Interface::processinputStateChatroom(InputBuffer & userInput) { - if (userInput.isready()) { + if (Utils::inputReady(userInput, this->_state)) { Command cmd(userInput); - if (!cmd.op().compareString(INTERFACE_COMMAND_LEAVE)) { // leave + if (cmd.op() == OP_LEAVE) { // leave // tell chat room we are leaving this->_chatroom.get()->resign(this->_user); @@ -666,15 +672,15 @@ int Interface::processinputStateChatroom(InputBuffer & userInput) { this->_chatroom = NULL; this->_state = kInterfaceStateLobby; - } else if (!cmd.op().compareString(INTERFACE_COMMAND_HELP)) { // help + } else if (cmd.op() == OP_HELP) { // help this->_returnfromhelpstate = this->_state; this->_state = kInterfaceStateHelp; - } else if (!cmd.op().compareString(INTERFACE_COMMAND_DRAFT)) { // draft + } else if (cmd.op() == OP_DRAFT) { // draft this->_state = kInterfaceStateDraft; this->converstaionHasChanged(); } else { - String errmsg("unknown command: %s", cmd.op().cString()); - this->setErrorMessage(*errmsg); + String errmsg("unknown command: '%s'", cmd.op().description().cString()); + this->setErrorMessage(errmsg); } userInput.reset(); @@ -683,8 +689,7 @@ int Interface::processinputStateChatroom(InputBuffer & userInput) { } int Interface::processinputStateDraft(InputBuffer & userInput) { - if (userInput.isready()) { - // send buf + if (Utils::inputReady(userInput, this->_state)) { // send buf this->_chatroom.get()->sendBuffer(userInput); this->_state = kInterfaceStateChatroom; diff --git a/src/operand.cpp b/src/operand.cpp new file mode 100644 index 0000000..6478828 --- /dev/null +++ b/src/operand.cpp @@ -0,0 +1,67 @@ +/** + * author: brando + * date: 1/9/25 + */ + +#include "operand.hpp" +#include + +extern "C" { +#include +} + +using namespace BF; + +const Operand OP_HELP({"help", "?"}); +const Operand OP_CREATE({"create"}); +const Operand OP_JOIN({"join"}); +const Operand OP_LEAVE({"leave"}); +const Operand OP_DRAFT({"draft", "i"}); +const Operand OP_QUIT({"quit", "q"}); + +void _OperandAcceptArgsRelease(char * a) { + BFFree(a); +} + +int _OperandAcceptArgsCompare(char * a, char * b) { + return strcmp(a, b); +} + +Operand::Operand(std::initializer_list list) : Object() { + this->_acceptedArgs.setReleaseCallback(_OperandAcceptArgsRelease); + for (const char * arg : list) { + char * buf = BFStringCopyString(arg); + this->_acceptedArgs.add(buf); + } + this->_acceptedArgs.setComparator(_OperandAcceptArgsCompare); +} + +Operand::~Operand() { } + +bool Operand::compare(const Operand & op) { + // FIXME: + // this is a poor implementation. time efficiency will decrease + // as more accepted arguments are implmented. I suggested to use + // algorithms as presented here:https://stackoverflow.com/a/245521/12135693 + // + // current implementation for BF::Array doesn't support the scope of those + // algorithms + for (int i = 0; i < this->_acceptedArgs.count(); i++) { + if (op._acceptedArgs.contains(this->_acceptedArgs[i])) + return true; + } + return false; +} + +bool Operand::operator==(const Operand & op) { + return this->compare(op); +} + +bool Operand::operator!=(const Operand & op) { + return !this->compare(op); +} + +String Operand::description() const { + return String("%s", this->_acceptedArgs[0]); +} + diff --git a/src/operand.hpp b/src/operand.hpp new file mode 100644 index 0000000..b2bbf99 --- /dev/null +++ b/src/operand.hpp @@ -0,0 +1,41 @@ +/** + * author: brando + * date: 1/9/25 + */ + +#ifndef OPERAND_HPP +#define OPERAND_HPP + +#include +#include + +/** + * I want the user to be able to pass '?' or ":help" in the + * command prompt to get the help menu + */ +class Operand : public BF::Object { +public: + Operand(std::initializer_list list); + virtual ~Operand(); + + bool compare(const Operand & op); + + BF::String description() const; + +private: + BF::Array _acceptedArgs; + +public: + bool operator==(const Operand & op); + bool operator!=(const Operand & op); +}; + +extern const Operand OP_HELP; +extern const Operand OP_CREATE; +extern const Operand OP_JOIN; +extern const Operand OP_LEAVE; +extern const Operand OP_DRAFT; +extern const Operand OP_QUIT; + +#endif // OPERAND_HPP + diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..9e715f4 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,19 @@ +/** + * author: brando + * date: 1/7/24 + */ + +#include "utils.hpp" +#include "inputbuffer.hpp" +#include "interface.hpp" + +bool Utils::inputReady(InputBuffer & buf, InterfaceState state) { + return + buf.starts_with(":") && (buf.length() > 1) && buf.enterPressed() + || + !buf.starts_with(":") && (buf.length() == 1) && (state != kInterfaceStateDraft) + || + (state == kInterfaceStateDraft) && buf.enterPressed(); + ; +} + diff --git a/src/utils.hpp b/src/utils.hpp new file mode 100644 index 0000000..5a60e3d --- /dev/null +++ b/src/utils.hpp @@ -0,0 +1,18 @@ +/** + * author: brando + * date: 1/7/24 + */ + +#ifndef UTILS_HPP +#define UTILS_HPP + +#include "typeinterfacestate.hpp" + +class InputBuffer; + +namespace Utils { +bool inputReady(InputBuffer & buf, InterfaceState state); +} + +#endif // UTILS_HPP + diff --git a/testbench/command_tests.hpp b/testbench/command_tests.hpp index bfaa7a7..aded932 100644 --- a/testbench/command_tests.hpp +++ b/testbench/command_tests.hpp @@ -10,6 +10,7 @@ #include #include "command.hpp" +#include "operand.hpp" #include "inputbuffer.hpp" extern "C" { @@ -28,33 +29,24 @@ BFTEST_UNIT_FUNC(test_commandop, 2<<10, { InputBuffer buf("command subcommand arg0 arg1"); Command c(buf); - if (strcmp(c.op(), "command")) { - result = 1; - } + BF_ASSERT(c.op() == Operand({"command"})); }) BFTEST_UNIT_FUNC(test_commandargs, 2<<10, { InputBuffer buf("command subcommand arg0 arg1"); Command c(buf); - if (strcmp(c.op(), "command")) { - result = 1; - } else if (strcmp(c[1], "subcommand")) { - result = 2; - } else if (strcmp(c[2], "arg0")) { - result = 3; - } else if (strcmp(c[3], "arg1")) { - result = 4; - } + BF_ASSERT(c.op() == Operand({"command"})); + BF_ASSERT(!strcmp(c[1], "subcommand")); + BF_ASSERT(!strcmp(c[2], "arg0")); + BF_ASSERT(!strcmp(c[3], "arg1")); }) BFTEST_UNIT_FUNC(test_commandargscount, 2<<10, { InputBuffer buf("command subcommand arg0 arg1"); Command c(buf); - if (c.count() == 0) { - result = max; - } + BF_ASSERT(c.count() > 0); }) BFTEST_COVERAGE_FUNC(command_tests, { diff --git a/testbench/inputbuffer_tests.hpp b/testbench/inputbuffer_tests.hpp index d993fe3..4f0b846 100644 --- a/testbench/inputbuffer_tests.hpp +++ b/testbench/inputbuffer_tests.hpp @@ -66,7 +66,7 @@ BFTEST_UNIT_FUNC(test_commandandarg, 2<<10, { buf.addChar('\n'); - if (!buf.isready()) { + if (!buf.enterPressed()) { result = max; } }) diff --git a/testbench/operand_tests.hpp b/testbench/operand_tests.hpp new file mode 100644 index 0000000..b9c5814 --- /dev/null +++ b/testbench/operand_tests.hpp @@ -0,0 +1,45 @@ +/** + * author: Brando + * date: 1/9/25 + */ + +#ifndef OPERAND_TESTS_HPP +#define OPERAND_TESTS_HPP + +#define ASSERT_PUBLIC_MEMBER_ACCESS + +#include +#include "operand.hpp" + +extern "C" { +#include +#include +} + +using namespace BF; + +BFTEST_UNIT_FUNC(test_operandinit, 2<<10, { + Operand op_draft({"draft", "i"}); + Operand op_help({"help", "?"}); +}) + +BFTEST_UNIT_FUNC(test_operandcompare, 2<<10, { + Operand op_draft({"draft", "i"}); + Operand op_help({"help", "?"}); + + Operand op_draft_long({"draft"}); + Operand op_draft_short({"i"}); + BF_ASSERT(op_draft_long == op_draft); + BF_ASSERT(op_draft_short == op_draft); + BF_ASSERT(op_draft_long != op_help); + BF_ASSERT(op_draft_short != op_help); +}) + +BFTEST_COVERAGE_FUNC(operand_tests, { + BFTEST_LAUNCH(test_operandinit); + BFTEST_LAUNCH(test_operandcompare); + +}) + +#endif // OPERAND_TESTS_HPP + diff --git a/testbench/tests.cpp b/testbench/tests.cpp index f8d97f8..a74b17a 100644 --- a/testbench/tests.cpp +++ b/testbench/tests.cpp @@ -8,9 +8,11 @@ #include "message_tests.hpp" #include "packet_tests.hpp" #include "command_tests.hpp" +#include "operand_tests.hpp" #include "agent_tests.hpp" #include "ciphersymmetric_tests.hpp" #include "cipherasymmetric_tests.hpp" +#include "utils_tests.hpp" #include "log.hpp" BFTEST_SUITE_FUNC({ @@ -22,5 +24,7 @@ BFTEST_SUITE_FUNC({ BFTEST_SUITE_LAUNCH(agent_tests); BFTEST_SUITE_LAUNCH(ciphersymmetric_tests); BFTEST_SUITE_LAUNCH(cipherasymmetric_tests); + BFTEST_SUITE_LAUNCH(operand_tests); + BFTEST_SUITE_LAUNCH(utils_tests); }) diff --git a/testbench/utils_tests.hpp b/testbench/utils_tests.hpp new file mode 100644 index 0000000..157b992 --- /dev/null +++ b/testbench/utils_tests.hpp @@ -0,0 +1,38 @@ +/** + * author: Brando + * date: 1/21/25 + */ + +#ifndef UTILS_TESTS_HPP +#define UTILS_TESTS_HPP + +#define ASSERT_PUBLIC_MEMBER_ACCESS + +#include +#include "utils.hpp" +#include "inputbuffer.hpp" + +extern "C" { +#include +#include +} + +using namespace BF; + +BFTEST_UNIT_FUNC(test_utilsIsReady, 2<<10, { + InputBuffer b0("i"); + BF_ASSERT(Utils::inputReady(b0, kInterfaceStateDraft)); + InputBuffer b1("asdf"); + BF_ASSERT(!Utils::inputReady(b1, kInterfaceStateDraft)); + InputBuffer b2("asdf"); + b2.addChar('\n'); + BF_ASSERT(Utils::inputReady(b2, kInterfaceStateDraft)); + +}) + +BFTEST_COVERAGE_FUNC(utils_tests, { + BFTEST_LAUNCH(test_utilsIsReady); +}) + +#endif // UTILS_TESTS_HPP + diff --git a/todo.md b/todo.md index 20972e7..0d720c5 100644 --- a/todo.md +++ b/todo.md @@ -8,12 +8,19 @@ x.x - [ ] ai bot chatroom 0.3 -- [ ] improved controls (easier commands or guidance) +- [x] improved controls (easier commands or guidance) + - [x] use of ":" for long commands + - [x] single key commands + - [x] make operand constants + - [x] drafting messages crashes + - [x] add text at the bottom that says "press '?' or type ":help" to show help" +- [ ] improve help - [ ] chat configuration implemented in ncurses - [ ] user typing - [x] update build and test process - [x] move package logic to libs - [ ] host vs server (allow user to run this application as a service) +- [ ] allow "localhost" for `-ip4` argument 0.2.1 - [x] issue with sending messages between two machines that are on macos and linux