2024-01-13 08:11:49 +02:00
|
|
|
#pragma once
|
|
|
|
/**
|
|
|
|
* @file
|
|
|
|
*
|
|
|
|
* Pure (no IO) infrastructure just for defining other path types;
|
|
|
|
* should not be used directly outside of utilities.
|
|
|
|
*/
|
|
|
|
#include <string>
|
|
|
|
#include <string_view>
|
|
|
|
|
|
|
|
namespace nix {
|
|
|
|
|
2024-01-30 00:16:18 +02:00
|
|
|
/**
|
|
|
|
* Unix-style path primives.
|
|
|
|
*
|
|
|
|
* Nix'result own "logical" paths are always Unix-style. So this is always
|
|
|
|
* used for that, and additionally used for native paths on Unix.
|
|
|
|
*/
|
|
|
|
struct UnixPathTrait
|
|
|
|
{
|
|
|
|
using CharT = char;
|
|
|
|
|
|
|
|
using String = std::string;
|
|
|
|
|
|
|
|
using StringView = std::string_view;
|
|
|
|
|
|
|
|
constexpr static char preferredSep = '/';
|
|
|
|
|
|
|
|
static inline bool isPathSep(char c)
|
|
|
|
{
|
|
|
|
return c == '/';
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline size_t findPathSep(StringView path, size_t from = 0)
|
|
|
|
{
|
|
|
|
return path.find('/', from);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline size_t rfindPathSep(StringView path, size_t from = StringView::npos)
|
|
|
|
{
|
|
|
|
return path.rfind('/', from);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2024-01-14 21:30:25 +02:00
|
|
|
/**
|
|
|
|
* Windows-style path primitives.
|
|
|
|
*
|
|
|
|
* The character type is a parameter because while windows paths rightly
|
|
|
|
* work over UTF-16 (*) using `wchar_t`, at the current time we are
|
|
|
|
* often manipulating them converted to UTF-8 (*) using `char`.
|
|
|
|
*
|
|
|
|
* (Actually neither are guaranteed to be valid unicode; both are
|
|
|
|
* arbitrary non-0 8- or 16-bit bytes. But for charcters with specifical
|
|
|
|
* meaning like '/', '\\', ':', etc., we refer to an encoding scheme,
|
|
|
|
* and also for sake of UIs that display paths a text.)
|
|
|
|
*/
|
|
|
|
template<class CharT0>
|
|
|
|
struct WindowsPathTrait
|
|
|
|
{
|
|
|
|
using CharT = CharT0;
|
|
|
|
|
|
|
|
using String = std::basic_string<CharT>;
|
|
|
|
|
|
|
|
using StringView = std::basic_string_view<CharT>;
|
|
|
|
|
|
|
|
constexpr static CharT preferredSep = '\\';
|
|
|
|
|
|
|
|
static inline bool isPathSep(CharT c)
|
|
|
|
{
|
|
|
|
return c == '/' || c == preferredSep;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t findPathSep(StringView path, size_t from = 0)
|
|
|
|
{
|
|
|
|
size_t p1 = path.find('/', from);
|
|
|
|
size_t p2 = path.find(preferredSep, from);
|
|
|
|
return p1 == String::npos ? p2 :
|
|
|
|
p2 == String::npos ? p1 :
|
|
|
|
std::min(p1, p2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t rfindPathSep(StringView path, size_t from = String::npos)
|
|
|
|
{
|
|
|
|
size_t p1 = path.rfind('/', from);
|
|
|
|
size_t p2 = path.rfind(preferredSep, from);
|
|
|
|
return p1 == String::npos ? p2 :
|
|
|
|
p2 == String::npos ? p1 :
|
|
|
|
std::max(p1, p2);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @todo Revisit choice of `char` or `wchar_t` for `WindowsPathTrait`
|
|
|
|
* argument.
|
|
|
|
*/
|
|
|
|
using NativePathTrait =
|
|
|
|
#ifdef _WIN32
|
|
|
|
WindowsPathTrait<char>
|
|
|
|
#else
|
|
|
|
UnixPathTrait
|
|
|
|
#endif
|
|
|
|
;
|
|
|
|
|
|
|
|
|
2024-01-13 08:11:49 +02:00
|
|
|
/**
|
|
|
|
* Core pure path canonicalization algorithm.
|
|
|
|
*
|
|
|
|
* @param hookComponent
|
|
|
|
* A callback which is passed two arguments,
|
|
|
|
* references to
|
|
|
|
*
|
|
|
|
* 1. the result so far
|
|
|
|
*
|
|
|
|
* 2. the remaining path to resolve
|
|
|
|
*
|
|
|
|
* This is a chance to modify those two paths in arbitrary way, e.g. if
|
|
|
|
* "result" points to a symlink.
|
|
|
|
*/
|
2024-01-30 00:16:18 +02:00
|
|
|
template<class PathDict>
|
|
|
|
typename PathDict::String canonPathInner(
|
|
|
|
typename PathDict::StringView remaining,
|
2024-01-13 08:11:49 +02:00
|
|
|
auto && hookComponent)
|
|
|
|
{
|
|
|
|
assert(remaining != "");
|
|
|
|
|
2024-01-30 00:16:18 +02:00
|
|
|
typename PathDict::String result;
|
2024-01-13 08:11:49 +02:00
|
|
|
result.reserve(256);
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
/* Skip slashes. */
|
2024-01-30 00:16:18 +02:00
|
|
|
while (!remaining.empty() && PathDict::isPathSep(remaining[0]))
|
2024-01-13 08:11:49 +02:00
|
|
|
remaining.remove_prefix(1);
|
|
|
|
|
|
|
|
if (remaining.empty()) break;
|
|
|
|
|
|
|
|
auto nextComp = ({
|
2024-01-30 00:16:18 +02:00
|
|
|
auto nextPathSep = PathDict::findPathSep(remaining);
|
2024-01-13 08:11:49 +02:00
|
|
|
nextPathSep == remaining.npos ? remaining : remaining.substr(0, nextPathSep);
|
|
|
|
});
|
|
|
|
|
|
|
|
/* Ignore `.'. */
|
|
|
|
if (nextComp == ".")
|
|
|
|
remaining.remove_prefix(1);
|
|
|
|
|
|
|
|
/* If `..', delete the last component. */
|
|
|
|
else if (nextComp == "..")
|
|
|
|
{
|
2024-01-30 00:16:18 +02:00
|
|
|
if (!result.empty()) result.erase(PathDict::rfindPathSep(result));
|
2024-01-13 08:11:49 +02:00
|
|
|
remaining.remove_prefix(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Normal component; copy it. */
|
|
|
|
else {
|
2024-01-30 00:16:18 +02:00
|
|
|
result += PathDict::preferredSep;
|
|
|
|
if (const auto slash = PathDict::findPathSep(remaining); slash == result.npos) {
|
2024-01-13 08:11:49 +02:00
|
|
|
result += remaining;
|
|
|
|
remaining = {};
|
|
|
|
} else {
|
|
|
|
result += remaining.substr(0, slash);
|
|
|
|
remaining = remaining.substr(slash);
|
|
|
|
}
|
|
|
|
|
|
|
|
hookComponent(result, remaining);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result.empty())
|
2024-01-30 00:16:18 +02:00
|
|
|
result = typename PathDict::String { PathDict::preferredSep };
|
2024-01-13 08:11:49 +02:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|