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:
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;