stamen

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

commit 4353db0fc0af665ffe8a735c52da61b641ab92c9
parent 26a75511cae93934528c2c3e09c1307d20086552
author Dimitrije Dobrota <mail@dimitrijedobrota.com>
date Fri, 17 Nov 2023 03:02:22 +0000

stamen namespace, header only * Put everything inside of stamen namespace * Main functionality is provided in header only * In order to use builtinDisplay you need to link against stamen library * Reflect the changes in demo and README * Improve code redability

Diffstat:
M CMakeLists.txt | + -
M README.md | +++++++++ ----
M demo/CMakeLists.txt | +
M demo/main.cpp | +++++ -
M include/stamen.h | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ----------------------
M src/CMakeLists.txt | +++++++++ ---
M src/generate.cpp | + -
M src/stamen.cpp | ++++++ -----------------------------------------------------------

8 files changed, 133 insertions(+), 105 deletions(-)


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

@@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project( Stamen
VERSION 0.0.8
VERSION 0.0.9
DESCRIPTION "Static menu generator" LANGUAGES CXX )

diff --git a/ README.md b/ README.md

@@ -68,13 +68,15 @@ Please reference demo folder for relevant usage example. There are a few things needed before you begin.
* Everything is contained in the `stamen namespace`
* 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`.
by using a build in `stamen::builtinDisplay`, just make sure to link stamen library
while building your project.
#### Dynamic menu

@@ -84,6 +86,9 @@ order to invoke the menu you need to add the following code to your C++ program: ```
// shorthand
using stamen::Menu;
// read the configuration Menu::read("path to config");

