stamen

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

commit e814fd0a34a1f28773e3551a76a341704fcc63e4
parent a5cb5bf5961c3321d890a0f5897689f76d2cc8ec
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Sun, 23 Feb 2025 17:20:01 +0100

Shuffle the code around

Diffstat:
M CMakeLists.txt | ++ --
M example/dynamic.cpp | ++++++++++++++ -------------
M example/static.cpp | +
D include/stamen/menu.hpp | ---------------------------------------------------------------------------------
A include/stamen/stamen.hpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M source/generate.cpp | ++++++ ------
D source/menu.cpp | ---------------------------------------------------------------------------------
A source/stamen.cpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

8 files changed, 229 insertions(+), 225 deletions(-)


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

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


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

@@ -20,7 +20,7 @@ find_package(cemplate 0.1.6 CONFIG REQUIRED)


add_library(
stamen_stamen
source/menu.cpp
source/stamen.cpp
)
target_link_libraries(stamen_stamen PUBLIC poafloc::poafloc)
add_library(stamen::stamen ALIAS stamen_stamen)

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

@@ -2,12 +2,13 @@

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

#include "stamen/menu.hpp"
#include "stamen/stamen.hpp"

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

@@ -58,26 +59,26 @@ int display(const stamen::menu::menu_t& menu)

return 1;
}

int finish(std::size_t /* unused */) // NOLINT
int finish(std::size_t /* unused */)
{
exit(0);
exit(0); // NOLINT
}

int operation1(std::size_t /* unused */) // NOLINT
int operation1(std::size_t /* unused */)
{
std::cout << "1\n";
std::cout << std::flush;
return 1;
}

int operation2(std::size_t /* unused */) // NOLINT
int operation2(std::size_t /* unused */)
{
std::cout << "2\n";
std::cout << std::flush;
return 1;
}

int operation3(std::size_t /* unused */) // NOLINT
int operation3(std::size_t /* unused */)
{
std::cout << "3\n";
std::cout << std::flush;

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

const std::span args(argv, argv + argc);

// read the configuration
for (const auto& arg : args.subspan(1)) stamen::menu::read(arg);
for (const auto& arg : args.subspan(1)) stamen::read(arg);

// register free functions
stamen::menu::insert("finish", finish);
stamen::menu::insert("operation1", operation1);
stamen::menu::insert("operation2", operation2);
stamen::menu::insert("operation3", operation3);
stamen::insert("finish", finish);
stamen::insert("operation1", operation1);
stamen::insert("operation2", operation2);
stamen::insert("operation3", operation3);

// start the menu on specific panel
stamen::menu::dynamic("menu_main", display);
stamen::dynamic("menu_main", display);

return 0;
}

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

@@ -2,6 +2,7 @@

#include <cstddef>
#include <format>
#include <iostream>
#include <limits>

#include "demo_menu.hpp"

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

@@ -1,89 +0,0 @@

#pragma once

#include <cstring>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>

namespace stamen::menu {

class menu_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;
// 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
{
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_t(const menu_t&) = delete;
menu_t& operator=(const menu_t&) = delete;
menu_t(menu_t&&) = delete;
menu_t& operator=(menu_t&&) = delete;

~menu_t() = default;

const std::string& code() const { return m_code; }
const std::string& title() const { return m_title; }

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; }
const auto& get_prompt(std::size_t idx) const { return m_codes[idx].prompt; }

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);

struct code_t
{
std::string code;
std::string prompt;
};

std::string m_code;
std::string m_title;
std::vector<code_t> m_codes;
std::vector<item_t> m_items;
};

} // namespace stamen::menu

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

@@ -0,0 +1,89 @@

#pragma once

#include <cstring>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>

namespace stamen {

class menu_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;
// 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
{
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_t(const menu_t&) = delete;
menu_t& operator=(const menu_t&) = delete;
menu_t(menu_t&&) = delete;
menu_t& operator=(menu_t&&) = delete;

~menu_t() = default;

const std::string& code() const { return m_code; }
const std::string& title() const { return m_title; }

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; }
const auto& get_prompt(std::size_t idx) const { return m_codes[idx].prompt; }

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);

struct code_t
{
std::string code;
std::string prompt;
};

std::string m_code;
std::string m_title;
std::vector<code_t> m_codes;
std::vector<item_t> m_items;
};

} // namespace stamen

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

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

#include <cemplate/cemplate.hpp>
#include <poafloc/poafloc.hpp>

