From c6610217b7b1dcc60ef24e0a3601f8ac2d89f8e2 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 26 Jun 2017 17:22:27 -0700 Subject: [PATCH] working on util::filesystem --- src/util/CMakeLists.txt | 2 + src/util/filesystem.cpp | 157 +++++ src/util/filesystem.h | 1211 +++++++++++++++++++++++++++++++++++---- src/util/text.h | 47 +- src/util/util_test.cpp | 27 + 5 files changed, 1298 insertions(+), 146 deletions(-) create mode 100644 src/util/filesystem.cpp create mode 100644 src/util/util_test.cpp diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 3f0fea9..43d6a8a 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -22,6 +22,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) set(sources bit_intrinsics.cpp bitset.cpp copy_cv_ref.cpp + filesystem.cpp in_place.cpp invoke.cpp is_referenceable.cpp @@ -32,3 +33,4 @@ set(sources bit_intrinsics.cpp variant.cpp void_t.cpp) add_library(util STATIC ${sources}) +add_executable(util_test ${sources} util_test.cpp) diff --git a/src/util/filesystem.cpp b/src/util/filesystem.cpp new file mode 100644 index 0000000..b95952e --- /dev/null +++ b/src/util/filesystem.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2017 Jacob Lifshay + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include "filesystem.h" +#include +#include + +namespace vulkan_cpu +{ +namespace util +{ +namespace filesystem +{ +namespace detail +{ +#if 1 +#warning testing util::filesystem::path +struct Path_tester +{ + template + static void write_path(const Path &path) + { + std::cout << path << ": kind="; + switch(path.kind) + { + case Path_part_kind::file_name: + std::cout << "file_name"; + break; + case Path_part_kind::multiple_parts: + std::cout << "multiple_parts"; + break; + case Path_part_kind::root_dir: + std::cout << "root_dir"; + break; + case Path_part_kind::relative_root_name: + std::cout << "relative_root_name"; + break; + case Path_part_kind::absolute_root_name: + std::cout << "absolute_root_name"; + break; + case Path_part_kind::path_separator: + std::cout << "path_separator"; + break; + } + if(Show_parts) + { + std::cout << " parts=["; + auto separator = ""; + for(auto &part : path.parts) + { + std::cout << separator; + separator = ", "; + write_path(part); + } + std::cout << "]"; + } + } + template + static void test_path(const char *traits_kind_name) + { +#if 0 + typedef basic_path<> Path; +#else + typedef basic_path Path; +#endif + std::cout << "testing basic_path<" << traits_kind_name << ">" << std::endl; + for(auto *test_path_string : { + "", + ".", + "..", + "C:", + "C:\\", + "C:/", + "/", + "//", + "//a", + "//a/", + "\\", + "\\\\", + "\\\\a", + "\\\\a\\", + "a/", + "a/.", + "a/..", + "a/...", + "a/a", + "a/.a", + "a/a.", + "a/a.a", + "a/.a.", + "a/.a.a", + }) + { + Path p(test_path_string); + std::cout << "'" << test_path_string << "' -> "; + write_path(p); + std::cout << std::endl; + std::cout << "make_preferred -> " << Path(p).make_preferred() << std::endl; + std::cout << "remove_filename -> " << Path(p).remove_filename() << std::endl; + std::cout << "lexically_normal -> " << p.lexically_normal() << std::endl; + std::cout << "operator/=:"; + for(auto *appended_path : { + "", + "/abc", + "C:abc", + "//a/abc", + "C:/abc", + "abc", + }) + { + std::cout << " \"" << appended_path << "\"->" + << Path(p).operator/=(appended_path); + } + std::cout << std::endl; + } + } + static void test() + { + test_path("posix"); + test_path("windows"); + std::string().assign("", 0); + } + Path_tester() + { + test(); + std::exit(1); + } +}; + +namespace +{ +Path_tester path_tester; +} +#endif +} +} +} +} diff --git a/src/util/filesystem.h b/src/util/filesystem.h index 7ede999..080182a 100644 --- a/src/util/filesystem.h +++ b/src/util/filesystem.h @@ -37,6 +37,9 @@ #include "optional.h" #include "text.h" #include +#include +#include +#include namespace vulkan_cpu { @@ -63,6 +66,7 @@ struct Path_traits { typedef char value_type; static constexpr value_type preferred_separator = '/'; + static constexpr bool needs_root_name_to_be_absolute = false; }; template <> @@ -70,14 +74,17 @@ struct Path_traits { typedef wchar_t value_type; static constexpr value_type preferred_separator = L'\\'; + static constexpr bool needs_root_name_to_be_absolute = true; }; -enum class Path_kind +enum class Path_part_kind { - root_name, + relative_root_name, // root name that has a current directory, like "C:" in windows + absolute_root_name, // root name that can't have a current directory, like "\\ABC" in windows root_dir, file_name, multiple_parts, + path_separator, }; template @@ -165,10 +172,10 @@ public: if(base_iterator) { assert(!rt.base_iterator); - return *base_iterator == value_type(); + return **base_iterator == value_type(); } if(rt.base_iterator) - return *rt.base_iterator == value_type(); + return **rt.base_iterator == value_type(); return true; } bool operator!=(const Path_convert_single_iterator_adaptor &rt) const @@ -178,7 +185,7 @@ public: bool operator==(Path_iterator_sentinel) const { if(base_iterator) - return *base_iterator == value_type(); + return **base_iterator == value_type(); return true; } bool operator!=(Path_iterator_sentinel) const @@ -251,7 +258,8 @@ private: *this = Path_convert_iterator(); else { - encode_result = text::Decode_encode_functions::encode(ch); + encode_result = + text::Decode_encode_functions::encode(ch, text::Convert_options()); encode_result_index = 0; } } @@ -281,11 +289,11 @@ public: operator++(); return retval; } - const char32_t &operator*() const noexcept + const Dest_char_type &operator*() const noexcept { return encode_result[encode_result_index]; } - const char32_t *operator->() const noexcept + const Dest_char_type *operator->() const noexcept { return &encode_result[encode_result_index]; } @@ -320,12 +328,15 @@ struct Path_convert_range::value>::type> { static constexpr bool is_convertible = true; - template - static std::basic_string to_string(Iterator iterator, Sentinel sentinel) + template , + typename Allocator = std::allocator, + typename Sentinel> + static std::basic_string to_string( + Iterator iterator, Sentinel sentinel, const Allocator &a = Allocator()) { typedef Path_convert_iterator Convert_iterator; - return std::basic_string(Convert_iterator(iterator, sentinel), - Convert_iterator()); + return std::basic_string( + Convert_iterator(iterator, sentinel), Convert_iterator(), a); } }; @@ -336,14 +347,20 @@ struct Path_convert_range:: { static constexpr bool is_convertible = true; typedef typename Path_is_convertable_iterator_type::Char_type Char_type; - static std::basic_string to_string(Iterator iterator, Iterator sentinel) + static std::basic_string to_string( + Iterator iterator, + Iterator sentinel, + const std::allocator &a = std::allocator()) { - return std::basic_string(iterator, sentinel); + return std::basic_string(iterator, sentinel, a); } - template - static std::basic_string to_string(Iterator iterator, Sentinel sentinel) + template , + typename Allocator = std::allocator, + typename Sentinel> + static std::basic_string to_string( + Iterator iterator, Sentinel sentinel, const Allocator &a = Allocator()) { - std::basic_string retval; + std::basic_string retval(a); while(iterator != sentinel) retval += *iterator++; return retval; @@ -372,10 +389,14 @@ struct Path_convert_source:: const_iterator> Convert_range; static constexpr bool is_convertible = true; - static std::basic_string to_string( - const std::basic_string &source) + template , + typename Dest_allocator = std::allocator> + static std::basic_string to_string( + const std::basic_string &source, + const Allocator &a = Allocator()) { - return Convert_range::to_string(source.begin(), source.end()); + return Convert_range::template to_string( + source.begin(), source.end(), a); } }; @@ -393,24 +414,30 @@ struct typename basic_string_view::const_iterator> Convert_range; static constexpr bool is_convertible = true; - static std::basic_string to_string( - const basic_string_view &source) + template , + typename Allocator = std::allocator> + static std::basic_string to_string( + const basic_string_view &source, const Allocator &a = Allocator()) { - return Convert_range::to_string(source.begin(), source.end()); + return Convert_range::template to_string( + source.begin(), source.end(), a); } }; template -struct Path_convert_source, - typename std:: - enable_if:: - const_iterator>::is_convertible>:: - type> +struct Path_convert_source, void> { static constexpr bool is_convertible = true; - static std::basic_string to_string(const std::basic_string &source) + template , + typename Allocator = std::allocator> + static std::basic_string to_string( + const std::basic_string &source, const Allocator &a = Allocator()) + { + return std::basic_string(source.begin(), source.end(), a); + } + static std::basic_string to_string( + const std::basic_string &source, + const std::allocator & = std::allocator()) { return source; } @@ -419,15 +446,20 @@ struct Path_convert_source struct Path_convert_source::Char_type>> + typename std::enable_if::Char_type, + Char_type>::value>::type> { static constexpr bool is_convertible = true; typedef Path_convert_range> Convert_range; - static std::basic_string to_string(Iterator iterator) + template , + typename Allocator = std::allocator> + static std::basic_string to_string( + Iterator iterator, const Allocator &a = Allocator()) { - return Convert_range::to_string(Path_convert_single_iterator_adaptor(iterator), - Path_iterator_sentinel()); + return Convert_range::template to_string( + Path_convert_single_iterator_adaptor(iterator), Path_iterator_sentinel(), a); } }; @@ -435,19 +467,68 @@ template struct Path_convert_source { static constexpr bool is_convertible = true; - static std::basic_string to_string(const Char_type *source) + template , + typename Allocator = std::allocator> + static std::basic_string to_string( + const Char_type *source, const Allocator &a = Allocator()) + { + return {source, a}; + } +}; + +template +struct Path_convert_source + : public Path_convert_source +{ +}; + +struct Path_index_range +{ + std::size_t begin; + std::size_t end; + constexpr Path_index_range() noexcept : begin(0), end(0) + { + } + constexpr Path_index_range(std::size_t begin, std::size_t end) noexcept : begin(begin), end(end) + { + } + template + constexpr Path_index_range(basic_string_view str, + typename basic_string_view::iterator begin, + typename basic_string_view::iterator end) noexcept + : begin(begin - str.begin()), + end(end - str.begin()) { - return source; + } + constexpr bool empty() const noexcept + { + return begin == end; + } + constexpr std::size_t size() const noexcept + { + return end - begin; } }; -#error finish +struct Path_tester; +} -template ::value_type, - Char_type Preferred_separator = Path_traits::preferred_separator> +template ::value_type, + Char_type Preferred_separator = detail::Path_traits::preferred_separator, + bool Needs_root_name_to_be_absolute = + detail::Path_traits::needs_root_name_to_be_absolute> class basic_path { + friend struct detail::Path_tester; + static_assert(std::is_same::value || std::is_same::value, + ""); + +private: + typedef detail::Path_traits_kind Path_traits_kind; + typedef detail::Path_part_kind Path_part_kind; + typedef detail::Path_index_range Path_index_range; + public: typedef Char_type value_type; typedef std::basic_string string_type; @@ -659,7 +740,72 @@ private: private: Parts parts; string_type value; - Path_kind kind; + detail::Path_part_kind kind; + +public: + class iterator + { + template + friend class basic_path; + + public: + typedef basic_path value_type; + typedef const basic_path *pointer; + typedef const basic_path &reference; + typedef std::ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + + private: + const basic_path *path; + std::size_t index; + constexpr iterator(const basic_path *path, std::size_t index) noexcept : path(path), + index(index) + { + } + + public: + constexpr iterator() noexcept : path(nullptr), index() + { + } + constexpr iterator &operator++() noexcept + { + index++; + return *this; + } + constexpr iterator &operator--() noexcept + { + index--; + return *this; + } + constexpr iterator operator++(int) noexcept + { + return iterator(path, index++); + } + constexpr iterator operator--(int) noexcept + { + return iterator(path, index--); + } + const basic_path *operator->() const + { + assert(path); + if(path->kind == Path_part_kind::multiple_parts) + return &path->parts[index]; + return path; + } + const basic_path &operator*() const + { + return *operator->(); + } + constexpr bool operator==(const iterator &rt) const noexcept + { + return index == rt.index; + } + constexpr bool operator!=(const iterator &rt) const noexcept + { + return index != rt.index; + } + }; + typedef iterator const_iterator; private: static constexpr bool is_ascii_letter(Char_type v) noexcept @@ -673,15 +819,15 @@ private: return true; return false; } - static constexpr bool is_separator(Char_type v) noexcept + template + static constexpr bool is_separator(Char_type2 v) noexcept { - return v == static_cast('/') || v == preferred_separator; + return v == static_cast('/') + || v == static_cast(preferred_separator); } - template - static void parse(string_view_type value, Fn callback, format fmt = auto_format) noexcept( - noexcept(callback(typename string_view_type::iterator(), - typename string_view_type::iterator(), - Path_kind()))) + template + static bool parse(string_view_type value, Fn callback, format fmt = auto_format) noexcept( + noexcept(callback(Path_index_range(), Path_part_kind()))) { constexpr Char_type colon = ':'; typedef typename std::char_traits::int_type Int_type; @@ -700,15 +846,17 @@ private: return std::char_traits::to_int_type(*char_iter++); }; if(value.empty()) - return; - if(Traits_kind == Path_traits_kind::windows && value.size() >= 2 + return true; + if(!Ignore_root_parts && Traits_kind == Path_traits_kind::windows && value.size() >= 2 && is_ascii_letter(value[0]) && value[1] == colon) { char_iter += 2; - callback(value.begin(), char_iter, Path_kind::root_name); + if(!callback(Path_index_range(value, value.begin(), char_iter), + Path_part_kind::relative_root_name)) + return false; } - else if(Traits_kind == Path_traits_kind::windows && value.size() >= 2 + else if(!Ignore_root_parts && Traits_kind == Path_traits_kind::windows && value.size() >= 2 && is_separator(value[0]) && is_separator(value[1])) { @@ -716,16 +864,24 @@ private: get(); while(peek() != eof && !is_separator(peek())) get(); - callback(value.begin(), char_iter, Path_kind::root_name); + if(!callback(Path_index_range(value, value.begin(), char_iter), + Path_part_kind::absolute_root_name)) + return false; } - if(peek() != eof && is_separator(peek())) + if(!Ignore_root_parts && peek() != eof && is_separator(peek())) { auto start_iter = char_iter; do { get(); } while(peek() != eof && is_separator(peek())); - callback(start_iter, char_iter, Path_kind::root_dir); + if(!callback(Path_index_range(value, start_iter, char_iter), Path_part_kind::root_dir)) + return false; + } + if(Ignore_root_parts && peek() != eof && is_separator(peek())) + { + if(!callback(Path_index_range(value, char_iter, char_iter), Path_part_kind::file_name)) + return false; } if(peek() != eof && !is_separator(peek())) { @@ -734,34 +890,55 @@ private: { get(); } while(peek() != eof && !is_separator(peek())); - callback(start_iter, char_iter, Path_kind::file_name); + if(!callback(Path_index_range(value, start_iter, char_iter), Path_part_kind::file_name)) + return false; } while(peek() != eof) { + auto start_iter = char_iter; do { get(); } while(peek() != eof && is_separator(peek())); - auto start_iter = char_iter; + if(!callback(Path_index_range(value, start_iter, char_iter), + Path_part_kind::path_separator)) + return false; + start_iter = char_iter; while(peek() != eof && !is_separator(peek())) get(); - callback(start_iter, char_iter, Path_kind::file_name); + if(!callback(Path_index_range(value, start_iter, char_iter), Path_part_kind::file_name)) + return false; } + return true; } void parse(format fmt = auto_format) { - auto last_part_kind = Path_kind::multiple_parts; + constexpr Char_type generic_separator = '/'; + auto last_part_kind = Path_part_kind::multiple_parts; std::size_t part_count = 0; + bool need_generic_conversion = false; parse(value, - [&]([[gnu::unused]] typename string_view_type::iterator part_string_begin, - [[gnu::unused]] typename string_view_type::iterator part_string_end, - Path_kind part_kind) noexcept + [&](Path_index_range index_range, Path_part_kind part_kind) noexcept { + if(part_kind == Path_part_kind::path_separator) + return true; + if(generic_separator != preferred_separator && !need_generic_conversion) + { + for(std::size_t i = index_range.begin; i < index_range.end; i++) + { + if(is_separator(value[i]) && value[i] != generic_separator) + { + need_generic_conversion = true; + break; + } + } + } last_part_kind = part_kind; part_count++; + return true; }, fmt); - if(part_count == 1) + if(part_count == 1 && !need_generic_conversion) { kind = last_part_kind; parts.clear(); @@ -769,67 +946,189 @@ private: } else { - kind = Path_kind::multiple_parts; + kind = Path_part_kind::multiple_parts; } while(parts.size() > part_count) parts.pop_back(); parts.reserve(part_count); std::size_t part_index = 0; parse(value, - [&](typename string_view_type::iterator part_string_begin, - typename string_view_type::iterator part_string_end, - Path_kind part_kind) + [&](Path_index_range index_range, Path_part_kind part_kind) { + if(part_kind == Path_part_kind::path_separator) + return true; if(part_index >= parts.size()) parts.emplace_back(); - // copy manually because libstdc++ allocates a new string in basic_string.assign - parts[part_index].value.clear(); - parts[part_index].value.reserve(part_string_end - part_string_begin); - for(auto i = part_string_begin; i != part_string_end; ++i) - parts[part_index].value.push_back(*i); + parts[part_index].value.assign(value.data() + index_range.begin, + index_range.size()); parts[part_index].kind = part_kind; + change_separator(parts[part_index].value, generic_separator); part_index++; + return true; }, fmt); } - std::size_t get_filename_start_index() noexcept + static Path_index_range get_filename_index_range(string_view_type value) noexcept { - std::size_t retval = value.size(); - auto value_view = string_view_type(value); + Path_index_range retval(value.size(), value.size()); parse(value, - [&](typename string_view_type::iterator part_string_begin, - [[gnu::unused]] typename string_view_type::iterator part_string_end, - Path_kind part_kind) noexcept + [&](Path_index_range index_range, Path_part_kind part_kind) noexcept { - if(part_kind == Path_kind::file_name) - retval = part_string_begin - value_view.begin(); + if(part_kind == Path_part_kind::file_name) + retval = index_range; + return true; }); return retval; } - std::size_t get_extension_start_index() noexcept + static Path_index_range get_stem_index_range(string_view_type value) noexcept + { + return get_stem_index_range(value, get_filename_index_range(value)); + } + static Path_index_range get_stem_index_range(string_view_type value, + Path_index_range filename_index_range) noexcept { constexpr Char_type dot = '.'; - std::size_t index = get_filename_start_index(); - if(index >= value.size()) - return value.size(); - if(value[index] == dot) - index++; - std::size_t retval = value.size(); - while(index < value.size()) + if(filename_index_range.size() <= 1) + return filename_index_range; + for(std::size_t i = filename_index_range.end; i > filename_index_range.begin; i--) { - if(value[index] == dot) - retval = index; - index++; + if(value[i - 1] == dot) + { + if(i == filename_index_range.begin + 1) + return filename_index_range; + if(i == filename_index_range.begin + 2 && value[filename_index_range.begin] == dot) + return filename_index_range; + return Path_index_range(filename_index_range.begin, i - 1); + } } + return filename_index_range; + } + static Path_index_range get_extension_index_range(string_view_type value) noexcept + { + return get_extension_index_range(value, get_filename_index_range(value)); + } + static Path_index_range get_extension_index_range( + string_view_type value, Path_index_range filename_index_range) noexcept + { + return get_extension_index_range( + value, filename_index_range, get_stem_index_range(value, filename_index_range)); + } + static Path_index_range get_extension_index_range([[gnu::unused]] string_view_type value, + Path_index_range filename_index_range, + Path_index_range stem_index_range) noexcept + { + return Path_index_range(stem_index_range.end, filename_index_range.end); + } + static Path_index_range get_root_name_index_range(string_view_type value) noexcept + { + Path_index_range retval(0, 0); + parse(value, + [&](Path_index_range index_range, Path_part_kind part_kind) noexcept + { + if(part_kind == Path_part_kind::absolute_root_name + || part_kind == Path_part_kind::relative_root_name) + retval = index_range; + return false; + }); + return retval; + } + static Path_index_range get_root_dir_index_range(string_view_type value) noexcept + { + Path_index_range retval(0, 0); + parse(value, + [&](Path_index_range index_range, Path_part_kind part_kind) noexcept + { + if(part_kind == Path_part_kind::root_dir) + { + retval = index_range; + } + else if(part_kind == Path_part_kind::absolute_root_name + || part_kind == Path_part_kind::relative_root_name) + { + retval = Path_index_range(index_range.end, index_range.end); + return true; + } + return false; + }); + return retval; + } + static Path_index_range get_root_path_index_range(string_view_type value) noexcept + { + Path_index_range retval(0, 0); + parse(value, + [&](Path_index_range index_range, Path_part_kind part_kind) noexcept + { + if(part_kind == Path_part_kind::absolute_root_name + || part_kind == Path_part_kind::relative_root_name) + { + retval = index_range; + return true; + } + else if(part_kind == Path_part_kind::root_dir) + { + retval.end = index_range.end; + return false; + } + return false; + }); + return retval; + } + static Path_index_range get_relative_path_index_range(string_view_type value) noexcept + { + Path_index_range retval(value.size(), value.size()); + parse(value, + [&](Path_index_range index_range, Path_part_kind part_kind) noexcept + { + if(part_kind == Path_part_kind::absolute_root_name + || part_kind == Path_part_kind::relative_root_name + || part_kind == Path_part_kind::root_dir) + { + return true; + } + retval.begin = index_range.begin; + return false; + }); + return retval; + } + static Path_index_range get_parent_path_index_range(string_view_type value) noexcept + { + Path_index_range retval(0, 0); + std::size_t last_file_name_end_index = 0; + parse(value, + [&](Path_index_range index_range, Path_part_kind part_kind) noexcept + { + switch(part_kind) + { + case Path_part_kind::path_separator: + return true; + case Path_part_kind::absolute_root_name: + case Path_part_kind::relative_root_name: + case Path_part_kind::root_dir: + retval.end = index_range.end; + return true; + case Path_part_kind::file_name: + if(last_file_name_end_index != 0) + retval.end = last_file_name_end_index; + last_file_name_end_index = index_range.end; + return true; + case Path_part_kind::multiple_parts: + break; + } + assert(false); + return false; + }); return retval; } public: - basic_path() noexcept : parts(), value(), kind(Path_kind::multiple_parts) + basic_path() noexcept : parts(), value(), kind(Path_part_kind::multiple_parts) { } basic_path(const basic_path &) = default; - basic_path(basic_path &&) noexcept = default; + basic_path(basic_path &&rt) noexcept : parts(), value(), kind() + { + swap(rt); + } basic_path(string_type &&source, format fmt = auto_format) : parts(), value(std::move(source)), kind() { @@ -837,16 +1136,21 @@ public: } template basic_path(const Source &source, format fmt = auto_format) - : basic_path(Path_convert_source::to_string(source), fmt) + : basic_path(detail::Path_convert_source::to_string(source), fmt) { } template basic_path(Input_iterator first, Input_iterator last, format fmt = auto_format) - : basic_path(Path_convert_range::to_string(first, last), fmt) + : basic_path(detail::Path_convert_range::to_string(first, last), + fmt) { } basic_path &operator=(const basic_path &rt) = default; - basic_path &operator=(basic_path &&rt) noexcept = default; + basic_path &operator=(basic_path &&rt) noexcept + { + basic_path(std::move(rt)).swap(*this); + return *this; + } basic_path &operator=(string_type &&new_value) { value = std::move(new_value); @@ -863,16 +1167,150 @@ public: { return operator=(new_value); } + basic_path &assign(const string_type &new_value) + { + value = new_value; + parse(); + return *this; + } + basic_path &assign(const string_view_type &new_value) + { + // use assign to prevent allocating a temporary string_type + value.assign(new_value.data(), new_value.size()); + parse(); + return *this; + } template basic_path &assign(const Source &source) { - assign(Path_convert_source::to_string(source)); + assign(detail::Path_convert_source::to_string(source)); return *this; } template basic_path &assign(Input_iterator first, Input_iterator last) { - assign(Path_convert_range::to_string(first, last)); + assign(detail::Path_convert_range::to_string(first, last)); + return *this; + } + +private: + template + void append_string(string_view_type str) + { + bool just_need_to_assign = is_absolute(str); + if(!just_need_to_assign && !get_root_name_index_range(str).empty()) + { + auto my_root_name_index_range = get_root_name_index_range(value); + auto str_root_name_index_range = get_root_name_index_range(str); + if(my_root_name_index_range.empty() + || string_view_type(value) + .substr(my_root_name_index_range.begin, my_root_name_index_range.size()) + == str.substr(str_root_name_index_range.begin, + str_root_name_index_range.size())) + just_need_to_assign = true; + } + if(just_need_to_assign) + { + assign(str); + return; + } + static_assert(std::is_same::value, + ""); + if(Check_for_shared_memory && str.begin() <= value.data() + value.size() + && str.end() >= value.data()) + { + // value and str share memory, reallocate str + append_string(static_cast(str)); + return; + } + auto str_root_name_index_range = get_root_name_index_range(str); + assert(str_root_name_index_range.begin == 0); + str.remove_prefix(str_root_name_index_range.end); + if(!get_root_dir_index_range(str).empty()) + { + auto my_root_name_index_range = get_root_name_index_range(value); + assert(my_root_name_index_range.begin == 0); + value.resize(my_root_name_index_range.end); + } + else if(!get_filename_index_range(value).empty() + || (get_root_dir_index_range(value).empty() && is_absolute())) + { + value.reserve(value.size() + 1 + str.size()); + value += preferred_separator; + } + value += str; + parse(); + } + +public: + basic_path &operator/=(const basic_path &p) + { + append_string(p.value); + return *this; + } + basic_path &operator/=(const string_type &p) + { + append_string(p); + } + basic_path &operator/=(const string_view_type &p) + { + append_string(p); + } + template + basic_path &operator/=(const Source &source) + { + append_string(detail::Path_convert_source::to_string(source)); + return *this; + } + template + basic_path &append(const Source &source) + { + operator/=(source); + return *this; + } + template + basic_path &append(Input_iterator first, Input_iterator last) + { + append_string( + detail::Path_convert_range::to_string(first, last)); + return *this; + } + basic_path &operator+=(const basic_path &p) + { + value += p.value; + parse(); + return *this; + } + basic_path &operator+=(const string_type &p) + { + value += p; + parse(); + return *this; + } + basic_path &operator+=(const string_view_type &p) + { + value += p; + parse(); + return *this; + } + template + basic_path &operator+=(const Source &source) + { + value += detail::Path_convert_source::to_string(source); + parse(); + return *this; + } + template + basic_path &concat(const Source &source) + { + operator+=(source); + return *this; + } + template + basic_path &concat(Input_iterator first, Input_iterator last) + { + value += detail::Path_convert_range::to_string(first, last); + parse(); return *this; } const Char_type *c_str() const noexcept @@ -892,25 +1330,38 @@ public: value.clear(); parse(); } - basic_path &make_preferred() + +private: + template + static void change_separator(std::basic_string &str, + Char_type2 separator) noexcept { - for(auto &ch : value) + for(auto &ch : str) { if(is_separator(ch)) - ch = preferred_separator; + ch = separator; } + } + basic_path &change_separator(Char_type separator) noexcept + { + change_separator(value, separator); for(auto &part : parts) - { - part.make_preferred(); - } + change_separator(part.value, separator); + return *this; + } + +public: + basic_path &make_preferred() noexcept + { + change_separator(preferred_separator); return *this; } basic_path &remove_filename() { - std::size_t filename_start_index = get_filename_start_index(); - if(filename_start_index < value.size()) + auto filename_index_range = get_filename_index_range(value); + if(!filename_index_range.empty()) { - value.resize(filename_start_index); + value.erase(filename_index_range.begin, filename_index_range.size()); parse(); } return *this; @@ -924,9 +1375,9 @@ public: basic_path &replace_extension(const basic_path &replacement = basic_path()) { constexpr Char_type dot = '.'; - std::size_t extension_start_index = get_extension_start_index(); - if(extension_start_index < value.size()) - value.resize(extension_start_index); + auto extension_index_range = get_extension_index_range(value); + if(!extension_index_range.empty()) + value.erase(extension_index_range.begin, extension_index_range.size()); else if(replacement.value.empty()) return *this; if(!replacement.value.empty() && replacement.value.front() != dot) @@ -949,29 +1400,543 @@ public: parts.swap(other.parts); swap(kind, other.kind); } -#error finish + bool has_root_path() const noexcept + { + return !get_root_path_index_range(value).empty(); + } + bool has_root_name() const noexcept + { + return !get_root_name_index_range(value).empty(); + } + bool has_root_directory() const noexcept + { + return !get_root_dir_index_range(value).empty(); + } + bool has_relative_path() const noexcept + { + return !get_relative_path_index_range(value).empty(); + } + bool has_parent_path() const noexcept + { + return !get_parent_path_index_range(value).empty(); + } + bool has_filename() const noexcept + { + return !get_filename_index_range(value).empty(); + } + bool has_stem() const noexcept + { + return !get_stem_index_range(value).empty(); + } + bool has_extension() const noexcept + { + return !get_extension_index_range(value).empty(); + } + +private: + static bool is_absolute(string_view_type value) noexcept + { + bool has_root_dir = false; + bool has_relative_root_name = false; + bool has_absolute_root_name = false; + parse(value, + [&]([[gnu::unused]] Path_index_range index_range, Path_part_kind part_kind) noexcept + { + if(part_kind == Path_part_kind::relative_root_name) + { + has_relative_root_name = true; + return true; + } + else if(part_kind == Path_part_kind::absolute_root_name) + { + has_absolute_root_name = true; + return false; + } + else if(part_kind == Path_part_kind::root_dir) + { + has_root_dir = true; + } + return false; + }); + if(has_absolute_root_name) + return true; + if(has_root_dir) + { + if(Needs_root_name_to_be_absolute) + return has_relative_root_name; + return true; + } + return false; + } + +public: + bool is_absolute() const noexcept + { + return is_absolute(value); + } + bool is_relative() const noexcept + { + return !is_absolute(value); + } + template , + typename Allocator = std::allocator> + std::basic_string string( + const Allocator &a = Allocator()) const + { + return detail::Path_convert_source::template to_string(value, a); + } + std::string string() const + { + return string(); + } + std::wstring wstring() const + { + return string(); + } + std::string u8string() const + { + return string(); + } + std::u16string u16string() const + { + return string(); + } + std::u32string u32string() const + { + return string(); + } + template , + typename Allocator = std::allocator> + std::basic_string generic_string( + const Allocator &a = Allocator()) const + { + auto retval = + detail::Path_convert_source::template to_string(value, a); + change_separator(retval, static_cast('/')); + return retval; + } + std::string generic_string() const + { + return generic_string(); + } + std::wstring generic_wstring() const + { + return generic_string(); + } + std::string generic_u8string() const + { + return generic_string(); + } + std::u16string generic_u16string() const + { + return generic_string(); + } + std::u32string generic_u32string() const + { + return generic_string(); + } + template + friend std::basic_ostream &operator<<( + std::basic_ostream &os, const basic_path &p) + { + os << std::quoted(p.string()); + return os; + } + template + friend std::basic_istream &operator>>( + std::basic_istream &is, basic_path &p) + { + std::basic_string str; + is >> std::quoted(str); + p = std::move(str); + return is; + } + +private: + static int compare_part(string_view_type a, + Path_part_kind a_kind, + string_view_type b, + Path_part_kind b_kind) noexcept + { + constexpr Char_type generic_separator_char = '/'; + constexpr string_view_type generic_separator(&generic_separator_char, 1); + if(a_kind == Path_part_kind::root_dir) + a = generic_separator; + if(b_kind == Path_part_kind::root_dir) + b = generic_separator; + for(std::size_t i = 0; i < a.size() && i < b.size(); i++) + { + Char_type a_char = a[i]; + Char_type b_char = b[i]; + if(a_char == preferred_separator) + a_char = generic_separator_char; + if(b_char == preferred_separator) + b_char = generic_separator_char; + if(a_char < b_char) + return -1; + if(a_char > b_char) + return 1; + } + if(a.size() < b.size()) + return -1; + if(a.size() > b.size()) + return 1; + return 0; + } + +public: + int compare(string_view_type str) const noexcept + { + int retval; + if(kind != Path_part_kind::multiple_parts) + { + retval = 1; // non-empty is more than empty + parse(str, + [&](Path_index_range index_range, Path_part_kind part_kind) noexcept + { + if(part_kind == Path_part_kind::path_separator) + return true; + if(retval == 1) // initial value + retval = compare_part(value, + kind, + str.substr(index_range.begin, index_range.size()), + part_kind); + else + retval = -1; // one-element is less than two-elements + return retval == 0; + }); + } + else + { + retval = 0; + auto part_iter = parts.begin(); + parse(str, + [&](Path_index_range index_range, Path_part_kind part_kind) noexcept + { + if(part_kind == Path_part_kind::path_separator) + return true; + if(part_iter == parts.end()) + { + retval = -1; // empty is less than non-empty + } + else + { + retval = compare_part(part_iter->value, + part_iter->kind, + str.substr(index_range.begin, index_range.size()), + part_kind); + ++part_iter; + } + return retval == 0; + }); + if(retval == 0 && part_iter != parts.end()) + retval = 1; // more-elements is more than fewer-elements + } + return retval; + } + int compare(const string_type &str) const noexcept + { + return compare(string_view_type(str)); + } + int compare(const Char_type *str) const + { + return compare(string_view_type(str)); + } + int compare(const basic_path &rt) const noexcept + { + return compare(rt.value); + } + iterator begin() const noexcept + { + return iterator(this, 0); + } + iterator end() const noexcept + { + return iterator(this, kind == Path_part_kind::multiple_parts ? parts.size() : 1); + } + basic_path root_name() const + { + auto iter = begin(); + if(iter == end()) + return {}; + if(iter->kind == Path_part_kind::relative_root_name + || iter->kind == Path_part_kind::absolute_root_name) + return *iter; + return {}; + } + basic_path root_directory() const + { + auto iter = begin(); + if(iter == end()) + return {}; + if(iter->kind == Path_part_kind::relative_root_name + || iter->kind == Path_part_kind::absolute_root_name) + ++iter; + if(iter == end()) + return {}; + if(iter->kind == Path_part_kind::root_dir) + return *iter; + return {}; + } + basic_path root_path() const + { + auto index_range = get_root_path_index_range(value); + if(index_range.empty()) + return {}; + return value.substr(index_range.begin, index_range.size()); + } + basic_path relative_path() const + { + auto index_range = get_relative_path_index_range(value); + if(index_range.empty()) + return {}; + return value.substr(index_range.begin, index_range.size()); + } + basic_path parent_path() const + { + auto index_range = get_parent_path_index_range(value); + if(index_range.empty()) + return {}; + return value.substr(index_range.begin, index_range.size()); + } + basic_path filename() const + { + auto iter = end(); + if(iter == begin()) + return {}; + --iter; + if(iter->kind == Path_part_kind::file_name) + return *iter; + return {}; + } + basic_path stem() const + { + auto index_range = get_stem_index_range(value); + if(index_range.empty()) + return {}; + return value.substr(index_range.begin, index_range.size()); + } + basic_path extension() const + { + auto index_range = get_extension_index_range(value); + if(index_range.empty()) + return {}; + return value.substr(index_range.begin, index_range.size()); + } + bool empty() const noexcept + { + return begin() == end(); + } + basic_path lexically_normal() const + { + constexpr Char_type dot = '.'; + constexpr std::size_t dot_dot_size = 2; + constexpr Char_type dot_dot_storage[dot_dot_size + 1] = {dot, dot}; + string_view_type dot_dot(dot_dot_storage, dot_dot_size); + if(empty()) + return {}; + auto relative_path_index_range = get_relative_path_index_range(value); + auto root_name_index_range = get_root_name_index_range(value); + bool has_root_dir = has_root_directory(); + basic_path retval; + retval.value.reserve(value.size()); + retval.value.assign(value.data() + relative_path_index_range.begin, + relative_path_index_range.size()); + std::size_t new_size = 0; + for(std::size_t i = 0; i < retval.value.size(); i++) + { + if(is_separator(retval.value[i])) + { + while(i + 1 < retval.value.size() && is_separator(retval.value[i + 1])) + i++; + retval.value[new_size++] = preferred_separator; + } + else + { + retval.value[new_size++] = retval.value[i]; + } + } + retval.value.resize(new_size); + new_size = 0; + bool last_was_separator = true; + for(std::size_t i = 0; i < retval.value.size(); i++) + { + if(last_was_separator && retval.value[i] == dot) + { + if(i + 1 >= retval.value.size()) + break; // don't write the dot + if(retval.value[i + 1] == preferred_separator) + { + i++; + last_was_separator = true; + continue; // skip the dot and separator + } + } + if(retval.value[i] == preferred_separator) + last_was_separator = true; + else + last_was_separator = false; + retval.value[new_size++] = retval.value[i]; + } + retval.value.resize(new_size); + retval.parts.reserve(parts.size()); + new_size = 0; + parse(retval.value, + [&](Path_index_range index_range, Path_part_kind part_kind) noexcept + { + if(part_kind == Path_part_kind::path_separator) + return true; + assert(part_kind == Path_part_kind::file_name); + if(index_range.size() == 2 && retval.value[index_range.begin] == dot + && retval.value[index_range.begin + 1] == dot) + { + if(new_size == 0 && has_root_dir) + return true; + if(new_size != 0) + { + new_size--; + return true; + } + } + if(new_size >= retval.parts.size()) + retval.parts.emplace_back(); + retval.parts[new_size].value.assign(retval.value.data() + index_range.begin, + index_range.size()); + retval.parts[new_size].kind = Path_part_kind::file_name; + new_size++; + return true; + }); + if(new_size >= 2 && retval.parts[new_size - 1].value.empty() + && retval.parts[new_size - 2].value == dot_dot) + new_size--; + std::size_t needed_space = 0; + if(!root_name_index_range.empty()) + needed_space++; + if(has_root_dir) + needed_space++; + if(needed_space > 0) + { + while(retval.parts.size() < new_size + needed_space) + retval.parts.emplace_back(); + for(std::size_t source = new_size - 1, target = new_size + needed_space - 1, i = 0; + i < new_size; + source--, target--, i++) + retval.parts[target] = std::move(retval.parts[source]); + std::size_t root_part_index = 0; + if(!root_name_index_range.empty()) + { + retval.parts[root_part_index].value.assign( + value.data() + root_name_index_range.begin, root_name_index_range.size()); + change_separator(retval.parts[root_part_index].value, static_cast('/')); + retval.parts[root_part_index].parts = Parts(); + retval.parts[root_part_index].kind = begin()->kind; + root_part_index++; + } + if(has_root_dir) + { + retval.parts[root_part_index].value.assign(1, static_cast('/')); + retval.parts[root_part_index].parts = Parts(); + retval.parts[root_part_index].kind = Path_part_kind::root_dir; + } + } + if(new_size + needed_space == 0) + { + if(retval.parts.empty()) + retval.parts.emplace_back(); + retval.parts[new_size].value.assign(1, dot); + retval.parts[new_size].parts = Parts(); + retval.parts[new_size].kind = Path_part_kind::file_name; + new_size++; + } + while(retval.parts.size() > new_size + needed_space) + retval.parts.pop_back(); + retval.value.clear(); + bool need_seperator = false; + for(auto &part : retval.parts) + { + switch(part.kind) + { + case Path_part_kind::absolute_root_name: + case Path_part_kind::relative_root_name: + retval.value += part.value; + change_separator(retval.value, preferred_separator); + need_seperator = false; + // absolute_root_name will be followed by root_dir if we need a seperator + continue; + case Path_part_kind::file_name: + if(need_seperator) + retval.value += preferred_separator; + retval.value += part.value; + need_seperator = true; + continue; + case Path_part_kind::root_dir: + retval.value += preferred_separator; + need_seperator = false; + continue; + case Path_part_kind::path_separator: + case Path_part_kind::multiple_parts: + break; + } + assert(false); + } + retval.parse(); + return retval; + } +#warning finish }; -template -constexpr Char_type basic_path::preferred_separator; +template +void swap( + basic_path &l, + basic_path + &r) noexcept +{ + l.swap(r); +} + +template +constexpr Char_type basic_path::preferred_separator; -template -basic_path - *basic_path::Parts::allocate(std::size_t count) +template +basic_path + *basic_path:: + Parts::allocate(std::size_t count) { if(count == 0) return nullptr; - return std::allocator::allocate(count); + return std::allocator().allocate(count); } -template -void basic_path::Parts::deallocate( - basic_path *values, std::size_t count) noexcept +template +void basic_path:: + Parts::deallocate(basic_path *values, std::size_t count) noexcept { if(count != 0) - std::allocator::deallocate(values, count); -} + std::allocator().deallocate(values, count); } + +typedef basic_path<> path; } } } diff --git a/src/util/text.h b/src/util/text.h index 3ea1245..7e9dab5 100644 --- a/src/util/text.h +++ b/src/util/text.h @@ -55,7 +55,7 @@ typename std::char_traits::int_type decode_utf8( && noexcept(iter == sentinel ? 0 : 0)) { if(iter == sentinel) - return error_value; + return std::char_traits::eof(); auto byte0 = static_cast(static_cast(*iter)); ++iter; if(byte0 < 0x80) @@ -119,7 +119,7 @@ struct Encoded_character final static_assert(max_Chars != 0, ""); Char_type chars[max_Chars]; std::size_t used; - Char_type &front() + constexpr Char_type &front() { return chars[0]; } @@ -127,7 +127,7 @@ struct Encoded_character final { return chars[0]; } - Char_type &back() + constexpr Char_type &back() { return chars[0]; } @@ -153,11 +153,11 @@ struct Encoded_character final { return begin() + used; } - iterator begin() + constexpr iterator begin() { return &chars[0]; } - iterator end() + constexpr iterator end() { return begin() + used; } @@ -171,9 +171,10 @@ struct Encoded_character final } constexpr const Char_type &operator[](std::size_t index) const { - return (assert(index < used), chars[index]); + assert(index < used); + return chars[index]; } - Char_type &operator[](std::size_t index) + constexpr Char_type &operator[](std::size_t index) { assert(index < used); return chars[index]; @@ -261,7 +262,7 @@ typename std::char_traits::int_type decode_utf16( && noexcept(iter == sentinel ? 0 : 0)) { if(iter == sentinel) - return error_value; + return std::char_traits::eof(); auto unit0 = static_cast(static_cast(*iter)); ++iter; if(unit0 >= 0xD800U && unit0 < 0xDC00U) @@ -296,7 +297,7 @@ typename std::char_traits::int_type decode_utf32( && noexcept(iter == sentinel ? 0 : 0)) { if(iter == sentinel) - return error_value; + return std::char_traits::eof(); auto retval = static_cast(static_cast(*iter)); ++iter; if(retval > 0x10FFFFUL) @@ -408,9 +409,9 @@ template struct Decode_encode_functions { template - typename std::char_traits::int_type decode( + static typename std::char_traits::int_type decode( Input_iterator &iter, Sentinel sentinel, const Convert_options &convert_options) = delete; - Encoded_character encode( + static Encoded_character encode( char32_t ch, const Convert_options &convert_options) noexcept = delete; }; @@ -418,7 +419,7 @@ template <> struct Decode_encode_functions { template - typename std::char_traits::int_type decode( + static typename std::char_traits::int_type decode( Input_iterator &iter, Sentinel sentinel, const Convert_options @@ -431,7 +432,8 @@ struct Decode_encode_functions convert_options.allow_2_byte_null, convert_options.error_value); } - Encoded_character encode(char32_t ch, const Convert_options &convert_options) noexcept + static Encoded_character encode(char32_t ch, + const Convert_options &convert_options) noexcept { return encode_utf8(ch, convert_options.use_2_byte_null); } @@ -441,7 +443,7 @@ template <> struct Decode_encode_functions { template - typename std::char_traits::int_type decode( + static typename std::char_traits::int_type decode( Input_iterator &iter, Sentinel sentinel, const Convert_options @@ -453,8 +455,8 @@ struct Decode_encode_functions convert_options.allow_unpaired_surrogate_code_points, convert_options.error_value); } - Encoded_character encode(char32_t ch, - const Convert_options &convert_options) noexcept + static Encoded_character encode(char32_t ch, + const Convert_options &convert_options) noexcept { return encode_utf16(ch); } @@ -464,7 +466,7 @@ template <> struct Decode_encode_functions { template - typename std::char_traits::int_type decode( + static typename std::char_traits::int_type decode( Input_iterator &iter, Sentinel sentinel, const Convert_options @@ -476,8 +478,8 @@ struct Decode_encode_functions convert_options.allow_unpaired_surrogate_code_points, convert_options.error_value); } - Encoded_character encode(char32_t ch, - const Convert_options &convert_options) noexcept + static Encoded_character encode(char32_t ch, + const Convert_options &convert_options) noexcept { return encode_utf32(ch); } @@ -487,7 +489,7 @@ template <> struct Decode_encode_functions { template - typename std::char_traits::int_type decode( + static typename std::char_traits::int_type decode( Input_iterator &iter, Sentinel sentinel, const Convert_options @@ -499,8 +501,8 @@ struct Decode_encode_functions convert_options.allow_unpaired_surrogate_code_points, convert_options.error_value); } - Encoded_character encode(char32_t ch, - const Convert_options &convert_options) noexcept + static Encoded_character encode(char32_t ch, + const Convert_options &convert_options) noexcept { return encode_wide(ch); } @@ -568,6 +570,5 @@ Target string_cast(basic_string_view source) } } } -} #endif /* UTIL_TEXT_H_ */ diff --git a/src/util/util_test.cpp b/src/util/util_test.cpp new file mode 100644 index 0000000..80a4c47 --- /dev/null +++ b/src/util/util_test.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Jacob Lifshay + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +int main() +{ + // tests called in static initializers +} -- 2.30.2