From 062d3e9c0093c375abae285db2749d140fb1280b Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Feb 2024 14:34:06 +0100 Subject: [PATCH] Add NormalizedView for normalized paths --- apps/openmw_test_suite/vfs/testpathutil.cpp | 58 ++++++++++++--- components/vfs/pathutil.hpp | 79 +++++++++++++++++++++ 2 files changed, 128 insertions(+), 9 deletions(-) diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp index 811b2b3691..23a4d46d12 100644 --- a/apps/openmw_test_suite/vfs/testpathutil.cpp +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -37,6 +37,13 @@ namespace VFS::Path EXPECT_EQ(value.view(), "foo/bar/baz"); } + TEST(NormalizedTest, shouldSupportConstructorFromNormalizedView) + { + const NormalizedView view = "foo/bar/baz"; + const Normalized value(view); + EXPECT_EQ(value.view(), "foo/bar/baz"); + } + TEST(NormalizedTest, supportMovingValueOut) { Normalized value("Foo\\Bar/baz"); @@ -67,9 +74,11 @@ namespace VFS::Path TYPED_TEST_P(NormalizedOperatorsTest, supportsEqual) { - const Normalized normalized("a/foo/bar/baz"); - const TypeParam otherEqual{ "a/foo/bar/baz" }; - const TypeParam otherNotEqual{ "b/foo/bar/baz" }; + using Type0 = typename TypeParam::Type0; + using Type1 = typename TypeParam::Type1; + const Type0 normalized{ "a/foo/bar/baz" }; + const Type1 otherEqual{ "a/foo/bar/baz" }; + const Type1 otherNotEqual{ "b/foo/bar/baz" }; EXPECT_EQ(normalized, otherEqual); EXPECT_EQ(otherEqual, normalized); EXPECT_NE(normalized, otherNotEqual); @@ -78,10 +87,12 @@ namespace VFS::Path TYPED_TEST_P(NormalizedOperatorsTest, supportsLess) { - const Normalized normalized("b/foo/bar/baz"); - const TypeParam otherEqual{ "b/foo/bar/baz" }; - const TypeParam otherLess{ "a/foo/bar/baz" }; - const TypeParam otherGreater{ "c/foo/bar/baz" }; + using Type0 = typename TypeParam::Type0; + using Type1 = typename TypeParam::Type1; + const Type0 normalized{ "b/foo/bar/baz" }; + const Type1 otherEqual{ "b/foo/bar/baz" }; + const Type1 otherLess{ "a/foo/bar/baz" }; + const Type1 otherGreater{ "c/foo/bar/baz" }; EXPECT_FALSE(normalized < otherEqual); EXPECT_FALSE(otherEqual < normalized); EXPECT_LT(otherLess, normalized); @@ -92,8 +103,37 @@ namespace VFS::Path REGISTER_TYPED_TEST_SUITE_P(NormalizedOperatorsTest, supportsEqual, supportsLess); - using StringTypes = Types; + template + struct TypePair + { + using Type0 = T0; + using Type1 = T1; + }; + + using TypePairs = Types, TypePair, + TypePair, TypePair, + TypePair, TypePair, + TypePair, TypePair, + TypePair, TypePair>; + + INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, TypePairs); - INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, StringTypes); + TEST(NormalizedViewTest, shouldSupportConstructorFromNormalized) + { + const Normalized value("Foo\\Bar/baz"); + const NormalizedView view(value); + EXPECT_EQ(view.value(), "foo/bar/baz"); + } + + TEST(NormalizedViewTest, shouldSupportConstexprConstructorFromNormalizedStringLiteral) + { + constexpr NormalizedView view("foo/bar/baz"); + EXPECT_EQ(view.value(), "foo/bar/baz"); + } + + TEST(NormalizedViewTest, constructorShouldThrowExceptionOnNotNormalized) + { + EXPECT_THROW([] { NormalizedView("Foo\\Bar/baz"); }(), std::invalid_argument); + } } } diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 6ee33f64d2..aa7cad8524 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -58,6 +59,59 @@ namespace VFS::Path bool operator()(std::string_view left, std::string_view right) const { return pathLess(left, right); } }; + class Normalized; + + class NormalizedView + { + public: + constexpr NormalizedView() noexcept = default; + + constexpr NormalizedView(const char* value) + : mValue(value) + { + if (!isNormalized(mValue)) + throw std::invalid_argument("NormalizedView value is not normalized: \"" + std::string(mValue) + "\""); + } + + NormalizedView(const Normalized& value) noexcept; + + constexpr std::string_view value() const noexcept { return mValue; } + + friend constexpr bool operator==(const NormalizedView& lhs, const NormalizedView& rhs) = default; + + friend constexpr bool operator==(const NormalizedView& lhs, const auto& rhs) { return lhs.mValue == rhs; } + +#if defined(_MSC_VER) && _MSC_VER <= 1935 + friend constexpr bool operator==(const auto& lhs, const NormalizedView& rhs) + { + return lhs == rhs.mValue; + } +#endif + + friend constexpr bool operator<(const NormalizedView& lhs, const NormalizedView& rhs) + { + return lhs.mValue < rhs.mValue; + } + + friend constexpr bool operator<(const NormalizedView& lhs, const auto& rhs) + { + return lhs.mValue < rhs; + } + + friend constexpr bool operator<(const auto& lhs, const NormalizedView& rhs) + { + return lhs < rhs.mValue; + } + + friend std::ostream& operator<<(std::ostream& stream, const NormalizedView& value) + { + return stream << value.mValue; + } + + private: + std::string_view mValue; + }; + class Normalized { public: @@ -84,6 +138,11 @@ namespace VFS::Path normalizeFilenameInPlace(mValue); } + explicit Normalized(NormalizedView value) + : mValue(value.value()) + { + } + const std::string& value() const& { return mValue; } std::string value() && { return std::move(mValue); } @@ -105,6 +164,11 @@ namespace VFS::Path } #endif + friend bool operator==(const Normalized& lhs, const NormalizedView& rhs) + { + return lhs.mValue == rhs.value(); + } + friend bool operator<(const Normalized& lhs, const Normalized& rhs) { return lhs.mValue < rhs.mValue; @@ -120,6 +184,16 @@ namespace VFS::Path return lhs < rhs.mValue; } + friend bool operator<(const Normalized& lhs, const NormalizedView& rhs) + { + return lhs.mValue < rhs.value(); + } + + friend bool operator<(const NormalizedView& lhs, const Normalized& rhs) + { + return lhs.value() < rhs.mValue; + } + friend std::ostream& operator<<(std::ostream& stream, const Normalized& value) { return stream << value.mValue; @@ -128,6 +202,11 @@ namespace VFS::Path private: std::string mValue; }; + + inline NormalizedView::NormalizedView(const Normalized& value) noexcept + : mValue(value.view()) + { + } } #endif