Skip to content

Commit

Permalink
Feat/Extend type formatting (#171)
Browse files Browse the repository at this point in the history
# Description
* Add enum, std::array and std::unordered_map to type formatting
* Extend concepts

## Type of change
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)

## Checklist before requesting a review
- [x] I have performed a self-review of my code.
- [x] If it is a core feature, I have added thorough tests.
- [ ] If this is a new component I have added examples.
- [ ] I updated the README and related documentation.
  • Loading branch information
lhruby authored Sep 24, 2024
1 parent 48a9be9 commit 9272bce
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 63 deletions.
78 changes: 48 additions & 30 deletions modules/types/include/hephaestus/types/type_formatting.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,66 @@

#include <fmt/chrono.h>
#include <fmt/format.h>
#include <magic_enum.hpp>
#include <range/v3/view/zip.hpp>

#include "hephaestus/utils/concepts.h"

namespace heph::types {

//=================================================================================================
// Timepoint
// Array and Vector
//=================================================================================================
// A system clock provides access to time and date.
template <typename T>
concept IsSystemClock = std::is_same_v<T, std::chrono::system_clock>;
concept ArrayOrVectorType = ArrayType<T> || VectorType<T>;

template <ArrayOrVectorType T>
[[nodiscard]] inline auto toString(const T& vec) -> std::string {
std::stringstream ss;

const auto indices = std::views::iota(0);
const auto indexed_vec = ranges::views::zip(indices, vec);

for (const auto& [index, value] : indexed_vec) {
const std::string str = fmt::format(" Index: {}, Value: {}\n", index, value);
ss << str;
}

return ss.str();
}

//=================================================================================================
// UnorderedMap
//=================================================================================================
template <UnorderedMapType T>
[[nodiscard]] inline auto toString(const T& umap) -> std::string {
std::stringstream ss;

for (const auto& [key, value] : umap) {
ss << " Key: " << key << ", Value: " << value << '\n';
}

return ss.str();
}

//=================================================================================================
// Enum
//=================================================================================================
template <EnumType T>
[[nodiscard]] inline auto toString(T value) -> std::string_view {
return magic_enum::enum_name(value);
}

template <IsSystemClock T>
//=================================================================================================
// ChronoTimePoint
//=================================================================================================

template <ChronoSystemClockType T>
[[nodiscard]] inline auto toString(const std::chrono::time_point<T>& timestamp) -> std::string {
return fmt::format("{:%Y-%m-%d %H:%M:%S}", timestamp);
}

// A steady clock does not have an anchor point in calendar time. It is only useful for measuring relative
// time intervals.
template <typename T>
concept IsSteadyClock = std::is_same_v<T, std::chrono::steady_clock>;

template <IsSteadyClock T>
template <ChronoSteadyClockType T>
[[nodiscard]] inline auto toString(const std::chrono::time_point<T>& timestamp) -> std::string {
auto duration_since_epoch = timestamp.time_since_epoch();
auto total_seconds = std::chrono::duration_cast<std::chrono::seconds>(duration_since_epoch).count();
Expand All @@ -48,24 +86,4 @@ template <IsSteadyClock T>
return fmt::format("{}d {:02}h:{:02}m:{:02}.{:09}s", days, hours, minutes, seconds, sub_seconds * SCALER);
}

//=================================================================================================
// Vector
//=================================================================================================
template <typename T>
concept IsVector = requires(T t) { std::is_same_v<T, std::vector<typename T::value_type>>; };

template <IsVector T>
[[nodiscard]] inline auto toString(const T& vec) -> std::string {
std::stringstream ss;

const auto indices = std::views::iota(0);
const auto indexed_vec = ranges::views::zip(indices, vec);

for (const auto& [index, value] : indexed_vec) {
const std::string str = fmt::format(" Index: {}, Value: {}\n", index, value);
ss << str;
}

return ss.str();
}
}; // namespace heph::types
147 changes: 119 additions & 28 deletions modules/types/tests/type_formatting_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
//=================================================================================================

#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>

#include <fmt/core.h>
Expand All @@ -16,40 +19,45 @@ using namespace ::testing; // NOLINT(google-build-using-namespace)

