stamen

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

commit4353db0fc0af665ffe8a735c52da61b641ab92c9
parent26a75511cae93934528c2c3e09c1307d20086552
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateFri, 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:
MCMakeLists.txt|+-
MREADME.md|+++++++++----
Mdemo/CMakeLists.txt|+
Mdemo/main.cpp|+++++-
Minclude/stamen.h|++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/CMakeLists.txt|+++++++++---
Msrc/generate.cpp|+-
Msrc/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