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