Thread overview
Some path functions
Jul 31, 2006
Kramer
Jul 31, 2006
Kramer
Aug 17, 2006
Bruno Medeiros
Aug 18, 2006
Kramer
July 31, 2006
I have some functions for the path module that I'd like to submit to the general public.  I haven't worked on them for about half a year and I had submitted them to Walter for inclusion into Phobos a while back, but he's got far more pressing issues (like bug fixes and spec. stability) to concern himself with.

The list of included functions:
getRoots
isRoot
containsRoot
normPath // normalize a path; similar to the Python version
         // http://docs.python.org/lib/module-os.path.html
isNormPath
normCase // normalize the case
normSep // normalize the separators for the given os
join // variadic join
absPath // includes a private isAbs because the one in std.path doesn't seem to be working
expandPath // similar to the Ruby version
           // http://www.rubycentral.com/ref/ref_c_file.html#expand_path

I offer the code free to anyone; use it as you may, it's public domain.  I hope it works, but offer no assurances on it's quality and am not responsible for it's use in other code.

It compiles clean under DMD 0.163 on Windows, but I haven't tested it on Linux since I wrote it -- I no longer have access to a Linux box.  The functions use an internal isAbs function because when I was writing the code I found the one in Phobos to not be accurate.  I'm probably wrong on that front, so to get the Phobos isAbs functionality, remove the internal one (the internal one returns a bool instead of an int, so any compares will need to be modified).

Hopefully someone will find it useful, but in the end, I just wanted to do my small share to contribute to this fantastic language.

Walter, keep up the good work on this language.  But I have to say, honestly, to the community, keep up the good work and motivation.  I've never come across a more intelligent and dedicated langauge community. And friendly by the way.  It's always a pleasure reading the newsgroups.

Onward, upward and D-ward!

-Kramer

P.S. - I'm having some trouble attaching the code so I'm just pasting it in this message.  Formatting will be screwy I'm sure, but I'm not sure how else to post.  I think my file is too large to send, so I'm leaving out the unittests in this message and will past them in another.

######################################################################
######################################################################

/**
 * Author: Joe Zuccarello
 * Date: February 2006
 *
 * By the author's permission, this code is considered in the public domain for modification and
 * redistribution at will.
 */

private import std.string,
               std.path,
               std.file,
               std.regexp,
               std.utf,
               std.stdarg,
               std.format;

version(Windows) private import std.c.windows.windows;

version(Windows)
{
extern(Windows)
{
    export
    {
        DWORD GetLogicalDriveStringsA(DWORD, LPTSTR);
        DWORD GetLogicalDriveStringsW(DWORD, LPWSTR);
    }
}
}

private static const char[] wSep = r"\",
                            lSep = "/",
                            rSeps = "[\\\\/]";  // For regexp use

void main() {}
/**
 * Returns all roots for the system.
 *
 * This will return all the roots that are know by the system.
 *
 * For Windows, it will be the drives that are available.
 * For Linux, it will always return root "/".
 *
 * Note, this function uses the Windows API to retrieve the roots.  If is ANSI characters are being
 * used, getRoots will attemp to convert it to UTF8.  If the UTF8 code throws a UTF exception,
 * that exception will not be caught and will be propogated to the caller code.
 *
 * Authors: Joe Zuccarello
 *
 * Date: February 14, 2006
 *
 * Returns: All roots for the system.
 *
 * Throws: (Windows) UtfException on conversion error from ANSI to UTF-8 if the OS is using ANSI.
 *
 * Version:
 *
 * License: Public domain.
 *
 * Examples:
 * ---------
 * version(Windows)
 * {
 *     // Assume roots "A:\", "C:\" and "D:\" exist
 *     getRoots() => "A:\", "C:\", "D:\"
 * }
 * version(linux)
 * {
 *     getRoots() => "/"
 * }
 * ---------
 */
char[][] getRoots()
{
    char[][] rtnBuffer;

    version(Windows)
    {
    DWORD bufferLen = 50,
          rtnVal;
    char[] buffer;

    // Unicode
    void getDrivesW()
    {
        wchar[] wBuffer;
        wBuffer.length = bufferLen;
        rtnVal = GetLogicalDriveStringsW(bufferLen, wBuffer);
        buffer = std.utf.toUTF8(wBuffer);
    }

    // ANSI
    void getDrivesA()
    {
        char* tmpBuf = std.windows.charset.toMBSz(buffer);
        rtnVal = GetLogicalDriveStringsA(bufferLen, tmpBuf);
        buffer = std.windows.charset.fromMBSz(tmpBuf);
    }

    // Set initial buffer size
    buffer.length = bufferLen;

    if (useWfuncs)
    {
        getDrivesW();
    }
    else
    {
        getDrivesA();
    }

    while (rtnVal > bufferLen)
    {
        bufferLen = rtnVal;
        buffer.length = bufferLen;
        if (useWfuncs)
        {
            getDrivesW();
        }
        else
        {
            getDrivesA();
        }
    }
    rtnBuffer.length = buffer.length;

    int j = 0;
    for (int i, nullCnt = 0; i < buffer.length && nullCnt < 2; i++)
    {
        if (buffer[i..(i + 1)] != "\0")
        {
            rtnBuffer[j] ~= buffer[i..(i + 1)];
            nullCnt = 0;
        }
        else
        {
            nullCnt++;
            j++;
        }
    }
    rtnBuffer.length = j - 1;

    return rtnBuffer.dup;
    }
    else version(linux)
    {
    rtnBuffer ~= "/";

    return rtnBuffer.dup;
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }
}

/**
 * Returns true/false whether a path is a root of the system.
 *
 * This can be used to test a path to see if it's a root of the system.
 *
 * Authors: Joe Zuccarello
 *
 * Date: February 14, 2006
 *
 * Returns: true/false whether a path is a root of the system.
 *
 * Version:
 *
 * License: Public domain.
 *
 * Examples:
 * ---------
 * version(Windows)
 * {
 *     // Assume on Windows, c:\ exists
 *     isRoot(r"c:\") => true
 * }
 * version(linux)
 * {
 *     isRoot("/") => true
 * }
 * ---------
 */
bool isRoot(char[] path, bool caseSensitive = false)
{
    char[][] roots = getRoots();

    foreach (char[] x; roots)
    {
        if (caseSensitive == true)
        {
            if (x == path)
            {
                return true;
            }
        }
        else
        {
            if (std.string.tolower(x) == std.string.tolower(path))
            {
                return true;
            }
        }
    }

    return false;
}

/**
 * Returns true/false whether a path contains a root of the system.
 *
 * This can be used to test a path, to determine if it starts with a system root.
 *
 * Authors: Joe Zuccarello
 *
 * Date: February 15, 2006
 *
 * Returns: true/false whether a path starts with a system root.
 *
 * Version:
 *
 * License: Public domain.
 *
 * Examples:
 * ---------
 * version(Windows)
 * {
 *     // Assume on Windows, c:\ exists
 *     containsRoot(r"c:\directory\file") => true
 *     containsRoot(r"\directory\file") => false
 * }
 * version(linux)
 * {
 *     containsRoot("/usr/d/src") => true
 *     containsRoot("../d/src") => false
 *     containsRoot("\d/src") => false
 * }
 * ---------
 */
