Cli arguments parsing support

This commit is contained in:
sha512sum 2024-07-02 13:52:01 +00:00
parent de09e4edce
commit 085f4d8b26
7 changed files with 279 additions and 21 deletions

View file

@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CXX_EXTENSIONS NO)
set(Boost_USE_MULTITHREADED ON)
find_package(fmt REQUIRED)
find_package(Boost 1.84.0 REQUIRED COMPONENTS url)
find_package(Boost 1.84.0 REQUIRED COMPONENTS url program_options)
find_package(utempl REQUIRED)
find_package(llhttp REQUIRED)
find_package(OpenSSL REQUIRED)

49
examples/src/cli.cpp Normal file
View file

@ -0,0 +1,49 @@
#include <cserver/components/cli/manager.hpp>
#include <cserver/components/cli/struct.hpp>
#include <cserver/server/server/server.hpp>
#include <cserver/server/handlers/http_handler_base.hpp>
struct SomeStruct {
static_assert(utempl::OpenStruct<SomeStruct>());
utempl::FieldAttribute<std::string, cserver::cli::Name<"field,f">> field;
static_assert(utempl::CloseStruct<SomeStruct>());
};
struct SomeComponent : cserver::cli::StructWithAdder<SomeComponent, SomeStruct> {
using cserver::cli::StructWithAdder<SomeComponent, SomeStruct>::StructWithAdder;
};
struct SomeOtherComponent : public cserver::server::handlers::HttpHandlerBaseWithAdder<SomeOtherComponent> {
static constexpr utempl::ConstexprString kPath = "/v1/some/";
static constexpr utempl::ConstexprString kName = "name";
static constexpr utempl::ConstexprString kHandlerManagerName = "server";
SomeComponent& some;
inline constexpr SomeOtherComponent(auto& context) :
HttpHandlerBaseWithAdder(context),
some(context.template FindComponent<"component">()) {};
inline auto HandleRequestThrow(const cserver::server::http::HttpRequest&) -> cserver::Task<cserver::server::http::HttpResponse> {
co_return cserver::server::http::HttpResponse{.body = this->some.field};
};
};
auto main(int argc, const char** argv) -> int {
cserver::ServiceContextBuilder{}
.AppendConfigParam<"threads", 8>()
.AppendConfigParam<"logging", cserver::ConstexprConfig{}
.Append<"level">(cserver::LoggingLevel::kTrace)>()
.AppendConfigParam<"cliArgs", utempl::ConstexprString{"cliManager"}>()
.AppendConfigParam<"server", cserver::ConstexprConfig{}
.Append<"taskProcessor">(utempl::ConstexprString{"basicTaskProcessor"})
.Append<"port">(55555)>()
.Append<cserver::Logging>()
.Append<cserver::cli::Manager<>>()
.Append<cserver::server::server::Server<>>()
.Append<SomeComponent, "component">()
.Append<SomeOtherComponent, "other">()
.Sort()
.Run(argc, argv);
};

View file

@ -0,0 +1,96 @@
#pragma once
#include <utempl/optional.hpp>
#include <cserver/engine/not_implemented.hpp>
#include <cserver/engine/components.hpp>
#include <boost/program_options.hpp>
#include <boost/type_index.hpp>
#include <iostream>
namespace cserver::cli {
template <std::size_t N, std::size_t NN, typename T>
struct OptionConfig {
utempl::ConstexprString<N> name;
utempl::ConstexprString<NN> description;
using Type = T;
};
template <typename T, OptionConfig... Configs>
struct StructConfig {
using Type = T;
static constexpr auto kValue = utempl::Tuple{Configs...};
};
template <typename T, std::size_t N, std::size_t NN>
consteval auto CreateOptionConfig(utempl::ConstexprString<N> name,
utempl::ConstexprString<NN> description) -> OptionConfig<N, NN, T> {
return {name, description};
};
template <StructConfig... Configs>
struct Manager {
boost::program_options::variables_map variableMap;
static constexpr utempl::ConstexprString kName = "cliManager";
constexpr Manager(auto& context) {
boost::program_options::options_description general("General options");
general.add_options()
("help,h", "Show help");
([&]{
using Current = decltype(Configs)::Type;
boost::program_options::options_description desc(fmt::format("{} options",
boost::typeindex::type_id<Current>().pretty_name()));
utempl::Unpack(utempl::PackConstexprWrapper<decltype(Configs)::kValue>(), [&](auto... vs) {
auto&& add = desc.add_options();
([&]{
//static_assert((std::ignore = utempl::kWrapper<*vs>, false));
if constexpr((*vs).description.size() == 0) {
add((*vs).name.data.begin(),
boost::program_options::value<typename decltype(*vs)::Type>());
} else {
add((*vs).name.data.begin(),
boost::program_options::value<typename decltype(*vs)::Type>(),
(*vs).description.data.begin());
};
}(), ...);
});
general.add(desc);
}(), ...);
boost::program_options::store(boost::program_options::parse_command_line(context.argc, context.argv, general), this->variableMap);
boost::program_options::notify(this->variableMap);
if(this->variableMap.count("help")) {
std::cout << general << std::endl;
context.template FindComponent<kBasicTaskProcessorName>().ioContext.stop();
return;
};
};
template <StructConfig Config>
constexpr auto Get() {
constexpr utempl::Tuple fieldConfigs = Config.kValue;
return [&](auto... is) -> decltype(Config)::Type::Type {
return {[&]{
static constexpr auto fieldConfig = utempl::Get<is>(fieldConfigs);
static constexpr std::string_view nnname = static_cast<std::string_view>(fieldConfig.name);
constexpr std::string_view nname = nnname.substr(0, nnname.find(','));
utempl::ConstexprString<nname.size() + 1> name;
std::ranges::copy_n(nname.begin(), nname.size(), name.begin());
if(this->variableMap.count(name.begin())) {
return this->variableMap[name.begin()].template as<typename decltype(fieldConfig)::Type>();
};
throw std::runtime_error(fmt::format("Not found cli option {}", name));
}()...};
} | utempl::kSeq<utempl::kTupleSize<decltype(fieldConfigs)>>;
};
template <StructConfig Config>
using AddStructConfig = Manager<Configs..., Config>;
};
} // namespace cserver::cli

