#pragma once #include "tl/detail/prologue.h" #include #include "tl/path_system.h" #include "tl/detail/path_base.h" #include "tl/plain_crash.h" namespace tl { template class abs_path; ////////////////////////////////////////////////////////////////////////// // relative path template class rel_path : public detail::path_system::path_base { template friend class abs_path; public: rel_path() noexcept; explicit rel_path(const char* path) noexcept; rel_path(const char* path, size_t size) noexcept; template explicit rel_path(const Str& path) noexcept; rel_path(const rel_path& other) noexcept; rel_path(rel_path&& other) noexcept; // for retro-compatibility with old path system rel_path(const rel_path& from, const rel_path& to) noexcept; // operators const string& operator[](size_t idx) const noexcept; rel_path& operator=(const char* path) noexcept; rel_path& operator=(const rel_path& other) noexcept; rel_path& operator=(rel_path&& other) noexcept; template rel_path& operator=(const Str& 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 rel_path operator+(const Str& 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 rel_path& operator+=(const Str& path) 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; void swap(rel_path& other) noexcept; void clear() noexcept; template string get_as() const noexcept; rel_path 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; string& front() noexcept; string& back() 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 other_rel_path cast_to() const noexcept; template other_rel_path move_cast_to() noexcept; private: bool parse(const char* path, size_t size) noexcept; }; ////////////////////////////////////////////////////////////////////////// } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// namespace tl { ////////////////////////////////////////////////////////////////////////// template rel_path::rel_path() noexcept { this->m_hash = 0; } ////////////////////////////////////////////////////////////////////////// template rel_path::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 rel_path::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 template rel_path::rel_path(const Str& path) noexcept { if (parse(path.data(), path.size()) == false) TL_PLAIN_FAIL("Failed to parse relative path"); } ////////////////////////////////////////////////////////////////////////// template rel_path::rel_path(const rel_path& other) noexcept { this->m_elements = other.m_elements; this->m_hash = other.m_hash; } ////////////////////////////////////////////////////////////////////////// template rel_path::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 rel_path::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]); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// template const string& rel_path::operator[](size_t idx) const noexcept { return this->m_elements[idx]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////// template rel_path& rel_path::operator=(const rel_path& other) noexcept { this->m_elements = other.m_elements; this->m_hash = other.m_hash; return *this; } ////////////////////////////////////////////////////////////////////////// template rel_path& rel_path::operator=(rel_path&& other) noexcept { std::swap(this->m_elements, other.m_elements); std::swap(this->m_hash, other.m_hash); return *this; } ////////////////////////////////////////////////////////////////////////// template template rel_path& rel_path::operator=(const Str& path) noexcept { if (parse(path.data(), path.size()) == false) TL_PLAIN_FAIL("Failed to parse relative path"); return *this; } ////////////////////////////////////////////////////////////////////////// template rel_path& rel_path::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 rel_path rel_path::operator+(const char* path) const noexcept { if (path && path[0] != '\0') { rel_path p; p.reserve(this->size() + 1); p = *this; size_t old_index = p.size(); //this will push pack in the m_elements PathSystems::parse_relative(p.m_elements, path, strlen(path)); for (size_t i = old_index; i < p.m_elements.size(); ++i) { const string& element = p.m_elements[i]; if (!p.validate_element(element)) { TL_PLAIN_FAIL("Failed to parse path"); p.m_elements.resize(old_index); return p; } } p.collapse_path(); return p; } return *this; } ////////////////////////////////////////////////////////////////////////// template template rel_path rel_path::operator+(const Str& path) const noexcept { if (!path.empty()) { rel_path p; p.reserve(this->size() + 1); p = *this; size_t old_index = p.size(); //this will push pack in the m_elements PathSystems::parse_relative(p.m_elements, path.data(), path.size()); for (size_t i = old_index; i < p.m_elements.size(); ++i) { const string& element = p.m_elements[i]; if (!p.validate_element(element)) { TL_PLAIN_FAIL("Failed to parse path"); p.m_elements.resize(old_index); return p; } } p.collapse_path(); return p; } return *this; } ////////////////////////////////////////////////////////////////////////// template rel_path rel_path::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 rel_path rel_path::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 rel_path& rel_path::operator+=(const char* path) noexcept { if (path && path[0] != '\0') { size_t old_index = this->size(); //this will push pack in the m_elements PathSystems::parse_relative(this->m_elements, path, strlen(path)); for (size_t i = old_index; i < this->m_elements.size(); ++i) { const string& element = this->m_elements[i]; if (!this->validate_element(element)) { TL_PLAIN_FAIL("Failed to parse path"); this->m_elements.resize(old_index); return *this; } } this->m_hash = 0; collapse_path(); } return *this; } ////////////////////////////////////////////////////////////////////////// template template rel_path& rel_path::operator+=(const Str& path) noexcept { if (!path.empty()) { size_t old_index = this->size(); //this will push pack in the m_elements PathSystems::parse_relative(this->m_elements, path.data(), path.size()); for (size_t i = old_index; i < this->m_elements.size(); ++i) { const string& element = this->m_elements[i]; if (!this->validate_element(element)) { TL_PLAIN_FAIL("Failed to parse path"); this->m_elements.resize(old_index); return *this; } } this->m_hash = 0; collapse_path(); } return *this; } ////////////////////////////////////////////////////////////////////////// template rel_path& rel_path::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 rel_path& rel_path::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 bool rel_path::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 bool rel_path::operator!=(const rel_path& other) const noexcept { return !(*this == other); } ////////////////////////////////////////////////////////////////////////// template auto rel_path::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 bool rel_path::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); for (const string& element : this->m_elements) { if (!this->validate_element(element)) { this->clear(); return false; } } collapse_path(); return true; } ////////////////////////////////////////////////////////////////////////// template void rel_path::collapse_path() noexcept { if (!this->empty()) { this->m_hash = 0; 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++; } } } ////////////////////////////////////////////////////////////////////////// template bool rel_path::is_prefix_of(const rel_path& path) const noexcept { 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 rel_path::swap(rel_path& other) noexcept { std::swap(this->m_elements, other.m_elements); std::swap(this->m_hash, other.m_hash); } ////////////////////////////////////////////////////////////////////////// template void rel_path::clear() noexcept { this->m_elements.resize(0); this->m_hash = 0; } ////////////////////////////////////////////////////////////////////////// template template string rel_path::get_as() const noexcept { return FormatSystem::format_relative(this->m_elements); } ////////////////////////////////////////////////////////////////////////// template rel_path rel_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()) { dst.m_elements.resize(count); for (int i = 0; i < count; i++) dst.m_elements[i] = this->m_elements[index + i]; } return dst; } ////////////////////////////////////////////////////////////////////////// template rel_path rel_path::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 const string& rel_path::back() const noexcept { TL_PLAIN_ASSERT(!this->empty()); return this->m_elements.back(); } ////////////////////////////////////////////////////////////////////////// template const string& rel_path::front() const noexcept { TL_PLAIN_ASSERT(!this->empty()); return this->m_elements.front(); } ////////////////////////////////////////////////////////////////////////// template string& rel_path::front() noexcept { TL_PLAIN_ASSERT(!this->empty()); this->m_hash = 0; return this->m_elements.front(); } ////////////////////////////////////////////////////////////////////////// template string& rel_path::back() noexcept { TL_PLAIN_ASSERT(!this->empty()); this->m_hash = 0; return this->m_elements.back(); } ////////////////////////////////////////////////////////////////////////// template string rel_path::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 void rel_path::push_front(string element) noexcept { this->m_hash = 0; this->m_elements.insert(this->m_elements.begin(), std::move(element)); } ////////////////////////////////////////////////////////////////////////// template bool rel_path::push_back(string element) noexcept { 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(); return true; } ////////////////////////////////////////////////////////////////////////// template template other_rel_path rel_path::cast_to() const noexcept { other_rel_path ret; for (const string& element : this->m_elements) ret.push_back(element); return ret; } ////////////////////////////////////////////////////////////////////////// template template other_rel_path rel_path::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 struct std::formatter>> { constexpr auto parse(format_parse_context& ctx) noexcept { return ctx.begin(); } auto format(const tl::rel_path>& p, std::format_context& ctx) const { return format_to(ctx.out(), "{}", p.template get_as()); } }; template void swap(tl::rel_path& a, tl::rel_path& b) noexcept { a.swap(b); } template struct std::hash> { std::size_t operator()(const tl::rel_path& p) const { return p.hash(); } }; template struct eastl::hash> { std::size_t operator()(const tl::rel_path& p) const { return p.hash(); } };