stamenStatic Menu Generator | 
          
| git clone git://git.dimitrijedobrota.com/stamen.git | 
| Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | 
| 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
| M | CMakeLists.txt | | | + - | 
| M | demo/CMakeLists.txt | | | ++++++++++++++ | 
| A | demo/dynamic.cpp | | | +++++++++++++++++++++++++++++++ | 
| M | demo/main.c | | | + - | 
| M | demo/main.cpp | | | +++++ ----- | 
| M | demo/shared.h | | | ++++ ---- | 
| M | include/menu.h | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ------------------- | 
| M | include/stamen.h | | | ++++++++++ ----- | 
| M | src/CMakeLists.txt | | | +++ --- | 
| M | src/generate.cpp | | | ++++++++++ ------- | 
| M | src/menu.cpp | | | +++++++++++++++++++++++++++++++++++ ----------- | 
| M | src/stamen.cpp | | | +++++++++++ -- | 
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