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