stamen

Static Menu Generator
git clone git://git.dimitrijedobrota.com/stamen.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING |

commit08ff305c12bb4a7f9cc10610f51616b096647aa6
parentf4fed18ad7916907f96caaa641143d3ed00f02f6
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateThu, 30 Nov 2023 19:11:48 +0000

Great simplification * Remove option for creating dynamic menus * Greatly simplify handling and future extensions * Simplify C interface * Get rid of hacky array conversion * Simplify demo programs

Diffstat:
MCMakeLists.txt|+-
Mdemo/main.c|+++----------------------------
Mdemo/main.cpp|+++++-----------------------------
Minclude/stamen.h|++++++++++++---------------------------------
Minclude/stamenc.h|-
Msrc/generate.cpp|+++++--------
Msrc/stamen.cpp|++++++++++-------
Msrc/stamenc.cpp|++---------

8 files changed, 38 insertions(+), 116 deletions(-)


diff --git a/CMakeLists.txt b/CMakeLists.txt

@@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

project(
Stamen
VERSION 0.0.12
VERSION 0.0.13
DESCRIPTION "Static menu generator"
LANGUAGES C CXX
)

diff --git a/demo/main.c b/demo/main.c

@@ -1,8 +1,8 @@

#include "demo_menu.h"
#include "stamenc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const display_f display = stamen_builtin_display;

@@ -29,37 +29,12 @@ int finish(void) {

exit(0);
}
int menu_static_run(void) { return menu_main(); }
int menu_dynamic_run(void) { return stamen_start("menu_main"); }
int menu_dynamic_print(void) {
stamen_print("menu_main");
return 1;
}
int main(int argc, const char *argv[]) {
const char *filename = "./demo_menu.conf";
char buffer[FILENAME_MAX] = {0};
const char *sep = strrchr(argv[0], '/');
if (sep) {
const size_t size = sep - argv[0] + 1;
memcpy(buffer, argv[0], size);
memcpy(buffer + size, filename, strlen(filename));
}
stamen_read(sep ? buffer : filename);
stamen_insert("finish", finish);
stamen_insert("operation1", operation1);
stamen_insert("operation2", operation2);
stamen_insert("operation3", operation3);
static const item_t items[] = {
{ menu_static_run, "Run statically generated menu"},
{ menu_dynamic_run, "Run dynamic menu"},
{menu_dynamic_print, "Print dynamic menu"},
{ finish, "Quit"},
};
display("Menu demo program", items, sizeof(items) / sizeof(item_t));
int main(void) {
menu_main();
return 0;
}

diff --git a/demo/main.cpp b/demo/main.cpp

@@ -1,6 +1,7 @@

#include <iostream>
#include "demo_menu.hpp"
#include "stamen.h"
#include <iostream>
using stamen::Menu;

@@ -31,32 +32,7 @@ int finish(void) {

exit(0);
}
int menu_static_run(void) { return stamen::menu_main(); }
int menu_dynamic_run(void) { return Menu::start("menu_main"); }
int menu_dynamic_print(void) {
Menu::print("menu_main");
return 1;
}
int main(int argc, const char *argv[]) {
std::string name(argv[0]);
std::string::size_type pos = name.rfind('/');
std::string base = pos != std::string::npos ? name.substr(0, pos) : ".";
Menu::read(base + "/demo_menu.conf");
Menu::insert("finish", finish);
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"},
{ menu_dynamic_run, "Run dynamic menu"},
{menu_dynamic_print, "Print dynamic menu"},
{ finish, "Quit"},
};
Menu::display("Menu demo program", items,
sizeof(items) / sizeof(Menu::item_t));
int main(void) {
stamen::menu_main();
return 0;
}

diff --git a/include/stamen.h b/include/stamen.h

@@ -32,41 +32,13 @@ public:

Menu(private_ctor_t, const std::string &code, const callback_f callback)
: Menu(code, callback) {}
struct item_t {
item_t() {}
item_t(const callback_f func, const std::string &prompt)
: callback(func), prompt(prompt) {}
item_t(const std::string &code, const std::string &prompt)
: code(code), prompt(prompt) {}
const std::string getCode(void) const { return code; }
const std::string getPrompt(void) const { return prompt; }
const callback_f getCallback(void) const { return callback; }
int operator()(void) const { return callback ? callback() : start(code); }
private:
std::string prompt, code;
callback_f callback = nullptr;
};
typedef int (*display_f)(const std::string &, const item_t[], std::size_t);
typedef int (*display_f)(const std::string &, const ::item_t[], std::size_t);
static const display_f display;
static void read(const std::string &s);
static void insert(const std::string &code, const callback_f callback);
static void print(const std::string &entry) { print(entry, 1); }
static int start(const std::string &entry) {
const Menu *menu = getMenu(entry);
if (!menu) return 1;
return menu->operator()();
}
int operator()() const {
return callback ? callback() : display(title, items.data(), items.size());
}
private:
Menu(const std::string &code, const std::string &prompt)

@@ -92,7 +64,14 @@ private:

const std::string code, title;
const callback_f callback = nullptr;
std::vector<item_t> items;
struct lookup_item_t {
lookup_item_t(const std::string &code, const std::string &prompt)
: code(code), prompt(prompt) {}
const std::string code, prompt;
};
std::vector<lookup_item_t> items;
};
inline void Menu::read(const std::string &s) {

@@ -132,13 +111,13 @@ inline void Menu::print(const std::string &code, const int depth) {

if (!menu->callback) {
for (const auto &item : menu->items) {
std::cout << std::format("{}{} ({})\n", std::string(depth << 1, ' '),
item.getPrompt(), item.getCode());
menu->print(item.getCode(), depth + 1);
item.prompt, item.code);
menu->print(item.code, depth + 1);
}
}
}
int builtinDisplay(const std::string &title, const Menu::item_t items[],
int builtinDisplay(const std::string &title, const ::item_t items[],
std::size_t size);
} // namespace stamen

