PageRenderTime 158ms CodeModel.GetById 15ms app.highlight 129ms RepoModel.GetById 1ms app.codeStats 1ms

/std/path.d

http://github.com/jcd/phobos
D | 2926 lines | 1991 code | 275 blank | 660 comment | 658 complexity | c3e22674ee5c1e4ccc9289b6acb496e0 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1// Written in the D programming language.
   2
   3/** This module is used to manipulate _path strings.
   4
   5    All functions, with the exception of $(LREF expandTilde) (and in some
   6    cases $(LREF absolutePath) and $(LREF relativePath)), are pure
   7    string manipulation functions; they don't depend on any state outside
   8    the program, nor do they perform any actual file system actions.
   9    This has the consequence that the module does not make any distinction
  10    between a _path that points to a directory and a _path that points to a
  11    file, and it does not know whether or not the object pointed to by the
  12    _path actually exists in the file system.
  13    To differentiate between these cases, use $(XREF file,isDir) and
  14    $(XREF file,exists).
  15
  16    Note that on Windows, both the backslash ($(D `\`)) and the slash ($(D `/`))
  17    are in principle valid directory separators.  This module treats them
  18    both on equal footing, but in cases where a $(I new) separator is
  19    added, a backslash will be used.  Furthermore, the $(LREF buildNormalizedPath)
  20    function will replace all slashes with backslashes on that platform.
  21
  22    In general, the functions in this module assume that the input paths
  23    are well-formed.  (That is, they should not contain invalid characters,
  24    they should follow the file system's _path format, etc.)  The result
  25    of calling a function on an ill-formed _path is undefined.  When there
  26    is a chance that a _path or a file name is invalid (for instance, when it
  27    has been input by the user), it may sometimes be desirable to use the
  28    $(LREF isValidFilename) and $(LREF isValidPath) functions to check
  29    this.
  30
  31    Most functions do not perform any memory allocations, and if a string is
  32    returned, it is usually a slice of an input string.  If a function
  33    allocates, this is explicitly mentioned in the documentation.
  34
  35    Authors:
  36        Lars Tandle Kyllingstad,
  37        $(WEB digitalmars.com, Walter Bright),
  38        Grzegorz Adam Hankiewicz,
  39        Thomas Kühne,
  40        $(WEB erdani.org, Andrei Alexandrescu)
  41    Copyright:
  42        Copyright (c) 20002011, the authors. All rights reserved.
  43    License:
  44        $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
  45    Source:
  46        $(PHOBOSSRC std/_path.d)
  47    Macros:
  48        WIKI = Phobos/StdPath
  49*/
  50module std.path;
  51
  52
  53import std.algorithm;
  54import std.array;
  55import std.conv;
  56import std.file: getcwd;
  57import std.range;
  58import std.string;
  59import std.traits;
  60
  61version(Posix)
  62{
  63    import core.exception;
  64    import core.stdc.errno;
  65    import core.sys.posix.pwd;
  66    import core.sys.posix.stdlib;
  67    private import core.exception : onOutOfMemoryError;
  68}
  69
  70
  71
  72
  73/** String used to separate directory names in a path.  Under
  74    POSIX this is a slash, under Windows a backslash.
  75*/
  76version(Posix)          enum string dirSeparator = "/";
  77else version(Windows)   enum string dirSeparator = "\\";
  78else static assert (0, "unsupported platform");
  79
  80
  81
  82
  83/** Path separator string.  A colon under POSIX, a semicolon
  84    under Windows.
  85*/
  86version(Posix)          enum string pathSeparator = ":";
  87else version(Windows)   enum string pathSeparator = ";";
  88else static assert (0, "unsupported platform");
  89
  90
  91
  92
  93/** Determines whether the given character is a directory separator.
  94
  95    On Windows, this includes both $(D `\`) and $(D `/`).
  96    On POSIX, it's just $(D `/`).
  97*/
  98bool isDirSeparator(dchar c)  @safe pure nothrow
  99{
 100    if (c == '/') return true;
 101    version(Windows) if (c == '\\') return true;
 102    return false;
 103}
 104
 105
 106/*  Determines whether the given character is a drive separator.
 107
 108    On Windows, this is true if c is the ':' character that separates
 109    the drive letter from the rest of the path.  On POSIX, this always
 110    returns false.
 111*/
 112private bool isDriveSeparator(dchar c)  @safe pure nothrow
 113{
 114    version(Windows) return c == ':';
 115    else return false;
 116}
 117
 118
 119/*  Combines the isDirSeparator and isDriveSeparator tests. */
 120version(Windows) private bool isSeparator(dchar c)  @safe pure nothrow
 121{
 122    return isDirSeparator(c) || isDriveSeparator(c);
 123}
 124version(Posix) private alias isDirSeparator isSeparator;
 125
 126
 127/*  Helper function that determines the position of the last
 128    drive/directory separator in a string.  Returns -1 if none
 129    is found.
 130*/
 131private ptrdiff_t lastSeparator(C)(in C[] path)  @safe pure nothrow
 132    if (isSomeChar!C)
 133{
 134    auto i = (cast(ptrdiff_t) path.length) - 1;
 135    while (i >= 0 && !isSeparator(path[i])) --i;
 136    return i;
 137}
 138
 139
 140version (Windows)
 141{
 142    private bool isUNC(C)(in C[] path) @safe pure nothrow  if (isSomeChar!C)
 143    {
 144        return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1])
 145            && !isDirSeparator(path[2]);
 146    }
 147
 148    private ptrdiff_t uncRootLength(C)(in C[] path) @safe pure nothrow  if (isSomeChar!C)
 149        in { assert (isUNC(path)); }
 150        body
 151    {
 152        ptrdiff_t i = 3;
 153        while (i < path.length && !isDirSeparator(path[i])) ++i;
 154        if (i < path.length)
 155        {
 156            auto j = i;
 157            do { ++j; } while (j < path.length && isDirSeparator(path[j]));
 158            if (j < path.length)
 159            {
 160                do { ++j; } while (j < path.length && !isDirSeparator(path[j]));
 161                i = j;
 162            }
 163        }
 164        return i;
 165    }
 166
 167    private bool hasDrive(C)(in C[] path)  @safe pure nothrow  if (isSomeChar!C)
 168    {
 169        return path.length >= 2 && isDriveSeparator(path[1]);
 170    }
 171
 172    private bool isDriveRoot(C)(in C[] path)  @safe pure nothrow  if (isSomeChar!C)
 173    {
 174        return path.length >= 3 && isDriveSeparator(path[1])
 175            && isDirSeparator(path[2]);
 176    }
 177}
 178
 179
 180/*  Helper functions that strip leading/trailing slashes and backslashes
 181    from a path.
 182*/
 183private inout(C)[] ltrimDirSeparators(C)(inout(C)[] path)  @safe pure nothrow
 184    if (isSomeChar!C)
 185{
 186    int i = 0;
 187    while (i < path.length && isDirSeparator(path[i])) ++i;
 188    return path[i .. $];
 189}
 190
 191private inout(C)[] rtrimDirSeparators(C)(inout(C)[] path)  @safe pure nothrow
 192    if (isSomeChar!C)
 193{
 194    auto i = (cast(ptrdiff_t) path.length) - 1;
 195    while (i >= 0 && isDirSeparator(path[i])) --i;
 196    return path[0 .. i+1];
 197}
 198
 199private inout(C)[] trimDirSeparators(C)(inout(C)[] path)  @safe pure nothrow
 200    if (isSomeChar!C)
 201{
 202    return ltrimDirSeparators(rtrimDirSeparators(path));
 203}
 204
 205
 206
 207
 208/** This $(D enum) is used as a template argument to functions which
 209    compare file names, and determines whether the comparison is
 210    case sensitive or not.
 211*/
 212enum CaseSensitive : bool
 213{
 214    /// File names are case insensitive
 215    no = false,
 216
 217    /// File names are case sensitive
 218    yes = true,
 219
 220    /** The default (or most common) setting for the current platform.
 221        That is, $(D no) on Windows and Mac OS X, and $(D yes) on all
 222        POSIX systems except OS X (Linux, *BSD, etc.).
 223    */
 224    osDefault = osDefaultCaseSensitivity
 225}
 226version (Windows)    private enum osDefaultCaseSensitivity = false;
 227else version (OSX)   private enum osDefaultCaseSensitivity = false;
 228else version (Posix) private enum osDefaultCaseSensitivity = true;
 229else static assert (0);
 230
 231
 232
 233
 234/** Returns the name of a file, without any leading directory
 235    and with an optional suffix chopped off.
 236
 237    If $(D suffix) is specified, it will be compared to $(D path)
 238    using $(D filenameCmp!cs),
 239    where $(D cs) is an optional template parameter determining whether
 240    the comparison is case sensitive or not.  See the
 241    $(LREF filenameCmp) documentation for details.
 242
 243    Examples:
 244    ---
 245    assert (baseName("dir/file.ext")         == "file.ext");
 246    assert (baseName("dir/file.ext", ".ext") == "file");
 247    assert (baseName("dir/file.ext", ".xyz") == "file.ext");
 248    assert (baseName("dir/filename", "name") == "file");
 249    assert (baseName("dir/subdir/")          == "subdir");
 250
 251    version (Windows)
 252    {
 253        assert (baseName(`d:file.ext`)      == "file.ext");
 254        assert (baseName(`d:\dir\file.ext`) == "file.ext");
 255    }
 256    ---
 257
 258    Note:
 259    This function $(I only) strips away the specified suffix, which
 260    doesn't necessarily have to represent an extension.  If you want
 261    to remove the extension from a path, regardless of what the extension
 262    is, use $(LREF stripExtension).
 263    If you want the filename without leading directories and without
 264    an extension, combine the functions like this:
 265    ---
 266    assert (baseName(stripExtension("dir/file.ext")) == "file");
 267    ---
 268
 269    Standards:
 270    This function complies with
 271    $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html,
 272    the POSIX requirements for the 'basename' shell utility)
 273    (with suitable adaptations for Windows paths).
 274*/
 275inout(C)[] baseName(C)(inout(C)[] path)
 276    @trusted pure //TODO: nothrow (BUG 5700)
 277    if (isSomeChar!C)
 278{
 279    auto p1 = stripDrive(path);
 280    if (p1.empty)
 281    {
 282        version (Windows) if (isUNC(path))
 283        {
 284            return cast(typeof(return)) dirSeparator.dup;
 285        }
 286        return null;
 287    }
 288
 289    auto p2 = rtrimDirSeparators(p1);
 290    if (p2.empty) return p1[0 .. 1];
 291
 292    return p2[lastSeparator(p2)+1 .. $];
 293}
 294
 295/// ditto
 296inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1)
 297    (inout(C)[] path, in C1[] suffix)
 298    @safe pure //TODO: nothrow (because of filenameCmp())
 299    if (isSomeChar!C && isSomeChar!C1)
 300{
 301    auto p = baseName(path);
 302    if (p.length > suffix.length
 303        && filenameCmp!cs(p[$-suffix.length .. $], suffix) == 0)
 304    {
 305        return p[0 .. $-suffix.length];
 306    }
 307    else return p;
 308}
 309
 310
 311unittest
 312{
 313    assert (baseName("").empty);
 314    assert (baseName("file.ext"w) == "file.ext");
 315    assert (baseName("file.ext"d, ".ext") == "file");
 316    assert (baseName("file", "file"w.dup) == "file");
 317    assert (baseName("dir/file.ext"d.dup) == "file.ext");
 318    assert (baseName("dir/file.ext", ".ext"d) == "file");
 319    assert (baseName("dir/file"w, "file"d) == "file");
 320    assert (baseName("dir///subdir////") == "subdir");
 321    assert (baseName("dir/subdir.ext/", ".ext") == "subdir");
 322    assert (baseName("dir/subdir/".dup, "subdir") == "subdir");
 323    assert (baseName("/"w.dup) == "/");
 324    assert (baseName("//"d.dup) == "/");
 325    assert (baseName("///") == "/");
 326
 327    assert (baseName!(CaseSensitive.yes)("file.ext", ".EXT") == "file.ext");
 328    assert (baseName!(CaseSensitive.no)("file.ext", ".EXT") == "file");
 329
 330    version (Windows)
 331    {
 332        assert (baseName(`dir\file.ext`) == `file.ext`);
 333        assert (baseName(`dir\file.ext`, `.ext`) == `file`);
 334        assert (baseName(`dir\file`, `file`) == `file`);
 335        assert (baseName(`d:file.ext`) == `file.ext`);
 336        assert (baseName(`d:file.ext`, `.ext`) == `file`);
 337        assert (baseName(`d:file`, `file`) == `file`);
 338        assert (baseName(`dir\\subdir\\\`) == `subdir`);
 339        assert (baseName(`dir\subdir.ext\`, `.ext`) == `subdir`);
 340        assert (baseName(`dir\subdir\`, `subdir`) == `subdir`);
 341        assert (baseName(`\`) == `\`);
 342        assert (baseName(`\\`) == `\`);
 343        assert (baseName(`\\\`) == `\`);
 344        assert (baseName(`d:\`) == `\`);
 345        assert (baseName(`d:`).empty);
 346        assert (baseName(`\\server\share\file`) == `file`);
 347        assert (baseName(`\\server\share\`) == `\`);
 348        assert (baseName(`\\server\share`) == `\`);
 349    }
 350
 351    assert (baseName(stripExtension("dir/file.ext")) == "file");
 352
 353    static assert (baseName("dir/file.ext") == "file.ext");
 354    static assert (baseName("dir/file.ext", ".ext") == "file");
 355}
 356
 357
 358
 359
 360/** Returns the directory part of a path.  On Windows, this
 361    includes the drive letter if present.
 362
 363    This function performs a memory allocation if and only if $(D path)
 364    is mutable and does not have a directory (in which case a new mutable
 365    string is needed to hold the returned current-directory symbol,
 366    $(D ".")).
 367
 368    Examples:
 369    ---
 370    assert (dirName("file")        == ".");
 371    assert (dirName("dir/file")    == "dir");
 372    assert (dirName("/file")       == "/");
 373    assert (dirName("dir/subdir/") == "dir");
 374
 375    version (Windows)
 376    {
 377        assert (dirName("d:file")      == "d:");
 378        assert (dirName(`d:\dir\file`) == `d:\dir`);
 379        assert (dirName(`d:\file`)     == `d:\`);
 380        assert (dirName(`dir\subdir\`) == `dir`);
 381    }
 382    ---
 383
 384    Standards:
 385    This function complies with
 386    $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html,
 387    the POSIX requirements for the 'dirname' shell utility)
 388    (with suitable adaptations for Windows paths).
 389*/
 390C[] dirName(C)(C[] path)
 391    //TODO: @safe (BUG 6169) pure nothrow (because of to())
 392    if (isSomeChar!C)
 393{
 394    if (path.empty) return to!(typeof(return))(".");
 395
 396    auto p = rtrimDirSeparators(path);
 397    if (p.empty) return path[0 .. 1];
 398
 399    version (Windows)
 400    {
 401        if (isUNC(p) && uncRootLength(p) == p.length)
 402            return p;
 403        if (p.length == 2 && isDriveSeparator(p[1]) && path.length > 2)
 404            return path[0 .. 3];
 405    }
 406
 407    auto i = lastSeparator(p);
 408    if (i == -1) return to!(typeof(return))(".");
 409    if (i == 0) return p[0 .. 1];
 410
 411    version (Windows)
 412    {
 413        // If the directory part is either d: or d:\, don't
 414        // chop off the last symbol.
 415        if (isDriveSeparator(p[i]) || isDriveSeparator(p[i-1]))
 416            return p[0 .. i+1];
 417    }
 418
 419    // Remove any remaining trailing (back)slashes.
 420    return rtrimDirSeparators(p[0 .. i]);
 421}
 422
 423
 424unittest
 425{
 426    assert (dirName("") == ".");
 427    assert (dirName("file"w) == ".");
 428    assert (dirName("dir/"d) == ".");
 429    assert (dirName("dir///") == ".");
 430    assert (dirName("dir/file"w.dup) == "dir");
 431    assert (dirName("dir///file"d.dup) == "dir");
 432    assert (dirName("dir/subdir/") == "dir");
 433    assert (dirName("/dir/file"w) == "/dir");
 434    assert (dirName("/file"d) == "/");
 435    assert (dirName("/") == "/");
 436    assert (dirName("///") == "/");
 437
 438    version (Windows)
 439    {
 440        assert (dirName(`dir\`) == `.`);
 441        assert (dirName(`dir\\\`) == `.`);
 442        assert (dirName(`dir\file`) == `dir`);
 443        assert (dirName(`dir\\\file`) == `dir`);
 444        assert (dirName(`dir\subdir\`) == `dir`);
 445        assert (dirName(`\dir\file`) == `\dir`);
 446        assert (dirName(`\file`) == `\`);
 447        assert (dirName(`\`) == `\`);
 448        assert (dirName(`\\\`) == `\`);
 449        assert (dirName(`d:`) == `d:`);
 450        assert (dirName(`d:file`) == `d:`);
 451        assert (dirName(`d:\`) == `d:\`);
 452        assert (dirName(`d:\file`) == `d:\`);
 453        assert (dirName(`d:\dir\file`) == `d:\dir`);
 454        assert (dirName(`\\server\share\dir\file`) == `\\server\share\dir`);
 455        assert (dirName(`\\server\share\file`) == `\\server\share`);
 456        assert (dirName(`\\server\share\`) == `\\server\share`);
 457        assert (dirName(`\\server\share`) == `\\server\share`);
 458    }
 459
 460    static assert (dirName("dir/file") == "dir");
 461}
 462
 463
 464
 465
 466/** Returns the root directory of the specified path, or $(D null) if the
 467    path is not rooted.
 468
 469    Examples:
 470    ---
 471    assert (rootName("foo") is null);
 472    assert (rootName("/foo") == "/");
 473
 474    version (Windows)
 475    {
 476        assert (rootName(`\foo`) == `\`);
 477        assert (rootName(`c:\foo`) == `c:\`);
 478        assert (rootName(`\\server\share\foo`) == `\\server\share`);
 479    }
 480    ---
 481*/
 482inout(C)[] rootName(C)(inout(C)[] path)  @safe pure nothrow  if (isSomeChar!C)
 483{
 484    if (path.empty) return null;
 485
 486    version (Posix)
 487    {
 488        if (isDirSeparator(path[0])) return path[0 .. 1];
 489    }
 490    else version (Windows)
 491    {
 492        if (isDirSeparator(path[0]))
 493        {
 494            if (isUNC(path)) return path[0 .. uncRootLength(path)];
 495            else return path[0 .. 1];
 496        }
 497        else if (path.length >= 3 && isDriveSeparator(path[1]) &&
 498            isDirSeparator(path[2]))
 499        {
 500            return path[0 .. 3];
 501        }
 502    }
 503    else static assert (0, "unsupported platform");
 504
 505    assert (!isRooted(path));
 506    return null;
 507}
 508
 509
 510unittest
 511{
 512    assert (rootName("") is null);
 513    assert (rootName("foo") is null);
 514    assert (rootName("/") == "/");
 515    assert (rootName("/foo/bar") == "/");
 516
 517    version (Windows)
 518    {
 519        assert (rootName("d:foo") is null);
 520        assert (rootName(`d:\foo`) == `d:\`);
 521        assert (rootName(`\\server\share\foo`) == `\\server\share`);
 522        assert (rootName(`\\server\share`) == `\\server\share`);
 523    }
 524}
 525
 526
 527
 528
 529/** Returns the drive of a path, or $(D null) if the drive
 530    is not specified.  In the case of UNC paths, the network share
 531    is returned.
 532
 533    Always returns $(D null) on POSIX.
 534
 535    Examples:
 536    ---
 537    version (Windows)
 538    {
 539        assert (driveName(`d:\file`) == "d:");
 540        assert (driveName(`\\server\share\file`) == `\\server\share`);
 541        assert (driveName(`dir\file`).empty);
 542    }
 543    ---
 544*/
 545inout(C)[] driveName(C)(inout(C)[] path)  @safe pure nothrow
 546    if (isSomeChar!C)
 547{
 548    version (Windows)
 549    {
 550        if (hasDrive(path))
 551            return path[0 .. 2];
 552        else if (isUNC(path))
 553            return path[0 .. uncRootLength(path)];
 554    }
 555    return null;
 556}
 557
 558
 559unittest
 560{
 561    version (Posix)  assert (driveName("c:/foo").empty);
 562    version (Windows)
 563    {
 564        assert (driveName(`dir\file`).empty);
 565        assert (driveName(`d:file`) == "d:");
 566        assert (driveName(`d:\file`) == "d:");
 567        assert (driveName("d:") == "d:");
 568        assert (driveName(`\\server\share\file`) == `\\server\share`);
 569        assert (driveName(`\\server\share\`) == `\\server\share`);
 570        assert (driveName(`\\server\share`) == `\\server\share`);
 571
 572        static assert (driveName(`d:\file`) == "d:");
 573    }
 574}
 575
 576
 577
 578
 579/** Strips the drive from a Windows path.  On POSIX, the path is returned
 580    unaltered.
 581
 582    Example:
 583    ---
 584    version (Windows)
 585    {
 586        assert (stripDrive(`d:\dir\file`) == `\dir\file`);
 587        assert (stripDrive(`\\server\share\dir\file`) == `\dir\file`);
 588    }
 589    ---
 590*/
 591inout(C)[] stripDrive(C)(inout(C)[] path)  @safe pure nothrow  if (isSomeChar!C)
 592{
 593    version(Windows)
 594    {
 595        if (hasDrive(path))      return path[2 .. $];
 596        else if (isUNC(path))    return path[uncRootLength(path) .. $];
 597    }
 598    return path;
 599}
 600
 601
 602unittest
 603{
 604    version(Windows)
 605    {
 606        assert (stripDrive(`d:\dir\file`) == `\dir\file`);
 607        assert (stripDrive(`\\server\share\dir\file`) == `\dir\file`);
 608        static assert (stripDrive(`d:\dir\file`) == `\dir\file`);
 609    }
 610    version(Posix)
 611    {
 612        assert (stripDrive(`d:\dir\file`) == `d:\dir\file`);
 613    }
 614}
 615
 616
 617
 618
 619/*  Helper function that returns the position of the filename/extension
 620    separator dot in path.  If not found, returns -1.
 621*/
 622private ptrdiff_t extSeparatorPos(C)(in C[] path)  @safe pure nothrow
 623    if (isSomeChar!C)
 624{
 625    auto i = (cast(ptrdiff_t) path.length) - 1;
 626    while (i >= 0 && !isSeparator(path[i]))
 627    {
 628        if (path[i] == '.' && i > 0 && !isSeparator(path[i-1])) return i;
 629        --i;
 630    }
 631    return -1;
 632}
 633
 634
 635
 636
 637/** Returns the _extension part of a file name, including the dot.
 638
 639    If there is no _extension, $(D null) is returned.
 640
 641    Examples:
 642    ---
 643    assert (extension("file").empty);
 644    assert (extension("file.ext")       == ".ext");
 645    assert (extension("file.ext1.ext2") == ".ext2");
 646    assert (extension("file.")          == ".");
 647    assert (extension(".file").empty);
 648    assert (extension(".file.ext")      == ".ext");
 649    ---
 650*/
 651inout(C)[] extension(C)(inout(C)[] path)  @safe pure nothrow  if (isSomeChar!C)
 652{
 653    auto i = extSeparatorPos(path);
 654    if (i == -1) return null;
 655    else return path[i .. $];
 656}
 657
 658
 659unittest
 660{
 661    assert (extension("file").empty);
 662    assert (extension("file.") == ".");
 663    assert (extension("file.ext"w) == ".ext");
 664    assert (extension("file.ext1.ext2"d) == ".ext2");
 665    assert (extension(".foo".dup).empty);
 666    assert (extension(".foo.ext"w.dup) == ".ext");
 667
 668    assert (extension("dir/file"d.dup).empty);
 669    assert (extension("dir/file.") == ".");
 670    assert (extension("dir/file.ext") == ".ext");
 671    assert (extension("dir/file.ext1.ext2"w) == ".ext2");
 672    assert (extension("dir/.foo"d).empty);
 673    assert (extension("dir/.foo.ext".dup) == ".ext");
 674
 675    version(Windows)
 676    {
 677        assert (extension(`dir\file`).empty);
 678        assert (extension(`dir\file.`) == ".");
 679        assert (extension(`dir\file.ext`) == `.ext`);
 680        assert (extension(`dir\file.ext1.ext2`) == `.ext2`);
 681        assert (extension(`dir\.foo`).empty);
 682        assert (extension(`dir\.foo.ext`) == `.ext`);
 683
 684        assert (extension(`d:file`).empty);
 685        assert (extension(`d:file.`) == ".");
 686        assert (extension(`d:file.ext`) == `.ext`);
 687        assert (extension(`d:file.ext1.ext2`) == `.ext2`);
 688        assert (extension(`d:.foo`).empty);
 689        assert (extension(`d:.foo.ext`) == `.ext`);
 690    }
 691
 692    static assert (extension("file").empty);
 693    static assert (extension("file.ext") == ".ext");
 694}
 695
 696
 697
 698
 699/** Returns the path with the extension stripped off.
 700
 701    Examples:
 702    ---
 703    assert (stripExtension("file")           == "file");
 704    assert (stripExtension("file.ext")       == "file");
 705    assert (stripExtension("file.ext1.ext2") == "file.ext1");
 706    assert (stripExtension("file.")          == "file");
 707    assert (stripExtension(".file")          == ".file");
 708    assert (stripExtension(".file.ext")      == ".file");
 709    assert (stripExtension("dir/file.ext")   == "dir/file");
 710    ---
 711*/
 712inout(C)[] stripExtension(C)(inout(C)[] path)  @safe pure nothrow
 713    if (isSomeChar!C)
 714{
 715    auto i = extSeparatorPos(path);
 716    if (i == -1) return path;
 717    else return path[0 .. i];
 718}
 719
 720
 721unittest
 722{
 723    assert (stripExtension("file") == "file");
 724    assert (stripExtension("file.ext"w) == "file");
 725    assert (stripExtension("file.ext1.ext2"d) == "file.ext1");
 726    assert (stripExtension(".foo".dup) == ".foo");
 727    assert (stripExtension(".foo.ext"w.dup) == ".foo");
 728
 729    assert (stripExtension("dir/file"d.dup) == "dir/file");
 730    assert (stripExtension("dir/file.ext") == "dir/file");
 731    assert (stripExtension("dir/file.ext1.ext2"w) == "dir/file.ext1");
 732    assert (stripExtension("dir/.foo"d) == "dir/.foo");
 733    assert (stripExtension("dir/.foo.ext".dup) == "dir/.foo");
 734
 735    version(Windows)
 736    {
 737    assert (stripExtension("dir\\file") == "dir\\file");
 738    assert (stripExtension("dir\\file.ext") == "dir\\file");
 739    assert (stripExtension("dir\\file.ext1.ext2") == "dir\\file.ext1");
 740    assert (stripExtension("dir\\.foo") == "dir\\.foo");
 741    assert (stripExtension("dir\\.foo.ext") == "dir\\.foo");
 742
 743    assert (stripExtension("d:file") == "d:file");
 744    assert (stripExtension("d:file.ext") == "d:file");
 745    assert (stripExtension("d:file.ext1.ext2") == "d:file.ext1");
 746    assert (stripExtension("d:.foo") == "d:.foo");
 747    assert (stripExtension("d:.foo.ext") == "d:.foo");
 748    }
 749
 750    static assert (stripExtension("file") == "file");
 751    static assert (stripExtension("file.ext"w) == "file");
 752}
 753
 754
 755
 756
 757/** Returns a string containing the _path given by $(D path), but where
 758    the extension has been set to $(D ext).
 759
 760    If the filename already has an extension, it is replaced.   If not, the
 761    extension is simply appended to the filename.  Including a leading dot
 762    in $(D ext) is optional.
 763
 764    If the extension is empty, this function is equivalent to
 765    $(LREF stripExtension).
 766
 767    This function normally allocates a new string (the possible exception
 768    being the case when path is immutable and doesn't already have an
 769    extension).
 770
 771    Examples:
 772    ---
 773    assert (setExtension("file", "ext")      == "file.ext");
 774    assert (setExtension("file", ".ext")     == "file.ext");
 775    assert (setExtension("file.old", "")     == "file");
 776    assert (setExtension("file.old", "new")  == "file.new");
 777    assert (setExtension("file.old", ".new") == "file.new");
 778    ---
 779*/
 780immutable(Unqual!C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext)
 781    @trusted pure nothrow
 782    if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2))
 783{
 784    if (ext.length > 0 && ext[0] != '.')
 785        return cast(typeof(return))(stripExtension(path)~'.'~ext);
 786    else
 787        return cast(typeof(return))(stripExtension(path)~ext);
 788}
 789
 790///ditto
 791immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext)
 792    @trusted pure nothrow
 793    if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
 794{
 795    if (ext.length == 0)
 796        return stripExtension(path);
 797
 798    // Optimised for the case where path is immutable and has no extension
 799    if (ext.length > 0 && ext[0] == '.') ext = ext[1 .. $];
 800    auto i = extSeparatorPos(path);
 801    if (i == -1)
 802    {
 803        path ~= '.';
 804        path ~= ext;
 805        return path;
 806    }
 807    else if (i == path.length - 1)
 808    {
 809        path ~= ext;
 810        return path;
 811    }
 812    else
 813    {
 814        return cast(typeof(return))(path[0 .. i+1] ~ ext);
 815    }
 816}
 817
 818
 819unittest
 820{
 821    assert (setExtension("file", "ext") == "file.ext");
 822    assert (setExtension("file"w, ".ext"w) == "file.ext");
 823    assert (setExtension("file."d, "ext"d) == "file.ext");
 824    assert (setExtension("file.", ".ext") == "file.ext");
 825    assert (setExtension("file.old"w, "new"w) == "file.new");
 826    assert (setExtension("file.old"d, ".new"d) == "file.new");
 827
 828    assert (setExtension("file"w.dup, "ext"w) == "file.ext");
 829    assert (setExtension("file"w.dup, ".ext"w) == "file.ext");
 830    assert (setExtension("file."w, "ext"w.dup) == "file.ext");
 831    assert (setExtension("file."w, ".ext"w.dup) == "file.ext");
 832    assert (setExtension("file.old"d.dup, "new"d) == "file.new");
 833    assert (setExtension("file.old"d.dup, ".new"d) == "file.new");
 834
 835    static assert (setExtension("file", "ext") == "file.ext");
 836    static assert (setExtension("file.old", "new") == "file.new");
 837
 838    static assert (setExtension("file"w.dup, "ext"w) == "file.ext");
 839    static assert (setExtension("file.old"d.dup, "new"d) == "file.new");
 840
 841    // Issue 10601
 842    assert (setExtension("file", "") == "file");
 843    assert (setExtension("file.ext", "") == "file");
 844}
 845
 846
 847
 848/** Returns the _path given by $(D path), with the extension given by
 849    $(D ext) appended if the path doesn't already have one.
 850
 851    Including the dot in the extension is optional.
 852
 853    This function always allocates a new string, except in the case when
 854    path is immutable and already has an extension.
 855
 856    Examples:
 857    ---
 858    assert (defaultExtension("file", "ext")      == "file.ext");
 859    assert (defaultExtension("file", ".ext")     == "file.ext");
 860    assert (defaultExtension("file.", "ext")     == "file.");
 861    assert (defaultExtension("file.old", "new")  == "file.old");
 862    assert (defaultExtension("file.old", ".new") == "file.old");
 863    ---
 864*/
 865immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext)
 866    @trusted pure // TODO: nothrow (because of to())
 867    if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
 868{
 869    auto i = extSeparatorPos(path);
 870    if (i == -1)
 871    {
 872        if (ext.length > 0 && ext[0] == '.')
 873            return cast(typeof(return))(path~ext);
 874        else
 875            return cast(typeof(return))(path~'.'~ext);
 876    }
 877    else return to!(typeof(return))(path);
 878}
 879
 880
 881unittest
 882{
 883    assert (defaultExtension("file", "ext") == "file.ext");
 884    assert (defaultExtension("file", ".ext") == "file.ext");
 885    assert (defaultExtension("file.", "ext")     == "file.");
 886    assert (defaultExtension("file.old", "new") == "file.old");
 887    assert (defaultExtension("file.old", ".new") == "file.old");
 888
 889    assert (defaultExtension("file"w.dup, "ext"w) == "file.ext");
 890    assert (defaultExtension("file.old"d.dup, "new"d) == "file.old");
 891
 892    static assert (defaultExtension("file", "ext") == "file.ext");
 893    static assert (defaultExtension("file.old", "new") == "file.old");
 894
 895    static assert (defaultExtension("file"w.dup, "ext"w) == "file.ext");
 896    static assert (defaultExtension("file.old"d.dup, "new"d) == "file.old");
 897}
 898
 899
 900/** Combines one or more path segments.
 901
 902    This function takes a set of path segments, given as an input
 903    range of string elements or as a set of string arguments,
 904    and concatenates them with each other.  Directory separators
 905    are inserted between segments if necessary.  If any of the
 906    path segments are absolute (as defined by $(LREF isAbsolute)), the
 907    preceding segments will be dropped.
 908
 909    On Windows, if one of the path segments are rooted, but not absolute
 910    (e.g. $(D `\foo`)), all preceding path segments down to the previous
 911    root will be dropped.  (See below for an example.)
 912
 913    This function always allocates memory to hold the resulting path.
 914    The variadic overload is guaranteed to only perform a single
 915    allocation, as is the range version if $(D paths) is a forward
 916    range.
 917*/
 918immutable(ElementEncodingType!(ElementType!Range))[]
 919    buildPath(Range)(Range segments)
 920        if (isInputRange!Range && isSomeString!(ElementType!Range))
 921{
 922    if (segments.empty) return null;
 923
 924    // If this is a forward range, we can pre-calculate a maximum length.
 925    static if (isForwardRange!Range)
 926    {
 927        auto segments2 = segments.save;
 928        size_t precalc = 0;
 929        foreach (segment; segments2) precalc += segment.length + 1;
 930    }
 931    // Otherwise, just venture a guess and resize later if necessary.
 932    else size_t precalc = 255;
 933
 934    auto buf = new Unqual!(ElementEncodingType!(ElementType!Range))[](precalc);
 935    size_t pos = 0;
 936    foreach (segment; segments)
 937    {
 938        if (segment.empty) continue;
 939        static if (!isForwardRange!Range)
 940        {
 941            immutable neededLength = pos + segment.length + 1;
 942            if (buf.length < neededLength)
 943                buf.length = reserve(buf, neededLength + buf.length/2);
 944        }
 945        if (pos > 0)
 946        {
 947            if (isRooted(segment))
 948            {
 949                version (Posix)
 950                {
 951                    pos = 0;
 952                }
 953                else version (Windows)
 954                {
 955                    if (isAbsolute(segment))
 956                        pos = 0;
 957                    else
 958                    {
 959                        pos = rootName(buf[0 .. pos]).length;
 960                        if (pos > 0 && isDirSeparator(buf[pos-1])) --pos;
 961                    }
 962                }
 963            }
 964            else if (!isDirSeparator(buf[pos-1]))
 965                buf[pos++] = dirSeparator[0];
 966        }
 967        buf[pos .. pos + segment.length] = segment[];
 968        pos += segment.length;
 969    }
 970    static U trustedCast(U, V)(V v) @trusted pure nothrow { return cast(U) v; }
 971    return trustedCast!(typeof(return))(buf[0 .. pos]);
 972}
 973
 974/// ditto
 975immutable(C)[] buildPath(C)(const(C[])[] paths...)
 976    @safe pure nothrow
 977    if (isSomeChar!C)
 978{
 979    return buildPath!(typeof(paths))(paths);
 980}
 981
 982///
 983unittest
 984{
 985    version (Posix)
 986    {
 987        assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
 988        assert (buildPath("/foo/", "bar/baz")  == "/foo/bar/baz");
 989        assert (buildPath("/foo", "/bar")      == "/bar");
 990    }
 991
 992    version (Windows)
 993    {
 994        assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
 995        assert (buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
 996        assert (buildPath("foo", `d:\bar`)     == `d:\bar`);
 997        assert (buildPath("foo", `\bar`)       == `\bar`);
 998        assert (buildPath(`c:\foo`, `\bar`)    == `c:\bar`);
 999    }
1000}
1001
1002unittest // non-documented
1003{
1004    // ir() wraps an array in a plain (i.e. non-forward) input range, so that
1005    // we can test both code paths
1006    InputRange!(C[]) ir(C)(C[][] p...) { return inputRangeObject(p); }
1007    version (Posix)
1008    {
1009        assert (buildPath("foo") == "foo");
1010        assert (buildPath("/foo/") == "/foo/");
1011        assert (buildPath("foo", "bar") == "foo/bar");
1012        assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
1013        assert (buildPath("foo/".dup, "bar") == "foo/bar");
1014        assert (buildPath("foo///", "bar".dup) == "foo///bar");
1015        assert (buildPath("/foo"w, "bar"w) == "/foo/bar");
1016        assert (buildPath("foo"w.dup, "/bar"w) == "/bar");
1017        assert (buildPath("foo"w, "bar/"w.dup) == "foo/bar/");
1018        assert (buildPath("/"d, "foo"d) == "/foo");
1019        assert (buildPath(""d.dup, "foo"d) == "foo");
1020        assert (buildPath("foo"d, ""d.dup) == "foo");
1021        assert (buildPath("foo", "bar".dup, "baz") == "foo/bar/baz");
1022        assert (buildPath("foo"w, "/bar"w, "baz"w.dup) == "/bar/baz");
1023
1024        static assert (buildPath("foo", "bar", "baz") == "foo/bar/baz");
1025        static assert (buildPath("foo", "/bar", "baz") == "/bar/baz");
1026
1027        // The following are mostly duplicates of the above, except that the
1028        // range version does not accept mixed constness.
1029        assert (buildPath(ir("foo")) == "foo");
1030        assert (buildPath(ir("/foo/")) == "/foo/");
1031        assert (buildPath(ir("foo", "bar")) == "foo/bar");
1032        assert (buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1033        assert (buildPath(ir("foo/".dup, "bar".dup)) == "foo/bar");
1034        assert (buildPath(ir("foo///".dup, "bar".dup)) == "foo///bar");
1035        assert (buildPath(ir("/foo"w, "bar"w)) == "/foo/bar");
1036        assert (buildPath(ir("foo"w.dup, "/bar"w.dup)) == "/bar");
1037        assert (buildPath(ir("foo"w.dup, "bar/"w.dup)) == "foo/bar/");
1038        assert (buildPath(ir("/"d, "foo"d)) == "/foo");
1039        assert (buildPath(ir(""d.dup, "foo"d.dup)) == "foo");
1040        assert (buildPath(ir("foo"d, ""d)) == "foo");
1041        assert (buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1042        assert (buildPath(ir("foo"w.dup, "/bar"w.dup, "baz"w.dup)) == "/bar/baz");
1043    }
1044    version (Windows)
1045    {
1046        assert (buildPath("foo") == "foo");
1047        assert (buildPath(`\foo/`) == `\foo/`);
1048        assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1049        assert (buildPath("foo", `\bar`) == `\bar`);
1050        assert (buildPath(`c:\foo`, "bar") == `c:\foo\bar`);
1051        assert (buildPath("foo"w, `d:\bar`w.dup) ==  `d:\bar`);
1052        assert (buildPath(`c:\foo\bar`, `\baz`) == `c:\baz`);
1053        assert (buildPath(`\\foo\bar\baz`d, `foo`d, `\bar`d) == `\\foo\bar\bar`d);
1054
1055        static assert (buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1056        static assert (buildPath("foo", `c:\bar`, "baz") == `c:\bar\baz`);
1057
1058        assert (buildPath(ir("foo")) == "foo");
1059        assert (buildPath(ir(`\foo/`)) == `\foo/`);
1060        assert (buildPath(ir("foo", "bar", "baz")) == `foo\bar\baz`);
1061        assert (buildPath(ir("foo", `\bar`)) == `\bar`);
1062        assert (buildPath(ir(`c:\foo`, "bar")) == `c:\foo\bar`);
1063        assert (buildPath(ir("foo"w.dup, `d:\bar`w.dup)) ==  `d:\bar`);
1064        assert (buildPath(ir(`c:\foo\bar`, `\baz`)) == `c:\baz`);
1065        assert (buildPath(ir(`\\foo\bar\baz`d, `foo`d, `\bar`d)) == `\\foo\bar\bar`d);
1066    }
1067
1068    // Test that allocation works as it should.
1069    auto manyShort = "aaa".repeat(1000).array();
1070    auto manyShortCombined = join(manyShort, dirSeparator);
1071    assert (buildPath(manyShort) == manyShortCombined);
1072    assert (buildPath(ir(manyShort)) == manyShortCombined);
1073
1074    auto fewLong = 'b'.repeat(500).array().repeat(10).array();
1075    auto fewLongCombined = join(fewLong, dirSeparator);
1076    assert (buildPath(fewLong) == fewLongCombined);
1077    assert (buildPath(ir(fewLong)) == fewLongCombined);
1078}
1079
1080unittest
1081{
1082    // Test for issue 7397
1083    string[] ary = ["a", "b"];
1084    version (Posix)
1085    {
1086        assert (buildPath(ary) == "a/b");
1087    }
1088    else version (Windows)
1089    {
1090        assert (buildPath(ary) == `a\b`);
1091    }
1092}
1093
1094
1095/** Performs the same task as $(LREF buildPath),
1096    while at the same time resolving current/parent directory
1097    symbols ($(D ".") and $(D "..")) and removing superfluous
1098    directory separators.
1099    On Windows, slashes are replaced with backslashes.
1100
1101    Note that this function does not resolve symbolic links.
1102
1103    This function always allocates memory to hold the resulting path.
1104
1105    Examples:
1106    ---
1107    version (Posix)
1108    {
1109        assert (buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz");
1110        assert (buildNormalizedPath("../foo/.") == "../foo");
1111        assert (buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
1112        assert (buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
1113        assert (buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
1114        assert (buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
1115    }
1116
1117    version (Windows)
1118    {
1119        assert (buildNormalizedPath(`c:\foo\.\bar/..\\baz\`) == `c:\foo\baz`);
1120        assert (buildNormalizedPath(`..\foo\.`) == `..\foo`);
1121        assert (buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
1122        assert (buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
1123        assert (buildNormalizedPath(`\\server\share\foo`, `..\bar`) == `\\server\share\bar`);
1124    }
1125    ---
1126*/
1127immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...)
1128    @trusted pure nothrow
1129    if (isSomeChar!C)
1130{
1131    import std.c.stdlib;
1132    auto paths2 = new const(C)[][](paths.length);
1133        //(cast(const(C)[]*)alloca((const(C)[]).sizeof * paths.length))[0 .. paths.length];
1134
1135    // Check whether the resulting path will be absolute or rooted,
1136    // calculate its maximum length, and discard segments we won't use.
1137    typeof(paths[0][0])[] rootElement;
1138    int numPaths = 0;
1139    bool seenAbsolute;
1140    size_t segmentLengthSum = 0;
1141    foreach (i; 0 .. paths.length)
1142    {
1143        auto p = paths[i];
1144        if (p.empty) continue;
1145        else if (isRooted(p))
1146        {
1147            immutable thisIsAbsolute = isAbsolute(p);
1148            if (thisIsAbsolute || !seenAbsolute)
1149            {
1150                if (thisIsAbsolute) seenAbsolute = true;
1151                rootElement = rootName(p);
1152                paths2[0] = p[rootElement.length .. $];
1153                numPaths = 1;
1154                segmentLengthSum = paths2[0].length;
1155            }
1156            else
1157            {
1158                paths2[0] = p;
1159                numPaths = 1;
1160                segmentLengthSum = p.length;
1161            }
1162        }
1163        else
1164        {
1165            paths2[numPaths++] = p;
1166            segmentLengthSum += p.length;
1167        }
1168    }
1169    if (rootElement.length + segmentLengthSum == 0) return null;
1170    paths2 = paths2[0 .. numPaths];
1171    immutable rooted = !rootElement.empty;
1172    assert (rooted || !seenAbsolute); // absolute => rooted
1173
1174    // Allocate memory for the resulting path, including room for
1175    // extra dir separators
1176    auto fullPath = new C[rootElement.length + segmentLengthSum + paths2.length];
1177
1178    // Copy the root element into fullPath, and let relPart be
1179    // the remaining slice.
1180    typeof(fullPath) relPart;
1181    if (rooted)
1182    {
1183        // For Windows, we also need to perform normalization on
1184        // the root element.
1185        version (Posix)
1186        {
1187            fullPath[0 .. rootElement.length] = rootElement[];
1188        }
1189        else version (Windows)
1190        {
1191            foreach (i, c; rootElement)
1192            {
1193                if (isDirSeparator(c))
1194                {
1195                    static assert (dirSeparator.length == 1);
1196                    fullPath[i] = dirSeparator[0];
1197                }
1198                else fullPath[i] = c;
1199            }
1200        }
1201        else static assert (0);
1202
1203        // If the root element doesn't end with a dir separator,
1204        // we add one.
1205        if (!isDirSeparator(rootElement[$-1]))
1206        {
1207            static assert (dirSeparator.length == 1);
1208            fullPath[rootElement.length] = dirSeparator[0];
1209            relPart = fullPath[rootElement.length + 1 .. $];
1210        }
1211        else
1212        {
1213            relPart = fullPath[rootElement.length .. $];
1214        }
1215    }
1216    else relPart = fullPath;
1217
1218    // Now, we have ensured that all segments in path are relative to the
1219    // root we found earlier.
1220    bool hasParents = rooted;
1221    ptrdiff_t i;
1222    foreach (path; paths2)
1223    {
1224        path = trimDirSeparators(path);
1225
1226        enum Prev { nonSpecial, dirSep, dot, doubleDot }
1227        Prev prev = Prev.dirSep;
1228        foreach (j; 0 .. path.length+1)
1229        {
1230            // Fake a dir separator between path segments
1231            immutable c = (j == path.length ? dirSeparator[0] : path[j]);
1232
1233            if (isDirSeparator(c))
1234            {
1235                final switch (prev)
1236                {
1237                    case Prev.doubleDot:
1238                        if (hasParents)
1239                        {
1240                            while (i > 0 && !isDirSeparator(relPart[i-1])) --i;
1241                            if (i > 0) --i; // skip the dir separator
1242                            while (i > 0 && !isDirSeparator(relPart[i-1])) --i;
1243                            if (i == 0) hasParents = rooted;
1244                        }
1245                        else
1246                        {
1247                            relPart[i++] = '.';
1248                            relPart[i++] = '.';
1249                            static assert (dirSeparator.length == 1);
1250                            relPart[i++] = dirSeparator[0];
1251                        }
1252                        break;
1253                    case Prev.dot:
1254                        while (i > 0 && !isDirSeparator(relPart[i-1])) --i;
1255                        break;
1256                    case Prev.nonSpecial:
1257                        static assert (dirSeparator.length == 1);
1258                        relPart[i++] = dirSeparator[0];
1259                        hasParents = true;
1260                        break;
1261                    case Prev.dirSep:
1262                        break;
1263                }
1264                prev = Prev.dirSep;
1265            }
1266            else if (c == '.')
1267            {
1268                final switch (prev)
1269                {
1270                    case Prev.dirSep:
1271                        prev = Prev.dot;
1272                        break;
1273                    case Prev.dot:
1274                        prev = Prev.doubleDot;
1275                        break;
1276                    case Prev.doubleDot:
1277                        prev = Prev.nonSpecial;
1278                        relPart[i .. i+3] = "...";
1279                        i += 3;
1280                        break;
1281                    case Prev.nonSpecial:
1282                        relPart[i] = '.';
1283                        ++i;
1284                        break;
1285                }
1286            }
1287            else
1288            {
1289                final switch (prev)
1290                {
1291                    case Prev.doubleDot:
1292                        relPart[i] = '.';
1293                        ++i;
1294                        goto case;
1295                    case Prev.dot:
1296                        relPart[i] = '.';
1297                        ++i;
1298                        break;
1299                    case Prev.dirSep:       break;
1300                    case Prev.nonSpecial:   break;
1301                }
1302                relPart[i] = c;
1303                ++i;
1304                prev = Prev.nonSpecial;
1305            }
1306        }
1307    }
1308
1309    // Return path, including root element and excluding the
1310    // final dir separator.
1311    immutable len = (relPart.ptr - fullPath.ptr) + (i > 0 ? i - 1 : 0);
1312    fullPath = fullPath[0 .. len];
1313    version (Windows)
1314    {
1315        // On Windows, if the path is on the form `\\server\share`,
1316        // with no further segments, normalization will have turned it
1317        // into `\\server\share\`.  If so, we need to remove the final
1318        // backslash.
1319        if (isUNC(fullPath) && uncRootLength(fullPath) == fullPath.length - 1)
1320            fullPath = fullPath[0 .. $-1];
1321    }
1322    return cast(typeof(return)) fullPath;
1323}
1324
1325unittest
1326{
1327    assert (buildNormalizedPath("") is null);
1328    assert (buildNormalizedPath("foo") == "foo");
1329
1330    version (Posix)
1331    {
1332        assert (buildNormalizedPath("/", "foo", "bar") == "/foo/bar");
1333        assert (buildNormalizedPath("foo", "bar", "baz") == "foo/bar/baz");
1334        assert (buildNormalizedPath("foo", "bar/baz") == "foo/bar/baz");
1335        assert (buildNormalizedPath("foo", "bar//baz///") == "foo/bar/baz");
1336        assert (buildNormalizedPath("/foo", "bar/baz") == "/foo/bar/baz");
1337        assert (buildNormalizedPath("/foo", "/bar/baz") == "/bar/baz");
1338        assert (buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1339        assert (buildNormalizedPath("/foo/..", "bar/baz") == "/bar/baz");
1340        assert (buildNormalizedPath("/foo/../../", "bar/baz") == "/bar/baz");
1341        assert (buildNormalizedPath("/foo/bar", "../baz") == "/foo/baz");
1342        assert (buildNormalizedPath("/foo/bar", "../../baz") == "/baz");
1343        assert (buildNormalizedPath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee");
1344        assert (buildNormalizedPath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee");
1345        static assert (buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1346        // Examples in docs:
1347        assert (buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
1348        assert (buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
1349        assert (buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
1350        assert (buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
1351    }
1352    else version (Windows)
1353    {
1354        assert (buildNormalizedPath(`\`, `foo`, `bar`) == `\foo\bar`);
1355        assert (buildNormalizedPath(`foo`, `bar`, `baz`) == `foo\bar\baz`);
1356        assert (buildNormalizedPath(`foo`, `bar\baz`) == `foo\bar\baz`);
1357        assert (buildNormalizedPath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`);
1358        assert (buildNormalizedPath(`\foo`, `bar\baz`) == `\foo\bar\baz`);
1359        assert (buildNormalizedPath(`\foo`, `\bar\baz`) == `\bar\baz`);
1360        assert (buildNormalizedPath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`);
1361        assert (buildNormalizedPath(`\foo\..`, `bar\baz`) == `\bar\baz`);
1362        assert (buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1363        assert (buildNormalizedPath(`\foo\bar`, `..\baz`) == `\foo\baz`);
1364        assert (buildNormalizedPath(`\foo\bar`, `../../baz`) == `\baz`);
1365        assert (buildNormalizedPath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`);
1366
1367        assert (buildNormalizedPath(`c:\`, `foo`, `bar`) == `c:\foo\bar`);
1368        assert (buildNormalizedPath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`);
1369        assert (buildNormalizedPath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`);
1370        assert (buildNormalizedPath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`);
1371        assert (buildNormalizedPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1372        assert (buildNormalizedPath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`);
1373        assert (buildNormalizedPath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`);
1374        assert (buildNormalizedPath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`);
1375        assert (buildNormalizedPath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`);
1376        assert (buildNormalizedPath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`);
1377        assert (buildNormalizedPath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`);
1378        assert (buildNormalizedPath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`);
1379
1380        assert (buildNormalizedPath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`);
1381        assert (buildNormalizedPath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`);
1382        assert (buildNormalizedPath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`);
1383        assert (buildNormalizedPath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`);
1384        assert (buildNormalizedPath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`);
1385        assert (buildNormalizedPath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`);
1386        assert (buildNormalizedPath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`);
1387        assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`);
1388        assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`);
1389        assert (buildNormalizedPath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`);
1390
1391        static assert (buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1392
1393        // Examples in docs:
1394        assert (buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
1395        assert (buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
1396        assert (buildNormalizedPath(`\\server\share\foo`, `..\bar`) == `\\server\share\bar`);
1397    }
1398    else static assert (0);
1399}
1400
1401unittest
1402{
1403    version (Posix)
1404    {
1405        // Trivial
1406        assert (buildNormalizedPath("").empty);
1407        assert (buildNormalizedPath("foo/bar") == "foo/bar");
1408
1409        // Correct handling of leading slashes
1410        assert (buildNormalizedPath("/") == "/");
1411        assert (buildNormalizedPath("///") == "/");
1412        assert (buildNormalizedPath("////") == "/");
1413        assert (buildNormalizedPath("/foo/bar") == "/foo/bar");
1414        assert (buildNormalizedPath("//foo/bar") == "/foo/bar");
1415        assert (buildNormalizedPath("///foo/bar") == "/foo/bar");
1416        assert (buildNormalizedPath("////foo/bar") == "/foo/bar");
1417
1418        // Correct handling of single-dot symbol (current directory)
1419        assert (buildNormalizedPath("/./foo") == "/foo");
1420        assert (buildNormalizedPath("/foo/./bar") == "/foo/bar");
1421
1422        assert (buildNormalizedPath("./foo") == "foo");
1423        assert (buildNormalizedPath("././foo") == "foo");
1424        assert (buildNormalizedPath("foo/././bar") == "foo/bar");
1425
1426        // Correct handling of double-dot symbol (previous directory)
1427        assert (buildNormalizedPath("/foo/../bar") == "/bar");
1428        assert (buildNormalizedPath("/foo/../../bar") == "/bar");
1429        assert (buildNormalizedPath("/../foo") == "/foo");
1430        assert (buildNormalizedPath("/../../foo") == "/foo");
1431        assert (buildNormalizedPath("/foo/..") == "/");
1432        assert (buildNormalizedPath("/foo/../..") == "/");
1433
1434        assert (buildNormalizedPath("foo/../bar") == "bar");
1435        assert (buildNormalizedPath("foo/../../bar") == "../bar");
1436        assert (buildNormalizedPath("../foo") == "../foo");
1437        assert (buildNormalizedPa

Large files files are truncated, but you can click here to view the full file