stamen

Stamen - static menu generator
git clone git://git.dimitrijedobrota.com/stamen.git
Log | Files | Refs | README | LICENSE

commit bed0d31a9d9d145fc559b43b4b5304c0b5373a78
parent 87599e12d3694eca29695587e4c71cf57c6cb94b
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Tue,  5 Dec 2023 23:22:56 +0000

Dynamic menus and restructure

* Dynamics menus are back!
* Extend C/C++ interface with new functions
* New demo program for showcase
* callback_f now accepts int for implementation reasons
* Refactor Menu class for easier data access
* Improve consistency
* Remove redundancy

Diffstat:
MCMakeLists.txt | 2+-
Mdemo/CMakeLists.txt | 14++++++++++++++
Ademo/dynamic.cpp | 31+++++++++++++++++++++++++++++++
Mdemo/main.c | 2+-
Mdemo/main.cpp | 10+++++-----
Mdemo/shared.h | 8++++----
Minclude/menu.h | 75++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Minclude/stamen.h | 15++++++++++-----
Msrc/CMakeLists.txt | 6+++---
Msrc/generate.cpp | 17++++++++++-------
Msrc/menu.cpp | 46+++++++++++++++++++++++++++++++++++-----------
Msrc/stamen.cpp | 13+++++++++++--
12 files changed, 181 insertions(+), 58 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project( Stamen - VERSION 0.0.17 + VERSION 0.0.18 DESCRIPTION "Static menu generator" LANGUAGES C CXX ) diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt @@ -57,3 +57,17 @@ set_target_properties(cdemo PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR} RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" ) + +add_executable(dynamic + dynamic.cpp +) + +target_link_libraries(dynamic + stamen +) + +set_target_properties(dynamic PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) diff --git a/demo/dynamic.cpp b/demo/dynamic.cpp @@ -0,0 +1,31 @@ +#include <stamen.h> +#include <iostream> + +const stamen_display_f stamen_display = stamen_builtin_display; + +int finish(int) { exit(1); } + +int operation1(int) { + std::cout << "1" << std::endl; + return 1; +} + +int operation2(int) { + std::cout << "2" << std::endl; + return 1; +} + +int operation3(int) { + std::cout << "3" << std::endl; + return 1; +} + +int main() { + stamen_read("./bin/demo_menu.conf"); + stamen_insert("finish", finish); + stamen_insert("operation1", operation1); + stamen_insert("operation2", operation2); + stamen_insert("operation3", operation3); + stamen_dynamic(); + return 0; +} diff --git a/demo/main.c b/demo/main.c @@ -32,6 +32,6 @@ int finish(void) { } int main(void) { - menu_main(); + menu_main(0); return 0; } diff --git a/demo/main.cpp b/demo/main.cpp @@ -7,30 +7,30 @@ // in order to use stamen_builtin_display const stamen_display_f stamen_display = stamen_builtin_display; -int operation1() { +int operation1(int) { std::cout << "operation 1" << std::endl; std::cout << "Some operation is done" << std::endl; return 1; } -int operation2() { +int operation2(int) { std::cout << "operation 2" << std::endl; std::cout << "Some other operation is done" << std::endl; return 1; } -int operation3() { +int operation3(int) { std::cout << "operation 3" << std::endl; std::cout << "Yet another operation is done" << std::endl; return 1; } -int finish() { +int finish(int) { std::cout << "finishing..." << std::endl; exit(0); } int main() { - menu_main(); + menu_main(0); return 0; } diff --git a/demo/shared.h b/demo/shared.h @@ -1,9 +1,9 @@ #ifndef STAMEN_DEMO_SHARED_H #define STAMEN_DEMO_SHARED_H -int finish(void); -int operation1(void); -int operation2(void); -int operation3(void); +int finish(int); +int operation1(int); +int operation2(int); +int operation3(int); #endif diff --git a/include/menu.h b/include/menu.h @@ -3,53 +3,90 @@ #include "stamen.h" +#include <cstring> +#include <iostream> #include <string> #include <unordered_map> #include <vector> class Menu { - struct private_ctor_t {}; + friend class Generator; -public: - using lookup_t = std::unordered_map<std::string, Menu>; - static lookup_t lookup; + using callback_f = stamen_callback_f; + using item_t = stamen_item_t; - static void read(const std::string &s); - static void print(const std::string &entry) { internal_print(entry, 1); } - static lookup_t &getLookup() { return lookup; } + static std::unordered_map<std::string, Menu> menu_lookup; + static std::unordered_map<std::string, callback_f> free_lookup; - Menu(const Menu &) = delete; - Menu &operator=(const Menu &) = delete; + struct private_ctor_t {}; +public: // Tag type dispatch Menu(private_ctor_t, const std::string &code, const std::string &prompt) : Menu(code, prompt) {} - struct lookup_item_t { - const std::string code; - const std::string prompt; - }; + Menu(const Menu &) = delete; + Menu &operator=(const Menu &) = delete; + + static int dynamic() { return display_stub(-1); }; + static void read(const std::string &s); + static void print(const std::string &entry) { print(entry, 1); } + static void insert(const std::string &s, callback_f callback) { + free_lookup.emplace(s, callback); + } [[nodiscard]] const std::string &getCode() const { return code; } [[nodiscard]] const std::string &getTitle() const { return title; } - [[nodiscard]] const std::vector<lookup_item_t> &getItems() const { - return lookup_items; + + [[nodiscard]] const item_t *getItemv() const { return entries.items.data(); } + [[nodiscard]] std::size_t getSize() const { return entries.items.size(); } + + [[nodiscard]] const std::string &getCode(std::size_t idx) const { + return entries.codes[idx].code; + } + + [[nodiscard]] const std::string &getPrompt(std::size_t idx) const { + return entries.codes[idx].prompt; + } + + [[nodiscard]] callback_f getCallback(std::size_t idx) const { + return entries.items[idx].callback; } private: Menu(std::string code, std::string prompt) : code(std::move(code)), title(std::move(prompt)) {} + static void print(const std::string &entry, const int depth); + static int display_stub(int idx); + static const Menu *getMenu(const std::string &code) { - const auto it = lookup.find(code); - if (it == lookup.end()) return nullptr; + const auto it = menu_lookup.find(code); + if (it == menu_lookup.end()) return nullptr; return &it->second; } - static void internal_print(const std::string &entry, const int depth); + struct Entries { + struct code_t { + const std::string code; + const std::string prompt; + }; + + std::vector<code_t> codes; + std::vector<item_t> items; + + void insert(const std::string &code, const std::string &prompt, + callback_f callback = display_stub) { + char *buffer = new char[prompt.size() + 1]; + strcpy(buffer, prompt.c_str()); + items.emplace_back(callback, buffer); + + codes.emplace_back(code, prompt); + } + }; - std::vector<lookup_item_t> lookup_items; const std::string code, title; + Entries entries; }; #endif diff --git a/include/stamen.h b/include/stamen.h @@ -7,17 +7,22 @@ #define EXTERNC extern #endif -typedef int (*stamen_callback_f)(void); +typedef int (*stamen_callback_f)(int); -typedef struct item_t item_t; -struct item_t { +typedef struct stamen_item_t stamen_item_t; +struct stamen_item_t { stamen_callback_f callback; const char *prompt; }; -typedef int (*stamen_display_f)(const char *, const item_t[], int); +typedef int (*stamen_display_f)(const char *, const stamen_item_t[], int); extern const stamen_display_f stamen_display; -EXTERNC int stamen_builtin_display(const char *title, const item_t itemv[], int size); +EXTERNC int stamen_dynamic(void); +EXTERNC void stamen_read(const char *filename); +EXTERNC void stamen_insert(const char *code, stamen_callback_f callback); + +EXTERNC int stamen_builtin_display(const char *title, + const stamen_item_t itemv[], int size); #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt @@ -1,7 +1,7 @@ add_library(stamen-include INTERFACE) target_include_directories(stamen-include INTERFACE ../include) -add_library(stamen stamen.cpp) +add_library(stamen stamen.cpp menu.cpp) target_link_libraries(stamen PUBLIC stamen-include) set_target_properties(stamen PROPERTIES VERSION ${PROJECT_VERSION} @@ -14,8 +14,8 @@ install(TARGETS stamen PUBLIC_HEADER DESTINATION include ) -add_executable(stamen-generate generate.cpp menu.cpp) -target_link_libraries(stamen-generate PRIVATE stamen-include) +add_executable(stamen-generate generate.cpp) +target_link_libraries(stamen-generate PRIVATE stamen) target_include_directories(stamen-generate PUBLIC ../include) set_target_properties(stamen-generate PROPERTIES diff --git a/src/generate.cpp b/src/generate.cpp @@ -1,4 +1,6 @@ #include "menu.h" +#include "stamen.h" + #include <format> #include <fstream> #include <iostream> @@ -10,8 +12,8 @@ public: os << "#ifndef STAMEN_MENU_H\n"; os << "#define STAMEN_MENU_H\n\n"; os << "#include <stamen.h>\n\n"; - for (const auto &[code, menu] : Menu::getLookup()) { - os << std::format("int {}(void);\n", menu.getCode()); + for (const auto &[code, menu] : Menu::menu_lookup) { + os << std::format("int {}(int);\n", menu.getCode()); } os << "\n#endif\n"; } @@ -19,12 +21,13 @@ public: static void generateSource(std::ostream &os) { os << "#include <stamen.h>\n"; os << "#include \"shared.h\"\n\n"; - for (const auto &[code, menu] : Menu::getLookup()) { - os << std::format("int {}(void) {{\n", menu.getCode()); + for (const auto &[code, menu] : Menu::menu_lookup) { + os << std::format("int {}(int) {{\n", menu.getCode()); - os << std::format("\tstatic const item_t items[] = {{\n"); - for (const auto &[code, prompt] : menu.getItems()) { - os << std::format("\t\t{{ {}, \"{}\" }},\n", code, prompt); + os << std::format("\tstatic const stamen_item_t items[] = {{\n"); + for (int i = 0; i < menu.getSize(); i++) { + os << std::format("\t\t{{ {}, \"{}\" }},\n", menu.getCode(i), + menu.getPrompt(i)); } os << std::format("\t}};\n"); diff --git a/src/menu.cpp b/src/menu.cpp @@ -1,5 +1,7 @@ #include "menu.h" +#include "stamen.h" +#include <deque> #include <format> #include <fstream> #include <iostream> @@ -7,40 +9,62 @@ #include <tuple> #include <utility> -Menu::lookup_t Menu::lookup; +std::unordered_map<std::string, Menu> Menu::menu_lookup; +std::unordered_map<std::string, Menu::callback_f> Menu::free_lookup; void Menu::read(const std::string &s) { std::string line, delim, code, prompt; std::fstream fs(s); char tmp = 0; - lookup_t &lookup = getLookup(); - auto last = lookup.end(); + auto last = menu_lookup.end(); while (std::getline(fs, line)) { if (line.empty()) continue; std::istringstream ss(line); ss >> delim >> code; ss.ignore(1, ' '), std::getline(ss, prompt); if (delim == "+") { - const auto [iter, succ] = - lookup.emplace(std::piecewise_construct, std::forward_as_tuple(code), - std::forward_as_tuple(private_ctor_t{}, code, prompt)); + const auto [iter, succ] = menu_lookup.emplace( + std::piecewise_construct, std::forward_as_tuple(code), + std::forward_as_tuple(private_ctor_t{}, code, prompt)); last = iter; } else { - last->second.lookup_items.emplace_back(code, prompt); + last->second.entries.insert(code, prompt); } } } -void Menu::internal_print(const std::string &code, const int depth) { +void Menu::print(const std::string &code, const int depth) { const Menu *menu = getMenu(code); if (!menu) return; if (depth == 1) std::cout << std::format("{}({})\n", menu->title, code); - for (const auto &[code, prompt] : menu->lookup_items) { + for (int i = 0; i < menu->getSize(); i++) { std::cout << std::format("{}{} ({})\n", std::string(depth << 1, ' '), - prompt, code); - menu->internal_print(code, depth + 1); + menu->getPrompt(i), menu->getCode(i)); + menu->print(code, depth + 1); } } + +int Menu::display_stub(int idx) { + static std::deque<const Menu *> st; + + const std::string &code = st.size() ? st.back()->getCode(idx) : "menu_main"; + + const auto ml_it = menu_lookup.find(code); + if (ml_it != menu_lookup.end()) { + const Menu &menu = ml_it->second; + st.push_back(&menu); + int ret = stamen_builtin_display(menu.title.c_str(), menu.getItemv(), + menu.getSize()); + st.pop_back(); + return ret; + } + + const auto fl_it = free_lookup.find(code); + if (fl_it != free_lookup.end()) { return fl_it->second(0); } + + std::cout << "Stamen: nothing to do..." << std::endl; + return 1; +} diff --git a/src/stamen.cpp b/src/stamen.cpp @@ -1,11 +1,20 @@ #include "stamen.h" +#include "menu.h" + #include <cmath> #include <format> #include <iostream> #include <ostream> #include <variant> -int stamen_builtin_display(const char *title, const ::item_t itemv[], int size) { +int stamen_dynamic(void) { return Menu::dynamic(); } +void stamen_read(const char *filename) { Menu::read(filename); } +void stamen_insert(const char *code, stamen_callback_f callback) { + Menu::insert(code, callback); +} + +int stamen_builtin_display(const char *title, const stamen_item_t itemv[], + int size) { const size_t digits = size_t(std::log10(size)) + 1; const auto items = std::span(itemv, size_t(size)); int choice = 0; @@ -25,7 +34,7 @@ int stamen_builtin_display(const char *title, const ::item_t itemv[], int size) } std::cout << std::format("Choice: {}\n\n", items[choice].prompt); - const int res = items[choice].callback(); + const int res = items[choice].callback(choice); if (res > 1) return res - 1; else