stamen

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

commit 4353db0fc0af665ffe8a735c52da61b641ab92c9
parent 26a75511cae93934528c2c3e09c1307d20086552
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Fri, 17 Nov 2023 03:02:22 +0000

stamen namespace, header only

* Put everything inside of stamen namespace
* Main functionality is provided in header only
* In order to use builtinDisplay you need to link against stamen library
* Reflect the changes in demo and README
* Improve code redability

Diffstat:
MCMakeLists.txt | 2+-
MREADME.md | 13+++++++++----
Mdemo/CMakeLists.txt | 1+
Mdemo/main.cpp | 6+++++-
Minclude/stamen.h | 137++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/CMakeLists.txt | 12+++++++++---
Msrc/generate.cpp | 2+-
Msrc/stamen.cpp | 65++++++-----------------------------------------------------------
8 files changed, 133 insertions(+), 105 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project( Stamen - VERSION 0.0.8 + VERSION 0.0.9 DESCRIPTION "Static menu generator" LANGUAGES CXX ) diff --git a/README.md b/README.md @@ -68,13 +68,15 @@ Please reference demo folder for relevant usage example. There are a few things needed before you begin. +* Everything is contained in the `stamen namespace` * Panel and item codes must be one word. In addition they must be valid C++ function names if static menu is to be build correctly. * Each free function must have `int name(void)` signature as prescribed by `Menu::callback_f`. * You must set a value of the static variable `const Menu::display_f Menu::display` to specify function used for displaying the menu. You can start -by using a build in `Menu::builtinDisplay`. +by using a build in `stamen::builtinDisplay`, just make sure to link stamen library +while building your project. #### Dynamic menu @@ -84,6 +86,9 @@ order to invoke the menu you need to add the following code to your C++ program: ``` +// shorthand +using stamen::Menu; + // read the configuration Menu::read("path to config"); @@ -118,6 +123,9 @@ menu starting from that specific pane. #### Custom display function +Please refer to the implementation of `stamen::builtinDisplay` to get a general +idea of the direction. + A display function should have `int name(const std::string&, const Menu::item_t[], std::size_t)` signature as prescribed by `Menu::display_f`. To get information about the specific item use `getPrompt()` and `getCallback()` @@ -129,9 +137,6 @@ return type of int is intended to be used as a measure how many panels back should be backtracked after a free function terminates, but you can use in any way you see fit. -Please refer to the implementation of `Menu::builtinDisplay` to get a general -idea of the direction. - ## Version History diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable(demo target_link_libraries(demo stamen + stamen-display ) ADD_CUSTOM_COMMAND( diff --git a/demo/main.cpp b/demo/main.cpp @@ -2,7 +2,11 @@ #include "demo_menu.h" -const Menu::display_f Menu::display = Menu::builtinDisplay; +using stamen::Menu; + +// need to link against stamen library +// in order to use stamen::BuiltinDisplay +const Menu::display_f Menu::display = stamen::builtinDisplay; int operation1(void) { std::cout << "operation 1" << std::endl; diff --git a/include/stamen.h b/include/stamen.h @@ -5,22 +5,23 @@ #include <format> #include <fstream> #include <iostream> -#include <memory> #include <sstream> #include <string> #include <tuple> #include <unordered_map> #include <vector> +namespace stamen { + class Menu { +public: + typedef int (*callback_f)(void); + 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) {} @@ -54,44 +55,18 @@ public: 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}); - } - } - } + typedef int (*display_f)(const std::string &, const item_t[], std::size_t); + static const display_f display; - 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 read(const std::string &s); + static void insert(const std::string &code, const callback_f callback); + static int start(const std::string &entry) { return getMenu(entry)(); } 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()); } @@ -104,11 +79,15 @@ private: : code(code), title(code), callback(callback) {} typedef std::unordered_map<std::string, Menu> lookup_t; - static lookup_t lookup; + static lookup_t &getLookup(void) { + static lookup_t lookup; + return lookup; + } static void print(const std::string &entry, const int depth); static const Menu &getMenu(const std::string &code) { + static lookup_t &lookup = getLookup(); const auto it = lookup.find(code); if (it == lookup.end()) throw EMenu(); return it->second; @@ -119,4 +98,90 @@ private: std::vector<item_t> items; }; +inline void Menu::read(const std::string &s) { + std::string line, delim, code, prompt; + std::fstream fs(s); + char tmp; + + lookup_t &lookup = getLookup(); + 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}); + } + } +} + +inline void Menu::insert(const std::string &code, const callback_f callback) { + getLookup().emplace(std::piecewise_construct, std::forward_as_tuple(code), + std::forward_as_tuple(private_ctor_t{}, code, callback)); +} + +inline 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, _] : getLookup()) { + const Menu &menu = getMenu(code); + if (menu.callback) continue; + os << std::format("int {}(void);\n", menu.code); + } + os << "\n}\n"; + os << "#endif\n"; +} + +inline void Menu::generateSource(std::ostream &os) { + os << "#include <stamen.h>\n"; + os << "#include \"shared.h\"\n"; + os << "\nnamespace stamen {\n"; + for (const auto &[code, _] : getLookup()) { + 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"; +} + +inline void Menu::print(const std::string &code, const int depth) { + static lookup_t &lookup = getLookup(); + 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); + } + } +} + +int builtinDisplay(const std::string &title, const Menu::item_t items[], + std::size_t size); + +} // namespace stamen + #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt @@ -1,9 +1,15 @@ -add_library(stamen +add_library(stamen INTERFACE) + +target_include_directories(stamen + INTERFACE ../include +) + +add_library(stamen-display stamen.cpp ) -target_include_directories(stamen - PUBLIC ../include +target_link_libraries(stamen-display + PRIVATE stamen ) add_executable(stamen-generate generate.cpp) diff --git a/src/generate.cpp b/src/generate.cpp @@ -1,7 +1,7 @@ #include "stamen.h" #include <string> -const Menu::display_f Menu::display = Menu::builtinDisplay; +using namespace stamen; int main(const int argc, const char *argv[]) { if (argc != 2) { diff --git a/src/stamen.cpp b/src/stamen.cpp @@ -1,16 +1,13 @@ #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; +namespace stamen { -int Menu::builtinDisplay(const std::string &title, const item_t items[], - std::size_t size) { +int builtinDisplay(const std::string &title, const Menu::item_t items[], + std::size_t size) { int choice; const int digits = std::log10(size) + 1; while (true) { @@ -27,12 +24,12 @@ int Menu::builtinDisplay(const std::string &title, const item_t items[], return 1; } - const item_t &chosen = items[choice]; + const Menu::item_t &chosen = items[choice]; std::cout << std::format("Choice: {}\n\n", chosen.getPrompt()); const int res = chosen(); if (res > 1) return res - 1; + else break; - break; } else if (std::cin.eof()) { std::cerr << "encountered end of input!\n"; return std::numeric_limits<int>::max(); @@ -48,54 +45,4 @@ int Menu::builtinDisplay(const std::string &title, const item_t items[], 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); - } - } -} +} // namespace stamen