The OpenD Programming Language

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