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