stamen

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

commit f4fed18ad7916907f96caaa641143d3ed00f02f6
parent 69b2fecb3c6afe1358abb309dd8a3a95cdf417e7
author Dimitrije Dobrota <mail@dimitrijedobrota.com>
date Thu, 30 Nov 2023 02:16:26 +0000

Provide C version of the library * Remove old .ccls * Add C demo code * Provide C binding for the library

Diffstat:
D .ccls | ----
M .gitignore | ++
M CMakeLists.txt | ++++++ --
M demo/CMakeLists.txt | +++++++++++++++++++++++++++++++ --
A demo/main.c | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M demo/main.cpp | + -
M include/stamen.h | +++++ --
A include/stamenc.h | +++++++++++++++++++++++++++
M src/CMakeLists.txt | +++++++++++++++++ -------
M src/generate.cpp | ++++++++++++++++++++++ ------------------
A src/stamenc.cpp | +++++++++++++++++++++++

11 files changed, 199 insertions(+), 36 deletions(-)


diff --git a/ .ccls b/ .ccls

@@ -1,4 +0,0 @@
clang++
-style=file
-std=c++17
-Iinclude

diff --git a/ .gitignore b/ .gitignore

@@ -1,4 +1,6 @@ build .cache
demo/demo_menu.c
demo/demo_menu.cpp demo/demo_menu.h
demo/demo_menu.hpp

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

@@ -3,14 +3,18 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project( Stamen
VERSION 0.0.11
VERSION 0.0.12
DESCRIPTION "Static menu generator"
LANGUAGES CXX
LANGUAGES C CXX
) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_C_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
add_subdirectory(src) add_subdirectory(demo)

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

@@ -8,14 +8,14 @@ target_link_libraries(demo ) add_custom_command(
OUTPUT demo_menu.h demo_menu.cpp
OUTPUT demo_menu.hpp demo_menu.cpp
COMMAND ${CMAKE_BINARY_DIR}/bin/stamen-generate ${CMAKE_CURRENT_SOURCE_DIR}/demo_menu.conf DEPENDS demo_menu.conf stamen-generate COMMENT "Generating menu files" ) add_custom_target(GenerateMenu
DEPENDS demo_menu.h demo_menu.cpp
DEPENDS demo_menu.hpp demo_menu.cpp
COMMENT "Check if re-generation of menu files is required" )

@@ -28,3 +28,32 @@ set_target_properties(demo PROPERTIES ) configure_file(demo_menu.conf ${CMAKE_BINARY_DIR}/bin/demo_menu.conf COPYONLY)
add_executable(cdemo
main.c
${CMAKE_CURRENT_SOURCE_DIR}/demo_menu.c
)
target_link_libraries(cdemo
stamenc
)
add_custom_command(
OUTPUT demo_menu.h demo_menu.c
COMMAND ${CMAKE_BINARY_DIR}/bin/stamen-generate ${CMAKE_CURRENT_SOURCE_DIR}/demo_menu.conf c
DEPENDS demo_menu.conf stamen-generate
COMMENT "Generating menu files"
)
add_custom_target(GenerateMenuC
DEPENDS demo_menu.h demo_menu.c
COMMENT "Check if re-generation of menu files is required"
)
add_dependencies(cdemo GenerateMenuC)
set_target_properties(cdemo PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)

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

@@ -0,0 +1,65 @@
#include "demo_menu.h"
#include "stamenc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const display_f display = stamen_builtin_display;
int operation1(void) {
printf("operation 1\n");
printf("Some operation is done\n");
return 1;
}
int operation2(void) {
printf("operation 2\n");
printf("Some other operation is done\n");
return 1;
}
int operation3(void) {
printf("operation 3\n");
printf("Yet another operation is done\n");
return 1;
}
int finish(void) {
printf("finishing...\n");
exit(0);
}
int menu_static_run(void) { return menu_main(); }
int menu_dynamic_run(void) { return stamen_start("menu_main"); }
int menu_dynamic_print(void) {
stamen_print("menu_main");
return 1;
}
int main(int argc, const char *argv[]) {
const char *filename = "./demo_menu.conf";
char buffer[FILENAME_MAX] = {0};
const char *sep = strrchr(argv[0], '/');
if (sep) {
const size_t size = sep - argv[0] + 1;
memcpy(buffer, argv[0], size);
memcpy(buffer + size, filename, strlen(filename));
}
stamen_read(sep ? buffer : filename);
stamen_insert("finish", finish);
stamen_insert("operation1", operation1);
stamen_insert("operation2", operation2);
stamen_insert("operation3", operation3);
static const item_t items[] = {
{ menu_static_run, "Run statically generated menu"},
{ menu_dynamic_run, "Run dynamic menu"},
{menu_dynamic_print, "Print dynamic menu"},
{ finish, "Quit"},
};
display("Menu demo program", items, sizeof(items) / sizeof(item_t));
return 0;
}

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

@@ -1,6 +1,6 @@ #include <iostream>
#include "demo_menu.h"
#include "demo_menu.hpp"
using stamen::Menu;

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

@@ -1,6 +1,8 @@ #ifndef STAMEN_H #define STAMEN_H
#include "stamenc.h"
#include <exception> #include <format> #include <fstream>

