This commit is contained in:
2026-03-28 23:02:28 +01:00
committed by Romain BOULLARD
parent 29491b8c3e
commit 1328b17b47
15 changed files with 636 additions and 121 deletions

View File

@@ -1,4 +1,62 @@
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
#include <Generator.hpp>
#include <Log.hpp>
#include <CLI/CLI.hpp>
int main(int argc, char** argv)
{
Bin2CPP::Singleton<Bin2CPP::Log>::Lifetime loggerLifetime;
CLI::App app {"Bin2CPP allows you to generate a C++ header containing the binary content of an input file"};
argv = app.ensure_utf8(argv);
std::string input;
app.add_option("-f,--input", input, "The input file")->required();
std::string output;
app.add_option("-o,--output", output, "The output file")->required();
std::optional<std::string> arrayType;
app.add_option("--arrayType", arrayType, "The type of the array");
std::optional<std::string> arrayInclude;
app.add_option("--arrayInclude", arrayInclude, "Include for the array type");
std::optional<std::string> arrayName;
app.add_option("--arrayName", arrayName, "The array name");
std::optional<std::string> customNamespace;
app.add_option("--namespace", customNamespace, "The namespace");
CLI11_PARSE(app, argc, argv);
Bin2CPP::Generator generator(input);
if (arrayType)
{
generator.SetMapping(Bin2CPP::MappingKey::ARRAY_TYPE, arrayType.value());
}
if (arrayInclude)
{
generator.SetMapping(Bin2CPP::MappingKey::ARRAY_TYPE_INCLUDE, arrayInclude.value());
}
if (arrayName)
{
generator.SetMapping(Bin2CPP::MappingKey::ARRAY_NAME, arrayName.value());
}
if (customNamespace)
{
generator.SetMapping(Bin2CPP::MappingKey::NAMESPACE, customNamespace.value());
}
if (generator.Generate())
{
std::ofstream out(output);
if (!out)
{
BIN2CPP_LOG_ERROR("Failed to open '{}'", output);
return 1;
}
out << generator.Get();
out.close();
}
BIN2CPP_LOG_INFO("'{}' Generated !", output);
return 0;
}

View File

