stamenStatic Menu Generator | 
          
| git clone git://git.dimitrijedobrota.com/stamen.git | 
| Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | 
| commit | b833ffadb09c2e07d744ac2e30ecb0562d8b89be | 
| parent | 1d14de3dcbe573ba1a156facdc0d477558f3c97e | 
| author | Dimitrije Dobrota < mail@dimitrijedobrota.com > | 
| date | Thu, 9 Nov 2023 18:53:33 +0000 | 
Refactoring
* Back to static menu interface
* Greatly simplified the implementation without loss of functionality
| M | CMakeLists.txt | | | + - | 
| M | include/menu.h | | | +++++++++++++++++++++++++++ ------------------------------------------------------- | 
| M | src/CMakeLists.txt | | | + - | 
| D | src/display.cpp | | | ------------------------------------------------------------ | 
| M | src/main.cpp | | | ++++++++ -------- | 
| M | src/menu.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
6 files changed, 97 insertions(+), 128 deletions(-)
diff --git a/ CMakeLists.txt b/ CMakeLists.txt
          @@ -3,7 +3,7 @@ 
          set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
        
        
          project(
              Menu
              VERSION 0.0.2
              VERSION 0.0.3
              DESCRIPTION "Experimentation with dinamic menus"
              LANGUAGES CXX
          )
        
        diff --git a/ include/menu.h b/ include/menu.h
