#include "generator.hpp" #include "../include/hash.hpp" #include #include #include #include #include #include "xxhash.hpp" #include // For file permissions #include // For strlen namespace fs = std::filesystem; static std::string sanitize(const std::string& name) { std::string out = name; for (char& c : out) if (!isalnum(c)) c = '_'; return out; } static uint64_t fnv1a_64(const void* data, size_t len) { const uint8_t* p = static_cast(data); uint64_t h = 0xcbf29ce484222325ULL; for (size_t i = 0; i < len; ++i) h = (h ^ p[i]) * 0x100000001b3ULL; return h; } // Base64 encoding function - no dependencies static std::string base64_encode(const unsigned char* data, size_t len) { const char* base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string result; result.reserve((len + 2) / 3 * 4); // Reserve space for the full encoded size int val = 0, valb = -6; for (size_t i = 0; i < len; i++) { val = (val << 8) + data[i]; valb += 8; while (valb >= 0) { result.push_back(base64_chars[(val >> valb) & 0x3F]); valb -= 6; } } if (valb > -6) { result.push_back(base64_chars[((val << 8) >> (valb + 8)) & 0x3F]); } // Add padding while (result.size() % 4) { result.push_back('='); } return result; } // Helper function to output the _recreate_file_ utility function and Base64 decoder static void output_recreate_file_utility(std::ofstream& cpp) { cpp << R"cpp( // Base64 decoding function - no dependencies static void base64_decode(const char* encoded_data, size_t encoded_len, unsigned char* output, size_t* output_len) { const char* base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; size_t out_pos = 0; int val = 0, valb = -8; for (size_t i = 0; i < encoded_len; i++) { char c = encoded_data[i]; if (c == '=') break; // Find position in base64_chars const char* pos = strchr(base64_chars, c); if (pos == nullptr) continue; // Skip invalid characters val = (val << 6) + static_cast(pos - base64_chars); valb += 6; if (valb >= 0) { output[out_pos++] = static_cast((val >> valb) & 0xFF); valb -= 8; } } *output_len = out_pos; } // Utility function to recreate a file with proper permissions static bool _recreate_file_(const std::filesystem::path& outpath, uint64_t file_hash, std::filesystem::perms file_perms, const unsigned char* filedata, size_t filedata_len) { namespace fs = std::filesystem; bool needs_write = false; // Check if file exists and has correct hash if (fs::exists(outpath)) { // Check content hash std::ifstream in(outpath, std::ios::binary); std::ostringstream oss; oss << in.rdbuf(); std::string data = oss.str(); uint64_t existing_hash = fnv1a_64(data.data(), data.size()); needs_write = existing_hash != file_hash; } else { needs_write = true; // File doesn't exist, need to create it } bool needs_permission_update = true; if (!needs_write) { // we always update permissions if the file is written or changed. Othewise we check. fs::perms current_perms = fs::status(outpath).permissions(); needs_permission_update = current_perms != file_perms; } if (needs_write) { bool existed = fs::exists(outpath); fs::create_directories(outpath.parent_path()); std::ofstream out(outpath, std::ios::binary); out.write(reinterpret_cast(filedata), filedata_len); out.close(); // Set the file permissions fs::permissions(outpath, file_perms); if (!existed) { std::cout << "[dehydrate] " << outpath.filename() << ": created\n"; } else { std::cout << "[dehydrate] " << outpath.filename() << ": updated (hash changed)\n"; } return true; } if (needs_permission_update) { // Update only permissions fs::permissions(outpath, file_perms); std::cout << "[dehydrate] " << outpath.filename() << ": updated (permissions changed)\n"; return true; } return false; } )cpp"; } void generate_file_code(const std::string& source, const std::string& destfolder, bool silent) { fs::path src(source); fs::path dest(destfolder); std::string ns = "recreate_" + sanitize(src.stem().string()); std::string cppname = "_" + src.stem().string() + ".cpp"; std::string hppname = "_" + src.stem().string() + ".hpp"; std::string bothname = "_" + src.stem().string() + ".{cpp,hpp}"; fs::create_directories(dest); std::ifstream in(source, std::ios::binary); std::ostringstream oss; oss << in.rdbuf(); std::string filedata = oss.str(); uint64_t hash = fnv1a_64(filedata.data(), filedata.size()); // Get source file permissions fs::perms src_perms = fs::status(src).permissions(); // Write HPP std::ofstream hpp(dest / hppname); hpp << "#pragma once\n#include \nnamespace " << ns << " {\nbool recreate_file(std::string destination_folder);\n}\n"; // Write CPP std::ofstream cpp(dest / cppname); cpp << R"cpp(#include #include #include #include #include // Tiny dependency-free FNV-1a 64-bit hash static uint64_t fnv1a_64(const void* data, size_t len) { const uint8_t* p = static_cast(data); uint64_t h = 0xcbf29ce484222325ULL; for (size_t i = 0; i < len; ++i) h = (h ^ p[i]) * 0x100000001b3ULL; return h; } )cpp"; cpp << "#include \"" << hppname << "\"\n"; cpp << "namespace " << ns << " {\n"; // Output the recreate_file utility function output_recreate_file_utility(cpp); // Write recreate_file function with embedded file data cpp << R"cpp( bool recreate_file(std::string destination_folder) { namespace fs = std::filesystem; fs::path outpath = fs::path(destination_folder) / ")cpp" << src.filename().string() << R"cpp("; // File data embedded as Base64 static const char filedata_base64[] = )cpp"; // Encode the file data to Base64 std::string base64 = base64_encode(reinterpret_cast(filedata.data()), filedata.size()); // Split into 76-character chunks for readability const size_t line_length = 76; for (size_t i = 0; i < base64.length(); i += line_length) { if (i > 0) cpp << "\n "; cpp << "\"" << base64.substr(i, std::min(line_length, base64.length() - i)) << "\""; if (i + line_length < base64.length()) cpp << "\\"; } cpp << ";\n\n"; // Decode Base64 at runtime cpp << " // Decode Base64 data\n"; cpp << " size_t decoded_size = (strlen(filedata_base64) * 3) / 4;\n"; cpp << " unsigned char* decoded_data = new unsigned char[decoded_size];\n"; cpp << " size_t actual_size;\n"; cpp << " base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size);\n\n"; // Call _recreate_file_ with the decoded data cpp << " bool result = _recreate_file_(outpath, " << hash << "ULL, " << "std::filesystem::perms(" << static_cast(src_perms) << "), " << "decoded_data, actual_size);\n"; // Clean up cpp << " delete[] decoded_data;\n"; cpp << " return result;\n"; cpp << "}\n"; cpp << "}\n"; if (!silent) { std::cout << "[dehydrate] Generated: " << (dest / bothname) << std::endl; } } // Helper to recursively collect all files in a directory template void walk_dir(const fs::path& dir, F&& f) { for (auto& p : fs::recursive_directory_iterator(dir)) { if (fs::is_regular_file(p)) f(p.path()); } } void generate_folder_code(const std::string& source, const std::string& destfolder, bool silent) { fs::path src(source); fs::path dest(destfolder); std::string ns = "recreate_" + sanitize(src.stem().string()); std::string cppname = "_" + src.stem().string() + ".cpp"; std::string hppname = "_" + src.stem().string() + ".hpp"; std::string bothname = "_" + src.stem().string() + ".{cpp,hpp}"; fs::create_directories(dest); // Collect all files std::vector files; walk_dir(src, [&](const fs::path& p) { files.push_back(p); }); // Write HPP std::ofstream hpp(dest / hppname); // ------------------------------------------------------------------------- // Generate HPP hpp << R"hpp( #pragma once /* THIS FILE IS AUTO-GENERATED BY DEHYDRATE. DO NOT EDIT THIS FILE. */ #include namespace )hpp" << ns << R"hpp( { bool recreate_tree(std::string destination_folder); } )hpp"; // ------------------------------------------------------------------------- // Write CPP std::ofstream cpp(dest / cppname); cpp << R"cpp(#include #include #include #include #include /* THIS FILE IS AUTO-GENERATED BY DEHYDRATE. DO NOT EDIT THIS FILE. */ )cpp"; cpp << "#include \"" << hppname << "\"\n"; cpp << "namespace " << ns << " {\n"; cpp << R"cpp( // Tiny dependency-free FNV-1a 64-bit hash static uint64_t fnv1a_64(const void* data, size_t len) { const uint8_t* p = static_cast(data); uint64_t h = 0xcbf29ce484222325ULL; for (size_t i = 0; i < len; ++i) h = (h ^ p[i]) * 0x100000001b3ULL; return h; } )cpp"; // Output the recreate_file utility function output_recreate_file_utility(cpp); // Start writing recreate_tree - we'll embed file data directly in function cpp << R"cpp( bool recreate_tree(std::string destination_folder) { namespace fs = std::filesystem; bool any_written = false; )cpp"; // Process each file for (const auto& file : files) { std::ifstream in(file, std::ios::binary); std::ostringstream oss; oss << in.rdbuf(); std::string filedata = oss.str(); uint64_t hash = fnv1a_64(filedata.data(), filedata.size()); fs::perms file_perms = fs::status(file).permissions(); std::string rel = fs::relative(file, src).string(); std::string var = sanitize(rel); // Start a scope to limit data's lifetime cpp << " {\n"; cpp << " // File: " << rel << "\n"; cpp << " fs::path outpath = fs::path(destination_folder) / \"" << rel << "\";\n"; // Embed file data as Base64 cpp << " static const char filedata_base64[] = "; // Encode the file data to Base64 std::string base64 = base64_encode(reinterpret_cast(filedata.data()), filedata.size()); // Split into 76-character chunks for readability const size_t line_length = 76; for (size_t i = 0; i < base64.length(); i += line_length) { if (i > 0) cpp << "\n "; cpp << "\"" << base64.substr(i, std::min(line_length, base64.length() - i)) << "\""; if (i + line_length < base64.length()) cpp << "\\"; } cpp << ";\n\n"; // Decode Base64 at runtime cpp << " // Decode Base64 data\n"; cpp << " size_t decoded_size = (strlen(filedata_base64) * 3) / 4;\n"; cpp << " unsigned char* decoded_data = new unsigned char[decoded_size];\n"; cpp << " size_t actual_size;\n"; cpp << " base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size);\n\n"; // Call _recreate_file_ with the decoded data cpp << " bool file_written = _recreate_file_(outpath, " << hash << "ULL, std::filesystem::perms(" << static_cast(file_perms) << "), " << "decoded_data, actual_size);\n"; // Clean up and update flag cpp << " delete[] decoded_data;\n"; cpp << " any_written = any_written || file_written;\n"; cpp << " }\n"; // Close scope to free memory } cpp << " return any_written;\n"; cpp << "}\n"; cpp << "}\n"; if (!silent) { std::cout << "[dehydrate] Generated: " << (dest / bothname) << std::endl; } }