stamenStatic Menu Generator |
git clone git://git.dimitrijedobrota.com/stamen.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | |
commit | 26a75511cae93934528c2c3e09c1307d20086552 |
parent | 77eabec835fddbe4e8d83608b538090d8de2c3cb |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Fri, 17 Nov 2023 02:22:39 +0000 |
Rebrand * Rebrand project to stamen * Add header guard to generated include file * Fix bug where -1 would not go back
Diffstat:M | CMakeLists.txt | | | +++--- |
M | README.md | | | +- |
M | demo/CMakeLists.txt | | | +++--- |
M | demo/main.cpp | | | +- |
M | demo/shared.h | | | ++-- |
D | include/menu.h | | | --------------------------------------------------------------------------------- |
A | include/stamen.h | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/CMakeLists.txt | | | ++++++++-------- |
M | src/generate.cpp | | | +- |
D | src/menu.cpp | | | --------------------------------------------------------------------------------- |
A | src/stamen.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
11 files changed, 242 insertions(+), 239 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.25.2)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(
Menu
VERSION 0.0.7
DESCRIPTION "Experimentation with dinamic menus"
Stamen
VERSION 0.0.8
DESCRIPTION "Static menu generator"
LANGUAGES CXX
)
diff --git a/README.md b/README.md
@@ -107,7 +107,7 @@ will create source file and include file in the current directory with the name
as the configuration file but with extensions `.cpp` and `.h` respectively.
Include file will contain declarations for all of the menu functions inside
`menu` namespace. You should include this file in your code.
`stamen` namespace. You should include this file in your code.
Source file contains definitions for the menu functions. It also includes
`shared.h` file which should contain declarations for all of the free functions
diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt
@@ -4,13 +4,13 @@ add_executable(demo
)
target_link_libraries(demo
menu
stamen
)
ADD_CUSTOM_COMMAND(
OUTPUT demo_menu.h demo_menu.cpp
COMMAND ${CMAKE_BINARY_DIR}/bin/generate ${CMAKE_CURRENT_SOURCE_DIR}/demo_menu.conf
DEPENDS demo_menu.conf generate
COMMAND ${CMAKE_BINARY_DIR}/bin/stamen-generate ${CMAKE_CURRENT_SOURCE_DIR}/demo_menu.conf
DEPENDS demo_menu.conf stamen-generate
COMMENT "Generating menu files"
)
diff --git a/demo/main.cpp b/demo/main.cpp
@@ -27,7 +27,7 @@ int finish(void) {
exit(0);
}
int menu_static_run(void) { return menu::menu_main(); }
int menu_static_run(void) { return stamen::menu_main(); }
int menu_dynamic_run(void) { return Menu::start("menu_main"); }
int menu_dynamic_print(void) {
diff --git a/demo/shared.h b/demo/shared.h
@@ -1,5 +1,5 @@
#ifndef DEMO_SHARED_H
#define DEMO_SHARED_H
#ifndef STAMEN_DEMO_SHARED_H
#define STAMEN_DEMO_SHARED_H
int finish(void);
int operation1(void);
diff --git a/include/menu.h b/include/menu.h
@@ -1,122 +0,0 @@
#ifndef MENU_H
#define MENU_H
#include <exception>
#include <format>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
class Menu {
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) {}
Menu(private_ctor_t, const std::string &code, const callback_f callback)
: Menu(code, callback) {}
class EMenu : std::exception {
virtual const char *what() const noexcept override {
return "Trying to access an unknown menu";
}
};
struct item_t {
friend class Menu;
item_t(const callback_f func, const std::string &prompt)
: callback(func), prompt(prompt) {}
const std::string getPrompt(void) const { return prompt; }
const callback_f getCallback(void) const { return callback; }
int operator()(void) const {
return callback ? callback() : getMenu(code)();
}
private:
item_t(const std::string &code, const std::string &prompt)
: code(code), prompt(prompt) {}
const std::string prompt, code;
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});
}
}
}
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 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());
}
private:
Menu(const std::string &code, const std::string &prompt)
: code(code), title(prompt) {}
Menu(const std::string &code, const callback_f callback)
: code(code), title(code), callback(callback) {}
typedef std::unordered_map<std::string, Menu> lookup_t;
static lookup_t lookup;
static void print(const std::string &entry, const int depth);
static const Menu &getMenu(const std::string &code) {
const auto it = lookup.find(code);
if (it == lookup.end()) throw EMenu();
return it->second;
}
const std::string code, title;
const callback_f callback = nullptr;
std::vector<item_t> items;
};
#endif
diff --git a/include/stamen.h b/include/stamen.h
@@ -0,0 +1,122 @@
#ifndef STAMEN_H
#define STAMEN_H
#include <exception>
#include <format>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
class Menu {
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) {}
Menu(private_ctor_t, const std::string &code, const callback_f callback)
: Menu(code, callback) {}
class EMenu : std::exception {
virtual const char *what() const noexcept override {
return "Trying to access an unknown menu";
}
};
struct item_t {
friend class Menu;
item_t(const callback_f func, const std::string &prompt)
: callback(func), prompt(prompt) {}
const std::string getPrompt(void) const { return prompt; }
const callback_f getCallback(void) const { return callback; }
int operator()(void) const {
return callback ? callback() : getMenu(code)();
}
private:
item_t(const std::string &code, const std::string &prompt)
: code(code), prompt(prompt) {}
const std::string prompt, code;
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});
}
}
}
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 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());
}
private:
Menu(const std::string &code, const std::string &prompt)
: code(code), title(prompt) {}
Menu(const std::string &code, const callback_f callback)
: code(code), title(code), callback(callback) {}
typedef std::unordered_map<std::string, Menu> lookup_t;
static lookup_t lookup;
static void print(const std::string &entry, const int depth);
static const Menu &getMenu(const std::string &code) {
const auto it = lookup.find(code);
if (it == lookup.end()) throw EMenu();
return it->second;
}
const std::string code, title;
const callback_f callback = nullptr;
std::vector<item_t> items;
};
#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
@@ -1,22 +1,22 @@
add_library(menu
menu.cpp
add_library(stamen
stamen.cpp
)
target_include_directories(menu
target_include_directories(stamen
PUBLIC ../include
)
add_executable(generate generate.cpp)
add_executable(stamen-generate generate.cpp)
target_link_libraries(generate
PRIVATE menu
target_link_libraries(stamen-generate
PRIVATE stamen
)
target_include_directories(generate
target_include_directories(stamen-generate
PUBLIC ../include
)
set_target_properties(generate PROPERTIES
set_target_properties(stamen-generate PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
diff --git a/src/generate.cpp b/src/generate.cpp
@@ -1,4 +1,4 @@
#include "menu.h"
#include "stamen.h"
#include <string>
const Menu::display_f Menu::display = Menu::builtinDisplay;
diff --git a/src/menu.cpp b/src/menu.cpp
@@ -1,98 +0,0 @@
#include "../include/menu.h"
#include <cmath>
#include <format>
#include <fstream>
#include <iostream>
#include <ostream>
#include <stack>
#include <unordered_set>
std::unordered_map<std::string, Menu> Menu::lookup;
int Menu::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());
}
while (true) {
std::cout << "Choose an option: ";
if (std::cin >> choice && choice >= -1 && choice < size) {
if (choice == -1) {
std::cout << "Choice: back\n";
return 1;
}
const item_t &chosen = items[choice];
std::cout << std::format("Choice: {}\n\n", chosen.getPrompt());
const int res = chosen();
if (res > 1) return res - 1;
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::generateInclude(std::ostream &os) {
os << "#include \"menu.h\"\n\n";
os << "namespace menu {\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";
}
void Menu::generateSource(std::ostream &os) {
os << "#include \"menu.h\"\n";
os << "#include \"shared.h\"\n";
os << "\nnamespace menu {\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);
}
}
}
diff --git a/src/stamen.cpp b/src/stamen.cpp
@@ -0,0 +1,101 @@
#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;
int Menu::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());
}
while (true) {
std::cout << "Choose an option: ";
if (std::cin >> choice && choice >= -1 && choice < (int)size) {
if (choice == -1) {
std::cout << "Choice: back\n";
return 1;
}
const item_t &chosen = items[choice];
std::cout << std::format("Choice: {}\n\n", chosen.getPrompt());
const int res = chosen();
if (res > 1) return res - 1;
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::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);
}
}
}