CMake/Utilities/std/cm/filesystem
Brad King ee9805ccd1 cm/filesystem: Fix crash with pre-C++11 std::string GNU ABI in C++17
The `remove_filename` and `replace_extension` methods compute an offset
between the whole path in a `std::string` and a part of a path in a
`std::string_view`.  This is done by subtracting their `.data()`
pointers.  However, C++17 adds a non-const `.data()` through which
modification of the string is allowed.  This means the copy-on-write
implementation used by the pre-C++11 std::string GNU ABI must reallocate
if the string has been copied.  Our subtraction then computes an offset
between two different allocations, which is undefined behavior.

The workaround in commit b3ca4f9ad1 (cm/filesystem: Work around crash
when compiled for CYGWIN/MSYS runtime, 2021-04-22, v3.21.0-rc1~271^2~2)
avoided the problem by calling the non-const `.data()` to reallocate
before constructing the `string_view`.  Instead, explicitly call the
const `.data()` method on the string, which does not reallocate.

Fixes: #22090, #23328
2022-10-20 18:31:50 -04:00

1177 lines
31 KiB
C++

// -*-c++-*-
// vim: set ft=cpp:
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include "cmSTL.hxx" // IWYU pragma: keep
#if defined(CMake_HAVE_CXX_FILESYSTEM)
# include <filesystem> // IWYU pragma: export
#else
# include <cstddef>
# include <cstdint>
# include <iostream>
# include <iterator>
# include <memory>
# include <string>
# include <utility>
# include <cm/iomanip>
# include <cm/string_view>
# include <cm/type_traits>
# include <cmext/iterator>
# if defined(_WIN32) && !defined(__CYGWIN__)
# include <algorithm>
# endif
#endif
namespace cm {
namespace filesystem {
#if defined(CMake_HAVE_CXX_FILESYSTEM)
using std::filesystem::path;
using std::filesystem::swap;
using std::filesystem::hash_value;
#else
# if !defined(CM_FILESYSTEM_SOURCE_TRAITS_ITERATOR)
// Oracle DeveloperStudio C++ compiler on Solaris/Sparc fails to compile
// the source_traits for iterator check. So disable it for now.
# define CM_FILESYSTEM_SOURCE_TRAITS_ITERATOR 0
# endif
namespace internals {
class path_parser;
class unicode_helper
{
protected:
using utf8_state = unsigned char;
static const utf8_state s_start = 0;
static const utf8_state s_reject = 8;
static inline bool in_range(std::uint32_t c, std::uint32_t lo,
std::uint32_t hi)
{
return (static_cast<std::uint32_t>(c - lo) < (hi - lo + 1));
}
static inline bool is_surrogate(std::uint32_t c)
{
return in_range(c, 0xd800, 0xdfff);
}
static inline bool is_high_surrogate(std::uint32_t c)
{
return (c & 0xfffffc00) == 0xd800;
}
static inline bool is_low_surrogate(std::uint32_t c)
{
return (c & 0xfffffc00) == 0xdc00;
}
static void append(std::string& str, std::uint32_t codepoint);
static utf8_state decode(const utf8_state state, const std::uint8_t fragment,
std::uint32_t& codepoint);
};
template <typename Char, typename = void>
class unicode
{
};
template <typename Char>
class unicode<Char, typename std::enable_if<(sizeof(Char) == 4)>::type>
: public unicode_helper
{
public:
// UTF32 -> UTF8
static std::string to_utf8(const std::wstring& str)
{
std::string result;
for (auto c : str) {
append(result, c);
}
return result;
}
static std::string to_utf8(Char c)
{
std::string result;
append(result, c);
return result;
}
// UTF8 -> UTF32
static std::wstring from_utf8(const std::string& str)
{
std::wstring result;
result.reserve(str.length());
auto iter = str.begin();
utf8_state state = s_start;
std::uint32_t codepoint = 0;
while (iter < str.end()) {
if ((state = decode(state, static_cast<std::uint8_t>(*iter++),
codepoint)) == s_start) {
result += static_cast<std::wstring::value_type>(codepoint);
codepoint = 0;
} else if (state == s_reject) {
result += static_cast<std::wstring::value_type>(0xfffd);
state = s_start;
codepoint = 0;
}
}
if (state) {
result += static_cast<std::wstring::value_type>(0xfffd);
}
return result;
}
static std::wstring from_utf8(char c)
{
std::wstring result;
utf8_state state = s_start;
std::uint32_t codepoint = 0;
if ((state = decode(state, static_cast<std::uint8_t>(c), codepoint)) ==
s_start) {
result += static_cast<std::wstring::value_type>(codepoint);
} else {
result += static_cast<std::wstring::value_type>(0xfffd);
}
return result;
}
};
template <typename Char>
class unicode<Char, typename std::enable_if<(sizeof(Char) == 2)>::type>
: public unicode_helper
{
public:
// UTF16 -> UTF8
static std::string to_utf8(const std::wstring& str)
{
std::string result;
for (auto iter = str.begin(); iter != str.end(); ++iter) {
std::uint32_t c = *iter;
if (is_surrogate(c)) {
++iter;
if (iter != str.end() && is_high_surrogate(c) &&
is_low_surrogate(*iter)) {
append(result, (std::uint32_t(c) << 10) + *iter - 0x35fdc00);
} else {
append(result, 0xfffd);
if (iter == str.end()) {
break;
}
}
} else {
append(result, c);
}
}
return result;
}
static std::string to_utf8(Char c)
{
std::string result;
if (is_surrogate(c)) {
append(result, 0xfffd);
} else {
append(result, c);
}
return result;
}
// UTF8 -> UTF16
static std::wstring from_utf8(const std::string& str)
{
std::wstring result;
result.reserve(str.length());
auto iter = str.begin();
utf8_state state = s_start;
std::uint32_t codepoint = 0;
while (iter < str.end()) {
if ((state = decode(state, static_cast<std::uint8_t>(*iter++),
codepoint)) == s_start) {
if (codepoint <= 0xffff) {
result += static_cast<std::wstring::value_type>(codepoint);
} else {
codepoint -= 0x10000;
result +=
static_cast<std::wstring::value_type>((codepoint >> 10) + 0xd800);
result += static_cast<std::wstring::value_type>((codepoint & 0x3ff) +
0xdc00);
}
codepoint = 0;
} else if (state == s_reject) {
result += static_cast<std::wstring::value_type>(0xfffd);
state = s_start;
codepoint = 0;
}
}
if (state) {
result += static_cast<std::wstring::value_type>(0xfffd);
}
return result;
}
static std::wstring from_utf8(char c)
{
std::wstring result;
utf8_state state = s_start;
std::uint32_t codepoint = 0;
if ((state = decode(state, static_cast<std::uint8_t>(c), codepoint)) ==
s_start) {
if (codepoint <= 0xffff) {
result += static_cast<std::wstring::value_type>(codepoint);
} else {
codepoint -= 0x10000;
result +=
static_cast<std::wstring::value_type>((codepoint >> 10) + 0xd800);
result +=
static_cast<std::wstring::value_type>((codepoint & 0x3ff) + 0xdc00);
}
} else {
result += static_cast<std::wstring::value_type>(0xfffd);
}
return result;
}
};
template <typename In, typename Out>
class unicode_converter;
template <>
class unicode_converter<char, wchar_t>
{
public:
std::wstring operator()(const std::string& in)
{
return unicode<wchar_t>::from_utf8(in);
}
std::wstring operator()(const char* in)
{
return unicode<wchar_t>::from_utf8(in);
}
std::wstring operator()(char in) { return unicode<wchar_t>::from_utf8(in); }
};
template <>
class unicode_converter<wchar_t, char>
{
public:
std::string operator()(const std::wstring& in)
{
return unicode<wchar_t>::to_utf8(in);
}
std::string operator()(const wchar_t* in)
{
return unicode<wchar_t>::to_utf8(in);
}
std::string operator()(wchar_t in) { return unicode<wchar_t>::to_utf8(in); }
};
template <>
class unicode_converter<char, char>
{
public:
std::string operator()(const std::string& in) { return in; }
std::string operator()(const char* in) { return std::string(in); }
std::string operator()(char in) { return std::string(1, in); }
};
template <>
class unicode_converter<wchar_t, wchar_t>
{
public:
std::wstring operator()(const std::wstring& in) { return in; }
std::wstring operator()(const wchar_t* in) { return std::wstring(in); }
std::wstring operator()(wchar_t in) { return std::wstring(1, in); }
};
template <typename In>
struct string_converter
{
};
template <>
struct string_converter<char>
{
// some compilers, like gcc 4.8 does not implement the following C++11
// signature:
// std::string::string(const string&, const Allocator&)
// As workaround, use char* pointer.
template <typename Char, typename Traits, typename Alloc>
static std::basic_string<Char, Traits, Alloc> to(const std::string& in,
const Alloc& a)
{
return std::basic_string<Char, Traits, Alloc>(
unicode_converter<char, Char>()(in).c_str(), a);
}
template <typename Char, typename Traits, typename Alloc>
static std::basic_string<Char, Traits, Alloc> to(const char* in,
const Alloc& a)
{
return std::basic_string<Char, Traits, Alloc>(
unicode_converter<char, Char>()(in).c_str(), a);
}
template <typename Char, typename Traits, typename Alloc>
static std::basic_string<Char, Traits, Alloc> to(char in, const Alloc& a)
{
return std::basic_string<Char, Traits, Alloc>(
unicode_converter<char, Char>()(in).c_str(), a);
}
template <typename Char>
static std::basic_string<Char> to(const std::string& in)
{
return std::basic_string<Char>(unicode_converter<char, Char>()(in));
}
template <typename Char>
static std::basic_string<Char> to(const char* in)
{
return std::basic_string<Char>(unicode_converter<char, Char>()(in));
}
template <typename Char>
static std::basic_string<Char> to(char in)
{
return std::basic_string<Char>(unicode_converter<char, Char>()(in));
}
};
template <>
struct string_converter<wchar_t>
{
// some compilers, like gcc 4.8 does not implement the following C++11
// signature:
// std::string::string(const string&, const Allocator&)
// As workaround, use char* pointer.
template <typename Char, typename Traits, typename Alloc>
static std::basic_string<Char, Traits, Alloc> to(const std::wstring& in,
const Alloc& a)
{
return std::basic_string<Char, Traits, Alloc>(
unicode_converter<wchar_t, Char>()(in).c_str(), a);
}
template <typename Char, typename Traits, typename Alloc>
static std::basic_string<Char, Traits, Alloc> to(const wchar_t* in,
const Alloc& a)
{
return std::basic_string<Char, Traits, Alloc>(
unicode_converter<wchar_t, Char>()(in).c_str(), a);
}
template <typename Char, typename Traits, typename Alloc>
static std::basic_string<Char, Traits, Alloc> to(wchar_t in, const Alloc& a)
{
return std::basic_string<Char, Traits, Alloc>(
unicode_converter<wchar_t, Char>()(in).c_str(), a);
}
template <typename Char>
static std::basic_string<Char> to(const std::wstring& in)
{
return std::basic_string<Char>(unicode_converter<wchar_t, Char>()(in));
}
template <typename Char>
static std::basic_string<Char> to(const wchar_t* in)
{
return std::basic_string<Char>(unicode_converter<wchar_t, Char>()(in));
}
template <typename Char>
static std::basic_string<Char> to(wchar_t in)
{
return std::basic_string<Char>(unicode_converter<wchar_t, Char>()(in));
}
};
template <typename T, typename = void>
struct source_traits
{
};
template <typename T, std::size_t N>
struct source_traits<T[N]>
{
using value_type = T;
};
template <typename Char, typename Traits, typename Alloc>
struct source_traits<std::basic_string<Char, Traits, Alloc>>
{
using value_type =
typename std::basic_string<Char, Traits, Alloc>::value_type;
};
template <>
struct source_traits<cm::string_view>
{
using value_type = cm::string_view::value_type;
};
# if CM_FILESYSTEM_SOURCE_TRAITS_ITERATOR
template <typename T>
struct source_traits<T, cm::enable_if_t<cm::is_iterator<T>::value, void>>
{
using value_type =
typename std::iterator_traits<typename std::decay<T>::type>::value_type;
};
# endif
template <typename In, typename Out>
struct source_converter
{
};
template <>
struct source_converter<char, char>
{
template <typename Iterator>
static void append_range(std::string& p, Iterator b, Iterator e)
{
if (b == e) {
return;
}
p.append(b, e);
}
template <typename Iterator>
static void append_range(std::string& p, Iterator b)
{
char e = '\0';
if (*b == e) {
return;
}
for (; *b != e; ++b) {
p.push_back(*b);
}
}
static void append_source(std::string& p, const cm::string_view s)
{
append_range(p, s.begin(), s.end());
}
template <typename Traits, typename Alloc>
static void append_source(std::string& p,
const std::basic_string<char, Traits, Alloc>& s)
{
append_range(p, s.begin(), s.end());
}
template <typename Source>
static void append_source(std::string& p, const Source& s)
{
append_range(p, s);
}
static void set_source(std::string& p, std::string&& s) { p = std::move(s); }
};
template <>
struct source_converter<wchar_t, char>
{
template <typename Iterator>
static void append_range(std::string& p, Iterator b, Iterator e)
{
if (b == e) {
return;
}
std::wstring tmp(b, e);
std::string dest = string_converter<wchar_t>::to<char>(tmp);
p.append(dest.begin(), dest.end());
}
template <typename Iterator>
static void append_range(std::string& p, Iterator b)
{
wchar_t e = '\0';
if (*b == e) {
return;
}
std::wstring tmp;
for (; *b != e; ++b) {
tmp.push_back(*b);
}
std::string dest = string_converter<wchar_t>::to<char>(tmp);
p.append(dest.begin(), dest.end());
}
template <typename Traits, typename Alloc>
static void append_source(std::string& p,
const std::basic_string<wchar_t, Traits, Alloc>& s)
{
append_range(p, s.begin(), s.end());
}
template <typename Source>
static void append_source(std::string& p, const Source& s)
{
append_range(p, s);
}
static void set_source(std::string& p, std::wstring&& s)
{
p = string_converter<wchar_t>::to<char>(s);
}
};
template <typename T>
struct is_pathable_string : std::false_type
{
};
template <typename Traits, typename Alloc>
struct is_pathable_string<std::basic_string<char, Traits, Alloc>>
: std::true_type
{
};
template <typename Traits, typename Alloc>
struct is_pathable_string<std::basic_string<wchar_t, Traits, Alloc>>
: std::true_type
{
};
template <>
struct is_pathable_string<cm::string_view> : std::true_type
{
};
template <typename T, typename = void>
struct is_pathable_char_array : std::false_type
{
};
template <typename T>
struct is_pathable_char_array<
T,
cm::enable_if_t<
std::is_same<char*, typename std::decay<T>::type>::value ||
std::is_same<wchar_t*, typename std::decay<T>::type>::value,
void>>
: bool_constant<std::is_same<char*, typename std::decay<T>::type>::value ||
std::is_same<wchar_t*, typename std::decay<T>::type>::value>
{
};
template <typename T, typename = void>
struct is_pathable_iterator : std::false_type
{
};
template <typename T>
struct is_pathable_iterator<
T,
cm::enable_if_t<
is_input_iterator<T>::value &&
(std::is_same<char,
typename std::iterator_traits<
typename std::decay<T>::type>::value_type>::value ||
std::is_same<wchar_t,
typename std::iterator_traits<
typename std::decay<T>::type>::value_type>::value),
void>>
: bool_constant<
std::is_same<char,
typename std::iterator_traits<
typename std::decay<T>::type>::value_type>::value ||
std::is_same<wchar_t,
typename std::iterator_traits<
typename std::decay<T>::type>::value_type>::value>
{
};
# if defined(__SUNPRO_CC) && defined(__sparc)
// Oracle DeveloperStudio C++ compiler on Solaris/Sparc fails to compile
// the full 'is_pathable' check. We use it only to improve error messages
// via 'enable_if' when calling methods with incorrect types. Just
// pretend all types are allowed so we can at least compile valid code.
template <typename T>
struct is_pathable : std::true_type
{
};
# else
template <typename T>
struct is_pathable
: bool_constant<is_pathable_string<T>::value ||
is_pathable_char_array<T>::value ||
is_pathable_iterator<T>::value>
{
};
# endif
}
class path
{
using path_type = std::string;
template <typename Source>
using enable_if_pathable =
enable_if_t<internals::is_pathable<Source>::value, path&>;
enum class filename_fragment : unsigned char
{
stem,
extension
};
public:
# if defined(_WIN32) && !defined(__CYGWIN__)
using value_type = wchar_t;
# else
using value_type = char;
# endif
using string_type = std::basic_string<value_type>;
class iterator;
using const_iterator = iterator;
enum format : unsigned char
{
auto_format,
native_format,
generic_format
};
# if defined(_WIN32) && !defined(__CYGWIN__)
static constexpr value_type preferred_separator = L'\\';
# else
static constexpr value_type preferred_separator = '/';
# endif
// Constructors
// ============
path() noexcept {}
path(const path& p)
: path_(p.path_)
{
}
path(path&& p) noexcept
: path_(std::move(p.path_))
{
}
path(string_type&& source, format fmt = auto_format)
{
(void)fmt;
internals::source_converter<value_type, path_type::value_type>::set_source(
this->path_, std::move(source));
}
template <typename Source, typename = enable_if_pathable<Source>>
path(const Source& source, format fmt = auto_format)
{
(void)fmt;
internals::source_converter<
typename internals::source_traits<Source>::value_type,
path_type::value_type>::append_source(this->path_, source);
}
template <typename Iterator, typename = enable_if_pathable<Iterator>>
path(const Iterator first, Iterator last, format fmt = auto_format)
{
(void)fmt;
internals::source_converter<
typename std::iterator_traits<Iterator>::value_type,
path_type::value_type>::append_range(this->path_, first, last);
}
~path() = default;
// Assignments
// ===========
path& operator=(const path& p)
{
if (this != &p) {
this->path_ = p.path_;
}
return *this;
}
path& operator=(path&& p) noexcept
{
if (this != &p) {
this->path_ = std::move(p.path_);
}
return *this;
}
path& operator=(string_type&& source) { return this->assign(source); }
template <typename Source, typename = enable_if_pathable<Source>>
path& operator=(const Source& source)
{
return this->assign(source);
}
path& assign(string_type&& source)
{
internals::source_converter<value_type, path_type::value_type>::set_source(
this->path_, std::move(source));
return *this;
}
template <typename Source, typename = enable_if_pathable<Source>>
path& assign(const Source& source)
{
this->path_.clear();
internals::source_converter<
typename internals::source_traits<Source>::value_type,
path_type::value_type>::append_source(this->path_, source);
return *this;
}
template <typename Iterator, typename = enable_if_pathable<Iterator>>
path& assign(Iterator first, Iterator last)
{
this->path_.clear();
internals::source_converter<
typename std::iterator_traits<Iterator>::value_type,
path_type::value_type>::append_range(this->path_, first, last);
return *this;
}
// Concatenation
// =============
path& operator/=(const path& p);
template <typename Source, typename = enable_if_pathable<Source>>
path& append(const Source& source)
{
return this->operator/=(path(source));
}
template <typename Source>
path& operator/=(const Source& source)
{
return this->append(source);
}
template <typename Iterator, typename = enable_if_pathable<Iterator>>
path& append(Iterator first, Iterator last)
{
return this->operator/=(path(first, last));
}
path& operator+=(const path& p)
{
this->path_ += p.path_;
return *this;
}
path& operator+=(const string_type& str)
{
this->path_ +=
internals::string_converter<value_type>::to<path_type::value_type>(str);
return *this;
}
path& operator+=(cm::string_view str)
{
this->path_.append(str.begin(), str.end());
return *this;
}
path& operator+=(const value_type* str)
{
this->path_ +=
internals::string_converter<value_type>::to<path_type::value_type>(str);
return *this;
}
path& operator+=(const value_type c)
{
this->path_ +=
internals::string_converter<value_type>::to<path_type::value_type>(c);
return *this;
}
template <typename Source, typename = enable_if_pathable<Source>>
path& concat(const Source& source)
{
internals::source_converter<
typename internals::source_traits<Source>::value_type,
path_type::value_type>::append_source(this->path_, source);
return *this;
}
template <typename Source>
path& operator+=(const Source& source)
{
return this->concat(source);
}
template <typename Iterator, typename = enable_if_pathable<Iterator>>
path& concat(Iterator first, Iterator last)
{
internals::source_converter<
typename std::iterator_traits<Iterator>::value_type,
path_type::value_type>::append_range(this->path_, first, last);
return *this;
}
// Modifiers
// =========
void clear() noexcept { this->path_.clear(); }
path& make_preferred()
{
# if defined(_WIN32) && !defined(__CYGWIN__)
std::replace(
this->path_.begin(), this->path_.end(), '/',
static_cast<path_type::value_type>(this->preferred_separator));
# endif
return *this;
}
path& remove_filename()
{
auto fname = this->get_filename();
if (!fname.empty()) {
this->path_.erase(fname.data() -
// Avoid C++17 non-const .data() that may reallocate.
static_cast<path_type const&>(this->path_).data());
}
return *this;
}
path& replace_filename(const path& replacement)
{
this->remove_filename();
this->operator/=(replacement);
return *this;
}
path& replace_extension(const path& replacement = path())
{
auto ext = this->get_filename_fragment(filename_fragment::extension);
if (!ext.empty()) {
this->path_.erase(ext.data() -
// Avoid C++17 non-const .data() that may reallocate.
static_cast<path_type const&>(this->path_).data());
}
if (!replacement.path_.empty()) {
if (replacement.path_[0] != '.') {
this->path_ += '.';
}
this->path_.append(replacement.path_);
}
return *this;
}
void swap(path& other) noexcept { this->path_.swap(other.path_); }
// Format observers
// ================
const string_type& native() const noexcept
{
# if defined(_WIN32) && !defined(__CYGWIN__)
this->native_path_ = internals::string_converter<
path_type::value_type>::to<string_type::value_type>(this->path_);
return this->native_path_;
# else
return this->path_;
# endif
}
const value_type* c_str() const noexcept { return this->native().c_str(); }
operator string_type() const { return this->native(); }
template <
typename Char, typename Traits = std::char_traits<Char>,
typename Alloc = std::allocator<Char>,
cm::enable_if_t<(std::is_same<Char, char>::value &&
std::is_same<Traits, std::char_traits<char>>::value) ||
(std::is_same<Char, wchar_t>::value &&
std::is_same<Traits, std::char_traits<wchar_t>>::value),
int> = 1>
std::basic_string<Char, Traits, Alloc> string(const Alloc& a = Alloc()) const
{
return internals::string_converter<path_type::value_type>::to<Char, Traits,
Alloc>(
this->path_, a);
}
const std::string string() const { return this->path_; }
std::wstring wstring() const
{
std::string out = this->string();
return internals::string_converter<path_type::value_type>::to<
std::wstring::value_type>(out);
}
template <
typename Char, typename Traits = std::char_traits<Char>,
typename Alloc = std::allocator<Char>,
cm::enable_if_t<(std::is_same<Char, char>::value &&
std::is_same<Traits, std::char_traits<char>>::value) ||
(std::is_same<Char, wchar_t>::value &&
std::is_same<Traits, std::char_traits<wchar_t>>::value),
int> = 1>
std::basic_string<Char, Traits, Alloc> generic_string(
const Alloc& a = Alloc()) const
{
return internals::string_converter<path_type::value_type>::to<Char, Traits,
Alloc>(
this->get_generic(), a);
}
std::string generic_string() const { return this->get_generic(); }
std::wstring generic_wstring() const
{
auto dest = this->generic_string();
return internals::string_converter<path_type::value_type>::to<
std::wstring::value_type>(dest);
}
// Compare
// =======
int compare(const path& p) const noexcept
{
return this->compare_path(p.path_);
}
int compare(const string_type& str) const
{
return this->compare_path(
internals::string_converter<value_type>::to<path_type::value_type>(str));
}
int compare(const value_type* str) const
{
return this->compare_path(
internals::string_converter<value_type>::to<path_type::value_type>(str));
}
int compare(cm::string_view str) const { return this->compare_path(str); }
// Generation
// ==========
path lexically_normal() const;
path lexically_relative(const path& base) const;
path lexically_proximate(const path& base) const
{
path result = this->lexically_relative(base);
return result.empty() ? *this : result;
}
// Decomposition
// =============
path root_name() const { return get_root_name(); }
path root_directory() const { return this->get_root_directory(); }
path root_path() const
{
return this->root_name().append(this->get_root_directory());
}
path relative_path() const { return this->get_relative_path(); }
path parent_path() const { return this->get_parent_path(); }
path filename() const { return this->get_filename(); }
path stem() const
{
return this->get_filename_fragment(filename_fragment::stem);
}
path extension() const
{
return this->get_filename_fragment(filename_fragment::extension);
}
// Queries
// =======
bool empty() const noexcept { return this->path_.empty(); }
bool has_root_name() const { return !this->get_root_name().empty(); }
bool has_root_directory() const
{
return !this->get_root_directory().empty();
}
bool has_root_path() const
{
return this->has_root_name() || this->has_root_directory();
}
bool has_relative_path() const { return !this->get_relative_path().empty(); }
bool has_parent_path() const { return !this->get_parent_path().empty(); }
bool has_filename() const { return !this->get_filename().empty(); }
bool has_stem() const
{
return !this->get_filename_fragment(filename_fragment::stem).empty();
}
bool has_extension() const
{
return !this->get_filename_fragment(filename_fragment::extension).empty();
}
bool is_absolute() const
{
# if defined(_WIN32) && !defined(__CYGWIN__)
return this->has_root_name() && this->has_root_directory();
# else
// For CYGWIN, root_name (i.e. //host or /cygdrive/x) is not considered.
// Same as current GNU g++ implementation (9.3).
return this->has_root_directory();
# endif
}
bool is_relative() const { return !this->is_absolute(); }
// Iterators
// =========
inline iterator begin() const;
inline iterator end() const;
// Non-members
// ===========
friend inline bool operator==(const path& lhs, const path& rhs) noexcept
{
return lhs.compare(rhs) == 0;
}
friend inline bool operator!=(const path& lhs, const path& rhs) noexcept
{
return lhs.compare(rhs) != 0;
}
friend inline bool operator<(const path& lhs, const path& rhs) noexcept
{
return lhs.compare(rhs) < 0;
}
friend inline bool operator<=(const path& lhs, const path& rhs) noexcept
{
return lhs.compare(rhs) <= 0;
}
friend inline bool operator>(const path& lhs, const path& rhs) noexcept
{
return lhs.compare(rhs) > 0;
}
friend inline bool operator>=(const path& lhs, const path& rhs) noexcept
{
return lhs.compare(rhs) >= 0;
}
friend inline path operator/(const path& lhs, const path& rhs)
{
path result(lhs);
result /= rhs;
return result;
}
template <typename Char, typename Traits>
friend inline cm::enable_if_t<
(std::is_same<Char, path::value_type>::value &&
std::is_same<Traits, std::char_traits<path::value_type>>::value) ||
(std::is_same<Char, path::path_type::value_type>::value &&
std::is_same<Traits,
std::char_traits<path::path_type::value_type>>::value),
std::basic_ostream<Char, Traits>&>
operator<<(std::basic_ostream<Char, Traits>& os, const path& p)
{
os << cm::quoted(p.string<Char, Traits>());
return os;
}
template <typename Char, typename Traits>
friend inline cm::enable_if_t<
(std::is_same<Char, path::value_type>::value &&
std::is_same<Traits, std::char_traits<path::value_type>>::value) ||
(std::is_same<Char, path::path_type::value_type>::value &&
std::is_same<Traits,
std::char_traits<path::path_type::value_type>>::value),
std::basic_istream<Char, Traits>&>
operator>>(std::basic_istream<Char, Traits>& is, path& p)
{
std::basic_string<Char, Traits> tmp;
is >> cm::quoted(tmp);
p = tmp;
return is;
}
private:
friend class iterator;
friend std::size_t hash_value(const path& p) noexcept;
path_type get_generic() const;
cm::string_view get_root_name() const;
cm::string_view get_root_directory() const;
cm::string_view get_relative_path() const;
cm::string_view get_parent_path() const;
cm::string_view get_filename() const;
cm::string_view get_filename_fragment(filename_fragment fragment) const;
int compare_path(cm::string_view str) const;
path_type path_;
# if defined(_WIN32) && !defined(__CYGWIN__)
mutable string_type native_path_;
# endif
};
class path::iterator
{
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = path;
using difference_type = std::ptrdiff_t;
using pointer = const path*;
using reference = const path&;
iterator();
iterator(const iterator& other);
~iterator();
iterator& operator=(const iterator& other);
reference operator*() const { return this->path_element_; }
pointer operator->() const { return &this->path_element_; }
iterator& operator++();
iterator operator++(int)
{
iterator it(*this);
this->operator++();
return it;
}
iterator& operator--();
iterator operator--(int)
{
iterator it(*this);
this->operator--();
return it;
}
private:
friend class path;
friend bool operator==(const iterator&, const iterator&);
iterator(const path* p, bool at_end = false);
const path* path_;
std::unique_ptr<internals::path_parser> parser_;
path path_element_;
};
inline path::iterator path::begin() const
{
return iterator(this);
}
inline path::iterator path::end() const
{
return iterator(this, true);
}
// Non-member functions
// ====================
bool operator==(const path::iterator& lhs, const path::iterator& rhs);
inline bool operator!=(const path::iterator& lhs, const path::iterator& rhs)
{
return !(lhs == rhs);
}
inline void swap(path& lhs, path& rhs) noexcept
{
lhs.swap(rhs);
}
std::size_t hash_value(const path& p) noexcept;
#endif
} // namespace filesystem
} // namespace cm