You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/components/vfs/pathutil.hpp

286 lines
8.3 KiB
C++

#ifndef OPENMW_COMPONENTS_VFS_PATHUTIL_H
#define OPENMW_COMPONENTS_VFS_PATHUTIL_H
#include <components/misc/strings/lower.hpp>
#include <algorithm>
#include <ostream>
#include <stdexcept>
#include <string>
#include <string_view>
namespace VFS::Path
{
inline constexpr char separator = '/';
inline constexpr char extensionSeparator = '.';
inline constexpr char normalize(char c)
{
return c == '\\' ? separator : Misc::StringUtils::toLower(c);
}
inline constexpr bool isNormalized(std::string_view name)
{
return std::all_of(name.begin(), name.end(), [](char v) { return v == normalize(v); });
}
inline void normalizeFilenameInPlace(auto begin, auto end)
{
std::transform(begin, end, begin, normalize);
}
inline void normalizeFilenameInPlace(std::string& name)
{
normalizeFilenameInPlace(name.begin(), name.end());
}
/// Normalize the given filename, making slashes/backslashes consistent, and lower-casing.
[[nodiscard]] inline std::string normalizeFilename(std::string_view name)
{
std::string out(name);
normalizeFilenameInPlace(out);
return out;
}
struct PathCharLess
{
bool operator()(char x, char y) const { return normalize(x) < normalize(y); }
};
inline bool pathLess(std::string_view x, std::string_view y)
{
return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), PathCharLess());
}
inline bool pathEqual(std::string_view x, std::string_view y)
{
if (std::size(x) != std::size(y))
return false;
return std::equal(
std::begin(x), std::end(x), std::begin(y), [](char l, char r) { return normalize(l) == normalize(r); });
}
struct PathLess
{
using is_transparent = void;
bool operator()(std::string_view left, std::string_view right) const { return pathLess(left, right); }
};
inline constexpr auto findSeparatorOrExtensionSeparator(auto begin, auto end)
{
return std::find_if(begin, end, [](char v) { return v == extensionSeparator || v == separator; });
}
class Normalized;
class NormalizedView
{
public:
constexpr NormalizedView() noexcept = default;
constexpr explicit 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:
Normalized() = default;
explicit Normalized(std::string_view value)
: mValue(normalizeFilename(value))
{
}
explicit Normalized(const char* value)
: Normalized(std::string_view(value))
{
}
Normalized(const std::string& value)
: Normalized(std::string_view(value))
{
}
explicit Normalized(std::string&& value)
: mValue(std::move(value))
{
normalizeFilenameInPlace(mValue);
}
explicit Normalized(NormalizedView value)
: mValue(value.value())
{
}
const std::string& value() const& { return mValue; }
std::string value() && { return std::move(mValue); }
std::string_view view() const { return mValue; }
operator std::string_view() const { return mValue; }
operator const std::string&() const { return mValue; }
bool changeExtension(std::string_view 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());
if (it == mValue.rend() || *it == separator)
return false;
const std::string::difference_type pos = mValue.rend() - it;
mValue.replace(pos, mValue.size(), extension);
normalizeFilenameInPlace(mValue.begin() + pos, mValue.end());
return true;
}
Normalized& operator=(NormalizedView value)
{
mValue = value.value();
return *this;
}
Normalized& operator/=(NormalizedView value)
{
mValue.reserve(mValue.size() + value.value().size() + 1);
mValue += separator;
mValue += value.value();
return *this;
}
Normalized& operator/=(std::string_view value)
{
mValue.reserve(mValue.size() + value.size() + 1);
mValue += separator;
const std::size_t offset = mValue.size();
mValue += value;
normalizeFilenameInPlace(mValue.begin() + offset, mValue.end());
return *this;
}
friend bool operator==(const Normalized& lhs, const Normalized& rhs) = default;
friend bool operator==(const Normalized& lhs, const auto& rhs) { return lhs.mValue == rhs; }
#if defined(_MSC_VER) && _MSC_VER <= 1935
friend bool operator==(const auto& lhs, const Normalized& rhs)
{
return lhs == rhs.mValue;
}
#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;
}
friend bool operator<(const Normalized& lhs, const auto& rhs)
{
return lhs.mValue < rhs;
}
friend bool operator<(const auto& lhs, const Normalized& rhs)
{
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;
}
private:
std::string mValue;
};
inline NormalizedView::NormalizedView(const Normalized& value) noexcept
: mValue(value.view())
{
}
inline Normalized operator/(NormalizedView lhs, NormalizedView rhs)
{
Normalized result(lhs);
result /= rhs;
return result;
}
struct Hash
{
using is_transparent = void;
[[nodiscard]] std::size_t operator()(std::string_view sv) const { return std::hash<std::string_view>{}(sv); }
[[nodiscard]] std::size_t operator()(const std::string& s) const { return std::hash<std::string>{}(s); }
[[nodiscard]] std::size_t operator()(const Normalized& s) const { return std::hash<std::string>{}(s.value()); }
[[nodiscard]] std::size_t operator()(NormalizedView s) const
{
return std::hash<std::string_view>{}(s.value());
}
};
}
#endif