diff --git a/Src/IACore/inc/hpp/IACore/CLI.hpp b/Src/IACore/inc/hpp/IACore/CLI.hpp new file mode 100644 index 0000000..864cb60 --- /dev/null +++ b/Src/IACore/inc/hpp/IACore/CLI.hpp @@ -0,0 +1,218 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2024 IAS (ias@iasoft.dev) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include +#include + +namespace ia +{ + class CLI + { + public: + enum class ParameterValueType + { + INT, + STRING, + INT_LIST, + STRING_LIST + }; + + struct ParameterValue + { + ParameterValueType Type{}; + INT32 IntValue{}; + String StringValue{}; + Vector IntListValue{}; + Vector StringListValue{}; + }; + + struct ParameterDesc + { + String ID; + String Help; + BOOL IsOptional{}; + ParameterValueType Type{}; + }; + + struct SwitchDesc + { + String ID; + String Help; + }; + + struct CommandDesc + { + String ID; + CHAR Shorthand{}; + String Help; + Vector Switches; + Vector Parameters; + std::function &¶meters, IN Map &&switchValues)> + Action; + }; + + public: + CLI(IN CONST String &appName, IN IA_VERSION_TYPE appVersion, IN CONST String ©rightNotice) + : m_appName(appName), m_appVersion(IA_STRINGIFY_VERSION(appVersion)), m_copyrightNotice(copyrightNotice) + { + RegisterCommand({ + .ID = "help", + .Shorthand = 'h', + .Help = "Displays this help menu", + .Action = + [&](IN Map &¶meters, IN Map &&switchValues) { + DisplayHelp(); + return 0; + }, + }); + } + + INLINE INT32 Run(IN INT32 argc, IN PCCHAR argv[]); + + protected: + INLINE VOID RegisterCommand(IN CONST CommandDesc &desc); + + private: + INLINE VOID DisplayHelp(); + + private: + CONST String m_appName; + CONST String m_appVersion; + CONST String m_copyrightNotice; + Map m_commands; + }; +} // namespace ia + +namespace ia +{ + INT32 CLI::Run(IN INT32 argc, IN PCCHAR argv[]) + { + printf(__CC_WHITE "%s %s\n%s\n\n" __CC_DEFAULT, m_appName.c_str(), m_appVersion.c_str(), + m_copyrightNotice.c_str()); + + if (argc < 2) + { + DisplayHelp(); + return 0; + } + + if (!m_commands.contains(argv[1])) + { + printf(__CC_RED "No such known command \"%s\".\n" __CC_YELLOW + "TIP: Use \"help\" to see available commands!" __CC_DEFAULT "\n", + argv[1]); + return -1; + } + + const auto cmd = m_commands[argv[1]]; + const auto requiredParamCount = (INT32) cmd.Parameters.size(); + if ((requiredParamCount << 1) >= (argc - 1)) + { + printf(__CC_RED "Command \"%s\" requires at least %i parameter(s).\n" __CC_YELLOW + "TIP: Use \"help\" to see correct usages!" __CC_DEFAULT "\n", + argv[1], requiredParamCount); + return -1; + } + + Map params; + for (const auto &t : cmd.Parameters) + params[t.ID] = t; + + Map paramValues; + + for (INT32 i = 2; i < argc; i++) + { + if (argv[i][0] == '-') + { + const auto paramName = &(argv[i][1]); + if (!params.contains(paramName)) + { + printf(__CC_RED "No such parameter \"%s\".\n" __CC_YELLOW + "TIP: Use \"help\" to see correct usages!" __CC_DEFAULT "\n", + argv[i]); + return -3; + } + + const auto& p = params[paramName]; + + // Read parameter value + const auto value = argv[++i]; + switch(p.Type) + { + case ParameterValueType::INT: + paramValues[paramName] = ParameterValue{.Type = p.Type}; + break; + + case ParameterValueType::STRING: + paramValues[paramName] = ParameterValue{.Type = p.Type, .StringValue = value}; + break; + + case ParameterValueType::INT_LIST: + paramValues[paramName] = ParameterValue{.Type = p.Type}; + break; + + case ParameterValueType::STRING_LIST: + paramValues[paramName] = ParameterValue{.Type = p.Type}; + break; + } + } + else if (argv[i][0] == '/') + { + } + else + { + printf(__CC_RED "Invalid command syntax, expected a parameter('-') or a switch('/')." __CC_DEFAULT + "\n"); + return -2; + } + } + + cmd.Action(IA_MOVE(paramValues), {}); + + return 0; + } + + VOID CLI::DisplayHelp() + { + INT32 i{1}; + for (const auto &t : m_commands) + { + printf(__CC_WHITE "%i) " __CC_GREEN "%s (%c) " __CC_YELLOW "- %s\n", i, t->Value.ID.c_str(), + t->Value.Shorthand, t->Value.Help.c_str()); + + for (const auto &v : t->Value.Parameters) + { + printf(__CC_WHITE "\t-%s %s %s\n", v.ID.c_str(), v.IsOptional ? "(optional)" : "", v.Help.c_str()); + } + + for (const auto &v : t->Value.Switches) + { + printf(__CC_WHITE, "\t/%s %s\n", v.ID.c_str(), v.Help.c_str()); + } + + printf(__CC_DEFAULT "\n"); + i++; + } + } + + VOID CLI::RegisterCommand(IN CONST CommandDesc &desc) + { + m_commands[desc.ID] = desc; + } +} // namespace ia \ No newline at end of file diff --git a/Src/IACore/inc/hpp/IACore/Definitions.hpp b/Src/IACore/inc/hpp/IACore/Definitions.hpp index 58767f5..14fb48b 100644 --- a/Src/IACore/inc/hpp/IACore/Definitions.hpp +++ b/Src/IACore/inc/hpp/IACore/Definitions.hpp @@ -151,6 +151,7 @@ #define IA_VERSION_TYPE UINT64 #define IA_MAKE_VERSION(major, minor, patch) ((static_cast(major) & 0xFFFFFF) << 40) | ((static_cast(minor) & 0xFFFFFF) << 16) | (static_cast(patch) & 0xFFFF) +#define IA_STRINGIFY_VERSION(version) BuildString("v", (version >> 40) & 0xFFFFFF, ".", (version >> 16) & 0xFFFFFF, ".", version & 0xFFFF) #if defined(_MSC_VER) #define IA_DLL_EXPORT __declspec(dllexport)