Files
TL/include/tl/rel_path.h
T
2024-07-10 11:52:47 +02:00

873 lines
23 KiB
C++

#pragma once
#include "tl/detail/prologue.h"
#include <cstddef>
#include "tl/rel_path_view.h"
#include "tl/path_system.h"
#include "tl/detail/path_base.h"
#include "tl/plain_crash.h"
namespace tl
{
template<typename PathSystems> class abs_path;
//////////////////////////////////////////////////////////////////////////
// relative path
template<typename PathSystems>
class rel_path : public detail::path_system::path_base<PathSystems>
{
template<typename T> friend class abs_path;
public:
using path_view_type = rel_path_view<PathSystems>;
rel_path() noexcept = default;
rel_path(rel_path_view<PathSystems> view) noexcept;
rel_path(span<const string> elements) noexcept;
explicit rel_path(const char* path) noexcept;
rel_path(const char* path, size_t size) noexcept;
template<typename Str>
explicit rel_path(const Str& path) noexcept;
rel_path(const rel_path& other) noexcept;
rel_path(rel_path&& other) noexcept;
rel_path(const rel_path& from, const rel_path& to) noexcept;
// operators
rel_path& operator=(const char* path) noexcept;
rel_path& operator=(const rel_path& other) noexcept;
rel_path& operator=(rel_path&& other) noexcept;
template<typename Str>
rel_path& operator=(const Str& path) noexcept;
rel_path& operator=(const rel_path_view<PathSystems>& path) noexcept;
rel_path operator+(const char* path) const noexcept;
rel_path operator+(const rel_path& path) const noexcept;
rel_path operator+(rel_path&& path) const noexcept;
template<typename Str>
rel_path operator+(const Str& path) const noexcept;
rel_path operator+(const rel_path_view<PathSystems>& path) const noexcept;
rel_path& operator+=(const char* path) noexcept;
rel_path& operator+=(const rel_path& path) noexcept;
rel_path& operator+=(rel_path&& path) noexcept;
template<typename Str>
rel_path& operator+=(const Str& path) noexcept;
rel_path& operator+=(const rel_path_view<PathSystems>& path) noexcept;
operator rel_path_view<PathSystems>() const noexcept;
bool operator==(const rel_path& other) const noexcept;
bool operator!=(const rel_path& other) const noexcept;
auto operator<=>(const rel_path& other) const noexcept;
bool operator==(const rel_path_view<PathSystems>& other) const noexcept;
bool operator!=(const rel_path_view<PathSystems>& other) const noexcept;
auto operator<=>(const rel_path_view<PathSystems>& other) const noexcept;
void swap(rel_path& other) noexcept;
void clear() noexcept;
template<typename FormatSystem>
string str() const noexcept;
template<typename FormatSystem>
eastl::string eastl_str() const noexcept;
template<typename FormatSystem>
std::string std_str() const noexcept;
template<typename FormatSystem>
rel_path_view<PathSystems> view() const noexcept;
rel_path_view<PathSystems> subpath(size_t index, int count = 0) const noexcept;
rel_path parent() const noexcept;
bool is_prefix_of(const rel_path& path) const noexcept;
string pop_front() noexcept;
void push_front(string element) noexcept;
bool push_back(string element) noexcept override;
const string& front() const noexcept;
const string& back() const noexcept;
void replace_back(string element) noexcept;
void collapse_path() noexcept;
// for compatibility with external path systems
// NOTE!!! this doesn't do parsing, it just copies the elements directly. If the path has elements incompatible with the destination path, it's your problem
// To handle proper conversion and validation, pass the path to string and then parse it in the new path
template<typename other_rel_path>
other_rel_path cast_to() const noexcept;
template<typename other_rel_path>
other_rel_path move_cast_to() noexcept;
private:
bool parse(const char* path, size_t size) noexcept;
};
//////////////////////////////////////////////////////////////////////////
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
namespace tl
{
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>::rel_path(rel_path_view<PathSystems> view) noexcept
{
this->m_elements.insert(this->m_elements.end(), view.begin(), view.end());
this->m_hash = view.hash();
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>::rel_path(span<const string> elements) noexcept
{
this->m_elements.insert(this->m_elements.end(), elements.begin(), elements.end());
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>::rel_path(const char* path) noexcept
{
if (!path)
TL_PLAIN_CRASH("null path in relative path constructor");
if (parse(path, strlen(path)) == false)
TL_PLAIN_FAIL("Failed to parse relative path");
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>::rel_path(const char* path, size_t size) noexcept
{
if (!path)
TL_PLAIN_CRASH("null path in relative path constructor");
if (parse(path, size) == false)
TL_PLAIN_FAIL("Failed to parse relative path");
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
template<typename Str>
rel_path<PathSystems>::rel_path(const Str& path) noexcept
{
if (parse(path.data(), path.size()) == false)
TL_PLAIN_FAIL("Failed to parse relative path");
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>::rel_path(const rel_path& other) noexcept
{
this->m_elements = other.m_elements;
this->m_hash = other.m_hash;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>::rel_path(rel_path&& other) noexcept
{
this->m_elements = std::move(other.m_elements);
this->m_hash = other.m_hash;
other.m_hash = 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>::rel_path(const rel_path& from, const rel_path& to) noexcept
{
if (from.empty())
{
*this = to;
return;
}
if (to.empty())
{
*this = from;
return;
}
const size_t count = std::min(from.size(), to.size());
size_t last = 0;
for (size_t i = 0; i < count; i++)
{
if (from.m_elements[i] != to.m_elements[i])
break;
last = i + 1;
}
this->reserve((from.size() - last) + (to.size() - last));
for (size_t i = last; i < from.size(); i++)
this->push_element(path_system::back_t::value());
for (size_t i = last; i < to.size(); i++)
this->push_element(to.m_elements[i]);
collapse_path();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>& rel_path<PathSystems>::operator=(const rel_path& other) noexcept
{
this->m_elements = other.m_elements;
this->m_hash = other.m_hash;
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>& rel_path<PathSystems>::operator=(rel_path&& other) noexcept
{
std::swap(this->m_elements, other.m_elements);
std::swap(this->m_hash, other.m_hash);
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
template<typename Str>
rel_path<PathSystems>& rel_path<PathSystems>::operator=(const Str& path) noexcept
{
if (parse(path.data(), path.size()) == false)
TL_PLAIN_FAIL("Failed to parse relative path");
return *this;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>& rel_path<PathSystems>::operator=(const rel_path_view<PathSystems>& path) noexcept
{
this->m_elements.clear();
this->m_elements.insert(this->m_elements.end(), path.m_elements.begin(), path.m_elements.end());
this->m_hash = path.m_hash;
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>& rel_path<PathSystems>::operator=(const char* path) noexcept
{
if (!path)
TL_PLAIN_CRASH("null path in relative path assignment");
if (parse(path, strlen(path)) == false)
TL_PLAIN_FAIL("Failed to parse relative path");
return *this;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems> rel_path<PathSystems>::operator+(const char* path) const noexcept
{
if (path && path[0] != '\0')
{
rel_path p;
p.reserve(this->size() + 1);
p = *this;
//this will push pack in the m_elements
PathSystems::parse_relative(p.m_elements, path, strlen(path));
p.collapse_path();
return p;
}
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
template<typename Str>
rel_path<PathSystems> rel_path<PathSystems>::operator+(const Str& path) const noexcept
{
if (!path.empty())
{
rel_path p;
p.reserve(this->size() + 1);
p = *this;
//this will push pack in the m_elements
PathSystems::parse_relative(p.m_elements, path.data(), path.size());
p.collapse_path();
return p;
}
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems> rel_path<PathSystems>::operator+(const rel_path& path) const noexcept
{
if (!path.empty())
{
rel_path p;
p.reserve(this->size() + path.size());
p = *this;
p += path;
return p;
}
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems> rel_path<PathSystems>::operator+(const rel_path_view<PathSystems>& path) const noexcept
{
if (!path.empty())
{
rel_path p;
p.reserve(this->size() + path.size());
p = *this;
p += path;
return p;
}
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems> rel_path<PathSystems>::operator+(rel_path&& path) const noexcept
{
if (!path.empty())
{
rel_path p;
p.reserve(this->size() + path.size());
p = *this;
p += std::move(path);
return p;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>& rel_path<PathSystems>::operator+=(const char* path) noexcept
{
if (path && path[0] != '\0')
{
//this will push pack in the m_elements
PathSystems::parse_relative(this->m_elements, path, strlen(path));
this->m_hash = 0;
collapse_path();
}
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
template<typename Str>
rel_path<PathSystems>& rel_path<PathSystems>::operator+=(const Str& path) noexcept
{
if (!path.empty())
{
//this will push pack in the m_elements
PathSystems::parse_relative(this->m_elements, path.data(), path.size());
this->m_hash = 0;
collapse_path();
}
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>& rel_path<PathSystems>::operator+=(const rel_path& path) noexcept
{
if (!path.empty())
{
this->m_elements.reserve(this->m_elements.size() + path.m_elements.size());
for (const string& element : path.m_elements)
this->push_element(element);
this->m_hash = 0;
this->collapse_path();
}
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>& rel_path<PathSystems>::operator+=(rel_path&& path) noexcept
{
if (!path.empty())
{
this->m_elements.reserve(this->m_elements.size() + path.m_elements.size());
for (string& element : path.m_elements)
this->push_element(std::move(element));
this->m_hash = 0;
this->collapse_path();
}
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>& rel_path<PathSystems>::operator+=(const rel_path_view<PathSystems>& path) noexcept
{
if (!path.empty())
{
this->m_elements.reserve(this->m_elements.size() + path.m_elements.size());
for (const string& element : path.m_elements)
this->push_element(element);
this->m_hash = 0;
this->collapse_path();
}
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems>::operator rel_path_view<PathSystems>() const noexcept
{
return rel_path_view<PathSystems>(this->m_elements);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
bool rel_path<PathSystems>::operator==(const rel_path& other) const noexcept
{
return (!this->m_hash || !other.m_hash || this->m_hash == other.m_hash) &&
this->m_elements == other.m_elements;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
bool rel_path<PathSystems>::operator!=(const rel_path& other) const noexcept
{
return !(*this == other);
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
auto rel_path<PathSystems>::operator<=>(const rel_path& other) const noexcept
{
const size_t sz1 = this->m_elements.size();
const size_t sz2 = other.m_elements.size();
for (size_t i = 0; i < sz1 && i < sz2; ++i)
{
auto r = this->m_elements[i] <=> other.m_elements[i];
if (r != decltype(r)::equal)
return r;
}
return sz1 <=> sz2;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
bool rel_path<PathSystems>::operator==(const rel_path_view<PathSystems>& other) const noexcept
{
return (!this->m_hash || !other.m_hash || this->m_hash == other.m_hash) &&
tl::span(this->m_elements) == other.m_elements;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
bool rel_path<PathSystems>::operator!=(const rel_path_view<PathSystems>& other) const noexcept
{
return !(*this == other);
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
auto rel_path<PathSystems>::operator<=>(const rel_path_view<PathSystems>& other) const noexcept
{
const size_t sz1 = this->m_elements.size();
const size_t sz2 = other.m_elements.size();
for (size_t i = 0; i < sz1 && i < sz2; ++i)
{
auto r = this->m_elements[i] <=> other.m_elements[i];
if (r != decltype(r)::equal)
return r;
}
return sz1 <=> sz2;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
bool rel_path<PathSystems>::parse(const char* path, size_t size) noexcept
{
this->clear();
if (size == 0 || !path || path[0] == 0)
return true;
PathSystems::parse_relative(this->m_elements, path, size);
collapse_path();
return true;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
void rel_path<PathSystems>::collapse_path() noexcept
{
if (!this->empty())
{
for (size_t i = 0; i < this->m_elements.size();)
{
if (i >= 1 &&
this->m_elements[i] == path_system::back_t::value() &&
this->m_elements[i - 1] != path_system::back_t::value())
{
this->m_hash = 0;
this->m_elements.erase(this->m_elements.begin() + i - 1, this->m_elements.begin() + i + 1); //remove the .. and the parent
i -= 1;
}
else if (this->m_elements[i] == path_system::current_t::value())
{
this->m_hash = 0;
this->m_elements.erase(this->m_elements.begin() + i); //remove the .
}
else
i++;
}
}
}
//////////////////////////////////////////////////////////////////////////
template<class PathSystems>
bool rel_path<PathSystems>::is_prefix_of(const rel_path& path) const noexcept
{
if (this->size() > path.size())
return false;
for (size_t i = 0, sz = this->size(); i < sz; ++i)
{
if (this->m_elements[i] != path[i])
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
void rel_path<PathSystems>::swap(rel_path& other) noexcept
{
std::swap(this->m_elements, other.m_elements);
std::swap(this->m_hash, other.m_hash);
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
void rel_path<PathSystems>::clear() noexcept
{
this->m_elements.clear();
this->m_hash = 0;
}
//////////////////////////////////////////////////////////////////////////
template<typename PathSystems>
template<typename FormatSystem>
string rel_path<PathSystems>::str() const noexcept
{
return FormatSystem::template format_rel<string>(this->m_elements);
}
//////////////////////////////////////////////////////////////////////////
template<typename PathSystems>
template<typename FormatSystem>
eastl::string rel_path<PathSystems>::eastl_str() const noexcept
{
return FormatSystem::template format_rel<eastl::string>(this->m_elements);
}
//////////////////////////////////////////////////////////////////////////
template<typename PathSystems>
template<typename FormatSystem>
std::string rel_path<PathSystems>::std_str() const noexcept
{
return FormatSystem::template format_rel<std::string>(this->m_elements);
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
template <typename FormatSystem>
rel_path_view<PathSystems> rel_path<PathSystems>::view() const noexcept
{
return rel_path_view<PathSystems>(this->m_elements);
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path_view<PathSystems> rel_path<PathSystems>::subpath(size_t index, int count /* = 0 */) const noexcept
{
if (count == 0)
count = (int)this->size() - (int)index;
if (count < 0)
count = (int)this->size() - (int)index + count;
if (count > 0 && index < this->size())
return rel_path_view<PathSystems>(tl::span(this->m_elements.begin() + index, this->m_elements.begin() + index + count));
return {};
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
rel_path<PathSystems> rel_path<PathSystems>::parent() const noexcept
{
rel_path p = *this;
//this removes all the .. it can from the path
p.collapse_path();
//remaining cases:
//1. [a/b/c] -> [a/b]
//2. [../b/c] -> [../b]
//3. [..] -> [../..]
//4. [] -> [..]
if (p.empty() || p.back() == path_system::back_t::value())
p += path_system::back_t::value();
else
p.pop_back();
return p;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
const string& rel_path<PathSystems>::back() const noexcept
{
TL_PLAIN_ASSERT(!this->empty());
return this->m_elements.back();
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
const string& rel_path<PathSystems>::front() const noexcept
{
TL_PLAIN_ASSERT(!this->empty());
return this->m_elements.front();
}
//////////////////////////////////////////////////////////////////////////
template<class PathSystems>
void rel_path<PathSystems>::replace_back(string element) noexcept
{
TL_PLAIN_ASSERT(!this->empty());
const bool is_back_element = element == path_system::back_t::value();
this->m_hash = 0;
this->m_elements.back() = std::move(element);
if (is_back_element)
collapse_path();
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
string rel_path<PathSystems>::pop_front() noexcept
{
if (this->empty())
return string();
this->m_hash = 0;
auto res = this->m_elements.front();
this->m_elements.erase(this->m_elements.begin());
return res;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
void rel_path<PathSystems>::push_front(string element) noexcept
{
const bool is_back_element = element == path_system::back_t::value();
this->m_hash = 0;
this->m_elements.insert(this->m_elements.begin(), std::move(element));
if (is_back_element)
collapse_path();
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
bool rel_path<PathSystems>::push_back(string element) noexcept
{
const bool is_back_element = element == path_system::back_t::value();
if (!detail::path_system::path_base<PathSystems>::push_back(std::move(element)))
return false;
if (is_back_element)
collapse_path();
return true;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
template<typename other_rel_path>
other_rel_path rel_path<PathSystems>::cast_to() const noexcept
{
other_rel_path ret;
for (const string& element : this->m_elements)
ret.push_back(element);
return ret;
}
//////////////////////////////////////////////////////////////////////////
template <typename PathSystems>
template<typename other_rel_path>
other_rel_path rel_path<PathSystems>::move_cast_to() noexcept
{
other_rel_path ret;
ret.take_elements(std::move(this->m_elements));
this->m_elements.clear();
this->m_hash = 0;
return ret;
}
template<class PathSystems>
rel_path<PathSystems> operator+(const rel_path_view<PathSystems>& a, const char* b) noexcept
{
rel_path<PathSystems> p;
p.reserve(a.size() + 1);
p = a;
p += b;
return p;
}
template<class PathSystems, typename Str> requires (!std::is_same_v<Str, const char*> && !std::is_same_v<Str, char*>)
rel_path<PathSystems> operator+(const rel_path_view<PathSystems>& a, const Str& b) noexcept
{
rel_path<PathSystems> p;
p.reserve(a.size() + 1);
p = a;
p += b;
return p;
}
template<class PathSystems>
rel_path<PathSystems> operator+(const rel_path_view<PathSystems>& a, const rel_path<PathSystems>& b) noexcept
{
rel_path<PathSystems> p;
p.reserve(a.size() + b.size());
p = a;
p += b;
return p;
}
template<class PathSystems>
rel_path<PathSystems> operator+(const rel_path_view<PathSystems>& a, const rel_path_view<PathSystems>& b) noexcept
{
rel_path<PathSystems> p;
p.reserve(a.size() + b.size());
p = a;
p += b;
return p;
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
struct std::formatter<tl::rel_path<tl::path_systems<T>>>
{
constexpr auto parse(format_parse_context& ctx) noexcept { return ctx.begin(); }
auto format(const tl::rel_path<tl::path_systems<T>>& p, std::format_context& ctx) const
{
return format_to(ctx.out(), "{}", p.template str<T>());
}
};
template <typename T>
void swap(tl::rel_path<T>& a, tl::rel_path<T>& b) noexcept
{
a.swap(b);
}
template <typename T>
struct std::hash<tl::rel_path<T>>
{
std::size_t operator()(const tl::rel_path<T>& p) const
{
return p.hash();
}
};
template <typename T>
struct eastl::hash<tl::rel_path<T>>
{
std::size_t operator()(const tl::rel_path<T>& p) const
{
return p.hash();
}
};