stamen

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

commit a5cb5bf5961c3321d890a0f5897689f76d2cc8ec
parent 76398d16dbd44d59cced0cd791f64a957e04034d
author Dimitrije Dobrota <mail@dimitrijedobrota.com>
date Sun, 23 Feb 2025 16:47:38 +0100

Drop C support, modernize codbase

Diffstat:
M CMakeLists.txt | + ---
M cmake/dev-mode.cmake | -----
M example/dynamic.cpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --
M example/static.cpp | ++++++++++++++++++++++++++++++++++++++++++++++ ------
D include/stamen/menu.h | ------------------
M include/stamen/menu.hpp | +++++++++++++++++ --------------------
D include/stamen/stamen.h | ----------------------------------------
D include/stamen/stamen.hpp | ----------------
D source/c_bindings.cpp | --------------------------------------
M source/generate.cpp | +++++ -----
M source/menu.cpp | +++++ --------
D source/stamen.cpp | ----------------------------------------------------------------
D test/CMakeLists.txt | -------------------------
D test/source/stamen_test.cpp | --------

14 files changed, 131 insertions(+), 258 deletions(-)


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

@@ -4,7 +4,7 @@ include(cmake/prelude.cmake) project( stamen
VERSION 1.2.4
VERSION 1.2.5
DESCRIPTION "Static menu generator" HOMEPAGE_URL "https://git.dimitrijedobrota.com/stamen" LANGUAGES C CXX

@@ -20,9 +20,7 @@ find_package(cemplate 0.1.6 CONFIG REQUIRED) add_library( stamen_stamen
source/stamen.cpp
source/menu.cpp
source/c_bindings.cpp
) target_link_libraries(stamen_stamen PUBLIC poafloc::poafloc) add_library(stamen::stamen ALIAS stamen_stamen)

diff --git a/ cmake/dev-mode.cmake b/ cmake/dev-mode.cmake

@@ -1,10 +1,5 @@ include(cmake/folders.cmake)
include(CTest)
if(BUILD_TESTING)
add_subdirectory(test)
endif()
option(ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF) if(ENABLE_COVERAGE) include(cmake/coverage.cmake)

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

@@ -1,9 +1,62 @@
#include <cmath>
#include <cstddef>
#include <format>
#include <iostream> #include <span> #include "stamen/menu.hpp"
#include "stamen/stamen.h"
namespace {
int display(const stamen::menu::menu_t& menu)
{
const int sizei = static_cast<int>(menu.items().size());
const size_t dgts = static_cast<size_t>(std::log10(sizei)) + 1;
int choice = 0;
while (true)
{
std::cout << std::format("{}:\n", menu.title());
for (auto i = 0UL; i < menu.items().size(); i++)
{
std::cout << std::format(
" {:{}}. {}\n", i, dgts, menu.items()[i].prompt);
}
while (true)
{
std::cout << "Choose an option: ";
if (std::cin >> choice && choice >= -1 && choice < sizei)
{
if (choice == -1)
{
std::cout << "Choice: back\n";
return 1;
}
const auto uchoice = static_cast<size_t>(choice);
std::cout << "Choice: " << menu.items()[uchoice].prompt << "\n\n";
const int res = menu.items()[uchoice].callback(uchoice);
if (res < 2) break;
return res - 1;
}
if (std::cin.eof())
{
std::cerr << "encountered end of input!\n";
return std::numeric_limits<int>::max();
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Invalid option, please choose again!\n";
}
std::cout << '\n' << std::flush;
}
return 1;
}
int finish(std::size_t /* unused */) // NOLINT {

@@ -31,6 +84,8 @@ int operation3(std::size_t /* unused */) // NOLINT return 1; }
} // namespace
int main(int argc, char* argv[]) { const std::span args(argv, argv + argc);

@@ -45,7 +100,7 @@ int main(int argc, char* argv[]) stamen::menu::insert("operation3", operation3); // start the menu on specific panel
stamen::menu::dynamic("menu_main", stamen::builtin_display);
stamen::menu::dynamic("menu_main", display);
return 0; }

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

@@ -1,4 +1,6 @@
#include <cmath>
#include <cstddef>
#include <format>
#include <iostream> #include "demo_menu.hpp"

@@ -7,14 +9,52 @@ namespace example { int menu_t::visit(const menu_t& menu) {
std::cout << menu.title << '\n';
for (auto i = 0UL; i < menu.items.size(); i++)
const int sizei = static_cast<int>(menu.items.size());
const size_t dgts = static_cast<size_t>(std::log10(sizei)) + 1;
int choice = 0;
while (true)
{
std::cout << i + 1 << ": " << menu.items[i].prompt << '\n';
std::cout << std::format("{}:\n", menu.title);
for (auto i = 0UL; i < menu.items.size(); i++)
{
std::cout << std::format(" {:{}}. {}\n", i, dgts, menu.items[i].prompt);
}
while (true)
{
std::cout << "Choose an option: ";
if (std::cin >> choice && choice >= -1 && choice < sizei)
{
if (choice == -1)
{
std::cout << "Choice: back\n";
return 1;
}
const auto uchoice = static_cast<size_t>(choice);
std::cout << "Choice: " << menu.items[uchoice].prompt << "\n\n";
const int res = menu.items[uchoice].callback(uchoice);
if (res < 2) break;
return res - 1;
}
if (std::cin.eof())
{
std::cerr << "encountered end of input!\n";
return std::numeric_limits<int>::max();
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Invalid option, please choose again!\n";
}
std::cout << '\n' << std::flush;
}
std::cout << "Auto calling option 1...\n";
menu.items[1].callback(1);
return 0;
return 1;
} int operation1(std::size_t /* unused */)

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

@@ -1,18 +0,0 @@
#pragma once
#include "stamen/stamen.h"
#ifdef __cplusplus
extern "C"
{
namespace stamen {
#endif
void stamen_menu_read(const char* filename);
void stamen_menu_insert(const char* code, stamen_callback_f callback);
int stamen_menu_dynamic(const char* code, stamen_display_f display);
#ifdef __cplusplus
} // namespace stamen
} // extern "C"
#endif

diff --git a/ include/stamen/menu.hpp b/ include/stamen/menu.hpp

@@ -1,30 +1,33 @@ #pragma once #include <cstring>
#include <functional>
#include <string> #include <unordered_map> #include <vector>
#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;
using callback_f = std::function<int(size_t)>;
using display_f = std::function<int(const menu_t&)>;
struct item_t
{
callback_f callback;
std::string prompt;
};
// NOLINTBEGIN extern std::unordered_map<std::string, callback_f> free_lookup; extern std::unordered_map<std::string, menu_t> menu_lookup; extern std::string display_stub_default;
extern display_f display;
// NOLINTEND void read(const char* filename);
void insert(const char* code, callback_f callback);
int dynamic(const char* code, display_f disp);
void insert(const char* code, const callback_f& callback);
int dynamic(const char* code, const display_f& disp);
int display_stub(std::size_t idx); class menu_t

@@ -48,19 +51,13 @@ public: menu_t(menu_t&&) = delete; menu_t& operator=(menu_t&&) = delete;
~menu_t() noexcept
{
for (const auto [_, prompt] : m_items)
{
delete[] prompt; // NOLINT
}
}
~menu_t() = default;
const std::string& get_code() const { return m_code; }
const std::string& get_title() const { return m_title; }
const std::string& code() const { return m_code; }
const std::string& title() const { return m_title; }
const item_t* get_itemv() const { return m_items.data(); }
std::size_t get_size() const { return m_items.size(); }
const auto& items() const { return m_items; }
auto& items() { return m_items; }
auto get_callback(std::size_t idx) const { return m_items[idx].callback; } const auto& get_code(std::size_t idx) const { return m_codes[idx].code; }

@@ -75,7 +72,7 @@ private: void insert(const std::string& code, const std::string& prompt,
callback_f callback = display_stub);
const callback_f& callback = display_stub);
struct code_t {

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

@@ -1,40 +0,0 @@
#pragma once
#include "stddef.h" // NOLINT
#ifdef __cplusplus
extern "C"
{
namespace stamen {
#endif
typedef int (*stamen_callback_f)(size_t); // NOLINT
typedef struct stamen_item_t stamen_item_t; // NOLINT
struct stamen_item_t
{
stamen_callback_f callback;
const char* prompt;
};
typedef int (*stamen_display_f)(const char*, // NOLINT
const stamen_item_t[],
size_t);
#if !defined __cplusplus || defined WITH_C_BINDINGS
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
#ifdef __cplusplus
} // namespace stamen
} // extern "C"
#endif

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

@@ -1,16 +0,0 @@
#pragma once
#include <functional>
#include <string>
namespace stamen {
using callback_f = std::function<int(size_t)>;
struct item_t
{
callback_f callback;
std::string prompt;
};
} // namespace stamen

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

@@ -1,38 +0,0 @@
#include "stamen/menu.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)
{
return builtin_display(title, itemv, size);
}
} // namespace stamen
namespace stamen::menu {
void stamen_menu_read(const char* filename)
{
read(filename);
}
void stamen_menu_insert(const char* code, stamen_callback_f callback)
{
insert(code, callback);
}
int stamen_menu_dynamic(const char* code, stamen_display_f disp)
{
return dynamic(code, disp);
}
} // namespace stamen::menu
}

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

@@ -21,7 +21,7 @@ auto accumulate_items(const stamen::menu::menu_t& lmenu) using namespace cemplate; // NOLINT initlist items;
for (auto i = 0UL; i < lmenu.get_size(); i++)
for (auto i = 0UL; i < lmenu.items().size(); i++)
{ items.emplace_back(initlist({ string(lmenu.get_prompt(i)),

@@ -99,18 +99,18 @@ void generate_source(std::ostream& ost, for (const auto& [code, menu] : stamen::menu::menu_lookup) { ost << func(
menu.get_code(),
menu.code(),
"extern int", {{"std::size_t", "/* unused */"}} ) << decl("static const menu_t", "menu") << initlist({
string(menu.get_title()),
menu.get_code(),
string(menu.title()),
menu.code(),
accumulate_items(menu), }) << ret("menu_t::visit(menu)")
<< func(menu.get_code());
<< func(menu.code());
} // clang-format on

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

@@ -59,7 +59,7 @@ void read(const char* filename) } }
void insert(const char* code, callback_f callback)
void insert(const char* code, const callback_f& callback)
{ auto itr = free_lookup.find(code); if (itr == free_lookup.end())

@@ -70,7 +70,7 @@ void insert(const char* code, callback_f callback) free_lookup.emplace(code, callback); }
int dynamic(const char* code, display_f disp)
int dynamic(const char* code, const display_f& disp)
{ menu::display_stub_default = code; menu::display = disp;

@@ -87,11 +87,8 @@ int display_stub(std::size_t idx) const auto ml_it = menu_lookup.find(code); if (ml_it != menu_lookup.end()) {
const auto& m = ml_it->second; // NOLINT
stack.push_back(&m);
const int ret =
display(m.get_title().c_str(), m.get_itemv(), m.get_size());
stack.push_back(&ml_it->second);
const int ret = display(ml_it->second);
stack.pop_back(); return ret;

@@ -106,7 +103,7 @@ int display_stub(std::size_t idx) void menu_t::insert(const std::string& code, const std::string& prompt,
callback_f callback)
const callback_f& callback)
{ char* buffer = new char[prompt.size() + 1]; // NOLINT strcpy(buffer, prompt.c_str()); // NOLINT

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

@@ -1,64 +0,0 @@
#include <cmath>
#include <format>
#include <iostream>
#include "stamen/stamen.h"
extern "C"
{
namespace stamen {
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;
int choice = 0;
while (true)
{
std::cout << std::format("{}:\n", title);
for (auto i = 0UL; i < size; i++)
{
std::cout << std::format(" {:{}}. {}\n", i, dgts, items[i].prompt);
}
while (true)
{
std::cout << "Choose an option: ";
if (std::cin >> choice && choice >= -1
&& choice < static_cast<int>(size))
{
if (choice == -1)
{
std::cout << "Choice: back\n";
return 1;
}
const auto uchoice = static_cast<size_t>(choice);
std::cout << "Choice: " << items[uchoice].prompt << "\n\n";
const int res = items[uchoice].callback(uchoice);
if (res < 2) break;
return res - 1;
}
if (std::cin.eof())
{
std::cerr << "encountered end of input!\n";
return std::numeric_limits<int>::max();
}
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Invalid option, please choose again!\n";
}
std::cout << std::endl;
}
return 1;
}
} // namespace stamen
}

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

@@ -1,25 +0,0 @@
cmake_minimum_required(VERSION 3.14)
project(stamenTests LANGUAGES CXX)
include(../cmake/project-is-top-level.cmake)
include(../cmake/folders.cmake)
# ---- Dependencies ----
if(PROJECT_IS_TOP_LEVEL)
find_package(stamen REQUIRED)
enable_testing()
endif()
# ---- Tests ----
add_executable(stamen_test source/stamen_test.cpp)
target_link_libraries(stamen_test PRIVATE stamen::stamen)
target_compile_features(stamen_test PRIVATE cxx_std_20)
add_test(NAME stamen_test COMMAND stamen_test)
# ---- End-of-file commands ----
add_folders(Test)

diff --git a/ test/source/stamen_test.cpp b/ test/source/stamen_test.cpp

@@ -1,8 +0,0 @@
#include <string>
#include "stamen/stamen.hpp"
int main()
{
return 0;
}