/std/path.d
http://github.com/jcd/phobos · D · 2926 lines · 1889 code · 298 blank · 739 comment · 728 complexity · c3e22674ee5c1e4ccc9289b6acb496e0 MD5 · raw file
Large files are truncated click here to view the full file
- // 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 $(XREF file,isDir) and
- $(XREF file,exists).
- 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.
- Authors:
- Lars Tandle Kyllingstad,
- $(WEB digitalmars.com, Walter Bright),
- Grzegorz Adam Hankiewicz,
- Thomas Kühne,
- $(WEB erdani.org, Andrei Alexandrescu)
- Copyright:
- Copyright (c) 2000–2011, the authors. All rights reserved.
- License:
- $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
- Source:
- $(PHOBOSSRC std/_path.d)
- Macros:
- WIKI = Phobos/StdPath
- */
- module std.path;
- import std.algorithm;
- import std.array;
- import std.conv;
- import std.file: getcwd;
- import std.range;
- import std.string;
- import std.traits;
- version(Posix)
- {
- import core.exception;
- import core.stdc.errno;
- import core.sys.posix.pwd;
- import core.sys.posix.stdlib;
- private import core.exception : onOutOfMemoryError;
- }
- /** 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
- {
- if (c == '/') return true;
- version(Windows) if (c == '\\') return true;
- return false;
- }
- /* 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
- {
- version(Windows) return c == ':';
- else return false;
- }
- /* Combines the isDirSeparator and isDriveSeparator tests. */
- version(Windows) private bool isSeparator(dchar c) @safe pure nothrow
- {
- return isDirSeparator(c) || isDriveSeparator(c);
- }
- version(Posix) private alias isDirSeparator isSeparator;
- /* 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(C)(in C[] path) @safe pure nothrow
- if (isSomeChar!C)
- {
- auto i = (cast(ptrdiff_t) path.length) - 1;
- while (i >= 0 && !isSeparator(path[i])) --i;
- return i;
- }
- version (Windows)
- {
- private bool isUNC(C)(in C[] path) @safe pure nothrow if (isSomeChar!C)
- {
- return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1])
- && !isDirSeparator(path[2]);
- }
- private ptrdiff_t uncRootLength(C)(in C[] path) @safe pure nothrow if (isSomeChar!C)
- in { assert (isUNC(path)); }
- body
- {
- 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(C)(in C[] path) @safe pure nothrow if (isSomeChar!C)
- {
- return path.length >= 2 && isDriveSeparator(path[1]);
- }
- private bool isDriveRoot(C)(in C[] path) @safe pure nothrow if (isSomeChar!C)
- {
- return path.length >= 3 && isDriveSeparator(path[1])
- && isDirSeparator(path[2]);
- }
- }
- /* Helper functions that strip leading/trailing slashes and backslashes
- from a path.
- */
- private inout(C)[] ltrimDirSeparators(C)(inout(C)[] path) @safe pure nothrow
- if (isSomeChar!C)
- {
- int i = 0;
- while (i < path.length && isDirSeparator(path[i])) ++i;
- return path[i .. $];
- }
- private inout(C)[] rtrimDirSeparators(C)(inout(C)[] path) @safe pure nothrow
- if (isSomeChar!C)
- {
- auto i = (cast(ptrdiff_t) path.length) - 1;
- while (i >= 0 && isDirSeparator(path[i])) --i;
- return path[0 .. i+1];
- }
- private inout(C)[] trimDirSeparators(C)(inout(C)[] path) @safe pure nothrow
- if (isSomeChar!C)
- {
- return ltrimDirSeparators(rtrimDirSeparators(path));
- }
- /** This $(D 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, $(D no) on Windows and Mac OS X, and $(D yes) on all
- POSIX systems except OS X (Linux, *BSD, etc.).
- */
- osDefault = osDefaultCaseSensitivity
- }
- version (Windows) private enum osDefaultCaseSensitivity = false;
- else version (OSX) private enum osDefaultCaseSensitivity = false;
- else version (Posix) private enum osDefaultCaseSensitivity = true;
- else static assert (0);
- /** Returns the name of a file, without any leading directory
- and with an optional suffix chopped off.
- If $(D suffix) is specified, it will be compared to $(D path)
- using $(D filenameCmp!cs),
- where $(D cs) is an optional template parameter determining whether
- the comparison is case sensitive or not. See the
- $(LREF filenameCmp) documentation for details.
- Examples:
- ---
- 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");
- }
- ---
- Note:
- This function $(I only) strips away the specified suffix, which
- doesn't necessarily have to represent an extension. If you want
- to remove the extension from a path, regardless of what the extension
- is, use $(LREF stripExtension).
- If you want 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).
- */
- inout(C)[] baseName(C)(inout(C)[] path)
- @trusted pure //TODO: nothrow (BUG 5700)
- if (isSomeChar!C)
- {
- auto p1 = stripDrive(path);
- if (p1.empty)
- {
- version (Windows) if (isUNC(path))
- {
- return cast(typeof(return)) dirSeparator.dup;
- }
- return null;
- }
- auto p2 = rtrimDirSeparators(p1);
- if (p2.empty) return p1[0 .. 1];
- return p2[lastSeparator(p2)+1 .. $];
- }
- /// ditto
- inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1)
- (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(p[$-suffix.length .. $], suffix) == 0)
- {
- return p[0 .. $-suffix.length];
- }
- else return p;
- }
- 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");
- 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`) == `\`);
- }
- assert (baseName(stripExtension("dir/file.ext")) == "file");
- static assert (baseName("dir/file.ext") == "file.ext");
- static assert (baseName("dir/file.ext", ".ext") == "file");
- }
- /** Returns the directory part of a path. On Windows, this
- includes the drive letter if present.
- This function performs a memory allocation if and only if $(D path)
- is mutable and does not have a directory (in which case a new mutable
- string is needed to hold the returned current-directory symbol,
- $(D ".")).
- Examples:
- ---
- assert (dirName("file") == ".");
- assert (dirName("dir/file") == "dir");
- assert (dirName("/file") == "/");
- assert (dirName("dir/subdir/") == "dir");
- version (Windows)
- {
- assert (dirName("d:file") == "d:");
- assert (dirName(`d:\dir\file`) == `d:\dir`);
- assert (dirName(`d:\file`) == `d:\`);
- assert (dirName(`dir\subdir\`) == `dir`);
- }
- ---
- Standards:
- This function complies with
- $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html,
- the POSIX requirements for the 'dirname' shell utility)
- (with suitable adaptations for Windows paths).
- */
- C[] dirName(C)(C[] path)
- //TODO: @safe (BUG 6169) pure nothrow (because of to())
- if (isSomeChar!C)
- {
- if (path.empty) return to!(typeof(return))(".");
- auto p = rtrimDirSeparators(path);
- if (p.empty) return path[0 .. 1];
- version (Windows)
- {
- if (isUNC(p) && uncRootLength(p) == p.length)
- return p;
- if (p.length == 2 && isDriveSeparator(p[1]) && path.length > 2)
- return path[0 .. 3];
- }
- auto i = lastSeparator(p);
- if (i == -1) return to!(typeof(return))(".");
- if (i == 0) return p[0 .. 1];
- version (Windows)
- {
- // If the directory part is either d: or d:\, don't
- // chop off the last symbol.
- if (isDriveSeparator(p[i]) || isDriveSeparator(p[i-1]))
- return p[0 .. i+1];
- }
- // Remove any remaining trailing (back)slashes.
- return rtrimDirSeparators(p[0 .. i]);
- }
- unittest
- {
- assert (dirName("") == ".");
- assert (dirName("file"w) == ".");
- assert (dirName("dir/"d) == ".");
- assert (dirName("dir///") == ".");
- assert (dirName("dir/file"w.dup) == "dir");
- assert (dirName("dir///file"d.dup) == "dir");
- assert (dirName("dir/subdir/") == "dir");
- assert (dirName("/dir/file"w) == "/dir");
- assert (dirName("/file"d) == "/");
- assert (dirName("/") == "/");
- assert (dirName("///") == "/");
- version (Windows)
- {
- assert (dirName(`dir\`) == `.`);
- assert (dirName(`dir\\\`) == `.`);
- assert (dirName(`dir\file`) == `dir`);
- assert (dirName(`dir\\\file`) == `dir`);
- assert (dirName(`dir\subdir\`) == `dir`);
- assert (dirName(`\dir\file`) == `\dir`);
- assert (dirName(`\file`) == `\`);
- assert (dirName(`\`) == `\`);
- assert (dirName(`\\\`) == `\`);
- assert (dirName(`d:`) == `d:`);
- assert (dirName(`d:file`) == `d:`);
- assert (dirName(`d:\`) == `d:\`);
- assert (dirName(`d:\file`) == `d:\`);
- assert (dirName(`d:\dir\file`) == `d:\dir`);
- assert (dirName(`\\server\share\dir\file`) == `\\server\share\dir`);
- assert (dirName(`\\server\share\file`) == `\\server\share`);
- assert (dirName(`\\server\share\`) == `\\server\share`);
- assert (dirName(`\\server\share`) == `\\server\share`);
- }
- static assert (dirName("dir/file") == "dir");
- }
- /** Returns the root directory of the specified path, or $(D null) if the
- path is not rooted.
- Examples:
- ---
- assert (rootName("foo") is null);
- assert (rootName("/foo") == "/");
- version (Windows)
- {
- assert (rootName(`\foo`) == `\`);
- assert (rootName(`c:\foo`) == `c:\`);
- assert (rootName(`\\server\share\foo`) == `\\server\share`);
- }
- ---
- */
- inout(C)[] rootName(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C)
- {
- if (path.empty) return null;
- version (Posix)
- {
- if (isDirSeparator(path[0])) return path[0 .. 1];
- }
- else version (Windows)
- {
- if (isDirSeparator(path[0]))
- {
- if (isUNC(path)) return path[0 .. uncRootLength(path)];
- else return path[0 .. 1];
- }
- else if (path.length >= 3 && isDriveSeparator(path[1]) &&
- isDirSeparator(path[2]))
- {
- return path[0 .. 3];
- }
- }
- else static assert (0, "unsupported platform");
- assert (!isRooted(path));
- return null;
- }
- unittest
- {
- assert (rootName("") is null);
- assert (rootName("foo") is null);
- assert (rootName("/") == "/");
- assert (rootName("/foo/bar") == "/");
- version (Windows)
- {
- assert (rootName("d:foo") is null);
- assert (rootName(`d:\foo`) == `d:\`);
- assert (rootName(`\\server\share\foo`) == `\\server\share`);
- assert (rootName(`\\server\share`) == `\\server\share`);
- }
- }
- /** Returns the drive of a path, or $(D null) if the drive
- is not specified. In the case of UNC paths, the network share
- is returned.
- Always returns $(D null) on POSIX.
- Examples:
- ---
- version (Windows)
- {
- assert (driveName(`d:\file`) == "d:");
- assert (driveName(`\\server\share\file`) == `\\server\share`);
- assert (driveName(`dir\file`).empty);
- }
- ---
- */
- inout(C)[] driveName(C)(inout(C)[] path) @safe pure nothrow
- if (isSomeChar!C)
- {
- version (Windows)
- {
- if (hasDrive(path))
- return path[0 .. 2];
- else if (isUNC(path))
- return path[0 .. uncRootLength(path)];
- }
- return null;
- }
- unittest
- {
- version (Posix) assert (driveName("c:/foo").empty);
- version (Windows)
- {
- assert (driveName(`dir\file`).empty);
- assert (driveName(`d:file`) == "d:");
- assert (driveName(`d:\file`) == "d:");
- assert (driveName("d:") == "d:");
- assert (driveName(`\\server\share\file`) == `\\server\share`);
- assert (driveName(`\\server\share\`) == `\\server\share`);
- assert (driveName(`\\server\share`) == `\\server\share`);
- static assert (driveName(`d:\file`) == "d:");
- }
- }
- /** Strips the drive from a Windows path. On POSIX, the path is returned
- unaltered.
- Example:
- ---
- version (Windows)
- {
- assert (stripDrive(`d:\dir\file`) == `\dir\file`);
- assert (stripDrive(`\\server\share\dir\file`) == `\dir\file`);
- }
- ---
- */
- inout(C)[] stripDrive(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C)
- {
- version(Windows)
- {
- if (hasDrive(path)) return path[2 .. $];
- else if (isUNC(path)) return path[uncRootLength(path) .. $];
- }
- return path;
- }
- unittest
- {
- version(Windows)
- {
- assert (stripDrive(`d:\dir\file`) == `\dir\file`);
- assert (stripDrive(`\\server\share\dir\file`) == `\dir\file`);
- static assert (stripDrive(`d:\dir\file`) == `\dir\file`);
- }
- version(Posix)
- {
- assert (stripDrive(`d:\dir\file`) == `d:\dir\file`);
- }
- }
- /* Helper function that returns the position of the filename/extension
- separator dot in path. If not found, returns -1.
- */
- private ptrdiff_t extSeparatorPos(C)(in C[] path) @safe pure nothrow
- if (isSomeChar!C)
- {
- auto i = (cast(ptrdiff_t) path.length) - 1;
- while (i >= 0 && !isSeparator(path[i]))
- {
- if (path[i] == '.' && i > 0 && !isSeparator(path[i-1])) return i;
- --i;
- }
- return -1;
- }
- /** Returns the _extension part of a file name, including the dot.
- If there is no _extension, $(D null) is returned.
- Examples:
- ---
- assert (extension("file").empty);
- assert (extension("file.ext") == ".ext");
- assert (extension("file.ext1.ext2") == ".ext2");
- assert (extension("file.") == ".");
- assert (extension(".file").empty);
- assert (extension(".file.ext") == ".ext");
- ---
- */
- inout(C)[] extension(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C)
- {
- auto i = extSeparatorPos(path);
- if (i == -1) return null;
- else return path[i .. $];
- }
- unittest
- {
- assert (extension("file").empty);
- assert (extension("file.") == ".");
- assert (extension("file.ext"w) == ".ext");
- assert (extension("file.ext1.ext2"d) == ".ext2");
- assert (extension(".foo".dup).empty);
- assert (extension(".foo.ext"w.dup) == ".ext");
- assert (extension("dir/file"d.dup).empty);
- assert (extension("dir/file.") == ".");
- assert (extension("dir/file.ext") == ".ext");
- assert (extension("dir/file.ext1.ext2"w) == ".ext2");
- assert (extension("dir/.foo"d).empty);
- assert (extension("dir/.foo.ext".dup) == ".ext");
- version(Windows)
- {
- assert (extension(`dir\file`).empty);
- assert (extension(`dir\file.`) == ".");
- assert (extension(`dir\file.ext`) == `.ext`);
- assert (extension(`dir\file.ext1.ext2`) == `.ext2`);
- assert (extension(`dir\.foo`).empty);
- assert (extension(`dir\.foo.ext`) == `.ext`);
- assert (extension(`d:file`).empty);
- assert (extension(`d:file.`) == ".");
- assert (extension(`d:file.ext`) == `.ext`);
- assert (extension(`d:file.ext1.ext2`) == `.ext2`);
- assert (extension(`d:.foo`).empty);
- assert (extension(`d:.foo.ext`) == `.ext`);
- }
- static assert (extension("file").empty);
- static assert (extension("file.ext") == ".ext");
- }
- /** Returns the path with the extension stripped off.
- Examples:
- ---
- assert (stripExtension("file") == "file");
- assert (stripExtension("file.ext") == "file");
- assert (stripExtension("file.ext1.ext2") == "file.ext1");
- assert (stripExtension("file.") == "file");
- assert (stripExtension(".file") == ".file");
- assert (stripExtension(".file.ext") == ".file");
- assert (stripExtension("dir/file.ext") == "dir/file");
- ---
- */
- inout(C)[] stripExtension(C)(inout(C)[] path) @safe pure nothrow
- if (isSomeChar!C)
- {
- auto i = extSeparatorPos(path);
- if (i == -1) return path;
- else return path[0 .. i];
- }
- unittest
- {
- assert (stripExtension("file") == "file");
- assert (stripExtension("file.ext"w) == "file");
- assert (stripExtension("file.ext1.ext2"d) == "file.ext1");
- assert (stripExtension(".foo".dup) == ".foo");
- assert (stripExtension(".foo.ext"w.dup) == ".foo");
- assert (stripExtension("dir/file"d.dup) == "dir/file");
- assert (stripExtension("dir/file.ext") == "dir/file");
- assert (stripExtension("dir/file.ext1.ext2"w) == "dir/file.ext1");
- assert (stripExtension("dir/.foo"d) == "dir/.foo");
- assert (stripExtension("dir/.foo.ext".dup) == "dir/.foo");
- version(Windows)
- {
- assert (stripExtension("dir\\file") == "dir\\file");
- assert (stripExtension("dir\\file.ext") == "dir\\file");
- assert (stripExtension("dir\\file.ext1.ext2") == "dir\\file.ext1");
- assert (stripExtension("dir\\.foo") == "dir\\.foo");
- assert (stripExtension("dir\\.foo.ext") == "dir\\.foo");
- assert (stripExtension("d:file") == "d:file");
- assert (stripExtension("d:file.ext") == "d:file");
- assert (stripExtension("d:file.ext1.ext2") == "d:file.ext1");
- assert (stripExtension("d:.foo") == "d:.foo");
- assert (stripExtension("d:.foo.ext") == "d:.foo");
- }
- static assert (stripExtension("file") == "file");
- static assert (stripExtension("file.ext"w) == "file");
- }
- /** Returns a string containing the _path given by $(D path), but where
- the extension has been set to $(D ext).
- If the filename already has an extension, it is replaced. If not, the
- extension is simply appended to the filename. Including a leading dot
- in $(D ext) is optional.
- If the extension is empty, this function is equivalent to
- $(LREF stripExtension).
- This function normally allocates a new string (the possible exception
- being the case when path is immutable and doesn't already have an
- extension).
- Examples:
- ---
- assert (setExtension("file", "ext") == "file.ext");
- assert (setExtension("file", ".ext") == "file.ext");
- assert (setExtension("file.old", "") == "file");
- assert (setExtension("file.old", "new") == "file.new");
- assert (setExtension("file.old", ".new") == "file.new");
- ---
- */
- immutable(Unqual!C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext)
- @trusted pure nothrow
- if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2))
- {
- if (ext.length > 0 && ext[0] != '.')
- return cast(typeof(return))(stripExtension(path)~'.'~ext);
- else
- return cast(typeof(return))(stripExtension(path)~ext);
- }
- ///ditto
- immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext)
- @trusted pure nothrow
- if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
- {
- if (ext.length == 0)
- return stripExtension(path);
- // Optimised for the case where path is immutable and has no extension
- if (ext.length > 0 && ext[0] == '.') ext = ext[1 .. $];
- auto i = extSeparatorPos(path);
- if (i == -1)
- {
- path ~= '.';
- path ~= ext;
- return path;
- }
- else if (i == path.length - 1)
- {
- path ~= ext;
- return path;
- }
- else
- {
- return cast(typeof(return))(path[0 .. i+1] ~ ext);
- }
- }
- unittest
- {
- assert (setExtension("file", "ext") == "file.ext");
- assert (setExtension("file"w, ".ext"w) == "file.ext");
- assert (setExtension("file."d, "ext"d) == "file.ext");
- assert (setExtension("file.", ".ext") == "file.ext");
- assert (setExtension("file.old"w, "new"w) == "file.new");
- assert (setExtension("file.old"d, ".new"d) == "file.new");
- assert (setExtension("file"w.dup, "ext"w) == "file.ext");
- assert (setExtension("file"w.dup, ".ext"w) == "file.ext");
- assert (setExtension("file."w, "ext"w.dup) == "file.ext");
- assert (setExtension("file."w, ".ext"w.dup) == "file.ext");
- assert (setExtension("file.old"d.dup, "new"d) == "file.new");
- assert (setExtension("file.old"d.dup, ".new"d) == "file.new");
- static assert (setExtension("file", "ext") == "file.ext");
- static assert (setExtension("file.old", "new") == "file.new");
- static assert (setExtension("file"w.dup, "ext"w) == "file.ext");
- static assert (setExtension("file.old"d.dup, "new"d) == "file.new");
- // Issue 10601
- assert (setExtension("file", "") == "file");
- assert (setExtension("file.ext", "") == "file");
- }
- /** Returns the _path given by $(D path), with the extension given by
- $(D ext) appended if the path doesn't already have one.
- Including the dot in the extension is optional.
- This function always allocates a new string, except in the case when
- path is immutable and already has an extension.
- Examples:
- ---
- assert (defaultExtension("file", "ext") == "file.ext");
- assert (defaultExtension("file", ".ext") == "file.ext");
- assert (defaultExtension("file.", "ext") == "file.");
- assert (defaultExtension("file.old", "new") == "file.old");
- assert (defaultExtension("file.old", ".new") == "file.old");
- ---
- */
- immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext)
- @trusted pure // TODO: nothrow (because of to())
- if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
- {
- auto i = extSeparatorPos(path);
- if (i == -1)
- {
- if (ext.length > 0 && ext[0] == '.')
- return cast(typeof(return))(path~ext);
- else
- return cast(typeof(return))(path~'.'~ext);
- }
- else return to!(typeof(return))(path);
- }
- unittest
- {
- assert (defaultExtension("file", "ext") == "file.ext");
- assert (defaultExtension("file", ".ext") == "file.ext");
- assert (defaultExtension("file.", "ext") == "file.");
- assert (defaultExtension("file.old", "new") == "file.old");
- assert (defaultExtension("file.old", ".new") == "file.old");
- assert (defaultExtension("file"w.dup, "ext"w) == "file.ext");
- assert (defaultExtension("file.old"d.dup, "new"d) == "file.old");
- static assert (defaultExtension("file", "ext") == "file.ext");
- static assert (defaultExtension("file.old", "new") == "file.old");
- static assert (defaultExtension("file"w.dup, "ext"w) == "file.ext");
- static assert (defaultExtension("file.old"d.dup, "new"d) == "file.old");
- }
- /** Combines one or more path segments.
- This function takes a set of path segments, given as an input
- range of string elements or as a set of string arguments,
- and concatenates them with each other. Directory separators
- are inserted between segments if necessary. If any of the
- path segments are absolute (as defined by $(LREF isAbsolute)), the
- preceding segments will be dropped.
- On Windows, if one of the path segments are rooted, but not absolute
- (e.g. $(D `\foo`)), all preceding path segments down to the previous
- root will be dropped. (See below for an example.)
- This function always allocates memory to hold the resulting path.
- The variadic overload is guaranteed to only perform a single
- allocation, as is the range version if $(D paths) is a forward
- range.
- */
- immutable(ElementEncodingType!(ElementType!Range))[]
- buildPath(Range)(Range segments)
- if (isInputRange!Range && isSomeString!(ElementType!Range))
- {
- if (segments.empty) return null;
- // If this is a forward range, we can pre-calculate a maximum length.
- static if (isForwardRange!Range)
- {
- auto segments2 = segments.save;
- size_t precalc = 0;
- foreach (segment; segments2) precalc += segment.length + 1;
- }
- // Otherwise, just venture a guess and resize later if necessary.
- else size_t precalc = 255;
- auto buf = new Unqual!(ElementEncodingType!(ElementType!Range))[](precalc);
- size_t pos = 0;
- foreach (segment; segments)
- {
- if (segment.empty) continue;
- static if (!isForwardRange!Range)
- {
- immutable neededLength = pos + segment.length + 1;
- if (buf.length < neededLength)
- buf.length = reserve(buf, neededLength + buf.length/2);
- }
- if (pos > 0)
- {
- if (isRooted(segment))
- {
- version (Posix)
- {
- pos = 0;
- }
- else version (Windows)
- {
- if (isAbsolute(segment))
- pos = 0;
- else
- {
- pos = rootName(buf[0 .. pos]).length;
- if (pos > 0 && isDirSeparator(buf[pos-1])) --pos;
- }
- }
- }
- else if (!isDirSeparator(buf[pos-1]))
- buf[pos++] = dirSeparator[0];
- }
- buf[pos .. pos + segment.length] = segment[];
- pos += segment.length;
- }
- static U trustedCast(U, V)(V v) @trusted pure nothrow { return cast(U) v; }
- return trustedCast!(typeof(return))(buf[0 .. pos]);
- }
- /// ditto
- immutable(C)[] buildPath(C)(const(C[])[] paths...)
- @safe pure nothrow
- if (isSomeChar!C)
- {
- return buildPath!(typeof(paths))(paths);
- }
- ///
- unittest
- {
- version (Posix)
- {
- assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
- assert (buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
- assert (buildPath("/foo", "/bar") == "/bar");
- }
- version (Windows)
- {
- assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
- assert (buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
- assert (buildPath("foo", `d:\bar`) == `d:\bar`);
- assert (buildPath("foo", `\bar`) == `\bar`);
- assert (buildPath(`c:\foo`, `\bar`) == `c:\bar`);
- }
- }
- unittest // non-documented
- {
- // ir() wraps an array in a plain (i.e. non-forward) input range, so that
- // we can test both code paths
- InputRange!(C[]) ir(C)(C[][] p...) { return inputRangeObject(p); }
- version (Posix)
- {
- assert (buildPath("foo") == "foo");
- assert (buildPath("/foo/") == "/foo/");
- assert (buildPath("foo", "bar") == "foo/bar");
- assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
- assert (buildPath("foo/".dup, "bar") == "foo/bar");
- assert (buildPath("foo///", "bar".dup) == "foo///bar");
- assert (buildPath("/foo"w, "bar"w) == "/foo/bar");
- assert (buildPath("foo"w.dup, "/bar"w) == "/bar");
- assert (buildPath("foo"w, "bar/"w.dup) == "foo/bar/");
- assert (buildPath("/"d, "foo"d) == "/foo");
- assert (buildPath(""d.dup, "foo"d) == "foo");
- assert (buildPath("foo"d, ""d.dup) == "foo");
- assert (buildPath("foo", "bar".dup, "baz") == "foo/bar/baz");
- assert (buildPath("foo"w, "/bar"w, "baz"w.dup) == "/bar/baz");
- static assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
- static assert (buildPath("foo", "/bar", "baz") == "/bar/baz");
- // The following are mostly duplicates of the above, except that the
- // range version does not accept mixed constness.
- assert (buildPath(ir("foo")) == "foo");
- assert (buildPath(ir("/foo/")) == "/foo/");
- assert (buildPath(ir("foo", "bar")) == "foo/bar");
- assert (buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
- assert (buildPath(ir("foo/".dup, "bar".dup)) == "foo/bar");
- assert (buildPath(ir("foo///".dup, "bar".dup)) == "foo///bar");
- assert (buildPath(ir("/foo"w, "bar"w)) == "/foo/bar");
- assert (buildPath(ir("foo"w.dup, "/bar"w.dup)) == "/bar");
- assert (buildPath(ir("foo"w.dup, "bar/"w.dup)) == "foo/bar/");
- assert (buildPath(ir("/"d, "foo"d)) == "/foo");
- assert (buildPath(ir(""d.dup, "foo"d.dup)) == "foo");
- assert (buildPath(ir("foo"d, ""d)) == "foo");
- assert (buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
- assert (buildPath(ir("foo"w.dup, "/bar"w.dup, "baz"w.dup)) == "/bar/baz");
- }
- version (Windows)
- {
- assert (buildPath("foo") == "foo");
- assert (buildPath(`\foo/`) == `\foo/`);
- assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
- assert (buildPath("foo", `\bar`) == `\bar`);
- assert (buildPath(`c:\foo`, "bar") == `c:\foo\bar`);
- assert (buildPath("foo"w, `d:\bar`w.dup) == `d:\bar`);
- assert (buildPath(`c:\foo\bar`, `\baz`) == `c:\baz`);
- assert (buildPath(`\\foo\bar\baz`d, `foo`d, `\bar`d) == `\\foo\bar\bar`d);
- static assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
- static assert (buildPath("foo", `c:\bar`, "baz") == `c:\bar\baz`);
- assert (buildPath(ir("foo")) == "foo");
- assert (buildPath(ir(`\foo/`)) == `\foo/`);
- assert (buildPath(ir("foo", "bar", "baz")) == `foo\bar\baz`);
- assert (buildPath(ir("foo", `\bar`)) == `\bar`);
- assert (buildPath(ir(`c:\foo`, "bar")) == `c:\foo\bar`);
- assert (buildPath(ir("foo"w.dup, `d:\bar`w.dup)) == `d:\bar`);
- assert (buildPath(ir(`c:\foo\bar`, `\baz`)) == `c:\baz`);
- assert (buildPath(ir(`\\foo\bar\baz`d, `foo`d, `\bar`d)) == `\\foo\bar\bar`d);
- }
- // Test that allocation works as it should.
- auto manyShort = "aaa".repeat(1000).array();
- auto manyShortCombined = join(manyShort, dirSeparator);
- assert (buildPath(manyShort) == manyShortCombined);
- assert (buildPath(ir(manyShort)) == manyShortCombined);
- auto fewLong = 'b'.repeat(500).array().repeat(10).array();
- auto fewLongCombined = join(fewLong, dirSeparator);
- assert (buildPath(fewLong) == fewLongCombined);
- assert (buildPath(ir(fewLong)) == fewLongCombined);
- }
- unittest
- {
- // Test for issue 7397
- string[] ary = ["a", "b"];
- version (Posix)
- {
- assert (buildPath(ary) == "a/b");
- }
- else version (Windows)
- {
- assert (buildPath(ary) == `a\b`);
- }
- }
- /** Performs the same task as $(LREF buildPath),
- while at the same time resolving current/parent directory
- symbols ($(D ".") and $(D "..")) and removing superfluous
- directory separators.
- On Windows, slashes are replaced with backslashes.
- Note that this function does not resolve symbolic links.
- This function always allocates memory to hold the resulting path.
- Examples:
- ---
- version (Posix)
- {
- assert (buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz");
- assert (buildNormalizedPath("../foo/.") == "../foo");
- assert (buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
- assert (buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
- assert (buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
- assert (buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
- }
- version (Windows)
- {
- assert (buildNormalizedPath(`c:\foo\.\bar/..\\baz\`) == `c:\foo\baz`);
- assert (buildNormalizedPath(`..\foo\.`) == `..\foo`);
- assert (buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
- assert (buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
- assert (buildNormalizedPath(`\\server\share\foo`, `..\bar`) == `\\server\share\bar`);
- }
- ---
- */
- immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...)
- @trusted pure nothrow
- if (isSomeChar!C)
- {
- import std.c.stdlib;
- auto paths2 = new const(C)[][](paths.length);
- //(cast(const(C)[]*)alloca((const(C)[]).sizeof * paths.length))[0 .. paths.length];
- // Check whether the resulting path will be absolute or rooted,
- // calculate its maximum length, and discard segments we won't use.
- typeof(paths[0][0])[] rootElement;
- int numPaths = 0;
- bool seenAbsolute;
- size_t segmentLengthSum = 0;
- foreach (i; 0 .. paths.length)
- {
- auto p = paths[i];
- if (p.empty) continue;
- else if (isRooted(p))
- {
- immutable thisIsAbsolute = isAbsolute(p);
- if (thisIsAbsolute || !seenAbsolute)
- {
- if (thisIsAbsolute) seenAbsolute = true;
- rootElement = rootName(p);
- paths2[0] = p[rootElement.length .. $];
- numPaths = 1;
- segmentLengthSum = paths2[0].length;
- }
- else
- {
- paths2[0] = p;
- numPaths = 1;
- segmentLengthSum = p.length;
- }
- }
- else
- {
- paths2[numPaths++] = p;
- segmentLengthSum += p.length;
- }
- }
- if (rootElement.length + segmentLengthSum == 0) return null;
- paths2 = paths2[0 .. numPaths];
- immutable rooted = !rootElement.empty;
- assert (rooted || !seenAbsolute); // absolute => rooted
- // Allocate memory for the resulting path, including room for
- // extra dir separators
- auto fullPath = new C[rootElement.length + segmentLengthSum + paths2.length];
- // Copy the root element into fullPath, and let relPart be
- // the remaining slice.
- typeof(fullPath) relPart;
- if (rooted)
- {
- // For Windows, we also need to perform normalization on
- // the root element.
- version (Posix)
- {
- fullPath[0 .. rootElement.length] = rootElement[];
- }
- else version (Windows)
- {
- foreach (i, c; rootElement)
- {
- if (isDirSeparator(c))
- {
- static assert (dirSeparator.length == 1);
- fullPath[i] = dirSeparator[0];
- }
- else fullPath[i] = c;
- }
- }
- else static assert (0);
- // If the root element doesn't end with a dir separator,
- // we add one.
- if (!isDirSeparator(rootElement[$-1]))
- {
- static assert (dirSeparator.length == 1);
- fullPath[rootElement.length] = dirSeparator[0];
- relPart = fullPath[rootElement.length + 1 .. $];
- }
- else
- {
- relPart = fullPath[rootElement.length .. $];
- }
- }
- else relPart = fullPath;
- // Now, we have ensured that all segments in path are relative to the
- // root we found earlier.
- bool hasParents = rooted;
- ptrdiff_t i;
- foreach (path; paths2)
- {
- path = trimDirSeparators(path);
- enum Prev { nonSpecial, dirSep, dot, doubleDot }
- Prev prev = Prev.dirSep;
- foreach (j; 0 .. path.length+1)
- {
- // Fake a dir separator between path segments
- immutable c = (j == path.length ? dirSeparator[0] : path[j]);
- if (isDirSeparator(c))
- {
- final switch (prev)
- {
- case Prev.doubleDot:
- if (hasParents)
- {
- while (i > 0 && !isDirSeparator(relPart[i-1])) --i;
- if (i > 0) --i; // skip the dir separator
- while (i > 0 && !isDirSeparator(relPart[i-1])) --i;
- if (i == 0) hasParents = rooted;
- }
- else
- {
- relPart[i++] = '.';
- relPart[i++] = '.';
- static assert (dirSeparator.length == 1);
- relPart[i++] = dirSeparator[0];
- }
- break;
- case Prev.dot:
- while (i > 0 && !isDirSeparator(relPart[i-1])) --i;
- break;
- case Prev.nonSpecial:
- static assert (dirSeparator.length == 1);
- relPart[i++] = dirSeparator[0];
- hasParents = true;
- break;
- case Prev.dirSep:
- break;
- }
- prev = Prev.dirSep;
- }
- else if (c == '.')
- {
- final switch (prev)
- {
- case Prev.dirSep:
- prev = Prev.dot;
- break;
- case Prev.dot:
- prev = Prev.doubleDot;
- break;
- case Prev.doubleDot:
- prev = Prev.nonSpecial;
- relPart[i .. i+3] = "...";
- i += 3;
- break;
- case Prev.nonSpecial:
- relPart[i] = '.';
- ++i;
- break;
- }
- }
- else
- {
- final switch (prev)
- {
- case Prev.doubleDot:
- relPart[i] = '.';
- ++i;
- goto case;
- case Prev.dot:
- relPart[i] = '.';
- ++i;
- break;
- case Prev.dirSep: break;
- case Prev.nonSpecial: break;
- }
- relPart[i] = c;
- ++i;
- prev = Prev.nonSpecial;
- }
- }
- }
- // Return path, including root element and excluding the
- // final dir separator.
- immutable len = (relPart.ptr - fullPath.ptr) + (i > 0 ? i - 1 : 0);
- fullPath = fullPath[0 .. len];
- version (Windows)
- {
- // On Windows, if the path is on the form `\\server\share`,
- // with no further segments, normalization will have turned it
- // into `\\server\share\`. If so, we need to remove the final
- // backslash.
- if (isUNC(fullPath) && uncRootLength(fullPath) == fullPath.length - 1)
- fullPath = fullPath[0 .. $-1];
- }
- return cast(typeof(return)) fullPath;
- }
- unittest
- {
- assert (buildNormalizedPath("") is null);
- assert (buildNormalizedPath("foo") == "foo");
- version (Posix)
- {
- assert (buildNormalizedPath("/", "foo", "bar") == "/foo/bar");
- assert (buildNormalizedPath("foo", "bar", "baz") == "foo/bar/baz");
- assert (buildNormalizedPath("foo", "bar/baz") == "foo/bar/baz");
- assert (buildNormalizedPath("foo", "bar//baz///") == "foo/bar/baz");
- assert (buildNormalizedPath("/foo", "bar/baz") == "/foo/bar/baz");
- assert (buildNormalizedPath("/foo", "/bar/baz") == "/bar/baz");
- assert (buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
- assert (buildNormalizedPath("/foo/..", "bar/baz") == "/bar/baz");
- assert (buildNormalizedPath("/foo/../../", "bar/baz") == "/bar/baz");
- assert (buildNormalizedPath("/foo/bar", "../baz") == "/foo/baz");
- assert (buildNormalizedPath("/foo/bar", "../../baz") == "/baz");
- assert (buildNormalizedPath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee");
- assert (buildNormalizedPath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee");
- static assert (buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
- // Examples in docs:
- assert (buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
- assert (buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
- assert (buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
- assert (buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
- }
- else version (Windows)
- {
- assert (buildNormalizedPath(`\`, `foo`, `bar`) == `\foo\bar`);
- assert (buildNormalizedPath(`foo`, `bar`, `baz`) == `foo\bar\baz`);
- assert (buildNormalizedPath(`foo`, `bar\baz`) == `foo\bar\baz`);
- assert (buildNormalizedPath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`);
- assert (buildNormalizedPath(`\foo`, `bar\baz`) == `\foo\bar\baz`);
- assert (buildNormalizedPath(`\foo`, `\bar\baz`) == `\bar\baz`);
- assert (buildNormalizedPath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`);
- assert (buildNormalizedPath(`\foo\..`, `bar\baz`) == `\bar\baz`);
- assert (buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
- assert (buildNormalizedPath(`\foo\bar`, `..\baz`) == `\foo\baz`);
- assert (buildNormalizedPath(`\foo\bar`, `../../baz`) == `\baz`);
- assert (buildNormalizedPath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`);
- assert (buildNormalizedPath(`c:\`, `foo`, `bar`) == `c:\foo\bar`);
- assert (buildNormalizedPath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`);
- assert (buildNormalizedPath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`);
- assert (buildNormalizedPath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`);
- assert (buildNormalizedPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
- assert (buildNormalizedPath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`);
- assert (buildNormalizedPath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`);
- assert (buildNormalizedPath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`);
- assert (buildNormalizedPath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`);
- assert (buildNormalizedPath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`);
- assert (buildNormalizedPath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`);
- assert (buildNormalizedPath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`);
- assert (buildNormalizedPath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`);
- assert (buildNormalizedPath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`);
- assert (buildNormalizedPath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`);
- assert (buildNormalizedPath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`);
- assert (buildNormalizedPath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`);
- assert (buildNormalizedPath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`);
- assert (buildNormalizedPath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`);
- assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`);
- assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`);
- assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`);
- static assert (buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
- // Examples in docs:
- assert (buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
- assert (buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
- assert (buildNormalizedPath(`\\server\share\foo`, `..\bar`) == `\\server\share\bar`);
- }
- else static assert (0);
- }
- unittest
- {
- version (Posix)
- {
- // Trivial
- assert (buildNormalizedPath("").empty);
- assert (buildNormalizedPath("foo/bar") == "foo/bar");
- // Correct handling of leading slashes
- assert (buildNormalizedPath("/") == "/");
- assert (buildNormalizedPath("///") == "/");
- assert (buildNormalizedPath("////") == "/");
- assert (buildNormalizedPath("/foo/bar") == "/foo/bar");
- assert (buildNormalizedPath("//foo/bar") == "/foo/bar");
- assert (buildNormalizedPath("///foo/bar") == "/foo/bar");
- assert (buildNormalizedPath("////foo/bar") == "/foo/bar");
- // Correct handling of single-dot symbol (current directory)
- assert (buildNormalizedPath("/./foo") == "/foo");
- assert (buildNormalizedPath("/foo/./bar") == "/foo/bar");
- assert (buildNormalizedPath("./foo") == "foo");
- assert (buildNormalizedPath("././foo") == "foo");
- assert (buildNormalizedPath("foo/././bar") == "foo/bar");
- // Correct handling of double-dot symbol (previous directory)
- assert (buildNormalizedPath("/foo/../bar") == "/bar");
- assert (buildNormalizedPath("/foo/../../bar") == "/bar");
- assert (buildNormalizedPath("/../foo") == "/foo");
- assert (buildNormalizedPath("/../../foo") == "/foo");
- assert (buildNormalizedPath("/foo/..") == "/");
- assert (buildNormalizedPath("/foo/../..") == "/");
- assert (buildNormalizedPath("foo/../bar") == "bar");
- assert (buildNormalizedPath("foo/../../bar") == "../bar");
- assert (buildNormalizedPath("../foo") == "../foo");
- assert (buildNormalizedPa…