// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include #include #include #include #include #include #include #include namespace zen { struct ZenCliOptions { bool IsDebug = false; bool IsVerbose = false; bool EnableExecutionHistory = true; ZenLoggingConfig LoggingConfig; std::string HttpClientBackend; // Choice of HTTP client implementation Oid ParentSessionId = Oid::Zero; // Arguments after " -- " on command line are passed through and not parsed std::string PassthroughCommandLine; std::string PassthroughArgs; std::vector PassthroughArgV; }; struct ZenCmdCategory { std::string Name; std::map SortedCmds; }; extern ZenCmdCategory g_UtilitiesCategory; extern ZenCmdCategory g_ProjectStoreCategory; extern ZenCmdCategory g_CacheStoreCategory; extern ZenCmdCategory g_StorageCategory; // RAII wrapper around `signal(2)` that restores the previous handler on scope // exit. Use in command Run() methods so an exception during option parsing or // execution doesn't leave a handler installed whose flag is scoped to a // now-dead lifetime. class ScopedSignalHandler { public: using Handler = void (*)(int); ScopedSignalHandler(int SigNum, Handler NewHandler) : m_SigNum(SigNum), m_PrevHandler(std::signal(SigNum, NewHandler)) {} ~ScopedSignalHandler() { if (m_PrevHandler != SIG_ERR) { std::signal(m_SigNum, m_PrevHandler); } } ScopedSignalHandler(const ScopedSignalHandler&) = delete; ScopedSignalHandler& operator=(const ScopedSignalHandler&) = delete; private: int m_SigNum; Handler m_PrevHandler; }; class ErrorWithReturnCode : public std::runtime_error { public: using _Mybase = runtime_error; ErrorWithReturnCode(const std::string& Message, int InReturnCode) : _Mybase(Message), m_ReturnCode(InReturnCode) {} ErrorWithReturnCode(const char* Message, int InReturnCode) : _Mybase(Message), m_ReturnCode(InReturnCode) {} const int m_ReturnCode = -1; }; /** Base class for command implementations */ class ZenCmdBase { public: virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) = 0; virtual cxxopts::Options& Options() = 0; virtual ZenCmdCategory& CommandCategory() const; // Hidden commands are dispatched normally but omitted from the top-level // `zen --help` listing. Used for deprecated aliases that remain functional // but should not be advertised. virtual bool IsHidden() const { return false; } bool ParseOptions(int argc, char** argv); static bool ParseOptions(cxxopts::Options& Options, int argc, char** argv); static bool ParseOptionsPermissive(cxxopts::Options& Options, int argc, char** argv, std::vector& OutUnmatched); static int GetSubCommand(cxxopts::Options& Options, int argc, char** argv, std::span SubOptions, cxxopts::Options*& OutSubOption, std::vector& OutSubCommandArguments); static std::string ResolveTargetHostSpec(const std::string& InHostSpec); static std::string ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEffectivePort); static bool IsUnixSocketSpec(std::string_view Spec); static HttpClient CreateHttpClient(const std::string& HostSpec, HttpClientSettings Settings = {}); static constexpr const char* kHostUrlHelp = "Host URL or unix:///path/to/socket"; std::string HelpText(); [[noreturn]] void ThrowOptionError(std::string_view Message); static void LogExecutableVersionAndPid(); }; class StorageCommand : public ZenCmdBase { virtual ZenCmdCategory& CommandCategory() const override { return g_StorageCategory; } }; class CacheStoreCommand : public ZenCmdBase { virtual ZenCmdCategory& CommandCategory() const override { return g_CacheStoreCategory; } }; // Base for individual subcommands class ZenSubCmdBase { public: ZenSubCmdBase(std::string_view Name, std::string_view Description); virtual ~ZenSubCmdBase() = default; cxxopts::Options& SubOptions() { return m_SubOptions; } std::string_view Description() const { return m_Description; } const std::vector& Aliases() const { return m_Aliases; } virtual void Run(const ZenCliOptions& GlobalOptions) = 0; protected: void AddAlias(std::string Alias) { m_Aliases.push_back(std::move(Alias)); } cxxopts::Options m_SubOptions; private: std::string m_Description; std::vector m_Aliases; }; // Base for commands that host subcommands - handles all dispatch boilerplate class ZenCmdWithSubCommands : public ZenCmdBase { public: void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) final; protected: void AddSubCommand(ZenSubCmdBase& SubCmd); virtual bool OnParentOptionsParsed(const ZenCliOptions& GlobalOptions); void PrintHelp(); void PrintSubCommandHelp(cxxopts::Options& SubCmdOptions); private: std::vector m_SubCommands; }; class CacheStoreCmdWithSubCommands : public ZenCmdWithSubCommands { ZenCmdCategory& CommandCategory() const override { return g_CacheStoreCategory; } }; class ProjectStoreCmdWithSubCommands : public ZenCmdWithSubCommands { ZenCmdCategory& CommandCategory() const override { return g_ProjectStoreCategory; } }; class StorageCmdWithSubCommands : public ZenCmdWithSubCommands { ZenCmdCategory& CommandCategory() const override { return g_StorageCategory; } }; } // namespace zen