diff options
-rw-r--r-- | base/file_path.cc | 85 | ||||
-rw-r--r-- | base/file_path.h | 33 | ||||
-rw-r--r-- | base/file_path_unittest.cc | 159 | ||||
-rw-r--r-- | base/file_util.h | 22 |
4 files changed, 282 insertions, 17 deletions
diff --git a/base/file_path.cc b/base/file_path.cc index 9a645b0..8738c37 100644 --- a/base/file_path.cc +++ b/base/file_path.cc @@ -21,6 +21,9 @@ const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("/"); const FilePath::CharType FilePath::kCurrentDirectory[] = FILE_PATH_LITERAL("."); const FilePath::CharType FilePath::kParentDirectory[] = FILE_PATH_LITERAL(".."); +const FilePath::CharType FilePath::kExtensionSeparator = FILE_PATH_LITERAL('.'); + + namespace { // If this FilePath contains a drive letter specification, returns the @@ -38,10 +41,8 @@ FilePath::StringType::size_type FindDriveLetter( (path[0] >= L'a' && path[0] <= L'z'))) { return 1; } - return FilePath::StringType::npos; -#else // FILE_PATH_USES_DRIVE_LETTERS - return FilePath::StringType::npos; #endif // FILE_PATH_USES_DRIVE_LETTERS + return FilePath::StringType::npos; } bool IsPathAbsolute(const FilePath::StringType& path) { @@ -136,7 +137,83 @@ FilePath FilePath::BaseName() const { return new_path; } -FilePath FilePath::Append(const FilePath::StringType& component) const { +FilePath::StringType FilePath::Extension() const { + // BaseName() calls StripTrailingSeparators, so cases like /foo.baz/// work. + StringType base = BaseName().value(); + + // Special case "." and ".." + if (base == kCurrentDirectory || base == kParentDirectory) + return StringType(); + + const StringType::size_type last_dot = base.rfind(kExtensionSeparator); + if (last_dot == StringType::npos) + return StringType(); + return StringType(base, last_dot); +} + +FilePath FilePath::RemoveExtension() const { + StringType ext = Extension(); + // It's important to check Extension() since that verifies that the + // kExtensionSeparator actually appeared in the last path component. + if (ext.empty()) + return FilePath(path_); + // Since Extension() verified that the extension is in fact in the last path + // component, this substr will effectively strip trailing separators. + const StringType::size_type last_dot = path_.rfind(kExtensionSeparator); + return FilePath(path_.substr(0, last_dot)); +} + +FilePath FilePath::InsertBeforeExtension(const StringType& suffix) const { + if (suffix.empty()) + return FilePath(path_); + + if (path_.empty()) + return FilePath(); + + StringType base = BaseName().value(); + if (base.empty()) + return FilePath(); + if (*(base.end() - 1) == kExtensionSeparator) { + // Special case "." and ".." + if (base == kCurrentDirectory || base == kParentDirectory) { + return FilePath(); + } + } + + StringType ext = Extension(); + StringType ret = RemoveExtension().value(); + ret.append(suffix); + ret.append(ext); + return FilePath(ret); +} + +FilePath FilePath::ReplaceExtension(const StringType& extension) const { + if (path_.empty()) + return FilePath(); + + StringType base = BaseName().value(); + if (base.empty()) + return FilePath(); + if (*(base.end() - 1) == kExtensionSeparator) { + // Special case "." and ".." + if (base == kCurrentDirectory || base == kParentDirectory) { + return FilePath(); + } + } + + FilePath no_ext = RemoveExtension(); + // If the new extension is "" or ".", then just remove the current extension. + if (extension.empty() || extension == StringType(1, kExtensionSeparator)) + return no_ext; + + StringType str = no_ext.value(); + if (extension[0] != kExtensionSeparator) + str.append(1, kExtensionSeparator); + str.append(extension); + return FilePath(str); +} + +FilePath FilePath::Append(const StringType& component) const { DCHECK(!IsPathAbsolute(component)); if (path_.compare(kCurrentDirectory) == 0) { // Append normally doesn't do any normalization, but as a special case, diff --git a/base/file_path.h b/base/file_path.h index 6ed9320..d80298d 100644 --- a/base/file_path.h +++ b/base/file_path.h @@ -109,6 +109,9 @@ class FilePath { // A special path component meaning "the parent directory." static const CharType kParentDirectory[]; + // The character used to identify a file extension. + static const CharType kExtensionSeparator; + FilePath() {} FilePath(const FilePath& that) : path_(that.path_) {} explicit FilePath(const StringType& path) : path_(path) {} @@ -147,6 +150,36 @@ class FilePath { // this is the only situation in which BaseName will return an absolute path. FilePath BaseName() const; + // Returns ".jpg" for path "C:\pics\jojo.jpg", or an empty string if + // the file has no extension. If non-empty, Extension() will always start + // with precisely one ".". The following code should always work regardless + // of the value of path. + // new_path = path.RemoveExtension().value().append(path.Extension()); + // ASSERT(new_path == path.value()); + // NOTE: this is different from the original file_util implementation which + // returned the extension without a leading "." ("jpg" instead of ".jpg") + StringType Extension() const; + + // Returns "C:\pics\jojo" for path "C:\pics\jojo.jpg" + // NOTE: this is slightly different from the similar file_util implementation + // which returned simply 'jojo'. + FilePath RemoveExtension() const; + + // Inserts |suffix| after the file name portion of |path| but before the + // extension. Returns "" if BaseName() == "." or "..". + // Examples: + // path == "C:\pics\jojo.jpg" suffix == " (1)", returns "C:\pics\jojo (1).jpg" + // path == "jojo.jpg" suffix == " (1)", returns "jojo (1).jpg" + // path == "C:\pics\jojo" suffix == " (1)", returns "C:\pics\jojo (1)" + // path == "C:\pics.old\jojo" suffix == " (1)", returns "C:\pics.old\jojo (1)" + FilePath InsertBeforeExtension(const StringType& suffix) const; + + // Replaces the extension of |file_name| with |extension|. If |file_name| + // does not have an extension, them |extension| is added. If |extension| is + // empty, then the extension is removed from |file_name|. + // Returns "" if BaseName() == "." or "..". + FilePath ReplaceExtension(const StringType& extension) const; + // Returns a FilePath by appending a separator and the supplied path // component to this object's path. Append takes care to avoid adding // excessive separators if this object's path already ends with a separator. diff --git a/base/file_path_unittest.cc b/base/file_path_unittest.cc index b4ad16f..ac45e46 100644 --- a/base/file_path_unittest.cc +++ b/base/file_path_unittest.cc @@ -406,3 +406,162 @@ TEST_F(FilePathTest, Contains) { // Note: whether } + +TEST_F(FilePathTest, Extension) { + FilePath base_dir(FILE_PATH_LITERAL("base_dir")); + + FilePath jpg = base_dir.Append(FILE_PATH_LITERAL("foo.jpg")); + EXPECT_EQ(jpg.Extension(), FILE_PATH_LITERAL(".jpg")); + + FilePath base = jpg.BaseName().RemoveExtension(); + EXPECT_EQ(base.value(), FILE_PATH_LITERAL("foo")); + + FilePath path_no_ext = base_dir.Append(base); + EXPECT_EQ(jpg.RemoveExtension().value(), path_no_ext.value()); + + EXPECT_EQ(path_no_ext.value(), path_no_ext.RemoveExtension().value()); + EXPECT_EQ(path_no_ext.Extension(), FILE_PATH_LITERAL("")); +} + +TEST_F(FilePathTest, Extension2) { + const struct UnaryTestData cases[] = { +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("C:\\a\\b\\c.ext"), FPL(".ext") }, + { FPL("C:\\a\\b\\c."), FPL(".") }, + { FPL("C:\\a\\b\\c"), FPL("") }, + { FPL("C:\\a\\b\\"), FPL("") }, + { FPL("C:\\a\\b.\\"), FPL(".") }, + { FPL("C:\\a\\b\\c.ext1.ext2"), FPL(".ext2") }, + { FPL("C:\\foo.bar\\\\\\"), FPL(".bar") }, + { FPL("C:\\foo.bar\\.."), FPL("") }, + { FPL("C:\\foo.bar\\..\\\\"), FPL("") }, +#endif + { FPL("/foo/bar/baz.ext"), FPL(".ext") }, + { FPL("/foo/bar/baz."), FPL(".") }, + { FPL("/foo/bar/baz.."), FPL(".") }, + { FPL("/foo/bar/baz"), FPL("") }, + { FPL("/foo/bar/"), FPL("") }, + { FPL("/foo/bar./"), FPL(".") }, + { FPL("/foo/bar/baz.ext1.ext2"), FPL(".ext2") }, + { FPL("."), FPL("") }, + { FPL(".."), FPL("") }, + { FPL("./foo"), FPL("") }, + { FPL("./foo.ext"), FPL(".ext") }, + { FPL("/foo.ext1/bar.ext2"), FPL(".ext2") }, + { FPL("/foo.bar////"), FPL(".bar") }, + { FPL("/foo.bar/.."), FPL("") }, + { FPL("/foo.bar/..////"), FPL("") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].input); + FilePath::StringType extension = path.Extension(); + EXPECT_STREQ(cases[i].expected, extension.c_str()) << "i: " << i << + ", path: " << path.value(); + } +} + +TEST_F(FilePathTest, InsertBeforeExtension) { + const struct BinaryTestData cases[] = { + { { FPL(""), FPL("") }, FPL("") }, + { { FPL(""), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("txt") }, FPL("") }, + { { FPL(".."), FPL("txt") }, FPL("") }, + { { FPL("foo.dll"), FPL("txt") }, FPL("footxt.dll") }, + { { FPL("."), FPL("") }, FPL(".") }, + { { FPL("foo.dll"), FPL(".txt") }, FPL("foo.txt.dll") }, + { { FPL("foo"), FPL("txt") }, FPL("footxt") }, + { { FPL("foo"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo.baz.dll"), FPL("txt") }, FPL("foo.baztxt.dll") }, + { { FPL("foo.baz.dll"), FPL(".txt") }, FPL("foo.baz.txt.dll") }, + { { FPL("foo.dll"), FPL("") }, FPL("foo.dll") }, + { { FPL("foo.dll"), FPL(".") }, FPL("foo..dll") }, + { { FPL("foo"), FPL("") }, FPL("foo") }, + { { FPL("foo"), FPL(".") }, FPL("foo.") }, + { { FPL("foo.baz.dll"), FPL("") }, FPL("foo.baz.dll") }, + { { FPL("foo.baz.dll"), FPL(".") }, FPL("foo.baz..dll") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("\\"), FPL("") }, FPL("\\") }, + { { FPL("\\"), FPL("txt") }, FPL("\\txt") }, + { { FPL("\\."), FPL("txt") }, FPL("") }, + { { FPL("\\.."), FPL("txt") }, FPL("") }, + { { FPL("\\."), FPL("") }, FPL("\\.") }, + { { FPL("C:\\bar\\foo.dll"), FPL("txt") }, + FPL("C:\\bar\\footxt.dll") }, + { { FPL("C:\\bar.baz\\foodll"), FPL("txt") }, + FPL("C:\\bar.baz\\foodlltxt") }, + { { FPL("C:\\bar.baz\\foo.dll"), FPL("txt") }, + FPL("C:\\bar.baz\\footxt.dll") }, + { { FPL("C:\\bar.baz\\foo.dll.exe"), FPL("txt") }, + FPL("C:\\bar.baz\\foo.dlltxt.exe") }, + { { FPL("C:\\bar.baz\\foo"), FPL("") }, + FPL("C:\\bar.baz\\foo") }, + { { FPL("C:\\bar.baz\\foo.exe"), FPL("") }, + FPL("C:\\bar.baz\\foo.exe") }, + { { FPL("C:\\bar.baz\\foo.dll.exe"), FPL("") }, + FPL("C:\\bar.baz\\foo.dll.exe") }, + { { FPL("C:\\bar\\baz\\foo.exe"), FPL(" (1)") }, + FPL("C:\\bar\\baz\\foo (1).exe") }, + { { FPL("C:\\foo.baz\\\\"), FPL(" (1)") }, FPL("C:\\foo (1).baz") }, + { { FPL("C:\\foo.baz\\..\\"), FPL(" (1)") }, FPL("") }, +#endif + { { FPL("/"), FPL("") }, FPL("/") }, + { { FPL("/"), FPL("txt") }, FPL("/txt") }, + { { FPL("/."), FPL("txt") }, FPL("") }, + { { FPL("/.."), FPL("txt") }, FPL("") }, + { { FPL("/."), FPL("") }, FPL("/.") }, + { { FPL("/bar/foo.dll"), FPL("txt") }, FPL("/bar/footxt.dll") }, + { { FPL("/bar.baz/foodll"), FPL("txt") }, FPL("/bar.baz/foodlltxt") }, + { { FPL("/bar.baz/foo.dll"), FPL("txt") }, FPL("/bar.baz/footxt.dll") }, + { { FPL("/bar.baz/foo.dll.exe"), FPL("txt") }, + FPL("/bar.baz/foo.dlltxt.exe") }, + { { FPL("/bar.baz/foo"), FPL("") }, FPL("/bar.baz/foo") }, + { { FPL("/bar.baz/foo.exe"), FPL("") }, FPL("/bar.baz/foo.exe") }, + { { FPL("/bar.baz/foo.dll.exe"), FPL("") }, FPL("/bar.baz/foo.dll.exe") }, + { { FPL("/bar/baz/foo.exe"), FPL(" (1)") }, FPL("/bar/baz/foo (1).exe") }, + { { FPL("/bar/baz/..////"), FPL(" (1)") }, FPL("") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].inputs[0]); + FilePath result = path.InsertBeforeExtension(cases[i].inputs[1]); + EXPECT_EQ(cases[i].expected, result.value()) << "i: " << i << + ", path: " << path.value() << ", insert: " << cases[i].inputs[1]; + } +} + +TEST_F(FilePathTest, ReplaceExtension) { + const struct BinaryTestData cases[] = { + { { FPL(""), FPL("") }, FPL("") }, + { { FPL(""), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("txt") }, FPL("") }, + { { FPL(".."), FPL("txt") }, FPL("") }, + { { FPL("."), FPL("") }, FPL("") }, + { { FPL("foo.dll"), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo..dll"), FPL("txt") }, FPL("foo..txt") }, + { { FPL("foo.dll"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo"), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo."), FPL("txt") }, FPL("foo.txt") }, + { { FPL("foo.."), FPL("txt") }, FPL("foo..txt") }, + { { FPL("foo"), FPL(".txt") }, FPL("foo.txt") }, + { { FPL("foo.baz.dll"), FPL("txt") }, FPL("foo.baz.txt") }, + { { FPL("foo.baz.dll"), FPL(".txt") }, FPL("foo.baz.txt") }, + { { FPL("foo.dll"), FPL("") }, FPL("foo") }, + { { FPL("foo.dll"), FPL(".") }, FPL("foo") }, + { { FPL("foo"), FPL("") }, FPL("foo") }, + { { FPL("foo"), FPL(".") }, FPL("foo") }, + { { FPL("foo.baz.dll"), FPL("") }, FPL("foo.baz") }, + { { FPL("foo.baz.dll"), FPL(".") }, FPL("foo.baz") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { { FPL("C:\\foo.bar\\foo"), FPL("baz") }, FPL("C:\\foo.bar\\foo.baz") }, + { { FPL("C:\\foo.bar\\..\\\\"), FPL("baz") }, FPL("") }, +#endif + { { FPL("/foo.bar/foo"), FPL("baz") }, FPL("/foo.bar/foo.baz") }, + { { FPL("/foo.bar/..////"), FPL("baz") }, FPL("") }, + }; + for (unsigned int i = 0; i < arraysize(cases); ++i) { + FilePath path(cases[i].inputs[0]); + FilePath replaced = path.ReplaceExtension(cases[i].inputs[1]); + EXPECT_EQ(cases[i].expected, replaced.value()) << "i: " << i << + ", path: " << path.value() << ", replace: " << cases[i].inputs[1]; + } +} + diff --git a/base/file_util.h b/base/file_util.h index 784ea36..c064b2c 100644 --- a/base/file_util.h +++ b/base/file_util.h @@ -75,13 +75,12 @@ void TrimFilename(std::wstring* path); // Deprecated. Use FilePath::BaseName instead. std::wstring GetFilenameFromPath(const std::wstring& path); -// Returns "jpg" for path "C:\pics\jojo.jpg", or an empty string if -// the file has no extension. +// Deprecated compatibility function. Use FilePath::Extension. FilePath::StringType GetFileExtensionFromPath(const FilePath& path); // Deprecated temporary compatibility function. std::wstring GetFileExtensionFromPath(const std::wstring& path); -// Returns 'jojo' for path "C:\pics\jojo.jpg". +// Deprecated compatibility function. Use FilePath::RemoveExtension. std::wstring GetFilenameWithoutExtensionFromPath(const std::wstring& path); // Returns the directory component of a path, without the trailing @@ -104,18 +103,15 @@ bool AbsolutePath(FilePath* path); // Deprecated temporary compatibility function. bool AbsolutePath(std::wstring* path); -// Inserts |suffix| after the file name portion of |path| but before the -// extension. -// Examples: -// path == "C:\pics\jojo.jpg" suffix == " (1)", returns "C:\pics\jojo (1).jpg" -// path == "jojo.jpg" suffix == " (1)", returns "jojo (1).jpg" -// path == "C:\pics\jojo" suffix == " (1)", returns "C:\pics\jojo (1)" -// path == "C:\pics.old\jojo" suffix == " (1)", returns "C:\pics.old\jojo (1)" +// Returns true if this FilePath represents a parent dir of |other|. Both +// paths are normalized before doing the comparison, but neither |this| nor +// |other| are modified. +bool PathContains(const FilePath& path, const FilePath& other); + +// Deprecated compatibility function. Use FilePath::InsertBeforeExtension. void InsertBeforeExtension(FilePath* path, const FilePath::StringType& suffix); -// Replaces the extension of |file_name| with |extension|. If |file_name| -// does not have an extension, them |extension| is added. If |extension| is -// empty, then the extension is removed from |file_name|. +// Deprecated compatibility function. Use FilePath::ReplaceExtension. void ReplaceExtension(FilePath* file_name, const FilePath::StringType& extension); |