From 085f4d8b266899adcc345952e353ff6ea036182c Mon Sep 17 00:00:00 2001 From: sha512sum Date: Tue, 2 Jul 2024 13:52:01 +0000 Subject: [PATCH] Cli arguments parsing support --- CMakeLists.txt | 2 +- examples/src/cli.cpp | 49 +++++++++ include/cserver/components/cli/manager.hpp | 96 ++++++++++++++++++ include/cserver/components/cli/struct.hpp | 99 +++++++++++++++++++ include/cserver/engine/components.hpp | 41 +++++--- include/cserver/engine/not_implemented.hpp | 8 +- .../server/handlers/http_handler_base.hpp | 5 +- 7 files changed, 279 insertions(+), 21 deletions(-) create mode 100644 examples/src/cli.cpp create mode 100644 include/cserver/components/cli/manager.hpp create mode 100644 include/cserver/components/cli/struct.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e35c8c3..214decf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/examples/src/cli.cpp b/examples/src/cli.cpp new file mode 100644 index 0000000..9a5b8cc --- /dev/null +++ b/examples/src/cli.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include + + +struct SomeStruct { + static_assert(utempl::OpenStruct()); + utempl::FieldAttribute> field; + static_assert(utempl::CloseStruct()); +}; + +struct SomeComponent : cserver::cli::StructWithAdder { + using cserver::cli::StructWithAdder::StructWithAdder; +}; + +struct SomeOtherComponent : public cserver::server::handlers::HttpHandlerBaseWithAdder { + 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 { + 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() + .Append>() + .Append>() + .Append() + .Append() + .Sort() + .Run(argc, argv); +}; diff --git a/include/cserver/components/cli/manager.hpp b/include/cserver/components/cli/manager.hpp new file mode 100644 index 0000000..f9df5f5 --- /dev/null +++ b/include/cserver/components/cli/manager.hpp @@ -0,0 +1,96 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace cserver::cli { + +template +struct OptionConfig { + utempl::ConstexprString name; + + utempl::ConstexprString description; + using Type = T; +}; + +template +struct StructConfig { + using Type = T; + static constexpr auto kValue = utempl::Tuple{Configs...}; +}; + + + + +template +consteval auto CreateOptionConfig(utempl::ConstexprString name, + utempl::ConstexprString description) -> OptionConfig { + return {name, description}; +}; + + + +template +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().pretty_name())); + utempl::Unpack(utempl::PackConstexprWrapper(), [&](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()); + } else { + add((*vs).name.data.begin(), + boost::program_options::value(), + (*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().ioContext.stop(); + return; + }; + }; + + template + constexpr auto Get() { + constexpr utempl::Tuple fieldConfigs = Config.kValue; + return [&](auto... is) -> decltype(Config)::Type::Type { + return {[&]{ + static constexpr auto fieldConfig = utempl::Get(fieldConfigs); + static constexpr std::string_view nnname = static_cast(fieldConfig.name); + constexpr std::string_view nname = nnname.substr(0, nnname.find(',')); + utempl::ConstexprString name; + std::ranges::copy_n(nname.begin(), nname.size(), name.begin()); + if(this->variableMap.count(name.begin())) { + return this->variableMap[name.begin()].template as(); + }; + throw std::runtime_error(fmt::format("Not found cli option {}", name)); + }()...}; + } | utempl::kSeq>; + }; + + + template + using AddStructConfig = Manager; +}; + +} // namespace cserver::cli diff --git a/include/cserver/components/cli/struct.hpp b/include/cserver/components/cli/struct.hpp new file mode 100644 index 0000000..e1b5212 --- /dev/null +++ b/include/cserver/components/cli/struct.hpp @@ -0,0 +1,99 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace cserver::cli { + +template +struct Name { + static constexpr utempl::ConstexprString kValue = Value; +}; + +template +struct Description { + static constexpr utempl::ConstexprString kValue = Value; +}; + + +template +struct IsNameM { + static constexpr bool value = utempl::Overloaded( + [](utempl::TypeList>) { + return true; + }, + [](auto) {return false;} + )(utempl::kType); +}; + +template +struct IsDescriptionM { + static constexpr bool value = utempl::Overloaded( + [](utempl::TypeList>) { + return true; + }, + [](auto) {return false;} + )(utempl::kType); +}; + + + + + + +template +struct Struct : T { + static constexpr utempl::ConstexprString kCliManagerName = "cliManager"; + using Type = T; + template typename F, typename Default> + static consteval auto GetFirstOrDefault(Default&& def) { + constexpr auto options = Get(utempl::GetAttributes()); + if constexpr(std::is_same_v) { + return std::forward(def); + } else { + if constexpr(Find(options) == Size(options)) { + return std::forward(def); + } else { + return decltype(utempl::Get(options)>(options))::kValue; + }; + }; + }; + + + static consteval auto GetConfig() { + static constexpr auto names = boost::pfr::names_as_array(); + return [&](auto... is) { + return StructConfig(std::declval()))>>( + GetFirstOrDefault(utempl::ConstexprString(names[is].data())), + GetFirstOrDefault(utempl::ConstexprString<0>{})); + }()...>{}; + } | utempl::kSeq>; + }; + + + constexpr Struct(auto& context) : + T(context.template FindComponent().template Get()) {}; + + + + template + static consteval auto CliStructAdder(const auto& context) { + return context.TransformComponents([](ComponentConfig) { + return ComponentConfig, Options>{}; + }); + }; +}; + +template +struct StructWithAdder : Struct { + using Struct::Struct; + template + static consteval auto Adder(const auto& context) { + return Struct::template CliStructAdder(context); + }; +}; + +} // namespace cserver::cli diff --git a/include/cserver/engine/components.hpp b/include/cserver/engine/components.hpp index 18e01b6..71710d7 100644 --- a/include/cserver/engine/components.hpp +++ b/include/cserver/engine/components.hpp @@ -174,10 +174,12 @@ struct ServiceContextForComponentWithCliArgs : ServiceContextForComponent -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()), void>) { return T::kConfig.template Get<"cliArgs">(); @@ -187,19 +189,27 @@ inline constexpr auto InitComponents(T& ccontext, int argc, const char** argv) - }(); static utempl::Tuple inited = TransformDependencyGraphToTupleWithDependenciesToInitedFlagChanges(ioContext); auto work = make_work_guard(ioContext); - [argc, argv](std::index_sequence){ - (boost::asio::co_spawn(ioContext, [argc, argv]() -> cserver::Task<> { + [](std::index_sequence){ + (boost::asio::co_spawn(ioContext, []() -> cserver::Task<> { + using Current = decltype(Get(decltype(T::kUtils)::kComponentConfigs)); auto& dependencies = Get(inited); for(auto* flag : dependencies) { co_await flag->AsyncWait(); }; - using Current = decltype(Get(decltype(T::kUtils)::kComponentConfigs)); - if constexpr(Current::kName == cliArgs) { - ServiceContextForComponentWithCliArgs currentContext{context, argc, argv}; - Get(context.storage).emplace(currentContext); - } else { - ServiceContextForComponent currentContext{context}; - Get(context.storage).emplace(currentContext); + try { + if constexpr(Current::kName == cliArgs) { + ServiceContextForComponentWithCliArgs currentContext{context, argc, argv}; + Get(context.storage).emplace(currentContext); + } else { + ServiceContextForComponent currentContext{context}; + Get(context.storage).emplace(currentContext); + }; + } catch(std::exception& error) { + fmt::println("An error occurred while initializing component {}: {}", static_cast(Current::kName), error.what()); + ioContext.stop(); + } catch(...) { + fmt::println("An unknown error occurred while initializing component {}", static_cast(Current::kName)); + ioContext.stop(); }; auto& componentInitFlag = GetInitFlagFor(ioContext); componentInitFlag.NotifyAll(); @@ -332,13 +342,13 @@ inline constexpr auto Use() { template consteval auto Ignore() {}; -template +template struct DependencyInfoInjector { int argc{}; const char** argv{}; static constexpr ConstexprConfig kConfig = Config; static constexpr DependenciesUtils kUtils; - static constexpr utempl::ConstexprString kName = Current::kName; + static constexpr utempl::ConstexprString kName = Name; template static consteval auto FindComponentType() { if constexpr(name == kBasicTaskProcessorName) { @@ -417,9 +427,8 @@ public: }() + ... + utempl::Tuple{}); } | utempl::kSeq()>; }; - template static inline consteval auto Inject() { - Ignore{}>())>(); + Ignore{}>())>(); }; }; @@ -544,8 +553,8 @@ struct ServiceContextBuilder { return DependencyGraph (ComponentConfig) { - impl::DependencyInfoInjector injector; - injector.template Inject(); + impl::DependencyInfoInjector injector; + injector.Inject(); return injector.GetDependencies(); }(ComponentConfigs{})>...>{}; }(ComponentConfigs{}...); diff --git a/include/cserver/engine/not_implemented.hpp b/include/cserver/engine/not_implemented.hpp index d10200e..f54eafb 100644 --- a/include/cserver/engine/not_implemented.hpp +++ b/include/cserver/engine/not_implemented.hpp @@ -1,10 +1,14 @@ -#include +#pragma once +#include +#include +#include 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 diff --git a/include/cserver/server/handlers/http_handler_base.hpp b/include/cserver/server/handlers/http_handler_base.hpp index cd08a8b..74c08f0 100644 --- a/include/cserver/server/handlers/http_handler_base.hpp +++ b/include/cserver/server/handlers/http_handler_base.hpp @@ -8,10 +8,11 @@ namespace cserver::server::handlers { + struct HttpHandlerBase : ComponentBase { - template + template static consteval auto HttpHandlerAdder(const auto& context) { - return context.TransformComponents([](const ComponentConfig) { + return context.TransformComponents([](const ComponentConfig) { return ComponentConfig>, Options>{}; }); };