namespace heph::types::tests {

// Test assumes sub-second precision of at most nanoseconds.
TEST(TypeFormattingTests, TimestampFormattingSteadyClock) {
const auto timestamp = std::chrono::steady_clock::now();
const auto str = fmt::format("{}", toString(timestamp));

ASSERT_LE(str.length(), 24);
//=================================================================================================
// Array
//=================================================================================================

const auto it = std::find(str.begin(), str.end(), 'd');
ASSERT_TRUE(it != str.end());
ASSERT_EQ(*(it + 1), ' ');
ASSERT_EQ(*(it + 4), 'h');
ASSERT_EQ(*(it + 5), ':');
ASSERT_EQ(*(it + 8), 'm');
ASSERT_EQ(*(it + 9), ':');
ASSERT_EQ(*(it + 12), '.');
ASSERT_EQ(str.back(), 's');
TEST(TypeFormattingTests, ConvertEmptyArray) {
const std::array<int, 0> arr = {};
const std::string result = toString(arr);
EXPECT_EQ(result, "");
}

// Test assumes sub-second precision of at most nanoseconds.
TEST(TypeFormattingTests, TimestampFormattingSystemClock) {
const auto timestamp = std::chrono::system_clock::now();
const auto str = fmt::format("{}", toString(timestamp));
TEST(TypeFormattingTests, ConvertIntArray) {
const std::array<int, 3> arr = { 1, 2, 3 };
const std::string result = toString(arr);
const std::string expected = " Index: 0, Value: 1\n"
" Index: 1, Value: 2\n"
" Index: 2, Value: 3\n";
EXPECT_EQ(result, expected);
}

ASSERT_TRUE(str.length() <= 27);
TEST(TypeFormattingTests, ConvertDoubleArray) {
const std::array<double, 3> arr = { 1.1, 2.2, 3.3 }; // NOLINT(cppcoreguidelines-avoid-magic-numbers)
const std::string result = toString(arr);
const std::string expected = " Index: 0, Value: 1.1\n"
" Index: 1, Value: 2.2\n"
" Index: 2, Value: 3.3\n";
EXPECT_EQ(result, expected);
}

ASSERT_EQ(str[0], '2');
ASSERT_EQ(str[1], '0');
ASSERT_EQ(str[4], '-');
ASSERT_EQ(str[7], '-');
ASSERT_EQ(str[10], ' ');
ASSERT_EQ(str[13], ':');
ASSERT_EQ(str[16], ':');
ASSERT_EQ(str[19], '.');
TEST(TypeFormattingTests, ConvertStringArray) {
const std::array<std::string, 3> arr = { "one", "two", "three" };
const std::string result = toString(arr);
const std::string expected = " Index: 0, Value: one\n"
" Index: 1, Value: two\n"
" Index: 2, Value: three\n";
EXPECT_EQ(result, expected);
}
//=================================================================================================
// Vector
//=================================================================================================

TEST(TypeFormattingTests, ConvertEmptyVector) {
const std::vector<int> vec;
Expand Down Expand Up @@ -84,4 +92,87 @@ TEST(TypeFormattingTests, ConvertStringVector) {
EXPECT_EQ(result, expected);
}

//=================================================================================================
// UnorderedMap
//=================================================================================================

TEST(UnorderedMapTests, ToStringEmpty) {
const std::unordered_map<int, std::string> empty_umap;
EXPECT_EQ(toString(empty_umap), "");
}

TEST(UnorderedMapTests, ToStringNonEmpty) {
const std::unordered_map<int, std::string> umap{ { 1, "one" }, { 3, "three" }, { 2, "two" } };

const std::vector<std::string> expected_outputs = {
" Key: 1, Value: one\n Key: 2, Value: two\n Key: 3, Value: three\n",
" Key: 1, Value: one\n Key: 3, Value: three\n Key: 2, Value: two\n",
" Key: 2, Value: two\n Key: 1, Value: one\n Key: 3, Value: three\n",
" Key: 2, Value: two\n Key: 3, Value: three\n Key: 1, Value: one\n",
" Key: 3, Value: three\n Key: 1, Value: one\n Key: 2, Value: two\n",
" Key: 3, Value: three\n Key: 2, Value: two\n Key: 1, Value: one\n"
};

const auto actual_output = toString(umap);

int match_count = 0;
for (const auto& expected_output : expected_outputs) {
if (actual_output == expected_output) {
++match_count;
}
}

EXPECT_EQ(match_count, 1);
}

//=================================================================================================
// Enum
//=================================================================================================

TEST(EnumTests, ToString) {
enum class TestEnum : uint8_t { A, B, C };
EXPECT_EQ(toString(TestEnum::A), "A");
EXPECT_EQ(toString(TestEnum::B), "B");
EXPECT_EQ(toString(TestEnum::C), "C");
}

//=================================================================================================
// ChronoTimePoint
//=================================================================================================

// Test assumes sub-second precision of at most nanoseconds.
TEST(TypeFormattingTests, ChronoTimestampFormattingSteadyClock) {
const auto timestamp = std::chrono::steady_clock::now();
const auto str = fmt::format("{}", toString(timestamp));

ASSERT_LE(str.length(), 24);

const auto it = std::find(str.begin(), str.end(), 'd');
ASSERT_TRUE(it != str.end());
ASSERT_EQ(*(it + 1), ' ');
ASSERT_EQ(*(it + 4), 'h');
ASSERT_EQ(*(it + 5), ':');
ASSERT_EQ(*(it + 8), 'm');
ASSERT_EQ(*(it + 9), ':');
ASSERT_EQ(*(it + 12), '.');
ASSERT_EQ(str.back(), 's');
}

// Test assumes sub-second precision of at most nanoseconds.
TEST(TypeFormattingTests, ChronoTimestampFormattingSystemClock) {
const auto timestamp = std::chrono::system_clock::now();
const auto str = fmt::format("{}", toString(timestamp));

ASSERT_TRUE(str.length() <= 27);

ASSERT_EQ(str[0], '2');
ASSERT_EQ(str[1], '0');
ASSERT_EQ(str[4], '-');
ASSERT_EQ(str[7], '-');
ASSERT_EQ(str[10], ' ');
ASSERT_EQ(str[13], ':');
ASSERT_EQ(str[16], ':');
ASSERT_EQ(str[19], '.');
}

} // namespace heph::types::tests
4 changes: 1 addition & 3 deletions modules/types_proto/proto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
# Copyright (C) 2023-2024 HEPHAESTUS Contributors
# =================================================================================================

set(SOURCES
hephaestus/types/proto/dummy_type.proto
)
set(SOURCES hephaestus/types/proto/dummy_type.proto)

# To create proto messages in another repository that import `hephaestus` proto messages, see readme.
define_module_proto_library(NAME types_gen_proto SOURCES ${SOURCES})
34 changes: 33 additions & 1 deletion modules/utils/include/hephaestus/utils/concepts.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,39 @@
namespace heph {

template <typename T>
concept ScalarType = requires(T a) { std::is_scalar_v<T>; };
concept ScalarType = std::is_scalar_v<T>;

template <typename T>
concept EnumType = std::is_enum_v<T>;

template <typename T>
concept ArrayType = requires {
typename T::value_type;
requires std::same_as<T, std::array<typename T::value_type, T().size()>>;
};

template <typename T>
concept VectorType = requires {
typename T::value_type;
typename T::allocator_type;
requires std::same_as<T, std::vector<typename T::value_type, typename T::allocator_type>>;
};

template <typename T>
concept UnorderedMapType = requires(T t) {
typename T::key_type;
typename T::mapped_type;
typename T::hasher;
typename T::key_equal;
std::is_same_v<T, std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::hasher,
typename T::key_equal>>;
};

template <typename T>
concept ChronoSystemClockType = std::is_same_v<T, std::chrono::system_clock>;

template <typename T>
concept ChronoSteadyClockType = std::is_same_v<T, std::chrono::steady_clock>;

/// Types that are convertable to and from a string
template <typename T>
Expand Down
9 changes: 8 additions & 1 deletion modules/utils/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
# Copyright (C) 2023-2024 HEPHAESTUS Contributors
# =================================================================================================

define_module_test(
NAME concepts_tests
SOURCES concepts_tests.cpp
PUBLIC_INCLUDE_PATHS $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
PUBLIC_LINK_LIBS ""
)

define_module_test(
NAME exception_tests
SOURCES exception_tests.cpp
Expand Down Expand Up @@ -42,4 +49,4 @@ define_module_test(
SOURCES watchdog_tests.cpp
PUBLIC_INCLUDE_PATHS $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
PUBLIC_LINK_LIBS ""
)
)
Loading

0 comments on commit 9272bce

Please sign in to comment.