diff --git a/include/stamenc.h b/include/stamenc.h

@@ -20,7 +20,6 @@ extern const display_f display;

EXTERNC void stamen_read(const char *file);
EXTERNC void stamen_print(const char *entry);
EXTERNC int stamen_start(const char *entry);
EXTERNC void stamen_insert(const char *code, callback_f callback);
EXTERNC int stamen_builtin_display(const char *title, const item_t items[], int size);

diff --git a/src/generate.cpp b/src/generate.cpp

@@ -16,7 +16,6 @@ public:

static void generateInclude(std::ostream &os, bool cpp) {
os << "#ifndef STAMEN_MENU_H\n";
os << "#define STAMEN_MENU_H\n\n";
os << std::format("#include <stamen{}.h>\n\n", !cpp ? "c" : "");
if (cpp) os << "namespace stamen {\n\n";
for (const auto &[code, _] : Menu::getLookup()) {
const Menu *menu = Menu::getMenu(code);

@@ -25,7 +24,7 @@ public:

os << std::format("int {}(void);\n", menu->code);
}
if (cpp) os << "\n}\n";
os << "#endif\n";
os << "\n#endif\n";
}
static void generateSource(std::ostream &os, bool cpp) {

@@ -38,15 +37,13 @@ public:

if (menu->callback) continue;
os << std::format("int {}(void) {{\n", menu->code);
os << std::format("\tstatic const {}item_t items[] = {{\n",
cpp ? "Menu::" : "");
os << std::format("\tstatic const item_t items[] = {{\n");
for (const auto &item : menu->items) {
os << std::format("\t\t{{ {}, \"{}\" }},\n", item.getCode(),
item.getPrompt());
os << std::format("\t\t{{ {}, \"{}\" }},\n", item.code, item.prompt);
}
os << std::format("\t}};\n");
os << std::format("\treturn {0}display(\"{1}\", items, "
"sizeof(items) / sizeof({0}item_t));\n",
os << std::format("\treturn {}display(\"{}\", items, "
"sizeof(items) / sizeof(item_t));\n",
cpp ? "Menu::" : "", menu->title);
os << std::format("}}\n\n");
}

diff --git a/src/stamen.cpp b/src/stamen.cpp

@@ -3,17 +3,19 @@

#include <format>
#include <iostream>
#include <ostream>
#include <variant>
namespace stamen {
int builtinDisplay(const std::string &title, const Menu::item_t items[],
int 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());
std::cout << std::format(" {:{}}. {}\n", i, digits, items[i].prompt);
}
while (true) {

@@ -24,11 +26,12 @@ int builtinDisplay(const std::string &title, const Menu::item_t items[],

return 1;
}
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;
std::cout << std::format("Choice: {}\n\n", items[choice].prompt);
const int res = items[choice].callback();
if (res > 1)
return res - 1;
else
break;
} else if (std::cin.eof()) {
std::cerr << "encountered end of input!\n";

diff --git a/src/stamenc.cpp b/src/stamenc.cpp

@@ -1,4 +1,5 @@

#include "../include/stamen.h"
#include "../include/stamenc.h"
using namespace stamen;

@@ -6,18 +7,10 @@ const Menu::display_f Menu::display = nullptr;

void stamen_read(const char *file) { stamen::Menu::read(file); }
void stamen_print(const char *entry) { stamen::Menu::print(entry); }
int stamen_start(const char *entry) { return stamen::Menu::start(entry); }
void stamen_insert(const char *code, callback_f callback) {
Menu::insert(code, callback);
}
int stamen_builtin_display(const char *title, const item_t items[], int size) {
Menu::item_t *litems = new Menu::item_t[size];
if (!litems) return -1;
for (int i = 0; i < size; i++) {
litems[i] = Menu::item_t(items[i].callback, items[i].prompt);
}
const int ret = builtinDisplay(title, litems, size);
delete[] litems;
return ret;
return builtinDisplay(title, items, size);
}