View file

@ -0,0 +1,99 @@
#pragma once
#include <cserver/engine/not_implemented.hpp>
#include <cserver/engine/components.hpp>
#include <cserver/components/cli/manager.hpp>
#include <utempl/attributes.hpp>
#include <boost/pfr.hpp>
namespace cserver::cli {
template <utempl::ConstexprString Value>
struct Name {
static constexpr utempl::ConstexprString kValue = Value;
};
template <utempl::ConstexprString Value>
struct Description {
static constexpr utempl::ConstexprString kValue = Value;
};
template <typename T>
struct IsNameM {
static constexpr bool value = utempl::Overloaded(
[]<utempl::ConstexprString V>(utempl::TypeList<Name<V>>) {
return true;
},
[](auto) {return false;}
)(utempl::kType<T>);
};
template <typename T>
struct IsDescriptionM {
static constexpr bool value = utempl::Overloaded(
[]<utempl::ConstexprString V>(utempl::TypeList<Description<V>>) {
return true;
},
[](auto) {return false;}
)(utempl::kType<T>);
};
template <typename Self, typename T>
struct Struct : T {
static constexpr utempl::ConstexprString kCliManagerName = "cliManager";
using Type = T;
template <std::size_t I, template <typename...> typename F, typename Default>
static consteval auto GetFirstOrDefault(Default&& def) {
constexpr auto options = Get<I>(utempl::GetAttributes<T>());
if constexpr(std::is_same_v<decltype(options), const utempl::NoInfo>) {
return std::forward<Default>(def);
} else {
if constexpr(Find<F>(options) == Size(options)) {
return std::forward<Default>(def);
} else {
return decltype(utempl::Get<Find<F>(options)>(options))::kValue;
};
};
};
static consteval auto GetConfig() {
static constexpr auto names = boost::pfr::names_as_array<T>();
return [&](auto... is) {
return StructConfig<Self, [&] {
return CreateOptionConfig<std::remove_reference_t<decltype(boost::pfr::get<is>(std::declval<T&>()))>>(
GetFirstOrDefault<is, IsNameM>(utempl::ConstexprString<names[is].size() + 1>(names[is].data())),
GetFirstOrDefault<is, IsDescriptionM>(utempl::ConstexprString<0>{}));
}()...>{};
} | utempl::kSeq<boost::pfr::tuple_size_v<T>>;
};
constexpr Struct(auto& context) :
T(context.template FindComponent<Self::kCliManagerName>().template Get<GetConfig()>()) {};
template <utempl::ConstexprString Name, Options>
static consteval auto CliStructAdder(const auto& context) {
return context.TransformComponents([]<typename TT, Options Options>(ComponentConfig<Self::kCliManagerName, TT, Options>) {
return ComponentConfig<Self::kCliManagerName, typename TT::template AddStructConfig<GetConfig()>, Options>{};
});
};
};
template <typename Self, typename T>
struct StructWithAdder : Struct<Self, T> {
using Struct<Self, T>::Struct;
template <utempl::ConstexprString Name, Options Options>
static consteval auto Adder(const auto& context) {
return Struct<Self, T>::template CliStructAdder<Name, Options>(context);
};
};
} // namespace cserver::cli

View file

