feat: add hex

This commit is contained in:
2026-01-29 23:22:17 +01:00
parent 110d1df50d
commit f49802a1a6
4 changed files with 179 additions and 0 deletions

View File

@@ -16,6 +16,7 @@ set_target_properties(cpputils PROPERTIES
target_sources(cpputils target_sources(cpputils
PRIVATE PRIVATE
src/hex.cpp
src/uuid.cpp src/uuid.cpp
) )
@@ -28,6 +29,7 @@ target_compile_features(cpputils PUBLIC cxx_std_17)
if(BUILD_TESTS) if(BUILD_TESTS)
add_executable(cpputils_tests add_executable(cpputils_tests
tests/test_main.cpp tests/test_main.cpp
tests/test_hex.cpp
tests/test_uuid.cpp) tests/test_uuid.cpp)
target_include_directories(cpputils_tests PRIVATE target_include_directories(cpputils_tests PRIVATE

58
include/cpputils/hex.h Normal file
View File

@@ -0,0 +1,58 @@
#pragma once
#include <string>
namespace cpputils {
namespace hex {
std::string toHex(std::string const& input);
std::string fromHex(std::string const& input);
constexpr int hexVal(char c) {
switch (c) {
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
case 'c':
case 'C':
return 12;
case 'd':
case 'D':
return 13;
case 'e':
case 'E':
return 14;
case 'f':
case 'F':
return 15;
default:
return -1;
}
}
} // namespace hex
} // namespace cpputils

38
src/hex.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include <cpputils/hex.h>
#include <iomanip>
#include <ios>
#include <string>
namespace cpputils {
namespace hex {
std::string toHex(std::string const& input) {
std::stringstream ss;
for (auto c : input) {
ss << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<unsigned int>(static_cast<unsigned char>(c));
}
return ss.str();
}
std::string fromHex(std::string const& input) {
if (input.size() % 2 != 0) {
return {};
}
std::string out;
out.reserve(input.size() / 2);
for (size_t i = 0; i < input.size(); i += 2) {
int hi = hexVal(input[i]);
int lo = hexVal(input[i + 1]);
if (hi < 0 || lo < 0) {
return {};
}
out.push_back(static_cast<char>((hi << 4) | lo));
}
return out;
}
} // namespace hex
} // namespace cpputils

81
tests/test_hex.cpp Normal file
View File

@@ -0,0 +1,81 @@
#include "catch.hpp"
#include <cpputils/hex.h>
#include <string>
using namespace cpputils::hex;
TEST_CASE("Hex conversion round-trip works", "[hex]") {
std::string original = "Hello, World!";
std::string hex = toHex(original);
std::string result = fromHex(hex);
REQUIRE(result == original);
}
TEST_CASE("Empty string conversion", "[hex]") {
std::string empty = "";
std::string hex = toHex(empty);
std::string result = fromHex(hex);
REQUIRE(hex.empty());
REQUIRE(result.empty());
}
TEST_CASE("Single character conversion", "[hex]") {
std::string single = "A";
std::string hex = toHex(single);
std::string result = fromHex(hex);
REQUIRE(result == single);
}
TEST_CASE("Non-printable ASCII characters", "[hex]") {
std::string nonPrintable;
for (char c = 0; c < 32; ++c) {
nonPrintable += c;
}
std::string hex = toHex(nonPrintable);
std::string result = fromHex(hex);
REQUIRE(result == nonPrintable);
}
TEST_CASE("Extended ASCII characters", "[hex]") {
std::string extended;
for (unsigned char c = 128; c < 255; ++c) {
extended += static_cast<char>(c);
}
std::string hex = toHex(extended);
std::string result = fromHex(hex);
REQUIRE(result == extended);
}
TEST_CASE("Hex string with lowercase and uppercase letters", "[hex]") {
std::string original = "abcABC123";
std::string hex = toHex(original);
std::string result = fromHex(hex);
REQUIRE(result == original);
}
TEST_CASE("Odd-length hex string should fail", "[hex]") {
std::string invalidHex = "A";
REQUIRE(fromHex(invalidHex).empty());
}
TEST_CASE("Hex string with invalid characters should fail", "[hex]") {
std::string invalidHex = "ZZ";
REQUIRE(fromHex(invalidHex).empty());
}
TEST_CASE("Large string conversion", "[hex]") {
std::string large(10000, 'x');
std::string hex = toHex(large);
std::string result = fromHex(hex);
REQUIRE(result == large);
}