stamenStatic 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