Cli arguments parsing support
This commit is contained in:
parent
de09e4edce
commit
085f4d8b26
7 changed files with 279 additions and 21 deletions
|
@ -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
49
examples/src/cli.cpp
Normal 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);
|
||||
};
|
96
include/cserver/components/cli/manager.hpp
Normal file
96
include/cserver/components/cli/manager.hpp
Normal 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
|
99
include/cserver/components/cli/struct.hpp
Normal file
99
include/cserver/components/cli/struct.hpp
Normal 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
|
|
@ -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{}...);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>{};
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue