From 2f6391b931f1bbae44ddbfbc53efe6e6f9f7af52 Mon Sep 17 00:00:00 2001 From: Beman Date: Tue, 1 Dec 2015 10:17:45 -0500 Subject: [PATCH] Add class path constexpr constants separator and dot of the type appropriate for the platform, and add class path member query functions filename_is_dot() and filename_is_dot_dot(). These add convenience and the implementations may be more efficient that user coded equivalent functions. Also add detail functions is_directory_separator() and is_element_separator(), and replace all uses of local is_separator() in path.cpp with detail::is_directory_separator(). --- doc/reference.html | 74 +++++++++++++++++++----- doc/release_history.html | 13 ++++- include/boost/filesystem/path.hpp | 60 +++++++++++++++++-- src/operations.cpp | 7 +-- src/path.cpp | 95 ++++++++++++++----------------- test/operations_test.cpp | 1 + test/path_unit_test.cpp | 45 +++++++++++++++ 7 files changed, 219 insertions(+), 76 deletions(-) diff --git a/doc/reference.html b/doc/reference.html index b4fb0a7..31dfe03 100644 --- a/doc/reference.html +++ b/doc/reference.html @@ -21,7 +21,7 @@
-

+

boost.png (6897 bytes)

@@ -51,7 +51,8 @@ -

Headings and synopsis for recently added functionality is show with a green background.

+

Headings and synopsis for recently added functionality are shown with a +green background.

@@ -146,7 +147,8 @@      last_write_time
     permissions
     read_symlink
-     relative
+     + relative
     remove
     remove_all
     rename
@@ -158,7 +160,8 @@      system_complete
     temp_directory_path
     unique_path
-     weakly_canonical
+     + weakly_canonical
File streams
Path decomposition table
Warning: Long paths on Windows and the @@ -386,7 +389,7 @@ native pathname format.

native pathname format [fs.def.native]

The operating system dependent pathname format accepted by the host operating system.

-

normal form path [fs.def.normal]

+

normal form path [fs.def.normal]

A path with no redundant current directory (dot) or parent directory (dot-dot) elements. The normal form for an empty path is an empty path. The normal form for a path ending in a directory-separator that is not the root directory @@ -998,6 +1001,8 @@ of a path. The path does not necessarily exist in external storage, and the pathname is not necessarily valid for the current operating system or for a particular file system.

+
+
namespace boost
 {
   namespace filesystem
@@ -1008,6 +1013,8 @@ system or for a particular file system.

typedef see below value_type; typedef std::basic_string<value_type> string_type; typedef std::codecvt<wchar_t, char, std::mbstate_t> codecvt_type; + constexpr value_type dot; + constexpr value_type separator; constexpr value_type preferred_separator; // constructors and destructor @@ -1117,6 +1124,8 @@ system or for a particular file system.

// query bool empty() const; + bool filename_is_dot() const; + bool filename_is_dot_dot() const; bool has_root_name() const; bool has_root_directory() const; bool has_root_path() const; @@ -1150,6 +1159,8 @@ system or for a particular file system.

} // namespace filesystem } // namespace boost
+
+

value_type is a typedef for the character type used by the operating system to represent pathnames.

@@ -1516,7 +1527,7 @@ does not affect path equality. —end example]

Returns: pathname.c_str().

-
string_type::size_type size() const noexcept;
+
string_type::size_type size() const noexcept;

Returns: pathname.size().

@@ -1704,11 +1715,10 @@ for (; !p.extension().empty(); p = p.stem())

—end example]

