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