getpkg/dehydrate/src/generator.cpp
Your Name dd0fc37798
All checks were successful
Build-Test-Publish / build (linux/amd64) (push) Successful in 34s
Build-Test-Publish / build (linux/arm64) (push) Successful in 44s
Build-Test-Publish / test-install-from-scratch (linux/amd64) (push) Successful in 8s
Build-Test-Publish / test-install-from-scratch (linux/arm64) (push) Successful in 8s
test: Update 19 files
2025-06-22 23:10:39 +12:00

372 lines
12 KiB
C++

#include "generator.hpp"
#include "../include/hash.hpp"
#include <iostream>
#include <fstream>
#include <filesystem>
#include <sstream>
#include <vector>
#include "xxhash.hpp"
#include <sys/stat.h> // For file permissions
#include <cstring> // 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<const uint8_t*>(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<int>(pos - base64_chars);
valb += 6;
if (valb >= 0) {
output[out_pos++] = static_cast<unsigned char>((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<const char*>(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 <string>\nnamespace " << ns << " {\nbool recreate_file(std::string destination_folder);\n}\n";
// Write CPP
std::ofstream cpp(dest / cppname);
cpp << R"cpp(#include <fstream>
#include <filesystem>
#include <string>
#include <iostream>
#include <cstring>
// 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<const uint8_t*>(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<const unsigned char*>(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<unsigned>(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<typename F>
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<fs::path> 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 <string>
namespace )hpp" << ns << R"hpp( {
bool recreate_tree(std::string destination_folder);
}
)hpp";
// -------------------------------------------------------------------------
// Write CPP
std::ofstream cpp(dest / cppname);
cpp << R"cpp(#include <fstream>
#include <filesystem>
#include <string>
#include <iostream>
#include <cstring>
/*
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<const uint8_t*>(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<const unsigned char*>(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<unsigned>(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;
}
}