diff --git a/source/mijin/types/path.hpp b/source/mijin/types/path.hpp new file mode 100644 index 0000000..f585870 --- /dev/null +++ b/source/mijin/types/path.hpp @@ -0,0 +1,129 @@ + +#pragma once + +#if !defined(MIJIN_TYPES_PATH_HPP_INCLUDED) +#define MIJIN_TYPES_PATH_HPP_INCLUDED 1 + +#include "../internal/common.hpp" + +#include +#include + +namespace mijin +{ +template, template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +class BasePath +{ +public: + using string_t = std::basic_string>; + using size_type = string_t::size_type; + using difference_type = string_t::difference_type; +private: + string_t storage_; +public: + BasePath() = default; + BasePath(const BasePath&) = default; + BasePath(BasePath&&) = default; + BasePath(string_t string) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible()) : storage_(std::move(string)) { + simplify(); + } + + BasePath& operator=(const BasePath&) = default; + BasePath& operator=(BasePath&&) = default; + + auto operator<=>(const BasePath&) const noexcept = default; + + [[nodiscard]] + const string_t& string() const MIJIN_NOEXCEPT + { + return storage_; + } +private: + void simplify() MIJIN_NOEXCEPT; +}; + +using Path = BasePath; +using WPath = BasePath; + +template typename TAllocator> +void BasePath::simplify() MIJIN_NOEXCEPT +{ + // step 1: remove double slashes + difference_type moveBy = 0; + for (auto it = std::next(storage_.begin()); it < storage_.end(); ++it) + { + const bool doubleSlash = (*it == separator && *std::prev(it) == separator); // check this before moving the current character, as that might create a double slash itself + if (moveBy > 0) { + *std::prev(it, moveBy) = *it; + } + if (doubleSlash) { + ++moveBy; + } + } + // step 1.5: remove trailing slash (but only if it's not the only remaining char) + if (moveBy < static_cast(storage_.size() - 1) && storage_[storage_.size() - moveBy - 1] == separator) { + ++moveBy; + } + storage_.resize(storage_.size() - moveBy); + + // step 2: get rid of any "/.." together with the preceeding segment + moveBy = 0; + for (auto it = std::next(storage_.begin(), 2); it < storage_.end(); ++it) + { + if (moveBy > 0) + { + *std::prev(it, moveBy) = *it; + } + if (*std::prev(it, moveBy) == TChar('.') && *std::prev(it, moveBy + 1) == TChar('.') && *std::prev(it, moveBy + 2) == separator + && (std::next(it) == storage_.end() || *std::next(it) == separator)) + { + if (std::prev(it, moveBy + 2) == storage_.begin()) + { + // leading "/.." -> just remove it + moveBy += 3; + continue; + } + // find the start of the preceeding segment + for (auto itStart = std::prev(it, moveBy + 3);; --itStart) + { + if (*std::prev(itStart) == separator || std::prev(itStart) == storage_.begin()) + { + // /path/with/../double/dot + // itStart --A A + // it -------------| + // remove everything from itStart to it + two slashes + moveBy += std::distance(itStart, std::prev(it, moveBy)) + 2; // skip it all + break; + } + } + } + } + storage_.resize(storage_.size() - moveBy); + + // step 3: eliminate any segments that are just "." + moveBy = 0; + if (storage_.size() == 1) { + return; // just stop it here + } + for (auto it = storage_.begin(); it < storage_.end(); ++it) + { + const bool atStart = (it == storage_.begin()); + const bool atEnd = (std::next(it) == storage_.end()); + const bool emptyEle = (*it == TChar('.') // char is a dot + && (atStart || *std::prev(it, moveBy + 1) == separator) // previous is a slash or doesn't exist + && (atEnd || *std::next(it) == separator)); // next is a slash or doesn't exist + if (moveBy > 0) { + *std::prev(it, moveBy) = *it; + } + if (emptyEle) { + moveBy += 2; + if (!atEnd) { + ++it; // skip the next one + } + } + } + storage_.resize(storage_.size() - moveBy); +} +} // namespace shiken + +#endif // !defined(MIJIN_TYPES_PATH_HPP_INCLUDED)