[Note: The dot is included in the return value so that it is - possible to distinguish between no extension and an empty extension. - See - - http://permalink.gmane.org/gmane.comp.lib.boost.devel/199744 for more - extensive rationale.  —end note]

+ possible to distinguish between no extension and an empty extension. + See + http://permalink.gmane.org/gmane.comp.lib.boost.devel/199744 for more + extensive rationale.  —end note]

path query [path.query]

@@ -1718,6 +1728,34 @@ for (; !p.extension().empty(); p = p.stem())

Returns: m_pathname.empty().

+
bool filename_is_dot() const;
+ +
+

Returns: filename() == path(".")

+

[Example:

+
+ +
std::cout << path(".").filename_is_dot();     // outputs 1
+std::cout << path("/.").filename_is_dot();    // outputs 1
+std::cout << path("foo/.").filename_is_dot(); // outputs 1
+std::cout << path("foo/").filename_is_dot();  // outputs 1
+std::cout << path("/").filename_is_dot();     // outputs 0
+std::cout << path("/foo").filename_is_dot();  // outputs 0
+std::cout << path("/foo.").filename_is_dot(); // outputs 0
+std::cout << path("..").filename_is_dot();    // outputs 0
+ +
+

See the last bullet item in the forward traversal order + list for why path("foo/").filename() is a dot filename.

+

—end example]

+
+ +
bool filename_is_dot_dot() const;
+ +
+

Returns: filename() == path("..")

+
+
bool has_root_path() const;
@@ -1897,7 +1935,11 @@ const string_type external_directory_string() const { return native(); }

[Note: A path aware lexicographical_compare algorithm is provided for historical reasons. —end note]

-
path lexically_normal(const path& p);
+
+ +
path lexically_normal(const path& p);
+ +

Overview: Returns p with redundant current @@ -1917,7 +1959,11 @@ const string_type external_directory_string() const { return native(); } —end example]

-
path lexically_relative(const path& p, const path& base);
+
+ +
path lexically_relative(const path& p, const path& base);
+ +

Overview: Returns p made relative to @@ -4221,7 +4267,7 @@ multiple string types. His idea became the basis for the version 3 path design.< www.boost.org/LICENSE_1_0.txt

Revised -25 October 2015

+01 December 2015

\ No newline at end of file diff --git a/doc/release_history.html b/doc/release_history.html index b7189e9..a7cd6c7 100644 --- a/doc/release_history.html +++ b/doc/release_history.html @@ -38,6 +38,17 @@
+

1.61.0

+ +

1.60.0


Revised -21 November, 2015

+01 December, 2015

© Copyright Beman Dawes, 2011

