stamen

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

commitbed0d31a9d9d145fc559b43b4b5304c0b5373a78
parent87599e12d3694eca29695587e4c71cf57c6cb94b
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateTue, 5 Dec 2023 23:22:56 +0000

Dynamic menus and restructure * Dynamics menus are back! * Extend C/C++ interface with new functions * New demo program for showcase * callback_f now accepts int for implementation reasons * Refactor Menu class for easier data access * Improve consistency * Remove redundancy

Diffstat:
MCMakeLists.txt|+-
Mdemo/CMakeLists.txt|++++++++++++++
Ademo/dynamic.cpp|+++++++++++++++++++++++++++++++
Mdemo/main.c|+-
Mdemo/main.cpp|+++++-----
Mdemo/shared.h|++++----
Minclude/menu.h|++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Minclude/stamen.h|++++++++++-----
Msrc/CMakeLists.txt|+++---
Msrc/generate.cpp|++++++++++-------
Msrc/menu.cpp|+++++++++++++++++++++++++++++++++++-----------
Msrc/stamen.cpp|+++++++++++--

12 files changed, 181 insertions(+), 58 deletions(-)


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

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

project(
Stamen
VERSION 0.0.17
VERSION 0.0.18
DESCRIPTION "Static menu generator"
LANGUAGES C CXX
)

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

