From 458084d79b10d8216b3f486fe976a32371c6a8f5 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Thu, 14 Jul 2022 17:36:21 +0200
Subject: [PATCH] fs/Path: add GetSuffix()

---
 src/fs/AllocatedPath.hxx |  9 +++++++++
 src/fs/Path.cxx          | 19 +++++++++++++------
 src/fs/Path.hxx          |  7 +++++++
 test/fs/TestPath.cxx     | 17 +++++++++++++++++
 4 files changed, 46 insertions(+), 6 deletions(-)

diff --git a/src/fs/AllocatedPath.hxx b/src/fs/AllocatedPath.hxx
index e2ff53cbd..e07007838 100644
--- a/src/fs/AllocatedPath.hxx
+++ b/src/fs/AllocatedPath.hxx
@@ -295,6 +295,15 @@ public:
 		return Traits::Relative(c_str(), other_fs.c_str());
 	}
 
+	/**
+	 * Returns the filename suffix (including the dot) or nullptr
+	 * if the path does not have one.
+	 */
+	[[gnu::pure]]
+	const_pointer GetSuffix() const noexcept {
+		return Path{*this}.GetSuffix();
+	}
+
 	/**
 	 * Returns the filename extension (excluding the dot) or
 	 * nullptr if the path does not have one.
diff --git a/src/fs/Path.cxx b/src/fs/Path.cxx
index 6c427a4c9..708ce4f03 100644
--- a/src/fs/Path.cxx
+++ b/src/fs/Path.cxx
@@ -37,7 +37,7 @@ Path::ToUTF8Throw() const
 }
 
 Path::const_pointer
-Path::GetExtension() const noexcept
+Path::GetSuffix() const noexcept
 {
 	const auto *base = GetBase().c_str();
 
@@ -46,9 +46,16 @@ Path::GetExtension() const noexcept
 	while (*base == '.')
 		++base;
 
-	const auto *dot = StringFindLast(base, '.');
-	if (dot == nullptr)
-		return nullptr;
-
-	return dot + 1;
+	return StringFindLast(base, '.');
+}
+
+Path::const_pointer
+Path::GetExtension() const noexcept
+{
+	const auto *result = GetSuffix();
+	if (result != nullptr)
+		/* skip the dot */
+		++result;
+
+	return result;
 }
diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx
index fceb5c3b5..d874fb015 100644
--- a/src/fs/Path.hxx
+++ b/src/fs/Path.hxx
@@ -166,6 +166,13 @@ public:
 		return Traits::IsAbsolute(c_str());
 	}
 
+	/**
+	 * Returns the filename suffix (including the dot) or nullptr
+	 * if the path does not have one.
+	 */
+	[[gnu::pure]]
+	const_pointer GetSuffix() const noexcept;
+
 	/**
 	 * Returns the filename extension (excluding the dot) or
 	 * nullptr if the path does not have one.
diff --git a/test/fs/TestPath.cxx b/test/fs/TestPath.cxx
index a0c991da2..f8830feb9 100644
--- a/test/fs/TestPath.cxx
+++ b/test/fs/TestPath.cxx
@@ -79,3 +79,20 @@ TEST(Path, Extension)
 	EXPECT_STREQ(Path::FromFS(PATH_LITERAL("/foo/.bar.abc")).GetExtension(), "abc");
 	EXPECT_STREQ(Path::FromFS(PATH_LITERAL("/foo/.bar.abc.def")).GetExtension(), "def");
 }
+
+TEST(Path, Suffix)
+{
+	EXPECT_EQ(Path::FromFS(PATH_LITERAL("foo")).GetSuffix(), nullptr);
+	EXPECT_EQ(Path::FromFS(PATH_LITERAL("/foo/bar")).GetSuffix(), nullptr);
+	EXPECT_EQ(Path::FromFS(PATH_LITERAL("/foo/./bar")).GetSuffix(), nullptr);
+	EXPECT_EQ(Path::FromFS(PATH_LITERAL("/foo/.bar")).GetSuffix(), nullptr);
+	EXPECT_EQ(Path::FromFS(PATH_LITERAL("/foo/.")).GetSuffix(), nullptr);
+	EXPECT_EQ(Path::FromFS(PATH_LITERAL("/foo/..")).GetSuffix(), nullptr);
+	EXPECT_EQ(Path::FromFS(PATH_LITERAL("/foo.abc/bar")).GetSuffix(), nullptr);
+	EXPECT_EQ(Path::FromFS(PATH_LITERAL("/foo.abc/")).GetSuffix(), nullptr);
+	EXPECT_STREQ(Path::FromFS(PATH_LITERAL("/foo.abc/bar.def")).GetSuffix(), ".def");
+	EXPECT_STREQ(Path::FromFS(PATH_LITERAL("/foo.abc/bar.")).GetSuffix(), ".");
+	EXPECT_STREQ(Path::FromFS(PATH_LITERAL("/foo.abc/bar.def.ghi")).GetSuffix(), ".ghi");
+	EXPECT_STREQ(Path::FromFS(PATH_LITERAL("/foo/.bar.abc")).GetSuffix(), ".abc");
+	EXPECT_STREQ(Path::FromFS(PATH_LITERAL("/foo/.bar.abc.def")).GetSuffix(), ".def");
+}