@@ -118,6 +123,9 @@ menu starting from that specific pane. #### Custom display function
Please refer to the implementation of `stamen::builtinDisplay` to get a general
idea of the direction.
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()`

@@ -129,9 +137,6 @@ 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

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

@@ -5,6 +5,7 @@ add_executable(demo target_link_libraries(demo stamen
stamen-display
) ADD_CUSTOM_COMMAND(

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

@@ -2,7 +2,11 @@ #include "demo_menu.h"
const Menu::display_f Menu::display = Menu::builtinDisplay;
using stamen::Menu;
// need to link against stamen library
// in order to use stamen::BuiltinDisplay
const Menu::display_f Menu::display = stamen::builtinDisplay;
int operation1(void) { std::cout << "operation 1" << std::endl;

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

@@ -5,22 +5,23 @@ #include <format> #include <fstream> #include <iostream>
#include <memory>
#include <sstream> #include <string> #include <tuple> #include <unordered_map> #include <vector>
namespace stamen {
class Menu {
public:
typedef int (*callback_f)(void);
Menu(const Menu &) = delete; Menu &operator=(const Menu &) = delete; struct private_ctor_t {};
public:
typedef int (*callback_f)(void);
Menu(private_ctor_t, const std::string &code, const std::string &prompt) : Menu(code, prompt) {}

@@ -54,44 +55,18 @@ public: const callback_f callback = nullptr; };
static void read(const std::string &s) {
std::string line, delim, code, prompt;
std::fstream fs(s);
char tmp;
lookup_t::iterator last = 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));
last = iter;
} else {
last->second.items.push_back({code, prompt});
}
}
}
typedef int (*display_f)(const std::string &, const item_t[], std::size_t);
static const display_f display;
static int start(const std::string &entry) { return getMenu(entry)(); }
static void insert(const std::string &code, const callback_f callback) {
lookup.emplace(std::piecewise_construct, std::forward_as_tuple(code),
std::forward_as_tuple(private_ctor_t{}, code, callback));
}
static void read(const std::string &s);
static void insert(const std::string &code, const callback_f callback);
static int start(const std::string &entry) { return getMenu(entry)(); }
static void print(const std::string &entry) { print(entry, 1); } static void generateSource(std::ostream &os); static void generateInclude(std::ostream &os);
typedef int (*display_f)(const std::string &, const item_t[], std::size_t);
static const display_f display;
static int builtinDisplay(const std::string &title, const item_t items[],
std::size_t size);
int operator()() const { return callback ? callback() : display(title, items.data(), items.size()); }

@@ -104,11 +79,15 @@ private: : code(code), title(code), callback(callback) {} typedef std::unordered_map<std::string, Menu> lookup_t;
static lookup_t lookup;
static lookup_t &getLookup(void) {
static lookup_t lookup;
return lookup;
}
static void print(const std::string &entry, const int depth); static const Menu &getMenu(const std::string &code) {
static lookup_t &lookup = getLookup();
const auto it = lookup.find(code); if (it == lookup.end()) throw EMenu(); return it->second;

@@ -119,4 +98,90 @@ private: std::vector<item_t> items; };
inline void Menu::read(const std::string &s) {
std::string line, delim, code, prompt;
std::fstream fs(s);
char tmp;
lookup_t &lookup = getLookup();
lookup_t::iterator last = 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));
last = iter;
} else {
last->second.items.push_back({code, prompt});
}
}
}
inline void Menu::insert(const std::string &code, const callback_f callback) {
getLookup().emplace(std::piecewise_construct, std::forward_as_tuple(code),
std::forward_as_tuple(private_ctor_t{}, code, callback));
}
inline void Menu::generateInclude(std::ostream &os) {
os << "#ifndef STAMEN_MENU_H\n";
os << "#define STAMEN_MENU_H\n\n";
os << "#include <stamen.h>\n\n";
os << "namespace stamen {\n\n";
for (const auto &[code, _] : getLookup()) {
const Menu &menu = getMenu(code);
if (menu.callback) continue;
os << std::format("int {}(void);\n", menu.code);
}
os << "\n}\n";
os << "#endif\n";
}
inline void Menu::generateSource(std::ostream &os) {
os << "#include <stamen.h>\n";
os << "#include \"shared.h\"\n";
os << "\nnamespace stamen {\n";
for (const auto &[code, _] : getLookup()) {
const Menu &menu = getMenu(code);
if (menu.callback) continue;
os << std::format("int {}(void) {{\n", menu.code);
os << std::format("\tstatic const Menu::item_t items[] = {{\n");
for (const auto &item : menu.items) {
os << std::format("\t\t{{{}, \"{}\"}},\n", item.code, item.prompt);
}
os << std::format("\t}};\n");
os << std::format("\treturn Menu::display(\"{}\", items, "
"sizeof(items) / sizeof(Menu::item_t));\n",
menu.title);
os << std::format("}}\n\n");
}
os << "\n}\n";
}
inline void Menu::print(const std::string &code, const int depth) {
static lookup_t &lookup = getLookup();
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.title, code);
if (!menu.callback) {
for (const auto &item : menu.items) {
std::cout << std::format("{}{} ({})\n", std::string(depth << 1, ' '),
item.prompt, item.code);
menu.print(item.code, depth + 1);
}
}
}
int builtinDisplay(const std::string &title, const Menu::item_t items[],
std::size_t size);
} // namespace stamen
#endif

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

@@ -1,9 +1,15 @@
add_library(stamen
add_library(stamen INTERFACE)
target_include_directories(stamen
INTERFACE ../include
)
add_library(stamen-display
stamen.cpp )
target_include_directories(stamen
PUBLIC ../include
target_link_libraries(stamen-display
PRIVATE stamen
) add_executable(stamen-generate generate.cpp)

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

@@ -1,7 +1,7 @@ #include "stamen.h" #include <string>
const Menu::display_f Menu::display = Menu::builtinDisplay;
using namespace stamen;
int main(const int argc, const char *argv[]) { if (argc != 2) {

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

@@ -1,16 +1,13 @@ #include "../include/stamen.h" #include <cmath> #include <format>
#include <fstream>
#include <iostream> #include <ostream>
#include <stack>
#include <unordered_set>
std::unordered_map<std::string, Menu> Menu::lookup;
namespace stamen {
int Menu::builtinDisplay(const std::string &title, const item_t items[],
std::size_t size) {
int builtinDisplay(const std::string &title, const Menu::item_t items[],
std::size_t size) {
int choice; const int digits = std::log10(size) + 1; while (true) {

@@ -27,12 +24,12 @@ int Menu::builtinDisplay(const std::string &title, const item_t items[], return 1; }
const item_t &chosen = items[choice];
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;
break;
} else if (std::cin.eof()) { std::cerr << "encountered end of input!\n"; return std::numeric_limits<int>::max();

@@ -48,54 +45,4 @@ int Menu::builtinDisplay(const std::string &title, const item_t items[], return 1; }
void Menu::generateInclude(std::ostream &os) {
os << "#ifndef STAMEN_MENU_H\n";
os << "#define STAMEN_MENU_H\n\n";
os << "#include <stamen.h>\n\n";
os << "namespace stamen {\n\n";
for (const auto &[code, _] : lookup) {
const Menu &menu = getMenu(code);
if (menu.callback) continue;
os << std::format("int {}(void);\n", menu.code);
}
os << "\n}\n";
os << "#endif\n";
}
void Menu::generateSource(std::ostream &os) {
os << "#include <stamen.h>\n";
os << "#include \"shared.h\"\n";
os << "\nnamespace stamen {\n";
for (const auto &[code, _] : lookup) {
const Menu &menu = getMenu(code);
if (menu.callback) continue;
os << std::format("int {}(void) {{\n", menu.code);
os << std::format("\tstatic const Menu::item_t items[] = {{\n");
for (const auto &item : menu.items) {
os << std::format("\t\t{{{}, \"{}\"}},\n", item.code, item.prompt);
}
os << std::format("\t}};\n");
os << std::format("\treturn Menu::display(\"{}\", items, "
"sizeof(items) / sizeof(Menu::item_t));\n",
menu.title);
os << std::format("}}\n\n");
}
os << "\n}\n";
}
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.title, code);
if (!menu.callback) {
for (const auto &item : menu.items) {
std::cout << std::format("{}{}({})\n", std::string(depth << 1, ' '),
item.prompt, item.code);
menu.print(item.code, depth + 1);
}
}
}
} // namespace stamen