@@ -36,9 +36,9 @@ target_link_libraries(${PROJECT_NAME}
Bin2CPPCompileAndLinkFlags
PUBLIC
$<IF:$<BOOL:${ASAN}>,mimalloc-asan,mimalloc-static>
EASTL::EASTL
quill::quill
$<$<CONFIG:Debug,RelWithDebInfo>:cpptrace::cpptrace>)
$<$<CONFIG:Debug,RelWithDebInfo>:cpptrace::cpptrace>
magic_enum::magic_enum)
target_compile_definitions(${PROJECT_NAME}
PUBLIC

View File

@@ -1,20 +0,0 @@
/**
* Auto-generated header from: {{FILENAME}}
* Generated by Bin2CPP
*
* DO NOT TOUCH
*/
#ifndef {{GUARD_NAME}}
#define {{GUARD_NAME}}
#include {{ARRAY_TYPE_INCLUDE}}
#include <cstddef>
namespace {{NAMESPACE}} {
inline constexpr {{ARRAY_TYPE}}<std::byte, {{ARRAY_SIZE}}> {{ARRAY_NAME}} = {
{{DATA}}
};
} // namespace {{NAMESPACE}}
#endif // {{GUARD_NAME}}

View File

@@ -6,7 +6,275 @@
*********************************************************************/
#include <Generator.hpp>
#include <Assert.hpp>
#include <GeneratedTemplate.hpp>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <regex>
#include <utility>
#include <vector>
namespace Bin2CPP
{
Generator::Generator(const std::string_view p_inputFile):
m_inputFile(p_inputFile)
{
}
/****************************************************************************************/
void Generator::SetMapping(const MappingKey p_key, const std::string_view p_value)
{
const std::optional<std::size_t> index = magic_enum::enum_index(p_key);
BIN2CPP_CRITICAL_ASSERT(index.has_value(), "Incorrect mapping key! Got {}", magic_enum::enum_integer(p_key));
m_mappingTable[index.value()] = p_value;
}
/****************************************************************************************/
bool Generator::Generate()
{
if (!ComputeMappings())
{
BIN2CPP_LOG_ERROR("Invalid mappings! Can't generate file!");
return false;
}
m_result = g_generatedTemplate;
for (std::uint32_t i = 0; i < m_mappingTable.size(); ++i)
{
const std::string KEY = magic_enum::enum_name(magic_enum::enum_value<MappingKey>(i)).data();
const std::string& VALUE = m_mappingTable[i];
const std::string pattern = "\\{\\{" + KEY + "\\}\\}";
std::regex placeholderRegex(pattern);
m_result = std::regex_replace(m_result, placeholderRegex, VALUE);
}
return Validate();
}
/****************************************************************************************/
std::string_view Generator::Get() const
{
return m_result;
}
/****************************************************************************************/
bool Generator::ComputeMappings()
{
std::filesystem::path file {m_inputFile.data()};
if (!std::filesystem::exists(file))
{
BIN2CPP_LOG_ERROR("File '{}' does not exists!", m_inputFile);
return false;
}
if (std::filesystem::is_directory(file))
{
BIN2CPP_LOG_ERROR("'{}' is not a file!", m_inputFile);
return false;
}
if (!ComputeFilename())
{
BIN2CPP_LOG_ERROR("Failed to compute filename");
return false;
}
if (!ComputeGuardName())
{
BIN2CPP_LOG_ERROR("Failed to compute GuardName");
return false;
}
if (!ComputeArrayTypeInclude())
{
BIN2CPP_LOG_ERROR("Failed to compute ArrayTypeInclude");
return false;
}
if (!ComputeArrayType())
{
BIN2CPP_LOG_ERROR("Failed to compute ArrayType");
return false;
}
if (!ComputeArrayName())
{
BIN2CPP_LOG_ERROR("Failed to compute ArrayName");
return false;
}
std::ifstream in {m_inputFile.data(), std::ios::binary};
if (!in)
{
BIN2CPP_LOG_ERROR("Failed to open '{}'", m_inputFile);
return false;
}
in.seekg(0, std::ios::end);
const size_t fileSize = static_cast<size_t>(in.tellg());
in.seekg(0, std::ios::beg);
std::vector<std::byte> data(fileSize);
in.read(std::bit_cast<char*>(data.data()), fileSize);
if (!ComputeArraySize(data))
{
BIN2CPP_LOG_ERROR("Failed to compute ArraySize");
return false;
}
if (!ComputeData(data))
{
BIN2CPP_LOG_ERROR("Failed to compute Data");
return false;
}
return true;
}
/****************************************************************************************/
bool Generator::ComputeFilename()
{
std::filesystem::path file {m_inputFile.data()};
m_mappingTable[magic_enum::enum_index(MappingKey::FILENAME).value()] = file.filename().string().c_str();
return true;
}
/****************************************************************************************/
bool Generator::ComputeGuardName()
{
std::filesystem::path file {m_inputFile.data()};
std::string guardName = file.filename().string();
std::transform(guardName.begin(),
guardName.end(),
guardName.begin(),
[](const char c)
{
if (!std::isalnum(c))
{
return static_cast<int>('_');
}
else
{
return std::toupper(c);
}
});
m_mappingTable[magic_enum::enum_index(MappingKey::GUARD_NAME).value()] = std::format("{}_HPP", guardName);
return true;
}
/****************************************************************************************/
bool Generator::ComputeArrayTypeInclude()
{
std::string& value = m_mappingTable[magic_enum::enum_index(MappingKey::ARRAY_TYPE_INCLUDE).value()];
if (value.empty())
{
value = "<array>";
}
return true;
}
/****************************************************************************************/
bool Generator::ComputeArrayType()
{
std::string& value = m_mappingTable[magic_enum::enum_index(MappingKey::ARRAY_TYPE).value()];
if (value.empty())
{
value = "std::array";
}
return true;
}
/****************************************************************************************/
bool Generator::ComputeArrayName()
{
std::string& value = m_mappingTable[magic_enum::enum_index(MappingKey::ARRAY_NAME).value()];
if (value.empty())
{
std::filesystem::path file {m_inputFile.data()};
value = std::format("g_{}", file.filename().string());
std::transform(value.begin(),
value.end(),
value.begin(),
[](const char c)
{
if (!std::isalnum(c))
{
return static_cast<int>('_');
}
else
{
return static_cast<int>(c);
}
});
}
return true;
}
/****************************************************************************************/
bool Generator::ComputeArraySize(const std::span<std::byte> p_data)
{
m_mappingTable[magic_enum::enum_index(MappingKey::ARRAY_SIZE).value()] = std::to_string(p_data.size());
return true;
}
/****************************************************************************************/
bool Generator::ComputeData(const std::span<std::byte> p_data)
{
std::string& value = m_mappingTable[magic_enum::enum_index(MappingKey::DATA).value()];
value.clear();
constexpr std::size_t bytesPerLine = 5;
constexpr std::string_view linePrefix = "\n ";
for (std::size_t i = 0; i < p_data.size(); ++i)
{
if (i > 0 && i % bytesPerLine == 0)
{
value.append(linePrefix.data(), linePrefix.size());
}
const std::string element = std::format("std::byte{{0x{:02X}}}", std::to_integer<std::uint8_t>(p_data[i]));
value.append(element.data(), element.size());
if (i + 1 < p_data.size())
{
value.append(", ");
}
}
return true;
}
/****************************************************************************************/
bool Generator::Validate()
{
std::regex unreplacedRegex(R"(\{\{[A-Z_]+\}\})");
std::smatch match;
if (std::regex_search(m_result, match, unreplacedRegex))
{
BIN2CPP_LOG_ERROR("Warning: Unreplaced placeholder found: {}", match.str());
return false;
}
return true;
}
} // namespace Bin2CPP

View File

@@ -1,87 +0,0 @@
/*********************************************************************
* \file EASTLFormatters.hpp
*
* \author Romain BOULLARD
* \date February 2026
*********************************************************************/
#ifndef BIN2CPP_EASTLFORMATTERS_HPP
#define BIN2CPP_EASTLFORMATTERS_HPP
#include <quill/DeferredFormatCodec.h>
#include <format>
#include <EASTL/string.h>
#include <EASTL/string_view.h>
// STRING
template<>
struct std::formatter<eastl::string>
{
constexpr auto parse(std::format_parse_context& ctx)
{
return ctx.begin();
}
template<typename FormatContext>
auto format(const eastl::string& p_string, FormatContext& ctx) const
{
return std::format_to(ctx.out(), "{}", p_string.c_str());
}
};
template<>
struct fmtquill::formatter<eastl::string>
{
constexpr auto parse(format_parse_context& ctx)
{
return ctx.begin();
}
auto format(const eastl::string& p_string, format_context& ctx) const
{
return fmtquill::format_to(ctx.out(), "{}", p_string.c_str());
}
};
template<>
struct quill::Codec<eastl::string>: quill::DeferredFormatCodec<eastl::string>
{
};
// STRING_VIEW
template<>
struct std::formatter<eastl::string_view>
{
constexpr auto parse(std::format_parse_context& ctx)
{
return ctx.begin();
}
template<typename FormatContext>
auto format(const eastl::string_view& p_stringView, FormatContext& ctx) const
{
return std::format_to(ctx.out(), "{}", p_stringView.data());
}
};
template<>
struct fmtquill::formatter<eastl::string_view>
{
constexpr auto parse(format_parse_context& ctx)
{
return ctx.begin();
}
auto format(const eastl::string_view& p_stringView, format_context& ctx) const
{
return fmtquill::format_to(ctx.out(), "{}", p_stringView.data());
}
};
template<>
struct quill::Codec<eastl::string_view>: quill::DeferredFormatCodec<eastl::string_view>
{
};
#endif

View File

@@ -0,0 +1,36 @@
/*********************************************************************
* \file GeneratedTemplate.hpp
*
* \author Romain BOULLARD
* \date March 2026
*********************************************************************/
#ifndef BIN2CPP_GENERATED_TEMPLATE_HPP
#define BIN2CPP_GENERATED_TEMPLATE_HPP
#include <string_view>
namespace Bin2CPP
{
constexpr std::string_view g_generatedTemplate = R"(/**
* Auto-generated header from: {{FILENAME}}
* Generated by Bin2CPP
*
* DO NOT TOUCH
*/
#ifndef {{GUARD_NAME}}
#define {{GUARD_NAME}}
#include {{ARRAY_TYPE_INCLUDE}}
#include <cstddef>
namespace {{NAMESPACE}}
{
inline constexpr {{ARRAY_TYPE}}<std::byte, {{ARRAY_SIZE}}> {{ARRAY_NAME}} = {
{{DATA}}
};
} // namespace {{NAMESPACE}}
#endif // {{GUARD_NAME}}
)";
} // namespace Bin2CPP
#endif