@@ -10,46 +10,16 @@
using json = nlohmann::json;
          class EMenu_callback : std::exception {
            virtual const char *what() const noexcept override {
              return "Unknown callback function or menu method";
            }
          };
          class EMenu_call : std::exception {
            virtual const char *what() const noexcept override {
              return "Trying a callback that doesn't exist";
            }
          };
          class Menu {
          public:
            typedef int (*callback_f)(void);
            Menu(const std::string &s) {
              for (const auto &menu : json::parse(std::fstream(s))) {
                const json &items = menu["items"];
                const function_t *mf =
                    new function_t(*this, menu["name"], {items.begin(), items.end()});
                lookup.insert({menu["code"], callback_t(mf)});
            class EMenu : std::exception {
              virtual const char *what() const noexcept override {
                return "Trying to access an unknown menu";
              }
            }
            void print(const std::string code = "main", const int depth = 1) const;
            void operator()() const { get_callback("main")(); }
            struct record_t {
              const std::string code;
              const callback_f callback;
            };
            void insert(const record_t &record) {
              lookup.insert({record.code, record.callback});
            }
            void insert(const std::vector<record_t> &records) {
              for (const auto &record : records) { insert(record); }
            }
          private:
            struct item_t {
              const std::string prompt;
        
        
          @@ -60,40 +30,40 @@ 
          private:
        
        
              item_t(const json &j) : item_t(j["prompt"], j["callback"]) {}
            };
            struct function_t {
              friend class Menu;
              function_t(const Menu &m, const std::string &n,
                         const std::vector<item_t> &i)
                  : menu(m), name(n), items(i) {}
            Menu(const json &json_data)
                : name(json_data["name"]), code(json_data["code"]),
                  items({json_data["items"].begin(), json_data["items"].end()}) {}
              int display() const;
            Menu(const std::string code, const callback_f callback)
                : name(code), code(code), items(), callback(callback) {}
            private:
              const Menu &menu;
              const std::string name;
              const std::vector<item_t> items;
            };
          public:
            static void read(const std::string &s) {
              for (const auto &json_data : json::parse(std::fstream(s))) {
                lookup.insert({json_data["code"], Menu(json_data)});
              }
            }
            struct callback_t {
              const function_t *menu_func = nullptr;
              const callback_f func = nullptr;
            static int start() { return getMenu("main")(); }
            static void insert(const std::string code, const callback_f callback) {
              lookup.insert({code, Menu(code, callback)});
            }
              callback_t(const callback_f f) : func(f) {}
              callback_t(const function_t *f) : menu_func(f) {}
            static void print(const std::string code = "main", const int depth = 1);
            int operator()() const;
              int operator()() const {
                if (!func && !menu_func) throw EMenu_callback();
                return func ? func() : menu_func->display();
              }
            };
          private:
            static std::unordered_map<std::string, Menu> lookup;
            const callback_t &get_callback(const std::string &s) const {
              const auto it = lookup.find(s);
              if (it == lookup.end()) throw EMenu_call();
            static const Menu &getMenu(const std::string &code) {
              const auto it = lookup.find(code);
              if (it == lookup.end()) throw EMenu();
              return it->second;
            }
            std::unordered_map<std::string, callback_t> lookup;
            const std::string name, code;
            const std::vector<item_t> items;
            const callback_f callback = nullptr;
          };
          #endif
        
        diff --git a/ src/CMakeLists.txt b/ src/CMakeLists.txt
@@ -1,5 +1,5 @@
add_library(menu
              display.cpp
              menu.cpp
          )
          target_include_directories(menu
        
        diff --git a/ src/display.cpp b/ src/display.cpp
@@ -1,60 +0,0 @@
#include "menu.h"
          #include <cmath>
          #include <format>
          #include <iostream>
          int Menu::function_t::display() const {
            int choice;
            const int n = items.size(), digits = std::log10(n) + 1;
            while (true) {
              std::cout << std::format("{}:\n", name);
              for (auto i = 0ul; i < n; i++) {
                std::cout << std::format(" {:{}}. {}\n", i, digits, items[i].prompt);
              }
              while (true) {
                std::cout << "Choose an option: ";
                if (std::cin >> choice && choice >= -1 && choice < n) {
                  if (choice == -1) {
                    std::cout << "choice: back\n";
                    return 1;
                  }
                  std::cout << std::format("Choice: {}\n\n", items[choice].prompt);
                  int res = menu.get_callback(items[choice].callback)();
                  if (--res) return res;
                  break;
                } else if (std::cin.eof()) {
                  std::cerr << "encountered end of input!\n";
                  return std::numeric_limits<int>::max();
                } else {
                  std::cin.clear();
                  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
                }
                std::cout << "Invalid option, please choose again!\n";
              }
              std::cout << std::endl;
            }
            return 1;
          }
          void Menu::print(const std::string code, const int depth) const {
            const auto it = lookup.find(code);
            if (it == lookup.end()) return;
            const callback_t &cb = it->second;
            if (depth == 1) {
              std::cout << std::format("{}({})\n", cb.menu_func->name, code);
            }
            if (cb.menu_func) {
              for (const auto &item : cb.menu_func->items) {
                std::cout << std::format("{}{}({})\n", std::string(depth << 1, ' '),
                                         item.prompt, item.callback);
                if (cb.menu_func) cb.menu_func->menu.print(item.callback, depth + 1);
              }
            }
          }
        
        diff --git a/ src/main.cpp b/ src/main.cpp
          @@ -19,13 +19,13 @@ 
          int finish(void) {
        
        
          }
          int main(void) {
            Menu menu("menu.json");
            menu.insert({
                {"algorithms", algorithms},
                {  "settings",   settings},
                {    "finish",     finish}
            });
            // menu();
            menu.print();
            Menu::read("menu.json");
            Menu::insert("algorithms", algorithms);
            Menu::insert("settings", settings);
            Menu::insert("finish", finish);
            Menu::print("main");
            Menu::start();
            return 0;
          }
        
        diff --git a/ src/menu.cpp b/ src/menu.cpp
@@ -1,2 +1,61 @@
#include "menu.h"
          #include <cmath>
          #include <format>
          #include <iostream>
          std::unordered_map<std::string, Menu> Menu::lookup;
          int Menu::operator()() const {
            if (callback) return callback();
            int choice;
            const int n = items.size(), digits = std::log10(n) + 1;
            while (true) {
              std::cout << std::format("{}:\n", name);
              for (auto i = 0ul; i < n; i++) {
                std::cout << std::format(" {:{}}. {}\n", i, digits, items[i].prompt);
              }
              while (true) {
                std::cout << "Choose an option: ";
                if (std::cin >> choice && choice >= -1 && choice < n) {
                  if (choice == -1) {
                    std::cout << "choice: back\n";
                    return 1;
                  }
                  std::cout << std::format("Choice: {}\n\n", items[choice].prompt);
                  int res = getMenu(items[choice].callback)();
                  if (--res) return res;
                  break;
                } else if (std::cin.eof()) {
                  std::cerr << "encountered end of input!\n";
                  return std::numeric_limits<int>::max();
                } else {
                  std::cin.clear();
                  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
                }
                std::cout << "Invalid option, please choose again!\n";
              }
              std::cout << std::endl;
            }
            return 1;
          }
          void Menu::print(const std::string code, const int depth) {
            const auto it = lookup.find(code);
            if (it == lookup.end()) return;
            const Menu &menu = it->second;
            if (depth == 1) { std::cout << std::format("{}({})\n", menu.name, code); }
            if (!menu.callback) {
              for (const auto &item : menu.items) {
                std::cout << std::format("{}{}({})\n", std::string(depth << 1, ' '),
                                         item.prompt, item.callback);
                menu.print(item.callback, depth + 1);
              }
            }
          }