stamenStatic Menu Generator | 
          
| git clone git://git.dimitrijedobrota.com/stamen.git | 
| Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | 
| commit | 26a75511cae93934528c2c3e09c1307d20086552 | 
| parent | 77eabec835fddbe4e8d83608b538090d8de2c3cb | 
| author | Dimitrije Dobrota < mail@dimitrijedobrota.com > | 
| date | Fri, 17 Nov 2023 02:22:39 +0000 | 
Rebrand
* Rebrand project to stamen
* Add header guard to generated include file
* Fix bug where -1 would not go back
| M | CMakeLists.txt | | | +++ --- | 
| M | README.md | | | + - | 
| M | demo/CMakeLists.txt | | | +++ --- | 
| M | demo/main.cpp | | | + - | 
| M | demo/shared.h | | | ++ -- | 
| D | include/menu.h | | | --------------------------------------------------------------------------------- | 
| A | include/stamen.h | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| M | src/CMakeLists.txt | | | ++++++++ -------- | 
| M | src/generate.cpp | | | + - | 
| D | src/menu.cpp | | | --------------------------------------------------------------------------------- | 
| A | src/stamen.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
11 files changed, 242 insertions(+), 239 deletions(-)
diff --git a/ CMakeLists.txt b/ CMakeLists.txt
          @@ -2,9 +2,9 @@ 
          cmake_minimum_required(VERSION 3.25.2)
        
        
          set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
          project(
              Menu
              VERSION 0.0.7
              DESCRIPTION "Experimentation with dinamic menus"
              Stamen
              VERSION 0.0.8
              DESCRIPTION "Static menu generator"
              LANGUAGES CXX
          )
          diff --git a/ README.md b/ README.md
          @@ -107,7 +107,7 @@ 
          will create source file and include file in the current directory with the name
        
        
          as the configuration file but with extensions `.cpp` and `.h` respectively.
          Include file will contain declarations for all of the menu functions inside
          `menu` namespace. You should include this file in your code.
          `stamen` namespace. You should include this file in your code.
          Source file contains definitions for the menu functions. It also includes
          `shared.h` file which should contain declarations for all of the free functions
        
        diff --git a/ demo/CMakeLists.txt b/ demo/CMakeLists.txt
          @@ -4,13 +4,13 @@ 
          add_executable(demo
        
        
          )
          target_link_libraries(demo
              menu
              stamen
          )
          ADD_CUSTOM_COMMAND(
              OUTPUT demo_menu.h demo_menu.cpp
              COMMAND ${CMAKE_BINARY_DIR}/bin/generate ${CMAKE_CURRENT_SOURCE_DIR}/demo_menu.conf
              DEPENDS demo_menu.conf generate
              COMMAND ${CMAKE_BINARY_DIR}/bin/stamen-generate ${CMAKE_CURRENT_SOURCE_DIR}/demo_menu.conf
              DEPENDS demo_menu.conf stamen-generate
              COMMENT "Generating menu files"
          )
          diff --git a/ demo/main.cpp b/ demo/main.cpp
          @@ -27,7 +27,7 @@ 
          int finish(void) {
        
        
            exit(0);
          }
          int menu_static_run(void) { return menu::menu_main(); }
          int menu_static_run(void) { return stamen::menu_main(); }
          int menu_dynamic_run(void) { return Menu::start("menu_main"); }
          int menu_dynamic_print(void) {
        
        diff --git a/ demo/shared.h b/ demo/shared.h
@@ -1,5 +1,5 @@
#ifndef DEMO_SHARED_H
          #define DEMO_SHARED_H
          #ifndef STAMEN_DEMO_SHARED_H
          #define STAMEN_DEMO_SHARED_H
          int finish(void);
          int operation1(void);
        
        diff --git a/ include/menu.h b/ include/menu.h
@@ -1,122 +0,0 @@
#ifndef MENU_H
          #define MENU_H
          #include <exception>
          #include <format>
          #include <fstream>
          #include <iostream>
          #include <memory>
          #include <sstream>
          #include <string>
          #include <tuple>
          #include <unordered_map>
          #include <vector>
          class Menu {
            Menu(const Menu &) = delete;
            Menu &operator=(const Menu &) = delete;
            struct private_ctor_t {};
          public:
            typedef int (*callback_f)(void);
            Menu(private_ctor_t, const std::string &code, const std::string &prompt)
                : Menu(code, prompt) {}
            Menu(private_ctor_t, const std::string &code, const callback_f callback)
                : Menu(code, callback) {}
            class EMenu : std::exception {
              virtual const char *what() const noexcept override {
                return "Trying to access an unknown menu";
              }
            };
            struct item_t {
              friend class Menu;
              item_t(const callback_f func, const std::string &prompt)
                  : callback(func), prompt(prompt) {}
              const std::string getPrompt(void) const { return prompt; }
              const callback_f getCallback(void) const { return callback; }
              int operator()(void) const {
                return callback ? callback() : getMenu(code)();
              }
            private:
              item_t(const std::string &code, const std::string &prompt)
                  : code(code), prompt(prompt) {}
              const std::string prompt, code;
              const callback_f callback = nullptr;
            };
            static void read(const std::string &s) {
              std::string line, delim, code, prompt;
              std::fstream fs(s);
              char tmp;
              lookup_t::iterator last = 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));
                  last = iter;
                } else {
                  last->second.items.push_back({code, prompt});
                }
              }
            }
            static int start(const std::string &entry) { return getMenu(entry)(); }
            static void insert(const std::string &code, const callback_f callback) {
              lookup.emplace(std::piecewise_construct, std::forward_as_tuple(code),
                             std::forward_as_tuple(private_ctor_t{}, code, callback));
            }
            static void print(const std::string &entry) { print(entry, 1); }
            static void generateSource(std::ostream &os);
            static void generateInclude(std::ostream &os);
            typedef int (*display_f)(const std::string &, const item_t[], std::size_t);
            static const display_f display;
            static int builtinDisplay(const std::string &title, const item_t items[],
                                      std::size_t size);
            int operator()() const {
              return callback ? callback() : display(title, items.data(), items.size());
            }
          private:
            Menu(const std::string &code, const std::string &prompt)
                : code(code), title(prompt) {}
            Menu(const std::string &code, const callback_f callback)
                : code(code), title(code), callback(callback) {}
            typedef std::unordered_map<std::string, Menu> lookup_t;
            static lookup_t lookup;
            static void print(const std::string &entry, const int depth);
            static const Menu &getMenu(const std::string &code) {
              const auto it = lookup.find(code);
              if (it == lookup.end()) throw EMenu();
              return it->second;
            }
            const std::string code, title;
            const callback_f callback = nullptr;
            std::vector<item_t> items;
          };
          #endif
        
        diff --git a/ include/stamen.h b/ include/stamen.h
