stamen

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

commita5cb5bf5961c3321d890a0f5897689f76d2cc8ec
parent76398d16dbd44d59cced0cd791f64a957e04034d
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateSun, 23 Feb 2025 16:47:38 +0100

Drop C support, modernize codbase

Diffstat:
MCMakeLists.txt|+---
Mcmake/dev-mode.cmake|-----
Mexample/dynamic.cpp|+++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mexample/static.cpp|++++++++++++++++++++++++++++++++++++++++++++++------
Dinclude/stamen/menu.h|------------------
Minclude/stamen/menu.hpp|+++++++++++++++++--------------------
Dinclude/stamen/stamen.h|----------------------------------------
Dinclude/stamen/stamen.hpp|----------------
Dsource/c_bindings.cpp|--------------------------------------
Msource/generate.cpp|+++++-----
Msource/menu.cpp|+++++--------
Dsource/stamen.cpp|----------------------------------------------------------------
Dtest/CMakeLists.txt|-------------------------
Dtest/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;
}