@@ -57,3 +57,17 @@ set_target_properties(cdemo PROPERTIES

SOVERSION ${PROJECT_VERSION_MAJOR}
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
add_executable(dynamic
dynamic.cpp
)
target_link_libraries(dynamic
stamen
)
set_target_properties(dynamic PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)

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

@@ -0,0 +1,31 @@

#include <stamen.h>
#include <iostream>
const stamen_display_f stamen_display = stamen_builtin_display;
int finish(int) { exit(1); }
int operation1(int) {
std::cout << "1" << std::endl;
return 1;
}
int operation2(int) {
std::cout << "2" << std::endl;
return 1;
}
int operation3(int) {
std::cout << "3" << std::endl;
return 1;
}
int main() {
stamen_read("./bin/demo_menu.conf");
stamen_insert("finish", finish);
stamen_insert("operation1", operation1);
stamen_insert("operation2", operation2);
stamen_insert("operation3", operation3);
stamen_dynamic();
return 0;
}

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

@@ -32,6 +32,6 @@ int finish(void) {

}
int main(void) {
menu_main();
menu_main(0);
return 0;
}

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

@@ -7,30 +7,30 @@

// in order to use stamen_builtin_display
const stamen_display_f stamen_display = stamen_builtin_display;
int operation1() {
int operation1(int) {
std::cout << "operation 1" << std::endl;
std::cout << "Some operation is done" << std::endl;
return 1;
}
int operation2() {
int operation2(int) {
std::cout << "operation 2" << std::endl;
std::cout << "Some other operation is done" << std::endl;
return 1;
}
int operation3() {
int operation3(int) {
std::cout << "operation 3" << std::endl;
std::cout << "Yet another operation is done" << std::endl;
return 1;
}
int finish() {
int finish(int) {
std::cout << "finishing..." << std::endl;
exit(0);
}
int main() {
menu_main();
menu_main(0);
return 0;
}

diff --git a/demo/shared.h b/demo/shared.h

@@ -1,9 +1,9 @@

#ifndef STAMEN_DEMO_SHARED_H
#define STAMEN_DEMO_SHARED_H
int finish(void);
int operation1(void);
int operation2(void);
int operation3(void);
int finish(int);
int operation1(int);
int operation2(int);
int operation3(int);
#endif

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

@@ -3,53 +3,90 @@

#include "stamen.h"
#include <cstring>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
class Menu {
struct private_ctor_t {};
friend class Generator;
public:
using lookup_t = std::unordered_map<std::string, Menu>;
static lookup_t lookup;
using callback_f = stamen_callback_f;
using item_t = stamen_item_t;
static void read(const std::string &s);
static void print(const std::string &entry) { internal_print(entry, 1); }
static lookup_t &getLookup() { return lookup; }
static std::unordered_map<std::string, Menu> menu_lookup;
static std::unordered_map<std::string, callback_f> free_lookup;
Menu(const Menu &) = delete;
Menu &operator=(const Menu &) = delete;
struct private_ctor_t {};
public:
// Tag type dispatch
Menu(private_ctor_t, const std::string &code, const std::string &prompt)
: Menu(code, prompt) {}
struct lookup_item_t {
const std::string code;
const std::string prompt;
};
Menu(const Menu &) = delete;
Menu &operator=(const Menu &) = delete;
static int dynamic() { return display_stub(-1); };
static void read(const std::string &s);
static void print(const std::string &entry) { print(entry, 1); }
static void insert(const std::string &s, callback_f callback) {
free_lookup.emplace(s, callback);
}
[[nodiscard]] const std::string &getCode() const { return code; }
[[nodiscard]] const std::string &getTitle() const { return title; }
[[nodiscard]] const std::vector<lookup_item_t> &getItems() const {
return lookup_items;
[[nodiscard]] const item_t *getItemv() const { return entries.items.data(); }
[[nodiscard]] std::size_t getSize() const { return entries.items.size(); }
[[nodiscard]] const std::string &getCode(std::size_t idx) const {
return entries.codes[idx].code;
}
[[nodiscard]] const std::string &getPrompt(std::size_t idx) const {
return entries.codes[idx].prompt;
}
[[nodiscard]] callback_f getCallback(std::size_t idx) const {
return entries.items[idx].callback;
}
private:
Menu(std::string code, std::string prompt)
: code(std::move(code)), title(std::move(prompt)) {}
static void print(const std::string &entry, const int depth);
static int display_stub(int idx);
static const Menu *getMenu(const std::string &code) {
const auto it = lookup.find(code);
if (it == lookup.end()) return nullptr;
const auto it = menu_lookup.find(code);
if (it == menu_lookup.end()) return nullptr;
return &it->second;
}
static void internal_print(const std::string &entry, const int depth);
struct Entries {
struct code_t {
const std::string code;
const std::string prompt;
};
std::vector<code_t> codes;
std::vector<item_t> items;
void insert(const std::string &code, const std::string &prompt,
callback_f callback = display_stub) {
char *buffer = new char[prompt.size() + 1];
strcpy(buffer, prompt.c_str());
items.emplace_back(callback, buffer);
codes.emplace_back(code, prompt);
}
};
std::vector<lookup_item_t> lookup_items;
const std::string code, title;
Entries entries;
};
#endif

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

@@ -7,17 +7,22 @@

#define EXTERNC extern
#endif
typedef int (*stamen_callback_f)(void);
typedef int (*stamen_callback_f)(int);
typedef struct item_t item_t;
struct item_t {
typedef struct stamen_item_t stamen_item_t;
struct stamen_item_t {
stamen_callback_f callback;
const char *prompt;
};
typedef int (*stamen_display_f)(const char *, const item_t[], int);
typedef int (*stamen_display_f)(const char *, const stamen_item_t[], int);
extern const stamen_display_f stamen_display;
EXTERNC int stamen_builtin_display(const char *title, const item_t itemv[], int size);
EXTERNC int stamen_dynamic(void);
EXTERNC void stamen_read(const char *filename);
EXTERNC void stamen_insert(const char *code, stamen_callback_f callback);
EXTERNC int stamen_builtin_display(const char *title,
const stamen_item_t itemv[], int size);
#endif

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

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

add_library(stamen-include INTERFACE)
target_include_directories(stamen-include INTERFACE ../include)
add_library(stamen stamen.cpp)
add_library(stamen stamen.cpp menu.cpp)
target_link_libraries(stamen PUBLIC stamen-include)
set_target_properties(stamen PROPERTIES
VERSION ${PROJECT_VERSION}

@@ -14,8 +14,8 @@ install(TARGETS stamen

PUBLIC_HEADER DESTINATION include
)
add_executable(stamen-generate generate.cpp menu.cpp)
target_link_libraries(stamen-generate PRIVATE stamen-include)
add_executable(stamen-generate generate.cpp)
target_link_libraries(stamen-generate PRIVATE stamen)
target_include_directories(stamen-generate PUBLIC ../include)
set_target_properties(stamen-generate PROPERTIES

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

@@ -1,4 +1,6 @@

#include "menu.h"
#include "stamen.h"
#include <format>
#include <fstream>
#include <iostream>

@@ -10,8 +12,8 @@ public:

os << "#ifndef STAMEN_MENU_H\n";
os << "#define STAMEN_MENU_H\n\n";
os << "#include <stamen.h>\n\n";
for (const auto &[code, menu] : Menu::getLookup()) {
os << std::format("int {}(void);\n", menu.getCode());
for (const auto &[code, menu] : Menu::menu_lookup) {
os << std::format("int {}(int);\n", menu.getCode());
}
os << "\n#endif\n";
}

@@ -19,12 +21,13 @@ public:

static void generateSource(std::ostream &os) {
os << "#include <stamen.h>\n";
os << "#include \"shared.h\"\n\n";
for (const auto &[code, menu] : Menu::getLookup()) {
os << std::format("int {}(void) {{\n", menu.getCode());
for (const auto &[code, menu] : Menu::menu_lookup) {
os << std::format("int {}(int) {{\n", menu.getCode());
os << std::format("\tstatic const item_t items[] = {{\n");
for (const auto &[code, prompt] : menu.getItems()) {
os << std::format("\t\t{{ {}, \"{}\" }},\n", code, prompt);
os << std::format("\tstatic const stamen_item_t items[] = {{\n");
for (int i = 0; i < menu.getSize(); i++) {
os << std::format("\t\t{{ {}, \"{}\" }},\n", menu.getCode(i),
menu.getPrompt(i));
}
os << std::format("\t}};\n");

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

@@ -1,5 +1,7 @@

#include "menu.h"
#include "stamen.h"
#include <deque>
#include <format>
#include <fstream>
#include <iostream>

@@ -7,40 +9,62 @@

#include <tuple>
#include <utility>
Menu::lookup_t Menu::lookup;
std::unordered_map<std::string, Menu> Menu::menu_lookup;
std::unordered_map<std::string, Menu::callback_f> Menu::free_lookup;
void Menu::read(const std::string &s) {
std::string line, delim, code, prompt;
std::fstream fs(s);
char tmp = 0;
lookup_t &lookup = getLookup();
auto last = lookup.end();
auto last = menu_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));
const auto [iter, succ] = menu_lookup.emplace(
std::piecewise_construct, std::forward_as_tuple(code),
std::forward_as_tuple(private_ctor_t{}, code, prompt));
last = iter;
} else {
last->second.lookup_items.emplace_back(code, prompt);
last->second.entries.insert(code, prompt);
}
}
}
void Menu::internal_print(const std::string &code, const int depth) {
void Menu::print(const std::string &code, const int depth) {
const Menu *menu = getMenu(code);
if (!menu) return;
if (depth == 1) std::cout << std::format("{}({})\n", menu->title, code);
for (const auto &[code, prompt] : menu->lookup_items) {
for (int i = 0; i < menu->getSize(); i++) {
std::cout << std::format("{}{} ({})\n", std::string(depth << 1, ' '),
prompt, code);
menu->internal_print(code, depth + 1);
menu->getPrompt(i), menu->getCode(i));
menu->print(code, depth + 1);
}
}
int Menu::display_stub(int idx) {
static std::deque<const Menu *> st;
const std::string &code = st.size() ? st.back()->getCode(idx) : "menu_main";
const auto ml_it = menu_lookup.find(code);
if (ml_it != menu_lookup.end()) {
const Menu &menu = ml_it->second;
st.push_back(&menu);
int ret = stamen_builtin_display(menu.title.c_str(), menu.getItemv(),
menu.getSize());
st.pop_back();
return ret;
}
const auto fl_it = free_lookup.find(code);
if (fl_it != free_lookup.end()) { return fl_it->second(0); }
std::cout << "Stamen: nothing to do..." << std::endl;
return 1;
}

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

@@ -1,11 +1,20 @@

#include "stamen.h"
#include "menu.h"
#include <cmath>
#include <format>
#include <iostream>
#include <ostream>
#include <variant>
int stamen_builtin_display(const char *title, const ::item_t itemv[], int size) {
int stamen_dynamic(void) { return Menu::dynamic(); }
void stamen_read(const char *filename) { Menu::read(filename); }
void stamen_insert(const char *code, stamen_callback_f callback) {
Menu::insert(code, callback);
}
int stamen_builtin_display(const char *title, const stamen_item_t itemv[],
int size) {
const size_t digits = size_t(std::log10(size)) + 1;
const auto items = std::span(itemv, size_t(size));
int choice = 0;

@@ -25,7 +34,7 @@ int stamen_builtin_display(const char *title, const ::item_t itemv[], int size)

}
std::cout << std::format("Choice: {}\n\n", items[choice].prompt);
const int res = items[choice].callback();
const int res = items[choice].callback(choice);
if (res > 1)
return res - 1;
else