deargui-vpl/applications/base/source/entry_point.cpp
2026-02-03 18:25:25 +01:00

325 lines
9.2 KiB
C++

#define _CRT_SECURE_NO_WARNINGS
# include "base.h"
# include "platform.h"
# include <map>
# include <vector>
# include <iostream>
# include <cstdio>
# include <csignal>
#if defined(_WIN32)
# include <io.h>
# include <fcntl.h>
# include <windows.h>
#endif
#define CLI11_DISABLE_EXTRA_VALIDATORS 1
# include "../../external/CLI11.hpp"
namespace {
class CommandLineParser
{
public:
// Using unique_ptr as a C++14 alternative to optional.
// A null return indicates the program should exit.
std::unique_ptr<ArgsMap> Parse(std::vector<std::string> args)
{
CLI::App app{"NodeHub"};
app.allow_extras();
app.add_option("--file", "the source path")->capture_default_str();
// Graph file path (absolute or relative)
app.add_option("--graph", "Path to graph file (absolute or relative)")->capture_default_str();
// CLI mode options
app.add_flag("--headless", "Run without GUI (CLI mode)");
app.add_option("--command", "Command to execute in headless mode (validate, export, execute, info)")
->capture_default_str();
app.add_option("--output", "Output file path for export commands")->capture_default_str();
app.add_option("--format", "Output format (json, xml, yaml, dot)")->capture_default_str();
// Runtime execution options
app.add_flag("--run", "Execute the graph runtime (used with --headless and --graph)");
app.add_option("--log", "Path to log file (absolute or relative)")->capture_default_str();
app.add_option("--log-level", "Minimum logging level (trace, debug, info, warn, error, critical, off)")->capture_default_str();
// Manual pre-check for help flag because allow_extras() interferes with normal help handling
for (const auto& arg : args) {
if (arg == "--help" || arg == "-h") {
PrintHelp(app);
m_ExitCode = 0;
return nullptr;
}
}
try {
app.parse(args);
} catch (const CLI::ParseError& e) {
HandleParseError(app, e);
m_ExitCode = app.exit(e);
return nullptr;
}
auto args_map = std::make_unique<ArgsMap>();
for (const CLI::Option* option : app.get_options())
{
if(option->count() > 0 && !option->get_lnames().empty())
{
if (!option->results().empty())
{
(*args_map)[option->get_lnames()[0]] = ParseValue(option->results()[0]);
}
else
{
(*args_map)[option->get_lnames()[0]] = ArgValue(true);
}
}
}
ParseKeyValueArgs(app.remaining(), *args_map);
m_ExitCode = 0;
return args_map;
}
int GetExitCode() const { return m_ExitCode; }
protected:
virtual void PrintHelp(CLI::App& app)
{
printf("%s\n", app.help().c_str());
fflush(stdout);
}
virtual void HandleParseError(CLI::App& app, const CLI::ParseError& e)
{
// Default console implementation
fprintf(stderr, "%s\n", app.help().c_str());
fprintf(stderr, "%s: %s\n", app.get_name().c_str(), e.what());
fflush(stderr);
}
int m_ExitCode = 0;
private:
ArgValue ParseValue(const std::string& s)
{
if (s.empty())
return ArgValue();
if (s == "true" || s == "TRUE" || s == "True")
return ArgValue(true);
else if (s == "false" || s == "FALSE" || s == "False")
return ArgValue(false);
char* end = nullptr;
const char* start = s.c_str();
// Try parsing as an integer
long long int_val = strtoll(start, &end, 10);
if (end == start + s.length())
return ArgValue(int_val);
// Try parsing as a double
end = nullptr;
double double_val = strtod(start, &end);
if (end == start + s.length())
return ArgValue(double_val);
return ArgValue(s);
}
void ParseKeyValueArgs(const std::vector<std::string>& args, ArgsMap& args_map)
{
for (size_t i = 0; i < args.size(); ++i) {
std::string arg = args[i];
if (arg.length() > 2 && arg.substr(0, 2) == "--") {
std::string body = arg.substr(2);
if (body.empty())
continue;
auto equalPos = body.find('=');
if (equalPos != std::string::npos) {
std::string key = body.substr(0, equalPos);
std::string value = body.substr(equalPos + 1);
if (!key.empty())
args_map[key] = ParseValue(value);
continue;
}
std::string key = body;
if (i + 1 < args.size() && !args[i + 1].empty() && args[i + 1][0] != '-') {
args_map[key] = ParseValue(args[i + 1]);
++i; // Skip the value
} else {
args_map[key] = true;
}
}
}
}
};
}
Application* g_Application = nullptr;
namespace
{
void SignalHandler(int signal)
{
if (g_Application)
g_Application->Quit();
}
}
# if defined(_WIN32) && !defined(_CONSOLE)
static void redirect_std_to_console() {
// reopen stdout/stderr to the console
FILE* fp;
freopen_s(&fp, "CONOUT$", "w", stdout);
freopen_s(&fp, "CONOUT$", "w", stderr);
freopen_s(&fp, "CONIN$", "r", stdin);
// make iostreams sync with C stdio
std::ios::sync_with_stdio(true);
}
static bool ensure_console_attached() {
// Try to attach if launched from a terminal
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
redirect_std_to_console();
return true;
}
return false;
}
static void ensure_console_allocated() {
if (AllocConsole()) {
redirect_std_to_console();
}
}
# if PLATFORM(WINDOWS)
# define NOMINMAX
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <stdlib.h> // __argc, argv
# endif
inline std::string WideToUtf8(const wchar_t* wstr) {
if (!wstr) return {};
// Ask for required size (includes NUL)
const int n = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
if (n <= 0) return {};
std::string out;
out.resize(n - 1); // we store without the trailing NUL
#if __cplusplus >= 201703L
// C++17: string::data() is char*
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, out.data(), n, nullptr, nullptr);
#else
// C++11/14: use &out[0] to get char*
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &out[0], n, nullptr, nullptr);
#endif
// The call above wrote a NUL at the end because we passed length n.
// Keep size at n-1 (already set via resize).
return out;
}
// Build argc/argv as **mutable** C strings
struct ArgvUtf8 {
int argc = 0;
std::vector<std::unique_ptr<char[]>> storage; // owns the writable C strings
std::vector<char*> argv; // pointers to the writable strings
};
static std::vector<std::string> GetUtf8Argv() {
int wargc = 0;
LPWSTR* wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
std::vector<std::string> args;
if (wargv) {
for (int i = 0; i < wargc; ++i)
args.emplace_back(WideToUtf8(wargv[i]));
LocalFree(wargv);
}
return args;
}
static std::vector<std::string> GetUtf8Args()
{
int wargc = 0;
LPWSTR* wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
std::vector<std::string> args;
if (wargv) {
for (int i = 1; i < wargc; ++i) // skip executable path
args.emplace_back(WideToUtf8(wargv[i]));
LocalFree(wargv);
}
return args;
}
namespace {
class WindowsCommandLineParser : public CommandLineParser
{
protected:
void HandleParseError(CLI::App& app, const CLI::ParseError& e) override
{
// Print to attached console first
CommandLineParser::HandleParseError(app, e);
// Then show a message box for the GUI user
std::string msg = std::string("Argument parsing failed:\n\n") + e.what() +
"\n\nTry '--help' for usage.";
MessageBoxA(nullptr, msg.c_str(), "CLI Error", MB_ICONERROR | MB_OK);
}
};
}
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
signal(SIGINT, SignalHandler);
setvbuf(stdout, nullptr, _IONBF, 0);
setvbuf(stderr, nullptr, _IONBF, 0);
if (!ensure_console_attached()) ensure_console_allocated();
fflush(stdout);
WindowsCommandLineParser parser;
auto args = GetUtf8Args();
auto args_map = parser.Parse(args);
int exit_code;
if (args_map)
{
exit_code = Main(*args_map);
}
else
{
exit_code = parser.GetExitCode();
}
return exit_code;
}
# else
int main(int argc, char** argv)
{
signal(SIGINT, SignalHandler);
CommandLineParser parser;
std::vector<std::string> args;
for (int i = 1; i < argc; ++i)
args.push_back(argv[i]);
auto args_map = parser.Parse(args);
if (args_map)
return Main(*args_map);
else
return parser.GetExitCode();
}
# endif