Skip to content

Commit

Permalink
Rework path::generic_path to remove duplicate separators and retain r…
Browse files Browse the repository at this point in the history
…oot name.

std::filesystem::path::generic_string mandates that the returned string
uses *single* forward slashes for directory separators, which means
any duplicates must be removed. Boost.Filesystem now follows this definition,
and also documents that forward slashes are used for directory separators.

Additionally, since only directory separators are supposed to be affected,
avoid converting any slashes that are part of the path root name. This
is the case on Windows with UNC paths and Windows-specific path prefixes.

Closes #299.
  • Loading branch information
Lastique committed Jan 3, 2024
1 parent ae5197f commit e201ccb
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 25 deletions.
3 changes: 2 additions & 1 deletion doc/reference.html
Original file line number Diff line number Diff line change
Expand Up @@ -1582,7 +1582,8 @@ <h3> <a name="path-native-format-observers"><code><font size="4">path</font></co

<h3> <a name="path-generic-format-observers"><code><font size="4">path</font></code> generic format observers</a>
[path.generic.obs]</h3>
<p>The string returned by all generic format observers is in the <a href="#generic-pathname-format">generic pathname format</a>.</p>
<p>The string returned by all generic format observers is in the <a href="#generic-pathname-format">generic pathname format</a>.
The returned strings use a single forward slash ('/') as directory separators.</p>

<pre>template &lt;class String&gt;
String <a name="generic_string-template">generic_string</a>(const codecvt_type&amp; cvt=codecvt()) const;</pre>
Expand Down
5 changes: 5 additions & 0 deletions doc/release_history.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
</td>
</table>

<h2>1.85.0</h2>
<ul>
<li><code>path::generic_path</code> and <code>path::generic_string</code> methods now remove duplicate directory separators in the returned paths. The methods also avoid converting backslashes to forward slashes in root names of the paths. For example, on Windows, <code>path("\\\\?\\c:\\foo").generic_string()</code> now returns "\\?\c:/foo" instead of "//?/c:/foo". Similarly, <code>path("\\\\host\\share").generic_string()</code> now returns "\\host/share".</li>
</ul>

<h2>1.84.0</h2>
<ul>
<li>As was announced in Boost 1.82.0, C++03 is no longer supported. A C++11 or later compiler is required.</li>
Expand Down
21 changes: 1 addition & 20 deletions include/boost/filesystem/path.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -976,30 +976,18 @@ class path :
// Experimental generic function returning generic formatted path (i.e. separators
// are forward slashes). Motivation: simpler than a family of generic_*string
// functions.
#ifdef BOOST_WINDOWS_API
BOOST_FILESYSTEM_DECL path generic_path() const;
#else
path generic_path() const;
#endif

template< typename String >
String generic_string() const;

template< typename String >
String generic_string(codecvt_type const& cvt) const;

#ifdef BOOST_WINDOWS_API
std::string generic_string() const { return generic_path().string(); }
std::string generic_string(codecvt_type const& cvt) const { return generic_path().string(cvt); }
std::wstring generic_wstring() const { return generic_path().wstring(); }
std::wstring generic_wstring(codecvt_type const&) const { return generic_wstring(); }
#else // BOOST_POSIX_API
// On POSIX-like systems, the generic format is the same as the native format
std::string const& generic_string() const { return m_pathname; }
std::string const& generic_string(codecvt_type const&) const { return m_pathname; }
std::wstring generic_wstring() const { return this->wstring(); }
std::wstring generic_wstring(codecvt_type const& cvt) const { return this->wstring(cvt); }
#endif
std::wstring generic_wstring(codecvt_type const& cvt) const { return generic_path().wstring(cvt); }

// ----- compare -----

Expand Down Expand Up @@ -1629,13 +1617,6 @@ BOOST_FORCEINLINE path& path::operator/=(path const& p)
return append(p);
}

#if !defined(BOOST_WINDOWS_API)
inline path path::generic_path() const
{
return path(*this);
}
#endif

inline path path::lexically_proximate(path const& base) const
{
path tmp(lexically_relative(base));
Expand Down
43 changes: 39 additions & 4 deletions src/path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -825,15 +825,50 @@ BOOST_FILESYSTEM_DECL path path::lexically_relative(path const& base) const
return tmp;
}

#if defined(BOOST_WINDOWS_API)

