// Written in the D programming language. /** This module is used to manipulate path strings. All functions, with the exception of $(LREF expandTilde) (and in some cases $(LREF absolutePath) and $(LREF relativePath)), are pure string manipulation functions; they don't depend on any state outside the program, nor do they perform any actual file system actions. This has the consequence that the module does not make any distinction between a path that points to a directory and a path that points to a file, and it does not know whether or not the object pointed to by the path actually exists in the file system. To differentiate between these cases, use $(REF isDir, std,file) and $(REF exists, std,file). Note that on Windows, both the backslash ($(D `\`)) and the slash ($(D `/`)) are in principle valid directory separators. This module treats them both on equal footing, but in cases where a $(I new) separator is added, a backslash will be used. Furthermore, the $(LREF buildNormalizedPath) function will replace all slashes with backslashes on that platform. In general, the functions in this module assume that the input paths are well-formed. (That is, they should not contain invalid characters, they should follow the file system's path format, etc.) The result of calling a function on an ill-formed path is undefined. When there is a chance that a path or a file name is invalid (for instance, when it has been input by the user), it may sometimes be desirable to use the $(LREF isValidFilename) and $(LREF isValidPath) functions to check this. Most functions do not perform any memory allocations, and if a string is returned, it is usually a slice of an input string. If a function allocates, this is explicitly mentioned in the documentation. $(SCRIPT inhibitQuickIndex = 1;)$(DIVC quickindex,$(BOOKTABLE,$(TR $(TH Category) $(TH Functions))$(TR $(TD Normalization) $(TD $(LREF absolutePath) $(LREF asAbsolutePath) $(LREF asNormalizedPath) $(LREF asRelativePath) $(LREF buildNormalizedPath) $(LREF buildPath) $(LREF chainPath) $(LREF expandTilde)))$(TR $(TD Partitioning) $(TD $(LREF baseName) $(LREF dirName) $(LREF dirSeparator) $(LREF driveName) $(LREF pathSeparator) $(LREF pathSplitter) $(LREF relativePath) $(LREF rootName) $(LREF stripDrive)))$(TR $(TD Validation) $(TD $(LREF isAbsolute) $(LREF isDirSeparator) $(LREF isRooted) $(LREF isValidFilename) $(LREF isValidPath)))$(TR $(TD Extension) $(TD $(LREF defaultExtension) $(LREF extension) $(LREF setExtension) $(LREF stripExtension) $(LREF withDefaultExtension) $(LREF withExtension)))$(TR $(TD Other) $(TD $(LREF filenameCharCmp) $(LREF filenameCmp) $(LREF globMatch) $(LREF CaseSensitive))))) Authors: Lars Tandle Kyllingstad, $(HTTP digitalmars.com, Walter Bright), Grzegorz Adam Hankiewicz, Thomas K$(UUML)hne, $(HTTP erdani.org, Andrei Alexandrescu) Copyright: Copyright (c) 2000-2014, the authors. All rights reserved. License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) Source: $(PHOBOSSRC std/path.d)*/module std.path; import std.file : getcwd;static import std.meta;import std.range;import std.traits; version (OSX) version = Darwin;else version (iOS) version = Darwin;else version (TVOS) version = Darwin;else version (WatchOS) version = Darwin; version (StdUnittest){private: struct TestAliasedString { string get() @safe @nogc pure nothrow return scope { return _s; } alias get this; @disable this(this); string _s; } bool testAliasedString(alias func, Args...)(scope string s, scope Args args) { return func(TestAliasedString(s), args) == func(s, args); }} /** String used to separate directory names in a path. Under POSIX this is a slash, under Windows a backslash.*/version (Posix) enum string dirSeparator = "/";else version (Windows) enum string dirSeparator = "\\";else static assert(0, "unsupported platform"); /** Path separator string. A colon under POSIX, a semicolon under Windows.*/version (Posix) enum string pathSeparator = ":";else version (Windows) enum string pathSeparator = ";";else static assert(0, "unsupported platform"); /** Determines whether the given character is a directory separator. On Windows, this includes both $(D `\`) and $(D `/`). On POSIX, it's just $(D `/`).*/bool isDirSeparator(dchar c) @safe pure nothrow @nogc{ if (c == '/') return true; version (Windows) if (c == '\\') return true; return false;} ///@safe pure nothrow @nogc unittest{ version (Windows) { assert( '/'.isDirSeparator); assert( '\\'.isDirSeparator); } else { assert( '/'.isDirSeparator); assert(!'\\'.isDirSeparator); }} /* Determines whether the given character is a drive separator. On Windows, this is true if c is the ':' character that separates the drive letter from the rest of the path. On POSIX, this always returns false.*/private bool isDriveSeparator(dchar c) @safe pure nothrow @nogc{ version (Windows) return c == ':'; else return false;} /* Combines the isDirSeparator and isDriveSeparator tests. */version (Windows) private bool isSeparator(dchar c) @safe pure nothrow @nogc{ return isDirSeparator(c) || isDriveSeparator(c);}version (Posix) private alias isSeparator = isDirSeparator; /* Helper function that determines the position of the last drive/directory separator in a string. Returns -1 if none is found.*/private ptrdiff_t lastSeparator(R)(R path)if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R){ auto i = (cast(ptrdiff_t) path.length) - 1; while (i >= 0 && !isSeparator(path[i])) --i; return i;} version (Windows){ private bool isUNC(R)(R path) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) { return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1]) && !isDirSeparator(path[2]); } private ptrdiff_t uncRootLength(R)(R path) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) in { assert(isUNC(path)); } do { ptrdiff_t i = 3; while (i < path.length && !isDirSeparator(path[i])) ++i; if (i < path.length) { auto j = i; do { ++j; } while (j < path.length && isDirSeparator(path[j])); if (j < path.length) { do { ++j; } while (j < path.length && !isDirSeparator(path[j])); i = j; } } return i; } private bool hasDrive(R)(R path) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) { return path.length >= 2 && isDriveSeparator(path[1]); } private bool isDriveRoot(R)(R path) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) { return path.length >= 3 && isDriveSeparator(path[1]) && isDirSeparator(path[2]); }} /* Helper functions that strip leading/trailing slashes and backslashes from a path.*/private auto ltrimDirSeparators(R)(R path)if (isSomeFiniteCharInputRange!R || isNarrowString!R){ static if (isRandomAccessRange!R && hasSlicing!R || isNarrowString!R) { int i = 0; while (i < path.length && isDirSeparator(path[i])) ++i; return path[i .. path.length]; } else { while (!path.empty && isDirSeparator(path.front)) path.popFront(); return path; }} @safe unittest{ import std.array; import std.utf : byDchar; assert(ltrimDirSeparators("//abc//").array == "abc//"); assert(ltrimDirSeparators("//abc//"d).array == "abc//"d); assert(ltrimDirSeparators("//abc//".byDchar).array == "abc//"d);} private auto rtrimDirSeparators(R)(R path)if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || isNarrowString!R){ static if (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R) { auto i = (cast(ptrdiff_t) path.length) - 1; while (i >= 0 && isDirSeparator(path[i])) --i; return path[0 .. i+1]; } else { while (!path.empty && isDirSeparator(path.back)) path.popBack(); return path; }} @safe unittest{ import std.array; assert(rtrimDirSeparators("//abc//").array == "//abc"); assert(rtrimDirSeparators("//abc//"d).array == "//abc"d); assert(rtrimDirSeparators(MockBiRange!char("//abc//")).array == "//abc");} private auto trimDirSeparators(R)(R path)if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || isNarrowString!R){ return ltrimDirSeparators(rtrimDirSeparators(path));} @safe unittest{ import std.array; assert(trimDirSeparators("//abc//").array == "abc"); assert(trimDirSeparators("//abc//"d).array == "abc"d); assert(trimDirSeparators(MockBiRange!char("//abc//")).array == "abc");} /** This `enum` is used as a template argument to functions which compare file names, and determines whether the comparison is case sensitive or not.*/enum CaseSensitive : bool{ /// File names are case insensitive no = false, /// File names are case sensitive yes = true, /** The default (or most common) setting for the current platform. That is, `no` on Windows and Mac OS X, and `yes` on all POSIX systems except Darwin (Linux, *BSD, etc.). */ osDefault = osDefaultCaseSensitivity} ///@safe unittest{ assert(baseName!(CaseSensitive.no)("dir/file.EXT", ".ext") == "file"); assert(baseName!(CaseSensitive.yes)("dir/file.EXT", ".ext") != "file"); version (Posix) assert(relativePath!(CaseSensitive.no)("/FOO/bar", "/foo/baz") == "../bar"); else assert(relativePath!(CaseSensitive.no)(`c:\FOO\bar`, `c:\foo\baz`) == `..\bar`);} version (Windows) private enum osDefaultCaseSensitivity = false;else version (Darwin) private enum osDefaultCaseSensitivity = false;else version (Posix) private enum osDefaultCaseSensitivity = true;else static assert(0); /** Params: cs = Whether or not suffix matching is case-sensitive. path = A path name. It can be a string, or any random-access range of characters. suffix = An optional suffix to be removed from the file name. Returns: The name of the file in the path name, without any leading directory and with an optional suffix chopped off. If `suffix` is specified, it will be compared to `path` using `filenameCmp!cs`, where `cs` is an optional template parameter determining whether the comparison is case sensitive or not. See the $(LREF filenameCmp) documentation for details. Note: This function $(I only) strips away the specified suffix, which doesn't necessarily have to represent an extension. To remove the extension from a path, regardless of what the extension is, use $(LREF stripExtension). To obtain the filename without leading directories and without an extension, combine the functions like this: --- assert(baseName(stripExtension("dir/file.ext")) == "file"); --- Standards: This function complies with $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html, the POSIX requirements for the 'basename' shell utility) (with suitable adaptations for Windows paths).*/auto baseName(R)(return scope R path)if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!R){ return _baseName(path);} /// dittoauto baseName(C)(return scope C[] path)if (isSomeChar!C){ return _baseName(path);} /// dittoinout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1) (return scope inout(C)[] path, in C1[] suffix) @safe pure //TODO: nothrow (because of filenameCmp())if (isSomeChar!C && isSomeChar!C1){ auto p = baseName(path); if (p.length > suffix.length && filenameCmp!cs(cast(const(C)[])p[$-suffix.length .. $], suffix) == 0) { return p[0 .. $-suffix.length]; } else return p;} ///@safe unittest{ assert(baseName("dir/file.ext") == "file.ext"); assert(baseName("dir/file.ext", ".ext") == "file"); assert(baseName("dir/file.ext", ".xyz") == "file.ext"); assert(baseName("dir/filename", "name") == "file"); assert(baseName("dir/subdir/") == "subdir"); version (Windows) { assert(baseName(`d:file.ext`) == "file.ext"); assert(baseName(`d:\dir\file.ext`) == "file.ext"); }} @safe unittest{ assert(baseName("").empty); assert(baseName("file.ext"w) == "file.ext"); assert(baseName("file.ext"d, ".ext") == "file"); assert(baseName("file", "file"w.dup) == "file"); assert(baseName("dir/file.ext"d.dup) == "file.ext"); assert(baseName("dir/file.ext", ".ext"d) == "file"); assert(baseName("dir/file"w, "file"d) == "file"); assert(baseName("dir///subdir////") == "subdir"); assert(baseName("dir/subdir.ext/", ".ext") == "subdir"); assert(baseName("dir/subdir/".dup, "subdir") == "subdir"); assert(baseName("/"w.dup) == "/"); assert(baseName("//"d.dup) == "/"); assert(baseName("///") == "/"); assert(baseName!(CaseSensitive.yes)("file.ext", ".EXT") == "file.ext"); assert(baseName!(CaseSensitive.no)("file.ext", ".EXT") == "file"); { auto r = MockRange!(immutable(char))(`dir/file.ext`); auto s = r.baseName(); foreach (i, c; `file`) assert(s[i] == c); } version (Windows) { assert(baseName(`dir\file.ext`) == `file.ext`); assert(baseName(`dir\file.ext`, `.ext`) == `file`); assert(baseName(`dir\file`, `file`) == `file`); assert(baseName(`d:file.ext`) == `file.ext`); assert(baseName(`d:file.ext`, `.ext`) == `file`); assert(baseName(`d:file`, `file`) == `file`); assert(baseName(`dir\\subdir\\\`) == `subdir`); assert(baseName(`dir\subdir.ext\`, `.ext`) == `subdir`); assert(baseName(`dir\subdir\`, `subdir`) == `subdir`); assert(baseName(`\`) == `\`); assert(baseName(`\\`) == `\`); assert(baseName(`\\\`) == `\`); assert(baseName(`d:\`) == `\`); assert(baseName(`d:`).empty); assert(baseName(`\\server\share\file`) == `file`); assert(baseName(`\\server\share\`) == `\`); assert(baseName(`\\server\share`) == `\`); auto r = MockRange!(immutable(char))(`\\server\share`); auto s = r.baseName(); foreach (i, c; `\`) assert(s[i] == c); }Lines 1–500 of 4319. Load more
// Written in the D programming language. /** This module is used to manipulate path strings. All functions, with the exception of $(LREF expandTilde) (and in some cases $(LREF absolutePath) and $(LREF relativePath)), are pure string manipulation functions; they don't depend on any state outside the program, nor do they perform any actual file system actions. This has the consequence that the module does not make any distinction between a path that points to a directory and a path that points to a file, and it does not know whether or not the object pointed to by the path actually exists in the file system. To differentiate between these cases, use $(REF isDir, std,file) and $(REF exists, std,file). Note that on Windows, both the backslash ($(D `\`)) and the slash ($(D `/`)) are in principle valid directory separators. This module treats them both on equal footing, but in cases where a $(I new) separator is added, a backslash will be used. Furthermore, the $(LREF buildNormalizedPath) function will replace all slashes with backslashes on that platform. In general, the functions in this module assume that the input paths are well-formed. (That is, they should not contain invalid characters, they should follow the file system's path format, etc.) The result of calling a function on an ill-formed path is undefined. When there is a chance that a path or a file name is invalid (for instance, when it has been input by the user), it may sometimes be desirable to use the $(LREF isValidFilename) and $(LREF isValidPath) functions to check this. Most functions do not perform any memory allocations, and if a string is returned, it is usually a slice of an input string. If a function allocates, this is explicitly mentioned in the documentation. $(SCRIPT inhibitQuickIndex = 1;) $(DIVC quickindex, $(BOOKTABLE, $(TR $(TH Category) $(TH Functions)) $(TR $(TD Normalization) $(TD $(LREF absolutePath) $(LREF asAbsolutePath) $(LREF asNormalizedPath) $(LREF asRelativePath) $(LREF buildNormalizedPath) $(LREF buildPath) $(LREF chainPath) $(LREF expandTilde) )) $(TR $(TD Partitioning) $(TD $(LREF baseName) $(LREF dirName) $(LREF dirSeparator) $(LREF driveName) $(LREF pathSeparator) $(LREF pathSplitter) $(LREF relativePath) $(LREF rootName) $(LREF stripDrive) )) $(TR $(TD Validation) $(TD $(LREF isAbsolute) $(LREF isDirSeparator) $(LREF isRooted) $(LREF isValidFilename) $(LREF isValidPath) )) $(TR $(TD Extension) $(TD $(LREF defaultExtension) $(LREF extension) $(LREF setExtension) $(LREF stripExtension) $(LREF withDefaultExtension) $(LREF withExtension) )) $(TR $(TD Other) $(TD $(LREF filenameCharCmp) $(LREF filenameCmp) $(LREF globMatch) $(LREF CaseSensitive) )) )) Authors: Lars Tandle Kyllingstad, $(HTTP digitalmars.com, Walter Bright), Grzegorz Adam Hankiewicz, Thomas K$(UUML)hne, $(HTTP erdani.org, Andrei Alexandrescu) Copyright: Copyright (c) 2000-2014, the authors. All rights reserved. License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) Source: $(PHOBOSSRC std/path.d) */ module std.path; import std.file : getcwd; static import std.meta; import std.range; import std.traits; version (OSX) version = Darwin; else version (iOS) version = Darwin; else version (TVOS) version = Darwin; else version (WatchOS) version = Darwin; version (StdUnittest) { private: struct TestAliasedString { string get() @safe @nogc pure nothrow return scope { return _s; } alias get this; @disable this(this); string _s; } bool testAliasedString(alias func, Args...)(scope string s, scope Args args) { return func(TestAliasedString(s), args) == func(s, args); } } /** String used to separate directory names in a path. Under POSIX this is a slash, under Windows a backslash. */ version (Posix) enum string dirSeparator = "/"; else version (Windows) enum string dirSeparator = "\\"; else static assert(0, "unsupported platform"); /** Path separator string. A colon under POSIX, a semicolon under Windows. */ version (Posix) enum string pathSeparator = ":"; else version (Windows) enum string pathSeparator = ";"; else static assert(0, "unsupported platform"); /** Determines whether the given character is a directory separator. On Windows, this includes both $(D `\`) and $(D `/`). On POSIX, it's just $(D `/`). */ bool isDirSeparator(dchar c) @safe pure nothrow @nogc { if (c == '/') return true; version (Windows) if (c == '\\') return true; return false; } /// @safe pure nothrow @nogc unittest { version (Windows) { assert( '/'.isDirSeparator); assert( '\\'.isDirSeparator); } else { assert( '/'.isDirSeparator); assert(!'\\'.isDirSeparator); } } /* Determines whether the given character is a drive separator. On Windows, this is true if c is the ':' character that separates the drive letter from the rest of the path. On POSIX, this always returns false. */ private bool isDriveSeparator(dchar c) @safe pure nothrow @nogc { version (Windows) return c == ':'; else return false; } /* Combines the isDirSeparator and isDriveSeparator tests. */ version (Windows) private bool isSeparator(dchar c) @safe pure nothrow @nogc { return isDirSeparator(c) || isDriveSeparator(c); } version (Posix) private alias isSeparator = isDirSeparator; /* Helper function that determines the position of the last drive/directory separator in a string. Returns -1 if none is found. */ private ptrdiff_t lastSeparator(R)(R path) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) { auto i = (cast(ptrdiff_t) path.length) - 1; while (i >= 0 && !isSeparator(path[i])) --i; return i; } version (Windows) { private bool isUNC(R)(R path) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) { return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1]) && !isDirSeparator(path[2]); } private ptrdiff_t uncRootLength(R)(R path) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) in { assert(isUNC(path)); } do { ptrdiff_t i = 3; while (i < path.length && !isDirSeparator(path[i])) ++i; if (i < path.length) { auto j = i; do { ++j; } while (j < path.length && isDirSeparator(path[j])); if (j < path.length) { do { ++j; } while (j < path.length && !isDirSeparator(path[j])); i = j; } } return i; } private bool hasDrive(R)(R path) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) { return path.length >= 2 && isDriveSeparator(path[1]); } private bool isDriveRoot(R)(R path) if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) { return path.length >= 3 && isDriveSeparator(path[1]) && isDirSeparator(path[2]); } } /* Helper functions that strip leading/trailing slashes and backslashes from a path. */ private auto ltrimDirSeparators(R)(R path) if (isSomeFiniteCharInputRange!R || isNarrowString!R) { static if (isRandomAccessRange!R && hasSlicing!R || isNarrowString!R) { int i = 0; while (i < path.length && isDirSeparator(path[i])) ++i; return path[i .. path.length]; } else { while (!path.empty && isDirSeparator(path.front)) path.popFront(); return path; } } @safe unittest { import std.array; import std.utf : byDchar; assert(ltrimDirSeparators("//abc//").array == "abc//"); assert(ltrimDirSeparators("//abc//"d).array == "abc//"d); assert(ltrimDirSeparators("//abc//".byDchar).array == "abc//"d); } private auto rtrimDirSeparators(R)(R path) if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) { static if (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R) { auto i = (cast(ptrdiff_t) path.length) - 1; while (i >= 0 && isDirSeparator(path[i])) --i; return path[0 .. i+1]; } else { while (!path.empty && isDirSeparator(path.back)) path.popBack(); return path; } } @safe unittest { import std.array; assert(rtrimDirSeparators("//abc//").array == "//abc"); assert(rtrimDirSeparators("//abc//"d).array == "//abc"d); assert(rtrimDirSeparators(MockBiRange!char("//abc//")).array == "//abc"); } private auto trimDirSeparators(R)(R path) if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || isNarrowString!R) { return ltrimDirSeparators(rtrimDirSeparators(path)); } @safe unittest { import std.array; assert(trimDirSeparators("//abc//").array == "abc"); assert(trimDirSeparators("//abc//"d).array == "abc"d); assert(trimDirSeparators(MockBiRange!char("//abc//")).array == "abc"); } /** This `enum` is used as a template argument to functions which compare file names, and determines whether the comparison is case sensitive or not. */ enum CaseSensitive : bool { /// File names are case insensitive no = false, /// File names are case sensitive yes = true, /** The default (or most common) setting for the current platform. That is, `no` on Windows and Mac OS X, and `yes` on all POSIX systems except Darwin (Linux, *BSD, etc.). */ osDefault = osDefaultCaseSensitivity } /// @safe unittest { assert(baseName!(CaseSensitive.no)("dir/file.EXT", ".ext") == "file"); assert(baseName!(CaseSensitive.yes)("dir/file.EXT", ".ext") != "file"); version (Posix) assert(relativePath!(CaseSensitive.no)("/FOO/bar", "/foo/baz") == "../bar"); else assert(relativePath!(CaseSensitive.no)(`c:\FOO\bar`, `c:\foo\baz`) == `..\bar`); } version (Windows) private enum osDefaultCaseSensitivity = false; else version (Darwin) private enum osDefaultCaseSensitivity = false; else version (Posix) private enum osDefaultCaseSensitivity = true; else static assert(0); /** Params: cs = Whether or not suffix matching is case-sensitive. path = A path name. It can be a string, or any random-access range of characters. suffix = An optional suffix to be removed from the file name. Returns: The name of the file in the path name, without any leading directory and with an optional suffix chopped off. If `suffix` is specified, it will be compared to `path` using `filenameCmp!cs`, where `cs` is an optional template parameter determining whether the comparison is case sensitive or not. See the $(LREF filenameCmp) documentation for details. Note: This function $(I only) strips away the specified suffix, which doesn't necessarily have to represent an extension. To remove the extension from a path, regardless of what the extension is, use $(LREF stripExtension). To obtain the filename without leading directories and without an extension, combine the functions like this: --- assert(baseName(stripExtension("dir/file.ext")) == "file"); --- Standards: This function complies with $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html, the POSIX requirements for the 'basename' shell utility) (with suitable adaptations for Windows paths). */ auto baseName(R)(return scope R path) if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!R) { return _baseName(path); } /// ditto auto baseName(C)(return scope C[] path) if (isSomeChar!C) { return _baseName(path); } /// ditto inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1) (return scope inout(C)[] path, in C1[] suffix) @safe pure //TODO: nothrow (because of filenameCmp()) if (isSomeChar!C && isSomeChar!C1) { auto p = baseName(path); if (p.length > suffix.length && filenameCmp!cs(cast(const(C)[])p[$-suffix.length .. $], suffix) == 0) { return p[0 .. $-suffix.length]; } else return p; } /// @safe unittest { assert(baseName("dir/file.ext") == "file.ext"); assert(baseName("dir/file.ext", ".ext") == "file"); assert(baseName("dir/file.ext", ".xyz") == "file.ext"); assert(baseName("dir/filename", "name") == "file"); assert(baseName("dir/subdir/") == "subdir"); version (Windows) { assert(baseName(`d:file.ext`) == "file.ext"); assert(baseName(`d:\dir\file.ext`) == "file.ext"); } } @safe unittest { assert(baseName("").empty); assert(baseName("file.ext"w) == "file.ext"); assert(baseName("file.ext"d, ".ext") == "file"); assert(baseName("file", "file"w.dup) == "file"); assert(baseName("dir/file.ext"d.dup) == "file.ext"); assert(baseName("dir/file.ext", ".ext"d) == "file"); assert(baseName("dir/file"w, "file"d) == "file"); assert(baseName("dir///subdir////") == "subdir"); assert(baseName("dir/subdir.ext/", ".ext") == "subdir"); assert(baseName("dir/subdir/".dup, "subdir") == "subdir"); assert(baseName("/"w.dup) == "/"); assert(baseName("//"d.dup) == "/"); assert(baseName("///") == "/"); assert(baseName!(CaseSensitive.yes)("file.ext", ".EXT") == "file.ext"); assert(baseName!(CaseSensitive.no)("file.ext", ".EXT") == "file"); { auto r = MockRange!(immutable(char))(`dir/file.ext`); auto s = r.baseName(); foreach (i, c; `file`) assert(s[i] == c); } version (Windows) { assert(baseName(`dir\file.ext`) == `file.ext`); assert(baseName(`dir\file.ext`, `.ext`) == `file`); assert(baseName(`dir\file`, `file`) == `file`); assert(baseName(`d:file.ext`) == `file.ext`); assert(baseName(`d:file.ext`, `.ext`) == `file`); assert(baseName(`d:file`, `file`) == `file`); assert(baseName(`dir\\subdir\\\`) == `subdir`); assert(baseName(`dir\subdir.ext\`, `.ext`) == `subdir`); assert(baseName(`dir\subdir\`, `subdir`) == `subdir`); assert(baseName(`\`) == `\`); assert(baseName(`\\`) == `\`); assert(baseName(`\\\`) == `\`); assert(baseName(`d:\`) == `\`); assert(baseName(`d:`).empty); assert(baseName(`\\server\share\file`) == `file`); assert(baseName(`\\server\share\`) == `\`); assert(baseName(`\\server\share`) == `\`); auto r = MockRange!(immutable(char))(`\\server\share`); auto s = r.baseName(); foreach (i, c; `\`) assert(s[i] == c); }