// -*-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 // IWYU pragma: export #else # include # include # include # include # include # include # include # include # include # include # include # if defined(_WIN32) && !defined(__CYGWIN__) # include # 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(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 class unicode { }; template class unicode::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(*iter++), codepoint)) == s_start) { result += static_cast(codepoint); codepoint = 0; } else if (state == s_reject) { result += static_cast(0xfffd); state = s_start; codepoint = 0; } } if (state) { result += static_cast(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(c), codepoint)) == s_start) { result += static_cast(codepoint); } else { result += static_cast(0xfffd); } return result; } }; template class unicode::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(*iter++), codepoint)) == s_start) { if (codepoint <= 0xffff) { result += static_cast(codepoint); } else { codepoint -= 0x10000; result += static_cast((codepoint >> 10) + 0xd800); result += static_cast((codepoint & 0x3ff) + 0xdc00); } codepoint = 0; } else if (state == s_reject) { result += static_cast(0xfffd); state = s_start; codepoint = 0; } } if (state) { result += static_cast(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(c), codepoint)) == s_start) { if (codepoint <= 0xffff) { result += static_cast(codepoint); } else { codepoint -= 0x10000; result += static_cast((codepoint >> 10) + 0xd800); result += static_cast((codepoint & 0x3ff) + 0xdc00); } } else { result += static_cast(0xfffd); } return result; } }; template class unicode_converter; template <> class unicode_converter { public: std::wstring operator()(const std::string& in) { return unicode::from_utf8(in); } std::wstring operator()(const char* in) { return unicode::from_utf8(in); } std::wstring operator()(char in) { return unicode::from_utf8(in); } }; template <> class unicode_converter { public: std::string operator()(const std::wstring& in) { return unicode::to_utf8(in); } std::string operator()(const wchar_t* in) { return unicode::to_utf8(in); } std::string operator()(wchar_t in) { return unicode::to_utf8(in); } }; template <> class unicode_converter { 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 { 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 struct string_converter { }; template <> struct string_converter { // 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 static std::basic_string to(const std::string& in, const Alloc& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template static std::basic_string to(const char* in, const Alloc& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template static std::basic_string to(char in, const Alloc& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template static std::basic_string to(const std::string& in) { return std::basic_string(unicode_converter()(in)); } template static std::basic_string to(const char* in) { return std::basic_string(unicode_converter()(in)); } template static std::basic_string to(char in) { return std::basic_string(unicode_converter()(in)); } }; template <> struct string_converter { // 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 static std::basic_string to(const std::wstring& in, const Alloc& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template static std::basic_string to(const wchar_t* in, const Alloc& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template static std::basic_string to(wchar_t in, const Alloc& a) { return std::basic_string( unicode_converter()(in).c_str(), a); } template static std::basic_string to(const std::wstring& in) { return std::basic_string(unicode_converter()(in)); } template static std::basic_string to(const wchar_t* in) { return std::basic_string(unicode_converter()(in)); } template static std::basic_string to(wchar_t in) { return std::basic_string(unicode_converter()(in)); } }; template struct source_traits { }; template struct source_traits { using value_type = T; }; template struct source_traits> { using value_type = typename std::basic_string::value_type; }; template <> struct source_traits { using value_type = cm::string_view::value_type; }; # if CM_FILESYSTEM_SOURCE_TRAITS_ITERATOR template struct source_traits::value, void>> { using value_type = typename std::iterator_traits::type>::value_type; }; # endif template struct source_converter { }; template <> struct source_converter { template static void append_range(std::string& p, Iterator b, Iterator e) { if (b == e) { return; } p.append(b, e); } template 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 static void append_source(std::string& p, const std::basic_string& s) { append_range(p, s.begin(), s.end()); } template 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 { template 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::to(tmp); p.append(dest.begin(), dest.end()); } template 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::to(tmp); p.append(dest.begin(), dest.end()); } template static void append_source(std::string& p, const std::basic_string& s) { append_range(p, s.begin(), s.end()); } template 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::to(s); } }; template struct is_pathable_string : std::false_type { }; template struct is_pathable_string> : std::true_type { }; template struct is_pathable_string> : std::true_type { }; template <> struct is_pathable_string : std::true_type { }; template struct is_pathable_char_array : std::false_type { }; template struct is_pathable_char_array< T, cm::enable_if_t< std::is_same::type>::value || std::is_same::type>::value, void>> : bool_constant::type>::value || std::is_same::type>::value> { }; template struct is_pathable_iterator : std::false_type { }; template struct is_pathable_iterator< T, cm::enable_if_t< is_input_iterator::value && (std::is_same::type>::value_type>::value || std::is_same::type>::value_type>::value), void>> : bool_constant< std::is_same::type>::value_type>::value || std::is_same::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 struct is_pathable : std::true_type { }; # else template struct is_pathable : bool_constant::value || is_pathable_char_array::value || is_pathable_iterator::value> { }; # endif } class path { using path_type = std::string; template using enable_if_pathable = enable_if_t::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; 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::set_source( this->path_, std::move(source)); } template > path(const Source& source, format fmt = auto_format) { (void)fmt; internals::source_converter< typename internals::source_traits::value_type, path_type::value_type>::append_source(this->path_, source); } template > path(const Iterator first, Iterator last, format fmt = auto_format) { (void)fmt; internals::source_converter< typename std::iterator_traits::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 > path& operator=(const Source& source) { return this->assign(source); } path& assign(string_type&& source) { internals::source_converter::set_source( this->path_, std::move(source)); return *this; } template > path& assign(const Source& source) { this->path_.clear(); internals::source_converter< typename internals::source_traits::value_type, path_type::value_type>::append_source(this->path_, source); return *this; } template > path& assign(Iterator first, Iterator last) { this->path_.clear(); internals::source_converter< typename std::iterator_traits::value_type, path_type::value_type>::append_range(this->path_, first, last); return *this; } // Concatenation // ============= path& operator/=(const path& p); template > path& append(const Source& source) { return this->operator/=(path(source)); } template path& operator/=(const Source& source) { return this->append(source); } template > 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::to(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::to(str); return *this; } path& operator+=(const value_type c) { this->path_ += internals::string_converter::to(c); return *this; } template > path& concat(const Source& source) { internals::source_converter< typename internals::source_traits::value_type, path_type::value_type>::append_source(this->path_, source); return *this; } template path& operator+=(const Source& source) { return this->concat(source); } template > path& concat(Iterator first, Iterator last) { internals::source_converter< typename std::iterator_traits::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(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(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(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(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, typename Alloc = std::allocator, cm::enable_if_t<(std::is_same::value && std::is_same>::value) || (std::is_same::value && std::is_same>::value), int> = 1> std::basic_string string(const Alloc& a = Alloc()) const { return internals::string_converter::to( this->path_, a); } const std::string string() const { return this->path_; } std::wstring wstring() const { std::string out = this->string(); return internals::string_converter::to< std::wstring::value_type>(out); } template < typename Char, typename Traits = std::char_traits, typename Alloc = std::allocator, cm::enable_if_t<(std::is_same::value && std::is_same>::value) || (std::is_same::value && std::is_same>::value), int> = 1> std::basic_string generic_string( const Alloc& a = Alloc()) const { return internals::string_converter::to( 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::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::to(str)); } int compare(const value_type* str) const { return this->compare_path( internals::string_converter::to(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 friend inline cm::enable_if_t< (std::is_same::value && std::is_same>::value) || (std::is_same::value && std::is_same>::value), std::basic_ostream&> operator<<(std::basic_ostream& os, const path& p) { os << cm::quoted(p.string()); return os; } template friend inline cm::enable_if_t< (std::is_same::value && std::is_same>::value) || (std::is_same::value && std::is_same>::value), std::basic_istream&> operator>>(std::basic_istream& is, path& p) { std::basic_string 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 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