bool containsRoot(char[] path, bool caseSensitive = false)
{
    char[][] roots = getRoots();

    foreach (char[] x; roots)
    {
        if (caseSensitive == true)
        {
            if (std.string.find(path, x) == 0)
            {
                return true;
            }
        }
        else
        {
            if (std.string.ifind(path, x) == 0)
            {
                return true;
            }
        }
    }

    return false;
}

/**
 * Test whether a path is normalized.
 *
 * Use this to test whether a path is normalized.
 *
 * Note: This function does not handle UNC paths.
 *
 * Authors: Joe Zuccarello
 *
 * Date: February 15, 2006
 *
 * Returns: true/false whether a path is normalized.
 *
 * Version:
 *
 * License: Public domain.
 *
 * Examples:
 * ---------
 * version(Windows)
 * {
 *     isNormPath(r"directory1\..\directory2\file\.") => false
 *     // This one returns true, because there's no parent directory to collapse to.
 *     isNormPath(r"..\directory\file") => true
 * }
 * version(linux)
 * {
 *     isNormPath("/dir/../file") => false
 *     isNormPath("/file") => true
 * }
 * ---------
 */
bool isNormPath(char[] path)
{
    RegExp re;

    version(Windows)
    {
    // Special cases
    if (path == "." || path == ".." || (path == r"\" || path == "/") ||
        std.regexp.find(path, "^\\.\\." ~ "(" ~ rSeps ~ "\\.\\.)+") != -1 ||
        std.regexp.find(path, "^[a-zA-Z]*:" ~ rSeps ~ "$") != -1)
    {
        return true;
    }
    else
    {
        // Look for the following.  If found, then this is not a normalized path
        if (std.regexp.find(path, rSeps ~ "$") != -1 ||
            std.regexp.find(path, rSeps ~ "\\.\\." ~ "(" ~ rSeps ~ "|$)") != -1 ||
            std.regexp.find(path, rSeps ~ "\\." ~ "(" ~ rSeps ~ "|$)") != -1 ||
            std.regexp.find(path, "^\\." ~ rSeps) != -1 || std.regexp.find(path, rSeps ~ "{2,}") != -1)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
    }
    else version(linux)
    {
    // Special cases
    if (path == "." || path == ".." || (path == r"\" || path == "/") ||
        std.regexp.find(path, "^\\.\\." ~ "(" ~ rSeps ~ "\\.\\.)+") != -1)
    {
        return true;
    }
    else
    {
        // Look for the following.  If found, then this is not a normalized path
        if (std.regexp.find(path, lSep ~ "$") != -1 ||
            std.regexp.find(path, lSep ~ "\\.\\." ~ "(" ~ lSep ~ "|$)") != -1 ||
            std.regexp.find(path, lSep ~ "\\." ~ "(" ~ lSep ~ "|$)") != -1 ||
            std.regexp.find(path, "^\\." ~ lSep) != -1 || std.regexp.find(path, lSep ~ "{2,}") != -1)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }
}

/**
 * Normalizes a path.
 *
 * This will normalize a path by collapsing redundant separators and parent/current directory
 * references.  It will also remove any trailing separators and normalize separators as appropriate
 * for the OS.
 *
 * Inspired by the Python v2.4.2 implementation.
 *
 * Note: This function does not handle UNC paths.
 *
 * Authors: Joe Zuccarello
 *
 * Date: February 15, 2006
 *
 * Returns: A normalized path.
 *
 * Version:
 *
 * License: Public domain.
 *
 * Examples:
 * ---------
 * normPath("/dir1/../dir2/./file/") => "/dir2/file"
 * normPath("/dir..../file/./") => "/dir..../file"
 * ---------
 */
char[] normPath(char[] path)
out(result)
{
    assert(isNormPath(result));
}
body
{
    int pcIdx, pcIdx2;
    char[][] pathComps;  // path components after splitting
    char[] result, drive;

    // Normalize the separators for the os
    path = normSep(path);

    // Sanity check.  No need to process a separator, curdir or pardir reference.
    if (path != sep && path != curdir && path != pardir)
    {
        // Remove the drive from the path
        version(Windows)
        {
        int idx = std.string.find(path, ":");
        drive ~= idx != -1 ? path[0..(idx + 1)] : "";
        if (idx != -1)
        {
            if ((idx + 1) < path.length)
            {
                path = path[(idx + 1)..$];
            }
            else
            {
                path = "";
            }
        }
        }

        // Remove repeating separators
        path = std.string.squeeze(path, sep);

        // If there's an initial separator even after a drive, save it off
        if (path != "")
        {
            if (path[0..1] == sep)
            {
                drive ~= sep;
            }
        }

        // Split the path components
        pathComps = std.string.split(path, sep);

        while (pcIdx < pathComps.length)
        {
            // Current directory
            if (pathComps[pcIdx] == curdir)
            {
                if (pathComps.length == 1)
                {
                    pathComps.length = 0;
                }
                else if (pathComps.length > 1)
                {
                    // At the beginning
                    if (pcIdx == 0)
                    {
                        pathComps = pathComps[1..$];
                    }
                    // At the end
                    else if ((pcIdx + 1) == pathComps.length)
                    {
                        pathComps = pathComps[0..pcIdx];
                    }
                    // In the middle
                    else
                    {
                        pathComps = pathComps[0..pcIdx] ~ pathComps[(pcIdx + 1)..$];
                    }
                }
            }
            // Parent directory reference
            else if (pathComps[pcIdx] == pardir)
            {
                if (pathComps.length == 1)
                {
                    pcIdx++;
                }
                else if (pathComps.length > 1)
                {
                    // At the beginning
                    if (pcIdx == 0)
                    {
                        // We don't know what to do with this, so move on
                        pcIdx++;
                    }
                    // Found a reference but there was a separator before it.  Need
                    // to remove this reference.
                    else if (pcIdx == 1 && pathComps[(pcIdx - 1)] == "")
                    {
                        // Delete the reference
                        if ((pcIdx + 1) < pathComps.length)
                        {
                            pathComps = pathComps[0..pcIdx] ~ pathComps[(pcIdx + 1)..$];
                            pcIdx--;
                        }
                        else
                        {
                            pathComps = pathComps[0..pcIdx];
                        }
                    }
                    else
                    {
                        if (pathComps[(pcIdx - 1)] != pardir)
                        {
                            if ((pcIdx + 1) < pathComps.length)
                            {
                                // Delete the reference and the preceding entry
                                pathComps = pathComps[0..(pcIdx - 1)] ~ pathComps[(pcIdx + 1)..$];
                                pcIdx--;
                            }
                            // End of line
                            else
                            {
                                pathComps = pathComps[0..(pcIdx - 1)];
                            }
                        }
                        else
                        {
                            pcIdx++;
                        }
                    }
                }
            }
            // Something else
            else
            {
                pcIdx++;
            }
        }

        // Delete any blank chunks out of the array for joining later
        for (int i = 0; i < pathComps.length; i++)
        {
            if (pathComps[i] == "")
            {
                if (pathComps.length == 1)
                {
                    pathComps.length = 0;
                }
                else if (pathComps.length > 1)
                {
                    // At the beginning
                    if (i == 0)
                    {
                        pathComps = pathComps[1..$];
                    }
                    // At the end
                    else if ((i + 1) == pathComps.length)
                    {
                        pathComps = pathComps[0..i];
                    }
                    // In the middle.  This should already have been taken care of from the logic near
                    // the top of this function from using the squeeze and then split, there shouldn't be
                    // any blank chunks in the middle.
                }
            }
        }

        result = std.string.join(pathComps, sep);
    }
    // Path was either a separator, curdir or pardir reference
    else
    {
        result = path;
    }


    if (result == "" && drive == "")
    {
        result = curdir;
    }
    else
    {
        result = drive ~ result;
    }

    return result.dup;
}

/**
 * Normalize the case and separators of a path.
 *
 * This will normalize the case for a path depending on the operating system in use.  For case
 * -insensitive os's (such as Windows), the path will be lower-cased. For case sensitive os's (such
 * as Linux), the path case will not be changed.  On Windows, forward slashes will be converted to
 * backward slashes and on Linux, backward slashes will be converted to forward slashes.
 *
 * Authors: Joe Zuccarello
 *
 * Date: February 15, 2006
 *
 * Returns: Normalized case and separators for a path.
 *
 * Version:
 *
 * License: Public domain.
 *
 * Examples:
 * ---------
 * version(Windows)
 * {
 *     normCase(r"C:\directory1\Subdirectory/FILE) => "c:\directory1\subdirectory\file"
 * }
 * version(linux)
 * {
 *     normCase(r"\usr/src\Path.d") => "\usr/src\Path.d"
 * }
 * ---------
 */
char[] normCase(char[] path)
{
    version(Windows)
    {
    // Take care of the case
    path = std.string.tolower(path);
    }

    path = normSep(path);

    return path.dup;
}

/**
 *
 * Normalizes the separators in a path.
 *
 * Use this to normalize separators as appropriate for the operating system in use.  On Windows,
 * forward slashes * will be converted to backward slashes.  On Linux, the path will just be
 * returned.
 *
 * Authors: Joe Zuccarello
 *
 * Date: February 15, 2006
 *
 * Returns: Normalized separators for a path.
 *
 * Version:
 *
 * License: Public domain.
 *
 * Examples:
 * ---------
 * version(Windows)
 * {
 *     normSep(r"c:/directory\file") => "c:\directory\file"
 * }
 * version(linux)
 * {
 *     normSep(r"/dir1\dir2\dir3/file") => "/dir1\dir2\dir3/file"
 * }
 * ---------
 */
char[] normSep(char[] path)
{
    version(Windows)
    {
    // Convert separators
    if (std.regexp.find(path, lSep) != -1)
    {
        path = std.string.replace(path, lSep, wSep);

        return path.dup;
    }
    else
    {
        return path;
    }
    }
    else version(linux)
    {
    return path;
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }
}

/**
 * Joins an arbitrary number of paths together, using std.path.join as the main joining component.
 *
 * This will take an arbitrary number of paths and join them by passing them to std.path.join to do
 * the actual joining.  *** Join rules follow that of std.path.join ***
 *
 * Note, this function may attempt to convert non-UTF8 characters to UTF8.  If the UTF8 code throws
 * a UTF exception, that exception will not be caught and will be propogated to the caller code.
 *
 * Authors: Joe Zuccarello
 *
 * Date: February 15, 2006
 *
 * Returns: A path joined of one or more separate paths.
 *
 * Throws: UtfException on error from conversion of wchar or dchar parameters.
 *
 * Version:
 *
 * License: Public domain.
 *
 * Examples:
 * ---------
 * join(r"\dir1", "dir2", "dir3/file") => "\dir1\dir2\dir3/file"
 * ---------
 */
char[] join(...)
{
    char[] pathX, rtnPath;

    char[] toStringW(wchar c)
    {
        wchar[] result;
        result.length = 1;
        result[0] = c;
        return std.utf.toUTF8(result);
    }

    char[] toStringD(dchar c)
    {
        dchar[] result;
        result.length = 1;
        result[0] = c;
        return std.utf.toUTF8(result);
    }

    for (int i = 0; i < _arguments.length; i++)
    {
        if (_arguments[i] == typeid(char[]))
        {
            pathX = va_arg!(char[])(_argptr);
        }
        else if (_arguments[i] == typeid(wchar[]))
        {
            pathX = std.string.toUTF8(va_arg!(wchar[])(_argptr));
        }
        else if (_arguments[i] == typeid(dchar[]))
        {
            pathX = std.string.toUTF8(va_arg!(dchar[])(_argptr));
        }
        else if (_arguments[i] == typeid(char))
        {
            pathX = std.string.toString(va_arg!(char)(_argptr));
        }
        else if (_arguments[i] == typeid(wchar))
        {
            pathX = toStringW(va_arg!(wchar)(_argptr));
        }
        else if (_arguments[i] == typeid(dchar))
        {
            pathX = toStringD(va_arg!(dchar)(_argptr));
        }

        if (pathX != "")
        {
            rtnPath = std.path.join(rtnPath, pathX);
            pathX = "";
        }
    }

    return rtnPath.dup;
}

/**
 * Returns a normalized absolutized path.
 *
 * If the path is not absolute, it will be joined with the current working directory.  If it is an
 * absolute path, nothing will be joined with it.  In either case, the path will also be checked to
 * see if it is normalized.  If it's not, it will be normalized.
 *
 * Note: This function does not handle UNC paths.
 *
 * Authors: Joe Zuccarello
 *
 * Date: February 15, 2006
 *
 * Returns: A normalized absolutized path.
 *
 * Version:
 *
 * License: Public domain.
 *
 * Examples:
 * ---------
 * version(Windows)
 * {
 *     // Assume c:\ is the current working directory
 *     absPath(r"file") => "c:\file"
 *     absPath(r"c:\d/src\project") => "c:\d\src\project"
 *     absPath(r".\dir\file\..\dir2\file2") => "c:\dir\dir2\file2"
 * }
 * version(linux)
 * {
 *     // Assume /usr is the current working directory
 *     absPath("d/bin") => "/usr/d/bin"
 *     absPath("/d/lib") => "/d/lib"
 *     absPath("d/src/../file") => "/usr/d/file"
 * }
 * ---------
 */
char[] absPath(char[] path)
out(result)
{
    assert(isNormPath(result));
}
body
{
    bool changed;

    version(Windows)
    {
    // Path is not absolute
    //if (std.regexp.find(path, "^[a-zA-Z]*:\\\\") == -1)
    if (isAbs(path) == false)
    {
        path = std.path.join(getcwd(), path);
        changed = true;
    }
    }
    else version(linux)
    {
    // Path is not absolute
    //if (path[0..1] != r"\" && path[0..1] != "/")
    if (isAbs(path) == false)
    {
        path = std.path.join(getcwd(), path);
        changed = true;
    }
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }

    // Normalize the path
    if (isNormPath(path) == false)
    {
        path = normPath(path);
        changed = true;
    }

    if (changed == true)
    {
        return path.dup;
    }
    else
    {
        return path;
    }
}

private bool isAbs(char[] path)
{
    version(Windows)
    {
    if (std.regexp.find(path, "^[a-zA-Z]*:\\\\") != -1)
    {
        return true;
    }
    else
    {
        return false;
    }
    }
    else version(linux)
    {
    if (path[0..1] == "/")
    {
        return true;
    }
    else
    {
        return false;
    }
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }
}

/**
 * Expands a path into a normalized absolutized path, with a optional reference directory to use
 * with relative paths.
 *
 * This will take a path and expand it into a normalized absolutized version of itself.  An optional
 * reference directory can be provided as well.  If the path passed in is relative, the reference
 * directory will be used to precede the path.  If the reference directory is a relative directory,
 * the reference directory and the path will be appended to the current working directory.
 *
 * Note: This function does not handle UNC paths.
 *
 * Authors: Joe Zuccarello
 *
 * Date: February 15, 2006
 *
 * Returns: A normalized absolutized path.
 *
 * Version:
 *
 * License: Public domain.
 *
 * Examples:
 * ---------
 * version(Windows)
 * {
 *     // Assume c:\ is the current working directory
 *     expandPath("file") => "c:\file"
 *     expandPath(r"\dir\file") => "c:\dir\file"
 *     expandPath("file", r"\dir") => "c:\dir\file"
 * }
 * version(linux)
 * {
 *     // Assume /usr is the current working directory
 *     expandPath("file") => "/usr/file"
 *     expandPath(r"/dir/file") => "/usr/dir/file"
 *     expandPath("file", "/dir") => "/usr/dir/file"
 * }
 * ---------
 */
char[] expandPath(char[] path, char[] dir = "")
{
    char[] result;

    if (path != "")
    {
        // Path is absolute; ditch the dir and return this after normalizing.
        if (isAbs(path) == true)
        {
            result = normPath(path);
        }
        // Path is not absolute
        else
        {
            if (dir != "")
            {
                // Check if dir is absolute
                if (isAbs(dir) == true)
                {
                    result = normPath(dir ~ sep ~ path);
                }
                // Dir is not absolute, but it is a directory (at least that's
                // what the caller is telling us.
                else
                {
                    result = normPath(getcwd() ~ sep ~ dir ~ sep ~ path);
                }
            }
            // Dir is empty and path is not absolute
            else
            {
                result = normPath(getcwd() ~ sep ~ path);
            }
        }
    }
    // Path is empty, check dir
    else
    {
        if (dir != "")
        {
            // Check if dir is absolute
            if (isAbs(dir) == true)
            {
                result = normPath(dir);
            }
            else
            {
                result = normPath(getcwd() ~ sep ~ dir);
            }
        }
        // Path and dir are empty
        else
        {
            result = "";
        }
    }

    return result.dup;
}
July 31, 2006
Here's the unittests.  An excessive amount to be sure, but, hey, sometimes overly verifing your code can be fun. :)

// isRoot
unittest
{
    version(Windows)
    {
    // Valid roots
    assert(isRoot(getDrive(getcwd()) ~ sep));
    assert(isRoot(std.string.toupper(getDrive(getcwd()) ~ sep), true));

    // Invalid roots
    char[][] roots = getRoots();
    char[] lastRoot = roots[($ - 1)];
    char[] succRoot = std.string.succ(lastRoot[0..($ - 2)]) ~ lastRoot[($ - 2)..$];
    assert(!isRoot(succRoot));
    assert(!isRoot(std.string.tolower(getDrive(getcwd()) ~ sep), true));
    }
    else version(linux)
    {
    // Valid roots
    assert(isRoot("/"));
    assert(isRoot(getRoots()[0]));

    // Invalid roots
    assert(!isRoot("../"));
    assert(!isRoot(r"\"));
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }
}


// containsRoot
unittest
{
    version(Windows)
    {
    // Valid roots
    assert(containsRoot(getcwd()));
    assert(containsRoot(getDrive(getcwd()) ~ sep));
    assert(containsRoot(std.string.toupper(getcwd()), true));

    // Invalid roots
    int idx = std.string.find(getcwd(), getDrive(getcwd()));
    assert(!containsRoot(getcwd()[idx..(getDrive(getcwd()).length)]));
    assert(!containsRoot(std.string.tolower(getcwd()), true));
    assert(!containsRoot(std.string.tolower(getDrive(getcwd()) ~ sep), true));
    assert(!containsRoot(r"\directory\file"));
    }
    else version(linux)
    {
    // Valid roots
    assert(containsRoot("/usr/d/src"));

    // Invalid roots
    assert(!containsRoot("../d/src"));
    assert(!containsRoot(r"\d/src"));
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }
}

// isNormPath
unittest
{
    version(Windows)
    {
    assert(!isNormPath(r" .\"));
    assert(!isNormPath(r".\"));
    assert(!isNormPath(r"\."));
    assert(!isNormPath(r"\.\"));
    assert(!isNormPath(r"..\"));
    assert(!isNormPath(r"\.."));
    assert(!isNormPath(r"\..\"));
    assert(!isNormPath(r"\\"));
    }
    else version(linux)
    {
    assert(isNormPath(r" .\"));
    assert(isNormPath(r".\"));
    assert(isNormPath(r"\."));
    assert(isNormPath(r"\.\"));
    assert(isNormPath(r"..\"));
    assert(isNormPath(r"\.."));
    assert(isNormPath(r"\..\"));
    assert(isNormPath(r"\\"));
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }

    // These patterns are not normalized paths
    assert(!isNormPath("//"));
    assert(!isNormPath(" ./"));
    assert(!isNormPath("./"));
    assert(!isNormPath("/."));
    assert(!isNormPath("/./"));
    assert(!isNormPath("../"));
    assert(!isNormPath("/.."));
    assert(!isNormPath("/../"));
    assert(!isNormPath(r"\/"));

    // These patterns are normalized paths
    assert(isNormPath(""));
    assert(isNormPath(" "));
    assert(isNormPath("  "));
    assert(isNormPath(" a b c"));
    assert(isNormPath("b"));
    assert(isNormPath("."));
    assert(isNormPath(".."));
    assert(isNormPath(r"\"));
    assert(isNormPath("/"));
    assert(isNormPath(r"..\.."));
    assert(isNormPath("../.."));

    // Now test the patterns that are part of the normPath unittest, duh duh dun...
    assert(isNormPath(normPath("")));
    assert(isNormPath(normPath(" ")));
    assert(isNormPath(normPath("  ")));
    assert(isNormPath(normPath(".")));
    assert(isNormPath(normPath("..")));
    assert(isNormPath(normPath(r"\")));
    assert(isNormPath(normPath("/")));
    assert(isNormPath(normPath("/.")));
    assert(isNormPath(normPath("./")));
    assert(isNormPath(normPath(r"\.")));
    assert(isNormPath(normPath(r".\")));
    assert(isNormPath(normPath(r".\dir1\dir2\file")));
    assert(isNormPath(normPath("./dir1/dir2/file")));
    assert(isNormPath(normPath(r"dir1\dir2\file\.")));
    assert(isNormPath(normPath("dir1/dir2/file/.")));
    assert(isNormPath(normPath(r"\dir1\dir2\file\.")));
    assert(isNormPath(normPath("/dir1/dir2/file/.")));
    assert(isNormPath(normPath(r".\dir1\dir2\file\.")));
    assert(isNormPath(normPath("./dir1/dir2/file/.")));
    assert(isNormPath(normPath(r".\.\dir1\dir2\file\.")));
    assert(isNormPath(normPath("././dir1/dir2/file/.")));
    assert(isNormPath(normPath(r".\.\dir1\dir2\file\.\.")));
    assert(isNormPath(normPath("././dir1/dir2/file/./.")));
    assert(isNormPath(normPath(r"dir1\.\dir2")));
    assert(isNormPath(normPath("dir1/./dir2")));
    assert(isNormPath(normPath(r"dir1\.\.\.\dir2\.\file")));
    assert(isNormPath(normPath("dir1/./././dir2/./file")));
    assert(isNormPath(normPath(r".\.")));
    assert(isNormPath(normPath("./.")));
    assert(isNormPath(normPath(r"\.\")));
    assert(isNormPath(normPath("/./")));
    assert(isNormPath(normPath(r"\.\.\.")));
    assert(isNormPath(normPath("/././.")));
    assert(isNormPath(normPath(r".\.\.\")));
    assert(isNormPath(normPath("./././")));
    assert(isNormPath(normPath("dir")));
    assert(isNormPath(normPath("c:")));
    assert(isNormPath(normPath(r"c:\..")));
    assert(isNormPath(normPath("c:/..")));
    assert(isNormPath(normPath(r"\dir")));
    assert(isNormPath(normPath("/dir")));
    assert(isNormPath(normPath(r"\dir1\file")));
    assert(isNormPath(normPath("/dir1/file")));
    assert(isNormPath(normPath(r"\dir1\file\")));
    assert(isNormPath(normPath("/dir1/file/")));
    assert(isNormPath(normPath(r"\dir1\file\..")));
    assert(isNormPath(normPath("/dir1/file/..")));
    assert(isNormPath(normPath(r"\dir1\dir2\..\file")));
    assert(isNormPath(normPath("/dir1/dir2/../file")));
    assert(isNormPath(normPath(r"\dir1\file..")));
    assert(isNormPath(normPath("/dir1/file..")));
    assert(isNormPath(normPath(r"\dir1\file\..\..")));
    assert(isNormPath(normPath("/dir1/file/../..")));
    assert(isNormPath(normPath("c:..file")));
    assert(isNormPath(normPath(r"c:\..\dir1\..\file")));
    assert(isNormPath(normPath("c:/../dir1/../file")));
    assert(isNormPath(normPath(r"c:..file\dir1")));
    assert(isNormPath(normPath("c:..file/dir1")));
    assert(isNormPath(normPath(r"c:..file\dir1\..")));
    assert(isNormPath(normPath("c:..file/dir1/..")));
    assert(isNormPath(normPath(".")));
    assert(isNormPath(normPath(r"c:\")));
    assert(isNormPath(normPath("c:/")));
    assert(isNormPath(normPath(r"c:\dir1\.\")));
    assert(isNormPath(normPath("c:/dir1/./")));
    assert(isNormPath(normPath(r"c:\dir1\.\file")));
    assert(isNormPath(normPath("c:/dir1/./file")));
    assert(isNormPath(normPath(r"c:\dir1\.\file\")));
    assert(isNormPath(normPath("c:/dir1/./file/")));
    assert(isNormPath(normPath(r"c:\dir1\..\file")));
    assert(isNormPath(normPath("c:/dir1/../file")));
    assert(isNormPath(normPath(r"c:\dir1\..\file\")));
    assert(isNormPath(normPath("c:/dir1/../file/")));
    assert(isNormPath(normPath(r"c:\dir1\..\file\.")));
    assert(isNormPath(normPath("c:/dir1/../file/.")));
    assert(isNormPath(normPath(r"c:\dir1\..\file\.\")));
    assert(isNormPath(normPath("c:/dir1/../file/./")));
    assert(isNormPath(normPath(r"c:\dir1\..\file\..")));
    assert(isNormPath(normPath("c:/dir1/../file/..")));
    assert(isNormPath(normPath(r"\\\dir1\file")));
    assert(isNormPath(normPath("///dir1/file")));
    assert(isNormPath(normPath(r"\\\dir1\..\dir2\file")));
    assert(isNormPath(normPath("///dir1/../dir2/file")));
    assert(isNormPath(normPath(r"\\\file")));
    assert(isNormPath(normPath("///file")));
    assert(isNormPath(normPath(r"..\..")));
    assert(isNormPath(normPath("../..")));
    assert(isNormPath(normPath(r"..\\\..")));
    assert(isNormPath(normPath("..///..")));
    assert(isNormPath(normPath(r"..\")));
    assert(isNormPath(normPath("../")));
    assert(isNormPath(normPath(r"..\\\\")));
    assert(isNormPath(normPath("..////")));
    assert(isNormPath(normPath(r".\")));
    assert(isNormPath(normPath("./")));
    assert(isNormPath(normPath(r"\.\file")));
    assert(isNormPath(normPath("/./file")));
    assert(isNormPath(normPath("c:" ~ sep ~ "dir1" ~ sep ~ "." ~ sep)));
    assert(isNormPath(normPath("c:" ~ sep ~ "dir1" ~ sep ~ "." ~ sep ~ "file")));
    assert(isNormPath(normPath("c:" ~ sep ~ "dir1" ~ sep ~ "." ~ sep ~ "file" ~ sep)));
    assert(isNormPath(normPath("c:" ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file")));
    assert(isNormPath(normPath("c:" ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file" ~ sep)));
    assert(isNormPath(normPath("c:" ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file" ~ sep ~ ".")));
    assert(isNormPath(normPath("c:" ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file" ~ sep ~ "." ~ sep)));
    assert(isNormPath(normPath("c:" ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file" ~ sep ~ "..")));
    assert(isNormPath(normPath(sep ~ sep ~ sep ~ "dir1" ~ sep ~ "file")));
    assert(isNormPath(normPath(sep ~ sep ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "dir2" ~ sep ~ "file")));
    assert(isNormPath(normPath(sep ~ sep ~ sep ~ "file")));
    assert(isNormPath(normPath(".." ~ sep ~ "..")));
    assert(isNormPath(normPath(".." ~ sep ~ sep ~ sep ~ "..")));
    assert(isNormPath(normPath(".." ~ sep)));
    assert(isNormPath(normPath(".." ~ sep ~ sep ~ sep ~ sep)));
    assert(isNormPath(normPath("." ~ sep)));
    assert(isNormPath(normPath(sep ~ "." ~ sep ~ "file")));
    assert(isNormPath(normPath(r"~\dir")));
    assert(isNormPath(normPath("~/dir")));
    assert(isNormPath(normPath(sep ~ "..")));
    assert(isNormPath(normPath(sep ~ ".." ~ sep)));
    assert(isNormPath(normPath(sep ~ ".." ~ sep ~ "dir")));
    assert(isNormPath(normPath(sep ~ "..file")));
    assert(isNormPath(normPath(sep ~ ".file")));
    assert(isNormPath(normPath(".file")));
    assert(isNormPath(normPath("file.")));
    assert(isNormPath(normPath("file.file" ~ sep ~ "." ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "..")));
    assert(isNormPath(normPath("file$.")));
}

// normPath
unittest
{
    version(Windows)
    {
    assert(normPath("c:") == "c:");
    assert(normPath("c:" ~ sep ~ "..") == r"c:" ~ sep);
    assert(normPath("c:" ~ sep ~ ".." ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file") == "c:" ~ sep ~ "file");
    assert(normPath("c:" ~ sep) == "c:" ~ sep);
    assert(normPath("c:" ~ sep ~ "dir1" ~ sep ~ "." ~ sep) == "c:" ~ sep ~ "dir1");
    assert(normPath("c:" ~ sep ~ "dir1" ~ sep ~ "." ~ sep ~ "file") == "c:" ~ sep ~ "dir1" ~ sep ~ "file");
    assert(normPath("c:" ~ sep ~ "dir1" ~ sep ~ "." ~ sep ~ "file" ~ sep) == "c:" ~ sep ~ "dir1" ~ sep ~ "file");
    assert(normPath("c:" ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file") == "c:" ~ sep ~ "file");
    assert(normPath("c:" ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file" ~ sep) == "c:" ~ sep ~ "file");
    assert(normPath("c:" ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file" ~ sep ~ ".") == "c:" ~ sep ~ "file");
    assert(normPath("c:" ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file" ~ sep ~ "." ~ sep) == "c:" ~ sep ~ "file");
    assert(normPath("c:" ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "file" ~ sep ~ "..") == "c:" ~ sep);
    assert(normPath(r"\\/dir1////\\dir2//..\\\\file/./") == sep ~ "dir1" ~ sep ~ "file");
    }
    else version(linux)
    {
    assert(normPath("c:/../file") == "file");
    assert(normPath(r"c:\..\file") == r"c:\..\file");
    assert(normPath("~/dir") == "~/dir");
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }

    assert(normPath("") == ".");
    assert(normPath(r" ./") == r" .");
    assert(normPath(" ") == " ");
    assert(normPath("  ") == "  ");
    assert(normPath(".") == ".");
    assert(normPath("..") == "..");
    assert(normPath(sep) == sep);
    assert(normPath(sep ~ curdir) == sep);
    assert(normPath(curdir ~ sep) == curdir);
    assert(normPath(curdir ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file") == "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(normPath("dir1" ~ sep ~ "dir2" ~ sep ~ "file" ~ sep ~ curdir) == "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(normPath(sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file" ~ sep ~ curdir) == sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(normPath(curdir ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file" ~ sep ~ curdir) == "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(normPath(curdir ~ sep ~ curdir ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file" ~ sep ~ curdir) == "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(normPath(curdir ~ sep ~ curdir ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file" ~ sep ~ curdir ~ sep ~ curdir) == "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(normPath("dir1" ~ sep ~ curdir ~ sep ~ "dir2") == "dir1" ~ sep ~ "dir2");
    assert(normPath("dir1" ~ sep ~ curdir ~ sep ~ curdir ~ sep ~ curdir ~ sep ~ "dir2" ~ sep ~ curdir ~ sep ~ "file") == "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(normPath(curdir ~ sep ~ curdir) == curdir);
    assert(normPath(sep ~ curdir ~ sep) == sep);
    assert(normPath(sep ~ curdir ~ sep ~ curdir ~ sep ~ curdir) == sep);
    assert(normPath(curdir ~ sep ~ curdir ~ sep ~ curdir ~ sep) == curdir);
    assert(normPath("dir") == "dir");
    assert(normPath(sep ~ "dir") == sep ~ "dir");
    assert(normPath(sep ~ "dir1" ~ sep ~ "file") == sep ~ "dir1" ~ sep ~ "file");
    assert(normPath(sep ~ "dir1" ~ sep ~ "file" ~ sep) == sep ~ "dir1" ~ sep ~ "file");
    assert(normPath(sep ~ "dir1" ~ sep ~ "file" ~ sep ~ "..") == sep ~ "dir1");
    assert(normPath(sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ ".." ~ sep ~ "file") == sep ~ "dir1" ~ sep ~ "file");
    assert(normPath(sep ~ "dir1" ~ sep ~ "file..") == sep ~ "dir1" ~ sep ~ "file..");
    assert(normPath(sep ~ "dir1" ~ sep ~ "file" ~ sep ~ ".." ~ sep ~ "..") == sep);
    assert(normPath("c:..file") == "c:..file");
    assert(normPath("c:..file" ~ sep ~ "dir1") == "c:..file" ~ sep ~ "dir1");
    assert(normPath("c:..file" ~ sep ~ "dir1" ~ sep ~ "..") == "c:..file");
    assert(normPath(sep ~ sep ~ sep ~ "dir1" ~ sep ~ "file") == sep ~ "dir1" ~ sep ~ "file");
    assert(normPath(sep ~ sep ~ sep ~ "dir1" ~ sep ~ ".." ~ sep ~ "dir2" ~ sep ~ "file") == sep ~ "dir2" ~ sep ~ "file");
    assert(normPath(sep ~ sep ~ sep ~ "file") == sep ~ "file");
    assert(normPath(".." ~ sep ~ "..") == ".." ~ sep ~ "..");
    assert(normPath(".." ~ sep ~ sep ~ sep ~ "..") == ".." ~ sep ~ "..");
    assert(normPath(".." ~ sep) == "..");
    assert(normPath(".." ~ sep ~ sep ~ sep ~ sep) == "..");
    assert(normPath("." ~ sep) == ".");
    assert(normPath(sep ~ "." ~ sep ~ "file") == sep ~ "file");
    assert(normPath(r"~\dir") == r"~\dir");
    assert(normPath("~") == "~");
    assert(normPath(sep ~ "..") == sep);
    assert(normPath(sep ~ ".." ~ sep) == sep);
    assert(normPath(sep ~ ".." ~ sep ~ "dir") == sep ~ "dir");
    assert(normPath(sep ~ "..file") == sep ~ "..file");
    assert(normPath(sep ~ ".file") == sep ~ ".file");
    assert(normPath(".file") == ".file");
    assert(normPath("file.") == "file.");
    assert(normPath("file.file" ~ sep ~ "." ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "..") == "file.file" ~ sep ~ "dir1");
    assert(normPath("file$.") == "file$.");
}

// normCase
unittest
{
    version(Windows)
    {
    assert(normCase(r"C:/dir1\file1/dir2/File2") == r"c:\dir1\file1\dir2\file2");
    }
    else version(linux)
    {
    assert(normCase(r"/Dir1\file1\DIR2/file2") == r"/Dir1\file1\DIR2/file2");
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }
}

// normSep
unittest
{
    version(Windows)
    {
    assert(normSep(r"c:/dir\file") == r"c:\dir\file");
    }
    else version(linux)
    {
    assert(normSep(r"\dir1/dir2\file") == r"\dir1/dir2\file");
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }
}

// join
unittest
{
    assert(join(r"\dir/file", "file2", r"file3\file4") == r"\dir/file" ~ sep ~ "file2" ~ sep ~ r"file3\file4");
    assert(join(r"\dir1\file") == r"\dir1\file");
    assert(join(r"\dir1\file", r"dir2\file") == r"\dir1\file" ~ sep ~ r"dir2\file");
    assert(join(r"\dir1\file", r"dir2\file", r"dir3\file") == r"\dir1\file" ~ sep ~ r"dir2\file" ~ sep ~ r"dir3\file");
    assert(join("file1", "file2", "file3", "file4") == "file1" ~ sep ~ "file2" ~ sep ~ "file3" ~ sep ~ "file4");
    assert(join("file1", "file2"w, "file3"d, "file4"c) == "file1" ~ sep ~ "file2" ~ sep ~ "file3" ~ sep ~ "file4");
    assert(join("f", "i"w, "l"d, "e"c) == "f" ~ sep ~ "i" ~ sep ~ "l" ~ sep ~ "e");
    char c = 'i'; wchar w = 'l'; dchar d = 'e';
    assert(join("f", c, w, d) == "f" ~ sep ~ "i" ~ sep ~ "l" ~ sep ~ "e");
    assert(join('f', 'i', 'l', 'e') == "f" ~ sep ~ "i" ~ sep ~ "l" ~ sep ~ "e");

    version(Windows)
    {
    assert(join("file1", r"\dir1\file2") == r"\dir1\file2");
    assert(join(r"\file1", r"\dir1") == r"\dir1");
    assert(join(r"\file1", "dir1", r"c:\dir2\file2") == r"c:\dir2\file2");
    assert(join("file1", r"\dir1", r"c:\dir2", r"d:\dir3", r"file3") == r"d:\dir3\file3");
    }
    version(linux)
    {
    assert(join("file1", r"/dir1\file2") == r"/dir1\file2");
    assert(join(r"\file1", r"\dir1") == r"\file1/\dir1");
    assert(join(r"\file1", "dir1", r"c:\dir2\file2") == r"\file1/dir1/c:\dir2\file2");
    assert(join("file1", r"\dir1", r"c:\dir2", r"d:\dir3", r"file3") == r"file1/\dir1/c:\dir2/d:\dir3/file3");
    }
}

// absPath
unittest
{
    version(Windows)
    {
    assert(absPath(r"c:\dir1\file1\") == r"c:\dir1\file1");
    }
    else version(linux)
    {
    assert(absPath("file") == std.path.join(getcwd(), "file"));
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }

    assert(absPath("file") == std.path.join(getcwd(), "file"));
    assert(absPath(r"\dir1\file") == std.path.join(getcwd(), r"\dir1\file"));
    assert(absPath(getcwd()));
    assert(absPath(absPath(getcwd())));
    assert(absPath(absPath(getDirName(getcwd()))));
}

// expandPath
unittest
{
    version(Windows)
    {
    assert(expandPath("", "u:") == getcwd() ~ sep ~ "u:");
    assert(expandPath("", r"u:\") == r"u:\");
    assert(expandPath("", r"u:\dir1") == r"u:\dir1");
    assert(expandPath("", r"u:\dir1\") == r"u:\dir1");
    assert(expandPath("", r"u:\dir1\file\..") == r"u:\dir1");
    assert(expandPath("", r"u:\dir1\..\file\.") == r"u:\file");
    assert(expandPath("", r"\dir1\\:file") == getcwd() ~ r"\dir1\:file");
    assert(expandPath("", r"\:dir1\\file") == getcwd() ~ r"\:dir1\file");
    assert(expandPath("", "u:") == getcwd() ~ r"\u:");
    assert(expandPath("", r"u:\") == r"u:\");
    assert(expandPath("", r"u:\dir1") == r"u:\dir1");
    assert(expandPath("", r"u:\dir1\") == r"u:\dir1");
    assert(expandPath("", r"u:\dir1\file\..") == r"u:\dir1");
    assert(expandPath("", r"u:\dir1\..\file\.") == r"u:\file");
    assert(expandPath("file", r"p:\" ~ "dir1") == r"p:\" ~ "dir1" ~ sep ~ "file");
    assert(expandPath("file", r"p:\" ~ "dir1" ~ sep) == r"p:\" ~ "dir1" ~ sep ~ "file");
    assert(expandPath("file", r"p:\" ~ "dir1" ~ sep ~ sep) == r"p:\" ~ "dir1" ~ sep ~ "file");
    assert(expandPath("file", "p:\\dir1\\\\dir2\\") == "p:" ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(expandPath("file", "p:\\dir1\\\\dir2\\\\\\dir3\\\\\\\\dir4\\\\") == "p:" ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "dir3" ~ sep ~ "dir4" ~ sep ~ "file");
    assert(expandPath("file", "p:") == getcwd() ~ sep ~ "p:" ~ sep ~ "file");
    assert(expandPath("file", "p:\\") == "p:" ~ sep ~ "file");
    assert(expandPath("file" , "p:\\dir\\.") == "p:" ~ sep ~ "dir" ~ sep ~ "file");
    assert(expandPath("file" , "p:\\dir1\\.\\dir2\\") == "p:" ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(expandPath("file" , "p:\\dir1\\.\\dir2\\.") == "p:" ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(expandPath("file" , "p:\\dir1\\.\\dir2\\.\\") == "p:" ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(expandPath("", r"\") == getcwd());
    assert(expandPath("", r"\dir1") == getcwd() ~ r"\dir1");
    assert(expandPath("", r"\dir1\\file") == getcwd() ~ r"\dir1\file");
    assert(expandPath("", r"\..\dir1\\file") == getDirName(getcwd()) ~ r"\dir1\file");
    assert(expandPath("", r"\\\dir1\\file\\..\\dir2\\dir3\\..") == getcwd() ~ r"\dir1\dir2");
    assert(expandPath("file") == getcwd() ~ r"\file");
    assert(expandPath(r"\dir\file") == getcwd() ~ r"\dir\file");
    assert(expandPath("file", r"\dir") == getcwd() ~ r"\dir\file");
    assert(expandPath("file", r"....\") == getcwd() ~ sep ~ "...." ~ sep ~ "file");
    assert(expandPath("file", "a.\\") == getcwd() ~ sep ~ "a." ~ sep ~ "file");
    assert(expandPath("file", "..\\..") == getDirName(getDirName(getcwd())) ~ sep ~ "file");
    assert(expandPath("file", "..\\..\\") == getDirName(getDirName(getcwd())) ~ sep ~ "file");
    assert(expandPath("file", "..\\..\\..\\") == getDirName(getDirName(getDirName(getcwd()))) ~ sep ~ "file");
    assert(expandPath("file", "..\\..\\..\\..\\") == getDirName(getDirName(getDirName(getDirName(getcwd())))) ~ sep ~ "file");
    assert(expandPath("file" , ".\\dir") == getcwd() ~ sep ~ "dir" ~ sep ~ "file");
    assert(expandPath("file" , "dir1\\.") == getcwd() ~ sep ~ "dir1" ~ sep ~ "file");
    assert(expandPath("file" , "dir1\\.\\dir2") == getcwd() ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(expandPath("file" , ".\\dir1\\.\\dir2") == getcwd() ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(expandPath("file" , ".\\dir1\\.\\dir2\\.") == getcwd() ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(expandPath("file" , ".\\..\\..\\.\\.") == getDirName(getDirName(getcwd())) ~ sep ~ "file");
    assert(expandPath("file", ".\\..\\.\\.\\..\\") == getDirName(getDirName(getcwd())) ~ sep ~ "file");
    assert(expandPath("file" , r"\\.\\dir1\\.\\dir2\\.") == getcwd() ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(expandPath("file", "\\.\\") == getcwd() ~ sep ~ "file");
    assert(expandPath("file", "\\") == getcwd() ~ sep ~ "file");
    assert(expandPath("file", "\\\\") == getcwd() ~ sep ~ "file");
    assert(expandPath("file", "\\..\\..") == getDirName(getDirName(getcwd())) ~ sep ~ "file");
    assert(expandPath("file", r"c:\..\..") == r"c:\file");
    assert(expandPath("file", "\\..\\..\\") == getDirName(getDirName(getcwd())) ~ sep ~ "file");
    assert(expandPath(r"\dir1\dir2/file", "/refdir") == getcwd() ~ sep ~ "refdir" ~ sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    }
    else version(linux)
    {
    assert(expandPath("file") == getcwd() ~ "/file");
    assert(expandPath(r"/dir/file") == "/dir/file");
    assert(expandPath("file", "/dir") == "/dir/file");
    assert(expandPath("", r"\") == getcwd() ~ r"/\");
    assert(expandPath("", "/") == "/");
    assert(expandPath("", r"\dir1") == getcwd() ~ r"/\dir1");
    assert(expandPath("", r"\dir1\\file") == getcwd() ~ r"/\dir1\\file");
    assert(expandPath("", r"/dir1\\file///file2") == r"/dir1\\file/file2");
    assert(expandPath("", r"\..\dir1\\file") == getcwd() ~ r"/\..\dir1\\file");
    assert(expandPath("", r"\\\dir1\\file\\..\\dir2\\dir3\\..") == getcwd() ~ r"/\\\dir1\\file\\..\\dir2\\dir3\\..");
    assert(expandPath("", r"/dir1/file/../dir2/dir3/..") == "/dir1/dir2");
    assert(expandPath("file") == getcwd() ~ "/file");
    assert(expandPath(r"\dir\file") == getcwd() ~ r"/\dir\file");
    assert(expandPath(r"/dir\file") == r"/dir\file");
    assert(expandPath("file", r"\dir") == getcwd() ~ r"/\dir/file");
    assert(expandPath("file", r"....\") == getcwd() ~ sep ~ r"....\" ~ sep ~ "file");
    assert(expandPath("file", r"a.\") == getcwd() ~ sep ~ r"a.\" ~ sep ~ "file");
    assert(expandPath("file", r"..\..") == getcwd() ~ r"/..\../file");
    assert(expandPath("file", "../..") == getDirName(getDirName(getcwd())) ~ sep ~ "file");
    assert(expandPath("file", r"..\..\") == getcwd() ~ r"/..\..\/file");
    assert(expandPath("file", "../../") == getDirName(getDirName(getcwd())) ~ sep ~ "file");
    assert(expandPath("file", r"..\..\..\") == getcwd() ~ r"/..\..\..\/file");
    assert(expandPath("file", "../../../") == getDirName(getDirName(getDirName(getcwd()))) ~ sep ~ "file");
    assert(expandPath("file", r"..\..\..\..\") == getcwd() ~ r"/..\..\..\..\/file");
    assert(expandPath("file", "../../../../") == getDirName(getDirName(getDirName(getDirName(getcwd())))) ~ sep ~ "file");
    assert(expandPath("file", r".\dir") == getcwd() ~ r"/.\dir/file");
    assert(expandPath("file", r"dir1\.") == getcwd() ~ r"/dir1\./file");
    assert(expandPath("file", r"dir1\.\dir2") == getcwd() ~ r"/dir1\.\dir2/file");
    assert(expandPath("file", r".\dir1\.\dir2") == getcwd() ~ r"/.\dir1\.\dir2/file");
    assert(expandPath("file", r".\dir1\.\dir2\.") == getcwd() ~ r"/.\dir1\.\dir2\./file");
    assert(expandPath("file", r".\..\..\.\.") == getcwd() ~ r"/.\..\..\.\./file");
    assert(expandPath("file", "./../.././.") == getDirName(getDirName(getcwd())) ~ sep ~ "file");
    assert(expandPath("file", r".\..\.\.\..\") == getcwd() ~ r"/.\..\.\.\..\/file");
    assert(expandPath("file", "./../././../") == getDirName(getDirName(getcwd())) ~ sep ~ "file");
    assert(expandPath("file" , r"\\.\\dir1\\.\\dir2\\.") == getcwd() ~ r"/\\.\\dir1\\.\\dir2\\./file");
    assert(expandPath("file" , "//.//dir1//.//dir2//.") == sep ~ "dir1" ~ sep ~ "dir2" ~ sep ~ "file");
    assert(expandPath("file", r"\.\") == getcwd() ~ r"/\.\/file");
    assert(expandPath("file", r"\") == getcwd() ~ r"/\/file");
    assert(expandPath("file", r"\\") == getcwd() ~ r"/\\/file");
    assert(expandPath("file", r"\..\..") == getcwd() ~ r"/\..\../file");
    assert(expandPath("file", "/../..") == "/file");
    assert(expandPath("file", r"\..\..\") == getcwd() ~ r"/\..\..\/file");
    assert(expandPath(r"\dir1\dir2/file", "/refdir") == r"/refdir/\dir1\dir2/file");
    }
    else
    {
    pragma(msg, "Unsupported OS");
    static assert(0);
    }

    assert(expandPath("") == "");
    assert(expandPath("file") == getcwd() ~ sep ~ "file");
    assert(expandPath("file", "") == getcwd() ~ sep ~ "file");
    assert(expandPath("file", "..") == getDirName(getcwd()) ~ sep ~ "file");
    assert(expandPath("..") == getDirName(getcwd()));
    assert(expandPath(getcwd()));
    assert(expandPath(getcwd(), ""));
    assert(expandPath(getcwd(), getcwd()));
    assert(expandPath(getcwd(), "refdir"));
}
August 17, 2006
Kramer wrote:
> I have some functions for the path module that I'd like to submit to the general public.  I haven't worked on them for about half a year and I had submitted them to Walter for inclusion into Phobos a while back, but he's got far more pressing issues (like bug fixes and spec. stability) to concern himself with.
> 

Seems nice (haven't used yet though, maybe in the future, for shell scripting). And indeed it seems like it would be a good addition for Phobos (std.path).

-- 
Bruno Medeiros - MSc in CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
August 18, 2006
Bruno Medeiros wrote:
> Kramer wrote:
>> I have some functions for the path module that I'd like to submit to the general public.  I haven't worked on them for about half a year and I had submitted them to Walter for inclusion into Phobos a while back, but he's got far more pressing issues (like bug fixes and spec. stability) to concern himself with.
>>
> 
> Seems nice (haven't used yet though, maybe in the future, for shell scripting). And indeed it seems like it would be a good addition for Phobos (std.path).
> 
Thanks.  Hopefully, they're useful. :)

-Kramer