utempl/include/utempl/menu.hpp
2024-02-29 00:25:48 +00:00

149 lines
4.6 KiB
C++

#pragma once
#include <utempl/optional.hpp>
#include <utempl/constexpr_string.hpp>
#include <utempl/tuple.hpp>
#include <utempl/utils.hpp>
#include <iostream>
#include <array>
#include <fmt/format.h>
#include <fmt/compile.h>
namespace utempl {
constexpr std::size_t CountDigits(std::size_t num) {
std::size_t count = 0;
do {
++count;
num /= 10;
} while (num != 0);
return count;
};
constexpr std::size_t GetDigit(std::size_t num, std::size_t index) {
for (std::size_t i = 0; i < index; ++i) {
num /= 10;
}
return num % 10;
};
template <std::size_t num>
consteval auto ToString() {
constexpr std::size_t digits = CountDigits(num);
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
return ConstexprString{std::array{static_cast<char>('0' + GetDigit(num, digits - 1 - Is))..., '\0'}};
}(std::make_index_sequence<digits>());
};
template <typename Range>
constexpr auto GetMax(Range&& range) {
std::remove_cvref_t<decltype(range[0])> response = 0;
for(const auto& element : range) {
response = element > response ? element : response;
};
return response;
};
namespace menu {
namespace impl {
template <std::size_t N1, std::size_t N2>
struct CallbackMessage {
ConstexprString<N1> message;
Optional<ConstexprString<N2>> need;
consteval CallbackMessage(const char (&need)[N2], const char (&message)[N1]) :
message(std::move(message))
,need(std::move(need)) {};
consteval CallbackMessage(const char (&message)[N1]) :
message(std::move(message))
,need(std::nullopt) {};
};
template <std::size_t N1, std::size_t N2>
CallbackMessage(const char(&)[N1], const char(&)[N2]) -> CallbackMessage<N2, N1>;
template <std::size_t N1>
CallbackMessage(const char(&)[N1]) -> CallbackMessage<N1, 0>;
} // namespace impl
template <Tuple storage = Tuple{}, typename... Fs>
struct Menu {
private:
template <ConstexprString fmt, ConstexprString message, ConstexprString alignment, ConstexprString neededInput>
static consteval auto FormatMessage() {
// + 1 - NULL Terminator
constexpr auto size = fmt::formatted_size(FMT_COMPILE(fmt.data.begin())
,neededInput
,message
,alignment) + 1;
char data[size]{};
fmt::format_to(data, FMT_COMPILE(fmt.data.begin())
,neededInput
,message
,alignment);
return ConstexprString<size>(data);
};
template <ConstexprString fmt, std::size_t I>
static consteval auto FormatMessageFor() {
constexpr ConstexprString message = Get<I>(storage).message;
constexpr ConstexprString neededInput = [&] {
if constexpr(Get<I>(storage).need) {
return *Get<I>(storage).need;
} else {
return ToString<I>();
};
}();
constexpr ConstexprString alignment = CreateStringWith<GetMaxSize() - (Get<I>(storage).need ? Get<I>(storage).need->size() : CountDigits(I))>(' ');
return FormatMessage<fmt, message, alignment, neededInput>();
};
public:
Tuple<Fs...> functionStorage;
static consteval auto GetMaxSize() -> std::size_t {
return [&]<auto... Is>(std::index_sequence<Is...>){
constexpr auto list = ListFromTuple(storage);
return GetMax(std::array{(std::remove_cvref_t<decltype(*std::declval<decltype(Get<Is>(list))>().need)>::kSize != 0 ? std::remove_cvref_t<decltype(*std::declval<decltype(Get<Is>(list))>().need)>::kSize : CountDigits(Is))...});
}(std::index_sequence_for<Fs...>());
};
template <impl::CallbackMessage message, std::invocable F>
constexpr auto With(F&& f) const {
return Menu<storage + Tuple{message}, Fs..., std::remove_cvref_t<F>>{.functionStorage = this->functionStorage + Tuple(std::forward<F>(f))};
};
template <ConstexprString fmt, ConstexprString enter = "|> ">
inline constexpr auto Run(std::istream& in = std::cin, std::FILE* out = stdout) const -> std::size_t {
return [&]<auto... Is>(std::index_sequence<Is...>) -> std::size_t {
constexpr auto message = ((FormatMessageFor<fmt, Is>() + ...) + enter);
auto result = std::fwrite(message.begin(), 1, message.size(), out);
if(result < message.size()) {
return EOF;
};
if(std::fflush(out) != 0) {
return EOF;
};
std::string input;
std::getline(in, input);
([&]<auto I, impl::CallbackMessage message = Get<I>(storage)>(Wrapper<I>) {
if constexpr(message.need) {
if(*message.need == input) {
Get<I>(this->functionStorage)();
};
} else {
if(ToString<I>() == input) {
Get<I>(this->functionStorage)();
};
};
}(Wrapper<Is>{}), ...);
return 0;
}(std::index_sequence_for<Fs...>());
};
};
} // namespace menu
} // namespace utempl