stamen

Static 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);
}