BOOST_FILESYSTEM_DECL path path::generic_path() const
{
path tmp(*this);
std::replace(tmp.m_pathname.begin(), tmp.m_pathname.end(), L'\\', L'/');
path tmp;
const size_type size = m_pathname.size();
tmp.m_pathname.reserve(size);

const value_type* const p = m_pathname.c_str();

// Treat root name specially as it may contain backslashes, duplicate ones too,
// in case of UNC paths and Windows-specific prefixes.
size_type root_name_size = 0u;
size_type root_dir_pos = find_root_directory_start(p, size, root_name_size);
if (root_name_size > 0u)
tmp.m_pathname.append(p, root_name_size);

size_type pos = root_name_size;
if (root_dir_pos < size)
{
tmp.m_pathname.push_back(path::separator);
pos = root_dir_pos + 1u;
}

while (pos < size)
{
size_type element_size = find_separator(p + pos, size - pos);
if (element_size > 0u)
{
tmp.m_pathname.append(p + pos, element_size);

pos += element_size;
if (pos >= size)
break;

tmp.m_pathname.push_back(path::separator);
}

++pos;
}

return tmp;
}

#if defined(BOOST_WINDOWS_API)

BOOST_FILESYSTEM_DECL path& path::make_preferred()
{
std::replace(m_pathname.begin(), m_pathname.end(), L'/', L'\\');
Expand Down
41 changes: 41 additions & 0 deletions test/path_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2618,6 +2618,46 @@ void make_preferred_tests()
}
}

// generic_path_tests --------------------------------------------------------------//

void generic_path_tests()
{
std::cout << "generic_path_tests..." << std::endl;

BOOST_TEST_EQ(path("").generic_path().string(), std::string(""));
BOOST_TEST_EQ(path("/").generic_path().string(), std::string("/"));
BOOST_TEST_EQ(path("//").generic_path().string(), std::string("//"));
BOOST_TEST_EQ(path("///").generic_path().string(), std::string("/"));

BOOST_TEST_EQ(path("foo").generic_path().string(), std::string("foo"));
BOOST_TEST_EQ(path("foo/bar").generic_path().string(), std::string("foo/bar"));
BOOST_TEST_EQ(path("..").generic_path().string(), std::string(".."));
BOOST_TEST_EQ(path("../..").generic_path().string(), std::string("../.."));
BOOST_TEST_EQ(path("/..").generic_path().string(), std::string("/.."));
BOOST_TEST_EQ(path("../foo").generic_path().string(), std::string("../foo"));
BOOST_TEST_EQ(path("foo/..").generic_path().string(), std::string("foo/.."));
BOOST_TEST_EQ(path("foo/../").generic_path().string(), std::string("foo/../"));

BOOST_TEST_EQ(path("foo//bar").generic_path().string(), std::string("foo/bar"));

BOOST_TEST_EQ(path("//net//foo//bar").generic_path().string(), std::string("//net/foo/bar"));

if (platform == "Windows")
{
BOOST_TEST_EQ(path("c:\\foo\\bar").generic_path().string(), std::string("c:/foo/bar"));
BOOST_TEST_EQ(path("c:\\\\foo\\\\bar//zoo").generic_path().string(), std::string("c:/foo/bar/zoo"));

BOOST_TEST_EQ(path("c:foo\\\\bar//zoo").generic_path().string(), std::string("c:foo/bar/zoo"));

BOOST_TEST_EQ(path("\\\\net\\foo\\bar").generic_path().string(), std::string("\\\\net/foo/bar"));
BOOST_TEST_EQ(path("\\\\net\\\\foo\\/bar//zoo").generic_path().string(), std::string("\\\\net/foo/bar/zoo"));

BOOST_TEST_EQ(path("\\\\?\\c:\\\\foo\\\\bar//zoo").generic_path().string(), std::string("\\\\?\\c:/foo/bar/zoo"));
BOOST_TEST_EQ(path("\\\\.\\c:\\\\foo\\\\bar//zoo").generic_path().string(), std::string("\\\\.\\c:/foo/bar/zoo"));
BOOST_TEST_EQ(path("\\??\\c:\\\\foo\\\\bar//zoo").generic_path().string(), std::string("\\??\\c:/foo/bar/zoo"));
}
}

// lexically_normal_tests ----------------------------------------------------------//

void lexically_normal_tests()
Expand Down Expand Up @@ -2864,6 +2904,7 @@ int cpp_main(int, char*[])
name_function_tests();
replace_extension_tests();
make_preferred_tests();
generic_path_tests();
lexically_normal_tests();
compare_tests();

Expand Down

0 comments on commit e201ccb

Please sign in to comment.