View File

@@ -6,9 +6,152 @@
*********************************************************************/
#ifndef BIN2CPP_GENERATOR_HPP
#define BIN2CPP_GENERATOR_HPP
#include <magic_enum/magic_enum.hpp>
#include <array>
#include <span>
#include <string>
namespace Bin2CPP
{
enum class MappingKey
{
FILENAME,
GUARD_NAME,
NAMESPACE,
ARRAY_TYPE_INCLUDE,
ARRAY_TYPE,
ARRAY_SIZE,
ARRAY_NAME,
DATA
};
class Generator
{
public:
/**
* Constructor
*
* \param p_inputFile the Input file
*/
Generator(const std::string_view p_inputFile);
Generator(const Generator& p_generator) = default;
Generator(Generator&& p_generator) = default;
/**
* Set a mapping
*
*/
void SetMapping(const MappingKey p_key, const std::string_view p_value);
/**
* Generate
*
* \return True if successful, false otherwise
*/
[[nodiscard]]
bool Generate();
/**
* Get
*
*/
[[nodiscard]]
std::string_view Get() const;
~Generator() = default;
Generator& operator=(const Generator& p_generator) = default;
Generator& operator=(Generator&& p_generator) = default;
private:
/**
* Compute the mappings
*
* \return True if all mappings have valid values, false otherwise
*/
[[nodiscard]]
bool ComputeMappings();
/**
* Compute the filename mapping
*
* \return True if success, false otherwise
*/
[[nodiscard]]
bool ComputeFilename();
/**
* Compute the guardName mapping
*
* \return True if success, false otherwise
*/
[[nodiscard]]
bool ComputeGuardName();
/**
* Compute the arrayTypeInclude mapping
*
* \return True if success, false otherwise
*/
[[nodiscard]]
bool ComputeArrayTypeInclude();
/**
* Compute the arrayType mapping
*
* \return True if success, false otherwise
*/
[[nodiscard]]
bool ComputeArrayType();
/**
* Compute the arrayName mapping
*
* \return True if success, false otherwise
*/
[[nodiscard]]
bool ComputeArrayName();
/**
* Compute the arraySize mapping
*
* \return True if success, false otherwise
*/
[[nodiscard]]
bool ComputeArraySize(const std::span<std::byte> p_data);
/**
* Compute the data mapping
*
* \return True if success, false otherwise
*/
[[nodiscard]]
bool ComputeData(const std::span<std::byte> p_data);
/**
* Validate generated is correct (no remaining tags)
*
* \return True if valid, false otherwise
*/
[[nodiscard]]
bool Validate();
/**
* The mapping table
*/
std::array<std::string, magic_enum::enum_count<MappingKey>()> m_mappingTable;
/**
* The input file
*/
std::string m_inputFile;
/**
* The result
*/
std::string m_result;
};
} // namespace Bin2CPP
#endif

