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 $(REF isDir, std,file) and
14    $(REF exists, std,file).
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$(SCRIPT inhibitQuickIndex = 1;)
36$(DIVC quickindex,
37$(BOOKTABLE,
38$(TR $(TH Category) $(TH Functions))
39$(TR $(TD Normalization) $(TD
40          $(LREF absolutePath)
41          $(LREF asAbsolutePath)
42          $(LREF asNormalizedPath)
43          $(LREF asRelativePath)
44          $(LREF buildNormalizedPath)
45          $(LREF buildPath)
46          $(LREF chainPath)
47          $(LREF expandTilde)
48))
49$(TR $(TD Partitioning) $(TD
50          $(LREF baseName)
51          $(LREF dirName)
52          $(LREF dirSeparator)
53          $(LREF driveName)
54          $(LREF pathSeparator)
55          $(LREF pathSplitter)
56          $(LREF relativePath)
57          $(LREF rootName)
58          $(LREF stripDrive)
59))
60$(TR $(TD Validation) $(TD
61          $(LREF isAbsolute)
62          $(LREF isDirSeparator)
63          $(LREF isRooted)
64          $(LREF isValidFilename)
65          $(LREF isValidPath)
66))
67$(TR $(TD Extension) $(TD
68          $(LREF defaultExtension)
69          $(LREF extension)
70          $(LREF setExtension)
71          $(LREF stripExtension)
72          $(LREF withDefaultExtension)
73          $(LREF withExtension)
74))
75$(TR $(TD Other) $(TD
76          $(LREF filenameCharCmp)
77          $(LREF filenameCmp)
78          $(LREF globMatch)
79          $(LREF CaseSensitive)
80))
81))
82
83    Authors:
84        Lars Tandle Kyllingstad,
85        $(HTTP digitalmars.com, Walter Bright),
86        Grzegorz Adam Hankiewicz,
87        Thomas K$(UUML)hne,
88        $(HTTP erdani.org, Andrei Alexandrescu)
89    Copyright:
90        Copyright (c) 2000-2014, the authors. All rights reserved.
91    License:
92        $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0)
93    Source:
94        $(PHOBOSSRC std/_path.d)
95*/
96module std.path;
97
98
99// FIXME
100import std.file; //: getcwd;
101static import std.meta;
102import std.range.primitives;
103import std.traits;
104
105version (unittest)
106{
107private:
108    struct TestAliasedString
109    {
110        string get() @safe @nogc pure nothrow { return _s; }
111        alias get this;
112        @disable this(this);
113        string _s;
114    }
115
116    bool testAliasedString(alias func, Args...)(string s, Args args)
117    {
118        return func(TestAliasedString(s), args) == func(s, args);
119    }
120}
121
122/** String used to separate directory names in a path.  Under
123    POSIX this is a slash, under Windows a backslash.
124*/
125version (Posix)          enum string dirSeparator = "/";
126else version (Windows)   enum string dirSeparator = "\\";
127else static assert(0, "unsupported platform");
128
129
130
131
132/** Path separator string.  A colon under POSIX, a semicolon
133    under Windows.
134*/
135version (Posix)          enum string pathSeparator = ":";
136else version (Windows)   enum string pathSeparator = ";";
137else static assert(0, "unsupported platform");
138
139
140
141
142/** Determines whether the given character is a directory separator.
143
144    On Windows, this includes both $(D `\`) and $(D `/`).
145    On POSIX, it's just $(D `/`).
146*/
147bool isDirSeparator(dchar c)  @safe pure nothrow @nogc
148{
149    if (c == '/') return true;
150    version (Windows) if (c == '\\') return true;
151    return false;
152}
153
154
155/*  Determines whether the given character is a drive separator.
156
157    On Windows, this is true if c is the ':' character that separates
158    the drive letter from the rest of the path.  On POSIX, this always
159    returns false.
160*/
161private bool isDriveSeparator(dchar c)  @safe pure nothrow @nogc
162{
163    version (Windows) return c == ':';
164    else return false;
165}
166
167
168/*  Combines the isDirSeparator and isDriveSeparator tests. */
169version (Windows) private bool isSeparator(dchar c)  @safe pure nothrow @nogc
170{
171    return isDirSeparator(c) || isDriveSeparator(c);
172}
173version (Posix) private alias isSeparator = isDirSeparator;
174
175
176/*  Helper function that determines the position of the last
177    drive/directory separator in a string.  Returns -1 if none
178    is found.
179*/
180private ptrdiff_t lastSeparator(R)(R path)
181if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
182    isNarrowString!R)
183{
184    auto i = (cast(ptrdiff_t) path.length) - 1;
185    while (i >= 0 && !isSeparator(path[i])) --i;
186    return i;
187}
188
189
190version (Windows)
191{
192    private bool isUNC(R)(R path)
193    if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
194        isNarrowString!R)
195    {
196        return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1])
197            && !isDirSeparator(path[2]);
198    }
199
200    private ptrdiff_t uncRootLength(R)(R path)
201    if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
202        isNarrowString!R)
203        in { assert(isUNC(path)); }
204        body
205    {
206        ptrdiff_t i = 3;
207        while (i < path.length && !isDirSeparator(path[i])) ++i;
208        if (i < path.length)
209        {
210            auto j = i;
211            do { ++j; } while (j < path.length && isDirSeparator(path[j]));
212            if (j < path.length)
213            {
214                do { ++j; } while (j < path.length && !isDirSeparator(path[j]));
215                i = j;
216            }
217        }
218        return i;
219    }
220
221    private bool hasDrive(R)(R path)
222    if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
223        isNarrowString!R)
224    {
225        return path.length >= 2 && isDriveSeparator(path[1]);
226    }
227
228    private bool isDriveRoot(R)(R path)
229    if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
230        isNarrowString!R)
231    {
232        return path.length >= 3 && isDriveSeparator(path[1])
233            && isDirSeparator(path[2]);
234    }
235}
236
237
238/*  Helper functions that strip leading/trailing slashes and backslashes
239    from a path.
240*/
241private auto ltrimDirSeparators(R)(R path)
242if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementType!R) ||
243    isNarrowString!R)
244{
245    static if (isRandomAccessRange!R && hasSlicing!R || isNarrowString!R)
246    {
247        int i = 0;
248        while (i < path.length && isDirSeparator(path[i]))
249            ++i;
250        return path[i .. path.length];
251    }
252    else
253    {
254        while (!path.empty && isDirSeparator(path.front))
255            path.popFront();
256        return path;
257    }
258}
259
260@system unittest
261{
262    import std.array;
263    import std.utf : byDchar;
264
265    assert(ltrimDirSeparators("//abc//").array == "abc//");
266    assert(ltrimDirSeparators("//abc//"d).array == "abc//"d);
267    assert(ltrimDirSeparators("//abc//".byDchar).array == "abc//"d);
268}
269
270private auto rtrimDirSeparators(R)(R path)
271if (isBidirectionalRange!R && isSomeChar!(ElementType!R) ||
272    isNarrowString!R)
273{
274    static if (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R)
275    {
276        auto i = (cast(ptrdiff_t) path.length) - 1;
277        while (i >= 0 && isDirSeparator(path[i]))
278            --i;
279        return path[0 .. i+1];
280    }
281    else
282    {
283        while (!path.empty && isDirSeparator(path.back))
284            path.popBack();
285        return path;
286    }
287}
288
289@system unittest
290{
291    import std.array;
292    import std.utf : byDchar;
293
294    assert(rtrimDirSeparators("//abc//").array == "//abc");
295    assert(rtrimDirSeparators("//abc//"d).array == "//abc"d);
296
297    assert(rtrimDirSeparators(MockBiRange!char("//abc//")).array == "//abc");
298}
299
300private auto trimDirSeparators(R)(R path)
301if (isBidirectionalRange!R && isSomeChar!(ElementType!R) ||
302    isNarrowString!R)
303{
304    return ltrimDirSeparators(rtrimDirSeparators(path));
305}
306
307@system unittest
308{
309    import std.array;
310    import std.utf : byDchar;
311
312    assert(trimDirSeparators("//abc//").array == "abc");
313    assert(trimDirSeparators("//abc//"d).array == "abc"d);
314
315    assert(trimDirSeparators(MockBiRange!char("//abc//")).array == "abc");
316}
317
318
319
320
321/** This $(D enum) is used as a template argument to functions which
322    compare file names, and determines whether the comparison is
323    case sensitive or not.
324*/
325enum CaseSensitive : bool
326{
327    /// File names are case insensitive
328    no = false,
329
330    /// File names are case sensitive
331    yes = true,
332
333    /** The default (or most common) setting for the current platform.
334        That is, $(D no) on Windows and Mac OS X, and $(D yes) on all
335        POSIX systems except OS X (Linux, *BSD, etc.).
336    */
337    osDefault = osDefaultCaseSensitivity
338}
339version (Windows)    private enum osDefaultCaseSensitivity = false;
340else version (OSX)   private enum osDefaultCaseSensitivity = false;
341else version (Posix) private enum osDefaultCaseSensitivity = true;
342else static assert(0);
343
344
345
346
347/**
348    Params:
349        cs = Whether or not suffix matching is case-sensitive.
350        path = A path name. It can be a string, or any random-access range of
351            characters.
352        suffix = An optional suffix to be removed from the file name.
353    Returns: The name of the file in the path name, without any leading
354        directory and with an optional suffix chopped off.
355
356    If $(D suffix) is specified, it will be compared to $(D path)
357    using $(D filenameCmp!cs),
358    where $(D cs) is an optional template parameter determining whether
359    the comparison is case sensitive or not.  See the
360    $(LREF filenameCmp) documentation for details.
361
362    Example:
363    ---
364    assert(baseName("dir/file.ext")         == "file.ext");
365    assert(baseName("dir/file.ext", ".ext") == "file");
366    assert(baseName("dir/file.ext", ".xyz") == "file.ext");
367    assert(baseName("dir/filename", "name") == "file");
368    assert(baseName("dir/subdir/")          == "subdir");
369
370    version (Windows)
371    {
372        assert(baseName(`d:file.ext`)      == "file.ext");
373        assert(baseName(`d:\dir\file.ext`) == "file.ext");
374    }
375    ---
376
377    Note:
378    This function $(I only) strips away the specified suffix, which
379    doesn't necessarily have to represent an extension.
380    To remove the extension from a path, regardless of what the extension
381    is, use $(LREF stripExtension).
382    To obtain the filename without leading directories and without
383    an extension, combine the functions like this:
384    ---
385    assert(baseName(stripExtension("dir/file.ext")) == "file");
386    ---
387
388    Standards:
389    This function complies with
390    $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html,
391    the POSIX requirements for the 'basename' shell utility)
392    (with suitable adaptations for Windows paths).
393*/
394auto baseName(R)(R path)
395if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!R)
396{
397    return _baseName(path);
398}
399
400/// ditto
401auto baseName(C)(C[] path)
402if (isSomeChar!C)
403{
404    return _baseName(path);
405}
406
407private R _baseName(R)(R path)
408if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || isNarrowString!R)
409{
410    auto p1 = stripDrive(path);
411    if (p1.empty)
412    {
413        version (Windows) if (isUNC(path))
414            return path[0 .. 1];
415        static if (isSomeString!R)
416            return null;
417        else
418            return p1; // which is empty
419    }
420
421    auto p2 = rtrimDirSeparators(p1);
422    if (p2.empty) return p1[0 .. 1];
423
424    return p2[lastSeparator(p2)+1 .. p2.length];
425}
426
427/// ditto
428inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1)
429    (inout(C)[] path, in C1[] suffix)
430    @safe pure //TODO: nothrow (because of filenameCmp())
431if (isSomeChar!C && isSomeChar!C1)
432{
433    auto p = baseName(path);
434    if (p.length > suffix.length
435        && filenameCmp!cs(cast(const(C)[])p[$-suffix.length .. $], suffix) == 0)
436    {
437        return p[0 .. $-suffix.length];
438    }
439    else return p;
440}
441
442@safe unittest
443{
444    assert(baseName("").empty);
445    assert(baseName("file.ext"w) == "file.ext");
446    assert(baseName("file.ext"d, ".ext") == "file");
447    assert(baseName("file", "file"w.dup) == "file");
448    assert(baseName("dir/file.ext"d.dup) == "file.ext");
449    assert(baseName("dir/file.ext", ".ext"d) == "file");
450    assert(baseName("dir/file"w, "file"d) == "file");
451    assert(baseName("dir///subdir////") == "subdir");
452    assert(baseName("dir/subdir.ext/", ".ext") == "subdir");
453    assert(baseName("dir/subdir/".dup, "subdir") == "subdir");
454    assert(baseName("/"w.dup) == "/");
455    assert(baseName("//"d.dup) == "/");
456    assert(baseName("///") == "/");
457
458    assert(baseName!(CaseSensitive.yes)("file.ext", ".EXT") == "file.ext");
459    assert(baseName!(CaseSensitive.no)("file.ext", ".EXT") == "file");
460
461    {
462        auto r = MockRange!(immutable(char))(`dir/file.ext`);
463        auto s = r.baseName();
464        foreach (i, c; `file`)
465            assert(s[i] == c);
466    }
467
468    version (Windows)
469    {
470        assert(baseName(`dir\file.ext`) == `file.ext`);
471        assert(baseName(`dir\file.ext`, `.ext`) == `file`);
472        assert(baseName(`dir\file`, `file`) == `file`);
473        assert(baseName(`d:file.ext`) == `file.ext`);
474        assert(baseName(`d:file.ext`, `.ext`) == `file`);
475        assert(baseName(`d:file`, `file`) == `file`);
476        assert(baseName(`dir\\subdir\\\`) == `subdir`);
477        assert(baseName(`dir\subdir.ext\`, `.ext`) == `subdir`);
478        assert(baseName(`dir\subdir\`, `subdir`) == `subdir`);
479        assert(baseName(`\`) == `\`);
480        assert(baseName(`\\`) == `\`);
481        assert(baseName(`\\\`) == `\`);
482        assert(baseName(`d:\`) == `\`);
483        assert(baseName(`d:`).empty);
484        assert(baseName(`\\server\share\file`) == `file`);
485        assert(baseName(`\\server\share\`) == `\`);
486        assert(baseName(`\\server\share`) == `\`);
487
488        auto r = MockRange!(immutable(char))(`\\server\share`);
489        auto s = r.baseName();
490        foreach (i, c; `\`)
491            assert(s[i] == c);
492    }
493
494    assert(baseName(stripExtension("dir/file.ext")) == "file");
495
496    static assert(baseName("dir/file.ext") == "file.ext");
497    static assert(baseName("dir/file.ext", ".ext") == "file");
498
499    static struct DirEntry { string s; alias s this; }
500    assert(baseName(DirEntry("dir/file.ext")) == "file.ext");
501}
502
503@safe unittest
504{
505    assert(testAliasedString!baseName("file"));
506
507    enum S : string { a = "file/path/to/test" }
508    assert(S.a.baseName == "test");
509
510    char[S.a.length] sa = S.a[];
511    assert(sa.baseName == "test");
512}
513
514/** Returns the directory part of a path.  On Windows, this
515    includes the drive letter if present.
516
517    Params:
518        path = A path name.
519
520    Returns:
521        A slice of $(D path) or ".".
522
523    Standards:
524    This function complies with
525    $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html,
526    the POSIX requirements for the 'dirname' shell utility)
527    (with suitable adaptations for Windows paths).
528*/
529auto dirName(R)(R path)
530if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R)
531{
532    return _dirName(path);
533}
534
535/// ditto
536auto dirName(C)(C[] path)
537if (isSomeChar!C)
538{
539    return _dirName(path);
540}
541
542private auto _dirName(R)(R path)
543if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
544    isNarrowString!R)
545{
546    static auto result(bool dot, typeof(path[0 .. 1]) p)
547    {
548        static if (isSomeString!R)
549            return dot ? "." : p;
550        else
551        {
552            import std.range : choose, only;
553            return choose(dot, only(cast(ElementEncodingType!R)'.'), p);
554        }
555    }
556
557    if (path.empty)
558        return result(true, path[0 .. 0]);
559
560    auto p = rtrimDirSeparators(path);
561    if (p.empty)
562        return result(false, path[0 .. 1]);
563
564    version (Windows)
565    {
566        if (isUNC(p) && uncRootLength(p) == p.length)
567            return result(false, p);
568
569        if (p.length == 2 && isDriveSeparator(p[1]) && path.length > 2)
570            return result(false, path[0 .. 3]);
571    }
572
573    auto i = lastSeparator(p);
574    if (i == -1)
575        return result(true, p);
576    if (i == 0)
577        return result(false, p[0 .. 1]);
578
579    version (Windows)
580    {
581        // If the directory part is either d: or d:\
582        // do not chop off the last symbol.
583        if (isDriveSeparator(p[i]) || isDriveSeparator(p[i-1]))
584            return result(false, p[0 .. i+1]);
585    }
586    // Remove any remaining trailing (back)slashes.
587    return result(false, rtrimDirSeparators(p[0 .. i]));
588}
589
590///
591@safe unittest
592{
593    assert(dirName("") == ".");
594    assert(dirName("file"w) == ".");
595    assert(dirName("dir/"d) == ".");
596    assert(dirName("dir///") == ".");
597    assert(dirName("dir/file"w.dup) == "dir");
598    assert(dirName("dir///file"d.dup) == "dir");
599    assert(dirName("dir/subdir/") == "dir");
600    assert(dirName("/dir/file"w) == "/dir");
601    assert(dirName("/file"d) == "/");
602    assert(dirName("/") == "/");
603    assert(dirName("///") == "/");
604
605    version (Windows)
606    {
607        assert(dirName(`dir\`) == `.`);
608        assert(dirName(`dir\\\`) == `.`);
609        assert(dirName(`dir\file`) == `dir`);
610        assert(dirName(`dir\\\file`) == `dir`);
611        assert(dirName(`dir\subdir\`) == `dir`);
612        assert(dirName(`\dir\file`) == `\dir`);
613        assert(dirName(`\file`) == `\`);
614        assert(dirName(`\`) == `\`);
615        assert(dirName(`\\\`) == `\`);
616        assert(dirName(`d:`) == `d:`);
617        assert(dirName(`d:file`) == `d:`);
618        assert(dirName(`d:\`) == `d:\`);
619        assert(dirName(`d:\file`) == `d:\`);
620        assert(dirName(`d:\dir\file`) == `d:\dir`);
621        assert(dirName(`\\server\share\dir\file`) == `\\server\share\dir`);
622        assert(dirName(`\\server\share\file`) == `\\server\share`);
623        assert(dirName(`\\server\share\`) == `\\server\share`);
624        assert(dirName(`\\server\share`) == `\\server\share`);
625    }
626}
627
628@safe unittest
629{
630    assert(testAliasedString!dirName("file"));
631
632    enum S : string { a = "file/path/to/test" }
633    assert(S.a.dirName == "file/path/to");
634
635    char[S.a.length] sa = S.a[];
636    assert(sa.dirName == "file/path/to");
637}
638
639@system unittest
640{
641    static assert(dirName("dir/file") == "dir");
642
643    import std.array;
644    import std.utf : byChar, byWchar, byDchar;
645
646    assert(dirName("".byChar).array == ".");
647    assert(dirName("file"w.byWchar).array == "."w);
648    assert(dirName("dir/"d.byDchar).array == "."d);
649    assert(dirName("dir///".byChar).array == ".");
650    assert(dirName("dir/subdir/".byChar).array == "dir");
651    assert(dirName("/dir/file"w.byWchar).array == "/dir"w);
652    assert(dirName("/file"d.byDchar).array == "/"d);
653    assert(dirName("/".byChar).array == "/");
654    assert(dirName("///".byChar).array == "/");
655
656    version (Windows)
657    {
658        assert(dirName(`dir\`.byChar).array == `.`);
659        assert(dirName(`dir\\\`.byChar).array == `.`);
660        assert(dirName(`dir\file`.byChar).array == `dir`);
661        assert(dirName(`dir\\\file`.byChar).array == `dir`);
662        assert(dirName(`dir\subdir\`.byChar).array == `dir`);
663        assert(dirName(`\dir\file`.byChar).array == `\dir`);
664        assert(dirName(`\file`.byChar).array == `\`);
665        assert(dirName(`\`.byChar).array == `\`);
666        assert(dirName(`\\\`.byChar).array == `\`);
667        assert(dirName(`d:`.byChar).array == `d:`);
668        assert(dirName(`d:file`.byChar).array == `d:`);
669        assert(dirName(`d:\`.byChar).array == `d:\`);
670        assert(dirName(`d:\file`.byChar).array == `d:\`);
671        assert(dirName(`d:\dir\file`.byChar).array == `d:\dir`);
672        assert(dirName(`\\server\share\dir\file`.byChar).array == `\\server\share\dir`);
673        assert(dirName(`\\server\share\file`) == `\\server\share`);
674        assert(dirName(`\\server\share\`.byChar).array == `\\server\share`);
675        assert(dirName(`\\server\share`.byChar).array == `\\server\share`);
676    }
677
678    //static assert(dirName("dir/file".byChar).array == "dir");
679}
680
681
682
683
684/** Returns the root directory of the specified path, or $(D null) if the
685    path is not rooted.
686
687    Params:
688        path = A path name.
689
690    Returns:
691        A slice of $(D path).
692*/
693auto rootName(R)(R path)
694if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
695    isNarrowString!R) &&
696    !isConvertibleToString!R)
697{
698    if (path.empty)
699        goto Lnull;
700
701    version (Posix)
702    {
703        if (isDirSeparator(path[0])) return path[0 .. 1];
704    }
705    else version (Windows)
706    {
707        if (isDirSeparator(path[0]))
708        {
709            if (isUNC(path)) return path[0 .. uncRootLength(path)];
710            else return path[0 .. 1];
711        }
712        else if (path.length >= 3 && isDriveSeparator(path[1]) &&
713            isDirSeparator(path[2]))
714        {
715            return path[0 .. 3];
716        }
717    }
718    else static assert(0, "unsupported platform");
719
720    assert(!isRooted(path));
721Lnull:
722    static if (is(StringTypeOf!R))
723        return null; // legacy code may rely on null return rather than slice
724    else
725        return path[0 .. 0];
726}
727
728///
729@safe unittest
730{
731    assert(rootName("") is null);
732    assert(rootName("foo") is null);
733    assert(rootName("/") == "/");
734    assert(rootName("/foo/bar") == "/");
735
736    version (Windows)
737    {
738        assert(rootName("d:foo") is null);
739        assert(rootName(`d:\foo`) == `d:\`);
740        assert(rootName(`\\server\share\foo`) == `\\server\share`);
741        assert(rootName(`\\server\share`) == `\\server\share`);
742    }
743}
744
745@safe unittest
746{
747    assert(testAliasedString!rootName("/foo/bar"));
748}
749
750@safe unittest
751{
752    import std.array;
753    import std.utf : byChar;
754
755    assert(rootName("".byChar).array == "");
756    assert(rootName("foo".byChar).array == "");
757    assert(rootName("/".byChar).array == "/");
758    assert(rootName("/foo/bar".byChar).array == "/");
759
760    version (Windows)
761    {
762        assert(rootName("d:foo".byChar).array == "");
763        assert(rootName(`d:\foo`.byChar).array == `d:\`);
764        assert(rootName(`\\server\share\foo`.byChar).array == `\\server\share`);
765        assert(rootName(`\\server\share`.byChar).array == `\\server\share`);
766    }
767}
768
769auto rootName(R)(R path)
770if (isConvertibleToString!R)
771{
772    return rootName!(StringTypeOf!R)(path);
773}
774
775
776/**
777    Get the drive portion of a path.
778
779    Params:
780        path = string or range of characters
781
782    Returns:
783        A slice of $(D _path) that is the drive, or an empty range if the drive
784        is not specified.  In the case of UNC paths, the network share
785        is returned.
786
787        Always returns an empty range on POSIX.
788*/
789auto driveName(R)(R path)
790if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
791    isNarrowString!R) &&
792    !isConvertibleToString!R)
793{
794    version (Windows)
795    {
796        if (hasDrive(path))
797            return path[0 .. 2];
798        else if (isUNC(path))
799            return path[0 .. uncRootLength(path)];
800    }
801    static if (isSomeString!R)
802        return cast(ElementEncodingType!R[]) null; // legacy code may rely on null return rather than slice
803    else
804        return path[0 .. 0];
805}
806
807///
808@safe unittest
809{
810    import std.range : empty;
811    version (Posix)  assert(driveName("c:/foo").empty);
812    version (Windows)
813    {
814        assert(driveName(`dir\file`).empty);
815        assert(driveName(`d:file`) == "d:");
816        assert(driveName(`d:\file`) == "d:");
817        assert(driveName("d:") == "d:");
818        assert(driveName(`\\server\share\file`) == `\\server\share`);
819        assert(driveName(`\\server\share\`) == `\\server\share`);
820        assert(driveName(`\\server\share`) == `\\server\share`);
821
822        static assert(driveName(`d:\file`) == "d:");
823    }
824}
825
826auto driveName(R)(auto ref R path)
827if (isConvertibleToString!R)
828{
829    return driveName!(StringTypeOf!R)(path);
830}
831
832@safe unittest
833{
834    assert(testAliasedString!driveName(`d:\file`));
835}
836
837@safe unittest
838{
839    import std.array;
840    import std.utf : byChar;
841
842    version (Posix)  assert(driveName("c:/foo".byChar).empty);
843    version (Windows)
844    {
845        assert(driveName(`dir\file`.byChar).empty);
846        assert(driveName(`d:file`.byChar).array == "d:");
847        assert(driveName(`d:\file`.byChar).array == "d:");
848        assert(driveName("d:".byChar).array == "d:");
849        assert(driveName(`\\server\share\file`.byChar).array == `\\server\share`);
850        assert(driveName(`\\server\share\`.byChar).array == `\\server\share`);
851        assert(driveName(`\\server\share`.byChar).array == `\\server\share`);
852
853        static assert(driveName(`d:\file`).array == "d:");
854    }
855}
856
857
858/** Strips the drive from a Windows path.  On POSIX, the path is returned
859    unaltered.
860
861    Params:
862        path = A pathname
863
864    Returns: A slice of path without the drive component.
865*/
866auto stripDrive(R)(R path)
867if ((isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) ||
868    isNarrowString!R) &&
869    !isConvertibleToString!R)
870{
871    version (Windows)
872    {
873        if (hasDrive!(BaseOf!R)(path))      return path[2 .. path.length];
874        else if (isUNC!(BaseOf!R)(path))    return path[uncRootLength!(BaseOf!R)(path) .. path.length];
875    }
876    return path;
877}
878
879///
880@safe unittest
881{
882    version (Windows)
883    {
884        assert(stripDrive(`d:\dir\file`) == `\dir\file`);
885        assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`);
886    }
887}
888
889auto stripDrive(R)(auto ref R path)
890if (isConvertibleToString!R)
891{
892    return stripDrive!(StringTypeOf!R)(path);
893}
894
895@safe unittest
896{
897    assert(testAliasedString!stripDrive(`d:\dir\file`));
898}
899
900@safe unittest
901{
902    version (Windows)
903    {
904        assert(stripDrive(`d:\dir\file`) == `\dir\file`);
905        assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`);
906        static assert(stripDrive(`d:\dir\file`) == `\dir\file`);
907
908        auto r = MockRange!(immutable(char))(`d:\dir\file`);
909        auto s = r.stripDrive();
910        foreach (i, c; `\dir\file`)
911            assert(s[i] == c);
912    }
913    version (Posix)
914    {
915        assert(stripDrive(`d:\dir\file`) == `d:\dir\file`);
916
917        auto r = MockRange!(immutable(char))(`d:\dir\file`);
918        auto s = r.stripDrive();
919        foreach (i, c; `d:\dir\file`)
920            assert(s[i] == c);
921    }
922}
923
924
925/*  Helper function that returns the position of the filename/extension
926    separator dot in path.
927
928    Params:
929        path = file spec as string or indexable range
930    Returns:
931        index of extension separator (the dot), or -1 if not found
932*/
933private ptrdiff_t extSeparatorPos(R)(const R path)
934if (isRandomAccessRange!R && hasLength!R && isSomeChar!(ElementType!R) ||
935    isNarrowString!R)
936{
937    for (auto i = path.length; i-- > 0 && !isSeparator(path[i]); )
938    {
939        if (path[i] == '.' && i > 0 && !isSeparator(path[i-1]))
940            return i;
941    }
942    return -1;
943}
944
945@safe unittest
946{
947    assert(extSeparatorPos("file") == -1);
948    assert(extSeparatorPos("file.ext"w) == 4);
949    assert(extSeparatorPos("file.ext1.ext2"d) == 9);
950    assert(extSeparatorPos(".foo".dup) == -1);
951    assert(extSeparatorPos(".foo.ext"w.dup) == 4);
952}
953
954@safe unittest
955{
956    assert(extSeparatorPos("dir/file"d.dup) == -1);
957    assert(extSeparatorPos("dir/file.ext") == 8);
958    assert(extSeparatorPos("dir/file.ext1.ext2"w) == 13);
959    assert(extSeparatorPos("dir/.foo"d) == -1);
960    assert(extSeparatorPos("dir/.foo.ext".dup) == 8);
961
962    version (Windows)
963    {
964        assert(extSeparatorPos("dir\\file") == -1);
965        assert(extSeparatorPos("dir\\file.ext") == 8);
966        assert(extSeparatorPos("dir\\file.ext1.ext2") == 13);
967        assert(extSeparatorPos("dir\\.foo") == -1);
968        assert(extSeparatorPos("dir\\.foo.ext") == 8);
969
970        assert(extSeparatorPos("d:file") == -1);
971        assert(extSeparatorPos("d:file.ext") == 6);
972        assert(extSeparatorPos("d:file.ext1.ext2") == 11);
973        assert(extSeparatorPos("d:.foo") == -1);
974        assert(extSeparatorPos("d:.foo.ext") == 6);
975    }
976
977    static assert(extSeparatorPos("file") == -1);
978    static assert(extSeparatorPos("file.ext"w) == 4);
979}
980
981
982/**
983    Params: path = A path name.
984    Returns: The _extension part of a file name, including the dot.
985
986    If there is no _extension, $(D null) is returned.
987*/
988auto extension(R)(R path)
989if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) ||
990    is(StringTypeOf!R))
991{
992    auto i = extSeparatorPos!(BaseOf!R)(path);
993    if (i == -1)
994    {
995        static if (is(StringTypeOf!R))
996            return StringTypeOf!R.init[];   // which is null
997        else
998            return path[0 .. 0];
999    }
1000    else return path[i .. path.length];
1001}
1002
1003///
1004@safe unittest
1005{
1006    import std.range : empty;
1007    assert(extension("file").empty);
1008    assert(extension("file.") == ".");
1009    assert(extension("file.ext"w) == ".ext");
1010    assert(extension("file.ext1.ext2"d) == ".ext2");
1011    assert(extension(".foo".dup).empty);
1012    assert(extension(".foo.ext"w.dup) == ".ext");
1013
1014    static assert(extension("file").empty);
1015    static assert(extension("file.ext") == ".ext");
1016}
1017
1018@safe unittest
1019{
1020    {
1021        auto r = MockRange!(immutable(char))(`file.ext1.ext2`);
1022        auto s = r.extension();
1023        foreach (i, c; `.ext2`)
1024            assert(s[i] == c);
1025    }
1026
1027    static struct DirEntry { string s; alias s this; }
1028    assert(extension(DirEntry("file")).empty);
1029}
1030
1031
1032/** Remove extension from path.
1033
1034    Params:
1035        path = string or range to be sliced
1036
1037    Returns:
1038        slice of path with the extension (if any) stripped off
1039*/
1040auto stripExtension(R)(R path)
1041if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
1042    isNarrowString!R) &&
1043    !isConvertibleToString!R)
1044{
1045    auto i = extSeparatorPos(path);
1046    return (i == -1) ? path : path[0 .. i];
1047}
1048
1049///
1050@safe unittest
1051{
1052    assert(stripExtension("file")           == "file");
1053    assert(stripExtension("file.ext")       == "file");
1054    assert(stripExtension("file.ext1.ext2") == "file.ext1");
1055    assert(stripExtension("file.")          == "file");
1056    assert(stripExtension(".file")          == ".file");
1057    assert(stripExtension(".file.ext")      == ".file");
1058    assert(stripExtension("dir/file.ext")   == "dir/file");
1059}
1060
1061auto stripExtension(R)(auto ref R path)
1062if (isConvertibleToString!R)
1063{
1064    return stripExtension!(StringTypeOf!R)(path);
1065}
1066
1067@safe unittest
1068{
1069    assert(testAliasedString!stripExtension("file"));
1070}
1071
1072@safe unittest
1073{
1074    assert(stripExtension("file.ext"w) == "file");
1075    assert(stripExtension("file.ext1.ext2"d) == "file.ext1");
1076
1077    import std.array;
1078    import std.utf : byChar, byWchar, byDchar;
1079
1080    assert(stripExtension("file".byChar).array == "file");
1081    assert(stripExtension("file.ext"w.byWchar).array == "file");
1082    assert(stripExtension("file.ext1.ext2"d.byDchar).array == "file.ext1");
1083}
1084
1085
1086/** Sets or replaces an extension.
1087
1088    If the filename already has an extension, it is replaced. If not, the
1089    extension is simply appended to the filename. Including a leading dot
1090    in $(D ext) is optional.
1091
1092    If the extension is empty, this function is equivalent to
1093    $(LREF stripExtension).
1094
1095    This function normally allocates a new string (the possible exception
1096    being the case when path is immutable and doesn't already have an
1097    extension).
1098
1099    Params:
1100        path = A path name
1101        ext = The new extension
1102
1103    Returns: A string containing the _path given by $(D path), but where
1104    the extension has been set to $(D ext).
1105
1106    See_Also:
1107        $(LREF withExtension) which does not allocate and returns a lazy range.
1108*/
1109immutable(Unqual!C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext)
1110if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2))
1111{
1112    try
1113    {
1114        import std.conv : to;
1115        return withExtension(path, ext).to!(typeof(return));
1116    }
1117    catch (Exception e)
1118    {
1119        assert(0);
1120    }
1121}
1122
1123///ditto
1124immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext)
1125if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
1126{
1127    if (ext.length == 0)
1128        return stripExtension(path);
1129
1130    try
1131    {
1132        import std.conv : to;
1133        return withExtension(path, ext).to!(typeof(return));
1134    }
1135    catch (Exception e)
1136    {
1137        assert(0);
1138    }
1139}
1140
1141///
1142@safe unittest
1143{
1144    assert(setExtension("file", "ext") == "file.ext");
1145    assert(setExtension("file"w, ".ext"w) == "file.ext");
1146    assert(setExtension("file."d, "ext"d) == "file.ext");
1147    assert(setExtension("file.", ".ext") == "file.ext");
1148    assert(setExtension("file.old"w, "new"w) == "file.new");
1149    assert(setExtension("file.old"d, ".new"d) == "file.new");
1150}
1151
1152@safe unittest
1153{
1154    assert(setExtension("file"w.dup, "ext"w) == "file.ext");
1155    assert(setExtension("file"w.dup, ".ext"w) == "file.ext");
1156    assert(setExtension("file."w, "ext"w.dup) == "file.ext");
1157    assert(setExtension("file."w, ".ext"w.dup) == "file.ext");
1158    assert(setExtension("file.old"d.dup, "new"d) == "file.new");
1159    assert(setExtension("file.old"d.dup, ".new"d) == "file.new");
1160
1161    static assert(setExtension("file", "ext") == "file.ext");
1162    static assert(setExtension("file.old", "new") == "file.new");
1163
1164    static assert(setExtension("file"w.dup, "ext"w) == "file.ext");
1165    static assert(setExtension("file.old"d.dup, "new"d) == "file.new");
1166
1167    // Issue 10601
1168    assert(setExtension("file", "") == "file");
1169    assert(setExtension("file.ext", "") == "file");
1170}
1171
1172/************
1173 * Replace existing extension on filespec with new one.
1174 *
1175 * Params:
1176 *      path = string or random access range representing a filespec
1177 *      ext = the new extension
1178 * Returns:
1179 *      Range with $(D path)'s extension (if any) replaced with $(D ext).
1180 *      The element encoding type of the returned range will be the same as $(D path)'s.
1181 * See_Also:
1182 *      $(LREF setExtension)
1183 */
1184auto withExtension(R, C)(R path, C[] ext)
1185if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
1186    isNarrowString!R) &&
1187    !isConvertibleToString!R &&
1188    isSomeChar!C)
1189{
1190    import std.range : only, chain;
1191    import std.utf : byUTF;
1192
1193    alias CR = Unqual!(ElementEncodingType!R);
1194    auto dot = only(CR('.'));
1195    if (ext.length == 0 || ext[0] == '.')
1196        dot.popFront();                 // so dot is an empty range, too
1197    return chain(stripExtension(path).byUTF!CR, dot, ext.byUTF!CR);
1198}
1199
1200///
1201@safe unittest
1202{
1203    import std.array;
1204    assert(withExtension("file", "ext").array == "file.ext");
1205    assert(withExtension("file"w, ".ext"w).array == "file.ext");
1206    assert(withExtension("file.ext"w, ".").array == "file.");
1207
1208    import std.utf : byChar, byWchar;
1209    assert(withExtension("file".byChar, "ext").array == "file.ext");
1210    assert(withExtension("file"w.byWchar, ".ext"w).array == "file.ext"w);
1211    assert(withExtension("file.ext"w.byWchar, ".").array == "file."w);
1212}
1213
1214auto withExtension(R, C)(auto ref R path, C[] ext)
1215if (isConvertibleToString!R)
1216{
1217    return withExtension!(StringTypeOf!R)(path, ext);
1218}
1219
1220@safe unittest
1221{
1222    assert(testAliasedString!withExtension("file", "ext"));
1223}
1224
1225/** Params:
1226        path = A path name.
1227        ext = The default extension to use.
1228
1229    Returns: The _path given by $(D path), with the extension given by $(D ext)
1230    appended if the path doesn't already have one.
1231
1232    Including the dot in the extension is optional.
1233
1234    This function always allocates a new string, except in the case when
1235    path is immutable and already has an extension.
1236*/
1237immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext)
1238if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
1239{
1240    import std.conv : to;
1241    return withDefaultExtension(path, ext).to!(typeof(return));
1242}
1243
1244///
1245@safe unittest
1246{
1247    assert(defaultExtension("file", "ext") == "file.ext");
1248    assert(defaultExtension("file", ".ext") == "file.ext");
1249    assert(defaultExtension("file.", "ext")     == "file.");
1250    assert(defaultExtension("file.old", "new") == "file.old");
1251    assert(defaultExtension("file.old", ".new") == "file.old");
1252}
1253
1254@safe unittest
1255{
1256    assert(defaultExtension("file"w.dup, "ext"w) == "file.ext");
1257    assert(defaultExtension("file.old"d.dup, "new"d) == "file.old");
1258
1259    static assert(defaultExtension("file", "ext") == "file.ext");
1260    static assert(defaultExtension("file.old", "new") == "file.old");
1261
1262    static assert(defaultExtension("file"w.dup, "ext"w) == "file.ext");
1263    static assert(defaultExtension("file.old"d.dup, "new"d) == "file.old");
1264}
1265
1266
1267/********************************
1268 * Set the extension of $(D path) to $(D ext) if $(D path) doesn't have one.
1269 *
1270 * Params:
1271 *      path = filespec as string or range
1272 *      ext = extension, may have leading '.'
1273 * Returns:
1274 *      range with the result
1275 */
1276auto withDefaultExtension(R, C)(R path, C[] ext)
1277if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
1278    isNarrowString!R) &&
1279    !isConvertibleToString!R &&
1280    isSomeChar!C)
1281{
1282    import std.range : only, chain;
1283    import std.utf : byUTF;
1284
1285    alias CR = Unqual!(ElementEncodingType!R);
1286    auto dot = only(CR('.'));
1287    auto i = extSeparatorPos(path);
1288    if (i == -1)
1289    {
1290        if (ext.length > 0 && ext[0] == '.')
1291            ext = ext[1 .. $];              // remove any leading . from ext[]
1292    }
1293    else
1294    {
1295        // path already has an extension, so make these empty
1296        ext = ext[0 .. 0];
1297        dot.popFront();
1298    }
1299    return chain(path.byUTF!CR, dot, ext.byUTF!CR);
1300}
1301
1302///
1303@safe unittest
1304{
1305    import std.array;
1306    assert(withDefaultExtension("file", "ext").array == "file.ext");
1307    assert(withDefaultExtension("file"w, ".ext").array == "file.ext"w);
1308    assert(withDefaultExtension("file.", "ext").array == "file.");
1309    assert(withDefaultExtension("file", "").array == "file.");
1310
1311    import std.utf : byChar, byWchar;
1312    assert(withDefaultExtension("file".byChar, "ext").array == "file.ext");
1313    assert(withDefaultExtension("file"w.byWchar, ".ext").array == "file.ext"w);
1314    assert(withDefaultExtension("file.".byChar, "ext"d).array == "file.");
1315    assert(withDefaultExtension("file".byChar, "").array == "file.");
1316}
1317
1318auto withDefaultExtension(R, C)(auto ref R path, C[] ext)
1319if (isConvertibleToString!R)
1320{
1321    return withDefaultExtension!(StringTypeOf!R, C)(path, ext);
1322}
1323
1324@safe unittest
1325{
1326    assert(testAliasedString!withDefaultExtension("file", "ext"));
1327}
1328
1329/** Combines one or more path segments.
1330
1331    This function takes a set of path segments, given as an input
1332    range of string elements or as a set of string arguments,
1333    and concatenates them with each other.  Directory separators
1334    are inserted between segments if necessary.  If any of the
1335    path segments are absolute (as defined by $(LREF isAbsolute)), the
1336    preceding segments will be dropped.
1337
1338    On Windows, if one of the path segments are rooted, but not absolute
1339    (e.g. $(D `\foo`)), all preceding path segments down to the previous
1340    root will be dropped.  (See below for an example.)
1341
1342    This function always allocates memory to hold the resulting path.
1343    The variadic overload is guaranteed to only perform a single
1344    allocation, as is the range version if $(D paths) is a forward
1345    range.
1346
1347    Params:
1348        segments = An input range of segments to assemble the path from.
1349    Returns: The assembled path.
1350*/
1351immutable(ElementEncodingType!(ElementType!Range))[]
1352    buildPath(Range)(Range segments)
1353    if (isInputRange!Range && !isInfinite!Range && isSomeString!(ElementType!Range))
1354{
1355    if (segments.empty) return null;
1356
1357    // If this is a forward range, we can pre-calculate a maximum length.
1358    static if (isForwardRange!Range)
1359    {
1360        auto segments2 = segments.save;
1361        size_t precalc = 0;
1362        foreach (segment; segments2) precalc += segment.length + 1;
1363    }
1364    // Otherwise, just venture a guess and resize later if necessary.
1365    else size_t precalc = 255;
1366
1367    auto buf = new Unqual!(ElementEncodingType!(ElementType!Range))[](precalc);
1368    size_t pos = 0;
1369    foreach (segment; segments)
1370    {
1371        if (segment.empty) continue;
1372        static if (!isForwardRange!Range)
1373        {
1374            immutable neededLength = pos + segment.length + 1;
1375            if (buf.length < neededLength)
1376                buf.length = reserve(buf, neededLength + buf.length/2);
1377        }
1378        auto r = chainPath(buf[0 .. pos], segment);
1379        size_t i;
1380        foreach (c; r)
1381        {
1382            buf[i] = c;
1383            ++i;
1384        }
1385        pos = i;
1386    }
1387    static U trustedCast(U, V)(V v) @trusted pure nothrow { return cast(U) v; }
1388    return trustedCast!(typeof(return))(buf[0 .. pos]);
1389}
1390
1391/// ditto
1392immutable(C)[] buildPath(C)(const(C)[][] paths...)
1393    @safe pure nothrow
1394if (isSomeChar!C)
1395{
1396    return buildPath!(typeof(paths))(paths);
1397}
1398
1399///
1400@safe unittest
1401{
1402    version (Posix)
1403    {
1404        assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1405        assert(buildPath("/foo/", "bar/baz")  == "/foo/bar/baz");
1406        assert(buildPath("/foo", "/bar")      == "/bar");
1407    }
1408
1409    version (Windows)
1410    {
1411        assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1412        assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1413        assert(buildPath("foo", `d:\bar`)     == `d:\bar`);
1414        assert(buildPath("foo", `\bar`)       == `\bar`);
1415        assert(buildPath(`c:\foo`, `\bar`)    == `c:\bar`);
1416    }
1417}
1418
1419@system unittest // non-documented
1420{
1421    import std.range;
1422    // ir() wraps an array in a plain (i.e. non-forward) input range, so that
1423    // we can test both code paths
1424    InputRange!(C[]) ir(C)(C[][] p...) { return inputRangeObject(p); }
1425    version (Posix)
1426    {
1427        assert(buildPath("foo") == "foo");
1428        assert(buildPath("/foo/") == "/foo/");
1429        assert(buildPath("foo", "bar") == "foo/bar");
1430        assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1431        assert(buildPath("foo/".dup, "bar") == "foo/bar");
1432        assert(buildPath("foo///", "bar".dup) == "foo///bar");
1433        assert(buildPath("/foo"w, "bar"w) == "/foo/bar");
1434        assert(buildPath("foo"w.dup, "/bar"w) == "/bar");
1435        assert(buildPath("foo"w, "bar/"w.dup) == "foo/bar/");
1436        assert(buildPath("/"d, "foo"d) == "/foo");
1437        assert(buildPath(""d.dup, "foo"d) == "foo");
1438        assert(buildPath("foo"d, ""d.dup) == "foo");
1439        assert(buildPath("foo", "bar".dup, "baz") == "foo/bar/baz");
1440        assert(buildPath("foo"w, "/bar"w, "baz"w.dup) == "/bar/baz");
1441
1442        static assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1443        static assert(buildPath("foo", "/bar", "baz") == "/bar/baz");
1444
1445        // The following are mostly duplicates of the above, except that the
1446        // range version does not accept mixed constness.
1447        assert(buildPath(ir("foo")) == "foo");
1448        assert(buildPath(ir("/foo/")) == "/foo/");
1449        assert(buildPath(ir("foo", "bar")) == "foo/bar");
1450        assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1451        assert(buildPath(ir("foo/".dup, "bar".dup)) == "foo/bar");
1452        assert(buildPath(ir("foo///".dup, "bar".dup)) == "foo///bar");
1453        assert(buildPath(ir("/foo"w, "bar"w)) == "/foo/bar");
1454        assert(buildPath(ir("foo"w.dup, "/bar"w.dup)) == "/bar");
1455        assert(buildPath(ir("foo"w.dup, "bar/"w.dup)) == "foo/bar/");
1456        assert(buildPath(ir("/"d, "foo"d)) == "/foo");
1457        assert(buildPath(ir(""d.dup, "foo"d.dup)) == "foo");
1458        assert(buildPath(ir("foo"d, ""d)) == "foo");
1459        assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1460        assert(buildPath(ir("foo"w.dup, "/bar"w.dup, "baz"w.dup)) == "/bar/baz");
1461    }
1462    version (Windows)
1463    {
1464        assert(buildPath("foo") == "foo");
1465        assert(buildPath(`\foo/`) == `\foo/`);
1466        assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1467        assert(buildPath("foo", `\bar`) == `\bar`);
1468        assert(buildPath(`c:\foo`, "bar") == `c:\foo\bar`);
1469        assert(buildPath("foo"w, `d:\bar`w.dup) ==  `d:\bar`);
1470        assert(buildPath(`c:\foo\bar`, `\baz`) == `c:\baz`);
1471        assert(buildPath(`\\foo\bar\baz`d, `foo`d, `\bar`d) == `\\foo\bar\bar`d);
1472
1473        static assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1474        static assert(buildPath("foo", `c:\bar`, "baz") == `c:\bar\baz`);
1475
1476        assert(buildPath(ir("foo")) == "foo");
1477        assert(buildPath(ir(`\foo/`)) == `\foo/`);
1478        assert(buildPath(ir("foo", "bar", "baz")) == `foo\bar\baz`);
1479        assert(buildPath(ir("foo", `\bar`)) == `\bar`);
1480        assert(buildPath(ir(`c:\foo`, "bar")) == `c:\foo\bar`);
1481        assert(buildPath(ir("foo"w.dup, `d:\bar`w.dup)) ==  `d:\bar`);
1482        assert(buildPath(ir(`c:\foo\bar`, `\baz`)) == `c:\baz`);
1483        assert(buildPath(ir(`\\foo\bar\baz`d, `foo`d, `\bar`d)) == `\\foo\bar\bar`d);
1484    }
1485
1486    // Test that allocation works as it should.
1487    auto manyShort = "aaa".repeat(1000).array();
1488    auto manyShortCombined = join(manyShort, dirSeparator);
1489    assert(buildPath(manyShort) == manyShortCombined);
1490    assert(buildPath(ir(manyShort)) == manyShortCombined);
1491
1492    auto fewLong = 'b'.repeat(500).array().repeat(10).array();
1493    auto fewLongCombined = join(fewLong, dirSeparator);
1494    assert(buildPath(fewLong) == fewLongCombined);
1495    assert(buildPath(ir(fewLong)) == fewLongCombined);
1496}
1497
1498@safe unittest
1499{
1500    // Test for issue 7397
1501    string[] ary = ["a", "b"];
1502    version (Posix)
1503    {
1504        assert(buildPath(ary) == "a/b");
1505    }
1506    else version (Windows)
1507    {
1508        assert(buildPath(ary) == `a\b`);
1509    }
1510}
1511
1512
1513/**
1514 * Concatenate path segments together to form one path.
1515 *
1516 * Params:
1517 *      r1 = first segment
1518 *      r2 = second segment
1519 *      ranges = 0 or more segments
1520 * Returns:
1521 *      Lazy range which is the concatenation of r1, r2 and ranges with path separators.
1522 *      The resulting element type is that of r1.
1523 * See_Also:
1524 *      $(LREF buildPath)
1525 */
1526auto chainPath(R1, R2, Ranges...)(R1 r1, R2 r2, Ranges ranges)
1527if ((isRandomAccessRange!R1 && hasSlicing!R1 && hasLength!R1 && isSomeChar!(ElementType!R1) ||
1528    isNarrowString!R1 &&
1529    !isConvertibleToString!R1) &&
1530    (isRandomAccessRange!R2 && hasSlicing!R2 && hasLength!R2 && isSomeChar!(ElementType!R2) ||
1531    isNarrowString!R2 &&
1532    !isConvertibleToString!R2) &&
1533    (Ranges.length == 0 || is(typeof(chainPath(r2, ranges))))
1534    )
1535{
1536    static if (Ranges.length)
1537    {
1538        return chainPath(chainPath(r1, r2), ranges);
1539    }
1540    else
1541    {
1542        import std.range : only, chain;
1543        import std.utf : byUTF;
1544
1545        alias CR = Unqual!(ElementEncodingType!R1);
1546        auto sep = only(CR(dirSeparator[0]));
1547        bool usesep = false;
1548
1549        auto pos = r1.length;
1550
1551        if (pos)
1552        {
1553            if (isRooted(r2))
1554            {
1555                version (Posix)
1556                {
1557                    pos = 0;
1558                }
1559                else version (Windows)
1560                {
1561                    if (isAbsolute(r2))
1562                        pos = 0;
1563                    else
1564                    {
1565                        pos = rootName(r1).length;
1566                        if (pos > 0 && isDirSeparator(r1[pos - 1]))
1567                            --pos;
1568                    }
1569                }
1570                else
1571                    static assert(0);
1572            }
1573            else if (!isDirSeparator(r1[pos - 1]))
1574                usesep = true;
1575        }
1576        if (!usesep)
1577            sep.popFront();
1578        // Return r1 ~ '/' ~ r2
1579        return chain(r1[0 .. pos].byUTF!CR, sep, r2.byUTF!CR);
1580    }
1581}
1582
1583///
1584@safe unittest
1585{
1586    import std.array;
1587    version (Posix)
1588    {
1589        assert(chainPath("foo", "bar", "baz").array == "foo/bar/baz");
1590        assert(chainPath("/foo/", "bar/baz").array  == "/foo/bar/baz");
1591        assert(chainPath("/foo", "/bar").array      == "/bar");
1592    }
1593
1594    version (Windows)
1595    {
1596        assert(chainPath("foo", "bar", "baz").array == `foo\bar\baz`);
1597        assert(chainPath(`c:\foo`, `bar\baz`).array == `c:\foo\bar\baz`);
1598        assert(chainPath("foo", `d:\bar`).array     == `d:\bar`);
1599        assert(chainPath("foo", `\bar`).array       == `\bar`);
1600        assert(chainPath(`c:\foo`, `\bar`).array    == `c:\bar`);
1601    }
1602
1603    import std.utf : byChar;
1604    version (Posix)
1605    {
1606        assert(chainPath("foo", "bar", "baz").array == "foo/bar/baz");
1607        assert(chainPath("/foo/".byChar, "bar/baz").array  == "/foo/bar/baz");
1608        assert(chainPath("/foo", "/bar".byChar).array      == "/bar");
1609    }
1610
1611    version (Windows)
1612    {
1613        assert(chainPath("foo", "bar", "baz").array == `foo\bar\baz`);
1614        assert(chainPath(`c:\foo`.byChar, `bar\baz`).array == `c:\foo\bar\baz`);
1615        assert(chainPath("foo", `d:\bar`).array     == `d:\bar`);
1616        assert(chainPath("foo", `\bar`.byChar).array       == `\bar`);
1617        assert(chainPath(`c:\foo`, `\bar`w).array    == `c:\bar`);
1618    }
1619}
1620
1621auto chainPath(Ranges...)(auto ref Ranges ranges)
1622if (Ranges.length >= 2 &&
1623    std.meta.anySatisfy!(isConvertibleToString, Ranges))
1624{
1625    import std.meta : staticMap;
1626    alias Types = staticMap!(convertToString, Ranges);
1627    return chainPath!Types(ranges);
1628}
1629
1630@safe unittest
1631{
1632    assert(chainPath(TestAliasedString(null), TestAliasedString(null), TestAliasedString(null)).empty);
1633    assert(chainPath(TestAliasedString(null), TestAliasedString(null), "").empty);
1634    assert(chainPath(TestAliasedString(null), "", TestAliasedString(null)).empty);
1635    static struct S { string s; }
1636    static assert(!__traits(compiles, chainPath(TestAliasedString(null), S(""), TestAliasedString(null))));
1637}
1638
1639/** Performs the same task as $(LREF buildPath),
1640    while at the same time resolving current/parent directory
1641    symbols ($(D ".") and $(D "..")) and removing superfluous
1642    directory separators.
1643    It will return "." if the path leads to the starting directory.
1644    On Windows, slashes are replaced with backslashes.
1645
1646    Using buildNormalizedPath on null paths will always return null.
1647
1648    Note that this function does not resolve symbolic links.
1649
1650    This function always allocates memory to hold the resulting path.
1651    Use $(LREF asNormalizedPath) to not allocate memory.
1652
1653    Params:
1654        paths = An array of paths to assemble.
1655
1656    Returns: The assembled path.
1657*/
1658immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...)
1659    @trusted pure nothrow
1660if (isSomeChar!C)
1661{
1662    import std.array : array;
1663
1664    const(C)[] result;
1665    foreach (path; paths)
1666    {
1667        if (result)
1668            result = chainPath(result, path).array;
1669        else
1670            result = path;
1671    }
1672    result = asNormalizedPath(result).array;
1673    return cast(typeof(return)) result;
1674}
1675
1676///
1677@safe unittest
1678{
1679    assert(buildNormalizedPath("foo", "..") == ".");
1680
1681    version (Posix)
1682    {
1683        assert(buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz");
1684        assert(buildNormalizedPath("../foo/.") == "../foo");
1685        assert(buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
1686        assert(buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
1687        assert(buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
1688        assert(buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
1689    }
1690
1691    version (Windows)
1692    {
1693        assert(buildNormalizedPath(`c:\foo\.\bar/..\\baz\`) == `c:\foo\baz`);
1694        assert(buildNormalizedPath(`..\foo\.`) == `..\foo`);
1695        assert(buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
1696        assert(buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
1697        assert(buildNormalizedPath(`\\server\share\foo`, `..\bar`) ==
1698                `\\server\share\bar`);
1699    }
1700}
1701
1702@safe unittest
1703{
1704    assert(buildNormalizedPath(".", ".") == ".");
1705    assert(buildNormalizedPath("foo", "..") == ".");
1706    assert(buildNormalizedPath("", "") is null);
1707    assert(buildNormalizedPath("", ".") == ".");
1708    assert(buildNormalizedPath(".", "") == ".");
1709    assert(buildNormalizedPath(null, "foo") == "foo");
1710    assert(buildNormalizedPath("", "foo") == "foo");
1711    assert(buildNormalizedPath("", "") == "");
1712    assert(buildNormalizedPath("", null) == "");
1713    assert(buildNormalizedPath(null, "") == "");
1714    assert(buildNormalizedPath!(char)(null, null) == "");
1715
1716    version (Posix)
1717    {
1718        assert(buildNormalizedPath("/", "foo", "bar") == "/foo/bar");
1719        assert(buildNormalizedPath("foo", "bar", "baz") == "foo/bar/baz");
1720        assert(buildNormalizedPath("foo", "bar/baz") == "foo/bar/baz");
1721        assert(buildNormalizedPath("foo", "bar//baz///") == "foo/bar/baz");
1722        assert(buildNormalizedPath("/foo", "bar/baz") == "/foo/bar/baz");
1723        assert(buildNormalizedPath("/foo", "/bar/baz") == "/bar/baz");
1724        assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1725        assert(buildNormalizedPath("/foo/..", "bar/baz") == "/bar/baz");
1726        assert(buildNormalizedPath("/foo/../../", "bar/baz") == "/bar/baz");
1727        assert(buildNormalizedPath("/foo/bar", "../baz") == "/foo/baz");
1728        assert(buildNormalizedPath("/foo/bar", "../../baz") == "/baz");
1729        assert(buildNormalizedPath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee");
1730        assert(buildNormalizedPath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee");
1731        static assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1732    }
1733    else version (Windows)
1734    {
1735        assert(buildNormalizedPath(`\`, `foo`, `bar`) == `\foo\bar`);
1736        assert(buildNormalizedPath(`foo`, `bar`, `baz`) == `foo\bar\baz`);
1737        assert(buildNormalizedPath(`foo`, `bar\baz`) == `foo\bar\baz`);
1738        assert(buildNormalizedPath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`);
1739        assert(buildNormalizedPath(`\foo`, `bar\baz`) == `\foo\bar\baz`);
1740        assert(buildNormalizedPath(`\foo`, `\bar\baz`) == `\bar\baz`);
1741        assert(buildNormalizedPath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`);
1742        assert(buildNormalizedPath(`\foo\..`, `bar\baz`) == `\bar\baz`);
1743        assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1744        assert(buildNormalizedPath(`\foo\bar`, `..\baz`) == `\foo\baz`);
1745        assert(buildNormalizedPath(`\foo\bar`, `../../baz`) == `\baz`);
1746        assert(buildNormalizedPath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`);
1747
1748        assert(buildNormalizedPath(`c:\`, `foo`, `bar`) == `c:\foo\bar`);
1749        assert(buildNormalizedPath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`);
1750        assert(buildNormalizedPath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`);
1751        assert(buildNormalizedPath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`);
1752        assert(buildNormalizedPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1753        assert(buildNormalizedPath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`);
1754        assert(buildNormalizedPath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`);
1755        assert(buildNormalizedPath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`);
1756        assert(buildNormalizedPath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`);
1757        assert(buildNormalizedPath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`);
1758        assert(buildNormalizedPath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`);
1759        assert(buildNormalizedPath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`);
1760
1761        assert(buildNormalizedPath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`);
1762        assert(buildNormalizedPath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`);
1763        assert(buildNormalizedPath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`);
1764        assert(buildNormalizedPath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`);
1765        assert(buildNormalizedPath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`);
1766        assert(buildNormalizedPath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`);
1767        assert(buildNormalizedPath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`);
1768        assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`);
1769        assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`);
1770        assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`);
1771
1772        static assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1773    }
1774    else static assert(0);
1775}
1776
1777@safe unittest
1778{
1779    // Test for issue 7397
1780    string[] ary = ["a", "b"];
1781    version (Posix)
1782    {
1783        assert(buildNormalizedPath(ary) == "a/b");
1784    }
1785    else version (Windows)
1786    {
1787        assert(buildNormalizedPath(ary) == `a\b`);
1788    }
1789}
1790
1791
1792/** Normalize a path by resolving current/parent directory
1793    symbols ($(D ".") and $(D "..")) and removing superfluous
1794    directory separators.
1795    It will return "." if the path leads to the starting directory.
1796    On Windows, slashes are replaced with backslashes.
1797
1798    Using asNormalizedPath on empty paths will always return an empty path.
1799
1800    Does not resolve symbolic links.
1801
1802    This function always allocates memory to hold the resulting path.
1803    Use $(LREF buildNormalizedPath) to allocate memory and return a string.
1804
1805    Params:
1806        path = string or random access range representing the _path to normalize
1807
1808    Returns:
1809        normalized path as a forward range
1810*/
1811
1812auto asNormalizedPath(R)(R path)
1813if (isSomeChar!(ElementEncodingType!R) &&
1814    (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R) &&
1815    !isConvertibleToString!R)
1816{
1817    alias C = Unqual!(ElementEncodingType!R);
1818    alias S = typeof(path[0 .. 0]);
1819
1820    static struct Result
1821    {
1822        @property bool empty()
1823        {
1824            return c == c.init;
1825        }
1826
1827        @property C front()
1828        {
1829            return c;
1830        }
1831
1832        void popFront()
1833        {
1834            C lastc = c;
1835            c = c.init;
1836            if (!element.empty)
1837            {
1838                c = getElement0();
1839                return;
1840            }
1841          L1:
1842            while (1)
1843            {
1844                if (elements.empty)
1845                {
1846                    element = element[0 .. 0];
1847                    return;
1848                }
1849                element = elements.front;
1850                elements.popFront();
1851                if (isDot(element) || (rooted && isDotDot(element)))
1852                    continue;
1853
1854                if (rooted || !isDotDot(element))
1855                {
1856                    int n = 1;
1857                    auto elements2 = elements.save;
1858                    while (!elements2.empty)
1859                    {
1860                        auto e = elements2.front;
1861                        elements2.popFront();
1862                        if (isDot(e))
1863                            continue;
1864                        if (isDotDot(e))
1865                        {
1866                            --n;
1867                            if (n == 0)
1868                            {
1869                                elements = elements2;
1870                                element = element[0 .. 0];
1871                                continue L1;
1872                            }
1873                        }
1874                        else
1875                            ++n;
1876                    }
1877                }
1878                break;
1879            }
1880
1881            static assert(dirSeparator.length == 1);
1882            if (lastc == dirSeparator[0] || lastc == lastc.init)
1883                c = getElement0();
1884            else
1885                c = dirSeparator[0];
1886        }
1887
1888        static if (isForwardRange!R)
1889        {
1890            @property auto save()
1891            {
1892                auto result = this;
1893                result.element = element.save;
1894                result.elements = elements.save;
1895                return result;
1896            }
1897        }
1898
1899      private:
1900        this(R path)
1901        {
1902            element = rootName(path);
1903            auto i = element.length;
1904            while (i < path.length && isDirSeparator(path[i]))
1905                ++i;
1906            rooted = i > 0;
1907            elements = pathSplitter(path[i .. $]);
1908            popFront();
1909            if (c == c.init && path.length)
1910                c = C('.');
1911        }
1912
1913        C getElement0()
1914        {
1915            static if (isNarrowString!S)  // avoid autodecode
1916            {
1917                C c = element[0];
1918                element = element[1 .. $];
1919            }
1920            else
1921            {
1922                C c = element.front;
1923                element.popFront();
1924            }
1925            version (Windows)
1926            {
1927                if (c == '/')   // can appear in root element
1928                    c = '\\';   // use native Windows directory separator
1929            }
1930            return c;
1931        }
1932
1933        // See if elem is "."
1934        static bool isDot(S elem)
1935        {
1936            return elem.length == 1 && elem[0] == '.';
1937        }
1938
1939        // See if elem is ".."
1940        static bool isDotDot(S elem)
1941        {
1942            return elem.length == 2 && elem[0] == '.' && elem[1] == '.';
1943        }
1944
1945        bool rooted;    // the path starts with a root directory
1946        C c;
1947        S element;
1948        typeof(pathSplitter(path[0 .. 0])) elements;
1949    }
1950
1951    return Result(path);
1952}
1953
1954///
1955@safe unittest
1956{
1957    import std.array;
1958    assert(asNormalizedPath("foo/..").array == ".");
1959
1960    version (Posix)
1961    {
1962        assert(asNormalizedPath("/foo/./bar/..//baz/").array == "/foo/baz");
1963        assert(asNormalizedPath("../foo/.").array == "../foo");
1964        assert(asNormalizedPath("/foo/bar/baz/").array == "/foo/bar/baz");
1965        assert(asNormalizedPath("/foo/./bar/../../baz").array == "/baz");
1966    }
1967
1968    version (Windows)
1969    {
1970        assert(asNormalizedPath(`c:\foo\.\bar/..\\baz\`).array == `c:\foo\baz`);
1971        assert(asNormalizedPath(`..\foo\.`).array == `..\foo`);
1972        assert(asNormalizedPath(`c:\foo\bar\baz\`).array == `c:\foo\bar\baz`);
1973        assert(asNormalizedPath(`c:\foo\bar/..`).array == `c:\foo`);
1974        assert(asNormalizedPath(`\\server\share\foo\..\bar`).array ==
1975                `\\server\share\bar`);
1976    }
1977}
1978
1979auto asNormalizedPath(R)(auto ref R path)
1980if (isConvertibleToString!R)
1981{
1982    return asNormalizedPath!(StringTypeOf!R)(path);
1983}
1984
1985@safe unittest
1986{
1987    assert(testAliasedString!asNormalizedPath(null));
1988}
1989
1990@safe unittest
1991{
1992    import std.array;
1993    import std.utf : byChar;
1994
1995    assert(asNormalizedPath("").array is null);
1996    assert(asNormalizedPath("foo").array == "foo");
1997    assert(asNormalizedPath(".").array == ".");
1998    assert(asNormalizedPath("./.").array == ".");
1999    assert(asNormalizedPath("foo/..").array == ".");
2000
2001    auto save = asNormalizedPath("fob").save;
2002    save.popFront();
2003    assert(save.front == 'o');
2004
2005    version (Posix)
2006    {
2007        assert(asNormalizedPath("/foo/bar").array == "/foo/bar");
2008        assert(asNormalizedPath("foo/bar/baz").array == "foo/bar/baz");
2009        assert(asNormalizedPath("foo/bar/baz").array == "foo/bar/baz");
2010        assert(asNormalizedPath("foo/bar//baz///").array == "foo/bar/baz");
2011        assert(asNormalizedPath("/foo/bar/baz").array == "/foo/bar/baz");
2012        assert(asNormalizedPath("/foo/../bar/baz").array == "/bar/baz");
2013        assert(asNormalizedPath("/foo/../..//bar/baz").array == "/bar/baz");
2014        assert(asNormalizedPath("/foo/bar/../baz").array == "/foo/baz");
2015        assert(asNormalizedPath("/foo/bar/../../baz").array == "/baz");
2016        assert(asNormalizedPath("/foo/bar/.././/baz/../wee/").array == "/foo/wee");
2017        assert(asNormalizedPath("//foo/bar/baz///wee").array == "/foo/bar/baz/wee");
2018
2019        assert(asNormalizedPath("foo//bar").array == "foo/bar");
2020        assert(asNormalizedPath("foo/bar").array == "foo/bar");
2021
2022        //Curent dir path
2023        assert(asNormalizedPath("./").array == ".");
2024        assert(asNormalizedPath("././").array == ".");
2025        assert(asNormalizedPath("./foo/..").array == ".");
2026        assert(asNormalizedPath("foo/..").array == ".");
2027    }
2028    else version (Windows)
2029    {
2030        assert(asNormalizedPath(`\foo\bar`).array == `\foo\bar`);
2031        assert(asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`);
2032        assert(asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`);
2033        assert(asNormalizedPath(`foo\bar\\baz\\\`).array == `foo\bar\baz`);
2034        assert(asNormalizedPath(`\foo\bar\baz`).array == `\foo\bar\baz`);
2035        assert(asNormalizedPath(`\foo\..\\bar\.\baz`).array == `\bar\baz`);
2036        assert(asNormalizedPath(`\foo\..\bar\baz`).array == `\bar\baz`);
2037        assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`);
2038
2039        assert(asNormalizedPath(`\foo\bar\..\baz`).array == `\foo\baz`);
2040        assert(asNormalizedPath(`\foo\bar\../../baz`).array == `\baz`);
2041        assert(asNormalizedPath(`\foo\bar\..\.\/baz\..\wee\`).array == `\foo\wee`);
2042
2043        assert(asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`);
2044        assert(asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`);
2045        assert(asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`);
2046        assert(asNormalizedPath(`c:foo\bar\\baz\\\`).array == `c:foo\bar\baz`);
2047        assert(asNormalizedPath(`c:\foo\bar\baz`).array == `c:\foo\bar\baz`);
2048
2049        assert(asNormalizedPath(`c:\foo\..\\bar\.\baz`).array == `c:\bar\baz`);
2050        assert(asNormalizedPath(`c:\foo\..\bar\baz`).array == `c:\bar\baz`);
2051        assert(asNormalizedPath(`c:\foo\..\..\\bar\baz`).array == `c:\bar\baz`);
2052        assert(asNormalizedPath(`c:\foo\bar\..\baz`).array == `c:\foo\baz`);
2053        assert(asNormalizedPath(`c:\foo\bar\..\..\baz`).array == `c:\baz`);
2054        assert(asNormalizedPath(`c:\foo\bar\..\.\\baz\..\wee\`).array == `c:\foo\wee`);
2055        assert(asNormalizedPath(`\\server\share\foo\bar`).array == `\\server\share\foo\bar`);
2056        assert(asNormalizedPath(`\\server\share\\foo\bar`).array == `\\server\share\foo\bar`);
2057        assert(asNormalizedPath(`\\server\share\foo\bar\baz`).array == `\\server\share\foo\bar\baz`);
2058        assert(asNormalizedPath(`\\server\share\foo\..\\bar\.\baz`).array == `\\server\share\bar\baz`);
2059        assert(asNormalizedPath(`\\server\share\foo\..\bar\baz`).array == `\\server\share\bar\baz`);
2060        assert(asNormalizedPath(`\\server\share\foo\..\..\\bar\baz`).array == `\\server\share\bar\baz`);
2061        assert(asNormalizedPath(`\\server\share\foo\bar\..\baz`).array == `\\server\share\foo\baz`);
2062        assert(asNormalizedPath(`\\server\share\foo\bar\..\..\baz`).array == `\\server\share\baz`);
2063        assert(asNormalizedPath(`\\server\share\foo\bar\..\.\\baz\..\wee\`).array == `\\server\share\foo\wee`);
2064
2065        static assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`);
2066
2067        assert(asNormalizedPath("foo//bar").array == `foo\bar`);
2068
2069        //Curent dir path
2070        assert(asNormalizedPath(`.\`).array == ".");
2071        assert(asNormalizedPath(`.\.\`).array == ".");
2072        assert(asNormalizedPath(`.\foo\..`).array == ".");
2073        assert(asNormalizedPath(`foo\..`).array == ".");
2074    }
2075    else static assert(0);
2076}
2077
2078@safe unittest
2079{
2080    import std.array;
2081
2082    version (Posix)
2083    {
2084        // Trivial
2085        assert(asNormalizedPath("").empty);
2086        assert(asNormalizedPath("foo/bar").array == "foo/bar");
2087
2088        // Correct handling of leading slashes
2089        assert(asNormalizedPath("/").array == "/");
2090        assert(asNormalizedPath("///").array == "/");
2091        assert(asNormalizedPath("////").array == "/");
2092        assert(asNormalizedPath("/foo/bar").array == "/foo/bar");
2093        assert(asNormalizedPath("//foo/bar").array == "/foo/bar");
2094        assert(asNormalizedPath("///foo/bar").array == "/foo/bar");
2095        assert(asNormalizedPath("////foo/bar").array == "/foo/bar");
2096
2097        // Correct handling of single-dot symbol (current directory)
2098        assert(asNormalizedPath("/./foo").array == "/foo");
2099        assert(asNormalizedPath("/foo/./bar").array == "/foo/bar");
2100
2101        assert(asNormalizedPath("./foo").array == "foo");
2102        assert(asNormalizedPath("././foo").array == "foo");
2103        assert(asNormalizedPath("foo/././bar").array == "foo/bar");
2104
2105        // Correct handling of double-dot symbol (previous directory)
2106        assert(asNormalizedPath("/foo/../bar").array == "/bar");
2107        assert(asNormalizedPath("/foo/../../bar").array == "/bar");
2108        assert(asNormalizedPath("/../foo").array == "/foo");
2109        assert(asNormalizedPath("/../../foo").array == "/foo");
2110        assert(asNormalizedPath("/foo/..").array == "/");
2111        assert(asNormalizedPath("/foo/../..").array == "/");
2112
2113        assert(asNormalizedPath("foo/../bar").array == "bar");
2114        assert(asNormalizedPath("foo/../../bar").array == "../bar");
2115        assert(asNormalizedPath("../foo").array == "../foo");
2116        assert(asNormalizedPath("../../foo").array == "../../foo");
2117        assert(asNormalizedPath("../foo/../bar").array == "../bar");
2118        assert(asNormalizedPath(".././../foo").array == "../../foo");
2119        assert(asNormalizedPath("foo/bar/..").array == "foo");
2120        assert(asNormalizedPath("/foo/../..").array == "/");
2121
2122        // The ultimate path
2123        assert(asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz");
2124        static assert(asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz");
2125    }
2126    else version (Windows)
2127    {
2128        // Trivial
2129        assert(asNormalizedPath("").empty);
2130        assert(asNormalizedPath(`foo\bar`).array == `foo\bar`);
2131        assert(asNormalizedPath("foo/bar").array == `foo\bar`);
2132
2133        // Correct handling of absolute paths
2134        assert(asNormalizedPath("/").array == `\`);
2135        assert(asNormalizedPath(`\`).array == `\`);
2136        assert(asNormalizedPath(`\\\`).array == `\`);
2137        assert(asNormalizedPath(`\\\\`).array == `\`);
2138        assert(asNormalizedPath(`\foo\bar`).array == `\foo\bar`);
2139        assert(asNormalizedPath(`\\foo`).array == `\\foo`);
2140        assert(asNormalizedPath(`\\foo\\`).array == `\\foo`);
2141        assert(asNormalizedPath(`\\foo/bar`).array == `\\foo\bar`);
2142        assert(asNormalizedPath(`\\\foo\bar`).array == `\foo\bar`);
2143        assert(asNormalizedPath(`\\\\foo\bar`).array == `\foo\bar`);
2144        assert(asNormalizedPath(`c:\`).array == `c:\`);
2145        assert(asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`);
2146        assert(asNormalizedPath(`c:\\foo\bar`).array == `c:\foo\bar`);
2147
2148        // Correct handling of single-dot symbol (current directory)
2149        assert(asNormalizedPath(`\./foo`).array == `\foo`);
2150        assert(asNormalizedPath(`\foo/.\bar`).array == `\foo\bar`);
2151
2152        assert(asNormalizedPath(`.\foo`).array == `foo`);
2153        assert(asNormalizedPath(`./.\foo`).array == `foo`);
2154        assert(asNormalizedPath(`foo\.\./bar`).array == `foo\bar`);
2155
2156        // Correct handling of double-dot symbol (previous directory)
2157        assert(asNormalizedPath(`\foo\..\bar`).array == `\bar`);
2158        assert(asNormalizedPath(`\foo\../..\bar`).array == `\bar`);
2159        assert(asNormalizedPath(`\..\foo`).array == `\foo`);
2160        assert(asNormalizedPath(`\..\..\foo`).array == `\foo`);
2161        assert(asNormalizedPath(`\foo\..`).array == `\`);
2162        assert(asNormalizedPath(`\foo\../..`).array == `\`);
2163
2164        assert(asNormalizedPath(`foo\..\bar`).array == `bar`);
2165        assert(asNormalizedPath(`foo\..\../bar`).array == `..\bar`);
2166
2167        assert(asNormalizedPath(`..\foo`).array == `..\foo`);
2168        assert(asNormalizedPath(`..\..\foo`).array == `..\..\foo`);
2169        assert(asNormalizedPath(`..\foo\..\bar`).array == `..\bar`);
2170        assert(asNormalizedPath(`..\.\..\foo`).array == `..\..\foo`);
2171        assert(asNormalizedPath(`foo\bar\..`).array == `foo`);
2172        assert(asNormalizedPath(`\foo\..\..`).array == `\`);
2173        assert(asNormalizedPath(`c:\foo\..\..`).array == `c:\`);
2174
2175        // Correct handling of non-root path with drive specifier
2176        assert(asNormalizedPath(`c:foo`).array == `c:foo`);
2177        assert(asNormalizedPath(`c:..\foo\.\..\bar`).array == `c:..\bar`);
2178
2179        // The ultimate path
2180        assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`);
2181        static assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`);
2182    }
2183    else static assert(false);
2184}
2185
2186/** Slice up a path into its elements.
2187
2188    Params:
2189        path = string or slicable random access range
2190
2191    Returns:
2192        bidirectional range of slices of `path`
2193*/
2194auto pathSplitter(R)(R path)
2195if ((isRandomAccessRange!R && hasSlicing!R ||
2196    isNarrowString!R) &&
2197    !isConvertibleToString!R)
2198{
2199    static struct PathSplitter
2200    {
2201        @property bool empty() const { return pe == 0; }
2202
2203        @property R front()
2204        {
2205            assert(!empty);
2206            return _path[fs .. fe];
2207        }
2208
2209        void popFront()
2210        {
2211            assert(!empty);
2212            if (ps == pe)
2213            {
2214                if (fs == bs && fe == be)
2215                {
2216                    pe = 0;
2217                }
2218                else
2219                {
2220                    fs = bs;
2221                    fe = be;
2222                }
2223            }
2224            else
2225            {
2226                fs = ps;
2227                fe = fs;
2228                while (fe < pe && !isDirSeparator(_path[fe]))
2229                    ++fe;
2230                ps = ltrim(fe, pe);
2231            }
2232        }
2233
2234        @property R back()
2235        {
2236            assert(!empty);
2237            return _path[bs .. be];
2238        }
2239
2240        void popBack()
2241        {
2242            assert(!empty);
2243            if (ps == pe)
2244            {
2245                if (fs == bs && fe == be)
2246                {
2247                    pe = 0;
2248                }
2249                else
2250                {
2251                    bs = fs;
2252                    be = fe;
2253                }
2254            }
2255            else
2256            {
2257                bs = pe;
2258                be = bs;
2259                while (bs > ps && !isDirSeparator(_path[bs - 1]))
2260                    --bs;
2261                pe = rtrim(ps, bs);
2262            }
2263        }
2264        @property auto save() { return this; }
2265
2266
2267    private:
2268        R _path;
2269        size_t ps, pe;
2270        size_t fs, fe;
2271        size_t bs, be;
2272
2273        this(R p)
2274        {
2275            if (p.empty)
2276            {
2277                pe = 0;
2278                return;
2279            }
2280            _path = p;
2281
2282            ps = 0;
2283            pe = _path.length;
2284
2285            // If path is rooted, first element is special
2286            version (Windows)
2287            {
2288                if (isUNC(_path))
2289                {
2290                    auto i = uncRootLength(_path);
2291                    fs = 0;
2292                    fe = i;
2293                    ps = ltrim(fe, pe);
2294                }
2295                else if (isDriveRoot(_path))
2296                {
2297                    fs = 0;
2298                    fe = 3;
2299                    ps = ltrim(fe, pe);
2300                }
2301                else if (_path.length >= 1 && isDirSeparator(_path[0]))
2302                {
2303                    fs = 0;
2304                    fe = 1;
2305                    ps = ltrim(fe, pe);
2306                }
2307                else
2308                {
2309                    assert(!isRooted(_path));
2310                    popFront();
2311                }
2312            }
2313            else version (Posix)
2314            {
2315                if (_path.length >= 1 && isDirSeparator(_path[0]))
2316                {
2317                    fs = 0;
2318                    fe = 1;
2319                    ps = ltrim(fe, pe);
2320                }
2321                else
2322                {
2323                    popFront();
2324                }
2325            }
2326            else static assert(0);
2327
2328            if (ps == pe)
2329            {
2330                bs = fs;
2331                be = fe;
2332            }
2333            else
2334            {
2335                pe = rtrim(ps, pe);
2336                popBack();
2337            }
2338        }
2339
2340        size_t ltrim(size_t s, size_t e)
2341        {
2342            while (s < e && isDirSeparator(_path[s]))
2343                ++s;
2344            return s;
2345        }
2346
2347        size_t rtrim(size_t s, size_t e)
2348        {
2349            while (s < e && isDirSeparator(_path[e - 1]))
2350                --e;
2351            return e;
2352        }
2353    }
2354
2355    return PathSplitter(path);
2356}
2357
2358///
2359@safe unittest
2360{
2361    import std.algorithm.comparison : equal;
2362    import std.conv : to;
2363
2364    assert(equal(pathSplitter("/"), ["/"]));
2365    assert(equal(pathSplitter("/foo/bar"), ["/", "foo", "bar"]));
2366    assert(equal(pathSplitter("foo/../bar//./"), ["foo", "..", "bar", "."]));
2367
2368    version (Posix)
2369    {
2370        assert(equal(pathSplitter("//foo/bar"), ["/", "foo", "bar"]));
2371    }
2372
2373    version (Windows)
2374    {
2375        assert(equal(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
2376        assert(equal(pathSplitter("c:"), ["c:"]));
2377        assert(equal(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
2378        assert(equal(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
2379    }
2380}
2381
2382auto pathSplitter(R)(auto ref R path)
2383if (isConvertibleToString!R)
2384{
2385    return pathSplitter!(StringTypeOf!R)(path);
2386}
2387
2388@safe unittest
2389{
2390    import std.algorithm.comparison : equal;
2391    assert(testAliasedString!pathSplitter("/"));
2392}
2393
2394@safe unittest
2395{
2396    // equal2 verifies that the range is the same both ways, i.e.
2397    // through front/popFront and back/popBack.
2398    import std.algorithm;
2399    import std.range;
2400    bool equal2(R1, R2)(R1 r1, R2 r2)
2401    {
2402        static assert(isBidirectionalRange!R1);
2403        return equal(r1, r2) && equal(retro(r1), retro(r2));
2404    }
2405
2406    assert(pathSplitter("").empty);
2407
2408    // Root directories
2409    assert(equal2(pathSplitter("/"), ["/"]));
2410    assert(equal2(pathSplitter("//"), ["/"]));
2411    assert(equal2(pathSplitter("///"w), ["/"w]));
2412
2413    // Absolute paths
2414    assert(equal2(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"]));
2415
2416    // General
2417    assert(equal2(pathSplitter("foo/bar"d.dup), ["foo"d, "bar"d]));
2418    assert(equal2(pathSplitter("foo//bar"), ["foo", "bar"]));
2419    assert(equal2(pathSplitter("foo/bar//"w), ["foo"w, "bar"w]));
2420    assert(equal2(pathSplitter("foo/../bar//./"d), ["foo"d, ".."d, "bar"d, "."d]));
2421
2422    // save()
2423    auto ps1 = pathSplitter("foo/bar/baz");
2424    auto ps2 = ps1.save;
2425    ps1.popFront();
2426    assert(equal2(ps1, ["bar", "baz"]));
2427    assert(equal2(ps2, ["foo", "bar", "baz"]));
2428
2429    // Platform specific
2430    version (Posix)
2431    {
2432        assert(equal2(pathSplitter("//foo/bar"w.dup), ["/"w, "foo"w, "bar"w]));
2433    }
2434    version (Windows)
2435    {
2436        assert(equal2(pathSplitter(`\`), [`\`]));
2437        assert(equal2(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
2438        assert(equal2(pathSplitter("c:"), ["c:"]));
2439        assert(equal2(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
2440        assert(equal2(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
2441        assert(equal2(pathSplitter(`\\foo\bar`), [`\\foo\bar`]));
2442        assert(equal2(pathSplitter(`\\foo\bar\\`), [`\\foo\bar`]));
2443        assert(equal2(pathSplitter(`\\foo\bar\baz`), [`\\foo\bar`, "baz"]));
2444    }
2445
2446    import std.exception;
2447    assertCTFEable!(
2448    {
2449        assert(equal(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"]));
2450    });
2451
2452    static assert(is(typeof(pathSplitter!(const(char)[])(null).front) == const(char)[]));
2453
2454    import std.utf : byDchar;
2455    assert(equal2(pathSplitter("foo/bar"d.byDchar), ["foo"d, "bar"d]));
2456}
2457
2458
2459
2460
2461/** Determines whether a path starts at a root directory.
2462
2463    Params: path = A path name.
2464    Returns: Whether a path starts at a root directory.
2465
2466    On POSIX, this function returns true if and only if the path starts
2467    with a slash (/).
2468    ---
2469    version (Posix)
2470    {
2471        assert(isRooted("/"));
2472        assert(isRooted("/foo"));
2473        assert(!isRooted("foo"));
2474        assert(!isRooted("../foo"));
2475    }
2476    ---
2477
2478    On Windows, this function returns true if the path starts at
2479    the root directory of the current drive, of some other drive,
2480    or of a network drive.
2481    ---
2482    version (Windows)
2483    {
2484        assert(isRooted(`\`));
2485        assert(isRooted(`\foo`));
2486        assert(isRooted(`d:\foo`));
2487        assert(isRooted(`\\foo\bar`));
2488        assert(!isRooted("foo"));
2489        assert(!isRooted("d:foo"));
2490    }
2491    ---
2492*/
2493bool isRooted(R)(R path)
2494if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2495    is(StringTypeOf!R))
2496{
2497    if (path.length >= 1 && isDirSeparator(path[0])) return true;
2498    version (Posix)         return false;
2499    else version (Windows)  return isAbsolute!(BaseOf!R)(path);
2500}
2501
2502
2503@safe unittest
2504{
2505    assert(isRooted("/"));
2506    assert(isRooted("/foo"));
2507    assert(!isRooted("foo"));
2508    assert(!isRooted("../foo"));
2509
2510    version (Windows)
2511    {
2512    assert(isRooted(`\`));
2513    assert(isRooted(`\foo`));
2514    assert(isRooted(`d:\foo`));
2515    assert(isRooted(`\\foo\bar`));
2516    assert(!isRooted("foo"));
2517    assert(!isRooted("d:foo"));
2518    }
2519
2520    static assert(isRooted("/foo"));
2521    static assert(!isRooted("foo"));
2522
2523    static struct DirEntry { string s; alias s this; }
2524    assert(!isRooted(DirEntry("foo")));
2525}
2526
2527
2528
2529
2530/** Determines whether a path is absolute or not.
2531
2532    Params: path = A path name.
2533
2534    Returns: Whether a path is absolute or not.
2535
2536    Example:
2537    On POSIX, an absolute path starts at the root directory.
2538    (In fact, $(D _isAbsolute) is just an alias for $(LREF isRooted).)
2539    ---
2540    version (Posix)
2541    {
2542        assert(isAbsolute("/"));
2543        assert(isAbsolute("/foo"));
2544        assert(!isAbsolute("foo"));
2545        assert(!isAbsolute("../foo"));
2546    }
2547    ---
2548
2549    On Windows, an absolute path starts at the root directory of
2550    a specific drive.  Hence, it must start with $(D `d:\`) or $(D `d:/`),
2551    where $(D d) is the drive letter.  Alternatively, it may be a
2552    network path, i.e. a path starting with a double (back)slash.
2553    ---
2554    version (Windows)
2555    {
2556        assert(isAbsolute(`d:\`));
2557        assert(isAbsolute(`d:\foo`));
2558        assert(isAbsolute(`\\foo\bar`));
2559        assert(!isAbsolute(`\`));
2560        assert(!isAbsolute(`\foo`));
2561        assert(!isAbsolute("d:foo"));
2562    }
2563    ---
2564*/
2565version (StdDdoc)
2566{
2567    bool isAbsolute(R)(R path) pure nothrow @safe
2568    if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2569        is(StringTypeOf!R));
2570}
2571else version (Windows)
2572{
2573    bool isAbsolute(R)(R path)
2574    if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2575        is(StringTypeOf!R))
2576    {
2577        return isDriveRoot!(BaseOf!R)(path) || isUNC!(BaseOf!R)(path);
2578    }
2579}
2580else version (Posix)
2581{
2582    alias isAbsolute = isRooted;
2583}
2584
2585
2586@safe unittest
2587{
2588    assert(!isAbsolute("foo"));
2589    assert(!isAbsolute("../foo"w));
2590    static assert(!isAbsolute("foo"));
2591
2592    version (Posix)
2593    {
2594    assert(isAbsolute("/"d));
2595    assert(isAbsolute("/foo".dup));
2596    static assert(isAbsolute("/foo"));
2597    }
2598
2599    version (Windows)
2600    {
2601    assert(isAbsolute("d:\\"w));
2602    assert(isAbsolute("d:\\foo"d));
2603    assert(isAbsolute("\\\\foo\\bar"));
2604    assert(!isAbsolute("\\"w.dup));
2605    assert(!isAbsolute("\\foo"d.dup));
2606    assert(!isAbsolute("d:"));
2607    assert(!isAbsolute("d:foo"));
2608    static assert(isAbsolute(`d:\foo`));
2609    }
2610
2611    {
2612        auto r = MockRange!(immutable(char))(`../foo`);
2613        assert(!r.isAbsolute());
2614    }
2615
2616    static struct DirEntry { string s; alias s this; }
2617    assert(!isAbsolute(DirEntry("foo")));
2618}
2619
2620
2621
2622
2623/** Transforms $(D path) into an absolute _path.
2624
2625    The following algorithm is used:
2626    $(OL
2627        $(LI If $(D path) is empty, return $(D null).)
2628        $(LI If $(D path) is already absolute, return it.)
2629        $(LI Otherwise, append $(D path) to $(D base) and return
2630            the result. If $(D base) is not specified, the current
2631            working directory is used.)
2632    )
2633    The function allocates memory if and only if it gets to the third stage
2634    of this algorithm.
2635
2636    Params:
2637        path = the relative path to transform
2638        base = the base directory of the relative path
2639
2640    Returns:
2641        string of transformed path
2642
2643    Throws:
2644    $(D Exception) if the specified _base directory is not absolute.
2645
2646    See_Also:
2647        $(LREF asAbsolutePath) which does not allocate
2648*/
2649string absolutePath(string path, lazy string base = getcwd())
2650    @safe pure
2651{
2652    import std.array : array;
2653    if (path.empty)  return null;
2654    if (isAbsolute(path))  return path;
2655    auto baseVar = base;
2656    if (!isAbsolute(baseVar)) throw new Exception("Base directory must be absolute");
2657    return chainPath(baseVar, path).array;
2658}
2659
2660///
2661@safe unittest
2662{
2663    version (Posix)
2664    {
2665        assert(absolutePath("some/file", "/foo/bar")  == "/foo/bar/some/file");
2666        assert(absolutePath("../file", "/foo/bar")    == "/foo/bar/../file");
2667        assert(absolutePath("/some/file", "/foo/bar") == "/some/file");
2668    }
2669
2670    version (Windows)
2671    {
2672        assert(absolutePath(`some\file`, `c:\foo\bar`)    == `c:\foo\bar\some\file`);
2673        assert(absolutePath(`..\file`, `c:\foo\bar`)      == `c:\foo\bar\..\file`);
2674        assert(absolutePath(`c:\some\file`, `c:\foo\bar`) == `c:\some\file`);
2675        assert(absolutePath(`\`, `c:\`)                   == `c:\`);
2676        assert(absolutePath(`\some\file`, `c:\foo\bar`)   == `c:\some\file`);
2677    }
2678}
2679
2680@safe unittest
2681{
2682    version (Posix)
2683    {
2684        static assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file");
2685    }
2686
2687    version (Windows)
2688    {
2689        static assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2690    }
2691
2692    import std.exception;
2693    assertThrown(absolutePath("bar", "foo"));
2694}
2695
2696/** Transforms $(D path) into an absolute _path.
2697
2698    The following algorithm is used:
2699    $(OL
2700        $(LI If $(D path) is empty, return $(D null).)
2701        $(LI If $(D path) is already absolute, return it.)
2702        $(LI Otherwise, append $(D path) to the current working directory,
2703        which allocates memory.)
2704    )
2705
2706    Params:
2707        path = the relative path to transform
2708
2709    Returns:
2710        the transformed path as a lazy range
2711
2712    See_Also:
2713        $(LREF absolutePath) which returns an allocated string
2714*/
2715auto asAbsolutePath(R)(R path)
2716if ((isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2717    isNarrowString!R) &&
2718    !isConvertibleToString!R)
2719{
2720    import std.file : getcwd;
2721    string base = null;
2722    if (!path.empty && !isAbsolute(path))
2723        base = getcwd();
2724    return chainPath(base, path);
2725}
2726
2727///
2728@system unittest
2729{
2730    import std.array;
2731    assert(asAbsolutePath(cast(string) null).array == "");
2732    version (Posix)
2733    {
2734        assert(asAbsolutePath("/foo").array == "/foo");
2735    }
2736    version (Windows)
2737    {
2738        assert(asAbsolutePath("c:/foo").array == "c:/foo");
2739    }
2740    asAbsolutePath("foo");
2741}
2742
2743auto asAbsolutePath(R)(auto ref R path)
2744if (isConvertibleToString!R)
2745{
2746    return asAbsolutePath!(StringTypeOf!R)(path);
2747}
2748
2749@system unittest
2750{
2751    assert(testAliasedString!asAbsolutePath(null));
2752}
2753
2754/** Translates $(D path) into a relative _path.
2755
2756    The returned _path is relative to $(D base), which is by default
2757    taken to be the current working directory.  If specified,
2758    $(D base) must be an absolute _path, and it is always assumed
2759    to refer to a directory.  If $(D path) and $(D base) refer to
2760    the same directory, the function returns $(D `.`).
2761
2762    The following algorithm is used:
2763    $(OL
2764        $(LI If $(D path) is a relative directory, return it unaltered.)
2765        $(LI Find a common root between $(D path) and $(D base).
2766            If there is no common root, return $(D path) unaltered.)
2767        $(LI Prepare a string with as many $(D `../`) or $(D `..\`) as
2768            necessary to reach the common root from base path.)
2769        $(LI Append the remaining segments of $(D path) to the string
2770            and return.)
2771    )
2772
2773    In the second step, path components are compared using $(D filenameCmp!cs),
2774    where $(D cs) is an optional template parameter determining whether
2775    the comparison is case sensitive or not.  See the
2776    $(LREF filenameCmp) documentation for details.
2777
2778    This function allocates memory.
2779
2780    Params:
2781        cs = Whether matching path name components against the base path should
2782            be case-sensitive or not.
2783        path = A path name.
2784        base = The base path to construct the relative path from.
2785
2786    Returns: The relative path.
2787
2788    See_Also:
2789        $(LREF asRelativePath) which does not allocate memory
2790
2791    Throws:
2792    $(D Exception) if the specified _base directory is not absolute.
2793*/
2794string relativePath(CaseSensitive cs = CaseSensitive.osDefault)
2795    (string path, lazy string base = getcwd())
2796{
2797    if (!isAbsolute(path))
2798        return path;
2799    auto baseVar = base;
2800    if (!isAbsolute(baseVar))
2801        throw new Exception("Base directory must be absolute");
2802
2803    import std.conv : to;
2804    return asRelativePath!cs(path, baseVar).to!string;
2805}
2806
2807///
2808@system unittest
2809{
2810    assert(relativePath("foo") == "foo");
2811
2812    version (Posix)
2813    {
2814        assert(relativePath("foo", "/bar") == "foo");
2815        assert(relativePath("/foo/bar", "/foo/bar") == ".");
2816        assert(relativePath("/foo/bar", "/foo/baz") == "../bar");
2817        assert(relativePath("/foo/bar/baz", "/foo/woo/wee") == "../../bar/baz");
2818        assert(relativePath("/foo/bar/baz", "/foo/bar") == "baz");
2819    }
2820    version (Windows)
2821    {
2822        assert(relativePath("foo", `c:\bar`) == "foo");
2823        assert(relativePath(`c:\foo\bar`, `c:\foo\bar`) == ".");
2824        assert(relativePath(`c:\foo\bar`, `c:\foo\baz`) == `..\bar`);
2825        assert(relativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`) == `..\..\bar\baz`);
2826        assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
2827        assert(relativePath(`c:\foo\bar`, `d:\foo`) == `c:\foo\bar`);
2828    }
2829}
2830
2831@system unittest
2832{
2833    import std.exception;
2834    assert(relativePath("foo") == "foo");
2835    version (Posix)
2836    {
2837        relativePath("/foo");
2838        assert(relativePath("/foo/bar", "/foo/baz") == "../bar");
2839        assertThrown(relativePath("/foo", "bar"));
2840    }
2841    else version (Windows)
2842    {
2843        relativePath(`\foo`);
2844        assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
2845        assertThrown(relativePath(`c:\foo`, "bar"));
2846    }
2847    else static assert(0);
2848}
2849
2850/** Transforms `path` into a _path relative to `base`.
2851
2852    The returned _path is relative to `base`, which is usually
2853    the current working directory.
2854    `base` must be an absolute _path, and it is always assumed
2855    to refer to a directory.  If `path` and `base` refer to
2856    the same directory, the function returns `'.'`.
2857
2858    The following algorithm is used:
2859    $(OL
2860        $(LI If `path` is a relative directory, return it unaltered.)
2861        $(LI Find a common root between `path` and `base`.
2862            If there is no common root, return `path` unaltered.)
2863        $(LI Prepare a string with as many `../` or `..\` as
2864            necessary to reach the common root from base path.)
2865        $(LI Append the remaining segments of `path` to the string
2866            and return.)
2867    )
2868
2869    In the second step, path components are compared using `filenameCmp!cs`,
2870    where `cs` is an optional template parameter determining whether
2871    the comparison is case sensitive or not.  See the
2872    $(LREF filenameCmp) documentation for details.
2873
2874    Params:
2875        path = _path to transform
2876        base = absolute path
2877        cs = whether filespec comparisons are sensitive or not; defaults to
2878         `CaseSensitive.osDefault`
2879
2880    Returns:
2881        a random access range of the transformed _path
2882
2883    See_Also:
2884        $(LREF relativePath)
2885*/
2886auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2)
2887    (R1 path, R2 base)
2888if ((isNarrowString!R1 ||
2889    (isRandomAccessRange!R1 && hasSlicing!R1 && isSomeChar!(ElementType!R1)) &&
2890    !isConvertibleToString!R1) &&
2891    (isNarrowString!R2 ||
2892    (isRandomAccessRange!R2 && hasSlicing!R2 && isSomeChar!(ElementType!R2)) &&
2893    !isConvertibleToString!R2))
2894{
2895    bool choosePath = !isAbsolute(path);
2896
2897    // Find common root with current working directory
2898
2899    auto basePS = pathSplitter(base);
2900    auto pathPS = pathSplitter(path);
2901    choosePath |= filenameCmp!cs(basePS.front, pathPS.front) != 0;
2902
2903    basePS.popFront();
2904    pathPS.popFront();
2905
2906    import std.algorithm.comparison : mismatch;
2907    import std.algorithm.iteration : joiner;
2908    import std.array : array;
2909    import std.range.primitives : walkLength;
2910    import std.range : repeat, chain, choose;
2911    import std.utf : byCodeUnit, byChar;
2912
2913    // Remove matching prefix from basePS and pathPS
2914    auto tup = mismatch!((a, b) => filenameCmp!cs(a, b) == 0)(basePS, pathPS);
2915    basePS = tup[0];
2916    pathPS = tup[1];
2917
2918    string sep;
2919    if (basePS.empty && pathPS.empty)
2920        sep = ".";              // if base == path, this is the return
2921    else if (!basePS.empty && !pathPS.empty)
2922        sep = dirSeparator;
2923
2924    // Append as many "../" as necessary to reach common base from path
2925    auto r1 = ".."
2926        .byChar
2927        .repeat(basePS.walkLength())
2928        .joiner(dirSeparator.byChar);
2929
2930    auto r2 = pathPS
2931        .joiner(dirSeparator.byChar)
2932        .byChar;
2933
2934    // Return (r1 ~ sep ~ r2)
2935    return choose(choosePath, path.byCodeUnit, chain(r1, sep.byChar, r2));
2936}
2937
2938///
2939@system unittest
2940{
2941    import std.array;
2942    version (Posix)
2943    {
2944        assert(asRelativePath("foo", "/bar").array == "foo");
2945        assert(asRelativePath("/foo/bar", "/foo/bar").array == ".");
2946        assert(asRelativePath("/foo/bar", "/foo/baz").array == "../bar");
2947        assert(asRelativePath("/foo/bar/baz", "/foo/woo/wee").array == "../../bar/baz");
2948        assert(asRelativePath("/foo/bar/baz", "/foo/bar").array == "baz");
2949    }
2950    else version (Windows)
2951    {
2952        assert(asRelativePath("foo", `c:\bar`).array == "foo");
2953        assert(asRelativePath(`c:\foo\bar`, `c:\foo\bar`).array == ".");
2954        assert(asRelativePath(`c:\foo\bar`, `c:\foo\baz`).array == `..\bar`);
2955        assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`);
2956        assert(asRelativePath(`c:/foo/bar/baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`);
2957        assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\bar`).array == "baz");
2958        assert(asRelativePath(`c:\foo\bar`, `d:\foo`).array == `c:\foo\bar`);
2959        assert(asRelativePath(`\\foo\bar`, `c:\foo`).array == `\\foo\bar`);
2960    }
2961    else
2962        static assert(0);
2963}
2964
2965auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2)
2966    (auto ref R1 path, auto ref R2 base)
2967if (isConvertibleToString!R1 || isConvertibleToString!R2)
2968{
2969    import std.meta : staticMap;
2970    alias Types = staticMap!(convertToString, R1, R2);
2971    return asRelativePath!(cs, Types)(path, base);
2972}
2973
2974@system unittest
2975{
2976    import std.array;
2977    version (Posix)
2978        assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("/bar")).array == "foo");
2979    else version (Windows)
2980        assert(asRelativePath(TestAliasedString("foo"), TestAliasedString(`c:\bar`)).array == "foo");
2981    assert(asRelativePath(TestAliasedString("foo"), "bar").array == "foo");
2982    assert(asRelativePath("foo", TestAliasedString("bar")).array == "foo");
2983    assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("bar")).array == "foo");
2984    import std.utf : byDchar;
2985    assert(asRelativePath("foo"d.byDchar, TestAliasedString("bar")).array == "foo");
2986}
2987
2988@system unittest
2989{
2990    import std.array, std.utf : bCU=byCodeUnit;
2991    version (Posix)
2992    {
2993        assert(asRelativePath("/foo/bar/baz".bCU, "/foo/bar".bCU).array == "baz");
2994        assert(asRelativePath("/foo/bar/baz"w.bCU, "/foo/bar"w.bCU).array == "baz"w);
2995        assert(asRelativePath("/foo/bar/baz"d.bCU, "/foo/bar"d.bCU).array == "baz"d);
2996    }
2997    else version (Windows)
2998    {
2999        assert(asRelativePath(`\\foo\bar`.bCU, `c:\foo`.bCU).array == `\\foo\bar`);
3000        assert(asRelativePath(`\\foo\bar`w.bCU, `c:\foo`w.bCU).array == `\\foo\bar`w);
3001        assert(asRelativePath(`\\foo\bar`d.bCU, `c:\foo`d.bCU).array == `\\foo\bar`d);
3002    }
3003}
3004
3005/** Compares filename characters.
3006
3007    This function can perform a case-sensitive or a case-insensitive
3008    comparison.  This is controlled through the $(D cs) template parameter
3009    which, if not specified, is given by $(LREF CaseSensitive)$(D .osDefault).
3010
3011    On Windows, the backslash and slash characters ($(D `\`) and $(D `/`))
3012    are considered equal.
3013
3014    Params:
3015        cs = Case-sensitivity of the comparison.
3016        a = A filename character.
3017        b = A filename character.
3018
3019    Returns:
3020        $(D < 0) if $(D a < b),
3021        $(D 0) if $(D a == b), and
3022        $(D > 0) if $(D a > b).
3023*/
3024int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b)
3025    @safe pure nothrow
3026{
3027    if (isDirSeparator(a) && isDirSeparator(b)) return 0;
3028    static if (!cs)
3029    {
3030        import std.uni : toLower;
3031        a = toLower(a);
3032        b = toLower(b);
3033    }
3034    return cast(int)(a - b);
3035}
3036
3037///
3038@safe unittest
3039{
3040    assert(filenameCharCmp('a', 'a') == 0);
3041    assert(filenameCharCmp('a', 'b') < 0);
3042    assert(filenameCharCmp('b', 'a') > 0);
3043
3044    version (linux)
3045    {
3046        // Same as calling filenameCharCmp!(CaseSensitive.yes)(a, b)
3047        assert(filenameCharCmp('A', 'a') < 0);
3048        assert(filenameCharCmp('a', 'A') > 0);
3049    }
3050    version (Windows)
3051    {
3052        // Same as calling filenameCharCmp!(CaseSensitive.no)(a, b)
3053        assert(filenameCharCmp('a', 'A') == 0);
3054        assert(filenameCharCmp('a', 'B') < 0);
3055        assert(filenameCharCmp('A', 'b') < 0);
3056    }
3057}
3058
3059@safe unittest
3060{
3061    assert(filenameCharCmp!(CaseSensitive.yes)('A', 'a') < 0);
3062    assert(filenameCharCmp!(CaseSensitive.yes)('a', 'A') > 0);
3063
3064    assert(filenameCharCmp!(CaseSensitive.no)('a', 'a') == 0);
3065    assert(filenameCharCmp!(CaseSensitive.no)('a', 'b') < 0);
3066    assert(filenameCharCmp!(CaseSensitive.no)('b', 'a') > 0);
3067    assert(filenameCharCmp!(CaseSensitive.no)('A', 'a') == 0);
3068    assert(filenameCharCmp!(CaseSensitive.no)('a', 'A') == 0);
3069    assert(filenameCharCmp!(CaseSensitive.no)('a', 'B') < 0);
3070    assert(filenameCharCmp!(CaseSensitive.no)('B', 'a') > 0);
3071    assert(filenameCharCmp!(CaseSensitive.no)('A', 'b') < 0);
3072    assert(filenameCharCmp!(CaseSensitive.no)('b', 'A') > 0);
3073
3074    version (Posix)   assert(filenameCharCmp('\\', '/') != 0);
3075    version (Windows) assert(filenameCharCmp('\\', '/') == 0);
3076}
3077
3078
3079/** Compares file names and returns
3080
3081    Individual characters are compared using $(D filenameCharCmp!cs),
3082    where $(D cs) is an optional template parameter determining whether
3083    the comparison is case sensitive or not.
3084
3085    Treatment of invalid UTF encodings is implementation defined.
3086
3087    Params:
3088        cs = case sensitivity
3089        filename1 = range for first file name
3090        filename2 = range for second file name
3091
3092    Returns:
3093        $(D < 0) if $(D filename1 < filename2),
3094        $(D 0) if $(D filename1 == filename2) and
3095        $(D > 0) if $(D filename1 > filename2).
3096
3097    See_Also:
3098        $(LREF filenameCharCmp)
3099*/
3100int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2)
3101    (Range1 filename1, Range2 filename2)
3102if (isInputRange!Range1 && !isInfinite!Range1 &&
3103    isSomeChar!(ElementEncodingType!Range1) &&
3104    !isConvertibleToString!Range1 &&
3105    isInputRange!Range2 && !isInfinite!Range2 &&
3106    isSomeChar!(ElementEncodingType!Range2) &&
3107    !isConvertibleToString!Range2)
3108{
3109    alias C1 = Unqual!(ElementEncodingType!Range1);
3110    alias C2 = Unqual!(ElementEncodingType!Range2);
3111
3112    static if (!cs && (C1.sizeof < 4 || C2.sizeof < 4) ||
3113               C1.sizeof != C2.sizeof)
3114    {
3115        // Case insensitive - decode so case is checkable
3116        // Different char sizes - decode to bring to common type
3117        import std.utf : byDchar;
3118        return filenameCmp!cs(filename1.byDchar, filename2.byDchar);
3119    }
3120    else static if (isSomeString!Range1 && C1.sizeof < 4 ||
3121                    isSomeString!Range2 && C2.sizeof < 4)
3122    {
3123        // Avoid autodecoding
3124        import std.utf : byCodeUnit;
3125        return filenameCmp!cs(filename1.byCodeUnit, filename2.byCodeUnit);
3126    }
3127    else
3128    {
3129        for (;;)
3130        {
3131            if (filename1.empty) return -(cast(int) !filename2.empty);
3132            if (filename2.empty) return  1;
3133            const c = filenameCharCmp!cs(filename1.front, filename2.front);
3134            if (c != 0) return c;
3135            filename1.popFront();
3136            filename2.popFront();
3137        }
3138    }
3139}
3140
3141///
3142@safe unittest
3143{
3144    assert(filenameCmp("abc", "abc") == 0);
3145    assert(filenameCmp("abc", "abd") < 0);
3146    assert(filenameCmp("abc", "abb") > 0);
3147    assert(filenameCmp("abc", "abcd") < 0);
3148    assert(filenameCmp("abcd", "abc") > 0);
3149
3150    version (linux)
3151    {
3152        // Same as calling filenameCmp!(CaseSensitive.yes)(filename1, filename2)
3153        assert(filenameCmp("Abc", "abc") < 0);
3154        assert(filenameCmp("abc", "Abc") > 0);
3155    }
3156    version (Windows)
3157    {
3158        // Same as calling filenameCmp!(CaseSensitive.no)(filename1, filename2)
3159        assert(filenameCmp("Abc", "abc") == 0);
3160        assert(filenameCmp("abc", "Abc") == 0);
3161        assert(filenameCmp("Abc", "abD") < 0);
3162        assert(filenameCmp("abc", "AbB") > 0);
3163    }
3164}
3165
3166int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2)
3167    (auto ref Range1 filename1, auto ref Range2 filename2)
3168if (isConvertibleToString!Range1 || isConvertibleToString!Range2)
3169{
3170    import std.meta : staticMap;
3171    alias Types = staticMap!(convertToString, Range1, Range2);
3172    return filenameCmp!(cs, Types)(filename1, filename2);
3173}
3174
3175@safe unittest
3176{
3177    assert(filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), "abc") < 0);
3178    assert(filenameCmp!(CaseSensitive.yes)("Abc", TestAliasedString("abc")) < 0);
3179    assert(filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), TestAliasedString("abc")) < 0);
3180}
3181
3182@safe unittest
3183{
3184    assert(filenameCmp!(CaseSensitive.yes)("Abc", "abc") < 0);
3185    assert(filenameCmp!(CaseSensitive.yes)("abc", "Abc") > 0);
3186
3187    assert(filenameCmp!(CaseSensitive.no)("abc", "abc") == 0);
3188    assert(filenameCmp!(CaseSensitive.no)("abc", "abd") < 0);
3189    assert(filenameCmp!(CaseSensitive.no)("abc", "abb") > 0);
3190    assert(filenameCmp!(CaseSensitive.no)("abc", "abcd") < 0);
3191    assert(filenameCmp!(CaseSensitive.no)("abcd", "abc") > 0);
3192    assert(filenameCmp!(CaseSensitive.no)("Abc", "abc") == 0);
3193    assert(filenameCmp!(CaseSensitive.no)("abc", "Abc") == 0);
3194    assert(filenameCmp!(CaseSensitive.no)("Abc", "abD") < 0);
3195    assert(filenameCmp!(CaseSensitive.no)("abc", "AbB") > 0);
3196
3197    version (Posix)   assert(filenameCmp(`abc\def`, `abc/def`) != 0);
3198    version (Windows) assert(filenameCmp(`abc\def`, `abc/def`) == 0);
3199}
3200
3201/** Matches a pattern against a path.
3202
3203    Some characters of pattern have a special meaning (they are
3204    $(I meta-characters)) and can't be escaped. These are:
3205
3206    $(BOOKTABLE,
3207    $(TR $(TD $(D *))
3208         $(TD Matches 0 or more instances of any character.))
3209    $(TR $(TD $(D ?))
3210         $(TD Matches exactly one instance of any character.))
3211    $(TR $(TD $(D [)$(I chars)$(D ]))
3212         $(TD Matches one instance of any character that appears
3213              between the brackets.))
3214    $(TR $(TD $(D [!)$(I chars)$(D ]))
3215         $(TD Matches one instance of any character that does not
3216              appear between the brackets after the exclamation mark.))
3217    $(TR $(TD $(D {)$(I string1)$(D ,)$(I string2)$(D ,)&hellip;$(D }))
3218         $(TD Matches either of the specified strings.))
3219    )
3220
3221    Individual characters are compared using $(D filenameCharCmp!cs),
3222    where $(D cs) is an optional template parameter determining whether
3223    the comparison is case sensitive or not.  See the
3224    $(LREF filenameCharCmp) documentation for details.
3225
3226    Note that directory
3227    separators and dots don't stop a meta-character from matching
3228    further portions of the path.
3229
3230    Params:
3231        cs = Whether the matching should be case-sensitive
3232        path = The path to be matched against
3233        pattern = The glob pattern
3234
3235    Returns:
3236    $(D true) if pattern matches path, $(D false) otherwise.
3237
3238    See_also:
3239    $(LINK2 http://en.wikipedia.org/wiki/Glob_%28programming%29,Wikipedia: _glob (programming))
3240 */
3241bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range)
3242    (Range path, const(C)[] pattern)
3243    @safe pure nothrow
3244if (isForwardRange!Range && !isInfinite!Range &&
3245    isSomeChar!(ElementEncodingType!Range) && !isConvertibleToString!Range &&
3246    isSomeChar!C && is(Unqual!C == Unqual!(ElementEncodingType!Range)))
3247in
3248{
3249    // Verify that pattern[] is valid
3250    import std.algorithm.searching : balancedParens;
3251    assert(balancedParens(pattern, '[', ']', 0));
3252    assert(balancedParens(pattern, '{', '}', 0));
3253}
3254body
3255{
3256    alias RC = Unqual!(ElementEncodingType!Range);
3257
3258    static if (RC.sizeof == 1 && isSomeString!Range)
3259    {
3260        import std.utf : byChar;
3261        return globMatch!cs(path.byChar, pattern);
3262    }
3263    else static if (RC.sizeof == 2 && isSomeString!Range)
3264    {
3265        import std.utf : byWchar;
3266        return globMatch!cs(path.byWchar, pattern);
3267    }
3268    else
3269    {
3270        C[] pattmp;
3271        foreach (ref pi; 0 .. pattern.length)
3272        {
3273            const pc = pattern[pi];
3274            switch (pc)
3275            {
3276                case '*':
3277                    if (pi + 1 == pattern.length)
3278                        return true;
3279                    for (; !path.empty; path.popFront())
3280                    {
3281                        auto p = path.save;
3282                        if (globMatch!(cs, C)(p,
3283                                        pattern[pi + 1 .. pattern.length]))
3284                            return true;
3285                    }
3286                    return false;
3287
3288                case '?':
3289                    if (path.empty)
3290                        return false;
3291                    path.popFront();
3292                    break;
3293
3294                case '[':
3295                    if (path.empty)
3296                        return false;
3297                    auto nc = path.front;
3298                    path.popFront();
3299                    auto not = false;
3300                    ++pi;
3301                    if (pattern[pi] == '!')
3302                    {
3303                        not = true;
3304                        ++pi;
3305                    }
3306                    auto anymatch = false;
3307                    while (1)
3308                    {
3309                        const pc2 = pattern[pi];
3310                        if (pc2 == ']')
3311                            break;
3312                        if (!anymatch && (filenameCharCmp!cs(nc, pc2) == 0))
3313                            anymatch = true;
3314                        ++pi;
3315                    }
3316                    if (anymatch == not)
3317                        return false;
3318                    break;
3319
3320                case '{':
3321                    // find end of {} section
3322                    auto piRemain = pi;
3323                    for (; piRemain < pattern.length
3324                             && pattern[piRemain] != '}'; ++piRemain)
3325                    {   }
3326
3327                    if (piRemain < pattern.length)
3328                        ++piRemain;
3329                    ++pi;
3330
3331                    while (pi < pattern.length)
3332                    {
3333                        const pi0 = pi;
3334                        C pc3 = pattern[pi];
3335                        // find end of current alternative
3336                        for (; pi < pattern.length && pc3 != '}' && pc3 != ','; ++pi)
3337                        {
3338                            pc3 = pattern[pi];
3339                        }
3340
3341                        auto p = path.save;
3342                        if (pi0 == pi)
3343                        {
3344                            if (globMatch!(cs, C)(p, pattern[piRemain..$]))
3345                            {
3346                                return true;
3347                            }
3348                            ++pi;
3349                        }
3350                        else
3351                        {
3352                            /* Match for:
3353                             *   pattern[pi0 .. pi-1] ~ pattern[piRemain..$]
3354                             */
3355                            if (pattmp is null)
3356                                // Allocate this only once per function invocation.
3357                                // Should do it with malloc/free, but that would make it impure.
3358                                pattmp = new C[pattern.length];
3359
3360                            const len1 = pi - 1 - pi0;
3361                            pattmp[0 .. len1] = pattern[pi0 .. pi - 1];
3362
3363                            const len2 = pattern.length - piRemain;
3364                            pattmp[len1 .. len1 + len2] = pattern[piRemain .. $];
3365
3366                            if (globMatch!(cs, C)(p, pattmp[0 .. len1 + len2]))
3367                            {
3368                                return true;
3369                            }
3370                        }
3371                        if (pc3 == '}')
3372                        {
3373                            break;
3374                        }
3375                    }
3376                    return false;
3377
3378                default:
3379                    if (path.empty)
3380                        return false;
3381                    if (filenameCharCmp!cs(pc, path.front) != 0)
3382                        return false;
3383                    path.popFront();
3384                    break;
3385            }
3386        }
3387        return path.empty;
3388    }
3389}
3390
3391///
3392@safe unittest
3393{
3394    assert(globMatch("foo.bar", "*"));
3395    assert(globMatch("foo.bar", "*.*"));
3396    assert(globMatch(`foo/foo\bar`, "f*b*r"));
3397    assert(globMatch("foo.bar", "f???bar"));
3398    assert(globMatch("foo.bar", "[fg]???bar"));
3399    assert(globMatch("foo.bar", "[!gh]*bar"));
3400    assert(globMatch("bar.fooz", "bar.{foo,bif}z"));
3401    assert(globMatch("bar.bifz", "bar.{foo,bif}z"));
3402
3403    version (Windows)
3404    {
3405        // Same as calling globMatch!(CaseSensitive.no)(path, pattern)
3406        assert(globMatch("foo", "Foo"));
3407        assert(globMatch("Goo.bar", "[fg]???bar"));
3408    }
3409    version (linux)
3410    {
3411        // Same as calling globMatch!(CaseSensitive.yes)(path, pattern)
3412        assert(!globMatch("foo", "Foo"));
3413        assert(!globMatch("Goo.bar", "[fg]???bar"));
3414    }
3415}
3416
3417bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range)
3418    (auto ref Range path, const(C)[] pattern)
3419    @safe pure nothrow
3420if (isConvertibleToString!Range)
3421{
3422    return globMatch!(cs, C, StringTypeOf!Range)(path, pattern);
3423}
3424
3425@safe unittest
3426{
3427    assert(testAliasedString!globMatch("foo.bar", "*"));
3428}
3429
3430@safe unittest
3431{
3432    assert(globMatch!(CaseSensitive.no)("foo", "Foo"));
3433    assert(!globMatch!(CaseSensitive.yes)("foo", "Foo"));
3434
3435    assert(globMatch("foo", "*"));
3436    assert(globMatch("foo.bar"w, "*"w));
3437    assert(globMatch("foo.bar"d, "*.*"d));
3438    assert(globMatch("foo.bar", "foo*"));
3439    assert(globMatch("foo.bar"w, "f*bar"w));
3440    assert(globMatch("foo.bar"d, "f*b*r"d));
3441    assert(globMatch("foo.bar", "f???bar"));
3442    assert(globMatch("foo.bar"w, "[fg]???bar"w));
3443    assert(globMatch("foo.bar"d, "[!gh]*bar"d));
3444
3445    assert(!globMatch("foo", "bar"));
3446    assert(!globMatch("foo"w, "*.*"w));
3447    assert(!globMatch("foo.bar"d, "f*baz"d));
3448    assert(!globMatch("foo.bar", "f*b*x"));
3449    assert(!globMatch("foo.bar", "[gh]???bar"));
3450    assert(!globMatch("foo.bar"w, "[!fg]*bar"w));
3451    assert(!globMatch("foo.bar"d, "[fg]???baz"d));
3452    assert(!globMatch("foo.di", "*.d")); // test issue 6634: triggered bad assertion
3453
3454    assert(globMatch("foo.bar", "{foo,bif}.bar"));
3455    assert(globMatch("bif.bar"w, "{foo,bif}.bar"w));
3456
3457    assert(globMatch("bar.foo"d, "bar.{foo,bif}"d));
3458    assert(globMatch("bar.bif", "bar.{foo,bif}"));
3459
3460    assert(globMatch("bar.fooz"w, "bar.{foo,bif}z"w));
3461    assert(globMatch("bar.bifz"d, "bar.{foo,bif}z"d));
3462
3463    assert(globMatch("bar.foo", "bar.{biz,,baz}foo"));
3464    assert(globMatch("bar.foo"w, "bar.{biz,}foo"w));
3465    assert(globMatch("bar.foo"d, "bar.{,biz}foo"d));
3466    assert(globMatch("bar.foo", "bar.{}foo"));
3467
3468    assert(globMatch("bar.foo"w, "bar.{ar,,fo}o"w));
3469    assert(globMatch("bar.foo"d, "bar.{,ar,fo}o"d));
3470    assert(globMatch("bar.o", "bar.{,ar,fo}o"));
3471
3472    assert(!globMatch("foo", "foo?"));
3473    assert(!globMatch("foo", "foo[]"));
3474    assert(!globMatch("foo", "foob"));
3475    assert(!globMatch("foo", "foo{b}"));
3476
3477
3478    static assert(globMatch("foo.bar", "[!gh]*bar"));
3479}
3480
3481
3482
3483
3484/** Checks that the given file or directory name is valid.
3485
3486    The maximum length of $(D filename) is given by the constant
3487    $(D core.stdc.stdio.FILENAME_MAX).  (On Windows, this number is
3488    defined as the maximum number of UTF-16 code points, and the
3489    test will therefore only yield strictly correct results when
3490    $(D filename) is a string of $(D wchar)s.)
3491
3492    On Windows, the following criteria must be satisfied
3493    ($(LINK2 http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx,source)):
3494    $(UL
3495        $(LI $(D filename) must not contain any characters whose integer
3496            representation is in the range 0-31.)
3497        $(LI $(D filename) must not contain any of the following $(I reserved
3498            characters): <>:"/\|?*)
3499        $(LI $(D filename) may not end with a space ($(D ' ')) or a period
3500            ($(D '.')).)
3501    )
3502
3503    On POSIX, $(D filename) may not contain a forward slash ($(D '/')) or
3504    the null character ($(D '\0')).
3505
3506    Params:
3507        filename = string to check
3508
3509    Returns:
3510        $(D true) if and only if $(D filename) is not
3511        empty, not too long, and does not contain invalid characters.
3512
3513*/
3514bool isValidFilename(Range)(Range filename)
3515if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) ||
3516    isNarrowString!Range) &&
3517    !isConvertibleToString!Range)
3518{
3519    import core.stdc.stdio : FILENAME_MAX;
3520    if (filename.length == 0 || filename.length >= FILENAME_MAX) return false;
3521    foreach (c; filename)
3522    {
3523        version (Windows)
3524        {
3525            switch (c)
3526            {
3527                case 0:
3528                ..
3529                case 31:
3530                case '<':
3531                case '>':
3532                case ':':
3533                case '"':
3534                case '/':
3535                case '\\':
3536                case '|':
3537                case '?':
3538                case '*':
3539                    return false;
3540
3541                default:
3542                    break;
3543            }
3544        }
3545        else version (Posix)
3546        {
3547            if (c == 0 || c == '/') return false;
3548        }
3549        else static assert(0);
3550    }
3551    version (Windows)
3552    {
3553        auto last = filename[filename.length - 1];
3554        if (last == '.' || last == ' ') return false;
3555    }
3556
3557    // All criteria passed
3558    return true;
3559}
3560
3561///
3562@safe pure @nogc nothrow
3563unittest
3564{
3565    import std.utf : byCodeUnit;
3566
3567    assert(isValidFilename("hello.exe".byCodeUnit));
3568}
3569
3570bool isValidFilename(Range)(auto ref Range filename)
3571if (isConvertibleToString!Range)
3572{
3573    return isValidFilename!(StringTypeOf!Range)(filename);
3574}
3575
3576@safe unittest
3577{
3578    assert(testAliasedString!isValidFilename("hello.exe"));
3579}
3580
3581@safe pure
3582unittest
3583{
3584    import std.conv;
3585    auto valid = ["foo"];
3586    auto invalid = ["", "foo\0bar", "foo/bar"];
3587    auto pfdep = [`foo\bar`, "*.txt"];
3588    version (Windows) invalid ~= pfdep;
3589    else version (Posix) valid ~= pfdep;
3590    else static assert(0);
3591
3592    import std.meta : AliasSeq;
3593    foreach (T; AliasSeq!(char[], const(char)[], string, wchar[],
3594        const(wchar)[], wstring, dchar[], const(dchar)[], dstring))
3595    {
3596        foreach (fn; valid)
3597            assert(isValidFilename(to!T(fn)));
3598        foreach (fn; invalid)
3599            assert(!isValidFilename(to!T(fn)));
3600    }
3601
3602    {
3603        auto r = MockRange!(immutable(char))(`dir/file.d`);
3604        assert(!isValidFilename(r));
3605    }
3606
3607    static struct DirEntry { string s; alias s this; }
3608    assert(isValidFilename(DirEntry("file.ext")));
3609
3610    version (Windows)
3611    {
3612        immutable string cases = "<>:\"/\\|?*";
3613        foreach (i; 0 .. 31 + cases.length)
3614        {
3615            char[3] buf;
3616            buf[0] = 'a';
3617            buf[1] = i <= 31 ? cast(char) i : cases[i - 32];
3618            buf[2] = 'b';
3619            assert(!isValidFilename(buf[]));
3620        }
3621    }
3622}
3623
3624
3625
3626/** Checks whether $(D path) is a valid _path.
3627
3628    Generally, this function checks that $(D path) is not empty, and that
3629    each component of the path either satisfies $(LREF isValidFilename)
3630    or is equal to $(D ".") or $(D "..").
3631
3632    $(B It does $(I not) check whether the _path points to an existing file
3633    or directory; use $(REF exists, std,file) for this purpose.)
3634
3635    On Windows, some special rules apply:
3636    $(UL
3637        $(LI If the second character of $(D path) is a colon ($(D ':')),
3638            the first character is interpreted as a drive letter, and
3639            must be in the range A-Z (case insensitive).)
3640        $(LI If $(D path) is on the form $(D `\\$(I server)\$(I share)\...`)
3641            (UNC path), $(LREF isValidFilename) is applied to $(I server)
3642            and $(I share) as well.)
3643        $(LI If $(D path) starts with $(D `\\?\`) (long UNC path), the
3644            only requirement for the rest of the string is that it does
3645            not contain the null character.)
3646        $(LI If $(D path) starts with $(D `\\.\`) (Win32 device namespace)
3647            this function returns $(D false); such paths are beyond the scope
3648            of this module.)
3649    )
3650
3651    Params:
3652        path = string or Range of characters to check
3653
3654    Returns:
3655        true if $(D path) is a valid _path.
3656*/
3657bool isValidPath(Range)(Range path)
3658if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) ||
3659    isNarrowString!Range) &&
3660    !isConvertibleToString!Range)
3661{
3662    alias C = Unqual!(ElementEncodingType!Range);
3663
3664    if (path.empty) return false;
3665
3666    // Check whether component is "." or "..", or whether it satisfies
3667    // isValidFilename.
3668    bool isValidComponent(Range component)
3669    {
3670        assert(component.length > 0);
3671        if (component[0] == '.')
3672        {
3673            if (component.length == 1) return true;
3674            else if (component.length == 2 && component[1] == '.') return true;
3675        }
3676        return isValidFilename(component);
3677    }
3678
3679    if (path.length == 1)
3680        return isDirSeparator(path[0]) || isValidComponent(path);
3681
3682    Range remainder;
3683    version (Windows)
3684    {
3685        if (isDirSeparator(path[0]) && isDirSeparator(path[1]))
3686        {
3687            // Some kind of UNC path
3688            if (path.length < 5)
3689            {
3690                // All valid UNC paths must have at least 5 characters
3691                return false;
3692            }
3693            else if (path[2] == '?')
3694            {
3695                // Long UNC path
3696                if (!isDirSeparator(path[3])) return false;
3697                foreach (c; path[4 .. $])
3698                {
3699                    if (c == '\0') return false;
3700                }
3701                return true;
3702            }
3703            else if (path[2] == '.')
3704            {
3705                // Win32 device namespace not supported
3706                return false;
3707            }
3708            else
3709            {
3710                // Normal UNC path, i.e. \\server\share\...
3711                size_t i = 2;
3712                while (i < path.length && !isDirSeparator(path[i])) ++i;
3713                if (i == path.length || !isValidFilename(path[2 .. i]))
3714                    return false;
3715                ++i; // Skip a single dir separator
3716                size_t j = i;
3717                while (j < path.length && !isDirSeparator(path[j])) ++j;
3718                if (!isValidFilename(path[i .. j])) return false;
3719                remainder = path[j .. $];
3720            }
3721        }
3722        else if (isDriveSeparator(path[1]))
3723        {
3724            import std.ascii : isAlpha;
3725            if (!isAlpha(path[0])) return false;
3726            remainder = path[2 .. $];
3727        }
3728        else
3729        {
3730            remainder = path;
3731        }
3732    }
3733    else version (Posix)
3734    {
3735        remainder = path;
3736    }
3737    else static assert(0);
3738    remainder = ltrimDirSeparators(remainder);
3739
3740    // Check that each component satisfies isValidComponent.
3741    while (!remainder.empty)
3742    {
3743        size_t i = 0;
3744        while (i < remainder.length && !isDirSeparator(remainder[i])) ++i;
3745        assert(i > 0);
3746        if (!isValidComponent(remainder[0 .. i])) return false;
3747        remainder = ltrimDirSeparators(remainder[i .. $]);
3748    }
3749
3750    // All criteria passed
3751    return true;
3752}
3753
3754///
3755@safe pure @nogc nothrow
3756unittest
3757{
3758    assert(isValidPath("/foo/bar"));
3759    assert(!isValidPath("/foo\0/bar"));
3760    assert(isValidPath("/"));
3761    assert(isValidPath("a"));
3762
3763    version (Windows)
3764    {
3765        assert(isValidPath(`c:\`));
3766        assert(isValidPath(`c:\foo`));
3767        assert(isValidPath(`c:\foo\.\bar\\\..\`));
3768        assert(!isValidPath(`!:\foo`));
3769        assert(!isValidPath(`c::\foo`));
3770        assert(!isValidPath(`c:\foo?`));
3771        assert(!isValidPath(`c:\foo.`));
3772
3773        assert(isValidPath(`\\server\share`));
3774        assert(isValidPath(`\\server\share\foo`));
3775        assert(isValidPath(`\\server\share\\foo`));
3776        assert(!isValidPath(`\\\server\share\foo`));
3777        assert(!isValidPath(`\\server\\share\foo`));
3778        assert(!isValidPath(`\\ser*er\share\foo`));
3779        assert(!isValidPath(`\\server\sha?e\foo`));
3780        assert(!isValidPath(`\\server\share\|oo`));
3781
3782        assert(isValidPath(`\\?\<>:"?*|/\..\.`));
3783        assert(!isValidPath("\\\\?\\foo\0bar"));
3784
3785        assert(!isValidPath(`\\.\PhysicalDisk1`));
3786        assert(!isValidPath(`\\`));
3787    }
3788
3789    import std.utf : byCodeUnit;
3790    assert(isValidPath("/foo/bar".byCodeUnit));
3791}
3792
3793bool isValidPath(Range)(auto ref Range path)
3794if (isConvertibleToString!Range)
3795{
3796    return isValidPath!(StringTypeOf!Range)(path);
3797}
3798
3799@safe unittest
3800{
3801    assert(testAliasedString!isValidPath("/foo/bar"));
3802}
3803
3804/** Performs tilde expansion in paths on POSIX systems.
3805    On Windows, this function does nothing.
3806
3807    There are two ways of using tilde expansion in a path. One
3808    involves using the tilde alone or followed by a path separator. In
3809    this case, the tilde will be expanded with the value of the
3810    environment variable $(D HOME).  The second way is putting
3811    a username after the tilde (i.e. $(D ~john/Mail)). Here,
3812    the username will be searched for in the user database
3813    (i.e. $(D /etc/passwd) on Unix systems) and will expand to
3814    whatever path is stored there.  The username is considered the
3815    string after the tilde ending at the first instance of a path
3816    separator.
3817
3818    Note that using the $(D ~user) syntax may give different
3819    values from just $(D ~) if the environment variable doesn't
3820    match the value stored in the user database.
3821
3822    When the environment variable version is used, the path won't
3823    be modified if the environment variable doesn't exist or it
3824    is empty. When the database version is used, the path won't be
3825    modified if the user doesn't exist in the database or there is
3826    not enough memory to perform the query.
3827
3828    This function performs several memory allocations.
3829
3830    Params:
3831        inputPath = The path name to expand.
3832
3833    Returns:
3834    $(D inputPath) with the tilde expanded, or just $(D inputPath)
3835    if it could not be expanded.
3836    For Windows, $(D expandTilde) merely returns its argument $(D inputPath).
3837
3838    Example:
3839    -----
3840    void processFile(string path)
3841    {
3842        // Allow calling this function with paths such as ~/foo
3843        auto fullPath = expandTilde(path);
3844        ...
3845    }
3846    -----
3847*/
3848string expandTilde(string inputPath) nothrow
3849{
3850    version (Posix)
3851    {
3852        import core.exception : onOutOfMemoryError;
3853        import core.stdc.errno : errno, EBADF, ENOENT, EPERM, ERANGE, ESRCH;
3854        import core.stdc.stdlib : malloc, free, realloc;
3855
3856        /*  Joins a path from a C string to the remainder of path.
3857
3858            The last path separator from c_path is discarded. The result
3859            is joined to path[char_pos .. length] if char_pos is smaller
3860            than length, otherwise path is not appended to c_path.
3861        */
3862        static string combineCPathWithDPath(char* c_path, string path, size_t char_pos) nothrow
3863        {
3864            import core.stdc.string : strlen;
3865
3866            assert(c_path != null);
3867            assert(path.length > 0);
3868            assert(char_pos >= 0);
3869
3870            // Search end of C string
3871            size_t end = strlen(c_path);
3872
3873            // Remove trailing path separator, if any
3874            if (end && isDirSeparator(c_path[end - 1]))
3875                end--;
3876
3877            // (this is the only GC allocation done in expandTilde())
3878            string cp;
3879            if (char_pos < path.length)
3880                // Append something from path
3881                cp = cast(string)(c_path[0 .. end] ~ path[char_pos .. $]);
3882            else
3883                // Create our own copy, as lifetime of c_path is undocumented
3884                cp = c_path[0 .. end].idup;
3885
3886            return cp;
3887        }
3888
3889        // Replaces the tilde from path with the environment variable HOME.
3890        static string expandFromEnvironment(string path) nothrow
3891        {
3892            import core.stdc.stdlib : getenv;
3893
3894            assert(path.length >= 1);
3895            assert(path[0] == '~');
3896
3897            // Get HOME and use that to replace the tilde.
3898            auto home = getenv("HOME");
3899            if (home == null)
3900                return path;
3901
3902            return combineCPathWithDPath(home, path, 1);
3903        }
3904
3905        // Replaces the tilde from path with the path from the user database.
3906        static string expandFromDatabase(string path) nothrow
3907        {
3908            // bionic doesn't really support this, as getpwnam_r
3909            // isn't provided and getpwnam is basically just a stub
3910            version (CRuntime_Bionic)
3911            {
3912                return path;
3913            }
3914            else
3915            {
3916                import core.sys.posix.pwd : passwd, getpwnam_r;
3917                import std.string : indexOf;
3918
3919                assert(path.length > 2 || (path.length == 2 && !isDirSeparator(path[1])));
3920                assert(path[0] == '~');
3921
3922                // Extract username, searching for path separator.
3923                auto last_char = indexOf(path, dirSeparator[0]);
3924
3925                size_t username_len = (last_char == -1) ? path.length : last_char;
3926                char* username = cast(char*) malloc(username_len * char.sizeof);
3927                if (!username)
3928                    onOutOfMemoryError();
3929                scope(exit) free(username);
3930
3931                if (last_char == -1)
3932                {
3933                    username[0 .. username_len - 1] = path[1 .. $];
3934                    last_char = path.length + 1;
3935                }
3936                else
3937                {
3938                    username[0 .. username_len - 1] = path[1 .. last_char];
3939                }
3940                username[username_len - 1] = 0;
3941
3942                assert(last_char > 1);
3943
3944                // Reserve C memory for the getpwnam_r() function.
3945                version (unittest)
3946                    uint extra_memory_size = 2;
3947                else
3948                    uint extra_memory_size = 5 * 1024;
3949                char* extra_memory;
3950                scope(exit) free(extra_memory);
3951
3952                passwd result;
3953                loop: while (1)
3954                {
3955                    extra_memory = cast(char*) realloc(extra_memory, extra_memory_size * char.sizeof);
3956                    if (extra_memory == null)
3957                        onOutOfMemoryError();
3958
3959                    // Obtain info from database.
3960                    passwd *verify;
3961                    errno = 0;
3962                    if (getpwnam_r(username, &result, extra_memory, extra_memory_size,
3963                            &verify) == 0)
3964                    {
3965                        // Succeeded if verify points at result
3966                        if (verify == &result)
3967                            // username is found
3968                            path = combineCPathWithDPath(result.pw_dir, path, last_char);
3969                        break;
3970                    }
3971
3972                    switch (errno)
3973                    {
3974                        case ERANGE:
3975                        // On BSD and OSX, errno can be left at 0 instead of set to ERANGE
3976                        case 0:
3977                            break;
3978
3979                        case ENOENT:
3980                        case ESRCH:
3981                        case EBADF:
3982                        case EPERM:
3983                            // The given name or uid was not found.
3984                            break loop;
3985
3986                        default:
3987                            onOutOfMemoryError();
3988                    }
3989
3990                    // extra_memory isn't large enough
3991                    import core.checkedint : mulu;
3992                    bool overflow;
3993                    extra_memory_size = mulu(extra_memory_size, 2, overflow);
3994                    if (overflow) assert(0);
3995                }
3996                return path;
3997            }
3998        }
3999
4000        // Return early if there is no tilde in path.
4001        if (inputPath.length < 1 || inputPath[0] != '~')
4002            return inputPath;
4003
4004        if (inputPath.length == 1 || isDirSeparator(inputPath[1]))
4005            return expandFromEnvironment(inputPath);
4006        else
4007            return expandFromDatabase(inputPath);
4008    }
4009    else version (Windows)
4010    {
4011        // Put here real windows implementation.
4012        return inputPath;
4013    }
4014    else
4015    {
4016        static assert(0); // Guard. Implement on other platforms.
4017    }
4018}
4019
4020
4021version (unittest) import std.process : environment;
4022@system unittest
4023{
4024    version (Posix)
4025    {
4026        // Retrieve the current home variable.
4027        auto oldHome = environment.get("HOME");
4028
4029        // Testing when there is no environment variable.
4030        environment.remove("HOME");
4031        assert(expandTilde("~/") == "~/");
4032        assert(expandTilde("~") == "~");
4033
4034        // Testing when an environment variable is set.
4035        environment["HOME"] = "dmd/test";
4036        assert(expandTilde("~/") == "dmd/test/");
4037        assert(expandTilde("~") == "dmd/test");
4038
4039        // The same, but with a variable ending in a slash.
4040        environment["HOME"] = "dmd/test/";
4041        assert(expandTilde("~/") == "dmd/test/");
4042        assert(expandTilde("~") == "dmd/test");
4043
4044        // Recover original HOME variable before continuing.
4045        if (oldHome !is null) environment["HOME"] = oldHome;
4046        else environment.remove("HOME");
4047
4048        // Test user expansion for root, no /root on Android
4049        version (OSX)
4050        {
4051            assert(expandTilde("~root") == "/var/root", expandTilde("~root"));
4052            assert(expandTilde("~root/") == "/var/root/", expandTilde("~root/"));
4053        }
4054        else version (Android)
4055        {
4056        }
4057        else
4058        {
4059            assert(expandTilde("~root") == "/root", expandTilde("~root"));
4060            assert(expandTilde("~root/") == "/root/", expandTilde("~root/"));
4061        }
4062        assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey");
4063    }
4064}
4065
4066version (unittest)
4067{
4068    /* Define a mock RandomAccessRange to use for unittesting.
4069     */
4070
4071    struct MockRange(C)
4072    {
4073        this(C[] array) { this.array = array; }
4074      const
4075      {
4076        @property size_t length() { return array.length; }
4077        @property bool empty() { return array.length == 0; }
4078        @property C front() { return array[0]; }
4079        @property C back()  { return array[$ - 1]; }
4080        @property size_t opDollar() { return length; }
4081        C opIndex(size_t i) { return array[i]; }
4082      }
4083        void popFront() { array = array[1 .. $]; }
4084        void popBack()  { array = array[0 .. $-1]; }
4085        MockRange!C opSlice( size_t lwr, size_t upr) const
4086        {
4087            return MockRange!C(array[lwr .. upr]);
4088        }
4089        @property MockRange save() { return this; }
4090      private:
4091        C[] array;
4092    }
4093
4094    static assert( isRandomAccessRange!(MockRange!(const(char))) );
4095}
4096
4097version (unittest)
4098{
4099    /* Define a mock BidirectionalRange to use for unittesting.
4100     */
4101
4102    struct MockBiRange(C)
4103    {
4104        this(const(C)[] array) { this.array = array; }
4105        const
4106        {
4107            @property bool empty() { return array.length == 0; }
4108            @property C front() { return array[0]; }
4109            @property C back()  { return array[$ - 1]; }
4110            @property size_t opDollar() { return array.length; }
4111        }
4112        void popFront() { array = array[1 .. $]; }
4113        void popBack()  { array = array[0 .. $-1]; }
4114        @property MockBiRange save() { return this; }
4115      private:
4116        const(C)[] array;
4117    }
4118
4119    static assert( isBidirectionalRange!(MockBiRange!(const(char))) );
4120}
4121
4122private template BaseOf(R)
4123{
4124    static if (isRandomAccessRange!R && isSomeChar!(ElementType!R))
4125        alias BaseOf = R;
4126    else
4127        alias BaseOf = StringTypeOf!R;
4128}
4129