stamen

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

commit903df3a3cfc31924e986d2ae0ac86a81d3178c54
parent92644eb864725cd28360172c93315e626f7b05e7
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateTue, 25 Feb 2025 11:36:32 +0100

Cleanup the code * No move static variables and free functions * Add a self containing class Stamen * const char* are replaced by const std::string& * Rethink acces rights

Diffstat:
M.clang-tidy|+-
MCMakeLists.txt|+-
Mexample/dynamic.cpp|++++++++++++++++-------
Minclude/stamen/stamen.hpp|++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msource/generate.cpp|+++++++++++++-----------
Msource/stamen.cpp|++++++++++++++++++++++++++++++++++++++++++++--------------------------------------

6 files changed, 120 insertions(+), 103 deletions(-)


diff --git a/.clang-tidy b/.clang-tidy

@@ -66,7 +66,7 @@ CheckOptions:

- key: 'readability-identifier-naming.AbstractClassCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassCase'
value: 'lower_case'
value: 'CamelCase'
- key: 'readability-identifier-naming.ClassConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassMemberCase'

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

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

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

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

@@ -1,6 +1,7 @@

#include <cmath>
#include <cstddef>
#include <format>
#include <fstream>
#include <iostream>
#include <limits>
#include <span>

@@ -8,7 +9,7 @@

