Treat filenames starting with a dot as filenames rather than extension.

Filenames starting with a dot (and no other dots) are commonly treated
as filenames with no extension rather than an extension. This is also
the behavior mandated in C++17 filesystem.

Reported in https://github.com/boostorg/filesystem/issues/88.
This commit is contained in:
Andrey Semashev 2021-06-06 18:31:41 +03:00
parent 62a598e3dd
commit 8328bb277b
3 changed files with 38 additions and 11 deletions

View File

@ -52,6 +52,7 @@
<li>In <code>canonical</code>, added a limit for the maximum number of symlinks that can be resolved during the call. The limit is currently at least 40 symlinks.</li>
<li><b>New:</b> Added <code>weakly_canonical</code> overloads taking <code>base</code> path as an argument.</li>
<li><b>Breaking change:</b><code>path::filename</code>, <code>path::stem</code> and <code>path::extension</code> no longer consider root name of the path as a filename if the path only consists of a root name. For example, <code>path("C:").filename()</code> used to return "C:" on Windows and will return an empty path now.</li>
<li><b>Breaking change:</b><code>path::stem</code> and <code>path::extension</code> no longer treat a filename that starts with a dot and has no other dots as an extension. Filenames starting with a dot are commonly treated as filenames with an empty extension. The leading dot is used to indicate a hidden file on most UNIX-like systems. (<a href="https://github.com/boostorg/filesystem/issues/88">#88</a>)</li>
<li><b>New:</b> Improved support for various path prefixes on Windows. Added support for local device prefix ("\\.\") and experimental support for NT path prefix ("\??\"). The prefixes will be included in the root name of a path.</li>
<li>In <code>read_symlink</code> on Windows, corrected reparse point handling. The operation would return an empty path for some mount points (for example, created by <a href="https://www.box.com/">Box</a> cloud storage driver) and directory junction points that had empty print names. The new implementation now parses substitute name of the reparse point and attempts to reconstruct a Win32 path from it. (<a href="https://github.com/boostorg/filesystem/issues/187">#187</a>)</li>
<li>Reworked <code>path::lexically_normal</code> implementation to eliminate some cases of duplicate dot (".") elements in the normalized paths.</li>

View File

@ -361,19 +361,45 @@ BOOST_FILESYSTEM_DECL bool path::has_filename() const
BOOST_FILESYSTEM_DECL path path::stem() const
{
path name(filename());
if (name == detail::dot_path() || name == detail::dot_dot_path())
return name;
size_type pos = name.m_pathname.rfind(dot);
return pos == string_type::npos ? name : path(name.m_pathname.c_str(), name.m_pathname.c_str() + pos);
if (name != detail::dot_path() && name != detail::dot_dot_path())
{
size_type pos = name.m_pathname.rfind(dot);
if (pos != 0 && pos != string_type::npos)
name.m_pathname.erase(name.m_pathname.begin() + pos, name.m_pathname.end());
}
return name;
}
BOOST_FILESYSTEM_DECL path path::extension() const
{
path name(filename());
if (name == detail::dot_path() || name == detail::dot_dot_path())
return path();
size_type pos = name.m_pathname.rfind(dot);
return pos == string_type::npos ? path() : path(name.m_pathname.c_str() + pos, name.m_pathname.c_str() + name.m_pathname.size());
path ext;
const size_type size = m_pathname.size();
size_type root_name_size = 0;
find_root_directory_start(m_pathname, size, root_name_size);
size_type fname_pos = filename_pos(m_pathname, root_name_size, size);
if
(
fname_pos >= root_name_size && fname_pos < size &&
// Check for the trailing separator
!detail::is_directory_separator(m_pathname[fname_pos]) &&
// Check for "." and ".." filenames
!(m_pathname[fname_pos] == dot &&
((size - fname_pos) == 1 || ((size - fname_pos) == 2 && m_pathname[fname_pos + 1] == dot)))
)
{
size_type ext_pos = size;
while (ext_pos > fname_pos)
{
--ext_pos;
if (m_pathname[ext_pos] == dot)
break;
}
if (ext_pos > fname_pos)
ext.assign(m_pathname.c_str() + ext_pos, m_pathname.c_str() + size);
}
return ext;
}
// lexical operations --------------------------------------------------------------//

View File

@ -823,7 +823,7 @@ void query_and_decomposition_tests()
// stem() tests not otherwise covered
BOOST_TEST(path(".").stem() == ".");
BOOST_TEST(path("..").stem() == "..");
BOOST_TEST(path(".a").stem() == "");
BOOST_TEST(path(".a").stem() == ".a");
BOOST_TEST(path("b").stem() == "b");
BOOST_TEST(path("a/b.txt").stem() == "b");
BOOST_TEST(path("a/b.").stem() == "b");
@ -833,7 +833,7 @@ void query_and_decomposition_tests()
// extension() tests not otherwise covered
BOOST_TEST(path(".").extension() == "");
BOOST_TEST(path("..").extension() == "");
BOOST_TEST(path(".a").extension() == ".a");
BOOST_TEST(path(".a").extension() == "");
BOOST_TEST(path("a/b").extension() == "");
BOOST_TEST(path("a.b/c").extension() == "");
BOOST_TEST(path("a/b.txt").extension() == ".txt");