#pragma once #include "tl/detail/prologue.h" #include #include "tl/abs_path_view.h" #include "tl/rel_path_view.h" #include "tl/path_system.h" #include "tl/rel_path.h" #include "tl/detail/path_base.h" #include "tl/plain_crash.h" #include "tl/format.h" namespace tl { template class abs_path; template struct path_key_less; ////////////////////////////////////////////////////////////////////////// template class abs_path : public detail::path_system::path_base { template friend class rel_path; public: using rel_path_type = rel_path; using path_view_type = abs_path_view; abs_path() noexcept = default; abs_path(abs_path_view view) noexcept; abs_path(int path_system_id, span elements) noexcept; explicit abs_path(const char* path) noexcept; abs_path(const char* path, size_t size) noexcept; template requires (!std::is_same_v && !std::is_same_v) explicit abs_path(const Str& path) noexcept; abs_path(const abs_path& other) noexcept; abs_path(abs_path&& other) noexcept; // operators abs_path& operator=(const abs_path& other) noexcept; abs_path& operator=(abs_path&& other) noexcept; abs_path& operator=(const char* path) noexcept; template requires (!std::is_same_v && !std::is_same_v) abs_path& operator=(const Str& path) noexcept; abs_path& operator=(const abs_path_view& path) noexcept; abs_path operator+(const char* path) const noexcept; abs_path operator+(const rel_path& path) const noexcept; abs_path operator+(rel_path&& path) const noexcept; template requires (!std::is_same_v && !std::is_same_v) abs_path operator+(const Str& path) const noexcept; abs_path& operator+=(const char* path) noexcept; abs_path& operator+=(const rel_path& path) noexcept; abs_path& operator+=(rel_path&& path) noexcept; template requires (!std::is_same_v && !std::is_same_v) abs_path& operator+=(const Str& path) noexcept; abs_path& operator+=(const rel_path_view& path) noexcept; operator abs_path_view() const noexcept; bool operator==(const abs_path& other) const noexcept; bool operator!=(const abs_path& other) const noexcept; auto operator<=>(const abs_path& other) const noexcept; bool operator==(const abs_path_view& other) const noexcept; bool operator!=(const abs_path_view& other) const noexcept; auto operator<=>(const abs_path_view& other) const noexcept; void swap(abs_path& other) noexcept; void clear() noexcept; string str() const noexcept; eastl::string eastl_str() const noexcept; std::string std_str() const noexcept; abs_path_view view() const noexcept; rel_path_view subpath(size_t idx, int count = 0) const noexcept; abs_path_view parent() const noexcept; abs_path_view parent(size_t levels) const noexcept; rel_path path_to(const abs_path& to) const noexcept; void path_to(rel_path& dst, const abs_path& to) const noexcept; bool is_prefix_of(const abs_path& path) const noexcept; template bool is() const noexcept; bool is_valid() const 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 take_elements(tl::vector&& elements) noexcept override; //Parses a string. // 1st attempt is done to parse it as an abs path. If it succeeds, it returns the path. // 2nd attempt is done to parse it as a relative path and if it succeeds, it returns it as fallbackRoot + relative path. This happens ONLY if the fallbackRoot is valid template static abs_path from_string(const Str& string, const abs_path& fallbackRoot) noexcept; //Parses a string as an abs path. If it fails it returns an invalid path. template static abs_path from_string(const Str& string) noexcept; template static bool is_valid_string(const Str& string) noexcept; template static bool has_valid_tag(const Str& string) noexcept; bool parse(const char* path, size_t size) noexcept; private: bool collapse_path() noexcept; int m_pathSystemId = -1; }; ////////////////////////////////////////////////////////////////////////// } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// namespace tl { ////////////////////////////////////////////////////////////////////////// template abs_path::abs_path(const char* path, size_t size) noexcept { if (!path) TL_PLAIN_CRASH("null path in abs_path constructor"); if (parse(path, size) == false) TL_PLAIN_FAIL("Failed to parse abs path"); } ////////////////////////////////////////////////////////////////////////// template abs_path::abs_path(abs_path_view view) noexcept : m_pathSystemId(view.m_pathSystemId) { this->m_elements.insert(this->m_elements.end(), view.begin(), view.end()); this->m_hash = view.hash(); } ////////////////////////////////////////////////////////////////////////// template abs_path::abs_path(int path_system_id, span elements) noexcept : m_pathSystemId(path_system_id) { this->m_elements.insert(this->m_elements.end(), elements.begin(), elements.end()); } ////////////////////////////////////////////////////////////////////////// template abs_path::abs_path(const char* path) noexcept { if (!path) TL_PLAIN_CRASH("null path in abs_path constructor"); if (parse(path, strlen(path)) == false) TL_PLAIN_FAIL("Failed to parse abs path"); } ////////////////////////////////////////////////////////////////////////// template template requires (!std::is_same_v && !std::is_same_v) abs_path::abs_path(const Str& path) noexcept { if (parse(path.data(), path.size()) == false) TL_PLAIN_FAIL("Failed to parse abs path"); } ////////////////////////////////////////////////////////////////////////// template abs_path::abs_path(const abs_path& other) noexcept : m_pathSystemId(other.m_pathSystemId) { this->m_elements = other.m_elements; this->m_hash = other.m_hash; } ////////////////////////////////////////////////////////////////////////// template abs_path::abs_path(abs_path&& other) noexcept : m_pathSystemId(other.m_pathSystemId) { this->m_elements = std::move(other.m_elements); this->m_hash = other.m_hash; other.m_hash = 0; } //////////////////////////////////////////////////////////////////////////////////////////////////////////// template abs_path& abs_path::operator=(const abs_path& other) noexcept { this->m_elements = other.m_elements; this->m_hash = other.m_hash; m_pathSystemId = other.m_pathSystemId; return *this; } ////////////////////////////////////////////////////////////////////////// template abs_path& abs_path::operator=(abs_path&& other) noexcept { std::swap(this->m_elements, other.m_elements); std::swap(this->m_hash, other.m_hash); std::swap(m_pathSystemId, other.m_pathSystemId); return *this; } ////////////////////////////////////////////////////////////////////////// template template requires (!std::is_same_v && !std::is_same_v) abs_path& abs_path::operator=(const Str& path) noexcept { if (parse(path.data(), path.size()) == false) TL_PLAIN_FAIL("Failed to parse abs path"); return *this; } ////////////////////////////////////////////////////////////////////////// template abs_path& abs_path::operator=(const abs_path_view& 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; m_pathSystemId = path.m_pathSystemId; return *this; } ////////////////////////////////////////////////////////////////////////// template abs_path& abs_path::operator=(const char* path) noexcept { if (!path) TL_PLAIN_CRASH("null path in abs_path assignment"); if (parse(path, strlen(path)) == false) TL_PLAIN_FAIL("Failed to parse abs path"); return *this; } //////////////////////////////////////////////////////////////////////////////////////////////////////////// template abs_path abs_path::operator+(const char* path) const noexcept { if (path && path[0] != '\0') { abs_path p; p.reserve(this->size() + 1); p = *this; p += path; return p; } return *this; } ////////////////////////////////////////////////////////////////////////// template template requires (!std::is_same_v && !std::is_same_v) abs_path abs_path::operator+(const Str& path) const noexcept { if (!path.empty()) { abs_path p; p.reserve(this->size() + 1); p = *this; p += path; return p; } return *this; } ////////////////////////////////////////////////////////////////////////// template abs_path abs_path::operator+(const rel_path& path) const noexcept { if (!path.empty()) { abs_path p; p.reserve(this->size() + path.size()); p = *this; p += path; return p; } return *this; } ////////////////////////////////////////////////////////////////////////// template abs_path abs_path::operator+(rel_path&& path) const noexcept { if (!path.empty()) { abs_path p; p.reserve(this->size() + path.size()); p = *this; p += std::move(path); return p; } return *this; } //////////////////////////////////////////////////////////////////////////////////////////////////////////// template abs_path& abs_path::operator+=(const char* path) noexcept { if (m_pathSystemId < 0 && this->empty()) *this = path; else if (path && path[0] != '\0') *this += rel_path(path); return *this; } ////////////////////////////////////////////////////////////////////////// template template requires (!std::is_same_v && !std::is_same_v) abs_path& abs_path::operator+=(const Str& path) noexcept { if (m_pathSystemId < 0 && this->empty()) *this = path; else if (!path.empty()) *this += rel_path(path); return *this; } ////////////////////////////////////////////////////////////////////////// template abs_path& abs_path::operator+=(const rel_path& path) noexcept { //use the move version *this += rel_path(path); return *this; } ////////////////////////////////////////////////////////////////////////// template abs_path& abs_path::operator+=(rel_path&& path) noexcept { if (!path.empty()) { this->m_elements.reserve(this->m_elements.size() + path.size()); for (auto& e : path) { if (!push_back(std::move(e))) return *this; } path.clear(); this->m_hash = 0; if (!this->collapse_path()) this->clear(); } return *this; } ////////////////////////////////////////////////////////////////////////// template abs_path& abs_path::operator+=(const rel_path_view& path) noexcept { if (!path.empty()) { this->m_elements.reserve(this->m_elements.size() + path.size()); for (auto& e : path) { if (!push_back(std::move(e))) return *this; } this->m_hash = 0; if (!this->collapse_path()) this->clear(); } return *this; } //////////////////////////////////////////////////////////////////////////////////////////////////////////// template bool abs_path::operator==(const abs_path& other) const noexcept { return (!this->m_hash || !other.m_hash || this->m_hash == other.m_hash) && this->m_pathSystemId == other.m_pathSystemId && this->m_elements == other.m_elements; } ////////////////////////////////////////////////////////////////////////// template bool abs_path::operator!=(const abs_path& other) const noexcept { return !(*this == other); } ////////////////////////////////////////////////////////////////////////// template auto abs_path::operator<=>(const abs_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 bool abs_path::operator==(const abs_path_view& other) const noexcept { return (!this->m_hash || !other.m_hash || this->m_hash == other.m_hash) && this->m_pathSystemId == other.m_pathSystemId && tl::span(this->m_elements) == other.m_elements; } ////////////////////////////////////////////////////////////////////////// template bool abs_path::operator!=(const abs_path_view& other) const noexcept { return !(*this == other); } ////////////////////////////////////////////////////////////////////////// template auto abs_path::operator<=>(const abs_path_view& 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 bool abs_path::parse(const char* path, size_t size) noexcept { this->clear(); if (size == 0 || !path || path[0] == 0) return true; m_pathSystemId = PathSystems::deduce_system_type(path, size); if (m_pathSystemId < 0) return false; PathSystems::parse_abs(this->m_elements, m_pathSystemId, path, size); if (collapse_path() == false) { this->clear(); return false; } return true; } ////////////////////////////////////////////////////////////////////////// template bool abs_path::collapse_path() noexcept { if (this->empty()) return is_valid(); const size_t oldSize = this->m_elements.size(); 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_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_elements.erase(this->m_elements.begin() + i); //remove the . else i++; } if (oldSize != this->m_elements.size()) this->m_hash = 0; if (!PathSystems::validate_abs_path(m_pathSystemId, this->m_elements)) return false; // if the first element is ".." the collapse failed if (!this->m_elements.empty() && this->m_elements[0] == path_system::back_t::value()) return false; return true; } ////////////////////////////////////////////////////////////////////////// template void abs_path::swap(abs_path& other) noexcept { std::swap(this->m_elements, other.m_elements); std::swap(this->m_hash, other.m_hash); std::swap(this->m_pathSystemId, other.m_pathSystemId); } ////////////////////////////////////////////////////////////////////////// template void abs_path::clear() noexcept { this->m_elements.resize(0); this->m_hash = 0; m_pathSystemId = -1; } ////////////////////////////////////////////////////////////////////////// template string abs_path::str() const noexcept { if (!is_valid() && this->empty()) return {}; return PathSystems::template format_abs(m_pathSystemId, this->m_elements); } ////////////////////////////////////////////////////////////////////////// template eastl::string abs_path::eastl_str() const noexcept { if (!is_valid() && this->empty()) return {}; return PathSystems::template format_abs(m_pathSystemId, this->m_elements); } ////////////////////////////////////////////////////////////////////////// template std::string abs_path::std_str() const noexcept { if (!is_valid() && this->empty()) return {}; return PathSystems::template format_abs(m_pathSystemId, this->m_elements); } ////////////////////////////////////////////////////////////////////////// template abs_path_view abs_path::view() const noexcept { return abs_path_view(m_pathSystemId, this->m_elements); } ////////////////////////////////////////////////////////////////////////// template abs_path::operator abs_path_view() const noexcept { return abs_path_view(m_pathSystemId, this->m_elements); } ////////////////////////////////////////////////////////////////////////// template rel_path_view abs_path::subpath(size_t index, int count /* = 0 */) const noexcept { rel_path dst; 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(tl::span(this->m_elements.begin() + index, this->m_elements.begin() + index + count)); return {}; } ////////////////////////////////////////////////////////////////////////// template abs_path_view abs_path::parent() const noexcept { if (this->empty()) { TL_PLAIN_FAIL("Invalid path"); return {}; } return abs_path_view(m_pathSystemId, tl::span(this->m_elements.begin(), this->m_elements.end() - 1)); } ////////////////////////////////////////////////////////////////////////// template abs_path_view abs_path::parent(size_t levels) const noexcept { if (this->empty() || this->size() <= levels) { TL_PLAIN_FAIL("Invalid path"); return {}; } return abs_path_view(m_pathSystemId, tl::span(this->m_elements.begin(), this->m_elements.end() - levels)); } ////////////////////////////////////////////////////////////////////////// template template bool abs_path::is() const noexcept { return PathSystems::template is(m_pathSystemId); } ////////////////////////////////////////////////////////////////////////// template inline bool abs_path::is_valid() const noexcept { return m_pathSystemId >= 0; } ////////////////////////////////////////////////////////////////////////// template rel_path abs_path::path_to(const abs_path& to) const noexcept { rel_path path; path_to(path, to); return path; } ////////////////////////////////////////////////////////////////////////// template void abs_path::path_to(rel_path& dst, const abs_path& to) const noexcept { dst.clear(); if (this->empty()) { dst.reserve(to.size()); for (string const& element : to) dst.push_element(element); } else if (to.empty()) { //ms relative path from an abs path to an empty path should be an empty path } else { const size_t count = std::min(this->size(), to.size()); size_t last = 0; for (size_t i = 0; i < count; i++) { if (this->m_elements[i] != to.m_elements[i]) break; last = i + 1; } dst.reserve((this->size() - last) + to.size() - last); for (size_t i = last; i < this->size(); i++) dst.push_element(path_system::back_t::value()); for (size_t i = last; i < to.size(); i++) dst.push_element(to.m_elements[i]); } } ////////////////////////////////////////////////////////////////////////// template bool abs_path::is_prefix_of(const abs_path& path) const noexcept { if (m_pathSystemId != path.m_pathSystemId) return false; if (this->size() > path.size()) return false; for (size_t i = 0, _count = this->size(); i < _count; ++i) { if (this->m_elements[i] != path[i]) return false; } return true; } ////////////////////////////////////////////////////////////////////////// template void abs_path::take_elements(tl::vector&& elements) noexcept { clear(); if (elements.empty()) return; m_pathSystemId = PathSystems::deduce_system_type(elements.front().data(), elements.front().size()); if (m_pathSystemId < 0) return; detail::path_system::path_base::take_elements(std::move(elements)); if (collapse_path() == false) clear(); } ////////////////////////////////////////////////////////////////////////// template bool abs_path::push_back(string element) noexcept { if (m_pathSystemId < 0) { TL_PLAIN_ASSERT(this->empty()); m_pathSystemId = PathSystems::deduce_system_type(element.data(), element.size()); if (m_pathSystemId < 0) return false; } const bool is_back_element = element == path_system::back_t::value(); if (!detail::path_system::path_base::push_back(std::move(element))) return false; if (is_back_element && collapse_path() == false) { clear(); return false; } return true; } ////////////////////////////////////////////////////////////////////////// template const tl::string& abs_path::back() const noexcept { TL_PLAIN_ASSERT(!this->empty()); return this->m_elements.back(); } ////////////////////////////////////////////////////////////////////////// template const tl::string& abs_path::front() const noexcept { TL_PLAIN_ASSERT(!this->empty()); return this->m_elements.front(); } ////////////////////////////////////////////////////////////////////////// template void abs_path::replace_back(string element) noexcept { TL_PLAIN_ASSERT(!this->empty()); this->m_hash = 0; this->m_elements.back() = std::move(element); if (!collapse_path()) clear(); } ////////////////////////////////////////////////////////////////////////// template template abs_path abs_path::from_string(const Str& string, const abs_path& fallbackRoot) noexcept { abs_path outPath; if (outPath.parse(string.data(), string.size())) return outPath; else if (fallbackRoot.is_valid()) return fallbackRoot + string; else return abs_path(); } ////////////////////////////////////////////////////////////////////////// template template abs_path abs_path::from_string(const Str& string) noexcept { abs_path outPath; if (outPath.parse(string.data(), string.size())) return outPath; return abs_path(); } ////////////////////////////////////////////////////////////////////////// template template bool abs_path::is_valid_string(const Str& string) noexcept { abs_path outPath; return outPath.parse(string.data(), string.size()); } template template bool abs_path::has_valid_tag(const Str& string) noexcept { if (string.empty()) return false; const int pathSystemId = PathSystems::deduce_system_type(string.data(), string.size()); return pathSystemId >= 0; } template abs_path operator+(const abs_path_view& a, const char* b) noexcept { abs_path p; p.reserve(a.size() + 1); p = a; p += b; return p; } template requires (!std::is_same_v && !std::is_same_v) abs_path operator+(const abs_path_view& a, const Str& b) noexcept { abs_path p; p.reserve(a.size() + 1); p = a; p += b; return p; } template abs_path operator+(const abs_path_view& a, const rel_path& b) noexcept { abs_path p; p.reserve(a.size() + b.size()); p = a; p += b; return p; } template abs_path operator+(const abs_path_view& a, const rel_path_view& b) noexcept { abs_path p; p.reserve(a.size() + b.size()); p = a; p += b; return p; } template abs_path operator+(const abs_path_view& a, const abs_path_view& b) noexcept { abs_path p; p.reserve(a.size() + b.size()); p = a; p += b; return p; } } ////////////////////////////////////////////////////////////////////////// template struct std::formatter> { constexpr auto parse(format_parse_context& ctx) noexcept { return ctx.begin(); } auto format(const tl::abs_path& p, std::format_context& ctx) const { return format_to(ctx.out(), "{}", p.str()); } }; template void swap(tl::abs_path& a, tl::abs_path& b) noexcept { a.swap(b); } template struct std::hash> { std::size_t operator()(const tl::abs_path& p) const { return p.hash(); } }; template struct eastl::hash> { std::size_t operator()(const tl::abs_path& p) const { return p.hash(); } };