stamenStatic Menu Generator |
git clone git://git.dimitrijedobrota.com/stamen.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | |
commit | a88c5a020580b045ff3a0f1a9c8c2f45d0b8f17b |
parent | 15ae7d7f7b1ca24b5c6fc4f9ccea7cca3be1ebe3 |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Fri, 14 Feb 2025 15:50:55 +0100 |
Reimagine Cpp generator by producing a class * Untie stamen.hpp from stamen.h * Get rid of shared header * Custom namespace name * Fix cmake build location * Auto declare free functions
Diffstat:M | CMakeLists.txt | | | +- |
M | example/CMakeLists.txt | | | +++++++++++++--------------- |
M | example/dynamic.cpp | | | ++++++++++++------- |
M | example/example_cpp.cpp | | | ++++++++++++++++++++++++++--------------------- |
M | include/stamen/menu.hpp | | | +++++-- |
M | include/stamen/stamen.h | | | +++++ |
M | include/stamen/stamen.hpp | | | ++++++++----- |
M | source/c_bindings.cpp | | | ++++++--- |
M | source/generate.cpp | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- |
M | source/menu.cpp | | | ++++++++++++++++++++++--- |
M | source/stamen.cpp | | | +++++++---- |
11 files changed, 229 insertions(+), 84 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -4,7 +4,7 @@ include(cmake/prelude.cmake)
project(
stamen
VERSION 1.2.0
VERSION 1.2.1
DESCRIPTION "Static menu generator"
HOMEPAGE_URL "https://git.dimitrijedobrota.com/stamen"
LANGUAGES C CXX
diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt
@@ -4,7 +4,6 @@ project(stamenExamples CXX)
include(../cmake/project-is-top-level.cmake)
include(../cmake/folders.cmake)
if(PROJECT_IS_TOP_LEVEL)
find_package(stamen REQUIRED)
endif()
@@ -15,37 +14,36 @@ configure_file(shared.h shared.h COPYONLY)
add_custom_target(run-examples)
add_custom_command(
OUTPUT demo_menu.hpp demo_menu.cpp
COMMAND stamen::exe -d test_display --cpp demo_menu.conf
DEPENDS demo_menu.conf stamen::exe
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
COMMENT "Generating menu files"
OUTPUT demo_menu.hpp demo_menu.cpp
COMMAND stamen::exe -d test_display --cpp --namespace example demo_menu.conf
DEPENDS demo_menu.conf stamen::exe
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "Generating menu files"
)
add_custom_command(
OUTPUT demo_menu.h demo_menu.c
COMMAND stamen::exe --c demo_menu.conf
DEPENDS demo_menu.conf stamen::exe
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
COMMENT "Generating cmenu files"
OUTPUT demo_menu.h demo_menu.c
COMMAND stamen::exe --c demo_menu.conf
DEPENDS demo_menu.conf stamen::exe
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "Generating cmenu files"
)
function(add_example NAME EXT)
add_executable("${NAME}" "${NAME}.${EXT}")
target_include_directories("${NAME}" PRIVATE "${PROJECT_BINARY_DIR}")
target_include_directories("${NAME}" PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
target_link_libraries("${NAME}" PRIVATE stamen::stamen)
target_compile_features("${NAME}" PRIVATE cxx_std_20)
add_custom_target("run_${NAME}" COMMAND "${NAME}" demo_menu.conf VERBATIM)
add_dependencies("run_${NAME}" "${NAME}")
add_dependencies(run-examples "run_${NAME}")
endfunction()
add_example(example_c c)
target_sources(example_c PRIVATE "demo_menu.c")
target_sources(example_c PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/demo_menu.c")
add_example(example_cpp cpp)
target_sources(example_cpp PRIVATE "demo_menu.cpp")
target_sources(example_cpp PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/demo_menu.cpp")
add_example(dynamic cpp)
diff --git a/example/dynamic.cpp b/example/dynamic.cpp
@@ -1,28 +1,33 @@
#include <cstddef>
#include <iostream>
#include <span>
#include "stamen/menu.hpp"
#include "stamen/stamen.h"
int finish(size_t /* unused */)
int finish(std::size_t /* unused */) // NOLINT
{
exit(0);
}
int operation1(size_t /* unused */)
int operation1(std::size_t /* unused */) // NOLINT
{
std::cout << "1" << std::endl;
std::cout << "1\n";
std::cout << std::flush;
return 1;
}
int operation2(size_t /* unused */)
int operation2(std::size_t /* unused */) // NOLINT
{
std::cout << "2" << std::endl;
std::cout << "2\n";
std::cout << std::flush;
return 1;
}
int operation3(size_t /* unused */)
int operation3(std::size_t /* unused */) // NOLINT
{
std::cout << "3" << std::endl;
std::cout << "3\n";
std::cout << std::flush;
return 1;
}
diff --git a/example/example_cpp.cpp b/example/example_cpp.cpp
@@ -1,52 +1,57 @@
#include <cstddef>
#include <iostream>
#include <span>
#include "demo_menu.hpp"
#include "stamen/stamen.hpp"
int test_display(const char* title, const stamen::item_t itemv[], size_t size)
{
const auto items = std::span(itemv, itemv + size);
namespace example {
std::cout << title << std::endl;
for (auto i = 0UL; i < size; i++)
int menu_t::visit(const menu_t& menu)
{
std::cout << menu.title << '\n';
for (auto i = 0UL; i < menu.items.size(); i++)
{
std::cout << i + 1 << ": " << items[i].prompt << '\n';
std::cout << i + 1 << ": " << menu.items[i].prompt << '\n';
}
std::cout << "Auto calling option 1...\n";
items[1].callback(1);
menu.items[1].callback(1);
return 0;
}
int operation1(size_t /* unused */)
int operation1(std::size_t /* unused */) // NOLINT
{
std::cout << "operation 1" << std::endl;
std::cout << "Some operation is done" << std::endl;
std::cout << "operation 1\n\n";
std::cout << "Some operation is done\n\n";
std::cout << std::flush;
return 1;
}
int operation2(size_t /* unused */)
int operation2(std::size_t /* unused */) // NOLINT
{
std::cout << "operation 2" << std::endl;
std::cout << "Some other operation is done" << std::endl;
std::cout << "operation 2\n";
std::cout << "Some other operation is done\n";
std::cout << std::flush;
return 1;
}
int operation3(size_t /* unused */)
int operation3(std::size_t /* unused */) // NOLINT
{
std::cout << "operation 3" << std::endl;
std::cout << "Yet another operation is done" << std::endl;
std::cout << "operation 3\n";
std::cout << "Yet another operation is done\n";
std::cout << std::flush;
return 1;
}
int finish(size_t /* unused */)
int finish(std::size_t /* unused */) // NOLINT
{
std::cout << "finishing..." << std::endl;
std::cout << "finishing...\n";
std::cout << std::flush;
exit(0);
}
} // namespace example
int main()
{
menu_main(0);
example::menu_main(0);
return 0;
}
diff --git a/include/stamen/menu.hpp b/include/stamen/menu.hpp
@@ -1,17 +1,20 @@
#pragma once
#include <cstring>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include "stamen/stamen.hpp"
#include "stamen/stamen.h"
namespace stamen::menu {
class menu_t;
using callback_f = stamen_callback_f;
using display_f = stamen_display_f;
using item_t = stamen_item_t;
// NOLINTBEGIN
extern std::unordered_map<std::string, callback_f> free_lookup;
extern std::unordered_map<std::string, menu_t> menu_lookup;
diff --git a/include/stamen/stamen.h b/include/stamen/stamen.h
@@ -26,6 +26,11 @@ typedef int (*stamen_display_f)(const char*, // NOLINT
int stamen_builtin_display(const char* title,
const stamen_item_t itemv[],
size_t size);
#else
int builtin_display(const char* title,
const stamen_item_t itemv[],
size_t size);
#endif
diff --git a/include/stamen/stamen.hpp b/include/stamen/stamen.hpp
@@ -1,13 +1,16 @@
#pragma once
#include "stamen/stamen.h"
#include <functional>
#include <string>
namespace stamen {
using callback_f = stamen_callback_f;
using display_f = stamen_display_f;
using item_t = stamen_item_t;
using callback_f = std::function<int(size_t)>;
int builtin_display(const char* title, const item_t itemv[], size_t size);
struct item_t
{
callback_f callback;
std::string prompt;
};
} // namespace stamen
diff --git a/source/c_bindings.cpp b/source/c_bindings.cpp
@@ -1,10 +1,13 @@
#include "stamen/menu.hpp"
#include "stamen/stamen.hpp"
extern "C"
{
namespace stamen {
int builtin_display(const char* title,
const menu::item_t itemv[],
size_t size);
int stamen_builtin_display(const char* title,
const stamen_item_t itemv[],
size_t size)
@@ -18,12 +21,12 @@ namespace stamen::menu {
void stamen_menu_read(const char* filename)
{
return read(filename);
read(filename);
}
void stamen_menu_insert(const char* code, stamen_callback_f callback)
{
return insert(code, callback);
insert(code, callback);
}
int stamen_menu_dynamic(const char* code, stamen_display_f disp)
diff --git a/source/generate.cpp b/source/generate.cpp
@@ -5,54 +5,57 @@
#include "poafloc/poafloc.hpp"
#include "stamen/menu.hpp"
#include "stamen/stamen.hpp"
struct arguments_t
{
std::string config;
std::string display;
std::string header = "shared.h";
std::string nspace = "stamen";
bool cpp = false;
bool user = false;
};
void generate_include(std::ostream& ost)
namespace {
void generate_include_c(std::ostream& ost, const arguments_t& args)
{
ost << "#ifndef STAMEN_MENU_H\n";
ost << "#define STAMEN_MENU_H\n\n";
for (const auto& [code, menu] : stamen::menu::menu_lookup)
{
ost << std::format("int {}(size_t);\n", menu.get_code());
ost << std::format("int {}(size_t /* unused */);\n", menu.get_code());
}
ost << "\n#endif\n";
(void)args;
}
void generate_source(std::ostream& ost, const arguments_t& args)
void generate_source_c(std::ostream& ost, const arguments_t& args)
{
ost << std::format("#include \"{}\"\n", args.header);
if (args.user)
{
if (args.cpp) ost << "#include \"stamen.hpp\"\n\n";
else ost << "#include \"stamen.h\"\n\n";
ost << "#include \"stamen.h\"\n\n";
}
else
{
if (args.cpp) ost << "#include <stamen/stamen.hpp>\n\n";
else ost << "#include <stamen/stamen.h>\n\n";
ost << "#include <stamen/stamen.h>\n\n";
}
ost << std::format("extern int {}(const char *title, ", args.display);
if (args.cpp) ost << "const stamen::item_t itemv[], size_t size);\n\n";
else ost << "const stamen_item_t itemv[], size_t size);\n\n";
ost << "const stamen_item_t itemv[], size_t size);\n\n";
for (const auto& [code, _] : stamen::menu::free_lookup)
{
ost << std::format("extern int {}(size_t);\n", code);
}
ost << '\n';
for (const auto& [code, menu] : stamen::menu::menu_lookup)
{
ost << std::format("int {}(size_t /* unused */) {{\n", menu.get_code());
if (args.cpp) ost << "\tstatic const stamen::item_t items[] = ";
else ost << "\tstatic const stamen_item_t items[] = ";
ost << "\tstatic const stamen_item_t items[] = ";
ost << "{\n";
for (auto i = 0UL; i < menu.get_size(); i++)
@@ -68,6 +71,80 @@ void generate_source(std::ostream& ost, const arguments_t& args)
}
}
void generate_include_cpp(std::ostream& ost, const arguments_t& args)
{
ost << "#pragma once\n\n";
ost << "#include <cstddef>\n";
ost << "#include <string>\n";
ost << "#include <vector>\n\n";
ost << (args.user ? "#include \"stamen.hpp\"\n\n"
: "#include <stamen/stamen.hpp>\n\n");
ost << std::format("namespace {}\n{{\n", args.nspace);
ost << R"(
struct menu_t {
std::string title;
std::vector<stamen::item_t> items;
static int visit(const menu_t& menu);
};
)";
for (const auto& [code, menu] : stamen::menu::menu_lookup)
{
ost << std::format("int {}(std::size_t /* unused */);\n", menu.get_code());
}
ost << std::format("\n}} // namespace {}\n", args.nspace);
}
void generate_source_cpp(std::ostream& ost,
const arguments_t& args,
const std::string& include)
{
ost << "#include <cstddef>\n\n";
ost << std::format("#include \"{}\"\n\n", include);
ost << std::format("namespace {}\n{{\n\n", args.nspace);
ost << std::format("extern int {}(const char *title, ", args.display);
ost << "const stamen::item_t itemv[], size_t size);\n\n";
for (const auto& [code, _] : stamen::menu::free_lookup)
{
ost << std::format("extern int {}(std::size_t);\n", code);
}
ost << '\n';
for (const auto& [code, menu] : stamen::menu::menu_lookup)
{
ost << std::format("int {}(size_t /* unused */) // NOLINT\n{{\n",
menu.get_code());
ost << "\tstatic const menu_t menu = {\n";
ost << std::format("\t\t.title = \"{}\",\n", menu.get_title());
ost << "\t\t.items = {\n";
for (auto i = 0UL; i < menu.get_size(); i++)
{
ost << std::format("\t\t\t{{.callback = {}, .prompt = \"{}\"}},\n",
menu.get_code(i),
menu.get_prompt(i));
}
ost << "\t\t}\n\t};\n\n";
ost << "\treturn menu_t::visit(menu);\n";
ost << "}\n\n";
}
ost << std::format("\n}} // namespace {}\n", args.nspace);
}
int parse_opt(int key, const char* arg, poafloc::Parser* parser)
{
auto* arguments = static_cast<arguments_t*>(parser->input());
@@ -76,8 +153,14 @@ int parse_opt(int key, const char* arg, poafloc::Parser* parser)
case 'd':
arguments->display = arg;
break;
case 'h':
arguments->header = arg;
case 'n':
if (!arguments->cpp)
{
poafloc::failure(parser, 0, 0, "Namespace only available in C++ mode");
poafloc::help(parser, stderr, poafloc::STD_USAGE);
break;
}
arguments->nspace = arg;
break;
case 'u':
arguments->user = true;
@@ -113,17 +196,21 @@ int parse_opt(int key, const char* arg, poafloc::Parser* parser)
return 0;
}
} // namespace
// clang-format off
static const poafloc::option_t options[] {
{nullptr, 0, nullptr, 0, "Output mode", 1},
{"c", 666, nullptr, 0, "Generate files for C"},
{"cpp", 777, nullptr, 0, "Generate files for C++"},
{nullptr, 0, nullptr, 0, "Output settings", 2},
{"display", 'd', "FUNC", 0, "Set display function to be called"},
{"display", 'd', "name", 0, "Set display function to be called"},
{"namespace", 'n', "name", 0, "Namespace, C++ only"},
{"user", 'u', nullptr, 0, "Include user stamen headers"},
{"header", 'h', "HDR", 0, "Header with free functions, default: shared.h"},
{nullptr, 0, nullptr, 0, "Informational Options", -1},
{nullptr},
};
// clang-format on
static const poafloc::arg_t arg {
options,
@@ -146,15 +233,29 @@ int main(int argc, char* argv[])
stamen::menu::read(config.c_str());
const std::string::size_type pos = args.config.rfind('.');
const std::string ext = args.cpp ? "pp" : "";
const std::string base =
pos != std::string::npos ? config.substr(0, pos) : config;
std::ofstream include(base + ".h" + ext);
generate_include(include);
if (args.cpp)
{
const auto include_filename = base + ".hpp";
std::ofstream include(include_filename);
generate_include_cpp(include, args);
const auto source_filename = base + ".cpp";
std::ofstream source(source_filename);
generate_source_cpp(source, args, include_filename);
}
else
{
const auto include_filename = base + ".h";
std::ofstream include(include_filename);
generate_include_c(include, args);
std::ofstream source(base + ".c" + ext);
generate_source(source, args);
const auto source_filename = base + ".c";
std::ofstream source(source_filename);
generate_source_c(source, args);
}
return 0;
}
diff --git a/source/menu.cpp b/source/menu.cpp
@@ -1,9 +1,9 @@
#include <deque>
#include <format>
#include <fstream>
#include <iostream>
#include <sstream>
#include <tuple>
#include <unordered_set>
#include <utility>
#include "stamen/menu.hpp"
@@ -19,6 +19,7 @@ display_f display;
void read(const char* filename)
{
std::unordered_set<std::string> refd;
std::fstream fst(filename);
std::string line;
std::string delim;
@@ -34,7 +35,11 @@ void read(const char* filename)
iss >> delim >> code >> std::ws;
std::getline(iss, prompt);
if (delim != "+") last->second.insert(code, prompt);
if (delim != "+")
{
last->second.insert(code, prompt);
refd.insert(code);
}
else
{
const auto [iter, succ] = menu_lookup.emplace(
@@ -44,10 +49,24 @@ void read(const char* filename)
last = iter;
}
}
for (const auto& ref : refd)
{
if (!menu_lookup.contains(ref))
{
free_lookup.emplace(ref, nullptr);
}
}
}
void insert(const char* code, callback_f callback)
{
auto itr = free_lookup.find(code);
if (itr == free_lookup.end())
{
std::cout << "Stamen: unknown callback registration...\n" << std::flush;
return;
}
free_lookup.emplace(code, callback);
}
@@ -81,7 +100,7 @@ int display_stub(std::size_t idx)
const auto fl_it = free_lookup.find(code);
if (fl_it != free_lookup.end()) return fl_it->second(0);
std::cout << "Stamen: nothing to do..." << std::endl;
std::cout << "Stamen: nothing to do...\n" << std::flush;
return 1;
}
diff --git a/source/stamen.cpp b/source/stamen.cpp
@@ -2,13 +2,15 @@
#include <format>
#include <iostream>
#include "stamen/stamen.hpp"
#include "stamen/menu.h"
#include "stamen/stamen.h"
extern "C"
{
namespace stamen {
int builtin_display(const char* title, const item_t itemv[], size_t size)
int builtin_display(const char* title,
const stamen_item_t itemv[],
size_t size)
{
const auto items = std::span(itemv, size);
const size_t dgts = static_cast<size_t>(std::log10(size)) + 1;
@@ -59,3 +61,4 @@ int builtin_display(const char* title, const item_t itemv[], size_t size)
}
} // namespace stamen
}