stamenStatic Menu Generator |
git clone git://git.dimitrijedobrota.com/stamen.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | |
commit | ad4b84a3bae704a23f2a417764b1bf62270adfc6 |
parent | 0ca7539acca2a05ac12c268598ee3cd1881515fc |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Sat, 2 Dec 2023 23:59:56 +0000 |
Restructure and improvements * Restructure the project * Remove redundancy * Improve consistency * Improve decomposition * Target C and C++
Diffstat:M | CMakeLists.txt | | | +- |
M | demo/CMakeLists.txt | | | +- |
M | demo/main.c | | | +++------ |
M | demo/main.cpp | | | +++--- |
A | include/menu.h | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | include/shared.h | | | --------------- |
M | include/stamen.h | | | ++++++++++------------------------------------------------------------------------ |
D | include/stamenc.h | | | ----------------- |
M | src/CMakeLists.txt | | | ++++------------- |
M | src/generate.cpp | | | +++++++++++++++++------------------------------------ |
A | src/menu.cpp | | | ++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/stamen.cpp | | | ++------ |
D | src/stamenc.cpp | | | -------------- |
13 files changed, 147 insertions(+), 224 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(
Stamen
VERSION 0.0.15
VERSION 0.0.16
DESCRIPTION "Static menu generator"
LANGUAGES C CXX
)
diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt
@@ -35,7 +35,7 @@ add_executable(cdemo
)
target_link_libraries(cdemo
stamenc
stamen
)
add_custom_command(
diff --git a/demo/main.c b/demo/main.c
@@ -1,9 +1,11 @@
#include "demo_menu.h"
#include "stamenc.h"
#include "stamen.h"
#include <stdio.h>
#include <stdlib.h>
// need to link against stamen library
// in order to use stamen_builtin_display
const stamen_display_f stamen_display = stamen_builtin_display;
int operation1(void) {
@@ -29,11 +31,6 @@ int finish(void) {
exit(0);
}
int menu_dynamic_print(void) {
stamen_print("menu_main");
return 1;
}
int main(void) {
menu_main();
return 0;
diff --git a/demo/main.cpp b/demo/main.cpp
@@ -4,8 +4,8 @@
#include <iostream>
// need to link against stamen library
// in order to use stamen::BuiltinDisplay
const stamen_display_f stamen_display = stamen::builtinDisplay;
// in order to use stamen_builtin_display
const stamen_display_f stamen_display = stamen_builtin_display;
int operation1() {
std::cout << "operation 1" << std::endl;
@@ -31,6 +31,6 @@ int finish() {
}
int main() {
stamen::menu_main();
menu_main();
return 0;
}
diff --git a/include/menu.h b/include/menu.h
@@ -0,0 +1,55 @@
#ifndef STAMEN_MENU_H
#define STAMEN_MENU_H
#include "stamen.h"
#include <string>
#include <unordered_map>
#include <vector>
class Menu {
struct private_ctor_t {};
public:
using lookup_t = std::unordered_map<std::string, Menu>;
static lookup_t lookup;
static void read(const std::string &s);
static void print(const std::string &entry) { internal_print(entry, 1); }
static lookup_t &getLookup() { return lookup; }
Menu(const Menu &) = delete;
Menu &operator=(const Menu &) = delete;
// 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;
};
[[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;
}
private:
Menu(std::string code, std::string prompt)
: code(std::move(code)), title(std::move(prompt)) {}
static const Menu *getMenu(const std::string &code) {
const auto it = lookup.find(code);
if (it == lookup.end()) return nullptr;
return &it->second;
}
static void internal_print(const std::string &entry, const int depth);
std::vector<lookup_item_t> lookup_items;
const std::string code, title;
};
#endif
diff --git a/include/shared.h b/include/shared.h
@@ -1,15 +0,0 @@
#ifndef STAMEN_SHARED_H
#define STAMEN_SHARED_H
typedef int (*stamen_callback_f)(void);
typedef struct item_t item_t;
struct item_t {
stamen_callback_f callback;
const char *prompt;
};
typedef int (*stamen_display_f)(const char *, const item_t[], int);
extern const stamen_display_f stamen_display;
#endif
diff --git a/include/stamen.h b/include/stamen.h
@@ -1,120 +1,23 @@
#ifndef STAMEN_H
#define STAMEN_H
#ifndef STAMEN_STAMEN_H
#define STAMEN_STAMEN_H
#include "shared.h"
#include <exception>
#include <format>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
namespace stamen {
class Menu {
struct private_ctor_t {};
public:
friend class Generator;
using callback_f = stamen_callback_f;
using display_f = stamen_display_f;
Menu(const Menu &) = delete;
Menu(Menu &&) = delete;
Menu &operator=(const Menu &) = delete;
Menu &operator=(Menu &&) = delete;
// Tag type dispatch
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) {}
static void read(const std::string &s);
static void print(const std::string &entry) { internal_print(entry, 1); }
static void insert(const std::string &code, const callback_f callback);
private:
Menu(std::string code, std::string prompt)
: code(std::move(code)), title(std::move(prompt)) {}
Menu(const std::string &code, const callback_f callback)
: code(code), title(code), callback(callback) {}
using lookup_t = std::unordered_map<std::string, Menu>;
static lookup_t &getLookup() {
static lookup_t lookup;
return lookup;
}
static void internal_print(const std::string &entry, const int depth);
static const Menu *getMenu(const std::string &code) {
const static lookup_t &lookup = getLookup();
const auto it = lookup.find(code);
if (it == lookup.end()) return nullptr;
return &it->second;
}
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC extern
#endif
const std::string code, title;
const callback_f callback = nullptr;
typedef int (*stamen_callback_f)(void);
using lookup_item_t = std::pair<std::string, std::string>;
std::vector<lookup_item_t> items;
typedef struct item_t item_t;
struct item_t {
stamen_callback_f callback;
const char *prompt;
};
inline 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();
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.emplace_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::internal_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);
if (!menu->callback) {
for (const auto &[code, prompt] : menu->items) {
std::cout << std::format("{}{} ({})\n", std::string(depth << 1, ' '),
prompt, code);
menu->internal_print(code, depth + 1);
}
}
}
int builtinDisplay(const char *title, const ::item_t itemv[], int size);
typedef int (*stamen_display_f)(const char *, const item_t[], int);
extern const stamen_display_f stamen_display;
} // namespace stamen
EXTERNC int stamen_builtin_display(const char *title, const item_t itemv[], int size);
#endif
diff --git a/include/stamenc.h b/include/stamenc.h
@@ -1,17 +0,0 @@
#ifndef CSTAMEN_H
#define CSTAMEN_H
#include "shared.h"
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC extern
#endif
EXTERNC void stamen_read(const char *file);
EXTERNC void stamen_print(const char *entry);
EXTERNC void stamen_insert(const char *code, stamen_callback_f callback);
EXTERNC int stamen_builtin_display(const char *title, const item_t itemv[], int size);
#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
@@ -9,15 +9,12 @@ set_target_properties(stamen PROPERTIES
PUBLIC_HEADER ../include/stamen.h
)
add_library(stamenc stamenc.cpp stamen.cpp)
target_link_libraries(stamenc PUBLIC stamen-include)
set_target_properties(stamenc PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
PUBLIC_HEADER ../include/stamenc.h
install(TARGETS stamen
LIBRARY DESTINATION lib
PUBLIC_HEADER DESTINATION include
)
add_executable(stamen-generate generate.cpp)
add_executable(stamen-generate generate.cpp menu.cpp)
target_link_libraries(stamen-generate PRIVATE stamen-include)
target_include_directories(stamen-generate PUBLIC ../include)
@@ -27,10 +24,4 @@ set_target_properties(stamen-generate PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
install(TARGETS stamen-generate DESTINATION bin)
install(TARGETS stamen
LIBRARY DESTINATION lib
PUBLIC_HEADER DESTINATION include
)
diff --git a/src/generate.cpp b/src/generate.cpp
@@ -1,60 +1,41 @@
#include "stamen.h"
#include "menu.h"
#include <format>
#include <fstream>
#include <iostream>
#include <string>
using namespace stamen;
namespace stamen {
class Generator {
public:
class EGenerate : std::exception {
[[nodiscard]] const char *what() const noexcept override {
return "Trying to access unknown code";
}
};
static void generateInclude(std::ostream &os, bool cpp) {
static void generateInclude(std::ostream &os) {
os << "#ifndef STAMEN_MENU_H\n";
os << "#define STAMEN_MENU_H\n\n";
if (cpp) os << "namespace stamen {\n\n";
for (const auto &[code, _] : Menu::getLookup()) {
const Menu *menu = Menu::getMenu(code);
if (!menu) throw EGenerate();
if (menu->callback) continue;
os << std::format("int {}(void);\n", menu->code);
os << "#include <stamen.h>\n\n";
for (const auto &[code, menu] : Menu::getLookup()) {
os << std::format("int {}(void);\n", menu.getCode());
}
if (cpp) os << "\n}\n";
os << "\n#endif\n";
}
static void generateSource(std::ostream &os, bool cpp) {
os << "#include <stamenc.h>\n";
static void generateSource(std::ostream &os) {
os << "#include <stamen.h>\n";
os << "#include \"shared.h\"\n\n";
if (cpp) os << "namespace stamen {\n\n";
for (const auto &[code, _] : Menu::getLookup()) {
const Menu *menu = Menu::getMenu(code);
if (!menu) throw EGenerate();
if (menu->callback) continue;
os << std::format("int {}(void) {{\n", menu->code);
for (const auto &[code, menu] : Menu::getLookup()) {
os << std::format("int {}(void) {{\n", menu.getCode());
os << std::format("\tstatic const item_t items[] = {{\n");
for (const auto &[code, prompt] : menu->items) {
for (const auto &[code, prompt] : menu.getItems()) {
os << std::format("\t\t{{ {}, \"{}\" }},\n", code, prompt);
}
os << std::format("\t}};\n");
os << std::format("\treturn stamen_display(\"{}\", items, "
"sizeof(items) / sizeof(item_t));\n",
menu->title);
"sizeof(items) / sizeof(items[0]));\n",
menu.getTitle());
os << std::format("}}\n\n");
}
if (cpp) os << "}\n";
}
};
} // namespace stamen
int main(const int argc, const char *argv[]) {
const auto args = std::span(argv, size_t(argc));
@@ -73,8 +54,8 @@ int main(const int argc, const char *argv[]) {
std::string ext = cpp ? "pp" : "";
std::ofstream source(base + ".c" + ext), include(base + ".h" + ext);
Generator::generateSource(source, cpp);
Generator::generateInclude(include, cpp);
Generator::generateSource(source);
Generator::generateInclude(include);
return 0;
}
diff --git a/src/menu.cpp b/src/menu.cpp
@@ -0,0 +1,46 @@
#include "menu.h"
#include <format>
#include <fstream>
#include <iostream>
#include <sstream>
#include <tuple>
#include <utility>
Menu::lookup_t Menu::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();
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.lookup_items.emplace_back(code, prompt);
}
}
}
void Menu::internal_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) {
std::cout << std::format("{}{} ({})\n", std::string(depth << 1, ' '),
prompt, code);
menu->internal_print(code, depth + 1);
}
}
diff --git a/src/stamen.cpp b/src/stamen.cpp
@@ -1,13 +1,11 @@
#include "../include/stamen.h"
#include "stamen.h"
#include <cmath>
#include <format>
#include <iostream>
#include <ostream>
#include <variant>
namespace stamen {
int builtinDisplay(const char *title, const ::item_t itemv[], int size) {
int stamen_builtin_display(const char *title, const ::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;
@@ -47,5 +45,3 @@ int builtinDisplay(const char *title, const ::item_t itemv[], int size) {
return 1;
}
} // namespace stamen
diff --git a/src/stamenc.cpp b/src/stamenc.cpp
@@ -1,14 +0,0 @@
#include "../include/stamenc.h"
#include "../include/stamen.h"
using namespace stamen;
void stamen_read(const char *file) { stamen::Menu::read(file); }
void stamen_print(const char *entry) { stamen::Menu::print(entry); }
void stamen_insert(const char *code, stamen_callback_f callback) {
Menu::insert(code, callback);
}
int stamen_builtin_display(const char *title, const item_t items[], int size) {
return builtinDisplay(title, items, size);
}