stamen

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

commit76398d16dbd44d59cced0cd791f64a957e04034d
parentc620723b9d2261f3348fe82b18b074f9a7e4ca9e
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateSun, 23 Feb 2025 16:14:00 +0100

Remove C support from generator

Diffstat:
MCMakeLists.txt|+-
Mexample/CMakeLists.txt|+++--------------
Dexample/example_c.c|----------------------------------------------
Dexample/example_cpp.cpp|---------------------------------------------------------
Aexample/static.cpp|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/generate.cpp|+++++++++++++++++-----------------------------------------------------------------

6 files changed, 93 insertions(+), 251 deletions(-)


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

@@ -4,7 +4,7 @@ include(cmake/prelude.cmake)

project(
stamen
VERSION 1.2.3
VERSION 1.2.4
DESCRIPTION "Static menu generator"
HOMEPAGE_URL "https://git.dimitrijedobrota.com/stamen"
LANGUAGES C CXX

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

@@ -15,20 +15,12 @@ add_custom_target(run-examples)

add_custom_command(
OUTPUT demo_menu.hpp demo_menu.cpp
COMMAND stamen::exe -d test_display --cpp --namespace example demo_menu.conf
COMMAND stamen::exe --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 "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "Generating cmenu files"
)
function(add_example NAME EXT)
add_executable("${NAME}" "${NAME}.${EXT}")
target_include_directories("${NAME}" PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")

@@ -39,11 +31,8 @@ function(add_example NAME EXT)

add_dependencies(run-examples "run_${NAME}")
endfunction()
add_example(example_c c)
target_sources(example_c PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/demo_menu.c")
add_example(example_cpp cpp)
target_sources(example_cpp PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/demo_menu.cpp")
add_example(static cpp)
target_sources(static PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/demo_menu.cpp")
add_example(dynamic cpp)

diff --git a/example/example_c.c b/example/example_c.c

@@ -1,46 +0,0 @@

#include <stdio.h>
#include <stdlib.h>
#include "demo_menu.h"
#include "stamen/stamen.h"
int operation1(size_t unused)
{
(void)unused;
printf("operation 1\n");
printf("Some operation is done\n");
return 1;
}
int operation2(size_t unused)
{
(void)unused;
printf("operation 2\n");
printf("Some other operation is done\n");
return 1;
}
int operation3(size_t unused)
{
(void)unused;
printf("operation 3\n");
printf("Yet another operation is done\n");
return 1;
}
int finish(size_t unused)
{
(void)unused;
printf("finishing...\n");
exit(0);
}
int main(void)
{
menu_main(0);
return 0;
}

diff --git a/example/example_cpp.cpp b/example/example_cpp.cpp

@@ -1,57 +0,0 @@

#include <cstddef>
#include <iostream>
#include "demo_menu.hpp"
namespace example {
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 << ": " << menu.items[i].prompt << '\n';
}
std::cout << "Auto calling option 1...\n";
menu.items[1].callback(1);
return 0;
}
int operation1(std::size_t /* unused */) // NOLINT
{
std::cout << "operation 1\n\n";
std::cout << "Some operation is done\n\n";
std::cout << std::flush;
return 1;
}
int operation2(std::size_t /* unused */) // NOLINT
{
std::cout << "operation 2\n";
std::cout << "Some other operation is done\n";
std::cout << std::flush;
return 1;
}
int operation3(std::size_t /* unused */) // NOLINT
{
std::cout << "operation 3\n";
std::cout << "Yet another operation is done\n";
std::cout << std::flush;
return 1;
}
int finish(std::size_t /* unused */) // NOLINT
{
std::cout << "finishing...\n";
std::cout << std::flush;
exit(0);
}
} // namespace example
int main()
{
example::menu_main(0);
return 0;
}

diff --git a/example/static.cpp b/example/static.cpp

@@ -0,0 +1,57 @@

#include <cstddef>
#include <iostream>
#include "demo_menu.hpp"
namespace example {
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 << ": " << menu.items[i].prompt << '\n';
}
std::cout << "Auto calling option 1...\n";
menu.items[1].callback(1);
return 0;
}
int operation1(std::size_t /* unused */)
{
std::cout << "operation 1\n\n";
std::cout << "Some operation is done\n\n";
std::cout << std::flush;
return 1;
}
int operation2(std::size_t /* unused */)
{
std::cout << "operation 2\n";
std::cout << "Some other operation is done\n";
std::cout << std::flush;
return 1;
}
int operation3(std::size_t /* unused */)
{
std::cout << "operation 3\n";
std::cout << "Yet another operation is done\n";
std::cout << std::flush;
return 1;
}
int finish(std::size_t /* unused */)
{
std::cout << "finishing...\n";
std::cout << std::flush;
exit(0); // NOLINT
}
} // namespace example
int main()
{
example::menu_main(0);
return 0;
}

diff --git a/source/generate.cpp b/source/generate.cpp

@@ -1,4 +1,4 @@

#include <format>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>

@@ -10,11 +10,8 @@

struct arguments_t
{
std::string config;
std::string display;
std::filesystem::path config;
std::string nspace = "stamen";
bool cpp = false;
bool user = false;
};
namespace {

@@ -23,74 +20,19 @@ auto accumulate_items(const stamen::menu::menu_t& lmenu)

{
using namespace cemplate; // NOLINT
initlist items; for (auto i = 0UL; i < lmenu.get_size(); i++)
initlist items;
for (auto i = 0UL; i < lmenu.get_size(); i++)
{
items.emplace_back(initlist({
lmenu.get_code(i),
string(lmenu.get_prompt(i)),
lmenu.get_code(i),
}));
}
return initlist_elem(items);
}
void generate_include_c(std::ostream& ost, const arguments_t& args)
{
using namespace cemplate; // NOLINT
ost << pragma_once();
for (const auto& [code, menu] : stamen::menu::menu_lookup)
{
ost << func_decl(menu.get_code(), "int", {{"size_t", "/* unused */"}});
}
(void)args;
}
void generate_source_c(std::ostream& ost, const arguments_t& args)
{
using namespace cemplate; // NOLINT
if (args.user)
{
ost << include("stamen.h", true);
}
else
{
ost << include("stamen/stamen.h");
}
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 {}(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());
ost << "\tstatic const stamen_item_t items[] = ";
ost << "{\n";
for (auto i = 0UL; i < menu.get_size(); i++)
{
ost << "\t\t{ " << menu.get_code(i);
ost << ", \"" << menu.get_prompt(i) << "\" },\n";
}
ost << "\t};\n";
ost << std::format("\treturn {}(\"{}\"", args.display, menu.get_title());
ost << ", items, sizeof(items) / sizeof(items[0]));\n";
ost << "}\n\n";
}
}
void generate_include_cpp(std::ostream& ost, const arguments_t& args)
void generate_include(std::ostream& ost, const arguments_t& args)
{
using namespace cemplate; // NOLINT

@@ -105,12 +47,14 @@ void generate_include_cpp(std::ostream& ost, const arguments_t& args)

ost << nspace(args.nspace);
ost << R"(
struct menu_t {
struct menu_t
{
using callback_f = std::function<int(std::size_t)>;
struct item_t {
callback_f callback;
struct item_t
{
std::string prompt;
callback_f callback;
};
std::string title;

@@ -122,32 +66,35 @@ struct menu_t {

)";
for (const auto& [code, menu] : stamen::menu::menu_lookup)
ost << "// generated function\n";
for (const auto& [code, _] : stamen::menu::menu_lookup)
{
ost << func_decl(
menu.get_code(), "int", {{"std::size_t", "/* unused */"}});
ost << func_decl(code, "int", {{"std::size_t", "/* unused */"}});
}
ost << "\n// free function\n";
for (const auto& [code, _] : stamen::menu::free_lookup)
{
ost << func_decl(code, "int", {{"std::size_t", "/* unused */"}});
}
ost << nspace(args.nspace);
}
void generate_source_cpp(std::ostream& ost,
const arguments_t& args,
const std::string& include_name)
void generate_source(std::ostream& ost,
const arguments_t& args,
const std::string& include_name)
{
using namespace cemplate; // NOLINT
ost << include("cstddef");
ost << '\n';
ost << include(include_name, true);
ost << '\n';
ost << nspace(args.nspace);
for (const auto& [code, _] : stamen::menu::free_lookup)
{
ost << func_decl(code, "extern int", {{"std::size_t", "/* unused */"}});
}
ost << '\n';
// clang-format off
for (const auto& [code, menu] : stamen::menu::menu_lookup)
{

@@ -175,27 +122,9 @@ int parse_opt(int key, const char* arg, poafloc::Parser* parser)

auto* arguments = static_cast<arguments_t*>(parser->input());
switch (key)
{
case 'd':
arguments->display = arg;
break;
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;
break;
case 666:
arguments->cpp = false;
break;
case 777:
arguments->cpp = true;
break;
case poafloc::ARG:
if (!arguments->config.empty())
{

@@ -208,13 +137,6 @@ int parse_opt(int key, const char* arg, poafloc::Parser* parser)

poafloc::failure(parser, 0, 0, "Missing an argument");
poafloc::help(parser, stderr, poafloc::STD_USAGE);
break;
case poafloc::END:
if (arguments->display.empty())
{
if (arguments->cpp) arguments->display = "stamen::builtin_display";
else arguments->display = "stamen_builtin_display";
}
break;
default:
break;
}

@@ -225,14 +147,8 @@ int parse_opt(int key, const char* arg, poafloc::Parser* parser)

// 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', "name", 0, "Set display function to be called"},
{"namespace", 'n', "name", 0, "Namespace, C++ only"},
{"user", 'u', nullptr, 0, "Include user stamen headers"},
{nullptr, 0, nullptr, 0, "Informational Options", -1},
{nullptr},
};
// clang-format on

@@ -257,30 +173,13 @@ int main(int argc, char* argv[])

const auto& config = args.config;
stamen::menu::read(config.c_str());
const std::string::size_type pos = args.config.rfind('.');
const std::string base =
pos != std::string::npos ? config.substr(0, pos) : config;
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);
const auto include_filename = args.config.stem().replace_extension(".hpp");
std::ofstream include(include_filename);
generate_include(include, args);
const auto source_filename = base + ".c";
std::ofstream source(source_filename);
generate_source_c(source, args);
}
const auto source_filename = args.config.stem().replace_extension(".cpp");
std::ofstream source(source_filename);
generate_source(source, args, include_filename);
return 0;
}