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:
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