From db39415db09cfb9212b9d2e1fa7e71c2934a2854 Mon Sep 17 00:00:00 2001 From: Cristian-Adrian Frasineanu Date: Sat, 19 Nov 2016 17:31:07 +0200 Subject: [PATCH] Separate headers, view caching and navigate to previous, memory leak fix. --- app/Bootstrap.cpp | 37 ++++--- app/ClassContainer.cpp | 142 +++++++++++++++++++------- app/ClassContainer.h | 35 ++++--- app/Console.cpp | 218 ++++++++++++++++++++++++++++++++++++++++ app/Console.h | 52 ++++++++++ app/Helpers.cpp | 5 + app/Helpers.h | 3 +- app/User.cpp | 27 +++++ app/User.h | 12 +++ app/View.cpp | 86 ++++++++++++++++ app/View.h | 34 +++++++ app/app.vcxproj | 10 +- app/app.vcxproj.filters | 22 +++- config/app.config | 0 views/browse-index.view | 13 +++ views/faq.view | 35 +++++++ views/help.view | 17 ++++ views/home.view | 19 ++-- views/login.view | 8 +- views/signup.view | 13 +++ 20 files changed, 698 insertions(+), 90 deletions(-) create mode 100644 app/Console.cpp create mode 100644 app/Console.h create mode 100644 app/User.cpp create mode 100644 app/User.h create mode 100644 app/View.cpp create mode 100644 app/View.h create mode 100644 config/app.config create mode 100644 views/browse-index.view create mode 100644 views/faq.view create mode 100644 views/help.view create mode 100644 views/signup.view diff --git a/app/Bootstrap.cpp b/app/Bootstrap.cpp index 390d640..e363ac0 100644 --- a/app/Bootstrap.cpp +++ b/app/Bootstrap.cpp @@ -1,9 +1,6 @@ -#include -#include #include -#include "Helpers.h" -#include "ClassContainer.h" +#include "Console.h" using namespace std; @@ -11,18 +8,19 @@ void main() { try { - Console console; View::loadViewsOptions(); + Console console; + do { try { - cout << " >"; - + console.showPrompt(); console.setLastInput(_getch()); - cout << console.getLastInput() - << endl; - //console.renderNextView(); + if (console.takeActionIfAny() == false) + { + console.renderNextView(); + } } catch (const invalid_argument &e) { @@ -31,21 +29,22 @@ void main() << endl; sleepAndClearBuffer(console.getDelay()); - console.reloadView(); } - catch (const system_error &e) - { - cout << e.code() - << " " - << e.what() - << endl; - } - } while (console.getLastInput() != 'q'); + } while (!console.shouldExit()); } catch (const invalid_argument &e) { cout << e.what() << endl; } + catch (const system_error &e) + { + clearScreen(); + + cout << e.code() + << " " + << e.what() + << endl; + } } diff --git a/app/ClassContainer.cpp b/app/ClassContainer.cpp index d973312..5d64a34 100644 --- a/app/ClassContainer.cpp +++ b/app/ClassContainer.cpp @@ -11,37 +11,13 @@ using namespace std; namespace fs = std::experimental::filesystem::v1; -//--- User --- -//------------ -unsigned User::count = 0; - -void User::setFromLastUuid() -{ - // Get the last uuid from the db file - - User::count = 30; -} - -User::User() : uuid(User::count++) -{ - this->nick = new char[4]; -} - -User::~User() -{ - delete[] this->nick; -} - -unsigned User::getUuid() -{ - return this->uuid; -} - //---View--- //---------- +// A view has attached to it a range of available actions or certain verbs that are used by the console to do some action. +// It's basically a dictionary holding the options mapping map> View::ViewsOptions = { { "", {{'\0', ""}} } }; -View::View(string viewName, map availableOptions, bool hasInterpolation) +View::View(string &viewName, map &availableOptions, bool hasInterpolation) { this->viewName = new char[strlen(viewName.c_str()) + 1]; strcpy(this->viewName, viewName.c_str()); @@ -49,7 +25,7 @@ View::View(string viewName, map availableOptions, bool hasInterpol this->hasInterpolation = hasInterpolation; } -map View::getAvailableOptions() +map &View::getAvailableOptions() { return this->availableOptions; } @@ -61,11 +37,27 @@ char *View::getViewName() void View::loadViewsOptions() { - string someView = "aView", - anotherView = "aaaView"; + string homeView = "home.view", + loginView = "login.view", + signupView = "signup.view", + browseIndexView = "browse-index.view", + faqView = "faq.view", + helpView = "help.view"; + + View::ViewsOptions = { + { homeView, { { '1', "login.view" }, { '2', "signup.view" }, { '3', "browse-index.view" }, + { '4', "faq.view" }, { '5', "help.view" }, { 'q', "quit" } } }, + { loginView, { { '1', "browse-index.view" }, { 'q', "quit" } } }, + { signupView, { { 'q', "quit" } } }, + { browseIndexView, { {'q', "quit"} } }, + { faqView, { {'q', "quit"} } }, + { helpView, { { 'q', "quit" } } } + }; +} - View::ViewsOptions = { { someView, {{'1', "theview.view"}}}, - {anotherView, {{'2', "someview.view"}}} }; +map> &View::getViewsOptions() +{ + return View::ViewsOptions; } View::~View() @@ -83,11 +75,14 @@ Console::Console() { this->mode = new char[strlen("live") + 1]; strcpy(this->mode, "live"); - map availableOptions = { {'1', "login.view"}, {'2', "signup.view"}, {'3', "browse-index.view"}, {'4', "faq.view"}, {'q', "quit"} }; + this->exit = false; + + map availableOptions = View::getViewsOptions().find(Console::initialView)->second; this->currentView = new View(Console::initialView, availableOptions, false); this->delay = 2000; + this->loadActions(); this->loadViews(Console::viewsFolder); if (find(this->loadedViews.begin(), this->loadedViews.end(), this->currentView->getViewName()) != this->loadedViews.end()) @@ -105,9 +100,15 @@ Console::Console(char *mode) { this->mode = new char[strlen("debug") + 1]; strcpy(this->mode, "debug"); + this->exit = false; + string viewName = "debug.view"; - this->currentView = new View(viewName, { {'q', "quit"} }, true); + map availableOptions = View::getViewsOptions().find(Console::initialView)->second; + + this->currentView = new View(viewName, availableOptions, true); + this->delay = 2000; + this->loadActions(); this->loadViews(Console::viewsFolder); if (find(this->loadedViews.begin(), this->loadedViews.end(), this->currentView->getViewName()) != this->loadedViews.end()) @@ -133,6 +134,17 @@ void Console::setLastInput(char input) } } +vector &Console::getActions() +{ + return this->actions; +} + +void Console::showPrompt() +{ + cout << endl + << ">> "; +} + char Console::getLastInput() { return this->lastInput; @@ -160,6 +172,13 @@ void Console::loadViews(const fs::path &viewsFolder) } } +void Console::loadActions() +{ + vector actions = { 'q', 'b', 'n' }; + + this->actions = actions; +} + void Console::renderView(View &view) { string content; @@ -177,7 +196,7 @@ void Console::renderView(View &view) } else { - throw system_error(error_code(500, system_category()), "The view stream couldn't be opened."); + throw system_error(error_code(3, system_category()), "The view stream couldn't be opened"); } viewFile.close(); @@ -185,9 +204,28 @@ void Console::renderView(View &view) void Console::renderNextView() { - this->currentView = new View(this->currentView->getAvailableOptions().find(this->getLastInput())->second, { {'5', "lala"} }, false); + string nextView = this->currentView->getAvailableOptions().find(this->getLastInput())->second; + map nextOptions = View::getViewsOptions().find(nextView)->second; - //TODO: finish the renderer + // Cache the current view + this->previousViews.push_back(this->currentView); + // The name and then the corresponding options map to the view from ViewsOptions and the hasInterpolation boolean + this->currentView = new View(nextView, + nextOptions, + false); + + //TODO: detemine if the view has interpolation enabled in order to replace the template strings or to splice the view in certain places + system("cls"); + this->renderView(*this->currentView); +} + +void Console::renderPreviousView() +{ + if (!this->previousViews.empty()) + { + this->renderView(*this->previousViews.back()); + this->previousViews.pop_back(); + } } void Console::reloadView() @@ -196,6 +234,34 @@ void Console::reloadView() this->renderView(*this->currentView); } +void Console::takeActionIfAny() +{ + if (isInCharVector(this->getActions(), this->lastInput)) + { + switch (this->lastInput) + { + case 'q': + this->breakTheLoop(); + break; + case 'b': + this->renderPreviousView(); + break; + case 'n': + // TODO: implement pagination for the questions index view + } + } +} + +bool Console::shouldExit() +{ + return this->exit; +} + +void Console::breakTheLoop() +{ + this->exit = true; +} + unsigned Console::getDelay() { return this->delay; @@ -207,6 +273,6 @@ Console::~Console() delete[] this->mode; system("cls"); - cout << "Program exited successfully..." + cout << "Program exited the main console." << endl; } diff --git a/app/ClassContainer.h b/app/ClassContainer.h index 75071a6..c67c936 100644 --- a/app/ClassContainer.h +++ b/app/ClassContainer.h @@ -8,17 +8,20 @@ namespace fs = std::experimental::filesystem::v1; class View { private: static map> ViewsOptions; + char *viewName; bool hasInterpolation; + map availableOptions; View(); public: - View(string, map, bool); + View(string &, map &, bool); - map getAvailableOptions(); + map &getAvailableOptions(); char *getViewName(); static void loadViewsOptions(); + static map> &getViewsOptions(); ~View(); }; @@ -27,35 +30,37 @@ class Console { private: static string initialView; static char *viewsFolder; + char *mode; char lastInput; unsigned delay; + bool exit; View *currentView; + vector previousViews; + vector loadedViews; + vector actions; void loadViews(const fs::path &); + void loadActions(); public: Console(); Console(char *); + vector &getActions(); + + void showPrompt(); char getLastInput(); void setLastInput(char); + unsigned getDelay(); + void renderView(View &); void renderNextView(); + void renderPreviousView(); void reloadView(); - unsigned getDelay(); + void takeActionIfAny(); + bool shouldExit(); + void breakTheLoop(); ~Console(); -}; - -class User { -private: - const unsigned uuid; - char *nick; -public: - static unsigned count; - static void setFromLastUuid(); - User(); - ~User(); - unsigned getUuid(); }; \ No newline at end of file diff --git a/app/Console.cpp b/app/Console.cpp new file mode 100644 index 0000000..ac8c9ac --- /dev/null +++ b/app/Console.cpp @@ -0,0 +1,218 @@ +#include "Console.h" + +//---Console--- +//------------- +// We need an initial state, because we don't have any means like mapping views to HTTP routes. +// The Console class represents an entry point for all the other components, like View +string Console::initialView = "home.view"; +string Console::viewsFolder = "..\\views"; + +Console::Console() +{ + this->mode = new char[strlen("live") + 1]; + strcpy(this->mode, "live"); + this->exit = false; + + map availableOptions = View::getViewsOptions().find(Console::initialView)->second; + + this->currentView = View(Console::initialView, availableOptions, false); + this->delay = 2000; + + this->loadActions(); + this->loadViews(Console::viewsFolder); + + if (find(this->loadedViews.begin(), this->loadedViews.end(), this->currentView.getViewName()) != this->loadedViews.end()) + { + this->renderView(this->currentView); + } + else + { + string exceptionIntro = "Please specify an existing view ("; + throw invalid_argument(exceptionIntro + this->currentView.getViewName() + ")"); + } +} + +Console::Console(char *mode) +{ + this->mode = new char[strlen("debug") + 1]; + strcpy(this->mode, "debug"); + this->exit = false; + + string viewName = "debug.view"; + map availableOptions = View::getViewsOptions().find(Console::initialView)->second; + + this->currentView = View(viewName, availableOptions, true); + this->delay = 2000; + + this->loadActions(); + this->loadViews(Console::viewsFolder); + + if (find(this->loadedViews.begin(), this->loadedViews.end(), this->currentView.getViewName()) != this->loadedViews.end()) + { + this->renderView(this->currentView); + } + else + { + string exceptionIntro = "No debug view found: "; + throw invalid_argument(exceptionIntro + this->currentView.getViewName()); + } +} + +void Console::setLastInput(char input) +{ + if (isInCharStringMap(this->currentView.getAvailableOptions(), input)) + { + this->lastInput = input; + } + else + { + throw invalid_argument("Please provide a valid selection."); + } +} + +vector &Console::getActions() +{ + return this->actions; +} + +void Console::showPrompt() +{ + cout << endl + << ">> "; +} + +char Console::getLastInput() +{ + return this->lastInput; +} + +void Console::loadViews(const fs::path &viewsFolder) +{ + if (!fs::exists(viewsFolder) + || !fs::is_directory(viewsFolder)) + { + throw invalid_argument("Received an invalid directory: \"" + viewsFolder.string() + "\""); + } + + fs::recursive_directory_iterator it(viewsFolder); + fs::recursive_directory_iterator endit; + + while (it != endit) + { + if (fs::is_regular_file(*it) + && it->path().extension() == ".view") + { + this->loadedViews.push_back(it->path().filename()); + } + ++it; + } +} + +void Console::loadActions() +{ + vector actions = { 'q', 'b', 'n' }; + + this->actions = actions; +} + +void Console::renderView(View &view) +{ + string content; + string path = Console::viewsFolder; + path.append("\\").append(view.getViewName()); + stringstream buffer; + ifstream viewFile(path, ios::out); + + if (viewFile.is_open()) + { + buffer << viewFile.rdbuf(); + + cout << buffer.str() + << endl; + + buffer.clear(); + + viewFile.close(); + } +} + +void Console::renderNextView() +{ + string nextView = this->currentView.getAvailableOptions().find(this->getLastInput())->second; + map nextOptions = View::getViewsOptions().find(nextView)->second; + + // Cache the current view + this->previousViews.push_back(this->currentView); + + this->currentView = View(nextView, nextOptions, false); + + //TODO: detemine if the view has interpolation enabled in order to replace the template strings or to splice the view in certain places (prepareView) + clearScreen(); + this->renderView(this->currentView); + + //Show it directly for now... +} + +void Console::renderPreviousView() +{ + if (!this->previousViews.empty()) + { + clearScreen(); + this->currentView = this->previousViews.back(); + this->renderView(this->currentView); + this->previousViews.pop_back(); + } +} + +void Console::reloadView() +{ + clearScreen(); + this->renderView(this->currentView); +} + +bool Console::takeActionIfAny() +{ + if (isInCharVector(this->getActions(), this->lastInput)) + { + switch (this->lastInput) + { + case 'q': + this->breakTheLoop(); + break; + case 'b': + this->renderPreviousView(); + break; + case 'n': + // TODO: implement pagination for the questions index view + break; + default: + break; + } + return true; + } + return false; +} + +bool Console::shouldExit() +{ + return this->exit; +} + +void Console::breakTheLoop() +{ + this->exit = true; +} + +unsigned Console::getDelay() +{ + return this->delay; +} + +Console::~Console() +{ + delete[] this->mode; + + clearScreen(); + cout << "Program exited the main console." + << endl; +} diff --git a/app/Console.h b/app/Console.h new file mode 100644 index 0000000..1c325fa --- /dev/null +++ b/app/Console.h @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include + +#include "Helpers.h" +#include "View.h" + +using namespace std; +namespace fs = std::experimental::filesystem::v1; + +class Console { +private: + static string initialView; + static string viewsFolder; + + char *mode; + char lastInput; + unsigned delay; + bool exit; + View currentView; + vector previousViews; + + vector loadedViews; + vector actions; + + void loadViews(const fs::path &); + void loadActions(); +public: + Console(); + Console(char *); + + vector &getActions(); + + void showPrompt(); + char getLastInput(); + void setLastInput(char); + unsigned getDelay(); + + void renderView(View &); + void renderNextView(); + void renderPreviousView(); + void reloadView(); + + bool takeActionIfAny(); + bool shouldExit(); + void breakTheLoop(); + + ~Console(); +}; \ No newline at end of file diff --git a/app/Helpers.cpp b/app/Helpers.cpp index cccefe0..a2f52ea 100644 --- a/app/Helpers.cpp +++ b/app/Helpers.cpp @@ -37,3 +37,8 @@ void sleepAndClearBuffer(unsigned delay) _getch(); } } + +void clearScreen() +{ + system("cls"); +} diff --git a/app/Helpers.h b/app/Helpers.h index db773bc..2b27905 100644 --- a/app/Helpers.h +++ b/app/Helpers.h @@ -8,4 +8,5 @@ bool isInCharStringMap(const map &, char); bool isInCharVector(const vector &, char); bool isInIntVector(const vector &, int); bool isInStringVector(const vector &, string); -void sleepAndClearBuffer(unsigned delay); \ No newline at end of file +void sleepAndClearBuffer(unsigned delay); +void clearScreen(); \ No newline at end of file diff --git a/app/User.cpp b/app/User.cpp new file mode 100644 index 0000000..774907a --- /dev/null +++ b/app/User.cpp @@ -0,0 +1,27 @@ +#include "User.h" + +//--- User --- +//------------ +unsigned User::count = 0; + +void User::setFromLastUuid() +{ + // Get the last uuid from the db file + + User::count = 30; +} + +User::User() : uuid(User::count++) +{ + this->nick = new char[4]; +} + +User::~User() +{ + delete[] this->nick; +} + +unsigned User::getUuid() +{ + return this->uuid; +} \ No newline at end of file diff --git a/app/User.h b/app/User.h new file mode 100644 index 0000000..9340973 --- /dev/null +++ b/app/User.h @@ -0,0 +1,12 @@ +class User { +private: + static unsigned count; + + const unsigned uuid; + char *nick; +public: + static void setFromLastUuid(); + User(); + ~User(); + unsigned getUuid(); +}; \ No newline at end of file diff --git a/app/View.cpp b/app/View.cpp new file mode 100644 index 0000000..793d53a --- /dev/null +++ b/app/View.cpp @@ -0,0 +1,86 @@ +#include "View.h" + +//---View--- +//---------- +// A view has attached to it a range of available actions or certain verbs that are used by the console to do some action. +// It's basically a dictionary holding the options mapping. +map> View::ViewsOptions = { { "",{ { '\0', "" } } } }; + +View::View() +{ + this->viewName = new char[strlen("NO_VIEW") + 1]; + strcpy(this->viewName, "NO_VIEW"); + this->hasInterpolation = false; +} + +View::View(string &viewName, map &availableOptions, bool hasInterpolation) +{ + this->viewName = new char[strlen(viewName.c_str()) + 1]; + strcpy(this->viewName, viewName.c_str()); + this->availableOptions = availableOptions; + this->hasInterpolation = hasInterpolation; +} + +View::View(const View &view) +{ + this->viewName = new char[strlen(view.viewName) + 1]; + strcpy(this->viewName, view.viewName); + this->hasInterpolation = view.hasInterpolation; + this->availableOptions = view.availableOptions; +} + +map &View::getAvailableOptions() +{ + return this->availableOptions; +} + +char *View::getViewName() +{ + return this->viewName; +} + +void View::loadViewsOptions() +{ + string homeView = "home.view", + loginView = "login.view", + signupView = "signup.view", + browseIndexView = "browse-index.view", + faqView = "faq.view", + helpView = "help.view"; + + View::ViewsOptions = { + { homeView, { { '1', "login.view" }, { '2', "signup.view" }, { '3', "browse-index.view" }, + { '4', "faq.view" }, { '5', "help.view" }, { 'q', "quit" } } }, + + { loginView, { { '1', "browse-index.view" }, { 'q', "quit" }, { 'b', "back" } } }, + + { signupView, { { 'q', "quit" }, { 'b', "back" } } }, + + { browseIndexView, { { 'q', "quit" }, { 'b', "back" } } }, + + { faqView, { { 'q', "quit" }, { 'b', "back" } } }, + + { helpView, { { 'q', "quit" }, { 'b', "back" } } } + }; +} + +map> &View::getViewsOptions() +{ + return View::ViewsOptions; +} + +// Prevent multiple assignments of views, i.e. a view can transition only once and cannot split into multiple views. +void View::operator=(const View &view) +{ + delete[] this->viewName; + + this->viewName = new char[strlen(view.viewName) + 1]; + strcpy(this->viewName, view.viewName); + this->hasInterpolation = view.hasInterpolation; + this->availableOptions = view.availableOptions; +} + +View::~View() +{ + delete[] this->viewName; +} diff --git a/app/View.h b/app/View.h new file mode 100644 index 0000000..f7d9575 --- /dev/null +++ b/app/View.h @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include + +#include "Helpers.h" + +using namespace std; + +class View { +private: + static map> ViewsOptions; + + char *viewName; + bool hasInterpolation; + + map availableOptions; + +public: + static void loadViewsOptions(); + static map> &getViewsOptions(); + + View(); + View(string &, map &, bool); + View(const View &); + + map &getAvailableOptions(); + char *getViewName(); + + void operator=(const View &); + ~View(); +}; \ No newline at end of file diff --git a/app/app.vcxproj b/app/app.vcxproj index 16f5793..6ff7d0b 100644 --- a/app/app.vcxproj +++ b/app/app.vcxproj @@ -87,7 +87,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) C:\Projects\StackPlusPlus\;%(AdditionalIncludeDirectories) @@ -144,12 +144,16 @@ - + + + - + + + diff --git a/app/app.vcxproj.filters b/app/app.vcxproj.filters index 4b0528d..0a71083 100644 --- a/app/app.vcxproj.filters +++ b/app/app.vcxproj.filters @@ -15,21 +15,33 @@ - - Source Files - Source Files Source Files + + Source Files + + + Source Files + + + Source Files + - + Header Files - + + Header Files + + + Header Files + + Header Files diff --git a/config/app.config b/config/app.config new file mode 100644 index 0000000..e69de29 diff --git a/views/browse-index.view b/views/browse-index.view new file mode 100644 index 0000000..42d32f8 --- /dev/null +++ b/views/browse-index.view @@ -0,0 +1,13 @@ + __ _ _ ___ _ ___ _ +/ _\ |_ __ _ ___| | __ / _ \ |_ _ ___ / _ \ |_ _ ___ +\ \| __/ _` |/ __| |/ // /_)/ | | | / __| / /_)/ | | | / __| +_\ \ || (_| | (__|