#include "stamen/stamen.hpp"
namespace {
int display(const stamen::menu_t& menu)
int display(const stamen::Menu& menu)
{
const int sizei = static_cast<int>(menu.items().size());
const size_t dgts = static_cast<size_t>(std::log10(sizei)) + 1;

@@ -91,17 +92,25 @@ int main(int argc, char* argv[])

{
const std::span args(argv, argv + argc);
if (argc != 2)
{
std::cout << std::format("Usage: {} filename\n", args[0]);
return 1;
}
std::ifstream ifs(args[1]);
// read the configuration
for (const auto& arg : args.subspan(1)) stamen::read(arg);
stamen::Stamen inst(ifs);
// register free functions
stamen::insert("finish", finish);
stamen::insert("operation1", operation1);
stamen::insert("operation2", operation2);
stamen::insert("operation3", operation3);
inst.insert("finish", finish);
inst.insert("operation1", operation1);
inst.insert("operation2", operation2);
inst.insert("operation3", operation3);
// start the menu on specific panel
stamen::dynamic("menu_main", display);
inst.dynamic("menu_main", display);
return 0;
}

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

@@ -2,16 +2,16 @@

#include <cstring>
#include <functional>
#include <stack>
#include <string>
#include <unordered_map>
#include <vector>
namespace stamen {
class menu_t;
class Menu;
using callback_f = std::function<int(size_t)>;
using display_f = std::function<int(const menu_t&)>;
struct item_t
{

@@ -20,64 +20,64 @@ struct item_t

callback_f callback;
};
// 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;
// NOLINTEND
void read(const char* filename);
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
class Menu
{
struct private_ctor_t
{
};
friend void read(const char* filename);
public:
// Tag type dispatch
menu_t(private_ctor_t, // NOLINT
const std::string& code,
const std::string& prompt)
: menu_t(code, prompt)
Menu(std::string code, std::string prompt)
: m_code(std::move(code))
, m_title(std::move(prompt))
{
}
menu_t(const menu_t&) = delete;
menu_t& operator=(const menu_t&) = delete;
menu_t(menu_t&&) = delete;
menu_t& operator=(menu_t&&) = delete;
Menu(const Menu&) = delete;
Menu& operator=(const Menu&) = delete;
Menu(Menu&&) = default;
Menu& operator=(Menu&&) = default;
~menu_t() = default;
~Menu() = default;
const std::string& code() const { return m_code; }
const std::string& title() const { return m_title; }
std::size_t size() const { return m_items.size(); }
const item_t& item(std::size_t idx) const { return m_items[idx]; }
item_t& item(std::size_t idx) { return m_items[idx]; }
const item_t& item(std::size_t idx) const { return m_items[idx]; }
const auto& items() const { return m_items; }
auto& items() { return m_items; }
private:
menu_t(std::string code, std::string prompt)
: m_code(std::move(code))
, m_title(std::move(prompt))
{
}
void insert(const std::string& code,
const std::string& prompt,
const callback_f& callback = display_stub);
const callback_f& callback);
private:
std::string m_code;
std::string m_title;
std::vector<item_t> m_items;
};
class Stamen
{
public:
using display_f = std::function<int(const Menu&)>;
explicit Stamen(std::istream& ist);
void insert(const std::string& code, const callback_f& callback);
int dynamic(const std::string& code, const display_f& disp);
const auto& free_lookup() const { return m_free_lookup; }
const auto& menu_lookup() const { return m_menu_lookup; }
private:
int display_stub(std::size_t idx);
std::unordered_map<std::string, callback_f> m_free_lookup;
std::unordered_map<std::string, Menu> m_menu_lookup;
display_f m_stub_display;
std::string m_stub_default;
std::stack<const Menu*> m_stub_stack;
};
} // namespace stamen

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

@@ -16,7 +16,7 @@ struct arguments_t

namespace {
auto accumulate_items(const stamen::menu_t& lmenu)
auto accumulate_items(const stamen::Menu& lmenu)
{
using namespace cemplate; // NOLINT

@@ -32,7 +32,9 @@ auto accumulate_items(const stamen::menu_t& lmenu)

return initlist_elem(items);
}
void generate_include(std::ostream& ost, const arguments_t& args)
void generate_include(std::ostream& ost,
const stamen::Stamen& inst,
const arguments_t& args)
{
using namespace std::string_literals; // NOLINT
using namespace cemplate; // NOLINT

@@ -48,9 +50,8 @@ void generate_include(std::ostream& ost, const arguments_t& args)

ost << nspace(args.nspace);
ost << R"(
class menu_t
struct menu_t
{
public:
using callback_f = std::function<int(std::size_t)>;
static int visit(const menu_t& menu);

@@ -69,13 +70,13 @@ public:

)";
ost << "// generated function\n";
for (const auto& [code, _] : stamen::menu_lookup)
for (const auto& [code, _] : inst.menu_lookup())
{
ost << func_decl(code, "int", {{{"std::size_t"s, "/* unused */"s}}});
}
ost << "\n// free function\n";
for (const auto& [code, _] : stamen::free_lookup)
for (const auto& [code, _] : inst.free_lookup())
{
ost << func_decl(code, "int", {{{"std::size_t"s, "/* unused */"s}}});
}

@@ -84,6 +85,7 @@ public:

}
void generate_source(std::ostream& ost,
const stamen::Stamen& inst,
const arguments_t& args,
const std::string& include_name)
{

@@ -99,7 +101,7 @@ void generate_source(std::ostream& ost,

ost << nspace(args.nspace);
// clang-format off
for (const auto& [code, menu] : stamen::menu_lookup)
for (const auto& [code, menu] : inst.menu_lookup())
{
ost << func(
menu.code(),

@@ -173,16 +175,16 @@ int main(int argc, char* argv[])

return 1;
}
const auto& config = args.config;
stamen::read(config.c_str());
std::ifstream ifs(args.config);
const stamen::Stamen inst(ifs);
const auto include_filename = args.config.stem().replace_extension(".hpp");
std::ofstream include(include_filename);
generate_include(include, args);
generate_include(include, inst, args);
const auto source_filename = args.config.stem().replace_extension(".cpp");
std::ofstream source(source_filename);
generate_source(source, args, include_filename);
generate_source(source, inst, args, include_filename);
return 0;
}

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

@@ -1,35 +1,40 @@

#include <cstddef>
#include <deque>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_set>
#include <utility>
#include "stamen/stamen.hpp"
namespace stamen {
namespace {
void warning(const std::string& message, const std::string& addition)
{
std::cerr << "Stamen: " << message;
if (!addition.empty())
{
std::cerr << ": " + addition;
}
std::cerr << '\n' << std::flush;
}
// NOLINTBEGIN
std::unordered_map<std::string, menu_t> menu_lookup;
std::unordered_map<std::string, callback_f> free_lookup;
std::string display_stub_default;
display_f display;
// NOLINTEND
} // namespace
void read(const char* filename)
namespace stamen {
Stamen::Stamen(std::istream& ist)
{
using namespace std::placeholders; // NOLINT
std::unordered_set<std::string> refd;
std::fstream fst(filename);
std::string line;
std::string delim;
std::string code;
std::string prompt;
auto last = menu_lookup.end();
while (std::getline(fst, line))
auto last = m_menu_lookup.end();
while (std::getline(ist, line))
{
if (line.empty()) continue;

@@ -39,74 +44,75 @@ void read(const char* filename)

if (delim != "+")
{
last->second.insert(code, prompt);
last->second.insert(code,
prompt,
[&](std::size_t idx)
{ return this->display_stub(idx); });
refd.insert(code);
}
else
{
const auto [iter, succ] = menu_lookup.emplace(
std::piecewise_construct,
std::forward_as_tuple(code),
std::forward_as_tuple(menu_t::private_ctor_t {}, code, prompt));
const auto [iter, succ] =
m_menu_lookup.emplace(code, Menu(code, prompt));
last = iter;
}
}
for (const auto& ref : refd)
{
if (!menu_lookup.contains(ref))
if (!m_menu_lookup.contains(ref))
{
free_lookup.emplace(ref, nullptr);
m_free_lookup.emplace(ref, nullptr);
}
}
}
void insert(const char* code, const callback_f& callback)
void Stamen::insert(const std::string& code, const callback_f& callback)
{
auto itr = free_lookup.find(code);
if (itr == free_lookup.end())
auto itr = m_free_lookup.find(code);
if (itr == m_free_lookup.end())
{
std::cout << "Stamen: unknown callback registration...\n" << std::flush;
warning("unknown callback registration", code);
return;
}
itr->second = callback;
}
int dynamic(const char* code, const display_f& disp)
int Stamen::dynamic(const std::string& code, const display_f& disp)
{
display_stub_default = code;
display = disp;
m_stub_default = code;
m_stub_display = disp;
return display_stub(0);
}
int display_stub(std::size_t idx)
int Stamen::display_stub(std::size_t idx)
{
static std::deque<const menu_t*> stack;
const std::string& code = !m_stub_stack.empty()
? m_stub_stack.top()->item(idx).code
: m_stub_default;
const std::string& code =
!stack.empty() ? stack.back()->item(idx).code : display_stub_default;
const auto ml_it = menu_lookup.find(code);
if (ml_it != menu_lookup.end())
const auto mit = m_menu_lookup.find(code);
if (mit != m_menu_lookup.end())
{
stack.push_back(&ml_it->second);
const int ret = display(ml_it->second);
stack.pop_back();
m_stub_stack.push(&mit->second);
const int ret = m_stub_display(mit->second);
m_stub_stack.pop();
return ret;
}
const auto fl_it = free_lookup.find(code);
if (fl_it != free_lookup.end() && fl_it->second != nullptr)
const auto fit = m_free_lookup.find(code);
if (fit != m_free_lookup.end() && fit->second != nullptr)
{
return fl_it->second(0);
return fit->second(0);
}
std::cout << "Stamen: nothing to do...\n" << std::flush;
warning("no callback for", code);
return 1;
}
void menu_t::insert(const std::string& code,
void Menu::insert(const std::string& code,
const std::string& prompt,
const callback_f& callback)
{