diff --git a/Src/IACore/inc/IACore/Process.hpp b/Src/IACore/inc/IACore/Process.hpp
index d0c894e..299ab63 100644
--- a/Src/IACore/inc/IACore/Process.hpp
+++ b/Src/IACore/inc/IACore/Process.hpp
@@ -1,16 +1,16 @@
-// IACore-OSS; The Core Library for All IA Open Source Projects
+// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 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 .
@@ -18,114 +18,129 @@
#include
-namespace IACore {
+namespace IACore
+{
- class Process {
- public:
+ class Process
+ {
+ public:
// ---------------------------------------------------------------------
// Static One-Shot Execution
// ---------------------------------------------------------------------
-
+
// Returns Exit Code or Error String
// callback receives distinct lines of output (stdout + stderr merged)
- STATIC tl::expected Run(
- CONST String& cmd,
- CONST String& args,
- Function onOutputLine
- ) {
- #if IA_PLATFORM_WINDOWS
- return RunWindows(cmd, args, onOutputLine);
- #else
- return RunPosix(cmd, args, onOutputLine);
- #endif
+ STATIC tl::expected Run(CONST String &cmd, CONST String &args,
+ Function onOutputLine)
+ {
+#if IA_PLATFORM_WINDOWS
+ return RunWindows(cmd, args, onOutputLine);
+#else
+ return RunPosix(cmd, args, onOutputLine);
+#endif
}
- private:
+ private:
// ---------------------------------------------------------------------
// Output Buffering Helper
// Splits raw chunks into lines, preserving partial lines across chunks
// ---------------------------------------------------------------------
- struct LineBuffer {
+ struct LineBuffer
+ {
String accumulator;
- Function& callback;
+ Function &callback;
- VOID Append(const char* data, SIZE_T size) {
+ VOID Append(const char *data, SIZE_T size)
+ {
SIZE_T start = 0;
- for (SIZE_T i = 0; i < size; ++i) {
- if (data[i] == '\n' || data[i] == '\r') {
+ for (SIZE_T i = 0; i < size; ++i)
+ {
+ if (data[i] == '\n' || data[i] == '\r')
+ {
// Flush accumulator + current chunk
- if (!accumulator.empty()) {
+ if (!accumulator.empty())
+ {
accumulator.append(data + start, i - start);
- if (!accumulator.empty()) callback(accumulator);
+ if (!accumulator.empty())
+ callback(accumulator);
accumulator.clear();
- } else {
- // Zero copy optimization for pure lines in one chunk
- if (i > start) callback(StringView(data + start, i - start));
}
-
+ else
+ {
+ // Zero copy optimization for pure lines in one chunk
+ if (i > start)
+ callback(StringView(data + start, i - start));
+ }
+
// Skip \r\n sequence if needed, or just start next
- if (data[i] == '\r' && i + 1 < size && data[i+1] == '\n') i++;
+ if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
+ i++;
start = i + 1;
}
}
// Save remaining partial line
- if (start < size) {
+ if (start < size)
+ {
accumulator.append(data + start, size - start);
}
}
- VOID Flush() {
- if (!accumulator.empty()) {
+ VOID Flush()
+ {
+ if (!accumulator.empty())
+ {
callback(accumulator);
accumulator.clear();
}
}
};
- // ---------------------------------------------------------------------
- // Windows Implementation
- // ---------------------------------------------------------------------
- #if IA_PLATFORM_WINDOWS
- STATIC tl::expected RunWindows(CONST String& cmd, CONST String& args, Function cb) {
- SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; // Allow inheritance
+// ---------------------------------------------------------------------
+// Windows Implementation
+// ---------------------------------------------------------------------
+#if IA_PLATFORM_WINDOWS
+ STATIC tl::expected RunWindows(CONST String &cmd, CONST String &args,
+ Function cb)
+ {
+ SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance
HANDLE hRead = NULL, hWrite = NULL;
- if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
+ if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
return tl::make_unexpected("Failed to create pipe");
// Ensure the read handle to the pipe for STDOUT is NOT inherited
- if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
+ if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
return tl::make_unexpected("Failed to secure pipe handles");
- STARTUPINFOA si = { sizeof(STARTUPINFOA) };
+ STARTUPINFOA si = {sizeof(STARTUPINFOA)};
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hWrite;
- si.hStdError = hWrite; // Merge stderr
- si.hStdInput = NULL; // No input
+ si.hStdError = hWrite; // Merge stderr
+ si.hStdInput = NULL; // No input
+
+ PROCESS_INFORMATION pi = {0};
- PROCESS_INFORMATION pi = { 0 };
-
// Windows command line needs to be mutable and concatenated
String commandLine = BuildString("\"", cmd, "\" ", args);
- BOOL success = CreateProcessA(
- NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi
- );
+ BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
// Important: Close write end in parent, otherwise ReadFile never returns EOF!
- CloseHandle(hWrite);
+ CloseHandle(hWrite);
- if (!success) {
+ if (!success)
+ {
CloseHandle(hRead);
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
}
// Read Loop
- LineBuffer lineBuf{ "", cb };
+ LineBuffer lineBuf{"", cb};
DWORD bytesRead;
CHAR buffer[4096];
- while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0) {
+ while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
+ {
lineBuf.Append(buffer, bytesRead);
}
lineBuf.Flush();
@@ -141,59 +156,95 @@ namespace IACore {
return static_cast(exitCode);
}
- #endif
+#endif
- // ---------------------------------------------------------------------
- // POSIX (Linux/Mac) Implementation
- // ---------------------------------------------------------------------
- #if IA_PLATFORM_UNIX
- STATIC tl::expected RunPosix(CONST String& cmd, CONST String& args, Function cb) {
+// ---------------------------------------------------------------------
+// POSIX (Linux/Mac) Implementation
+// ---------------------------------------------------------------------
+#if IA_PLATFORM_UNIX
+ STATIC tl::expected RunPosix(CONST String &cmd, CONST String &args,
+ Function cb)
+ {
int pipefd[2];
- if (pipe(pipefd) == -1) return tl::make_unexpected("Failed to create pipe");
+ if (pipe(pipefd) == -1)
+ return tl::make_unexpected("Failed to create pipe");
pid_t pid = fork();
- if (pid == -1) {
+ if (pid == -1)
+ {
return tl::make_unexpected("Failed to fork process");
- }
- else if (pid == 0) {
+ }
+ else if (pid == 0)
+ {
// --- Child Process ---
- close(pipefd[0]); // Close read end
+ close(pipefd[0]);
- // Redirect stdout/stderr to pipe
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
- close(pipefd[1]); // Close original write end
+ close(pipefd[1]);
- // Prepare Args. execvp requires an array of char*
- // This is quick and dirty splitting.
- // In a real engine you might pass args as a vector.
- std::vector argv;
- std::string cmdStr = cmd; // copy to modify if needed
+ // --- ARGUMENT PARSING START ---
+ std::vector argStorage; // To keep strings alive
+ std::vector argv;
+
+ std::string cmdStr = cmd;
argv.push_back(cmdStr.data());
- // Split args string by space (simplistic)
- // Better: Pass vector to Run()
- std::string argsCopy = args;
- char* token = strtok(argsCopy.data(), " ");
- while (token) {
- argv.push_back(token);
- token = strtok(NULL, " ");
+ // Manual Quote-Aware Splitter
+ std::string currentToken;
+ bool inQuotes = false;
+
+ for (char c : args)
+ {
+ if (c == '\"')
+ {
+ inQuotes = !inQuotes;
+ // Determine if you want to keep the quotes or strip them.
+ // Usually for execvp, you strip them so the shell receives the raw content.
+ continue;
+ }
+
+ if (c == ' ' && !inQuotes)
+ {
+ if (!currentToken.empty())
+ {
+ argStorage.push_back(currentToken);
+ currentToken.clear();
+ }
+ }
+ else
+ {
+ currentToken += c;
+ }
+ }
+ if (!currentToken.empty())
+ {
+ argStorage.push_back(currentToken);
+ }
+
+ // Build char* array from the std::string storage
+ for (auto &s : argStorage)
+ {
+ argv.push_back(s.data());
}
argv.push_back(nullptr);
+ // --- ARGUMENT PARSING END ---
execvp(argv[0], argv.data());
- _exit(127); // If execvp fails
- }
- else {
+ _exit(127);
+ }
+ else
+ {
// --- Parent Process ---
- close(pipefd[1]); // Close write end
+ close(pipefd[1]);
- LineBuffer lineBuf{ "", cb };
+ LineBuffer lineBuf{"", cb};
char buffer[4096];
ssize_t count;
- while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
+ while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
+ {
lineBuf.Append(buffer, count);
}
lineBuf.Flush();
@@ -201,11 +252,12 @@ namespace IACore {
int status;
waitpid(pid, &status, 0);
-
- if (WIFEXITED(status)) return WEXITSTATUS(status);
- return -1; // Crashed or killed
+
+ if (WIFEXITED(status))
+ return WEXITSTATUS(status);
+ return -1;
}
}
- #endif
+#endif
};
-}
\ No newline at end of file
+} // namespace IACore
\ No newline at end of file
diff --git a/Src/IACore/inc/IACore/Utils.hpp b/Src/IACore/inc/IACore/Utils.hpp
index 658968e..8fedd95 100644
--- a/Src/IACore/inc/IACore/Utils.hpp
+++ b/Src/IACore/inc/IACore/Utils.hpp
@@ -101,13 +101,24 @@ namespace IACore
// -------------------------------------------------------------------------
template INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v)
{
- // Use Ankerl's high-speed hasher for the individual type
- // This automatically handles ints, floats, strings, etc. efficiently.
- auto hasher = ankerl::unordered_dense::hash();
- UINT64 h = hasher(v);
+ UINT64 h;
+
+ // 1. Compile-Time check: Can this be treated as a string view?
+ // This catches "Literal Strings" (char[N]), const char*, and std::string
+ if constexpr (std::is_constructible_v)
+ {
+ std::string_view sv(v);
+ auto hasher = ankerl::unordered_dense::hash();
+ h = hasher(sv);
+ }
+ else
+ {
+ // 2. Standard types (int, float, custom structs)
+ auto hasher = ankerl::unordered_dense::hash();
+ h = hasher(v);
+ }
// 0x9e3779b97f4a7c15 is the 64-bit golden ratio (phi) approximation
- // This spreads bits to avoid collisions in the hash table.
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
}
@@ -123,7 +134,7 @@ namespace IACore
}
// -------------------------------------------------------------------------
- // F;pat Hasher
+ // Flat Hasher
// Allows: IACore::ComputeHashFlat(x, y, z);
// -------------------------------------------------------------------------
template INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)
diff --git a/Tests/Unit/Utils.cpp b/Tests/Unit/Utils.cpp
index 37b9b67..3a38ffa 100644
--- a/Tests/Unit/Utils.cpp
+++ b/Tests/Unit/Utils.cpp
@@ -59,7 +59,7 @@ IAT_BEGIN_BLOCK(Core, Utils)
// B. Hex To Binary (Valid Upper)
auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF");
IAT_CHECK(resUpper.has_value());
- IAT_CHECK_EQ(resUpper->size(), 6);
+ IAT_CHECK_EQ(resUpper->size(), (SIZE_T)6);
IAT_CHECK_EQ((*resUpper)[0], 0xDE);
IAT_CHECK_EQ((*resUpper)[5], 0xFF);
@@ -189,11 +189,24 @@ IAT_BEGIN_BLOCK(Core, Utils)
IAT_CHECK_EQ(h1, h2); // Same content = same hash
IAT_CHECK_NEQ(h1, h3); // Different content = different hash
- // Verify it works with ComputeHash when passed as object
- // (Assuming you extend ComputeHash to handle custom types via the specialized hasher)
- // Since ComputeHash uses ankerl::unordered_dense::hash internally, this should work:
+ // -------------------------------------------------------------
+ // Verify ComputeHash integration
+ // -------------------------------------------------------------
+
+ // We cannot check EQ(h1, ComputeHash(v1)) because ComputeHash applies
+ // one extra layer of "Golden Ratio Mixing" on top of the object's hash.
+ // Instead, we verify that ComputeHash behaves exactly like a manual HashCombine.
+
+ UINT64 hManual = 0;
+ IACore::Utils::HashCombine(hManual, v1);
+
UINT64 hWrapper = IACore::Utils::ComputeHash(v1);
- IAT_CHECK_EQ(h1, hWrapper);
+
+ // This proves ComputeHash found the specialization and mixed it correctly
+ IAT_CHECK_EQ(hManual, hWrapper);
+
+ // Optional: Verify the avalanche effect took place (hWrapper should NOT be h1)
+ IAT_CHECK_NEQ(h1, hWrapper);
return TRUE;
}