stamen

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

commit 903df3a3cfc31924e986d2ae0ac86a81d3178c54
parent 92644eb864725cd28360172c93315e626f7b05e7
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Tue, 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 | + -
M CMakeLists.txt | + -
M example/dynamic.cpp | ++++++++++++++++ -------
M include/stamen/stamen.hpp | ++++++++++++++++++++++++++++++++++++++++ ----------------------------------------
M source/generate.cpp | +++++++++++++ -----------
M source/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)
{