blob: a935573740fbb35300662f7c7bd9f1757424f6c4 [file] [log] [blame]
// Class experimental::filesystem::path -*- C++ -*-
// Copyright (C) 2014-2021 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
// <http://www.gnu.org/licenses/>.
#ifndef _GLIBCXX_USE_CXX11_ABI
# define _GLIBCXX_USE_CXX11_ABI 1
#endif
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
using fs::path;
fs::filesystem_error::~filesystem_error() = default;
constexpr path::value_type path::preferred_separator [[gnu::used]];
path&
path::remove_filename()
{
if (_M_type == _Type::_Multi)
{
if (!_M_cmpts.empty())
{
auto cmpt = std::prev(_M_cmpts.end());
_M_pathname.erase(cmpt->_M_pos);
_M_cmpts.erase(cmpt);
_M_trim();
}
}
else
clear();
return *this;
}
path&
path::replace_filename(const path& replacement)
{
remove_filename();
operator/=(replacement);
return *this;
}
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
const fs::path::value_type dot = L'.';
#else
const fs::path::value_type dot = '.';
#endif
path&
path::replace_extension(const path& replacement)
{
auto ext = _M_find_extension();
if (ext.first && ext.second != string_type::npos)
{
if (ext.first == &_M_pathname)
_M_pathname.erase(ext.second);
else
{
const auto& back = _M_cmpts.back();
if (ext.first != &back._M_pathname)
_GLIBCXX_THROW_OR_ABORT(
std::logic_error("path::replace_extension failed"));
_M_pathname.erase(back._M_pos + ext.second);
}
}
if (!replacement.empty() && replacement.native()[0] != dot)
_M_pathname += dot;
_M_pathname += replacement.native();
_M_split_cmpts();
return *this;
}
namespace
{
template<typename Iter1, typename Iter2>
int do_compare(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2)
{
int cmpt = 1;
while (begin1 != end1 && begin2 != end2)
{
if (begin1->native() < begin2->native())
return -cmpt;
if (begin1->native() > begin2->native())
return +cmpt;
++begin1;
++begin2;
++cmpt;
}
if (begin1 == end1)
{
if (begin2 == end2)
return 0;
return -cmpt;
}
return +cmpt;
}
}
int
path::compare(const path& p) const noexcept
{
struct CmptRef
{
const path* ptr;
const string_type& native() const noexcept { return ptr->native(); }
};
if (_M_type == _Type::_Multi && p._M_type == _Type::_Multi)
return do_compare(_M_cmpts.begin(), _M_cmpts.end(),
p._M_cmpts.begin(), p._M_cmpts.end());
else if (_M_type == _Type::_Multi)
{
CmptRef c[1] = { { &p } };
return do_compare(_M_cmpts.begin(), _M_cmpts.end(), c, c+1);
}
else if (p._M_type == _Type::_Multi)
{
CmptRef c[1] = { { this } };
return do_compare(c, c+1, p._M_cmpts.begin(), p._M_cmpts.end());
}
else
return _M_pathname.compare(p._M_pathname);
}
path
path::root_name() const
{
path __ret;
if (_M_type == _Type::_Root_name)
__ret = *this;
else if (_M_cmpts.size() && _M_cmpts.begin()->_M_type == _Type::_Root_name)
__ret = *_M_cmpts.begin();
return __ret;
}
path
path::root_directory() const
{
path __ret;
if (_M_type == _Type::_Root_dir)
__ret = *this;
else if (!_M_cmpts.empty())
{
auto __it = _M_cmpts.begin();
if (__it->_M_type == _Type::_Root_name)
++__it;
if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
__ret = *__it;
}
return __ret;
}
path
path::root_path() const
{
path __ret;
if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir)
__ret = *this;
else if (!_M_cmpts.empty())
{
auto __it = _M_cmpts.begin();
if (__it->_M_type == _Type::_Root_name)
{
__ret = *__it++;
if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
{
__ret._M_pathname += preferred_separator;
__ret._M_split_cmpts();
}
}
else if (__it->_M_type == _Type::_Root_dir)
__ret = *__it;
}
return __ret;
}
path
path::relative_path() const
{
path __ret;
if (_M_type == _Type::_Filename)
__ret = *this;
else if (!_M_cmpts.empty())
{
auto __it = _M_cmpts.begin();
if (__it->_M_type == _Type::_Root_name)
++__it;
if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
++__it;
if (__it != _M_cmpts.end())
__ret.assign(_M_pathname.substr(__it->_M_pos));
}
return __ret;
}
path
path::parent_path() const
{
path __ret;
if (_M_cmpts.size() < 2)
return __ret;
for (auto __it = _M_cmpts.begin(), __end = std::prev(_M_cmpts.end());
__it != __end; ++__it)
{
__ret /= *__it;
}
return __ret;
}
bool
path::has_root_name() const
{
if (_M_type == _Type::_Root_name)
return true;
if (!_M_cmpts.empty() && _M_cmpts.begin()->_M_type == _Type::_Root_name)
return true;
return false;
}
bool
path::has_root_directory() const
{
if (_M_type == _Type::_Root_dir)
return true;
if (!_M_cmpts.empty())
{
auto __it = _M_cmpts.begin();
if (__it->_M_type == _Type::_Root_name)
++__it;
if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
return true;
}
return false;
}
bool
path::has_root_path() const
{
if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir)
return true;
if (!_M_cmpts.empty())
{
auto __type = _M_cmpts.front()._M_type;
if (__type == _Type::_Root_name || __type == _Type::_Root_dir)
return true;
}
return false;
}
bool
path::has_relative_path() const
{
if (_M_type == _Type::_Filename)
return true;
if (!_M_cmpts.empty())
{
auto __it = _M_cmpts.begin();
if (__it->_M_type == _Type::_Root_name)
++__it;
if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
++__it;
if (__it != _M_cmpts.end())
return true;
}
return false;
}
bool
path::has_parent_path() const
{
return _M_cmpts.size() > 1;
}
bool
path::has_filename() const
{
return !empty();
}
std::pair<const path::string_type*, std::size_t>
path::_M_find_extension() const
{
const string_type* s = nullptr;
if (_M_type != _Type::_Multi)
s = &_M_pathname;
else if (!_M_cmpts.empty())
{
const auto& c = _M_cmpts.back();
if (c._M_type == _Type::_Filename)
s = &c._M_pathname;
}
if (s)
{
if (auto sz = s->size())
{
if (sz <= 2 && (*s)[0] == dot)
{
if (sz == 1 || (*s)[1] == dot) // filename is "." or ".."
return { s, string_type::npos };
else
return { s, 0 }; // filename is like ".?"
}
return { s, s->rfind(dot) };
}
}
return {};
}
void
path::_M_split_cmpts()
{
_M_type = _Type::_Multi;
_M_cmpts.clear();
if (_M_pathname.empty())
return;
{
// Approximate count of components, to reserve space in _M_cmpts vector:
int count = 1;
bool saw_sep_last = _S_is_dir_sep(_M_pathname[0]);
bool saw_non_sep = !saw_sep_last;
for (value_type c : _M_pathname)
{
if (_S_is_dir_sep(c))
saw_sep_last = true;
else if (saw_sep_last)
{
++count;
saw_sep_last = false;
saw_non_sep = true;
}
}
if (saw_non_sep && saw_sep_last)
++count; // empty filename after trailing slash
if (count > 1)
_M_cmpts.reserve(count);
}
size_t pos = 0;
const size_t len = _M_pathname.size();
// look for root name or root directory
if (_S_is_dir_sep(_M_pathname[0]))
{
// look for root name, such as "//" or "//foo"
if (len > 1 && _M_pathname[1] == _M_pathname[0])
{
if (len == 2)
{
// entire path is just "//"
_M_type = _Type::_Root_name;
return;
}
if (!_S_is_dir_sep(_M_pathname[2]))
{
// got root name, find its end
pos = 3;
while (pos < len && !_S_is_dir_sep(_M_pathname[pos]))
++pos;
if (pos == len)
{
_M_type = _Type::_Root_name;
return;
}
_M_add_root_name(pos);
_M_add_root_dir(pos);
}
else
{
// got something like "///foo" which is just a root directory
// composed of multiple redundant directory separators
_M_add_root_dir(0);
}
}
else if (len == 1) // got root directory only
{
_M_type = _Type::_Root_dir;
return;
}
else // got root directory
_M_add_root_dir(0);
++pos;
}
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
else if (len > 1 && _M_pathname[1] == L':')
{
// got disk designator
if (len == 2)
{
_M_type = _Type::_Root_name;
return;
}
_M_add_root_name(2);
if (len > 2 && _S_is_dir_sep(_M_pathname[2]))
_M_add_root_dir(2);
pos = 2;
}
#endif
else
{
size_t n = 1;
for (; n < _M_pathname.size() && !_S_is_dir_sep(_M_pathname[n]); ++n)
{ }
if (n == _M_pathname.size())
{
_M_type = _Type::_Filename;
return;
}
}
size_t back = pos;
while (pos < len)
{
if (_S_is_dir_sep(_M_pathname[pos]))
{
if (back != pos)
_M_add_filename(back, pos - back);
back = ++pos;
}
else
++pos;
}
if (back != pos)
_M_add_filename(back, pos - back);
else if (_S_is_dir_sep(_M_pathname.back()))
{
// [path.itr]/8
// "Dot, if one or more trailing non-root slash characters are present."
if (_M_cmpts.back()._M_type == _Type::_Filename)
{
const auto& last = _M_cmpts.back();
pos = last._M_pos + last._M_pathname.size();
_M_cmpts.emplace_back(string_type(1, dot), _Type::_Filename, pos);
}
}
_M_trim();
}
void
path::_M_add_root_name(size_t n)
{
_M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0);
}
void
path::_M_add_root_dir(size_t pos)
{
_M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos);
}
void
path::_M_add_filename(size_t pos, size_t n)
{
_M_cmpts.emplace_back(_M_pathname.substr(pos, n), _Type::_Filename, pos);
}
void
path::_M_trim()
{
if (_M_cmpts.size() == 1)
{
_M_type = _M_cmpts.front()._M_type;
_M_cmpts.clear();
}
}
path::string_type
path::_S_convert_loc(const char* __first, const char* __last,
const std::locale& __loc)
{
#if _GLIBCXX_USE_WCHAR_T
auto& __cvt = std::use_facet<codecvt<wchar_t, char, mbstate_t>>(__loc);
basic_string<wchar_t> __ws;
if (!__str_codecvt_in_all(__first, __last, __ws, __cvt))
_GLIBCXX_THROW_OR_ABORT(filesystem_error(
"Cannot convert character sequence",
std::make_error_code(errc::illegal_byte_sequence)));
#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
return __ws;
#else
return _Cvt<wchar_t>::_S_convert(__ws.data(), __ws.data() + __ws.size());
#endif
#else
return {__first, __last};
#endif
}
std::size_t
fs::hash_value(const path& p) noexcept
{
// [path.non-member]
// "If for two paths, p1 == p2 then hash_value(p1) == hash_value(p2)."
// Equality works as if by traversing the range [begin(), end()), meaning
// e.g. path("a//b") == path("a/b"), so we cannot simply hash _M_pathname
// but need to iterate over individual elements. Use the hash_combine from
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf
size_t seed = 0;
for (const auto& x : p)
{
seed ^= std::hash<path::string_type>()(x.native()) + 0x9e3779b9
+ (seed<<6) + (seed>>2);
}
return seed;
}
#include <experimental/string_view>
std::string
fs::filesystem_error::_M_gen_what()
{
const std::string pstr1 = _M_path1.u8string();
const std::string pstr2 = _M_path2.u8string();
experimental::string_view s = this->system_error::what();
const size_t len = 18 + s.length()
+ (pstr1.length() || pstr2.length() ? pstr1.length() + 3 : 0)
+ (pstr2.length() ? pstr2.length() + 3 : 0);
std::string w;
w.reserve(len);
w = "filesystem error: ";
w.append(s.data(), s.length());
if (!pstr1.empty())
{
w += " [";
w += pstr1;
w += ']';
}
if (!pstr2.empty())
{
if (pstr1.empty())
w += " []";
w += " [";
w += pstr2;
w += ']';
}
return w;
}