@ -174,10 +174,12 @@ struct ServiceContextForComponentWithCliArgs : ServiceContextForComponent<Compon
template <utempl::Tuple DependencyGraph, typename T>
inline constexpr auto InitComponents(T& ccontext, int argc, const char** argv) -> void {
inline constexpr auto InitComponents(T& ccontext, int ac, const char** av) -> void {
static auto& context = ccontext;
static auto& taskProcessor = context.taskProcessor;
static auto& ioContext = taskProcessor.ioContext;
static const auto argc = ac;
static const auto argv = av;
static constexpr utempl::ConstexprString cliArgs = [] -> decltype(auto) {
if constexpr(!std::is_same_v<decltype(T::kConfig.template Get<"cliArgs">()), void>) {
return T::kConfig.template Get<"cliArgs">();
@ -187,13 +189,14 @@ inline constexpr auto InitComponents(T& ccontext, int argc, const char** argv) -
}();
static utempl::Tuple inited = TransformDependencyGraphToTupleWithDependenciesToInitedFlagChanges<AsyncConditionVariable, DependencyGraph>(ioContext);
auto work = make_work_guard(ioContext);
[argc, argv]<std::size_t... Is>(std::index_sequence<Is...>){
(boost::asio::co_spawn(ioContext, [argc, argv]() -> cserver::Task<> {
[]<std::size_t... Is>(std::index_sequence<Is...>){
(boost::asio::co_spawn(ioContext, []() -> cserver::Task<> {
using Current = decltype(Get<Is>(decltype(T::kUtils)::kComponentConfigs));
auto& dependencies = Get<Is>(inited);
for(auto* flag : dependencies) {
co_await flag->AsyncWait();
};
using Current = decltype(Get<Is>(decltype(T::kUtils)::kComponentConfigs));
try {
if constexpr(Current::kName == cliArgs) {
ServiceContextForComponentWithCliArgs<Current, T> currentContext{context, argc, argv};
Get<Is>(context.storage).emplace(currentContext);
@ -201,6 +204,13 @@ inline constexpr auto InitComponents(T& ccontext, int argc, const char** argv) -
ServiceContextForComponent<Current, T> currentContext{context};
Get<Is>(context.storage).emplace(currentContext);
};
} catch(std::exception& error) {
fmt::println("An error occurred while initializing component {}: {}", static_cast<std::string_view>(Current::kName), error.what());
ioContext.stop();
} catch(...) {
fmt::println("An unknown error occurred while initializing component {}", static_cast<std::string_view>(Current::kName));
ioContext.stop();
};
auto& componentInitFlag = GetInitFlagFor<AsyncConditionVariable, Is>(ioContext);
componentInitFlag.NotifyAll();
if constexpr(requires{Get<Is>(context.storage)->Run();}) {
@ -332,13 +342,13 @@ inline constexpr auto Use() {
template <typename...>
consteval auto Ignore() {};
template <typename Current, ConstexprConfig Config, typename... Ts>
template <typename Current, utempl::ConstexprString Name, ConstexprConfig Config, typename... Ts>
struct DependencyInfoInjector {
int argc{};
const char** argv{};
static constexpr ConstexprConfig kConfig = Config;
static constexpr DependenciesUtils<Ts...> kUtils;
static constexpr utempl::ConstexprString kName = Current::kName;
static constexpr utempl::ConstexprString kName = Name;
template <utempl::ConstexprString name>
static consteval auto FindComponentType() {
if constexpr(name == kBasicTaskProcessorName) {
@ -417,9 +427,8 @@ public:
}() + ... + utempl::Tuple{});
} | utempl::kSeq<utempl::loopholes::Counter<Current>()>;
};
template <utempl::ConstexprString name>
static inline consteval auto Inject() {
Ignore<decltype(Use<Current, DependencyInfoInjector<Current, Config, Ts...>{}>())>();
Ignore<decltype(Use<Current, DependencyInfoInjector<Current, Name, Config, Ts...>{}>())>();
};
};
@ -544,8 +553,8 @@ struct ServiceContextBuilder {
return DependencyGraph<DependencyGraphElement<names,
[]<utempl::ConstexprString name, typename Component, ::cserver::Options OOptions>
(ComponentConfig<name, Component, OOptions>) {
impl::DependencyInfoInjector<Component, config, ComponentConfigs...> injector;
injector.template Inject<name>();
impl::DependencyInfoInjector<Component, name, config, ComponentConfigs...> injector;
injector.Inject();
return injector.GetDependencies();
}(ComponentConfigs{})>...>{};
}(ComponentConfigs{}...);

View file

@ -1,10 +1,14 @@
#include <stdexcept>
#pragma once
#include <stdexcept>
#include <fmt/format.h>
#include <fmt/compile.h>
namespace cserver::engine {
struct NotImplemented : std::runtime_error {
using std::runtime_error::runtime_error;
NotImplemented(std::string context) :
std::runtime_error{fmt::format(FMT_COMPILE("Not Implemented Error [{}]"), context)} {};
};
} // namespace cserver::engine

View file

@ -8,10 +8,11 @@
namespace cserver::server::handlers {
struct HttpHandlerBase : ComponentBase {
template <typename T, utempl::ConstexprString name, Options Options>
template <typename T, utempl::ConstexprString name, Options>
static consteval auto HttpHandlerAdder(const auto& context) {
return context.TransformComponents([]<typename TT>(const ComponentConfig<T::kHandlerManagerName, TT, Options>) {
return context.TransformComponents([]<typename TT, Options Options>(const ComponentConfig<T::kHandlerManagerName, TT, Options>) {
return ComponentConfig<T::kHandlerManagerName, typename TT::template AddHandler<ComponentConfig<name, T, Options>>, Options>{};
});
};