Use, modification, and distribution are subject to the Boost Software License, Version 1.0. See diff --git a/include/boost/filesystem/path.hpp b/include/boost/filesystem/path.hpp index af5bfcc..9c6e32a 100644 --- a/include/boost/filesystem/path.hpp +++ b/include/boost/filesystem/path.hpp @@ -62,10 +62,14 @@ namespace filesystem # ifdef BOOST_WINDOWS_API typedef wchar_t value_type; + BOOST_STATIC_CONSTEXPR value_type separator = L'/'; BOOST_STATIC_CONSTEXPR value_type preferred_separator = L'\\'; + BOOST_STATIC_CONSTEXPR value_type dot = L'.'; # else typedef char value_type; + BOOST_STATIC_CONSTEXPR value_type separator = '/'; BOOST_STATIC_CONSTEXPR value_type preferred_separator = '/'; + BOOST_STATIC_CONSTEXPR value_type dot = '.'; # endif typedef std::basic_string string_type; typedef std::codecvt= 2 && m_pathname[size()-1] == dot && m_pathname[size()-2] == dot + && (m_pathname.size() == 2 || detail::is_element_separator(m_pathname[size()-3])); + // use detail::is_element_separator() rather than detail::is_directory_separator + // to deal with "c:.." edge case on Windows when ':' acts as a separator + } //--------------------------------------------------------------------------------------// // class path member template implementation // diff --git a/src/operations.cpp b/src/operations.cpp index b5e0f66..f34b076 100644 --- a/src/operations.cpp +++ b/src/operations.cpp @@ -937,12 +937,9 @@ namespace detail BOOST_FILESYSTEM_DECL bool create_directories(const path& p, system::error_code* ec) { - path filename(p.filename()); - if ((filename.native().size() == 1 && filename.native()[0] == dot) - || (filename.native().size() == 2 - && filename.native()[0] == dot && filename.native()[1] == dot)) + if (p.filename_is_dot() || p.filename_is_dot_dot()) return create_directories(p.parent_path(), ec); - + error_code local_ec; file_status p_status = status(p, local_ec); diff --git a/src/path.cpp b/src/path.cpp index dcef9d0..6e4ceb8 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -96,15 +96,6 @@ namespace # endif - inline bool is_separator(fs::path::value_type c) - { - return c == separator -# ifdef BOOST_WINDOWS_API - || c == path::preferred_separator -# endif - ; - } - bool is_root_separator(const string_type& str, size_type pos); // pos is position of the separator @@ -145,13 +136,13 @@ namespace filesystem if (this == &p) // self-append { path rhs(p); - if (!is_separator(rhs.m_pathname[0])) + if (!detail::is_directory_separator(rhs.m_pathname[0])) m_append_separator_if_needed(); m_pathname += rhs.m_pathname; } else { - if (!is_separator(*p.m_pathname.begin())) + if (!detail::is_directory_separator(*p.m_pathname.begin())) m_append_separator_if_needed(); m_pathname += p.m_pathname; } @@ -166,13 +157,13 @@ namespace filesystem && ptr < m_pathname.data() + m_pathname.size()) // overlapping source { path rhs(ptr); - if (!is_separator(rhs.m_pathname[0])) + if (!detail::is_directory_separator(rhs.m_pathname[0])) m_append_separator_if_needed(); m_pathname += rhs.m_pathname; } else { - if (!is_separator(*ptr)) + if (!detail::is_directory_separator(*ptr)) m_append_separator_if_needed(); m_pathname += ptr; } @@ -217,7 +208,7 @@ namespace filesystem # ifdef BOOST_WINDOWS_API *(m_pathname.end()-1) != colon && # endif - !is_separator(*(m_pathname.end()-1))) + !detail::is_directory_separator(*(m_pathname.end()-1))) { string_type::size_type tmp(m_pathname.size()); m_pathname += preferred_separator; @@ -257,7 +248,8 @@ namespace filesystem path& path::remove_trailing_separator() { - if (!m_pathname.empty() && is_separator(m_pathname[m_pathname.size() - 1])) + if (!m_pathname.empty() + && detail::is_directory_separator(m_pathname[m_pathname.size() - 1])) m_pathname.erase(m_pathname.size() - 1); return *this; } @@ -294,8 +286,8 @@ namespace filesystem return (itr.m_pos != m_pathname.size() && ( (itr.m_element.m_pathname.size() > 1 - && is_separator(itr.m_element.m_pathname[0]) - && is_separator(itr.m_element.m_pathname[1]) + && detail::is_directory_separator(itr.m_element.m_pathname[0]) + && detail::is_directory_separator(itr.m_element.m_pathname[1]) ) # ifdef BOOST_WINDOWS_API || itr.m_element.m_pathname[itr.m_element.m_pathname.size()-1] == colon @@ -319,7 +311,7 @@ namespace filesystem iterator itr(begin()); for (; itr.m_pos != m_pathname.size() - && (is_separator(itr.m_element.m_pathname[0]) + && (detail::is_directory_separator(itr.m_element.m_pathname[0]) # ifdef BOOST_WINDOWS_API || itr.m_element.m_pathname[itr.m_element.m_pathname.size()-1] == colon # endif @@ -333,14 +325,14 @@ namespace filesystem size_type end_pos(filename_pos(m_pathname, m_pathname.size())); bool filename_was_separator(m_pathname.size() - && is_separator(m_pathname[end_pos])); + && detail::is_directory_separator(m_pathname[end_pos])); // skip separators unless root directory size_type root_dir_pos(root_directory_start(m_pathname, end_pos)); for (; end_pos > 0 && (end_pos-1) != root_dir_pos - && is_separator(m_pathname[end_pos-1]) + && detail::is_directory_separator(m_pathname[end_pos-1]) ; --end_pos) {} @@ -362,7 +354,7 @@ namespace filesystem size_type pos(filename_pos(m_pathname, m_pathname.size())); return (m_pathname.size() && pos - && is_separator(m_pathname[pos]) + && detail::is_directory_separator(m_pathname[pos]) && !is_root_separator(m_pathname, pos)) ? detail::dot_path() : path(m_pathname.c_str() + pos); @@ -513,11 +505,11 @@ namespace bool is_root_separator(const string_type & str, size_type pos) // pos is position of the separator { - BOOST_ASSERT_MSG(!str.empty() && is_separator(str[pos]), + BOOST_ASSERT_MSG(!str.empty() && fs::detail::is_directory_separator(str[pos]), "precondition violation"); // subsequent logic expects pos to be for leftmost slash of a set - while (pos > 0 && is_separator(str[pos-1])) + while (pos > 0 && fs::detail::is_directory_separator(str[pos-1])) --pos; // "/" [...] @@ -531,7 +523,8 @@ namespace # endif // "//" name "/" - if (pos < 3 || !is_separator(str[0]) || !is_separator(str[1])) + if (pos < 3 || !fs::detail::is_directory_separator(str[0]) + || !fs::detail::is_directory_separator(str[1])) return false; return str.find_first_of(separators, 2) == pos; @@ -545,11 +538,11 @@ namespace { // case: "//" if (end_pos == 2 - && is_separator(str[0]) - && is_separator(str[1])) return 0; + && fs::detail::is_directory_separator(str[0]) + && fs::detail::is_directory_separator(str[1])) return 0; // case: ends in "/" - if (end_pos && is_separator(str[end_pos-1])) + if (end_pos && fs::detail::is_directory_separator(str[end_pos-1])) return end_pos-1; // set pos to start of last element @@ -561,7 +554,7 @@ namespace # endif return (pos == string_type::npos // path itself must be a filename (or empty) - || (pos == 1 && is_separator(str[0]))) // or net + || (pos == 1 && fs::detail::is_directory_separator(str[0]))) // or net ? 0 // so filename is entire string : pos + 1; // or starts after delimiter } @@ -576,21 +569,21 @@ namespace // case "c:/" if (size > 2 && path[1] == colon - && is_separator(path[2])) return 2; + && fs::detail::is_directory_separator(path[2])) return 2; # endif // case "//" if (size == 2 - && is_separator(path[0]) - && is_separator(path[1])) return string_type::npos; + && fs::detail::is_directory_separator(path[0]) + && fs::detail::is_directory_separator(path[1])) return string_type::npos; # ifdef BOOST_WINDOWS_API // case "\\?\" if (size > 4 - && is_separator(path[0]) - && is_separator(path[1]) + && fs::detail::is_directory_separator(path[0]) + && fs::detail::is_directory_separator(path[1]) && path[2] == questionmark - && is_separator(path[3])) + && fs::detail::is_directory_separator(path[3])) { string_type::size_type pos(path.find_first_of(separators, 4)); return pos < size ? pos : string_type::npos; @@ -599,16 +592,16 @@ namespace // case "//net {/}" if (size > 3 - && is_separator(path[0]) - && is_separator(path[1]) - && !is_separator(path[2])) + && fs::detail::is_directory_separator(path[0]) + && fs::detail::is_directory_separator(path[1]) + && !fs::detail::is_directory_separator(path[2])) { string_type::size_type pos(path.find_first_of(separators, 2)); return pos < size ? pos : string_type::npos; } // case "/" - if (size > 0 && is_separator(path[0])) return 0; + if (size > 0 && fs::detail::is_directory_separator(path[0])) return 0; return string_type::npos; } @@ -632,22 +625,22 @@ namespace string_type::size_type cur(0); // deal with // [network] - if (size >= 2 && is_separator(src[0]) - && is_separator(src[1]) + if (size >= 2 && fs::detail::is_directory_separator(src[0]) + && fs::detail::is_directory_separator(src[1]) && (size == 2 - || !is_separator(src[2]))) + || !fs::detail::is_directory_separator(src[2]))) { cur += 2; element_size += 2; } // leading (not non-network) separator - else if (is_separator(src[0])) + else if (fs::detail::is_directory_separator(src[0])) { ++element_size; // bypass extra leading separators while (cur+1 < size - && is_separator(src[cur+1])) + && fs::detail::is_directory_separator(src[cur+1])) { ++cur; ++element_pos; @@ -663,7 +656,7 @@ namespace # ifdef BOOST_WINDOWS_API && src[cur] != colon # endif - && !is_separator(src[cur])) + && !fs::detail::is_directory_separator(src[cur])) { ++cur; ++element_size; @@ -772,12 +765,12 @@ namespace filesystem // both POSIX and Windows treat paths that begin with exactly two separators specially bool was_net(it.m_element.m_pathname.size() > 2 - && is_separator(it.m_element.m_pathname[0]) - && is_separator(it.m_element.m_pathname[1]) - && !is_separator(it.m_element.m_pathname[2])); + && detail::is_directory_separator(it.m_element.m_pathname[0]) + && detail::is_directory_separator(it.m_element.m_pathname[1]) + && !detail::is_directory_separator(it.m_element.m_pathname[2])); // process separator (Windows drive spec is only case not a separator) - if (is_separator(it.m_path_ptr->m_pathname[it.m_pos])) + if (detail::is_directory_separator(it.m_path_ptr->m_pathname[it.m_pos])) { // detect root directory if (was_net @@ -793,7 +786,7 @@ namespace filesystem // skip separators until it.m_pos points to the start of the next element while (it.m_pos != it.m_path_ptr->m_pathname.size() - && is_separator(it.m_path_ptr->m_pathname[it.m_pos])) + && detail::is_directory_separator(it.m_path_ptr->m_pathname[it.m_pos])) { ++it.m_pos; } // detect trailing separator, and treat it as ".", per POSIX spec @@ -822,7 +815,7 @@ namespace filesystem // if at end and there was a trailing non-root '/', return "." if (it.m_pos == it.m_path_ptr->m_pathname.size() && it.m_path_ptr->m_pathname.size() > 1 - && is_separator(it.m_path_ptr->m_pathname[it.m_pos-1]) + && detail::is_directory_separator(it.m_path_ptr->m_pathname[it.m_pos-1]) && !is_root_separator(it.m_path_ptr->m_pathname, it.m_pos-1) ) { @@ -838,7 +831,7 @@ namespace filesystem ; end_pos > 0 && (end_pos-1) != root_dir_pos - && is_separator(it.m_path_ptr->m_pathname[end_pos-1]) + && detail::is_directory_separator(it.m_path_ptr->m_pathname[end_pos-1]) ; --end_pos) {} diff --git a/test/operations_test.cpp b/test/operations_test.cpp index 31ba341..9f09da6 100644 --- a/test/operations_test.cpp +++ b/test/operations_test.cpp @@ -1108,6 +1108,7 @@ namespace // trailing "/.", "/./..", and "/" in the above elements test ticket #7258 and // related issues + cout << " p is " << p << endl; BOOST_TEST(!fs::exists(p)); BOOST_TEST(fs::create_directories(p)); BOOST_TEST(fs::exists(p)); diff --git a/test/path_unit_test.cpp b/test/path_unit_test.cpp index 78e207f..c81ead8 100644 --- a/test/path_unit_test.cpp +++ b/test/path_unit_test.cpp @@ -606,6 +606,51 @@ namespace CHECK(p1 == "bar"); CHECK(p2 == "foo"); + + CHECK(!path("").filename_is_dot()); + CHECK(!path("").filename_is_dot_dot()); + CHECK(!path("..").filename_is_dot()); + CHECK(!path(".").filename_is_dot_dot()); + CHECK(!path("...").filename_is_dot_dot()); + CHECK(path(".").filename_is_dot()); + CHECK(path("..").filename_is_dot_dot()); + CHECK(path("/.").filename_is_dot()); + CHECK(path("/..").filename_is_dot_dot()); + CHECK(!path("a.").filename_is_dot()); + CHECK(!path("a..").filename_is_dot_dot()); + + // edge cases + CHECK(path("foo/").filename() == path(".")); + CHECK(path("foo/").filename_is_dot()); + CHECK(path("/").filename() == path("/")); + CHECK(!path("/").filename_is_dot()); +# ifdef BOOST_WINDOWS_API + CHECK(path("c:.").filename() == path(".")); + CHECK(path("c:.").filename_is_dot()); + CHECK(path("c:/").filename() == path("/")); + CHECK(!path("c:\\").filename_is_dot()); +# else + CHECK(path("c:.").filename() == path("c:.")); + CHECK(!path("c:.").filename_is_dot()); + CHECK(path("c:/").filename() == path(".")); + CHECK(path("c:/").filename_is_dot()); +# endif + + // check that the implementation code to make the edge cases above work right + // doesn't cause some non-edge cases to fail + CHECK(path("c:").filename() != path(".")); + CHECK(!path("c:").filename_is_dot()); + + // examples from reference.html + std::cout << path(".").filename_is_dot(); // outputs 1 + std::cout << path("/.").filename_is_dot(); // outputs 1 + std::cout << path("foo/.").filename_is_dot(); // outputs 1 + std::cout << path("foo/").filename_is_dot(); // outputs 1 + std::cout << path("/").filename_is_dot(); // outputs 0 + std::cout << path("/foo").filename_is_dot(); // outputs 0 + std::cout << path("/foo.").filename_is_dot(); // outputs 0 + std::cout << path("..").filename_is_dot(); // outputs 0 + cout << std::endl; } // // test_modifiers ------------------------------------------------------------------//