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