carb/extras/Path.h
File members: carb/extras/Path.h
// Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "../Defines.h"
#include "../logging/Log.h"
#include "../../omni/String.h"
#include <functional>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
namespace carb
{
namespace extras
{
// Forward declarations
class Path;
inline Path operator/(const Path& left, const Path& right);
inline Path operator+(const Path& left, const Path& right);
inline Path operator+(const Path& left, const char* right);
inline Path operator+(const Path& left, const std::string& right);
inline Path operator+(const Path& left, const omni::string& right);
inline Path operator+(const char* left, const Path& right);
inline Path operator+(const std::string& left, const Path& right);
inline Path operator+(const omni::string& left, const Path& right);
class Path
{
public:
static constexpr size_t npos = std::string::npos;
static_assert(npos == size_t(-1), "Invalid assumption");
//--------------------------------------------------------------------------------------
// Constructors/destructor and assignment operations
Path() = default;
Path(const char* path, size_t pathLen)
{
if (path && pathLen)
{
m_pathString.assign(path, pathLen);
_sanitizePath();
}
}
Path(const char* path)
{
if (path)
{
m_pathString = path;
}
_sanitizePath();
}
Path(std::string path) : m_pathString(std::move(path))
{
_sanitizePath();
}
Path(const omni::string& path) : m_pathString(path.c_str(), path.length())
{
_sanitizePath();
}
Path(const Path& other) : m_pathString(other.m_pathString)
{
}
Path(Path&& other) noexcept : m_pathString(std::move(other.m_pathString))
{
}
Path& operator=(const Path& other)
{
m_pathString = other.m_pathString;
return *this;
}
Path& operator=(Path&& other) noexcept
{
m_pathString = std::move(other.m_pathString);
return *this;
}
~Path() = default;
//--------------------------------------------------------------------------------------
// Getting a string representation of the internal data
std::string getString() const
{
return m_pathString;
}
operator std::string() const
{
return m_pathString;
}
const char* c_str() const noexcept
{
return m_pathString.c_str();
}
const char* getStringBuffer() const noexcept
{
return m_pathString.c_str();
}
CARB_NO_DOC(friend) // Sphinx 3.5.4 complains that this is an invalid C++ declaration, so ignore `friend` for docs.
std::ostream& operator<<(std::ostream& os, const Path& path)
{
os << path.m_pathString;
return os;
}
explicit operator const char*() const noexcept
{
return getStringBuffer();
}
//--------------------------------------------------------------------------------------
// Path operations
// Compare operations
bool operator==(const Path& other) const noexcept
{
return m_pathString == other.m_pathString;
}
bool operator==(const std::string& other) const noexcept
{
return m_pathString == other;
}
bool operator==(const char* other) const noexcept
{
if (other == nullptr)
{
return false;
}
return m_pathString == other;
}
bool operator!=(const Path& other) const noexcept
{
return !(*this == other);
}
bool operator!=(const std::string& other) const noexcept
{
return !(*this == other);
}
bool operator!=(const char* other) const noexcept
{
return !(*this == other);
}
size_t getLength() const noexcept
{
return m_pathString.size();
}
size_t length() const noexcept
{
return m_pathString.size();
}
size_t size() const noexcept
{
return m_pathString.size();
}
Path& clear()
{
m_pathString.clear();
return *this;
}
bool isEmpty() const noexcept
{
return m_pathString.empty();
}
bool empty() const noexcept
{
return m_pathString.empty();
}
Path getFilename() const
{
size_t offset = _getFilenameOffset();
if (offset == npos)
return {};
return Path(Sanitized, m_pathString.substr(offset));
}
Path getExtension() const
{
size_t ext = _getExtensionOffset();
if (ext == npos)
return {};
return Path(Sanitized, m_pathString.substr(ext));
}
Path getParent() const;
Path getStem() const
{
size_t offset = _getFilenameOffset();
if (offset == npos)
return {};
size_t ext = _getExtensionOffset();
CARB_ASSERT(ext == npos || ext >= offset);
// If ext is npos, `ext - offset` results in the full filename since there is no extension
return Path(Sanitized, m_pathString.substr(offset, ext - offset));
}
Path getRootName() const
{
size_t rootNameEnd = _getRootNameEndOffset();
if (rootNameEnd == npos)
{
return {};
}
return Path(Sanitized, m_pathString.substr(0, rootNameEnd));
}
Path getRelativePart() const
{
size_t relativePart = _getRelativePartOffset();
if (relativePart == npos)
{
return {};
}
return Path(Sanitized, m_pathString.substr(relativePart));
}
Path getRootDirectory() const
{
constexpr static auto kForwardSlashChar_ = kForwardSlashChar; // CC-1110
return hasRootDirectory() ? Path(Sanitized, std::string(&kForwardSlashChar_, 1)) : Path();
}
bool hasRootDirectory() const noexcept
{
size_t rootDirectoryEnd = _getRootDirectoryEndOffset();
if (rootDirectoryEnd == npos)
{
return false;
}
size_t rootNameEnd = _getRootNameEndOffset();
if (rootNameEnd == rootDirectoryEnd)
{
return false;
}
return true;
}
Path getRoot() const
{
size_t rootDirectoryEnd = _getRootDirectoryEndOffset();
if (rootDirectoryEnd == npos)
{
return {};
}
return Path(Sanitized, m_pathString.substr(0, rootDirectoryEnd));
}
Path concat(const Path& toAppend) const
{
if (isEmpty())
{
return toAppend;
}
if (toAppend.isEmpty())
{
return *this;
}
std::string total;
total.reserve(m_pathString.length() + toAppend.m_pathString.length());
total.append(m_pathString);
total.append(toAppend.m_pathString);
return Path(Sanitized, std::move(total));
}
Path join(const Path& toJoin) const;
Path& operator/=(const Path& path)
{
return *this = *this / path;
}
Path& operator+=(const Path& path)
{
return *this = *this + path;
}
Path& replaceExtension(const Path& newExtension);
Path getAbsolute(const Path& root = Path()) const
{
if (isAbsolute() || root.isEmpty())
{
return this->getNormalized();
}
return root.join(*this).getNormalized();
}
Path getNormalized() const;
Path& normalize()
{
return *this = getNormalized();
}
bool isAbsolute() const noexcept;
bool isRelative() const
{
return !isAbsolute();
}
Path getRelative(const Path& base) const noexcept;
private:
static constexpr char kDotChar = '.';
static constexpr char kForwardSlashChar = '/';
static constexpr char kColonChar = ':';
struct Sanitized_t
{
};
constexpr static Sanitized_t Sanitized{};
Path(Sanitized_t, std::string s) : m_pathString(std::move(s))
{
// Pre-sanitized, so no need to do it again
}
enum class PathTokenType
{
Slash,
RootName,
Dot,
DotDot,
Name
};
// Helper function to parse next path token from `[bufferStart, bufferEnd)` (bufferEnd is an off-the-end pointer).
// On success returns pointer immediately after the token data and returns token type in the
// resultType. On failure returns nullptr and `resultType` is unchanged.
// Note: it doesn't determine if a Name is a RootName. (RootName is added to enum for convenience)
static const char* _getTokenEnd(const char* bufferBegin, const char* bufferEnd, PathTokenType& resultType)
{
if (bufferBegin == nullptr || bufferEnd == nullptr || bufferEnd <= bufferBegin)
{
return nullptr;
}
// Trying to find the next slash
constexpr static auto kForwardSlashChar_ = kForwardSlashChar; // CC-1110
const char* tokenEnd = std::find(bufferBegin, bufferEnd, kForwardSlashChar_);
// If found a slash as the first symbol then return pointer to the data after it
if (tokenEnd == bufferBegin)
{
resultType = PathTokenType::Slash;
return tokenEnd + 1;
}
// If no slash found we consider all passed data as a single token
if (!tokenEnd)
{
tokenEnd = bufferEnd;
}
const size_t tokenSize = tokenEnd - bufferBegin;
if (tokenSize == 1 && *bufferBegin == kDotChar)
{
resultType = PathTokenType::Dot;
}
else if (tokenSize == 2 && bufferBegin[0] == kDotChar && bufferBegin[1] == kDotChar)
{
resultType = PathTokenType::DotDot;
}
else
{
resultType = PathTokenType::Name;
}
return tokenEnd;
}
size_t _getFilenameOffset() const noexcept
{
if (isEmpty())
{
return npos;
}
// Find the last slash
size_t offset = m_pathString.rfind(kForwardSlashChar);
if (offset == npos)
{
// Not empty, so no slash means only filename
return 0;
}
// If the slash is the end, no filename
if ((offset + 1) == m_pathString.length())
{
return npos;
}
return offset + 1;
}
size_t _getExtensionOffset() const noexcept
{
size_t filename = _getFilenameOffset();
if (filename == npos)
{
return npos;
}
size_t dot = m_pathString.rfind(kDotChar);
// No extension if:
// - No dot in filename portion (dot not found or last dot proceeds filename)
// - filename ends with a dot
if (dot == npos || dot < filename || dot == (m_pathString.length() - 1))
{
return npos;
}
// If the only dot found starts the filename, we don't consider that an extension
return dot == filename ? npos : dot;
}
size_t _getRootNameEndOffset() const noexcept
{
if (isEmpty())
{
return npos;
}
if (m_pathString.length() < 2)
{
return 0;
}
#if CARB_PLATFORM_WINDOWS
// Check if the path starts with a drive letter and colon (e.g. "C:/...")
if (m_pathString.at(1) == kColonChar && std::isalpha(m_pathString.at(0)))
{
return 2;
}
#endif
// Check if the path is a UNC path (e.g. "//server/location/...")
// This simple check is two slashes and not a slash
if (m_pathString.length() >= 3 && m_pathString.at(0) == kForwardSlashChar &&
m_pathString.at(1) == kForwardSlashChar && m_pathString.at(2) != kForwardSlashChar)
{
// Silence GCC 11 with C++20 warnings about string bounds (gcc bug 97185):
// `__builtin_memchr` specified bound 18446744073709551612 exceeds maximum object size 9223372036854775807
CARB_GNUC_ONLY(if (long(m_pathString.size()) < 0) __builtin_unreachable();)
// Find the next slash
size_t offset = m_pathString.find(kForwardSlashChar, 3);
return offset == npos ? m_pathString.length() : offset;
}
return 0;
}
size_t _getRelativePartOffset(size_t rootNameEnd = npos) const noexcept
{
size_t offset = rootNameEnd != npos ? rootNameEnd : _getRootNameEndOffset();
if (offset == npos)
return npos;
// Find the first non-slash character
constexpr static auto kForwardSlashChar_ = kForwardSlashChar; // CC-1110
return m_pathString.find_first_not_of(&kForwardSlashChar_, offset, 1);
}
size_t _getRootDirectoryEndOffset() const noexcept
{
size_t rootNameEnd = _getRootNameEndOffset();
size_t relativePart = _getRelativePartOffset(rootNameEnd);
if (relativePart != rootNameEnd)
{
if (rootNameEnd >= m_pathString.length())
{
return rootNameEnd;
}
return rootNameEnd + 1;
}
return rootNameEnd;
}
// Patching paths in the constructors (using external string data) if needed
void _sanitizePath()
{
#if CARB_PLATFORM_WINDOWS
constexpr char kBackwardSlashChar = '\\';
// changing the backward slashes for Windows to forward ones
for (char& c : m_pathString)
{
if (c == kBackwardSlashChar)
{
c = kForwardSlashChar;
}
}
#endif
}
std::string m_pathString;
};
inline Path operator+(const Path& left, const Path& right)
{
return left.concat(right);
}
inline Path operator+(const Path& left, const char* right)
{
return left.concat(right);
}
inline Path operator+(const Path& left, const std::string& right)
{
return left.concat(right);
}
inline Path operator+(const Path& left, const omni::string& right)
{
return left.concat(right);
}
inline Path operator+(const char* left, const Path& right)
{
return Path(left).concat(right);
}
inline Path operator+(const std::string& left, const Path& right)
{
return Path(left).concat(right);
}
inline Path operator+(const omni::string& left, const Path& right)
{
return Path(left).concat(right);
}
inline Path operator/(const Path& left, const Path& right)
{
return left.join(right);
}
inline Path getPathParent(std::string path)
{
return Path(std::move(path)).getParent();
}
inline Path getPathExtension(std::string path)
{
return Path(std::move(path)).getExtension();
}
inline Path getPathStem(std::string path)
{
return Path(std::move(path)).getStem();
}
inline Path getPathRelative(std::string path, std::string base)
{
return Path(std::move(path)).getRelative(Path(std::move(base)));
}
inline bool operator==(const std::string& left, const Path& right)
{
return right == left;
}
inline bool operator==(const char* left, const Path& right)
{
return right == left;
}
inline bool operator!=(const std::string& left, const Path& right)
{
return right != left;
}
inline bool operator!=(const char* left, const Path& right)
{
return right != left;
}
// Implementations of large public functions
inline Path Path::getParent() const
{
size_t parentPathEnd = _getFilenameOffset();
if (parentPathEnd == npos)
parentPathEnd = m_pathString.length();
size_t slashesDataStart = 0;
if (hasRootDirectory())
{
slashesDataStart += _getRootDirectoryEndOffset();
}
while (parentPathEnd > slashesDataStart && m_pathString.at(parentPathEnd - 1) == kForwardSlashChar)
{
--parentPathEnd;
}
if (parentPathEnd == 0)
{
return {};
}
return Path(Sanitized, m_pathString.substr(0, parentPathEnd));
}
inline Path Path::join(const Path& joinedPart) const
{
if (isEmpty())
{
return joinedPart;
}
if (joinedPart.isEmpty())
{
return *this;
}
const bool needSeparator =
(m_pathString.back() != kForwardSlashChar) && (joinedPart.m_pathString.front() != kForwardSlashChar);
std::string joined;
joined.reserve(m_pathString.length() + joinedPart.m_pathString.length() + (needSeparator ? 1 : 0));
joined.assign(m_pathString);
if (needSeparator)
joined.push_back(kForwardSlashChar);
joined.append(joinedPart.m_pathString);
return Path(Sanitized, std::move(joined));
}
inline Path& Path::replaceExtension(const Path& newExtension)
{
CARB_ASSERT(std::addressof(newExtension) != this);
// Erase the current extension
size_t ext = _getExtensionOffset();
if (ext != npos)
{
m_pathString.erase(ext);
}
// If the new extension is empty, we're done
if (newExtension.isEmpty())
{
return *this;
}
// Append a dot if there isn't one in the new extension
if (newExtension.m_pathString.front() != kDotChar)
{
m_pathString.push_back(kDotChar);
}
// Append new extension
m_pathString.append(newExtension.m_pathString);
return *this;
}
inline Path Path::getNormalized() const
{
if (isEmpty())
{
return {};
}
enum class NormalizePartType
{
Slash,
RootName,
RootSlash,
Dot,
DotDot,
Name,
Error
};
struct ParsedPathPartDescription
{
NormalizePartType type;
const char* data;
size_t size;
ParsedPathPartDescription(const char* partData, size_t partSize, PathTokenType partType)
: data(partData), size(partSize)
{
switch (partType)
{
case PathTokenType::Slash:
type = NormalizePartType::Slash;
break;
case PathTokenType::RootName:
type = NormalizePartType::RootName;
break;
case PathTokenType::Dot:
type = NormalizePartType::Dot;
break;
case PathTokenType::DotDot:
type = NormalizePartType::DotDot;
break;
case PathTokenType::Name:
type = NormalizePartType::Name;
break;
default:
type = NormalizePartType::Error;
CARB_LOG_ERROR("Invalid internal token state while normalizing a path");
CARB_ASSERT(false);
break;
}
}
ParsedPathPartDescription(const char* partData, size_t partSize, NormalizePartType partType)
: type(partType), data(partData), size(partSize)
{
}
};
std::vector<ParsedPathPartDescription> resultPathTokens;
size_t prevTokenEndOffset = _getRootDirectoryEndOffset();
const char* prevTokenEnd = prevTokenEndOffset == npos ? nullptr : m_pathString.data() + prevTokenEndOffset;
const char* pathDataStart = m_pathString.data();
const size_t pathDataLength = getLength();
if (prevTokenEnd && prevTokenEnd > pathDataStart)
{
// Adding the root name and the root directory as different elements
const char* possibleSlashPos = prevTokenEnd - 1;
if (*possibleSlashPos == kForwardSlashChar)
{
if (possibleSlashPos > pathDataStart)
{
resultPathTokens.emplace_back(
pathDataStart, static_cast<size_t>(possibleSlashPos - pathDataStart), PathTokenType::RootName);
}
constexpr static auto kForwardSlashChar_ = kForwardSlashChar; // CC-1110
resultPathTokens.emplace_back(&kForwardSlashChar_, 1, NormalizePartType::RootSlash);
}
else
{
resultPathTokens.emplace_back(
pathDataStart, static_cast<size_t>(prevTokenEnd - pathDataStart), PathTokenType::RootName);
}
}
else
{
prevTokenEnd = pathDataStart;
}
bool alreadyNormalized = true;
const char* bufferEnd = pathDataStart + pathDataLength;
PathTokenType curTokenType = PathTokenType::Name;
for (const char* curTokenEnd = _getTokenEnd(prevTokenEnd, bufferEnd, curTokenType); curTokenEnd != nullptr;
prevTokenEnd = curTokenEnd, curTokenEnd = _getTokenEnd(prevTokenEnd, bufferEnd, curTokenType))
{
switch (curTokenType)
{
case PathTokenType::Slash:
if (resultPathTokens.empty() || resultPathTokens.back().type == NormalizePartType::Slash ||
resultPathTokens.back().type == NormalizePartType::RootSlash)
{
// Skip if we already have a slash at the end
alreadyNormalized = false;
continue;
}
break;
case PathTokenType::Dot:
// Just skip it
alreadyNormalized = false;
continue;
case PathTokenType::DotDot:
if (resultPathTokens.empty())
{
break;
}
// Check if the previous element is a part of the root name (even without a slash) and skip dot-dot in
// such case
if (resultPathTokens.back().type == NormalizePartType::RootName ||
resultPathTokens.back().type == NormalizePartType::RootSlash)
{
alreadyNormalized = false;
continue;
}
if (resultPathTokens.size() > 1)
{
CARB_ASSERT(resultPathTokens.back().type == NormalizePartType::Slash);
const NormalizePartType tokenTypeBeforeSlash = resultPathTokens[resultPathTokens.size() - 2].type;
// Remove <name>/<dot-dot> pattern
if (tokenTypeBeforeSlash == NormalizePartType::Name)
{
resultPathTokens.pop_back(); // remove the last slash
resultPathTokens.pop_back(); // remove the last named token
alreadyNormalized = false;
continue; // and we skip the addition of the dot-dot
}
}
break;
case PathTokenType::Name:
// No special processing needed
break;
default:
CARB_LOG_ERROR("Invalid internal state while normalizing the path {%s}", getStringBuffer());
CARB_ASSERT(false);
alreadyNormalized = false;
continue;
}
resultPathTokens.emplace_back(prevTokenEnd, static_cast<size_t>(curTokenEnd - prevTokenEnd), curTokenType);
}
if (resultPathTokens.empty())
{
constexpr static auto kDotChar_ = kDotChar; // CC-1110
return Path(Sanitized, std::string(&kDotChar_, 1));
}
else if (resultPathTokens.back().type == NormalizePartType::Slash && resultPathTokens.size() > 1)
{
// Removing the trailing slash for special cases like "./" and "../"
const size_t indexOfTokenBeforeSlash = resultPathTokens.size() - 2;
const NormalizePartType typeOfTokenBeforeSlash = resultPathTokens[indexOfTokenBeforeSlash].type;
if (typeOfTokenBeforeSlash == NormalizePartType::Dot || typeOfTokenBeforeSlash == NormalizePartType::DotDot)
{
resultPathTokens.pop_back();
alreadyNormalized = false;
}
}
if (alreadyNormalized)
{
return *this;
}
size_t totalSize = 0;
for (auto& token : resultPathTokens)
totalSize += token.size;
std::string joined;
joined.reserve(totalSize);
for (auto& token : resultPathTokens)
joined.append(token.data, token.size);
return Path(Sanitized, std::move(joined));
}
inline bool Path::isAbsolute() const noexcept
{
#if CARB_POSIX
return !isEmpty() && m_pathString[0] == kForwardSlashChar;
#elif CARB_PLATFORM_WINDOWS
// Drive root (D:/abc) case. This is the only position where : is allowed on windows. Checking for separator is
// important, because D:temp.txt is a relative path on windows.
const char* pathDataStart = m_pathString.data();
const size_t pathDataLength = getLength();
if (pathDataLength > 2 && pathDataStart[1] == kColonChar && pathDataStart[2] == kForwardSlashChar)
return true;
// Drive letter (D:) case
if (pathDataLength == 2 && pathDataStart[1] == kColonChar)
return true;
// extended drive letter path (ie: prefixed with "//./D:").
if (pathDataLength > 4 && pathDataStart[0] == kForwardSlashChar && pathDataStart[1] == kForwardSlashChar &&
pathDataStart[2] == kDotChar && pathDataStart[3] == kForwardSlashChar)
{
// at least a drive name was specified.
if (pathDataLength > 6 && pathDataStart[5] == kColonChar)
{
// a drive plus an absolute path was specified (ie: "//./d:/abc") => succeed.
if (pathDataStart[6] == kForwardSlashChar)
return true;
// a drive and relative path was specified (ie: "//./d:temp.txt") => fail. We need to
// specifically fail here because this path would also get picked up by the generic
// special path check below and report success erroneously.
else
return false;
}
// requesting the full drive volume (ie: "//./d:") => report absolute to match behavior
// in the "d:" case above.
if (pathDataLength == 6 && pathDataStart[5] == kColonChar)
return true;
}
// check for special paths. This includes all windows paths that begin with "\\" (converted
// to Unix path separators for our purposes). This class of paths includes extended path
// names (ie: prefixed with "\\?\"), device names (ie: prefixed with "\\.\"), physical drive
// paths (ie: prefixed with "\\.\PhysicalDrive<n>"), removable media access (ie: "\\.\X:")
// COM ports (ie: "\\.\COM*"), and UNC paths (ie: prefixed with "\\servername\sharename\").
//
// Note that it is not necessarily sufficient to get absolute vs relative based solely on
// the "//" prefix here without needing to dig further into the specific name used and what
// it actually represents. For now, we'll just assume that device, drive, volume, and
// port names will not be used here and treat it as a UNC path. Since all extended paths
// and UNC paths must always be absolute, this should hold up at least for those. If a
// path for a drive, volume, or device is actually passed in here, it will still be treated
// as though it were an absolute path. The results of using such a path further may be
// undefined however.
if (pathDataLength > 2 && pathDataStart[0] == kForwardSlashChar && pathDataStart[1] == kForwardSlashChar &&
pathDataStart[2] != kForwardSlashChar)
return true;
return false;
#else
CARB_UNSUPPORTED_PLATFORM();
#endif
}
inline Path Path::getRelative(const Path& base) const noexcept
{
// checking if the operation is possible
if (isAbsolute() != base.isAbsolute() || (!hasRootDirectory() && base.hasRootDirectory()) ||
getRootName() != base.getRootName())
{
return {};
}
PathTokenType curPathTokenType = PathTokenType::RootName;
size_t curPathTokenEndOffset = _getRootDirectoryEndOffset();
const char* curPathTokenEnd = curPathTokenEndOffset == npos ? nullptr : m_pathString.data() + curPathTokenEndOffset;
const char* curPathTokenStart = curPathTokenEnd;
const char* curPathEnd = m_pathString.data() + m_pathString.length();
PathTokenType basePathTokenType = PathTokenType::RootName;
size_t basePathTokenEndOffset = base._getRootDirectoryEndOffset();
const char* basePathTokenEnd =
basePathTokenEndOffset == npos ? nullptr : base.m_pathString.data() + basePathTokenEndOffset;
const char* basePathEnd = base.m_pathString.data() + base.m_pathString.length();
// finding the first mismatch
for (;;)
{
curPathTokenStart = curPathTokenEnd;
curPathTokenEnd = _getTokenEnd(curPathTokenEnd, curPathEnd, curPathTokenType);
const char* baseTokenStart = basePathTokenEnd;
basePathTokenEnd = _getTokenEnd(basePathTokenEnd, basePathEnd, basePathTokenType);
if (!curPathTokenEnd || !basePathTokenEnd)
{
// Checking if both are null
if (curPathTokenEnd == basePathTokenEnd)
{
constexpr static auto kDotChar_ = kDotChar; // CC-1110
return Path(Sanitized, std::string(&kDotChar_, 1));
}
break;
}
if (curPathTokenType != basePathTokenType ||
!std::equal(curPathTokenStart, curPathTokenEnd, baseTokenStart, basePathTokenEnd))
{
break;
}
}
int requiredDotDotCount = 0;
while (basePathTokenEnd)
{
if (basePathTokenType == PathTokenType::DotDot)
{
--requiredDotDotCount;
}
else if (basePathTokenType == PathTokenType::Name)
{
++requiredDotDotCount;
}
basePathTokenEnd = _getTokenEnd(basePathTokenEnd, basePathEnd, basePathTokenType);
}
if (requiredDotDotCount < 0)
{
return {};
}
if (requiredDotDotCount == 0 && !curPathTokenEnd)
{
constexpr static auto kDotChar_ = kDotChar; // CC-1110
return Path(Sanitized, std::string(&kDotChar_, 1));
}
const size_t leftoverCurPathSymbols = curPathTokenEnd != nullptr ? curPathEnd - curPathTokenStart : 0;
constexpr static char kDotDotString[] = "..";
constexpr static size_t kDotDotStrlen = CARB_COUNTOF(kDotDotString) - 1;
const size_t requiredResultSize = ((1 /* '/' */) + (kDotDotStrlen)) * requiredDotDotCount + leftoverCurPathSymbols;
Path result;
result.m_pathString.reserve(requiredResultSize);
if (requiredDotDotCount > 0)
{
result.m_pathString.append(kDotDotString, kDotDotStrlen);
--requiredDotDotCount;
for (int i = 0; i < requiredDotDotCount; ++i)
{
result.m_pathString.push_back(kForwardSlashChar);
result.m_pathString.append(kDotDotString, kDotDotStrlen);
}
}
bool needsSeparator = !result.m_pathString.empty();
while (curPathTokenEnd)
{
if (curPathTokenType != PathTokenType::Slash)
{
if (CARB_LIKELY(needsSeparator))
{
result.m_pathString += kForwardSlashChar;
}
else
{
needsSeparator = true;
}
result.m_pathString.append(curPathTokenStart, curPathTokenEnd - curPathTokenStart);
}
curPathTokenStart = curPathTokenEnd;
curPathTokenEnd = _getTokenEnd(curPathTokenEnd, curPathEnd, curPathTokenType);
}
return result;
}
} // namespace extras
} // namespace carb