stamenStatic Menu Generator |
git clone git://git.dimitrijedobrota.com/stamen.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | |
commit | b833ffadb09c2e07d744ac2e30ecb0562d8b89be |
parent | 1d14de3dcbe573ba1a156facdc0d477558f3c97e |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Thu, 9 Nov 2023 18:53:33 +0000 |
Refactoring * Back to static menu interface * Greatly simplified the implementation without loss of functionality
Diffstat:M | CMakeLists.txt | | | +- |
M | include/menu.h | | | +++++++++++++++++++++++++++------------------------------------------------------- |
M | src/CMakeLists.txt | | | +- |
D | src/display.cpp | | | ------------------------------------------------------------ |
M | src/main.cpp | | | ++++++++-------- |
M | src/menu.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 97 insertions(+), 128 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(
Menu
VERSION 0.0.2
VERSION 0.0.3
DESCRIPTION "Experimentation with dinamic menus"
LANGUAGES CXX
)
diff --git a/include/menu.h b/include/menu.h
@@ -10,46 +10,16 @@
using json = nlohmann::json;
class EMenu_callback : std::exception {
virtual const char *what() const noexcept override {
return "Unknown callback function or menu method";
}
};
class EMenu_call : std::exception {
virtual const char *what() const noexcept override {
return "Trying a callback that doesn't exist";
}
};
class Menu {
public:
typedef int (*callback_f)(void);
Menu(const std::string &s) {
for (const auto &menu : json::parse(std::fstream(s))) {
const json &items = menu["items"];
const function_t *mf =
new function_t(*this, menu["name"], {items.begin(), items.end()});
lookup.insert({menu["code"], callback_t(mf)});
class EMenu : std::exception {
virtual const char *what() const noexcept override {
return "Trying to access an unknown menu";
}
}
void print(const std::string code = "main", const int depth = 1) const;
void operator()() const { get_callback("main")(); }
struct record_t {
const std::string code;
const callback_f callback;
};
void insert(const record_t &record) {
lookup.insert({record.code, record.callback});
}
void insert(const std::vector<record_t> &records) {
for (const auto &record : records) { insert(record); }
}
private:
struct item_t {
const std::string prompt;
@@ -60,40 +30,40 @@ private:
item_t(const json &j) : item_t(j["prompt"], j["callback"]) {}
};
struct function_t {
friend class Menu;
function_t(const Menu &m, const std::string &n,
const std::vector<item_t> &i)
: menu(m), name(n), items(i) {}
Menu(const json &json_data)
: name(json_data["name"]), code(json_data["code"]),
items({json_data["items"].begin(), json_data["items"].end()}) {}
int display() const;
Menu(const std::string code, const callback_f callback)
: name(code), code(code), items(), callback(callback) {}
private:
const Menu &menu;
const std::string name;
const std::vector<item_t> items;
};
public:
static void read(const std::string &s) {
for (const auto &json_data : json::parse(std::fstream(s))) {
lookup.insert({json_data["code"], Menu(json_data)});
}
}
struct callback_t {
const function_t *menu_func = nullptr;
const callback_f func = nullptr;
static int start() { return getMenu("main")(); }
static void insert(const std::string code, const callback_f callback) {
lookup.insert({code, Menu(code, callback)});
}
callback_t(const callback_f f) : func(f) {}
callback_t(const function_t *f) : menu_func(f) {}
static void print(const std::string code = "main", const int depth = 1);
int operator()() const;
int operator()() const {
if (!func && !menu_func) throw EMenu_callback();
return func ? func() : menu_func->display();
}
};
private:
static std::unordered_map<std::string, Menu> lookup;
const callback_t &get_callback(const std::string &s) const {
const auto it = lookup.find(s);
if (it == lookup.end()) throw EMenu_call();
static const Menu &getMenu(const std::string &code) {
const auto it = lookup.find(code);
if (it == lookup.end()) throw EMenu();
return it->second;
}
std::unordered_map<std::string, callback_t> lookup;
const std::string name, code;
const std::vector<item_t> items;
const callback_f callback = nullptr;
};
#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
@@ -1,5 +1,5 @@
add_library(menu
display.cpp
menu.cpp
)
target_include_directories(menu
diff --git a/src/display.cpp b/src/display.cpp
@@ -1,60 +0,0 @@
#include "menu.h"
#include <cmath>
#include <format>
#include <iostream>
int Menu::function_t::display() const {
int choice;
const int n = items.size(), digits = std::log10(n) + 1;
while (true) {
std::cout << std::format("{}:\n", name);
for (auto i = 0ul; i < n; i++) {
std::cout << std::format(" {:{}}. {}\n", i, digits, items[i].prompt);
}
while (true) {
std::cout << "Choose an option: ";
if (std::cin >> choice && choice >= -1 && choice < n) {
if (choice == -1) {
std::cout << "choice: back\n";
return 1;
}
std::cout << std::format("Choice: {}\n\n", items[choice].prompt);
int res = menu.get_callback(items[choice].callback)();
if (--res) return res;
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::print(const std::string code, const int depth) const {
const auto it = lookup.find(code);
if (it == lookup.end()) return;
const callback_t &cb = it->second;
if (depth == 1) {
std::cout << std::format("{}({})\n", cb.menu_func->name, code);
}
if (cb.menu_func) {
for (const auto &item : cb.menu_func->items) {
std::cout << std::format("{}{}({})\n", std::string(depth << 1, ' '),
item.prompt, item.callback);
if (cb.menu_func) cb.menu_func->menu.print(item.callback, depth + 1);
}
}
}
diff --git a/src/main.cpp b/src/main.cpp
@@ -19,13 +19,13 @@ int finish(void) {
}
int main(void) {
Menu menu("menu.json");
menu.insert({
{"algorithms", algorithms},
{ "settings", settings},
{ "finish", finish}
});
// menu();
menu.print();
Menu::read("menu.json");
Menu::insert("algorithms", algorithms);
Menu::insert("settings", settings);
Menu::insert("finish", finish);
Menu::print("main");
Menu::start();
return 0;
}
diff --git a/src/menu.cpp b/src/menu.cpp
@@ -1,2 +1,61 @@
#include "menu.h"
#include <cmath>
#include <format>
#include <iostream>
std::unordered_map<std::string, Menu> Menu::lookup;
int Menu::operator()() const {
if (callback) return callback();
int choice;
const int n = items.size(), digits = std::log10(n) + 1;
while (true) {
std::cout << std::format("{}:\n", name);
for (auto i = 0ul; i < n; i++) {
std::cout << std::format(" {:{}}. {}\n", i, digits, items[i].prompt);
}
while (true) {
std::cout << "Choose an option: ";
if (std::cin >> choice && choice >= -1 && choice < n) {
if (choice == -1) {
std::cout << "choice: back\n";
return 1;
}
std::cout << std::format("Choice: {}\n\n", items[choice].prompt);
int res = getMenu(items[choice].callback)();
if (--res) return res;
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::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.name, code); }
if (!menu.callback) {
for (const auto &item : menu.items) {
std::cout << std::format("{}{}({})\n", std::string(depth << 1, ' '),
item.prompt, item.callback);
menu.print(item.callback, depth + 1);
}
}
}