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