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)