stamen

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

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

Diffstat:
MCMakeLists.txt | 6+++---
MREADME.md | 2+-
Mdemo/CMakeLists.txt | 6+++---
Mdemo/main.cpp | 2+-
Mdemo/shared.h | 4++--
Dinclude/menu.h | 122-------------------------------------------------------------------------------
Ainclude/stamen.h | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/CMakeLists.txt | 16++++++++--------
Msrc/generate.cpp | 2+-
Dsrc/menu.cpp | 98-------------------------------------------------------------------------------
Asrc/stamen.cpp | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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); + } + } +}