First
This commit is contained in:
parent
52e5a1fe61
commit
8fdeb354a3
3 changed files with 361 additions and 0 deletions
8
CMakeLists.txt
Normal file
8
CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
cmake_minimum_required(VERSION 3.27)
|
||||
project(utempl)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
find_package(fmt REQUIRED)
|
||||
set(CMAKE_CXX_STANDART 23)
|
||||
add_library(utempl INTERFACE)
|
||||
target_link_libraries(utempl INTERFACE fmt::fmt-header-only)
|
||||
target_include_directories(utempl INTERFACE include)
|
9
README.md
Normal file
9
README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# uTempl - Modern C++ Template Library
|
||||
|
||||
## Features
|
||||
- ConstexprString: A template for working with compile-time strings using constexpr.
|
||||
- Tuple Realization: Implementation of tuples in a modern C++ way.
|
||||
- 🔥Blazing🔥 Fast Menu Builder🚀🚀🚀: Quickly build interactive menus in your terminal applications.
|
||||
|
||||
## License
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
344
include/utempl/menu.hpp
Normal file
344
include/utempl/menu.hpp
Normal file
|
@ -0,0 +1,344 @@
|
|||
#include <iostream>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <concepts>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/compile.h>
|
||||
|
||||
namespace utempl {
|
||||
|
||||
namespace utils {
|
||||
|
||||
template <std::size_t Size>
|
||||
struct ConstexprString {
|
||||
std::array<char, Size> data;
|
||||
static constexpr auto kSize = Size == 0 ? 0 : Size - 1;
|
||||
inline constexpr ConstexprString() = default;
|
||||
inline constexpr ConstexprString(const char (&data)[Size]) : data{} {
|
||||
std::ranges::copy_n(data, Size, this->data.begin());
|
||||
|
||||
};
|
||||
inline constexpr ConstexprString(std::string data) : data{} {
|
||||
std::ranges::copy_n(data.begin(), Size, this->data.begin());
|
||||
};
|
||||
inline constexpr ConstexprString(std::array<char, Size> data) : data(std::move(data)) {};
|
||||
inline constexpr auto size() const {
|
||||
return Size == 0 ? 0 : Size - 1;
|
||||
};
|
||||
inline constexpr operator std::string_view() const {
|
||||
return {this->data.begin()};
|
||||
};
|
||||
inline constexpr operator std::string() const {
|
||||
return static_cast<std::string>(static_cast<std::string_view>(*this));
|
||||
};
|
||||
inline constexpr bool operator==(std::string_view other) const {
|
||||
return static_cast<std::string_view>(*this) == other;
|
||||
};
|
||||
template <std::size_t SSize>
|
||||
inline constexpr auto operator+(const ConstexprString<SSize>& other) -> ConstexprString<Size + SSize - 1> {
|
||||
ConstexprString<Size + SSize - 1> response;
|
||||
std::copy_n(this->data.begin(), Size - 1, response.data.begin());
|
||||
std::copy_n(other.data.begin(), SSize, response.data.begin() + Size - 1);
|
||||
return response;
|
||||
};
|
||||
inline constexpr ConstexprString(const ConstexprString&) = default;
|
||||
inline constexpr ConstexprString(ConstexprString&&) = default;
|
||||
};
|
||||
|
||||
template <std::size_t Count>
|
||||
consteval auto createStringWith(char c) {
|
||||
ConstexprString<Count + 1> str = {};
|
||||
for(std::size_t i = 0; i < Count; i++) {
|
||||
str.data[i] = c;
|
||||
};
|
||||
str.data[Count] = '\0';
|
||||
return str;
|
||||
};
|
||||
|
||||
|
||||
template <std::size_t Size>
|
||||
ConstexprString(const char (&data)[Size]) -> ConstexprString<Size>;
|
||||
|
||||
|
||||
template <auto>
|
||||
struct Wrapper {};
|
||||
|
||||
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 utils::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;
|
||||
};
|
||||
|
||||
struct Caster {
|
||||
constexpr Caster(auto&&) {};
|
||||
};
|
||||
|
||||
|
||||
template <typename... Ts>
|
||||
struct TypeList {
|
||||
};
|
||||
template <typename... Ts, typename... TTs>
|
||||
consteval auto operator==(const TypeList<Ts...>& first, const TypeList<TTs...>& second) -> bool {
|
||||
return std::is_same_v<decltype(first), decltype(second)>;
|
||||
};
|
||||
template <std::size_t... Is, typename T>
|
||||
consteval auto get(std::index_sequence<Is...>, decltype(Caster(Is))..., T, ...) -> T;
|
||||
|
||||
|
||||
template <std::size_t I, typename... Ts>
|
||||
consteval auto get(const TypeList<Ts...>&) -> decltype(get(std::make_index_sequence<I>(), std::declval<Ts>()...)) requires (I < sizeof...(Ts));
|
||||
|
||||
|
||||
|
||||
|
||||
template <auto, typename T>
|
||||
struct TupleLeaf {
|
||||
T value;
|
||||
template <typename TT>
|
||||
inline constexpr TupleLeaf(TT&& arg) : value(std::forward<TT>(arg)) {};
|
||||
inline constexpr TupleLeaf(const TupleLeaf&) = default;
|
||||
inline constexpr TupleLeaf(TupleLeaf&&) = default;
|
||||
inline constexpr bool operator==(const TupleLeaf&) const = default;
|
||||
};
|
||||
|
||||
template <std::size_t I, typename... Ts>
|
||||
struct TupleHelper {
|
||||
consteval TupleHelper() = default;
|
||||
inline constexpr TupleHelper(const TupleHelper&) = default;
|
||||
inline constexpr TupleHelper(TupleHelper&&) = default;
|
||||
inline constexpr bool operator==(const TupleHelper&) const = default;
|
||||
};
|
||||
|
||||
template <std::size_t I, typename T, typename... Ts>
|
||||
struct TupleHelper<I, T, Ts...> : public TupleLeaf<I, T> , public TupleHelper<I + 1, Ts...> {
|
||||
template <typename TT, typename... TTs>
|
||||
inline constexpr TupleHelper(TT&& arg, TTs&&... args) :
|
||||
TupleLeaf<I, T>{std::forward<TT>(arg)},
|
||||
TupleHelper<I + 1, Ts...>{std::forward<TTs>(args)...} {};
|
||||
inline constexpr TupleHelper(const TupleHelper&) = default;
|
||||
inline constexpr TupleHelper(TupleHelper&&) = default;
|
||||
inline constexpr bool operator==(const TupleHelper&) const = default;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct Tuple;
|
||||
|
||||
template <std::size_t I, typename... Ts>
|
||||
inline constexpr auto get(Tuple<Ts...>& tuple) -> auto& requires (I < sizeof...(Ts)) {
|
||||
using Type = decltype(get<I>(TypeList<Ts...>{}));
|
||||
return static_cast<TupleLeaf<I, Type>&>(tuple).value;
|
||||
};
|
||||
|
||||
template <std::size_t I, typename... Ts>
|
||||
inline constexpr auto get(const Tuple<Ts...>& tuple) -> const auto& requires (I < sizeof...(Ts)) {
|
||||
using Type = decltype(get<I>(TypeList<Ts...>{}));
|
||||
return static_cast<const TupleLeaf<I, Type>&>(tuple).value;
|
||||
};
|
||||
|
||||
template <std::size_t I, typename... Ts>
|
||||
inline constexpr auto get(Tuple<Ts...>&& tuple) -> auto&& requires (I < sizeof...(Ts)) {
|
||||
using Type = decltype(get<I>(TypeList<Ts...>{}));
|
||||
return std::move(static_cast<TupleLeaf<I, Type>&&>(tuple).value);
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct Tuple : public TupleHelper<0, Ts...> {
|
||||
template <typename... TTs>
|
||||
inline constexpr Tuple(TTs&&... args) :
|
||||
TupleHelper<0, Ts...>(std::forward<TTs>(args)...) {};
|
||||
inline constexpr Tuple(const Tuple&) = default;
|
||||
inline constexpr Tuple(Tuple&&) = default;
|
||||
inline constexpr bool operator==(const Tuple&) const = default;
|
||||
template <typename... TTs>
|
||||
inline constexpr auto operator+(const Tuple<TTs...>& other) const -> Tuple<Ts..., TTs...> {
|
||||
return [&]<auto... Is, auto... IIs>(std::index_sequence<Is...>, std::index_sequence<IIs...>) -> Tuple<Ts..., TTs...> {
|
||||
return {get<Is>(*this)..., get<IIs>(other)...};
|
||||
}(std::make_index_sequence<sizeof...(Ts)>(), std::make_index_sequence<sizeof...(TTs)>());
|
||||
};
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
Tuple(Ts&&...) -> Tuple<std::remove_cvref_t<Ts>...>;
|
||||
|
||||
|
||||
template <typename... Ts>
|
||||
consteval auto listFromTuple(const utils::Tuple<Ts...>&) -> utils::TypeList<Ts...> {
|
||||
return {};
|
||||
};
|
||||
template <typename>
|
||||
struct TupleSize {};
|
||||
|
||||
template <typename... Ts>
|
||||
struct TupleSize<Tuple<Ts...>> {
|
||||
static constexpr auto value = sizeof...(Ts);
|
||||
};
|
||||
template <typename Tuple>
|
||||
inline constexpr auto kTupleSize = TupleSize<Tuple>::value;
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
struct Optional {
|
||||
bool flag = false;
|
||||
union {
|
||||
char null;
|
||||
T _value;
|
||||
};
|
||||
inline constexpr Optional() = default;
|
||||
inline constexpr Optional(const Optional&) = default;
|
||||
inline constexpr Optional(Optional&&) = default;
|
||||
inline constexpr Optional(T&& arg) : _value(std::move(arg)), flag(true) {};
|
||||
inline constexpr Optional(const T& arg) : _value(arg), flag(true) {};
|
||||
inline constexpr Optional(std::nullopt_t) : flag(false), null(0) {};
|
||||
inline constexpr auto has_value() const -> bool {
|
||||
return this->flag;
|
||||
};
|
||||
inline constexpr auto value() -> T& {
|
||||
return this->_value;
|
||||
};
|
||||
inline constexpr auto operator*() -> T& {
|
||||
return this->value();
|
||||
};
|
||||
inline constexpr auto operator->() -> T* {
|
||||
return &this->value();
|
||||
};
|
||||
inline constexpr auto value() const -> const T& {
|
||||
return this->_value;
|
||||
};
|
||||
inline constexpr auto operator*() const -> const T& {
|
||||
return this->value();
|
||||
};
|
||||
inline constexpr auto operator->() const -> const T* {
|
||||
return &this->value();
|
||||
};
|
||||
inline constexpr explicit operator bool() const {
|
||||
return this->has_value();
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace utils
|
||||
|
||||
namespace menu {
|
||||
|
||||
namespace impl {
|
||||
|
||||
template <std::size_t N1, std::size_t N2>
|
||||
struct CallbackMessage {
|
||||
utils::ConstexprString<N1> message;
|
||||
utils::Optional<utils::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 <utils::Tuple storage = utils::Tuple{}, typename... Fs>
|
||||
struct Menu {
|
||||
utils::Tuple<Fs...> functionStorage;
|
||||
static constexpr auto kMessages = storage;
|
||||
static consteval auto getMaxSize() -> std::size_t {
|
||||
return [&]<auto... Is>(std::index_sequence<Is...>){
|
||||
constexpr auto list = utils::listFromTuple(storage);
|
||||
return utils::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 : utils::countDigits(Is))...});
|
||||
}(std::make_index_sequence<sizeof...(Fs)>());
|
||||
};
|
||||
template <impl::CallbackMessage message, std::invocable F>
|
||||
constexpr auto With(F&& f) const {
|
||||
return Menu<storage + utils::Tuple{message}, Fs..., std::remove_cvref_t<F>>{.functionStorage = this->functionStorage + utils::Tuple(std::forward<F>(f))};
|
||||
};
|
||||
};
|
||||
|
||||
template <utils::ConstexprString fmt, utils::ConstexprString enter = "|> ", typename Menu>
|
||||
inline auto Run(Menu&& menu) {
|
||||
using Cleared = std::remove_cvref_t<Menu>;
|
||||
constexpr auto maxSize = Cleared::getMaxSize();
|
||||
constexpr auto messagesCount = utils::kTupleSize<std::remove_cv_t<decltype(Cleared::kMessages)>>;
|
||||
[&]<auto... Is, auto messages = Cleared::kMessages>(std::index_sequence<Is...>){
|
||||
constexpr auto message = ([&]<auto I>(utils::Wrapper<I>){
|
||||
static constexpr auto message = get<I>(messages);
|
||||
constexpr std::size_t s = maxSize - (message.need ? message.need->size() : utils::countDigits(I));
|
||||
static constexpr auto str = utils::createStringWith<s>(' ');
|
||||
constexpr std::string_view str1{[&] {
|
||||
static constexpr auto st = utils::toString<I>();
|
||||
return message.need ? std::string_view(*message.need) : std::string_view(st);
|
||||
}()}
|
||||
,str2{message.message}
|
||||
,str3{str};
|
||||
// + 1 - NULL Terminator
|
||||
constexpr auto size = fmt::formatted_size(FMT_COMPILE(fmt.data.begin())
|
||||
,str1
|
||||
,str2
|
||||
,str3) + 1;
|
||||
char data[size] = {};
|
||||
fmt::format_to(data, FMT_COMPILE(fmt.data.begin())
|
||||
,str1
|
||||
,str2
|
||||
,str3);
|
||||
return utils::ConstexprString<size>(data);
|
||||
}(utils::Wrapper<Is>{}) + ...) + enter;
|
||||
|
||||
std::fwrite(message.data.data(), 1, message.size(), stdout);
|
||||
std::fflush(stdout);
|
||||
std::string input;
|
||||
std::getline(std::cin, input);
|
||||
([&]<auto I, impl::CallbackMessage message = get<I>(messages)>(utils::Wrapper<I>) {
|
||||
if constexpr(message.need) {
|
||||
if(input == std::string_view(*message.need)) {
|
||||
get<I>(menu.functionStorage)();
|
||||
};
|
||||
} else {
|
||||
if(input == std::string_view(utils::toString<I>())) {
|
||||
get<I>(menu.functionStorage)();
|
||||
};
|
||||
};
|
||||
}(utils::Wrapper<Is>{}), ...);
|
||||
}(std::make_index_sequence<messagesCount>());
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace menu
|
||||
} // namespace utempl
|
||||
|
||||
|
Loading…
Reference in a new issue