#include "stamen/menu.hpp"
#include "stamen/stamen.hpp"

struct arguments_t
{

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


namespace {

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

@@ -67,13 +67,13 @@ struct menu_t

)";

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

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

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

ost << nspace(args.nspace);

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

@@ -171,7 +171,7 @@ int main(int argc, char* argv[])

}

const auto& config = args.config;
stamen::menu::read(config.c_str());
stamen::read(config.c_str());

const auto include_filename = args.config.stem().replace_extension(".hpp");
std::ofstream include(include_filename);

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

@@ -1,115 +0,0 @@

#include <deque>
#include <fstream>
#include <iostream>
#include <sstream>
#include <tuple>
#include <unordered_set>
#include <utility>

#include "stamen/menu.hpp"

namespace stamen::menu {

// 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

void read(const char* filename)
{
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))
{
if (line.empty()) continue;

std::istringstream iss(line);
iss >> delim >> code >> std::ws;
std::getline(iss, prompt);

if (delim != "+")
{
last->second.insert(code, prompt);
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));
last = iter;
}
}

for (const auto& ref : refd)
{
if (!menu_lookup.contains(ref))
{
free_lookup.emplace(ref, nullptr);
}
}
}

void insert(const char* code, const callback_f& callback)
{
auto itr = free_lookup.find(code);
if (itr == free_lookup.end())
{
std::cout << "Stamen: unknown callback registration...\n" << std::flush;
return;
}
free_lookup.emplace(code, callback);
}

int dynamic(const char* code, const display_f& disp)
{
menu::display_stub_default = code;
menu::display = disp;
return display_stub(0);
}

int display_stub(std::size_t idx)
{
static std::deque<const menu_t*> stack;

const std::string& code =
!stack.empty() ? stack.back()->get_code(idx) : display_stub_default;

const auto ml_it = menu_lookup.find(code);
if (ml_it != menu_lookup.end())
{
stack.push_back(&ml_it->second);
const int ret = display(ml_it->second);
stack.pop_back();

return ret;
}

const auto fl_it = free_lookup.find(code);
if (fl_it != free_lookup.end()) return fl_it->second(0);

std::cout << "Stamen: nothing to do...\n" << std::flush;
return 1;
}

void menu_t::insert(const std::string& code,
const std::string& prompt,
const callback_f& callback)
{
char* buffer = new char[prompt.size() + 1]; // NOLINT
strcpy(buffer, prompt.c_str()); // NOLINT

m_items.emplace_back(callback, buffer);
m_codes.emplace_back(code, prompt);
}

} // namespace stamen::menu

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

@@ -0,0 +1,117 @@

#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 {

// 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

void read(const char* filename)
{
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))
{
if (line.empty()) continue;

std::istringstream iss(line);
iss >> delim >> code >> std::ws;
std::getline(iss, prompt);

if (delim != "+")
{
last->second.insert(code, prompt);
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));
last = iter;
}
}

for (const auto& ref : refd)
{
if (!menu_lookup.contains(ref))
{
free_lookup.emplace(ref, nullptr);
}
}
}

void insert(const char* code, const callback_f& callback)
{
auto itr = free_lookup.find(code);
if (itr == free_lookup.end())
{
std::cout << "Stamen: unknown callback registration...\n" << std::flush;
return;
}
free_lookup.emplace(code, callback);
}

int dynamic(const char* code, const display_f& disp)
{
display_stub_default = code;
display = disp;
return display_stub(0);
}

int display_stub(std::size_t idx)
{
static std::deque<const menu_t*> stack;

const std::string& code =
!stack.empty() ? stack.back()->get_code(idx) : display_stub_default;

const auto ml_it = menu_lookup.find(code);
if (ml_it != menu_lookup.end())
{
stack.push_back(&ml_it->second);
const int ret = display(ml_it->second);
stack.pop_back();

return ret;
}

const auto fl_it = free_lookup.find(code);
if (fl_it != free_lookup.end()) return fl_it->second(0);

std::cout << "Stamen: nothing to do...\n" << std::flush;
return 1;
}

void menu_t::insert(const std::string& code,
const std::string& prompt,
const callback_f& callback)
{
char* buffer = new char[prompt.size() + 1]; // NOLINT
strcpy(buffer, prompt.c_str()); // NOLINT

m_items.emplace_back(callback, buffer);
m_codes.emplace_back(code, prompt);
}

} // namespace stamen