/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup bke */ #include "BKE_asset_catalog_path.hh" #include "BLI_path_util.h" namespace blender::bke { const char AssetCatalogPath::SEPARATOR = '/'; AssetCatalogPath::AssetCatalogPath(std::string path) : path_(std::move(path)) { } AssetCatalogPath::AssetCatalogPath(StringRef path) : path_(path) { } AssetCatalogPath::AssetCatalogPath(const char *path) : path_(path) { } AssetCatalogPath::AssetCatalogPath(AssetCatalogPath &&other_path) noexcept : path_(std::move(other_path.path_)) { } uint64_t AssetCatalogPath::hash() const { std::hash hasher{}; return hasher(this->path_); } uint64_t AssetCatalogPath::length() const { return this->path_.length(); } const char *AssetCatalogPath::c_str() const { return this->path_.c_str(); } const std::string &AssetCatalogPath::str() const { return this->path_; } StringRefNull AssetCatalogPath::name() const { const size_t last_sep_index = this->path_.rfind(SEPARATOR); if (last_sep_index == std::string::npos) { return StringRefNull(this->path_); } return StringRefNull(this->path_.c_str() + last_sep_index + 1); } bool AssetCatalogPath::operator==(const AssetCatalogPath &other_path) const { return this->path_ == other_path.path_; } bool AssetCatalogPath::operator!=(const AssetCatalogPath &other_path) const { return !(*this == other_path); } bool AssetCatalogPath::operator<(const AssetCatalogPath &other_path) const { return this->path_ < other_path.path_; } AssetCatalogPath AssetCatalogPath::operator/(const AssetCatalogPath &path_to_append) const { /* `"" / "path"` or `"path" / ""` should just result in `"path"` */ if (!*this) { return path_to_append; } if (!path_to_append) { return *this; } std::stringstream new_path; new_path << this->path_ << SEPARATOR << path_to_append.path_; return AssetCatalogPath(new_path.str()); } AssetCatalogPath::operator bool() const { return !this->path_.empty(); } std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append) { stream << path_to_append.path_; return stream; } AssetCatalogPath AssetCatalogPath::cleanup() const { std::stringstream clean_components; bool first_component_seen = false; this->iterate_components([&clean_components, &first_component_seen](StringRef component_name, bool /*is_last_component*/) { const std::string clean_component = cleanup_component(component_name); if (clean_component.empty()) { /* These are caused by leading, trailing, or double slashes. */ return; } /* If a previous path component has been streamed already, we need a path separator. This * cannot use the `is_last_component` boolean, because the last component might be skipped due * to the condition above. */ if (first_component_seen) { clean_components << SEPARATOR; } first_component_seen = true; clean_components << clean_component; }); return AssetCatalogPath(clean_components.str()); } std::string AssetCatalogPath::cleanup_component(StringRef component) { std::string cleaned = component.trim(); /* Replace colons with something else, as those are used in the CDF file as delimiter. */ std::replace(cleaned.begin(), cleaned.end(), ':', '-'); return cleaned; } bool AssetCatalogPath::is_contained_in(const AssetCatalogPath &other_path) const { if (!other_path) { /* The empty path contains all other paths. */ return true; } if (this->path_ == other_path.path_) { /* Weak is-in relation: equal paths contain each other. */ return true; } /* To be a child path of 'other_path', our path must be at least a separator and another * character longer. */ if (this->length() < other_path.length() + 2) { return false; } /* Create StringRef to be able to use .startswith(). */ const StringRef this_path(this->path_); const bool prefix_ok = this_path.startswith(other_path.path_); const char next_char = this_path[other_path.length()]; return prefix_ok && next_char == SEPARATOR; } AssetCatalogPath AssetCatalogPath::parent() const { if (!*this) { return AssetCatalogPath(""); } std::string::size_type last_sep_index = this->path_.rfind(SEPARATOR); if (last_sep_index == std::string::npos) { return AssetCatalogPath(""); } return AssetCatalogPath(this->path_.substr(0, last_sep_index)); } void AssetCatalogPath::iterate_components(ComponentIteratorFn callback) const { const char *next_slash_ptr; for (const char *path_component = this->path_.data(); path_component && path_component[0]; /* Jump to one after the next slash if there is any. */ path_component = next_slash_ptr ? next_slash_ptr + 1 : nullptr) { /* Note that this also treats backslashes as component separators, which * helps in cleaning up backslash-separated paths. */ next_slash_ptr = BLI_path_slash_find(path_component); const bool is_last_component = next_slash_ptr == nullptr; /* Note that this won't be null terminated. */ const StringRef component_name = is_last_component ? path_component : StringRef(path_component, next_slash_ptr - path_component); callback(component_name, is_last_component); } } AssetCatalogPath AssetCatalogPath::rebase(const AssetCatalogPath &from_path, const AssetCatalogPath &to_path) const { if (!from_path) { if (!to_path) { return AssetCatalogPath(""); } return to_path / *this; } if (!this->is_contained_in(from_path)) { return AssetCatalogPath(""); } if (*this == from_path) { /* Early return, because otherwise the length+1 below is going to cause problems. */ return to_path; } /* When from_path = "test", we need to skip "test/" to get the rest of the path, hence the +1. */ const StringRef suffix = StringRef(this->path_).substr(from_path.length() + 1); const AssetCatalogPath path_suffix(suffix); return to_path / path_suffix; } } // namespace blender::bke