View File

@@ -6,10 +6,9 @@
*********************************************************************/
#ifndef BIN2CPP_LOG_HPP
#define BIN2CPP_LOG_HPP
#include <EASTLFormatters.hpp>
#include <Singleton.hpp>
#include <EASTL/array.h>
#include <array>
#ifdef BIN2CPP_WINDOWS
#pragma warning(disable: 4702)
@@ -56,7 +55,7 @@ class Log
/*
* The sinks
*/
eastl::array<std::shared_ptr<quill::Sink>, 1> m_sinks;
std::array<std::shared_ptr<quill::Sink>, 1> m_sinks;
};
} // namespace Bin2CPP

View File

@@ -6,7 +6,7 @@
*********************************************************************/
#ifndef BIN2CPP_SINGLETON_HPP
#define BIN2CPP_SINGLETON_HPP
#include <EASTL/optional.h>
#include <optional>
namespace Bin2CPP
{
@@ -98,7 +98,7 @@ class Singleton
/**
* The singleton.
*/
inline static eastl::optional<TYPE> ms_instance;
inline static std::optional<TYPE> ms_instance;
};
} // namespace Bin2CPP
#endif

View File

@@ -17,4 +17,123 @@ class GeneratorFixture: public ::testing::Test
protected:
Singleton<Log>::Lifetime m_lifetime;
};
/****************************************************************************************/
TEST_F(GeneratorFixture, GenerateDefault)
{
Generator generator("Fixture/toto.txt");
EXPECT_TRUE(generator.Generate());
constexpr std::string_view expectedResult = R"(/**
* Auto-generated header from: toto.txt
* Generated by Bin2CPP
*
* DO NOT TOUCH
*/
#ifndef TOTO_TXT_HPP
#define TOTO_TXT_HPP
#include <array>
#include <cstddef>
namespace
{
inline constexpr std::array<std::byte, 11> g_toto_txt = {
std::byte{0x48}, std::byte{0x65}, std::byte{0x6C}, std::byte{0x6C}, std::byte{0x6F},
std::byte{0x20}, std::byte{0x57}, std::byte{0x6F}, std::byte{0x72}, std::byte{0x6C},
std::byte{0x64}
};
} // namespace
#endif // TOTO_TXT_HPP
)";
EXPECT_STREQ(expectedResult.data(), generator.Get().data());
}
/****************************************************************************************/
TEST_F(GeneratorFixture, GenerateNamespace)
{
Generator generator("Fixture/toto.txt");
generator.SetMapping(MappingKey::NAMESPACE, "Test");
EXPECT_TRUE(generator.Generate());
constexpr std::string_view expectedResult = R"(/**
* Auto-generated header from: toto.txt
* Generated by Bin2CPP
*
* DO NOT TOUCH
*/
#ifndef TOTO_TXT_HPP
#define TOTO_TXT_HPP
#include <array>
#include <cstddef>
namespace Test
{
inline constexpr std::array<std::byte, 11> g_toto_txt = {
std::byte{0x48}, std::byte{0x65}, std::byte{0x6C}, std::byte{0x6C}, std::byte{0x6F},
std::byte{0x20}, std::byte{0x57}, std::byte{0x6F}, std::byte{0x72}, std::byte{0x6C},
std::byte{0x64}
};
} // namespace Test
#endif // TOTO_TXT_HPP
)";
EXPECT_STREQ(expectedResult.data(), generator.Get().data());
}
/****************************************************************************************/
TEST_F(GeneratorFixture, GenerateCustomArray)
{
Generator generator("Fixture/toto.txt");
generator.SetMapping(MappingKey::ARRAY_TYPE, "eastl::array");
generator.SetMapping(MappingKey::ARRAY_TYPE_INCLUDE, "<EASTL/array.h>");
generator.SetMapping(MappingKey::ARRAY_NAME, "g_myArray");
EXPECT_TRUE(generator.Generate());
constexpr std::string_view expectedResult = R"(/**
* Auto-generated header from: toto.txt
* Generated by Bin2CPP
*
* DO NOT TOUCH
*/
#ifndef TOTO_TXT_HPP
#define TOTO_TXT_HPP
#include <EASTL/array.h>
#include <cstddef>
namespace
{
inline constexpr eastl::array<std::byte, 11> g_myArray = {
std::byte{0x48}, std::byte{0x65}, std::byte{0x6C}, std::byte{0x6C}, std::byte{0x6F},
std::byte{0x20}, std::byte{0x57}, std::byte{0x6F}, std::byte{0x72}, std::byte{0x6C},
std::byte{0x64}
};
} // namespace
#endif // TOTO_TXT_HPP
)";
EXPECT_STREQ(expectedResult.data(), generator.Get().data());
}
/****************************************************************************************/
TEST_F(GeneratorFixture, NonExistentFile)
{
Generator generator("Fixture/tata.txt");
EXPECT_FALSE(generator.Generate());
}
/****************************************************************************************/
TEST_F(GeneratorFixture, Directory)
{
Generator generator("Fixture/");
EXPECT_FALSE(generator.Generate());
}
} // namespace Bin2CPP

