stamen

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

commit 77eabec835fddbe4e8d83608b538090d8de2c3cb
parent dbf1d6332cb703654cdac22af736871ab192cf66
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Fri, 17 Nov 2023 02:54:39 +0100

LICENSE, README, and small improvement

* Add LICENSE.md
* Add README.md
* More picturesque example
* Improve naming
* Improve interface

Diffstat:
MCMakeLists.txt | 4++--
ALICENSE.md | 21+++++++++++++++++++++
AREADME.md | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdemo/demo_menu.conf | 18+++++++++---------
Mdemo/main.cpp | 20+++++++++++++++++---
Mdemo/shared.h | 4+++-
Minclude/menu.h | 13++++++++-----
Msrc/menu.cpp | 8++++----
8 files changed, 209 insertions(+), 24 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -1,9 +1,9 @@ -cmake_minimum_required(VERSION 3.27.2) +cmake_minimum_required(VERSION 3.25.2) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project( Menu - VERSION 0.0.6 + VERSION 0.0.7 DESCRIPTION "Experimentation with dinamic menus" LANGUAGES CXX ) diff --git a/LICENSE.md b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Dimitrije Dobrota + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md @@ -0,0 +1,145 @@ +# stamen + +Static menu generator written in C++20 + + +## Description + +The goal of the project is experimentation.I have been toying with flexible +menus for a while and I've noticed a lot of boilerplate code emerging. That's +why I decided to automate the task by generating menus on the fly. I aim for +maximum flexibility while maintaining minimalism. + + +## Getting Started + +### Dependencies + +* CMake 3.25.2 or latter +* Compiler with C++20 support + + +### Installing + +* Clone the repo +* Make a build folder and cd into it +* Run `cmake -DCMAKE_BUILD_TYPE=Release <path to cloned repo>` + + +### Configuration + +In order to generate the desired menu, a configuration file needs to be +written. Here is an example: +``` ++ menu_main Main Menu +- submenu_1 Enter Submenu 1 +- submenu_2 Enter Submenu 2 +- finish Quit + ++ submenu_1 Submenu 1 +- operation1 Operation 1 +- operation2 Operation 2 + ++ submenu_2 Submenu 2 +- operation1 Operation 1 +- operation2 Operation 2 +``` + +Configuration file consists of 2 types of entities: panels and items. Empty +lines are ignored. + +Panel is detonated by `+` sign at the start of the line and consists of two +parts: code(one word) and title(rest of the line). + +Item entity is detonated by `-` sign at the start of the line and consists of +two parts: code(one word) and prompt(rest of the line). + +Menu entity creates a new panel, and each subsequent menu item is added as an +option to the panel, until new panel is created. + +Panel code is an unique reference to the panel, whilst item code can be a +reference to another panel or any other function (from now on referred to as +`free function`). + + +### Usage + +Please reference demo folder for relevant usage example. + +There are a few things needed before you begin. + +* 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`. + + +#### Dynamic menu + +In dynamic mode, configuration file is read every time the program is run. In +order to invoke the menu you need to add the following code to your C++ +program: + +``` +// read the configuration +Menu::read("path to config"); + +// register free functions +Menu::insert("reference name", some_function); +... + +// start the menu on specific panel +Menu::start("panel code"); + +// print tree-like menu structure +// starting from a specific panel +// for debugging purposes +Menu::print("panel code"); +``` + +#### Static menu + +After writing a configuration file, run `./bin/generate <config file>` which +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. + +Source file contains definitions for the menu functions. It also includes +`shared.h` file which should contain declarations for all of the free functions +you have specified in the configuration. Generated source file should be +compiled with the rest of your code. You can call any function to display the +menu starting from that specific pane. + + +#### Custom display function + +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()` +member functions. + +After prompting user to select one of the items all you have to do is call +`operator()` on selected item to invoke the next panel or free function. The +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 + +* 1.0 + * Initial Release + + +## License + +This project is licensed under the MIT License - see the LICENSE.md file for details + diff --git a/demo/demo_menu.conf b/demo/demo_menu.conf @@ -7,19 +7,19 @@ + submenu_1 Submenu 1 - submenu_3 Enter Submenu 3 -- operation Operation 1 -- operation Operation 2 -- operation Operation 3 +- operation1 Operation 1 +- operation2 Operation 2 +- operation3 Operation 3 + submenu_2 Submenu 2 - submenu_3 Enter Submenu 3 -- operation Operation 1 -- operation Operation 2 -- operation Operation 3 +- operation1 Operation 1 +- operation2 Operation 2 +- operation3 Operation 3 + submenu_3 Submenu 3 -- operation Operation 1 -- operation Operation 2 -- operation Operation 3 +- operation1 Operation 1 +- operation2 Operation 2 +- operation3 Operation 3 diff --git a/demo/main.cpp b/demo/main.cpp @@ -4,12 +4,24 @@ const Menu::display_f Menu::display = Menu::builtinDisplay; -int operation(void) { - std::cout << "operation" << std::endl; +int operation1(void) { + std::cout << "operation 1" << std::endl; std::cout << "Some operation is done" << std::endl; return 1; } +int operation2(void) { + std::cout << "operation 2" << std::endl; + std::cout << "Some other operation is done" << std::endl; + return 1; +} + +int operation3(void) { + std::cout << "operation 3" << std::endl; + std::cout << "Yet another operation is done" << std::endl; + return 1; +} + int finish(void) { std::cout << "finishing..." << std::endl; exit(0); @@ -30,7 +42,9 @@ int main(int argc, const char *argv[]) { Menu::read(base + "/demo_menu.conf"); Menu::insert("finish", finish); - Menu::insert("operation", operation); + Menu::insert("operation1", operation1); + Menu::insert("operation2", operation2); + Menu::insert("operation3", operation3); static const Menu::item_t items[] = { { menu_static_run, "Run statically generated menu"}, diff --git a/demo/shared.h b/demo/shared.h @@ -1,7 +1,9 @@ #ifndef DEMO_SHARED_H #define DEMO_SHARED_H -int operation(void); int finish(void); +int operation1(void); +int operation2(void); +int operation3(void); #endif diff --git a/include/menu.h b/include/menu.h @@ -39,16 +39,19 @@ public: item_t(const callback_f func, const std::string &prompt) : callback(func), prompt(prompt) {} - private: + 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; - callback_f callback = nullptr; + const callback_f callback = nullptr; }; static void read(const std::string &s) { @@ -84,10 +87,9 @@ public: static void generateSource(std::ostream &os); static void generateInclude(std::ostream &os); - typedef int (*display_f)(const std::string &name, const item_t items[], - std::size_t size); + typedef int (*display_f)(const std::string &, const item_t[], std::size_t); static const display_f display; - static int builtinDisplay(const std::string &name, const item_t items[], + static int builtinDisplay(const std::string &title, const item_t items[], std::size_t size); int operator()() const { @@ -105,6 +107,7 @@ private: 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(); diff --git a/src/menu.cpp b/src/menu.cpp @@ -9,14 +9,14 @@ std::unordered_map<std::string, Menu> Menu::lookup; -int Menu::builtinDisplay(const std::string &name, const item_t items[], +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", name); + std::cout << std::format("{}:\n", title); for (auto i = 0ul; i < size; i++) { - std::cout << std::format(" {:{}}. {}\n", i, digits, items[i].prompt); + std::cout << std::format(" {:{}}. {}\n", i, digits, items[i].getPrompt()); } while (true) { @@ -28,7 +28,7 @@ int Menu::builtinDisplay(const std::string &name, const item_t items[], } const item_t &chosen = items[choice]; - std::cout << std::format("Choice: {}\n\n", chosen.prompt); + std::cout << std::format("Choice: {}\n\n", chosen.getPrompt()); const int res = chosen(); if (res > 1) return res - 1;