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