@@ -0,0 +1,122 @@
#ifndef STAMEN_H
          #define STAMEN_H
          #include <exception>
          #include <format>
          #include <fstream>
          #include <iostream>
          #include <memory>
          #include <sstream>
          #include <string>
          #include <tuple>
          #include <unordered_map>
          #include <vector>
          class Menu {
            Menu(const Menu &) = delete;
            Menu &operator=(const Menu &) = delete;
            struct private_ctor_t {};
          public:
            typedef int (*callback_f)(void);
            Menu(private_ctor_t, const std::string &code, const std::string &prompt)
                : Menu(code, prompt) {}
            Menu(private_ctor_t, const std::string &code, const callback_f callback)
                : Menu(code, callback) {}
            class EMenu : std::exception {
              virtual const char *what() const noexcept override {
                return "Trying to access an unknown menu";
              }
            };
            struct item_t {
              friend class Menu;
              item_t(const callback_f func, const std::string &prompt)
                  : callback(func), prompt(prompt) {}
              const std::string getPrompt(void) const { return prompt; }
              const callback_f getCallback(void) const { return callback; }
              int operator()(void) const {
                return callback ? callback() : getMenu(code)();
              }
            private:
              item_t(const std::string &code, const std::string &prompt)
                  : code(code), prompt(prompt) {}
              const std::string prompt, code;
              const callback_f callback = nullptr;
            };
            static void read(const std::string &s) {
              std::string line, delim, code, prompt;
              std::fstream fs(s);
              char tmp;
              lookup_t::iterator last = 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));
                  last = iter;
                } else {
                  last->second.items.push_back({code, prompt});
                }
              }
            }
            static int start(const std::string &entry) { return getMenu(entry)(); }
            static void insert(const std::string &code, const callback_f callback) {
              lookup.emplace(std::piecewise_construct, std::forward_as_tuple(code),
                             std::forward_as_tuple(private_ctor_t{}, code, callback));
            }
            static void print(const std::string &entry) { print(entry, 1); }
            static void generateSource(std::ostream &os);
            static void generateInclude(std::ostream &os);
            typedef int (*display_f)(const std::string &, const item_t[], std::size_t);
            static const display_f display;
            static int builtinDisplay(const std::string &title, const item_t items[],
                                      std::size_t size);
            int operator()() const {
              return callback ? callback() : display(title, items.data(), items.size());
            }
          private:
            Menu(const std::string &code, const std::string &prompt)
                : code(code), title(prompt) {}
            Menu(const std::string &code, const callback_f callback)
                : code(code), title(code), callback(callback) {}
            typedef std::unordered_map<std::string, Menu> lookup_t;
            static lookup_t lookup;
            static void print(const std::string &entry, const int depth);
            static const Menu &getMenu(const std::string &code) {
              const auto it = lookup.find(code);
              if (it == lookup.end()) throw EMenu();
              return it->second;
            }
            const std::string code, title;
            const callback_f callback = nullptr;
            std::vector<item_t> items;
          };
          #endif
        
        diff --git a/ src/CMakeLists.txt b/ src/CMakeLists.txt
