1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2026-01-05 21:43:07 +00:00

Remove duplicated and leading slashes in normalizeFilenameInPlace

To be consistent with correctResourcePath.
This commit is contained in:
elsid 2025-09-07 17:42:48 +02:00
parent 96869fd9b9
commit 1b362140ae
No known key found for this signature in database
GPG key ID: B845CB9FEE18AB40
3 changed files with 99 additions and 20 deletions

View file

@ -10,6 +10,49 @@ namespace VFS::Path
{
using namespace testing;
struct VFSPathIsNormalizedTest : TestWithParam<std::pair<std::string_view, bool>>
{
};
TEST_P(VFSPathIsNormalizedTest, shouldReturnExpectedResult)
{
EXPECT_EQ(isNormalized(GetParam().first), GetParam().second);
}
const std::pair<std::string_view, bool> isNormalizedTestParams[] = {
{ std::string_view(), true },
{ "foo", true },
{ "foo/bar", true },
{ "foo/bar/baz", true },
{ "/foo", false },
{ "foo//", false },
{ "foo\\", false },
{ "Foo", false },
};
INSTANTIATE_TEST_SUITE_P(IsNormalizedTestParams, VFSPathIsNormalizedTest, ValuesIn(isNormalizedTestParams));
TEST(VFSPathNormalizeFilenameInPlaceTest, shouldRemoveLeadingSeparators)
{
std::string value("//foo");
normalizeFilenameInPlace(value);
EXPECT_EQ(value, "foo");
}
TEST(VFSPathNormalizeFilenameInPlaceTest, shouldRemoveDuplicatedSeparators)
{
std::string value("foo//bar///baz");
normalizeFilenameInPlace(value);
EXPECT_EQ(value, "foo/bar/baz");
}
TEST(VFSPathNormalizeFilenameInPlaceTest, shouldRemoveDuplicatedLeadingSeparator)
{
std::string value("//foo");
normalizeFilenameInPlace(value);
EXPECT_EQ(value, "foo");
}
TEST(VFSPathNormalizedTest, shouldSupportDefaultConstructor)
{
const Normalized value;
@ -79,6 +122,13 @@ namespace VFS::Path
EXPECT_EQ(value.value(), "foo/bar/baz");
}
TEST(VFSPathNormalizedTest, operatorDivShouldNormalizeSuffix)
{
Normalized value("foo/bar");
value /= std::string_view("\\A\\\\B");
EXPECT_EQ(value.value(), "foo/bar/a/b");
}
TEST(VFSPathNormalizedTest, changeExtensionShouldReplaceAfterLastDot)
{
Normalized value("foo/bar.a");
@ -86,11 +136,10 @@ namespace VFS::Path
EXPECT_EQ(value.value(), "foo/bar.so");
}
TEST(VFSPathNormalizedTest, changeExtensionShouldNormalizeExtension)
TEST(VFSPathNormalizedTest, changeExtensionShouldThrowExceptionOnNotNormalizedExtension)
{
Normalized value("foo/bar.a");
ASSERT_TRUE(value.changeExtension("SO"));
EXPECT_EQ(value.value(), "foo/bar.so");
EXPECT_THROW(value.changeExtension("\\SO"), std::invalid_argument);
}
TEST(VFSPathNormalizedTest, changeExtensionShouldIgnorePathWithoutADot)
@ -116,7 +165,7 @@ namespace VFS::Path
TEST(VFSPathNormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithSeparator)
{
Normalized value("foo.a");
EXPECT_THROW(value.changeExtension("/so"), std::invalid_argument);
EXPECT_THROW(value.changeExtension("so/"), std::invalid_argument);
}
template <class T>

View file

@ -1,6 +1,5 @@
#include "resourcehelpers.hpp"
#include <algorithm>
#include <sstream>
#include <string_view>
@ -39,14 +38,6 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::span<const std::stri
{
std::string correctedPath = VFS::Path::normalizeFilename(resPath);
// Flatten slashes
auto bothSeparators = [](char a, char b) { return a == VFS::Path::separator && b == VFS::Path::separator; };
correctedPath.erase(std::unique(correctedPath.begin(), correctedPath.end(), bothSeparators), correctedPath.end());
// Remove leading separator
if (!correctedPath.empty() && correctedPath[0] == VFS::Path::separator)
correctedPath.erase(0, 1);
// Handle top level directory
bool needsPrefix = true;
for (std::string_view potentialTopLevelDirectory : topLevelDirectories)

View file

@ -14,24 +14,59 @@ namespace VFS::Path
inline constexpr char separator = '/';
inline constexpr char extensionSeparator = '.';
inline constexpr char normalize(char c)
[[nodiscard]] inline constexpr char normalize(char c)
{
return c == '\\' ? separator : Misc::StringUtils::toLower(c);
}
inline constexpr bool isNormalized(std::string_view name)
[[nodiscard]] inline constexpr bool isNormalized(std::string_view name)
{
return std::all_of(name.begin(), name.end(), [](char v) { return v == normalize(v); });
if (name.empty())
return true;
if (name.front() != normalize(name.front()))
return false;
if (name.front() == separator)
return false;
for (std::size_t i = 1, n = name.size(); i < n; ++i)
{
if (name[i] != normalize(name[i]))
return false;
if (name[i] == separator && name[i - 1] == name[i])
return false;
}
return true;
}
inline void normalizeFilenameInPlace(auto begin, auto end)
[[nodiscard]] inline auto removeDuplicatedSeparators(auto begin, auto end)
{
return std::unique(begin, end, [](char a, char b) { return a == separator && b == separator; });
}
[[nodiscard]] inline auto removeLeadingSeparator(auto begin, auto end)
{
if (begin != end && *begin == separator)
return begin + 1;
return begin;
}
[[nodiscard]] inline auto normalizeFilenameInPlace(auto begin, auto end)
{
std::transform(begin, end, begin, normalize);
end = removeDuplicatedSeparators(begin, end);
begin = removeLeadingSeparator(begin, end);
return std::pair(begin, end);
}
inline void normalizeFilenameInPlace(std::string& name)
{
normalizeFilenameInPlace(name.begin(), name.end());
const auto [begin, end] = normalizeFilenameInPlace(name.begin(), name.end());
name.erase(end, name.end());
name.erase(name.begin(), begin);
}
/// Normalize the given filename, making slashes/backslashes consistent, and lower-casing.
@ -197,6 +232,8 @@ namespace VFS::Path
bool changeExtension(std::string_view extension)
{
if (!isNormalized(extension))
throw std::invalid_argument("Not normalized extension: " + std::string(extension));
if (findSeparatorOrExtensionSeparator(extension.begin(), extension.end()) != extension.end())
throw std::invalid_argument("Invalid extension: " + std::string(extension));
const auto it = findSeparatorOrExtensionSeparator(mValue.rbegin(), mValue.rend());
@ -204,7 +241,7 @@ namespace VFS::Path
return false;
const std::string::difference_type pos = mValue.rend() - it;
mValue.replace(pos, mValue.size(), extension);
normalizeFilenameInPlace(mValue.begin() + pos, mValue.end());
std::transform(mValue.begin() + pos, mValue.end(), mValue.begin() + pos, normalize);
return true;
}
@ -230,7 +267,9 @@ namespace VFS::Path
mValue += separator;
const std::size_t offset = mValue.size();
mValue += value;
normalizeFilenameInPlace(mValue.begin() + offset, mValue.end());
const auto [begin, end] = normalizeFilenameInPlace(mValue.begin() + offset, mValue.end());
std::copy(begin, end, mValue.begin() + offset);
mValue.resize(offset + (end - begin));
return *this;
}