@@ -31,6 +33,7 @@ public: : Menu(code, callback) {} struct item_t {
item_t() {}
item_t(const callback_f func, const std::string &prompt) : callback(func), prompt(prompt) {}

@@ -44,8 +47,8 @@ public: int operator()(void) const { return callback ? callback() : start(code); } private:
const std::string prompt, code;
const callback_f callback = nullptr;
std::string prompt, code;
callback_f callback = nullptr;
}; typedef int (*display_f)(const std::string &, const item_t[], std::size_t);

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

@@ -0,0 +1,27 @@
#ifndef CSTAMEN_H
#define CSTAMEN_H
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC extern
#endif
typedef int (*callback_f)(void);
typedef struct item_t item_t;
struct item_t {
callback_f callback;
const char *prompt;
};
typedef int (*display_f)(const char *, const item_t[], int);
extern const display_f display;
EXTERNC void stamen_read(const char *file);
EXTERNC void stamen_print(const char *entry);
EXTERNC int stamen_start(const char *entry);
EXTERNC void stamen_insert(const char *code, callback_f callback);
EXTERNC int stamen_builtin_display(const char *title, const item_t items[], int size);
#endif

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

@@ -1,6 +1,22 @@ add_library(stamen-include INTERFACE) target_include_directories(stamen-include INTERFACE ../include)
add_library(stamen stamen.cpp)
target_link_libraries(stamen PUBLIC stamen-include)
set_target_properties(stamen PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
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
)
add_executable(stamen-generate generate.cpp) target_link_libraries(stamen-generate PRIVATE stamen-include) target_include_directories(stamen-generate PUBLIC ../include)

@@ -11,16 +27,10 @@ set_target_properties(stamen-generate PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" )
add_library(stamen stamen.cpp)
target_link_libraries(stamen PUBLIC stamen-include)
set_target_properties(stamen PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
PUBLIC_HEADER ../include/stamen.h
)
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

@@ -13,63 +13,67 @@ public: } };
static void generateInclude(std::ostream &os) {
static void generateInclude(std::ostream &os, bool cpp) {
os << "#ifndef STAMEN_MENU_H\n"; os << "#define STAMEN_MENU_H\n\n";
os << "#include <stamen.h>\n\n";
os << "namespace stamen {\n\n";
os << std::format("#include <stamen{}.h>\n\n", !cpp ? "c" : "");
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 << "\n}\n";
if (cpp) os << "\n}\n";
os << "#endif\n"; }
static void generateSource(std::ostream &os) {
os << "#include <stamen.h>\n";
os << "#include \"shared.h\"\n";
os << "\nnamespace stamen {\n";
static void generateSource(std::ostream &os, bool cpp) {
os << std::format("#include <stamen{}.h>\n", !cpp ? "c" : "");
os << "#include \"shared.h\"\n\n";
if (cpp) os << "namespace stamen {\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 << std::format("\tstatic const Menu::item_t items[] = {{\n");
os << std::format("\tstatic const {}item_t items[] = {{\n",
cpp ? "Menu::" : "");
for (const auto &item : menu->items) {
os << std::format("\t\t{{{}, \"{}\"}},\n", item.getCode(),
os << std::format("\t\t{{ {}, \"{}\" }},\n", item.getCode(),
item.getPrompt()); } os << std::format("\t}};\n");
os << std::format("\treturn Menu::display(\"{}\", items, "
"sizeof(items) / sizeof(Menu::item_t));\n",
menu->title);
os << std::format("\treturn {0}display(\"{1}\", items, "
"sizeof(items) / sizeof({0}item_t));\n",
cpp ? "Menu::" : "", menu->title);
os << std::format("}}\n\n"); }
os << "\n}\n";
if (cpp) os << "\n}\n";
} }; } // namespace stamen int main(const int argc, const char *argv[]) {
if (argc != 2) {
if (argc != 2 && argc != 3) {
std::cout << "please enter exaclty one config file" << std::endl; return 1; }
const bool cpp = argc == 2 || std::string(argv[2]) == "cpp";
std::string path = argv[1]; Menu::read(path); std::string::size_type pos = path.rfind('.'); std::string base = pos != std::string::npos ? path.substr(0, pos) : path;
std::ofstream cpp(base + ".cpp"), h(base + ".h");
std::string ext = cpp ? "pp" : "";
Generator::generateSource(cpp);
Generator::generateInclude(h);
std::ofstream source(base + ".c" + ext), include(base + ".h" + ext);
Generator::generateSource(source, cpp);
Generator::generateInclude(include, cpp);
return 0; }

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

@@ -0,0 +1,23 @@
#include "../include/stamen.h"
using namespace stamen;
const Menu::display_f Menu::display = nullptr;
void stamen_read(const char *file) { stamen::Menu::read(file); }
void stamen_print(const char *entry) { stamen::Menu::print(entry); }
int stamen_start(const char *entry) { return stamen::Menu::start(entry); }
void stamen_insert(const char *code, callback_f callback) {
Menu::insert(code, callback);
}
int stamen_builtin_display(const char *title, const item_t items[], int size) {
Menu::item_t *litems = new Menu::item_t[size];
if (!litems) return -1;
for (int i = 0; i < size; i++) {
litems[i] = Menu::item_t(items[i].callback, items[i].prompt);
}
const int ret = builtinDisplay(title, litems, size);
delete[] litems;
return ret;
}