View File

@@ -5,7 +5,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
find_package(Threads REQUIRED)
endif()
find_package(EASTL REQUIRED)
find_package(magic_enum REQUIRED)
find_package(CLI11 REQUIRED)
find_package(quill REQUIRED)

View File

@@ -15,7 +15,7 @@ tools.build:exelinkflags=["/LTCG", "/INCREMENTAL:NO"]
tools.build:sharedlinkflags=["/LTCG", "/INCREMENTAL:NO"]
tools.build:cflags=["/Zc:preprocessor", "/Zc:__STDC__", "/D_CRT_DECLARE_NONSTDC_NAMES=1", "/GL"]
tools.build:cxxflags=["/Zc:preprocessor", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope", "/Zc:strictStrings", "/Zc:rvalueCast", "/Zc:hiddenFriend", "/Zc:externConstexpr", "/Zc:ternary", "/GL"]
tools.build:cxxflags=["/Zc:preprocessor", "/permissive-", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope", "/Zc:throwingNew"]
[tool_requires]
!cmake/*: cmake/[>=4.2]

View File

@@ -12,8 +12,7 @@ build_type=Debug
tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/msvc_ccache.cmake
tools.build:cflags=["/Zc:preprocessor", "/Zc:__STDC__", "/D_CRT_DECLARE_NONSTDC_NAMES=1"]
tools.build:cxxflags=["/Zc:preprocessor", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope", "/Zc:strictStrings", "/Zc:rvalueCast", "/Zc:hiddenFriend", "/Zc:externConstexpr", "/Zc:ternary"]
tools.build:cxxflags=["/Zc:preprocessor", "/permissive-", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope", "/Zc:throwingNew"]
[tool_requires]
!cmake/*: cmake/[>=4.2]

View File

@@ -15,7 +15,7 @@ tools.build:exelinkflags=["/INCREMENTAL:NO"]
tools.build:sharedlinkflags=["/INCREMENTAL:NO"]
tools.build:cflags=["/Zc:preprocessor", "/Zc:__STDC__", "/D_CRT_DECLARE_NONSTDC_NAMES=1"]
tools.build:cxxflags=["/Zc:preprocessor", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope", "/Zc:strictStrings", "/Zc:rvalueCast", "/Zc:hiddenFriend", "/Zc:externConstexpr", "/Zc:ternary"]
tools.build:cxxflags=["/Zc:preprocessor", "/permissive-", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope", "/Zc:throwingNew"]
[tool_requires]
!cmake/*: cmake/[>=4.2]

View File

@@ -45,7 +45,7 @@ class Bin2CPP(ConanFile):
def requirements(self):
self.requires("quill/11.0.2", transitive_headers=True)
self.requires("eastl/3.27.01@bigfootdev/main", transitive_headers=True)
self.requires("magic_enum/0.9.7", transitive_headers=True)
self.requires("mimalloc/3.2.8@bigfootdev/main", transitive_headers=True)
self.requires("cli11/2.6.1@bigfootdev/main")