@@ -1,22 +1,22 @@
add_library(menu
              menu.cpp
          add_library(stamen
              stamen.cpp
          )
          target_include_directories(menu
          target_include_directories(stamen
              PUBLIC ../include
          )
          add_executable(generate generate.cpp)
          add_executable(stamen-generate generate.cpp)
          target_link_libraries(generate
              PRIVATE menu
          target_link_libraries(stamen-generate
              PRIVATE stamen
          )
          target_include_directories(generate
          target_include_directories(stamen-generate
              PUBLIC ../include
          )
          set_target_properties(generate PROPERTIES
          set_target_properties(stamen-generate PROPERTIES
              VERSION ${PROJECT_VERSION}
              SOVERSION ${PROJECT_VERSION_MAJOR}
              RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
        
        diff --git a/ src/generate.cpp b/ src/generate.cpp
@@ -1,4 +1,4 @@
#include "menu.h"
          #include "stamen.h"
          #include <string>
          const Menu::display_f Menu::display = Menu::builtinDisplay;
        
        diff --git a/ src/menu.cpp b/ src/menu.cpp
@@ -1,98 +0,0 @@
#include "../include/menu.h"
          #include <cmath>
          #include <format>
          #include <fstream>
          #include <iostream>
          #include <ostream>
          #include <stack>
          #include <unordered_set>
          std::unordered_map<std::string, Menu> Menu::lookup;
          int Menu::builtinDisplay(const std::string &title, const item_t items[],
                                   std::size_t size) {
            int choice;
            const int digits = std::log10(size) + 1;
            while (true) {
              std::cout << std::format("{}:\n", title);
              for (auto i = 0ul; i < size; i++) {
                std::cout << std::format(" {:{}}. {}\n", i, digits, items[i].getPrompt());
              }
              while (true) {
                std::cout << "Choose an option: ";
                if (std::cin >> choice && choice >= -1 && choice < size) {
                  if (choice == -1) {
                    std::cout << "Choice: back\n";
                    return 1;
                  }
                  const item_t &chosen = items[choice];
                  std::cout << std::format("Choice: {}\n\n", chosen.getPrompt());
                  const int res = chosen();
                  if (res > 1) return res - 1;
                  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::generateInclude(std::ostream &os) {
            os << "#include \"menu.h\"\n\n";
            os << "namespace menu {\n\n";
            for (const auto &[code, _] : lookup) {
              const Menu &menu = getMenu(code);
              if (menu.callback) continue;
              os << std::format("int {}(void);\n", menu.code);
            }
            os << "\n}\n";
          }
          void Menu::generateSource(std::ostream &os) {
            os << "#include \"menu.h\"\n";
            os << "#include \"shared.h\"\n";
            os << "\nnamespace menu {\n";
            for (const auto &[code, _] : lookup) {
              const Menu &menu = getMenu(code);
              if (menu.callback) continue;
              os << std::format("int {}(void) {{\n", menu.code);
              os << std::format("\tstatic const Menu::item_t items[] = {{\n");
              for (const auto &item : menu.items) {
                os << std::format("\t\t{{{}, \"{}\"}},\n", item.code, item.prompt);
              }
              os << std::format("\t}};\n");
              os << std::format("\treturn Menu::display(\"{}\", items, "
                                "sizeof(items) / sizeof(Menu::item_t));\n",
                                menu.title);
              os << std::format("}}\n\n");
            }
            os << "\n}\n";
          }
          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.title, code);
            if (!menu.callback) {
              for (const auto &item : menu.items) {
                std::cout << std::format("{}{}({})\n", std::string(depth << 1, ' '),
                                         item.prompt, item.code);
                menu.print(item.code, depth + 1);
              }
            }
          }
        
        diff --git a/ src/stamen.cpp b/ src/stamen.cpp
@@ -0,0 +1,101 @@
#include "../include/stamen.h"
          #include <cmath>
          #include <format>
          #include <fstream>
          #include <iostream>
          #include <ostream>
          #include <stack>
          #include <unordered_set>
          std::unordered_map<std::string, Menu> Menu::lookup;
          int Menu::builtinDisplay(const std::string &title, const item_t items[],
                                   std::size_t size) {
            int choice;
            const int digits = std::log10(size) + 1;
            while (true) {
              std::cout << std::format("{}:\n", title);
              for (auto i = 0ul; i < size; i++) {
                std::cout << std::format(" {:{}}. {}\n", i, digits, items[i].getPrompt());
              }
              while (true) {
                std::cout << "Choose an option: ";
                if (std::cin >> choice && choice >= -1 && choice < (int)size) {
                  if (choice == -1) {
                    std::cout << "Choice: back\n";
                    return 1;
                  }
                  const item_t &chosen = items[choice];
                  std::cout << std::format("Choice: {}\n\n", chosen.getPrompt());
                  const int res = chosen();
                  if (res > 1) return res - 1;
                  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::generateInclude(std::ostream &os) {
            os << "#ifndef STAMEN_MENU_H\n";
            os << "#define STAMEN_MENU_H\n\n";
            os << "#include <stamen.h>\n\n";
            os << "namespace stamen {\n\n";
            for (const auto &[code, _] : lookup) {
              const Menu &menu = getMenu(code);
              if (menu.callback) continue;
              os << std::format("int {}(void);\n", menu.code);
            }
            os << "\n}\n";
            os << "#endif\n";
          }
          void Menu::generateSource(std::ostream &os) {
            os << "#include <stamen.h>\n";
            os << "#include \"shared.h\"\n";
            os << "\nnamespace stamen {\n";
            for (const auto &[code, _] : lookup) {
              const Menu &menu = getMenu(code);
              if (menu.callback) continue;
              os << std::format("int {}(void) {{\n", menu.code);
              os << std::format("\tstatic const Menu::item_t items[] = {{\n");
              for (const auto &item : menu.items) {
                os << std::format("\t\t{{{}, \"{}\"}},\n", item.code, item.prompt);
              }
              os << std::format("\t}};\n");
              os << std::format("\treturn Menu::display(\"{}\", items, "
                                "sizeof(items) / sizeof(Menu::item_t));\n",
                                menu.title);
              os << std::format("}}\n\n");
            }
            os << "\n}\n";
          }
          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.title, code);
            if (!menu.callback) {
              for (const auto &item : menu.items) {
                std::cout << std::format("{}{}({})\n", std::string(depth << 1, ' '),
                                         item.prompt, item.code);
                menu.print(item.code, depth + 1);
              }
            }
          }