doc: Describe limitations re Ada, D, and Go on FreeBSD
[official-gcc.git] / libphobos / src / std / path.d
blob449235a6820045947bd0fbdc2bc701946f4771bc
1 // Written in the D programming language.
3 /** This module is used to manipulate path strings.
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).
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.
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.
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.
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)
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)
60 $(TR $(TD Validation) $(TD
61 $(LREF isAbsolute)
62 $(LREF isDirSeparator)
63 $(LREF isRooted)
64 $(LREF isValidFilename)
65 $(LREF isValidPath)
67 $(TR $(TD Extension) $(TD
68 $(LREF defaultExtension)
69 $(LREF extension)
70 $(LREF setExtension)
71 $(LREF stripExtension)
72 $(LREF withDefaultExtension)
73 $(LREF withExtension)
75 $(TR $(TD Other) $(TD
76 $(LREF filenameCharCmp)
77 $(LREF filenameCmp)
78 $(LREF globMatch)
79 $(LREF CaseSensitive)
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)
96 module std.path;
99 import std.file : getcwd;
100 static import std.meta;
101 import std.range;
102 import std.traits;
104 version (OSX)
105 version = Darwin;
106 else version (iOS)
107 version = Darwin;
108 else version (TVOS)
109 version = Darwin;
110 else version (WatchOS)
111 version = Darwin;
113 version (StdUnittest)
115 private:
116 struct TestAliasedString
118 string get() @safe @nogc pure nothrow return scope { return _s; }
119 alias get this;
120 @disable this(this);
121 string _s;
124 bool testAliasedString(alias func, Args...)(scope string s, scope Args args)
126 return func(TestAliasedString(s), args) == func(s, args);
130 /** String used to separate directory names in a path. Under
131 POSIX this is a slash, under Windows a backslash.
133 version (Posix) enum string dirSeparator = "/";
134 else version (Windows) enum string dirSeparator = "\\";
135 else static assert(0, "unsupported platform");
140 /** Path separator string. A colon under POSIX, a semicolon
141 under Windows.
143 version (Posix) enum string pathSeparator = ":";
144 else version (Windows) enum string pathSeparator = ";";
145 else static assert(0, "unsupported platform");
150 /** Determines whether the given character is a directory separator.
152 On Windows, this includes both $(D `\`) and $(D `/`).
153 On POSIX, it's just $(D `/`).
155 bool isDirSeparator(dchar c) @safe pure nothrow @nogc
157 if (c == '/') return true;
158 version (Windows) if (c == '\\') return true;
159 return false;
163 @safe pure nothrow @nogc unittest
165 version (Windows)
167 assert( '/'.isDirSeparator);
168 assert( '\\'.isDirSeparator);
170 else
172 assert( '/'.isDirSeparator);
173 assert(!'\\'.isDirSeparator);
178 /* Determines whether the given character is a drive separator.
180 On Windows, this is true if c is the ':' character that separates
181 the drive letter from the rest of the path. On POSIX, this always
182 returns false.
184 private bool isDriveSeparator(dchar c) @safe pure nothrow @nogc
186 version (Windows) return c == ':';
187 else return false;
191 /* Combines the isDirSeparator and isDriveSeparator tests. */
192 version (Windows) private bool isSeparator(dchar c) @safe pure nothrow @nogc
194 return isDirSeparator(c) || isDriveSeparator(c);
196 version (Posix) private alias isSeparator = isDirSeparator;
199 /* Helper function that determines the position of the last
200 drive/directory separator in a string. Returns -1 if none
201 is found.
203 private ptrdiff_t lastSeparator(R)(R path)
204 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
205 isNarrowString!R)
207 auto i = (cast(ptrdiff_t) path.length) - 1;
208 while (i >= 0 && !isSeparator(path[i])) --i;
209 return i;
213 version (Windows)
215 private bool isUNC(R)(R path)
216 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
217 isNarrowString!R)
219 return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1])
220 && !isDirSeparator(path[2]);
223 private ptrdiff_t uncRootLength(R)(R path)
224 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
225 isNarrowString!R)
226 in { assert(isUNC(path)); }
229 ptrdiff_t i = 3;
230 while (i < path.length && !isDirSeparator(path[i])) ++i;
231 if (i < path.length)
233 auto j = i;
234 do { ++j; } while (j < path.length && isDirSeparator(path[j]));
235 if (j < path.length)
237 do { ++j; } while (j < path.length && !isDirSeparator(path[j]));
238 i = j;
241 return i;
244 private bool hasDrive(R)(R path)
245 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
246 isNarrowString!R)
248 return path.length >= 2 && isDriveSeparator(path[1]);
251 private bool isDriveRoot(R)(R path)
252 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
253 isNarrowString!R)
255 return path.length >= 3 && isDriveSeparator(path[1])
256 && isDirSeparator(path[2]);
261 /* Helper functions that strip leading/trailing slashes and backslashes
262 from a path.
264 private auto ltrimDirSeparators(R)(R path)
265 if (isSomeFiniteCharInputRange!R || isNarrowString!R)
267 static if (isRandomAccessRange!R && hasSlicing!R || isNarrowString!R)
269 int i = 0;
270 while (i < path.length && isDirSeparator(path[i]))
271 ++i;
272 return path[i .. path.length];
274 else
276 while (!path.empty && isDirSeparator(path.front))
277 path.popFront();
278 return path;
282 @safe unittest
284 import std.array;
285 import std.utf : byDchar;
287 assert(ltrimDirSeparators("//abc//").array == "abc//");
288 assert(ltrimDirSeparators("//abc//"d).array == "abc//"d);
289 assert(ltrimDirSeparators("//abc//".byDchar).array == "abc//"d);
292 private auto rtrimDirSeparators(R)(R path)
293 if (isBidirectionalRange!R && isSomeChar!(ElementType!R) ||
294 isNarrowString!R)
296 static if (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R)
298 auto i = (cast(ptrdiff_t) path.length) - 1;
299 while (i >= 0 && isDirSeparator(path[i]))
300 --i;
301 return path[0 .. i+1];
303 else
305 while (!path.empty && isDirSeparator(path.back))
306 path.popBack();
307 return path;
311 @safe unittest
313 import std.array;
314 import std.utf : byDchar;
316 assert(rtrimDirSeparators("//abc//").array == "//abc");
317 assert(rtrimDirSeparators("//abc//"d).array == "//abc"d);
319 assert(rtrimDirSeparators(MockBiRange!char("//abc//")).array == "//abc");
322 private auto trimDirSeparators(R)(R path)
323 if (isBidirectionalRange!R && isSomeChar!(ElementType!R) ||
324 isNarrowString!R)
326 return ltrimDirSeparators(rtrimDirSeparators(path));
329 @safe unittest
331 import std.array;
332 import std.utf : byDchar;
334 assert(trimDirSeparators("//abc//").array == "abc");
335 assert(trimDirSeparators("//abc//"d).array == "abc"d);
337 assert(trimDirSeparators(MockBiRange!char("//abc//")).array == "abc");
340 /** This `enum` is used as a template argument to functions which
341 compare file names, and determines whether the comparison is
342 case sensitive or not.
344 enum CaseSensitive : bool
346 /// File names are case insensitive
347 no = false,
349 /// File names are case sensitive
350 yes = true,
352 /** The default (or most common) setting for the current platform.
353 That is, `no` on Windows and Mac OS X, and `yes` on all
354 POSIX systems except Darwin (Linux, *BSD, etc.).
356 osDefault = osDefaultCaseSensitivity
360 @safe unittest
362 assert(baseName!(CaseSensitive.no)("dir/file.EXT", ".ext") == "file");
363 assert(baseName!(CaseSensitive.yes)("dir/file.EXT", ".ext") != "file");
365 version (Posix)
366 assert(relativePath!(CaseSensitive.no)("/FOO/bar", "/foo/baz") == "../bar");
367 else
368 assert(relativePath!(CaseSensitive.no)(`c:\FOO\bar`, `c:\foo\baz`) == `..\bar`);
371 version (Windows) private enum osDefaultCaseSensitivity = false;
372 else version (Darwin) private enum osDefaultCaseSensitivity = false;
373 else version (Posix) private enum osDefaultCaseSensitivity = true;
374 else static assert(0);
377 Params:
378 cs = Whether or not suffix matching is case-sensitive.
379 path = A path name. It can be a string, or any random-access range of
380 characters.
381 suffix = An optional suffix to be removed from the file name.
382 Returns: The name of the file in the path name, without any leading
383 directory and with an optional suffix chopped off.
385 If `suffix` is specified, it will be compared to `path`
386 using `filenameCmp!cs`,
387 where `cs` is an optional template parameter determining whether
388 the comparison is case sensitive or not. See the
389 $(LREF filenameCmp) documentation for details.
391 Note:
392 This function $(I only) strips away the specified suffix, which
393 doesn't necessarily have to represent an extension.
394 To remove the extension from a path, regardless of what the extension
395 is, use $(LREF stripExtension).
396 To obtain the filename without leading directories and without
397 an extension, combine the functions like this:
399 assert(baseName(stripExtension("dir/file.ext")) == "file");
402 Standards:
403 This function complies with
404 $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html,
405 the POSIX requirements for the 'basename' shell utility)
406 (with suitable adaptations for Windows paths).
408 auto baseName(R)(return scope R path)
409 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!R)
411 return _baseName(path);
414 /// ditto
415 auto baseName(C)(return scope C[] path)
416 if (isSomeChar!C)
418 return _baseName(path);
421 /// ditto
422 inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1)
423 (return scope inout(C)[] path, in C1[] suffix)
424 @safe pure //TODO: nothrow (because of filenameCmp())
425 if (isSomeChar!C && isSomeChar!C1)
427 auto p = baseName(path);
428 if (p.length > suffix.length
429 && filenameCmp!cs(cast(const(C)[])p[$-suffix.length .. $], suffix) == 0)
431 return p[0 .. $-suffix.length];
433 else return p;
437 @safe unittest
439 assert(baseName("dir/file.ext") == "file.ext");
440 assert(baseName("dir/file.ext", ".ext") == "file");
441 assert(baseName("dir/file.ext", ".xyz") == "file.ext");
442 assert(baseName("dir/filename", "name") == "file");
443 assert(baseName("dir/subdir/") == "subdir");
445 version (Windows)
447 assert(baseName(`d:file.ext`) == "file.ext");
448 assert(baseName(`d:\dir\file.ext`) == "file.ext");
452 @safe unittest
454 assert(baseName("").empty);
455 assert(baseName("file.ext"w) == "file.ext");
456 assert(baseName("file.ext"d, ".ext") == "file");
457 assert(baseName("file", "file"w.dup) == "file");
458 assert(baseName("dir/file.ext"d.dup) == "file.ext");
459 assert(baseName("dir/file.ext", ".ext"d) == "file");
460 assert(baseName("dir/file"w, "file"d) == "file");
461 assert(baseName("dir///subdir////") == "subdir");
462 assert(baseName("dir/subdir.ext/", ".ext") == "subdir");
463 assert(baseName("dir/subdir/".dup, "subdir") == "subdir");
464 assert(baseName("/"w.dup) == "/");
465 assert(baseName("//"d.dup) == "/");
466 assert(baseName("///") == "/");
468 assert(baseName!(CaseSensitive.yes)("file.ext", ".EXT") == "file.ext");
469 assert(baseName!(CaseSensitive.no)("file.ext", ".EXT") == "file");
472 auto r = MockRange!(immutable(char))(`dir/file.ext`);
473 auto s = r.baseName();
474 foreach (i, c; `file`)
475 assert(s[i] == c);
478 version (Windows)
480 assert(baseName(`dir\file.ext`) == `file.ext`);
481 assert(baseName(`dir\file.ext`, `.ext`) == `file`);
482 assert(baseName(`dir\file`, `file`) == `file`);
483 assert(baseName(`d:file.ext`) == `file.ext`);
484 assert(baseName(`d:file.ext`, `.ext`) == `file`);
485 assert(baseName(`d:file`, `file`) == `file`);
486 assert(baseName(`dir\\subdir\\\`) == `subdir`);
487 assert(baseName(`dir\subdir.ext\`, `.ext`) == `subdir`);
488 assert(baseName(`dir\subdir\`, `subdir`) == `subdir`);
489 assert(baseName(`\`) == `\`);
490 assert(baseName(`\\`) == `\`);
491 assert(baseName(`\\\`) == `\`);
492 assert(baseName(`d:\`) == `\`);
493 assert(baseName(`d:`).empty);
494 assert(baseName(`\\server\share\file`) == `file`);
495 assert(baseName(`\\server\share\`) == `\`);
496 assert(baseName(`\\server\share`) == `\`);
498 auto r = MockRange!(immutable(char))(`\\server\share`);
499 auto s = r.baseName();
500 foreach (i, c; `\`)
501 assert(s[i] == c);
504 assert(baseName(stripExtension("dir/file.ext")) == "file");
506 static assert(baseName("dir/file.ext") == "file.ext");
507 static assert(baseName("dir/file.ext", ".ext") == "file");
509 static struct DirEntry { string s; alias s this; }
510 assert(baseName(DirEntry("dir/file.ext")) == "file.ext");
513 @safe unittest
515 assert(testAliasedString!baseName("file"));
517 enum S : string { a = "file/path/to/test" }
518 assert(S.a.baseName == "test");
520 char[S.a.length] sa = S.a[];
521 assert(sa.baseName == "test");
524 private R _baseName(R)(return scope R path)
525 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || isNarrowString!R)
527 auto p1 = stripDrive(path);
528 if (p1.empty)
530 version (Windows) if (isUNC(path))
531 return path[0 .. 1];
532 static if (isSomeString!R)
533 return null;
534 else
535 return p1; // which is empty
538 auto p2 = rtrimDirSeparators(p1);
539 if (p2.empty) return p1[0 .. 1];
541 return p2[lastSeparator(p2)+1 .. p2.length];
544 /** Returns the parent directory of `path`. On Windows, this
545 includes the drive letter if present. If `path` is a relative path and
546 the parent directory is the current working directory, returns `"."`.
548 Params:
549 path = A path name.
551 Returns:
552 A slice of `path` or `"."`.
554 Standards:
555 This function complies with
556 $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html,
557 the POSIX requirements for the 'dirname' shell utility)
558 (with suitable adaptations for Windows paths).
560 auto dirName(R)(return scope R path)
561 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R)
563 return _dirName(path);
566 /// ditto
567 auto dirName(C)(return scope C[] path)
568 if (isSomeChar!C)
570 return _dirName(path);
574 @safe unittest
576 assert(dirName("") == ".");
577 assert(dirName("file"w) == ".");
578 assert(dirName("dir/"d) == ".");
579 assert(dirName("dir///") == ".");
580 assert(dirName("dir/file"w.dup) == "dir");
581 assert(dirName("dir///file"d.dup) == "dir");
582 assert(dirName("dir/subdir/") == "dir");
583 assert(dirName("/dir/file"w) == "/dir");
584 assert(dirName("/file"d) == "/");
585 assert(dirName("/") == "/");
586 assert(dirName("///") == "/");
588 version (Windows)
590 assert(dirName(`dir\`) == `.`);
591 assert(dirName(`dir\\\`) == `.`);
592 assert(dirName(`dir\file`) == `dir`);
593 assert(dirName(`dir\\\file`) == `dir`);
594 assert(dirName(`dir\subdir\`) == `dir`);
595 assert(dirName(`\dir\file`) == `\dir`);
596 assert(dirName(`\file`) == `\`);
597 assert(dirName(`\`) == `\`);
598 assert(dirName(`\\\`) == `\`);
599 assert(dirName(`d:`) == `d:`);
600 assert(dirName(`d:file`) == `d:`);
601 assert(dirName(`d:\`) == `d:\`);
602 assert(dirName(`d:\file`) == `d:\`);
603 assert(dirName(`d:\dir\file`) == `d:\dir`);
604 assert(dirName(`\\server\share\dir\file`) == `\\server\share\dir`);
605 assert(dirName(`\\server\share\file`) == `\\server\share`);
606 assert(dirName(`\\server\share\`) == `\\server\share`);
607 assert(dirName(`\\server\share`) == `\\server\share`);
611 @safe unittest
613 assert(testAliasedString!dirName("file"));
615 enum S : string { a = "file/path/to/test" }
616 assert(S.a.dirName == "file/path/to");
618 char[S.a.length] sa = S.a[];
619 assert(sa.dirName == "file/path/to");
622 @safe unittest
624 static assert(dirName("dir/file") == "dir");
626 import std.array;
627 import std.utf : byChar, byWchar, byDchar;
629 assert(dirName("".byChar).array == ".");
630 assert(dirName("file"w.byWchar).array == "."w);
631 assert(dirName("dir/"d.byDchar).array == "."d);
632 assert(dirName("dir///".byChar).array == ".");
633 assert(dirName("dir/subdir/".byChar).array == "dir");
634 assert(dirName("/dir/file"w.byWchar).array == "/dir"w);
635 assert(dirName("/file"d.byDchar).array == "/"d);
636 assert(dirName("/".byChar).array == "/");
637 assert(dirName("///".byChar).array == "/");
639 version (Windows)
641 assert(dirName(`dir\`.byChar).array == `.`);
642 assert(dirName(`dir\\\`.byChar).array == `.`);
643 assert(dirName(`dir\file`.byChar).array == `dir`);
644 assert(dirName(`dir\\\file`.byChar).array == `dir`);
645 assert(dirName(`dir\subdir\`.byChar).array == `dir`);
646 assert(dirName(`\dir\file`.byChar).array == `\dir`);
647 assert(dirName(`\file`.byChar).array == `\`);
648 assert(dirName(`\`.byChar).array == `\`);
649 assert(dirName(`\\\`.byChar).array == `\`);
650 assert(dirName(`d:`.byChar).array == `d:`);
651 assert(dirName(`d:file`.byChar).array == `d:`);
652 assert(dirName(`d:\`.byChar).array == `d:\`);
653 assert(dirName(`d:\file`.byChar).array == `d:\`);
654 assert(dirName(`d:\dir\file`.byChar).array == `d:\dir`);
655 assert(dirName(`\\server\share\dir\file`.byChar).array == `\\server\share\dir`);
656 assert(dirName(`\\server\share\file`) == `\\server\share`);
657 assert(dirName(`\\server\share\`.byChar).array == `\\server\share`);
658 assert(dirName(`\\server\share`.byChar).array == `\\server\share`);
661 //static assert(dirName("dir/file".byChar).array == "dir");
664 private auto _dirName(R)(return scope R path)
666 static auto result(bool dot, typeof(path[0 .. 1]) p)
668 static if (isSomeString!R)
669 return dot ? "." : p;
670 else
672 import std.range : choose, only;
673 return choose(dot, only(cast(ElementEncodingType!R)'.'), p);
677 if (path.empty)
678 return result(true, path[0 .. 0]);
680 auto p = rtrimDirSeparators(path);
681 if (p.empty)
682 return result(false, path[0 .. 1]);
684 version (Windows)
686 if (isUNC(p) && uncRootLength(p) == p.length)
687 return result(false, p);
689 if (p.length == 2 && isDriveSeparator(p[1]) && path.length > 2)
690 return result(false, path[0 .. 3]);
693 auto i = lastSeparator(p);
694 if (i == -1)
695 return result(true, p);
696 if (i == 0)
697 return result(false, p[0 .. 1]);
699 version (Windows)
701 // If the directory part is either d: or d:\
702 // do not chop off the last symbol.
703 if (isDriveSeparator(p[i]) || isDriveSeparator(p[i-1]))
704 return result(false, p[0 .. i+1]);
706 // Remove any remaining trailing (back)slashes.
707 return result(false, rtrimDirSeparators(p[0 .. i]));
710 /** Returns the root directory of the specified path, or `null` if the
711 path is not rooted.
713 Params:
714 path = A path name.
716 Returns:
717 A slice of `path`.
719 auto rootName(R)(R path)
720 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R)
722 return _rootName(path);
725 /// ditto
726 auto rootName(C)(C[] path)
727 if (isSomeChar!C)
729 return _rootName(path);
733 @safe unittest
735 assert(rootName("") is null);
736 assert(rootName("foo") is null);
737 assert(rootName("/") == "/");
738 assert(rootName("/foo/bar") == "/");
740 version (Windows)
742 assert(rootName("d:foo") is null);
743 assert(rootName(`d:\foo`) == `d:\`);
744 assert(rootName(`\\server\share\foo`) == `\\server\share`);
745 assert(rootName(`\\server\share`) == `\\server\share`);
749 @safe unittest
751 assert(testAliasedString!rootName("/foo/bar"));
753 enum S : string { a = "/foo/bar" }
754 assert(S.a.rootName == "/");
756 char[S.a.length] sa = S.a[];
757 assert(sa.rootName == "/");
760 @safe unittest
762 import std.array;
763 import std.utf : byChar;
765 assert(rootName("".byChar).array == "");
766 assert(rootName("foo".byChar).array == "");
767 assert(rootName("/".byChar).array == "/");
768 assert(rootName("/foo/bar".byChar).array == "/");
770 version (Windows)
772 assert(rootName("d:foo".byChar).array == "");
773 assert(rootName(`d:\foo`.byChar).array == `d:\`);
774 assert(rootName(`\\server\share\foo`.byChar).array == `\\server\share`);
775 assert(rootName(`\\server\share`.byChar).array == `\\server\share`);
779 private auto _rootName(R)(R path)
781 if (path.empty)
782 goto Lnull;
784 version (Posix)
786 if (isDirSeparator(path[0])) return path[0 .. 1];
788 else version (Windows)
790 if (isDirSeparator(path[0]))
792 if (isUNC(path)) return path[0 .. uncRootLength(path)];
793 else return path[0 .. 1];
795 else if (path.length >= 3 && isDriveSeparator(path[1]) &&
796 isDirSeparator(path[2]))
798 return path[0 .. 3];
801 else static assert(0, "unsupported platform");
803 assert(!isRooted(path));
804 Lnull:
805 static if (is(StringTypeOf!R))
806 return null; // legacy code may rely on null return rather than slice
807 else
808 return path[0 .. 0];
812 Get the drive portion of a path.
814 Params:
815 path = string or range of characters
817 Returns:
818 A slice of `path` that is the drive, or an empty range if the drive
819 is not specified. In the case of UNC paths, the network share
820 is returned.
822 Always returns an empty range on POSIX.
824 auto driveName(R)(R path)
825 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R)
827 return _driveName(path);
830 /// ditto
831 auto driveName(C)(C[] path)
832 if (isSomeChar!C)
834 return _driveName(path);
838 @safe unittest
840 import std.range : empty;
841 version (Posix) assert(driveName("c:/foo").empty);
842 version (Windows)
844 assert(driveName(`dir\file`).empty);
845 assert(driveName(`d:file`) == "d:");
846 assert(driveName(`d:\file`) == "d:");
847 assert(driveName("d:") == "d:");
848 assert(driveName(`\\server\share\file`) == `\\server\share`);
849 assert(driveName(`\\server\share\`) == `\\server\share`);
850 assert(driveName(`\\server\share`) == `\\server\share`);
852 static assert(driveName(`d:\file`) == "d:");
856 @safe unittest
858 assert(testAliasedString!driveName("d:/file"));
860 version (Posix)
861 immutable result = "";
862 else version (Windows)
863 immutable result = "d:";
865 enum S : string { a = "d:/file" }
866 assert(S.a.driveName == result);
868 char[S.a.length] sa = S.a[];
869 assert(sa.driveName == result);
872 @safe unittest
874 import std.array;
875 import std.utf : byChar;
877 version (Posix) assert(driveName("c:/foo".byChar).empty);
878 version (Windows)
880 assert(driveName(`dir\file`.byChar).empty);
881 assert(driveName(`d:file`.byChar).array == "d:");
882 assert(driveName(`d:\file`.byChar).array == "d:");
883 assert(driveName("d:".byChar).array == "d:");
884 assert(driveName(`\\server\share\file`.byChar).array == `\\server\share`);
885 assert(driveName(`\\server\share\`.byChar).array == `\\server\share`);
886 assert(driveName(`\\server\share`.byChar).array == `\\server\share`);
888 static assert(driveName(`d:\file`).array == "d:");
892 private auto _driveName(R)(R path)
894 version (Windows)
896 if (hasDrive(path))
897 return path[0 .. 2];
898 else if (isUNC(path))
899 return path[0 .. uncRootLength(path)];
901 static if (isSomeString!R)
902 return cast(ElementEncodingType!R[]) null; // legacy code may rely on null return rather than slice
903 else
904 return path[0 .. 0];
907 /** Strips the drive from a Windows path. On POSIX, the path is returned
908 unaltered.
910 Params:
911 path = A pathname
913 Returns: A slice of path without the drive component.
915 auto stripDrive(R)(R path)
916 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!R)
918 return _stripDrive(path);
921 /// ditto
922 auto stripDrive(C)(C[] path)
923 if (isSomeChar!C)
925 return _stripDrive(path);
929 @safe unittest
931 version (Windows)
933 assert(stripDrive(`d:\dir\file`) == `\dir\file`);
934 assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`);
938 @safe unittest
940 assert(testAliasedString!stripDrive("d:/dir/file"));
942 version (Posix)
943 immutable result = "d:/dir/file";
944 else version (Windows)
945 immutable result = "/dir/file";
947 enum S : string { a = "d:/dir/file" }
948 assert(S.a.stripDrive == result);
950 char[S.a.length] sa = S.a[];
951 assert(sa.stripDrive == result);
954 @safe unittest
956 version (Windows)
958 assert(stripDrive(`d:\dir\file`) == `\dir\file`);
959 assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`);
960 static assert(stripDrive(`d:\dir\file`) == `\dir\file`);
962 auto r = MockRange!(immutable(char))(`d:\dir\file`);
963 auto s = r.stripDrive();
964 foreach (i, c; `\dir\file`)
965 assert(s[i] == c);
967 version (Posix)
969 assert(stripDrive(`d:\dir\file`) == `d:\dir\file`);
971 auto r = MockRange!(immutable(char))(`d:\dir\file`);
972 auto s = r.stripDrive();
973 foreach (i, c; `d:\dir\file`)
974 assert(s[i] == c);
978 private auto _stripDrive(R)(R path)
980 version (Windows)
982 if (hasDrive!(BaseOf!R)(path)) return path[2 .. path.length];
983 else if (isUNC!(BaseOf!R)(path)) return path[uncRootLength!(BaseOf!R)(path) .. path.length];
985 return path;
989 /* Helper function that returns the position of the filename/extension
990 separator dot in path.
992 Params:
993 path = file spec as string or indexable range
994 Returns:
995 index of extension separator (the dot), or -1 if not found
997 private ptrdiff_t extSeparatorPos(R)(const R path)
998 if (isRandomAccessRange!R && hasLength!R && isSomeChar!(ElementType!R) ||
999 isNarrowString!R)
1001 for (auto i = path.length; i-- > 0 && !isSeparator(path[i]); )
1003 if (path[i] == '.' && i > 0 && !isSeparator(path[i-1]))
1004 return i;
1006 return -1;
1009 @safe unittest
1011 assert(extSeparatorPos("file") == -1);
1012 assert(extSeparatorPos("file.ext"w) == 4);
1013 assert(extSeparatorPos("file.ext1.ext2"d) == 9);
1014 assert(extSeparatorPos(".foo".dup) == -1);
1015 assert(extSeparatorPos(".foo.ext"w.dup) == 4);
1018 @safe unittest
1020 assert(extSeparatorPos("dir/file"d.dup) == -1);
1021 assert(extSeparatorPos("dir/file.ext") == 8);
1022 assert(extSeparatorPos("dir/file.ext1.ext2"w) == 13);
1023 assert(extSeparatorPos("dir/.foo"d) == -1);
1024 assert(extSeparatorPos("dir/.foo.ext".dup) == 8);
1026 version (Windows)
1028 assert(extSeparatorPos("dir\\file") == -1);
1029 assert(extSeparatorPos("dir\\file.ext") == 8);
1030 assert(extSeparatorPos("dir\\file.ext1.ext2") == 13);
1031 assert(extSeparatorPos("dir\\.foo") == -1);
1032 assert(extSeparatorPos("dir\\.foo.ext") == 8);
1034 assert(extSeparatorPos("d:file") == -1);
1035 assert(extSeparatorPos("d:file.ext") == 6);
1036 assert(extSeparatorPos("d:file.ext1.ext2") == 11);
1037 assert(extSeparatorPos("d:.foo") == -1);
1038 assert(extSeparatorPos("d:.foo.ext") == 6);
1041 static assert(extSeparatorPos("file") == -1);
1042 static assert(extSeparatorPos("file.ext"w) == 4);
1047 Params: path = A path name.
1048 Returns: The _extension part of a file name, including the dot.
1050 If there is no _extension, `null` is returned.
1052 auto extension(R)(R path)
1053 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) ||
1054 is(StringTypeOf!R))
1056 auto i = extSeparatorPos!(BaseOf!R)(path);
1057 if (i == -1)
1059 static if (is(StringTypeOf!R))
1060 return StringTypeOf!R.init[]; // which is null
1061 else
1062 return path[0 .. 0];
1064 else return path[i .. path.length];
1068 @safe unittest
1070 import std.range : empty;
1071 assert(extension("file").empty);
1072 assert(extension("file.") == ".");
1073 assert(extension("file.ext"w) == ".ext");
1074 assert(extension("file.ext1.ext2"d) == ".ext2");
1075 assert(extension(".foo".dup).empty);
1076 assert(extension(".foo.ext"w.dup) == ".ext");
1078 static assert(extension("file").empty);
1079 static assert(extension("file.ext") == ".ext");
1082 @safe unittest
1085 auto r = MockRange!(immutable(char))(`file.ext1.ext2`);
1086 auto s = r.extension();
1087 foreach (i, c; `.ext2`)
1088 assert(s[i] == c);
1091 static struct DirEntry { string s; alias s this; }
1092 assert(extension(DirEntry("file")).empty);
1096 /** Remove extension from path.
1098 Params:
1099 path = string or range to be sliced
1101 Returns:
1102 slice of path with the extension (if any) stripped off
1104 auto stripExtension(R)(R path)
1105 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R)
1107 return _stripExtension(path);
1110 /// Ditto
1111 auto stripExtension(C)(C[] path)
1112 if (isSomeChar!C)
1114 return _stripExtension(path);
1118 @safe unittest
1120 assert(stripExtension("file") == "file");
1121 assert(stripExtension("file.ext") == "file");
1122 assert(stripExtension("file.ext1.ext2") == "file.ext1");
1123 assert(stripExtension("file.") == "file");
1124 assert(stripExtension(".file") == ".file");
1125 assert(stripExtension(".file.ext") == ".file");
1126 assert(stripExtension("dir/file.ext") == "dir/file");
1129 @safe unittest
1131 assert(testAliasedString!stripExtension("file"));
1133 enum S : string { a = "foo.bar" }
1134 assert(S.a.stripExtension == "foo");
1136 char[S.a.length] sa = S.a[];
1137 assert(sa.stripExtension == "foo");
1140 @safe unittest
1142 assert(stripExtension("file.ext"w) == "file");
1143 assert(stripExtension("file.ext1.ext2"d) == "file.ext1");
1145 import std.array;
1146 import std.utf : byChar, byWchar, byDchar;
1148 assert(stripExtension("file".byChar).array == "file");
1149 assert(stripExtension("file.ext"w.byWchar).array == "file");
1150 assert(stripExtension("file.ext1.ext2"d.byDchar).array == "file.ext1");
1153 private auto _stripExtension(R)(R path)
1155 immutable i = extSeparatorPos(path);
1156 return i == -1 ? path : path[0 .. i];
1159 /** Sets or replaces an extension.
1161 If the filename already has an extension, it is replaced. If not, the
1162 extension is simply appended to the filename. Including a leading dot
1163 in `ext` is optional.
1165 If the extension is empty, this function is equivalent to
1166 $(LREF stripExtension).
1168 This function normally allocates a new string (the possible exception
1169 being the case when path is immutable and doesn't already have an
1170 extension).
1172 Params:
1173 path = A path name
1174 ext = The new extension
1176 Returns: A string containing the path given by `path`, but where
1177 the extension has been set to `ext`.
1179 See_Also:
1180 $(LREF withExtension) which does not allocate and returns a lazy range.
1182 immutable(C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext)
1183 if (isSomeChar!C1 && !is(C1 == immutable) && is(immutable C1 == immutable C2))
1187 import std.conv : to;
1188 return withExtension(path, ext).to!(typeof(return));
1190 catch (Exception e)
1192 assert(0);
1196 ///ditto
1197 immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext)
1198 if (isSomeChar!C1 && is(immutable C1 == immutable C2))
1200 if (ext.length == 0)
1201 return stripExtension(path);
1205 import std.conv : to;
1206 return withExtension(path, ext).to!(typeof(return));
1208 catch (Exception e)
1210 assert(0);
1215 @safe unittest
1217 assert(setExtension("file", "ext") == "file.ext");
1218 assert(setExtension("file"w, ".ext"w) == "file.ext");
1219 assert(setExtension("file."d, "ext"d) == "file.ext");
1220 assert(setExtension("file.", ".ext") == "file.ext");
1221 assert(setExtension("file.old"w, "new"w) == "file.new");
1222 assert(setExtension("file.old"d, ".new"d) == "file.new");
1225 @safe unittest
1227 assert(setExtension("file"w.dup, "ext"w) == "file.ext");
1228 assert(setExtension("file"w.dup, ".ext"w) == "file.ext");
1229 assert(setExtension("file."w, "ext"w.dup) == "file.ext");
1230 assert(setExtension("file."w, ".ext"w.dup) == "file.ext");
1231 assert(setExtension("file.old"d.dup, "new"d) == "file.new");
1232 assert(setExtension("file.old"d.dup, ".new"d) == "file.new");
1234 static assert(setExtension("file", "ext") == "file.ext");
1235 static assert(setExtension("file.old", "new") == "file.new");
1237 static assert(setExtension("file"w.dup, "ext"w) == "file.ext");
1238 static assert(setExtension("file.old"d.dup, "new"d) == "file.new");
1240 // https://issues.dlang.org/show_bug.cgi?id=10601
1241 assert(setExtension("file", "") == "file");
1242 assert(setExtension("file.ext", "") == "file");
1245 /************
1246 * Replace existing extension on filespec with new one.
1248 * Params:
1249 * path = string or random access range representing a filespec
1250 * ext = the new extension
1251 * Returns:
1252 * Range with `path`'s extension (if any) replaced with `ext`.
1253 * The element encoding type of the returned range will be the same as `path`'s.
1254 * See_Also:
1255 * $(LREF setExtension)
1257 auto withExtension(R, C)(R path, C[] ext)
1258 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) &&
1259 !isSomeString!R && isSomeChar!C)
1261 return _withExtension(path, ext);
1264 /// Ditto
1265 auto withExtension(C1, C2)(C1[] path, C2[] ext)
1266 if (isSomeChar!C1 && isSomeChar!C2)
1268 return _withExtension(path, ext);
1272 @safe unittest
1274 import std.array;
1275 assert(withExtension("file", "ext").array == "file.ext");
1276 assert(withExtension("file"w, ".ext"w).array == "file.ext");
1277 assert(withExtension("file.ext"w, ".").array == "file.");
1279 import std.utf : byChar, byWchar;
1280 assert(withExtension("file".byChar, "ext").array == "file.ext");
1281 assert(withExtension("file"w.byWchar, ".ext"w).array == "file.ext"w);
1282 assert(withExtension("file.ext"w.byWchar, ".").array == "file."w);
1285 @safe unittest
1287 import std.algorithm.comparison : equal;
1289 assert(testAliasedString!withExtension("file", "ext"));
1291 enum S : string { a = "foo.bar" }
1292 assert(equal(S.a.withExtension(".txt"), "foo.txt"));
1294 char[S.a.length] sa = S.a[];
1295 assert(equal(sa.withExtension(".txt"), "foo.txt"));
1298 private auto _withExtension(R, C)(R path, C[] ext)
1300 import std.range : only, chain;
1301 import std.utf : byUTF;
1303 alias CR = Unqual!(ElementEncodingType!R);
1304 auto dot = only(CR('.'));
1305 if (ext.length == 0 || ext[0] == '.')
1306 dot.popFront(); // so dot is an empty range, too
1307 return chain(stripExtension(path).byUTF!CR, dot, ext.byUTF!CR);
1310 /** Params:
1311 path = A path name.
1312 ext = The default extension to use.
1314 Returns: The path given by `path`, with the extension given by `ext`
1315 appended if the path doesn't already have one.
1317 Including the dot in the extension is optional.
1319 This function always allocates a new string, except in the case when
1320 path is immutable and already has an extension.
1322 immutable(C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext)
1323 if (isSomeChar!C1 && is(immutable C1 == immutable C2))
1325 import std.conv : to;
1326 return withDefaultExtension(path, ext).to!(typeof(return));
1330 @safe unittest
1332 assert(defaultExtension("file", "ext") == "file.ext");
1333 assert(defaultExtension("file", ".ext") == "file.ext");
1334 assert(defaultExtension("file.", "ext") == "file.");
1335 assert(defaultExtension("file.old", "new") == "file.old");
1336 assert(defaultExtension("file.old", ".new") == "file.old");
1339 @safe unittest
1341 assert(defaultExtension("file"w.dup, "ext"w) == "file.ext");
1342 assert(defaultExtension("file.old"d.dup, "new"d) == "file.old");
1344 static assert(defaultExtension("file", "ext") == "file.ext");
1345 static assert(defaultExtension("file.old", "new") == "file.old");
1347 static assert(defaultExtension("file"w.dup, "ext"w) == "file.ext");
1348 static assert(defaultExtension("file.old"d.dup, "new"d) == "file.old");
1352 /********************************
1353 * Set the extension of `path` to `ext` if `path` doesn't have one.
1355 * Params:
1356 * path = filespec as string or range
1357 * ext = extension, may have leading '.'
1358 * Returns:
1359 * range with the result
1361 auto withDefaultExtension(R, C)(R path, C[] ext)
1362 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) &&
1363 !isSomeString!R && isSomeChar!C)
1365 return _withDefaultExtension(path, ext);
1368 /// Ditto
1369 auto withDefaultExtension(C1, C2)(C1[] path, C2[] ext)
1370 if (isSomeChar!C1 && isSomeChar!C2)
1372 return _withDefaultExtension(path, ext);
1376 @safe unittest
1378 import std.array;
1379 assert(withDefaultExtension("file", "ext").array == "file.ext");
1380 assert(withDefaultExtension("file"w, ".ext").array == "file.ext"w);
1381 assert(withDefaultExtension("file.", "ext").array == "file.");
1382 assert(withDefaultExtension("file", "").array == "file.");
1384 import std.utf : byChar, byWchar;
1385 assert(withDefaultExtension("file".byChar, "ext").array == "file.ext");
1386 assert(withDefaultExtension("file"w.byWchar, ".ext").array == "file.ext"w);
1387 assert(withDefaultExtension("file.".byChar, "ext"d).array == "file.");
1388 assert(withDefaultExtension("file".byChar, "").array == "file.");
1391 @safe unittest
1393 import std.algorithm.comparison : equal;
1395 assert(testAliasedString!withDefaultExtension("file", "ext"));
1397 enum S : string { a = "foo" }
1398 assert(equal(S.a.withDefaultExtension(".txt"), "foo.txt"));
1400 char[S.a.length] sa = S.a[];
1401 assert(equal(sa.withDefaultExtension(".txt"), "foo.txt"));
1404 private auto _withDefaultExtension(R, C)(R path, C[] ext)
1406 import std.range : only, chain;
1407 import std.utf : byUTF;
1409 alias CR = Unqual!(ElementEncodingType!R);
1410 auto dot = only(CR('.'));
1411 immutable i = extSeparatorPos(path);
1412 if (i == -1)
1414 if (ext.length > 0 && ext[0] == '.')
1415 ext = ext[1 .. $]; // remove any leading . from ext[]
1417 else
1419 // path already has an extension, so make these empty
1420 ext = ext[0 .. 0];
1421 dot.popFront();
1423 return chain(path.byUTF!CR, dot, ext.byUTF!CR);
1426 /** Combines one or more path segments.
1428 This function takes a set of path segments, given as an input
1429 range of string elements or as a set of string arguments,
1430 and concatenates them with each other. Directory separators
1431 are inserted between segments if necessary. If any of the
1432 path segments are absolute (as defined by $(LREF isAbsolute)), the
1433 preceding segments will be dropped.
1435 On Windows, if one of the path segments are rooted, but not absolute
1436 (e.g. $(D `\foo`)), all preceding path segments down to the previous
1437 root will be dropped. (See below for an example.)
1439 This function always allocates memory to hold the resulting path.
1440 The variadic overload is guaranteed to only perform a single
1441 allocation, as is the range version if `paths` is a forward
1442 range.
1444 Params:
1445 segments = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
1446 of segments to assemble the path from.
1447 Returns: The assembled path.
1449 immutable(ElementEncodingType!(ElementType!Range))[]
1450 buildPath(Range)(scope Range segments)
1451 if (isInputRange!Range && !isInfinite!Range && isSomeString!(ElementType!Range))
1453 if (segments.empty) return null;
1455 // If this is a forward range, we can pre-calculate a maximum length.
1456 static if (isForwardRange!Range)
1458 auto segments2 = segments.save;
1459 size_t precalc = 0;
1460 foreach (segment; segments2) precalc += segment.length + 1;
1462 // Otherwise, just venture a guess and resize later if necessary.
1463 else size_t precalc = 255;
1465 auto buf = new Unqual!(ElementEncodingType!(ElementType!Range))[](precalc);
1466 size_t pos = 0;
1467 foreach (segment; segments)
1469 if (segment.empty) continue;
1470 static if (!isForwardRange!Range)
1472 immutable neededLength = pos + segment.length + 1;
1473 if (buf.length < neededLength)
1474 buf.length = reserve(buf, neededLength + buf.length/2);
1476 auto r = chainPath(buf[0 .. pos], segment);
1477 size_t i;
1478 foreach (c; r)
1480 buf[i] = c;
1481 ++i;
1483 pos = i;
1485 static U trustedCast(U, V)(V v) @trusted pure nothrow { return cast(U) v; }
1486 return trustedCast!(typeof(return))(buf[0 .. pos]);
1489 /// ditto
1490 immutable(C)[] buildPath(C)(const(C)[][] paths...)
1491 @safe pure nothrow
1492 if (isSomeChar!C)
1494 return buildPath!(typeof(paths))(paths);
1498 @safe unittest
1500 version (Posix)
1502 assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1503 assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
1504 assert(buildPath("/foo", "/bar") == "/bar");
1507 version (Windows)
1509 assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1510 assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1511 assert(buildPath("foo", `d:\bar`) == `d:\bar`);
1512 assert(buildPath("foo", `\bar`) == `\bar`);
1513 assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
1517 @system unittest // non-documented
1519 import std.range;
1520 // ir() wraps an array in a plain (i.e. non-forward) input range, so that
1521 // we can test both code paths
1522 InputRange!(C[]) ir(C)(C[][] p...) { return inputRangeObject(p.dup); }
1523 version (Posix)
1525 assert(buildPath("foo") == "foo");
1526 assert(buildPath("/foo/") == "/foo/");
1527 assert(buildPath("foo", "bar") == "foo/bar");
1528 assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1529 assert(buildPath("foo/".dup, "bar") == "foo/bar");
1530 assert(buildPath("foo///", "bar".dup) == "foo///bar");
1531 assert(buildPath("/foo"w, "bar"w) == "/foo/bar");
1532 assert(buildPath("foo"w.dup, "/bar"w) == "/bar");
1533 assert(buildPath("foo"w, "bar/"w.dup) == "foo/bar/");
1534 assert(buildPath("/"d, "foo"d) == "/foo");
1535 assert(buildPath(""d.dup, "foo"d) == "foo");
1536 assert(buildPath("foo"d, ""d.dup) == "foo");
1537 assert(buildPath("foo", "bar".dup, "baz") == "foo/bar/baz");
1538 assert(buildPath("foo"w, "/bar"w, "baz"w.dup) == "/bar/baz");
1540 static assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1541 static assert(buildPath("foo", "/bar", "baz") == "/bar/baz");
1543 // The following are mostly duplicates of the above, except that the
1544 // range version does not accept mixed constness.
1545 assert(buildPath(ir("foo")) == "foo");
1546 assert(buildPath(ir("/foo/")) == "/foo/");
1547 assert(buildPath(ir("foo", "bar")) == "foo/bar");
1548 assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1549 assert(buildPath(ir("foo/".dup, "bar".dup)) == "foo/bar");
1550 assert(buildPath(ir("foo///".dup, "bar".dup)) == "foo///bar");
1551 assert(buildPath(ir("/foo"w, "bar"w)) == "/foo/bar");
1552 assert(buildPath(ir("foo"w.dup, "/bar"w.dup)) == "/bar");
1553 assert(buildPath(ir("foo"w.dup, "bar/"w.dup)) == "foo/bar/");
1554 assert(buildPath(ir("/"d, "foo"d)) == "/foo");
1555 assert(buildPath(ir(""d.dup, "foo"d.dup)) == "foo");
1556 assert(buildPath(ir("foo"d, ""d)) == "foo");
1557 assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1558 assert(buildPath(ir("foo"w.dup, "/bar"w.dup, "baz"w.dup)) == "/bar/baz");
1560 version (Windows)
1562 assert(buildPath("foo") == "foo");
1563 assert(buildPath(`\foo/`) == `\foo/`);
1564 assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1565 assert(buildPath("foo", `\bar`) == `\bar`);
1566 assert(buildPath(`c:\foo`, "bar") == `c:\foo\bar`);
1567 assert(buildPath("foo"w, `d:\bar`w.dup) == `d:\bar`);
1568 assert(buildPath(`c:\foo\bar`, `\baz`) == `c:\baz`);
1569 assert(buildPath(`\\foo\bar\baz`d, `foo`d, `\bar`d) == `\\foo\bar\bar`d);
1571 static assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1572 static assert(buildPath("foo", `c:\bar`, "baz") == `c:\bar\baz`);
1574 assert(buildPath(ir("foo")) == "foo");
1575 assert(buildPath(ir(`\foo/`)) == `\foo/`);
1576 assert(buildPath(ir("foo", "bar", "baz")) == `foo\bar\baz`);
1577 assert(buildPath(ir("foo", `\bar`)) == `\bar`);
1578 assert(buildPath(ir(`c:\foo`, "bar")) == `c:\foo\bar`);
1579 assert(buildPath(ir("foo"w.dup, `d:\bar`w.dup)) == `d:\bar`);
1580 assert(buildPath(ir(`c:\foo\bar`, `\baz`)) == `c:\baz`);
1581 assert(buildPath(ir(`\\foo\bar\baz`d, `foo`d, `\bar`d)) == `\\foo\bar\bar`d);
1584 // Test that allocation works as it should.
1585 auto manyShort = "aaa".repeat(1000).array();
1586 auto manyShortCombined = join(manyShort, dirSeparator);
1587 assert(buildPath(manyShort) == manyShortCombined);
1588 assert(buildPath(ir(manyShort)) == manyShortCombined);
1590 auto fewLong = 'b'.repeat(500).array().repeat(10).array();
1591 auto fewLongCombined = join(fewLong, dirSeparator);
1592 assert(buildPath(fewLong) == fewLongCombined);
1593 assert(buildPath(ir(fewLong)) == fewLongCombined);
1596 @safe unittest
1598 // Test for https://issues.dlang.org/show_bug.cgi?id=7397
1599 string[] ary = ["a", "b"];
1600 version (Posix)
1602 assert(buildPath(ary) == "a/b");
1604 else version (Windows)
1606 assert(buildPath(ary) == `a\b`);
1612 * Concatenate path segments together to form one path.
1614 * Params:
1615 * r1 = first segment
1616 * r2 = second segment
1617 * ranges = 0 or more segments
1618 * Returns:
1619 * Lazy range which is the concatenation of r1, r2 and ranges with path separators.
1620 * The resulting element type is that of r1.
1621 * See_Also:
1622 * $(LREF buildPath)
1624 auto chainPath(R1, R2, Ranges...)(R1 r1, R2 r2, Ranges ranges)
1625 if ((isRandomAccessRange!R1 && hasSlicing!R1 && hasLength!R1 && isSomeChar!(ElementType!R1) ||
1626 isNarrowString!R1 &&
1627 !isConvertibleToString!R1) &&
1628 (isRandomAccessRange!R2 && hasSlicing!R2 && hasLength!R2 && isSomeChar!(ElementType!R2) ||
1629 isNarrowString!R2 &&
1630 !isConvertibleToString!R2) &&
1631 (Ranges.length == 0 || is(typeof(chainPath(r2, ranges))))
1634 static if (Ranges.length)
1636 return chainPath(chainPath(r1, r2), ranges);
1638 else
1640 import std.range : only, chain;
1641 import std.utf : byUTF;
1643 alias CR = Unqual!(ElementEncodingType!R1);
1644 auto sep = only(CR(dirSeparator[0]));
1645 bool usesep = false;
1647 auto pos = r1.length;
1649 if (pos)
1651 if (isRooted(r2))
1653 version (Posix)
1655 pos = 0;
1657 else version (Windows)
1659 if (isAbsolute(r2))
1660 pos = 0;
1661 else
1663 pos = rootName(r1).length;
1664 if (pos > 0 && isDirSeparator(r1[pos - 1]))
1665 --pos;
1668 else
1669 static assert(0);
1671 else if (!isDirSeparator(r1[pos - 1]))
1672 usesep = true;
1674 if (!usesep)
1675 sep.popFront();
1676 // Return r1 ~ '/' ~ r2
1677 return chain(r1[0 .. pos].byUTF!CR, sep, r2.byUTF!CR);
1682 @safe unittest
1684 import std.array;
1685 version (Posix)
1687 assert(chainPath("foo", "bar", "baz").array == "foo/bar/baz");
1688 assert(chainPath("/foo/", "bar/baz").array == "/foo/bar/baz");
1689 assert(chainPath("/foo", "/bar").array == "/bar");
1692 version (Windows)
1694 assert(chainPath("foo", "bar", "baz").array == `foo\bar\baz`);
1695 assert(chainPath(`c:\foo`, `bar\baz`).array == `c:\foo\bar\baz`);
1696 assert(chainPath("foo", `d:\bar`).array == `d:\bar`);
1697 assert(chainPath("foo", `\bar`).array == `\bar`);
1698 assert(chainPath(`c:\foo`, `\bar`).array == `c:\bar`);
1701 import std.utf : byChar;
1702 version (Posix)
1704 assert(chainPath("foo", "bar", "baz").array == "foo/bar/baz");
1705 assert(chainPath("/foo/".byChar, "bar/baz").array == "/foo/bar/baz");
1706 assert(chainPath("/foo", "/bar".byChar).array == "/bar");
1709 version (Windows)
1711 assert(chainPath("foo", "bar", "baz").array == `foo\bar\baz`);
1712 assert(chainPath(`c:\foo`.byChar, `bar\baz`).array == `c:\foo\bar\baz`);
1713 assert(chainPath("foo", `d:\bar`).array == `d:\bar`);
1714 assert(chainPath("foo", `\bar`.byChar).array == `\bar`);
1715 assert(chainPath(`c:\foo`, `\bar`w).array == `c:\bar`);
1719 auto chainPath(Ranges...)(auto ref Ranges ranges)
1720 if (Ranges.length >= 2 &&
1721 std.meta.anySatisfy!(isConvertibleToString, Ranges))
1723 import std.meta : staticMap;
1724 alias Types = staticMap!(convertToString, Ranges);
1725 return chainPath!Types(ranges);
1728 @safe unittest
1730 assert(chainPath(TestAliasedString(null), TestAliasedString(null), TestAliasedString(null)).empty);
1731 assert(chainPath(TestAliasedString(null), TestAliasedString(null), "").empty);
1732 assert(chainPath(TestAliasedString(null), "", TestAliasedString(null)).empty);
1733 static struct S { string s; }
1734 static assert(!__traits(compiles, chainPath(TestAliasedString(null), S(""), TestAliasedString(null))));
1737 /** Performs the same task as $(LREF buildPath),
1738 while at the same time resolving current/parent directory
1739 symbols (`"."` and `".."`) and removing superfluous
1740 directory separators.
1741 It will return "." if the path leads to the starting directory.
1742 On Windows, slashes are replaced with backslashes.
1744 Using buildNormalizedPath on null paths will always return null.
1746 Note that this function does not resolve symbolic links.
1748 This function always allocates memory to hold the resulting path.
1749 Use $(LREF asNormalizedPath) to not allocate memory.
1751 Params:
1752 paths = An array of paths to assemble.
1754 Returns: The assembled path.
1756 immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...)
1757 @safe pure nothrow
1758 if (isSomeChar!C)
1760 import std.array : array;
1762 const(C)[] chained;
1763 foreach (path; paths)
1765 if (chained)
1766 chained = chainPath(chained, path).array;
1767 else
1768 chained = path;
1770 auto result = asNormalizedPath(chained);
1771 // .array returns a copy, so it is unique
1772 return result.array;
1776 @safe unittest
1778 assert(buildNormalizedPath("foo", "..") == ".");
1780 version (Posix)
1782 assert(buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz");
1783 assert(buildNormalizedPath("../foo/.") == "../foo");
1784 assert(buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
1785 assert(buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
1786 assert(buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
1787 assert(buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
1790 version (Windows)
1792 assert(buildNormalizedPath(`c:\foo\.\bar/..\\baz\`) == `c:\foo\baz`);
1793 assert(buildNormalizedPath(`..\foo\.`) == `..\foo`);
1794 assert(buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
1795 assert(buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
1796 assert(buildNormalizedPath(`\\server\share\foo`, `..\bar`) ==
1797 `\\server\share\bar`);
1801 @safe unittest
1803 assert(buildNormalizedPath(".", ".") == ".");
1804 assert(buildNormalizedPath("foo", "..") == ".");
1805 assert(buildNormalizedPath("", "") is null);
1806 assert(buildNormalizedPath("", ".") == ".");
1807 assert(buildNormalizedPath(".", "") == ".");
1808 assert(buildNormalizedPath(null, "foo") == "foo");
1809 assert(buildNormalizedPath("", "foo") == "foo");
1810 assert(buildNormalizedPath("", "") == "");
1811 assert(buildNormalizedPath("", null) == "");
1812 assert(buildNormalizedPath(null, "") == "");
1813 assert(buildNormalizedPath!(char)(null, null) == "");
1815 version (Posix)
1817 assert(buildNormalizedPath("/", "foo", "bar") == "/foo/bar");
1818 assert(buildNormalizedPath("foo", "bar", "baz") == "foo/bar/baz");
1819 assert(buildNormalizedPath("foo", "bar/baz") == "foo/bar/baz");
1820 assert(buildNormalizedPath("foo", "bar//baz///") == "foo/bar/baz");
1821 assert(buildNormalizedPath("/foo", "bar/baz") == "/foo/bar/baz");
1822 assert(buildNormalizedPath("/foo", "/bar/baz") == "/bar/baz");
1823 assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1824 assert(buildNormalizedPath("/foo/..", "bar/baz") == "/bar/baz");
1825 assert(buildNormalizedPath("/foo/../../", "bar/baz") == "/bar/baz");
1826 assert(buildNormalizedPath("/foo/bar", "../baz") == "/foo/baz");
1827 assert(buildNormalizedPath("/foo/bar", "../../baz") == "/baz");
1828 assert(buildNormalizedPath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee");
1829 assert(buildNormalizedPath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee");
1830 static assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1832 else version (Windows)
1834 assert(buildNormalizedPath(`\`, `foo`, `bar`) == `\foo\bar`);
1835 assert(buildNormalizedPath(`foo`, `bar`, `baz`) == `foo\bar\baz`);
1836 assert(buildNormalizedPath(`foo`, `bar\baz`) == `foo\bar\baz`);
1837 assert(buildNormalizedPath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`);
1838 assert(buildNormalizedPath(`\foo`, `bar\baz`) == `\foo\bar\baz`);
1839 assert(buildNormalizedPath(`\foo`, `\bar\baz`) == `\bar\baz`);
1840 assert(buildNormalizedPath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`);
1841 assert(buildNormalizedPath(`\foo\..`, `bar\baz`) == `\bar\baz`);
1842 assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1843 assert(buildNormalizedPath(`\foo\bar`, `..\baz`) == `\foo\baz`);
1844 assert(buildNormalizedPath(`\foo\bar`, `../../baz`) == `\baz`);
1845 assert(buildNormalizedPath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`);
1847 assert(buildNormalizedPath(`c:\`, `foo`, `bar`) == `c:\foo\bar`);
1848 assert(buildNormalizedPath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`);
1849 assert(buildNormalizedPath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`);
1850 assert(buildNormalizedPath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`);
1851 assert(buildNormalizedPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1852 assert(buildNormalizedPath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`);
1853 assert(buildNormalizedPath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`);
1854 assert(buildNormalizedPath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`);
1855 assert(buildNormalizedPath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`);
1856 assert(buildNormalizedPath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`);
1857 assert(buildNormalizedPath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`);
1858 assert(buildNormalizedPath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`);
1860 assert(buildNormalizedPath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`);
1861 assert(buildNormalizedPath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`);
1862 assert(buildNormalizedPath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`);
1863 assert(buildNormalizedPath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`);
1864 assert(buildNormalizedPath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`);
1865 assert(buildNormalizedPath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`);
1866 assert(buildNormalizedPath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`);
1867 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`);
1868 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`);
1869 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`);
1871 static assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1873 else static assert(0);
1876 @safe unittest
1878 // Test for https://issues.dlang.org/show_bug.cgi?id=7397
1879 string[] ary = ["a", "b"];
1880 version (Posix)
1882 assert(buildNormalizedPath(ary) == "a/b");
1884 else version (Windows)
1886 assert(buildNormalizedPath(ary) == `a\b`);
1891 /** Normalize a path by resolving current/parent directory
1892 symbols (`"."` and `".."`) and removing superfluous
1893 directory separators.
1894 It will return "." if the path leads to the starting directory.
1895 On Windows, slashes are replaced with backslashes.
1897 Using asNormalizedPath on empty paths will always return an empty path.
1899 Does not resolve symbolic links.
1901 This function always allocates memory to hold the resulting path.
1902 Use $(LREF buildNormalizedPath) to allocate memory and return a string.
1904 Params:
1905 path = string or random access range representing the path to normalize
1907 Returns:
1908 normalized path as a forward range
1911 auto asNormalizedPath(R)(return scope R path)
1912 if (isSomeChar!(ElementEncodingType!R) &&
1913 (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R) &&
1914 !isConvertibleToString!R)
1916 alias C = Unqual!(ElementEncodingType!R);
1917 alias S = typeof(path[0 .. 0]);
1919 static struct Result
1921 @property bool empty()
1923 return c == c.init;
1926 @property C front()
1928 return c;
1931 void popFront()
1933 C lastc = c;
1934 c = c.init;
1935 if (!element.empty)
1937 c = getElement0();
1938 return;
1941 while (1)
1943 if (elements.empty)
1945 element = element[0 .. 0];
1946 return;
1948 element = elements.front;
1949 elements.popFront();
1950 if (isDot(element) || (rooted && isDotDot(element)))
1951 continue;
1953 if (rooted || !isDotDot(element))
1955 int n = 1;
1956 auto elements2 = elements.save;
1957 while (!elements2.empty)
1959 auto e = elements2.front;
1960 elements2.popFront();
1961 if (isDot(e))
1962 continue;
1963 if (isDotDot(e))
1965 --n;
1966 if (n == 0)
1968 elements = elements2;
1969 element = element[0 .. 0];
1970 continue L1;
1973 else
1974 ++n;
1977 break;
1980 static assert(dirSeparator.length == 1);
1981 if (lastc == dirSeparator[0] || lastc == lastc.init)
1982 c = getElement0();
1983 else
1984 c = dirSeparator[0];
1987 static if (isForwardRange!R)
1989 @property auto save()
1991 auto result = this;
1992 result.element = element.save;
1993 result.elements = elements.save;
1994 return result;
1998 private:
1999 this(R path)
2001 element = rootName(path);
2002 auto i = element.length;
2003 while (i < path.length && isDirSeparator(path[i]))
2004 ++i;
2005 rooted = i > 0;
2006 elements = pathSplitter(path[i .. $]);
2007 popFront();
2008 if (c == c.init && path.length)
2009 c = C('.');
2012 C getElement0()
2014 static if (isNarrowString!S) // avoid autodecode
2016 C c = element[0];
2017 element = element[1 .. $];
2019 else
2021 C c = element.front;
2022 element.popFront();
2024 version (Windows)
2026 if (c == '/') // can appear in root element
2027 c = '\\'; // use native Windows directory separator
2029 return c;
2032 // See if elem is "."
2033 static bool isDot(S elem)
2035 return elem.length == 1 && elem[0] == '.';
2038 // See if elem is ".."
2039 static bool isDotDot(S elem)
2041 return elem.length == 2 && elem[0] == '.' && elem[1] == '.';
2044 bool rooted; // the path starts with a root directory
2045 C c;
2046 S element;
2047 typeof(pathSplitter(path[0 .. 0])) elements;
2050 return Result(path);
2054 @safe unittest
2056 import std.array;
2057 assert(asNormalizedPath("foo/..").array == ".");
2059 version (Posix)
2061 assert(asNormalizedPath("/foo/./bar/..//baz/").array == "/foo/baz");
2062 assert(asNormalizedPath("../foo/.").array == "../foo");
2063 assert(asNormalizedPath("/foo/bar/baz/").array == "/foo/bar/baz");
2064 assert(asNormalizedPath("/foo/./bar/../../baz").array == "/baz");
2067 version (Windows)
2069 assert(asNormalizedPath(`c:\foo\.\bar/..\\baz\`).array == `c:\foo\baz`);
2070 assert(asNormalizedPath(`..\foo\.`).array == `..\foo`);
2071 assert(asNormalizedPath(`c:\foo\bar\baz\`).array == `c:\foo\bar\baz`);
2072 assert(asNormalizedPath(`c:\foo\bar/..`).array == `c:\foo`);
2073 assert(asNormalizedPath(`\\server\share\foo\..\bar`).array ==
2074 `\\server\share\bar`);
2078 auto asNormalizedPath(R)(return scope auto ref R path)
2079 if (isConvertibleToString!R)
2081 return asNormalizedPath!(StringTypeOf!R)(path);
2084 @safe unittest
2086 assert(testAliasedString!asNormalizedPath(null));
2089 @safe unittest
2091 import std.array;
2092 import std.utf : byChar;
2094 assert(asNormalizedPath("").array is null);
2095 assert(asNormalizedPath("foo").array == "foo");
2096 assert(asNormalizedPath(".").array == ".");
2097 assert(asNormalizedPath("./.").array == ".");
2098 assert(asNormalizedPath("foo/..").array == ".");
2100 auto save = asNormalizedPath("fob").save;
2101 save.popFront();
2102 assert(save.front == 'o');
2104 version (Posix)
2106 assert(asNormalizedPath("/foo/bar").array == "/foo/bar");
2107 assert(asNormalizedPath("foo/bar/baz").array == "foo/bar/baz");
2108 assert(asNormalizedPath("foo/bar/baz").array == "foo/bar/baz");
2109 assert(asNormalizedPath("foo/bar//baz///").array == "foo/bar/baz");
2110 assert(asNormalizedPath("/foo/bar/baz").array == "/foo/bar/baz");
2111 assert(asNormalizedPath("/foo/../bar/baz").array == "/bar/baz");
2112 assert(asNormalizedPath("/foo/../..//bar/baz").array == "/bar/baz");
2113 assert(asNormalizedPath("/foo/bar/../baz").array == "/foo/baz");
2114 assert(asNormalizedPath("/foo/bar/../../baz").array == "/baz");
2115 assert(asNormalizedPath("/foo/bar/.././/baz/../wee/").array == "/foo/wee");
2116 assert(asNormalizedPath("//foo/bar/baz///wee").array == "/foo/bar/baz/wee");
2118 assert(asNormalizedPath("foo//bar").array == "foo/bar");
2119 assert(asNormalizedPath("foo/bar").array == "foo/bar");
2121 //Curent dir path
2122 assert(asNormalizedPath("./").array == ".");
2123 assert(asNormalizedPath("././").array == ".");
2124 assert(asNormalizedPath("./foo/..").array == ".");
2125 assert(asNormalizedPath("foo/..").array == ".");
2127 else version (Windows)
2129 assert(asNormalizedPath(`\foo\bar`).array == `\foo\bar`);
2130 assert(asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`);
2131 assert(asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`);
2132 assert(asNormalizedPath(`foo\bar\\baz\\\`).array == `foo\bar\baz`);
2133 assert(asNormalizedPath(`\foo\bar\baz`).array == `\foo\bar\baz`);
2134 assert(asNormalizedPath(`\foo\..\\bar\.\baz`).array == `\bar\baz`);
2135 assert(asNormalizedPath(`\foo\..\bar\baz`).array == `\bar\baz`);
2136 assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`);
2138 assert(asNormalizedPath(`\foo\bar\..\baz`).array == `\foo\baz`);
2139 assert(asNormalizedPath(`\foo\bar\../../baz`).array == `\baz`);
2140 assert(asNormalizedPath(`\foo\bar\..\.\/baz\..\wee\`).array == `\foo\wee`);
2142 assert(asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`);
2143 assert(asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`);
2144 assert(asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`);
2145 assert(asNormalizedPath(`c:foo\bar\\baz\\\`).array == `c:foo\bar\baz`);
2146 assert(asNormalizedPath(`c:\foo\bar\baz`).array == `c:\foo\bar\baz`);
2148 assert(asNormalizedPath(`c:\foo\..\\bar\.\baz`).array == `c:\bar\baz`);
2149 assert(asNormalizedPath(`c:\foo\..\bar\baz`).array == `c:\bar\baz`);
2150 assert(asNormalizedPath(`c:\foo\..\..\\bar\baz`).array == `c:\bar\baz`);
2151 assert(asNormalizedPath(`c:\foo\bar\..\baz`).array == `c:\foo\baz`);
2152 assert(asNormalizedPath(`c:\foo\bar\..\..\baz`).array == `c:\baz`);
2153 assert(asNormalizedPath(`c:\foo\bar\..\.\\baz\..\wee\`).array == `c:\foo\wee`);
2154 assert(asNormalizedPath(`\\server\share\foo\bar`).array == `\\server\share\foo\bar`);
2155 assert(asNormalizedPath(`\\server\share\\foo\bar`).array == `\\server\share\foo\bar`);
2156 assert(asNormalizedPath(`\\server\share\foo\bar\baz`).array == `\\server\share\foo\bar\baz`);
2157 assert(asNormalizedPath(`\\server\share\foo\..\\bar\.\baz`).array == `\\server\share\bar\baz`);
2158 assert(asNormalizedPath(`\\server\share\foo\..\bar\baz`).array == `\\server\share\bar\baz`);
2159 assert(asNormalizedPath(`\\server\share\foo\..\..\\bar\baz`).array == `\\server\share\bar\baz`);
2160 assert(asNormalizedPath(`\\server\share\foo\bar\..\baz`).array == `\\server\share\foo\baz`);
2161 assert(asNormalizedPath(`\\server\share\foo\bar\..\..\baz`).array == `\\server\share\baz`);
2162 assert(asNormalizedPath(`\\server\share\foo\bar\..\.\\baz\..\wee\`).array == `\\server\share\foo\wee`);
2164 static assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`);
2166 assert(asNormalizedPath("foo//bar").array == `foo\bar`);
2168 //Curent dir path
2169 assert(asNormalizedPath(`.\`).array == ".");
2170 assert(asNormalizedPath(`.\.\`).array == ".");
2171 assert(asNormalizedPath(`.\foo\..`).array == ".");
2172 assert(asNormalizedPath(`foo\..`).array == ".");
2174 else static assert(0);
2177 @safe unittest
2179 import std.array;
2181 version (Posix)
2183 // Trivial
2184 assert(asNormalizedPath("").empty);
2185 assert(asNormalizedPath("foo/bar").array == "foo/bar");
2187 // Correct handling of leading slashes
2188 assert(asNormalizedPath("/").array == "/");
2189 assert(asNormalizedPath("///").array == "/");
2190 assert(asNormalizedPath("////").array == "/");
2191 assert(asNormalizedPath("/foo/bar").array == "/foo/bar");
2192 assert(asNormalizedPath("//foo/bar").array == "/foo/bar");
2193 assert(asNormalizedPath("///foo/bar").array == "/foo/bar");
2194 assert(asNormalizedPath("////foo/bar").array == "/foo/bar");
2196 // Correct handling of single-dot symbol (current directory)
2197 assert(asNormalizedPath("/./foo").array == "/foo");
2198 assert(asNormalizedPath("/foo/./bar").array == "/foo/bar");
2200 assert(asNormalizedPath("./foo").array == "foo");
2201 assert(asNormalizedPath("././foo").array == "foo");
2202 assert(asNormalizedPath("foo/././bar").array == "foo/bar");
2204 // Correct handling of double-dot symbol (previous directory)
2205 assert(asNormalizedPath("/foo/../bar").array == "/bar");
2206 assert(asNormalizedPath("/foo/../../bar").array == "/bar");
2207 assert(asNormalizedPath("/../foo").array == "/foo");
2208 assert(asNormalizedPath("/../../foo").array == "/foo");
2209 assert(asNormalizedPath("/foo/..").array == "/");
2210 assert(asNormalizedPath("/foo/../..").array == "/");
2212 assert(asNormalizedPath("foo/../bar").array == "bar");
2213 assert(asNormalizedPath("foo/../../bar").array == "../bar");
2214 assert(asNormalizedPath("../foo").array == "../foo");
2215 assert(asNormalizedPath("../../foo").array == "../../foo");
2216 assert(asNormalizedPath("../foo/../bar").array == "../bar");
2217 assert(asNormalizedPath(".././../foo").array == "../../foo");
2218 assert(asNormalizedPath("foo/bar/..").array == "foo");
2219 assert(asNormalizedPath("/foo/../..").array == "/");
2221 // The ultimate path
2222 assert(asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz");
2223 static assert(asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz");
2225 else version (Windows)
2227 // Trivial
2228 assert(asNormalizedPath("").empty);
2229 assert(asNormalizedPath(`foo\bar`).array == `foo\bar`);
2230 assert(asNormalizedPath("foo/bar").array == `foo\bar`);
2232 // Correct handling of absolute paths
2233 assert(asNormalizedPath("/").array == `\`);
2234 assert(asNormalizedPath(`\`).array == `\`);
2235 assert(asNormalizedPath(`\\\`).array == `\`);
2236 assert(asNormalizedPath(`\\\\`).array == `\`);
2237 assert(asNormalizedPath(`\foo\bar`).array == `\foo\bar`);
2238 assert(asNormalizedPath(`\\foo`).array == `\\foo`);
2239 assert(asNormalizedPath(`\\foo\\`).array == `\\foo`);
2240 assert(asNormalizedPath(`\\foo/bar`).array == `\\foo\bar`);
2241 assert(asNormalizedPath(`\\\foo\bar`).array == `\foo\bar`);
2242 assert(asNormalizedPath(`\\\\foo\bar`).array == `\foo\bar`);
2243 assert(asNormalizedPath(`c:\`).array == `c:\`);
2244 assert(asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`);
2245 assert(asNormalizedPath(`c:\\foo\bar`).array == `c:\foo\bar`);
2247 // Correct handling of single-dot symbol (current directory)
2248 assert(asNormalizedPath(`\./foo`).array == `\foo`);
2249 assert(asNormalizedPath(`\foo/.\bar`).array == `\foo\bar`);
2251 assert(asNormalizedPath(`.\foo`).array == `foo`);
2252 assert(asNormalizedPath(`./.\foo`).array == `foo`);
2253 assert(asNormalizedPath(`foo\.\./bar`).array == `foo\bar`);
2255 // Correct handling of double-dot symbol (previous directory)
2256 assert(asNormalizedPath(`\foo\..\bar`).array == `\bar`);
2257 assert(asNormalizedPath(`\foo\../..\bar`).array == `\bar`);
2258 assert(asNormalizedPath(`\..\foo`).array == `\foo`);
2259 assert(asNormalizedPath(`\..\..\foo`).array == `\foo`);
2260 assert(asNormalizedPath(`\foo\..`).array == `\`);
2261 assert(asNormalizedPath(`\foo\../..`).array == `\`);
2263 assert(asNormalizedPath(`foo\..\bar`).array == `bar`);
2264 assert(asNormalizedPath(`foo\..\../bar`).array == `..\bar`);
2266 assert(asNormalizedPath(`..\foo`).array == `..\foo`);
2267 assert(asNormalizedPath(`..\..\foo`).array == `..\..\foo`);
2268 assert(asNormalizedPath(`..\foo\..\bar`).array == `..\bar`);
2269 assert(asNormalizedPath(`..\.\..\foo`).array == `..\..\foo`);
2270 assert(asNormalizedPath(`foo\bar\..`).array == `foo`);
2271 assert(asNormalizedPath(`\foo\..\..`).array == `\`);
2272 assert(asNormalizedPath(`c:\foo\..\..`).array == `c:\`);
2274 // Correct handling of non-root path with drive specifier
2275 assert(asNormalizedPath(`c:foo`).array == `c:foo`);
2276 assert(asNormalizedPath(`c:..\foo\.\..\bar`).array == `c:..\bar`);
2278 // The ultimate path
2279 assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`);
2280 static assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`);
2282 else static assert(false);
2285 /** Slice up a path into its elements.
2287 Params:
2288 path = string or slicable random access range
2290 Returns:
2291 bidirectional range of slices of `path`
2293 auto pathSplitter(R)(R path)
2294 if ((isRandomAccessRange!R && hasSlicing!R ||
2295 isNarrowString!R) &&
2296 !isConvertibleToString!R)
2298 static struct PathSplitter
2300 @property bool empty() const { return pe == 0; }
2302 @property R front()
2304 assert(!empty);
2305 return _path[fs .. fe];
2308 void popFront()
2310 assert(!empty);
2311 if (ps == pe)
2313 if (fs == bs && fe == be)
2315 pe = 0;
2317 else
2319 fs = bs;
2320 fe = be;
2323 else
2325 fs = ps;
2326 fe = fs;
2327 while (fe < pe && !isDirSeparator(_path[fe]))
2328 ++fe;
2329 ps = ltrim(fe, pe);
2333 @property R back()
2335 assert(!empty);
2336 return _path[bs .. be];
2339 void popBack()
2341 assert(!empty);
2342 if (ps == pe)
2344 if (fs == bs && fe == be)
2346 pe = 0;
2348 else
2350 bs = fs;
2351 be = fe;
2354 else
2356 bs = pe;
2357 be = bs;
2358 while (bs > ps && !isDirSeparator(_path[bs - 1]))
2359 --bs;
2360 pe = rtrim(ps, bs);
2363 @property auto save() { return this; }
2366 private:
2367 R _path;
2368 size_t ps, pe;
2369 size_t fs, fe;
2370 size_t bs, be;
2372 this(R p)
2374 if (p.empty)
2376 pe = 0;
2377 return;
2379 _path = p;
2381 ps = 0;
2382 pe = _path.length;
2384 // If path is rooted, first element is special
2385 version (Windows)
2387 if (isUNC(_path))
2389 auto i = uncRootLength(_path);
2390 fs = 0;
2391 fe = i;
2392 ps = ltrim(fe, pe);
2394 else if (isDriveRoot(_path))
2396 fs = 0;
2397 fe = 3;
2398 ps = ltrim(fe, pe);
2400 else if (_path.length >= 1 && isDirSeparator(_path[0]))
2402 fs = 0;
2403 fe = 1;
2404 ps = ltrim(fe, pe);
2406 else
2408 assert(!isRooted(_path));
2409 popFront();
2412 else version (Posix)
2414 if (_path.length >= 1 && isDirSeparator(_path[0]))
2416 fs = 0;
2417 fe = 1;
2418 ps = ltrim(fe, pe);
2420 else
2422 popFront();
2425 else static assert(0);
2427 if (ps == pe)
2429 bs = fs;
2430 be = fe;
2432 else
2434 pe = rtrim(ps, pe);
2435 popBack();
2439 size_t ltrim(size_t s, size_t e)
2441 while (s < e && isDirSeparator(_path[s]))
2442 ++s;
2443 return s;
2446 size_t rtrim(size_t s, size_t e)
2448 while (s < e && isDirSeparator(_path[e - 1]))
2449 --e;
2450 return e;
2454 return PathSplitter(path);
2458 @safe unittest
2460 import std.algorithm.comparison : equal;
2461 import std.conv : to;
2463 assert(equal(pathSplitter("/"), ["/"]));
2464 assert(equal(pathSplitter("/foo/bar"), ["/", "foo", "bar"]));
2465 assert(equal(pathSplitter("foo/../bar//./"), ["foo", "..", "bar", "."]));
2467 version (Posix)
2469 assert(equal(pathSplitter("//foo/bar"), ["/", "foo", "bar"]));
2472 version (Windows)
2474 assert(equal(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
2475 assert(equal(pathSplitter("c:"), ["c:"]));
2476 assert(equal(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
2477 assert(equal(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
2481 auto pathSplitter(R)(auto ref R path)
2482 if (isConvertibleToString!R)
2484 return pathSplitter!(StringTypeOf!R)(path);
2487 @safe unittest
2489 import std.algorithm.comparison : equal;
2490 assert(testAliasedString!pathSplitter("/"));
2493 @safe unittest
2495 // equal2 verifies that the range is the same both ways, i.e.
2496 // through front/popFront and back/popBack.
2497 import std.algorithm;
2498 import std.range;
2499 bool equal2(R1, R2)(R1 r1, R2 r2)
2501 static assert(isBidirectionalRange!R1);
2502 return equal(r1, r2) && equal(retro(r1), retro(r2));
2505 assert(pathSplitter("").empty);
2507 // Root directories
2508 assert(equal2(pathSplitter("/"), ["/"]));
2509 assert(equal2(pathSplitter("//"), ["/"]));
2510 assert(equal2(pathSplitter("///"w), ["/"w]));
2512 // Absolute paths
2513 assert(equal2(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"]));
2515 // General
2516 assert(equal2(pathSplitter("foo/bar"d.dup), ["foo"d, "bar"d]));
2517 assert(equal2(pathSplitter("foo//bar"), ["foo", "bar"]));
2518 assert(equal2(pathSplitter("foo/bar//"w), ["foo"w, "bar"w]));
2519 assert(equal2(pathSplitter("foo/../bar//./"d), ["foo"d, ".."d, "bar"d, "."d]));
2521 // save()
2522 auto ps1 = pathSplitter("foo/bar/baz");
2523 auto ps2 = ps1.save;
2524 ps1.popFront();
2525 assert(equal2(ps1, ["bar", "baz"]));
2526 assert(equal2(ps2, ["foo", "bar", "baz"]));
2528 // Platform specific
2529 version (Posix)
2531 assert(equal2(pathSplitter("//foo/bar"w.dup), ["/"w, "foo"w, "bar"w]));
2533 version (Windows)
2535 assert(equal2(pathSplitter(`\`), [`\`]));
2536 assert(equal2(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
2537 assert(equal2(pathSplitter("c:"), ["c:"]));
2538 assert(equal2(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
2539 assert(equal2(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
2540 assert(equal2(pathSplitter(`\\foo\bar`), [`\\foo\bar`]));
2541 assert(equal2(pathSplitter(`\\foo\bar\\`), [`\\foo\bar`]));
2542 assert(equal2(pathSplitter(`\\foo\bar\baz`), [`\\foo\bar`, "baz"]));
2545 import std.exception;
2546 assertCTFEable!(
2548 assert(equal(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"]));
2551 static assert(is(typeof(pathSplitter!(const(char)[])(null).front) == const(char)[]));
2553 import std.utf : byDchar;
2554 assert(equal2(pathSplitter("foo/bar"d.byDchar), ["foo"d, "bar"d]));
2560 /** Determines whether a path starts at a root directory.
2562 Params:
2563 path = A path name.
2564 Returns:
2565 Whether a path starts at a root directory.
2567 On POSIX, this function returns true if and only if the path starts
2568 with a slash (/).
2570 On Windows, this function returns true if the path starts at
2571 the root directory of the current drive, of some other drive,
2572 or of a network drive.
2574 bool isRooted(R)(R path)
2575 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2576 is(StringTypeOf!R))
2578 if (path.length >= 1 && isDirSeparator(path[0])) return true;
2579 version (Posix) return false;
2580 else version (Windows) return isAbsolute!(BaseOf!R)(path);
2584 @safe unittest
2586 version (Posix)
2588 assert( isRooted("/"));
2589 assert( isRooted("/foo"));
2590 assert(!isRooted("foo"));
2591 assert(!isRooted("../foo"));
2594 version (Windows)
2596 assert( isRooted(`\`));
2597 assert( isRooted(`\foo`));
2598 assert( isRooted(`d:\foo`));
2599 assert( isRooted(`\\foo\bar`));
2600 assert(!isRooted("foo"));
2601 assert(!isRooted("d:foo"));
2605 @safe unittest
2607 assert(isRooted("/"));
2608 assert(isRooted("/foo"));
2609 assert(!isRooted("foo"));
2610 assert(!isRooted("../foo"));
2612 version (Windows)
2614 assert(isRooted(`\`));
2615 assert(isRooted(`\foo`));
2616 assert(isRooted(`d:\foo`));
2617 assert(isRooted(`\\foo\bar`));
2618 assert(!isRooted("foo"));
2619 assert(!isRooted("d:foo"));
2622 static assert(isRooted("/foo"));
2623 static assert(!isRooted("foo"));
2625 static struct DirEntry { string s; alias s this; }
2626 assert(!isRooted(DirEntry("foo")));
2629 /** Determines whether a path is absolute or not.
2631 Params: path = A path name.
2633 Returns: Whether a path is absolute or not.
2635 Example:
2636 On POSIX, an absolute path starts at the root directory.
2637 (In fact, `_isAbsolute` is just an alias for $(LREF isRooted).)
2639 version (Posix)
2641 assert(isAbsolute("/"));
2642 assert(isAbsolute("/foo"));
2643 assert(!isAbsolute("foo"));
2644 assert(!isAbsolute("../foo"));
2648 On Windows, an absolute path starts at the root directory of
2649 a specific drive. Hence, it must start with $(D `d:\`) or $(D `d:/`),
2650 where `d` is the drive letter. Alternatively, it may be a
2651 network path, i.e. a path starting with a double (back)slash.
2653 version (Windows)
2655 assert(isAbsolute(`d:\`));
2656 assert(isAbsolute(`d:\foo`));
2657 assert(isAbsolute(`\\foo\bar`));
2658 assert(!isAbsolute(`\`));
2659 assert(!isAbsolute(`\foo`));
2660 assert(!isAbsolute("d:foo"));
2664 version (StdDdoc)
2666 bool isAbsolute(R)(R path) pure nothrow @safe
2667 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2668 is(StringTypeOf!R));
2670 else version (Windows)
2672 bool isAbsolute(R)(R path)
2673 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2674 is(StringTypeOf!R))
2676 return isDriveRoot!(BaseOf!R)(path) || isUNC!(BaseOf!R)(path);
2679 else version (Posix)
2681 alias isAbsolute = isRooted;
2685 @safe unittest
2687 assert(!isAbsolute("foo"));
2688 assert(!isAbsolute("../foo"w));
2689 static assert(!isAbsolute("foo"));
2691 version (Posix)
2693 assert(isAbsolute("/"d));
2694 assert(isAbsolute("/foo".dup));
2695 static assert(isAbsolute("/foo"));
2698 version (Windows)
2700 assert(isAbsolute("d:\\"w));
2701 assert(isAbsolute("d:\\foo"d));
2702 assert(isAbsolute("\\\\foo\\bar"));
2703 assert(!isAbsolute("\\"w.dup));
2704 assert(!isAbsolute("\\foo"d.dup));
2705 assert(!isAbsolute("d:"));
2706 assert(!isAbsolute("d:foo"));
2707 static assert(isAbsolute(`d:\foo`));
2711 auto r = MockRange!(immutable(char))(`../foo`);
2712 assert(!r.isAbsolute());
2715 static struct DirEntry { string s; alias s this; }
2716 assert(!isAbsolute(DirEntry("foo")));
2722 /** Transforms `path` into an absolute path.
2724 The following algorithm is used:
2725 $(OL
2726 $(LI If `path` is empty, return `null`.)
2727 $(LI If `path` is already absolute, return it.)
2728 $(LI Otherwise, append `path` to `base` and return
2729 the result. If `base` is not specified, the current
2730 working directory is used.)
2732 The function allocates memory if and only if it gets to the third stage
2733 of this algorithm.
2735 Params:
2736 path = the relative path to transform
2737 base = the base directory of the relative path
2739 Returns:
2740 string of transformed path
2742 Throws:
2743 `Exception` if the specified _base directory is not absolute.
2745 See_Also:
2746 $(LREF asAbsolutePath) which does not allocate
2748 string absolutePath(return scope const string path, lazy string base = getcwd())
2749 @safe pure
2751 import std.array : array;
2752 if (path.empty) return null;
2753 if (isAbsolute(path)) return path;
2754 auto baseVar = base;
2755 if (!isAbsolute(baseVar)) throw new Exception("Base directory must be absolute");
2756 return chainPath(baseVar, path).array;
2760 @safe unittest
2762 version (Posix)
2764 assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file");
2765 assert(absolutePath("../file", "/foo/bar") == "/foo/bar/../file");
2766 assert(absolutePath("/some/file", "/foo/bar") == "/some/file");
2769 version (Windows)
2771 assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2772 assert(absolutePath(`..\file`, `c:\foo\bar`) == `c:\foo\bar\..\file`);
2773 assert(absolutePath(`c:\some\file`, `c:\foo\bar`) == `c:\some\file`);
2774 assert(absolutePath(`\`, `c:\`) == `c:\`);
2775 assert(absolutePath(`\some\file`, `c:\foo\bar`) == `c:\some\file`);
2779 @safe unittest
2781 version (Posix)
2783 static assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file");
2786 version (Windows)
2788 static assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2791 import std.exception;
2792 assertThrown(absolutePath("bar", "foo"));
2795 // Ensure that we can call absolute path with scope paramaters
2796 @safe unittest
2798 string testAbsPath(scope const string path, scope const string base) {
2799 return absolutePath(path, base);
2802 version (Posix)
2803 assert(testAbsPath("some/file", "/foo/bar") == "/foo/bar/some/file");
2804 version (Windows)
2805 assert(testAbsPath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2808 /** Transforms `path` into an absolute path.
2810 The following algorithm is used:
2811 $(OL
2812 $(LI If `path` is empty, return `null`.)
2813 $(LI If `path` is already absolute, return it.)
2814 $(LI Otherwise, append `path` to the current working directory,
2815 which allocates memory.)
2818 Params:
2819 path = the relative path to transform
2821 Returns:
2822 the transformed path as a lazy range
2824 See_Also:
2825 $(LREF absolutePath) which returns an allocated string
2827 auto asAbsolutePath(R)(R path)
2828 if ((isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2829 isNarrowString!R) &&
2830 !isConvertibleToString!R)
2832 import std.file : getcwd;
2833 string base = null;
2834 if (!path.empty && !isAbsolute(path))
2835 base = getcwd();
2836 return chainPath(base, path);
2840 @system unittest
2842 import std.array;
2843 assert(asAbsolutePath(cast(string) null).array == "");
2844 version (Posix)
2846 assert(asAbsolutePath("/foo").array == "/foo");
2848 version (Windows)
2850 assert(asAbsolutePath("c:/foo").array == "c:/foo");
2852 asAbsolutePath("foo");
2855 auto asAbsolutePath(R)(auto ref R path)
2856 if (isConvertibleToString!R)
2858 return asAbsolutePath!(StringTypeOf!R)(path);
2861 @system unittest
2863 assert(testAliasedString!asAbsolutePath(null));
2866 /** Translates `path` into a relative path.
2868 The returned path is relative to `base`, which is by default
2869 taken to be the current working directory. If specified,
2870 `base` must be an absolute path, and it is always assumed
2871 to refer to a directory. If `path` and `base` refer to
2872 the same directory, the function returns $(D `.`).
2874 The following algorithm is used:
2875 $(OL
2876 $(LI If `path` is a relative directory, return it unaltered.)
2877 $(LI Find a common root between `path` and `base`.
2878 If there is no common root, return `path` unaltered.)
2879 $(LI Prepare a string with as many $(D `../`) or $(D `..\`) as
2880 necessary to reach the common root from base path.)
2881 $(LI Append the remaining segments of `path` to the string
2882 and return.)
2885 In the second step, path components are compared using `filenameCmp!cs`,
2886 where `cs` is an optional template parameter determining whether
2887 the comparison is case sensitive or not. See the
2888 $(LREF filenameCmp) documentation for details.
2890 This function allocates memory.
2892 Params:
2893 cs = Whether matching path name components against the base path should
2894 be case-sensitive or not.
2895 path = A path name.
2896 base = The base path to construct the relative path from.
2898 Returns: The relative path.
2900 See_Also:
2901 $(LREF asRelativePath) which does not allocate memory
2903 Throws:
2904 `Exception` if the specified _base directory is not absolute.
2906 string relativePath(CaseSensitive cs = CaseSensitive.osDefault)
2907 (string path, lazy string base = getcwd())
2909 if (!isAbsolute(path))
2910 return path;
2911 auto baseVar = base;
2912 if (!isAbsolute(baseVar))
2913 throw new Exception("Base directory must be absolute");
2915 import std.conv : to;
2916 return asRelativePath!cs(path, baseVar).to!string;
2920 @safe unittest
2922 assert(relativePath("foo") == "foo");
2924 version (Posix)
2926 assert(relativePath("foo", "/bar") == "foo");
2927 assert(relativePath("/foo/bar", "/foo/bar") == ".");
2928 assert(relativePath("/foo/bar", "/foo/baz") == "../bar");
2929 assert(relativePath("/foo/bar/baz", "/foo/woo/wee") == "../../bar/baz");
2930 assert(relativePath("/foo/bar/baz", "/foo/bar") == "baz");
2932 version (Windows)
2934 assert(relativePath("foo", `c:\bar`) == "foo");
2935 assert(relativePath(`c:\foo\bar`, `c:\foo\bar`) == ".");
2936 assert(relativePath(`c:\foo\bar`, `c:\foo\baz`) == `..\bar`);
2937 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`) == `..\..\bar\baz`);
2938 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
2939 assert(relativePath(`c:\foo\bar`, `d:\foo`) == `c:\foo\bar`);
2943 @safe unittest
2945 import std.exception;
2946 assert(relativePath("foo") == "foo");
2947 version (Posix)
2949 relativePath("/foo");
2950 assert(relativePath("/foo/bar", "/foo/baz") == "../bar");
2951 assertThrown(relativePath("/foo", "bar"));
2953 else version (Windows)
2955 relativePath(`\foo`);
2956 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
2957 assertThrown(relativePath(`c:\foo`, "bar"));
2959 else static assert(0);
2962 /** Transforms `path` into a path relative to `base`.
2964 The returned path is relative to `base`, which is usually
2965 the current working directory.
2966 `base` must be an absolute path, and it is always assumed
2967 to refer to a directory. If `path` and `base` refer to
2968 the same directory, the function returns `'.'`.
2970 The following algorithm is used:
2971 $(OL
2972 $(LI If `path` is a relative directory, return it unaltered.)
2973 $(LI Find a common root between `path` and `base`.
2974 If there is no common root, return `path` unaltered.)
2975 $(LI Prepare a string with as many `../` or `..\` as
2976 necessary to reach the common root from base path.)
2977 $(LI Append the remaining segments of `path` to the string
2978 and return.)
2981 In the second step, path components are compared using `filenameCmp!cs`,
2982 where `cs` is an optional template parameter determining whether
2983 the comparison is case sensitive or not. See the
2984 $(LREF filenameCmp) documentation for details.
2986 Params:
2987 path = path to transform
2988 base = absolute path
2989 cs = whether filespec comparisons are sensitive or not; defaults to
2990 `CaseSensitive.osDefault`
2992 Returns:
2993 a random access range of the transformed path
2995 See_Also:
2996 $(LREF relativePath)
2998 auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2)
2999 (R1 path, R2 base)
3000 if ((isNarrowString!R1 ||
3001 (isRandomAccessRange!R1 && hasSlicing!R1 && isSomeChar!(ElementType!R1)) &&
3002 !isConvertibleToString!R1) &&
3003 (isNarrowString!R2 ||
3004 (isRandomAccessRange!R2 && hasSlicing!R2 && isSomeChar!(ElementType!R2)) &&
3005 !isConvertibleToString!R2))
3007 bool choosePath = !isAbsolute(path);
3009 // Find common root with current working directory
3011 auto basePS = pathSplitter(base);
3012 auto pathPS = pathSplitter(path);
3013 choosePath |= filenameCmp!cs(basePS.front, pathPS.front) != 0;
3015 basePS.popFront();
3016 pathPS.popFront();
3018 import std.algorithm.comparison : mismatch;
3019 import std.algorithm.iteration : joiner;
3020 import std.array : array;
3021 import std.range.primitives : walkLength;
3022 import std.range : repeat, chain, choose;
3023 import std.utf : byCodeUnit, byChar;
3025 // Remove matching prefix from basePS and pathPS
3026 auto tup = mismatch!((a, b) => filenameCmp!cs(a, b) == 0)(basePS, pathPS);
3027 basePS = tup[0];
3028 pathPS = tup[1];
3030 string sep;
3031 if (basePS.empty && pathPS.empty)
3032 sep = "."; // if base == path, this is the return
3033 else if (!basePS.empty && !pathPS.empty)
3034 sep = dirSeparator;
3036 // Append as many "../" as necessary to reach common base from path
3037 auto r1 = ".."
3038 .byChar
3039 .repeat(basePS.walkLength())
3040 .joiner(dirSeparator.byChar);
3042 auto r2 = pathPS
3043 .joiner(dirSeparator.byChar)
3044 .byChar;
3046 // Return (r1 ~ sep ~ r2)
3047 return choose(choosePath, path.byCodeUnit, chain(r1, sep.byChar, r2));
3051 @safe unittest
3053 import std.array;
3054 version (Posix)
3056 assert(asRelativePath("foo", "/bar").array == "foo");
3057 assert(asRelativePath("/foo/bar", "/foo/bar").array == ".");
3058 assert(asRelativePath("/foo/bar", "/foo/baz").array == "../bar");
3059 assert(asRelativePath("/foo/bar/baz", "/foo/woo/wee").array == "../../bar/baz");
3060 assert(asRelativePath("/foo/bar/baz", "/foo/bar").array == "baz");
3062 else version (Windows)
3064 assert(asRelativePath("foo", `c:\bar`).array == "foo");
3065 assert(asRelativePath(`c:\foo\bar`, `c:\foo\bar`).array == ".");
3066 assert(asRelativePath(`c:\foo\bar`, `c:\foo\baz`).array == `..\bar`);
3067 assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`);
3068 assert(asRelativePath(`c:/foo/bar/baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`);
3069 assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\bar`).array == "baz");
3070 assert(asRelativePath(`c:\foo\bar`, `d:\foo`).array == `c:\foo\bar`);
3071 assert(asRelativePath(`\\foo\bar`, `c:\foo`).array == `\\foo\bar`);
3073 else
3074 static assert(0);
3077 @safe unittest
3079 version (Posix)
3081 assert(isBidirectionalRange!(typeof(asRelativePath("foo/bar/baz", "/foo/woo/wee"))));
3084 version (Windows)
3086 assert(isBidirectionalRange!(typeof(asRelativePath(`c:\foo\bar`, `c:\foo\baz`))));
3090 auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2)
3091 (auto ref R1 path, auto ref R2 base)
3092 if (isConvertibleToString!R1 || isConvertibleToString!R2)
3094 import std.meta : staticMap;
3095 alias Types = staticMap!(convertToString, R1, R2);
3096 return asRelativePath!(cs, Types)(path, base);
3099 @safe unittest
3101 import std.array;
3102 version (Posix)
3103 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("/bar")).array == "foo");
3104 else version (Windows)
3105 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString(`c:\bar`)).array == "foo");
3106 assert(asRelativePath(TestAliasedString("foo"), "bar").array == "foo");
3107 assert(asRelativePath("foo", TestAliasedString("bar")).array == "foo");
3108 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("bar")).array == "foo");
3109 import std.utf : byDchar;
3110 assert(asRelativePath("foo"d.byDchar, TestAliasedString("bar")).array == "foo");
3113 @safe unittest
3115 import std.array, std.utf : bCU=byCodeUnit;
3116 version (Posix)
3118 assert(asRelativePath("/foo/bar/baz".bCU, "/foo/bar".bCU).array == "baz");
3119 assert(asRelativePath("/foo/bar/baz"w.bCU, "/foo/bar"w.bCU).array == "baz"w);
3120 assert(asRelativePath("/foo/bar/baz"d.bCU, "/foo/bar"d.bCU).array == "baz"d);
3122 else version (Windows)
3124 assert(asRelativePath(`\\foo\bar`.bCU, `c:\foo`.bCU).array == `\\foo\bar`);
3125 assert(asRelativePath(`\\foo\bar`w.bCU, `c:\foo`w.bCU).array == `\\foo\bar`w);
3126 assert(asRelativePath(`\\foo\bar`d.bCU, `c:\foo`d.bCU).array == `\\foo\bar`d);
3130 /** Compares filename characters.
3132 This function can perform a case-sensitive or a case-insensitive
3133 comparison. This is controlled through the `cs` template parameter
3134 which, if not specified, is given by $(LREF CaseSensitive)`.osDefault`.
3136 On Windows, the backslash and slash characters ($(D `\`) and $(D `/`))
3137 are considered equal.
3139 Params:
3140 cs = Case-sensitivity of the comparison.
3141 a = A filename character.
3142 b = A filename character.
3144 Returns:
3145 $(D < 0) if $(D a < b),
3146 `0` if $(D a == b), and
3147 $(D > 0) if $(D a > b).
3149 int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b)
3150 @safe pure nothrow
3152 if (isDirSeparator(a) && isDirSeparator(b)) return 0;
3153 static if (!cs)
3155 import std.uni : toLower;
3156 a = toLower(a);
3157 b = toLower(b);
3159 return cast(int)(a - b);
3163 @safe unittest
3165 assert(filenameCharCmp('a', 'a') == 0);
3166 assert(filenameCharCmp('a', 'b') < 0);
3167 assert(filenameCharCmp('b', 'a') > 0);
3169 version (linux)
3171 // Same as calling filenameCharCmp!(CaseSensitive.yes)(a, b)
3172 assert(filenameCharCmp('A', 'a') < 0);
3173 assert(filenameCharCmp('a', 'A') > 0);
3175 version (Windows)
3177 // Same as calling filenameCharCmp!(CaseSensitive.no)(a, b)
3178 assert(filenameCharCmp('a', 'A') == 0);
3179 assert(filenameCharCmp('a', 'B') < 0);
3180 assert(filenameCharCmp('A', 'b') < 0);
3184 @safe unittest
3186 assert(filenameCharCmp!(CaseSensitive.yes)('A', 'a') < 0);
3187 assert(filenameCharCmp!(CaseSensitive.yes)('a', 'A') > 0);
3189 assert(filenameCharCmp!(CaseSensitive.no)('a', 'a') == 0);
3190 assert(filenameCharCmp!(CaseSensitive.no)('a', 'b') < 0);
3191 assert(filenameCharCmp!(CaseSensitive.no)('b', 'a') > 0);
3192 assert(filenameCharCmp!(CaseSensitive.no)('A', 'a') == 0);
3193 assert(filenameCharCmp!(CaseSensitive.no)('a', 'A') == 0);
3194 assert(filenameCharCmp!(CaseSensitive.no)('a', 'B') < 0);
3195 assert(filenameCharCmp!(CaseSensitive.no)('B', 'a') > 0);
3196 assert(filenameCharCmp!(CaseSensitive.no)('A', 'b') < 0);
3197 assert(filenameCharCmp!(CaseSensitive.no)('b', 'A') > 0);
3199 version (Posix) assert(filenameCharCmp('\\', '/') != 0);
3200 version (Windows) assert(filenameCharCmp('\\', '/') == 0);
3204 /** Compares file names and returns
3206 Individual characters are compared using `filenameCharCmp!cs`,
3207 where `cs` is an optional template parameter determining whether
3208 the comparison is case sensitive or not.
3210 Treatment of invalid UTF encodings is implementation defined.
3212 Params:
3213 cs = case sensitivity
3214 filename1 = range for first file name
3215 filename2 = range for second file name
3217 Returns:
3218 $(D < 0) if $(D filename1 < filename2),
3219 `0` if $(D filename1 == filename2) and
3220 $(D > 0) if $(D filename1 > filename2).
3222 See_Also:
3223 $(LREF filenameCharCmp)
3225 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2)
3226 (Range1 filename1, Range2 filename2)
3227 if (isSomeFiniteCharInputRange!Range1 && !isConvertibleToString!Range1 &&
3228 isSomeFiniteCharInputRange!Range2 && !isConvertibleToString!Range2)
3230 alias C1 = Unqual!(ElementEncodingType!Range1);
3231 alias C2 = Unqual!(ElementEncodingType!Range2);
3233 static if (!cs && (C1.sizeof < 4 || C2.sizeof < 4) ||
3234 C1.sizeof != C2.sizeof)
3236 // Case insensitive - decode so case is checkable
3237 // Different char sizes - decode to bring to common type
3238 import std.utf : byDchar;
3239 return filenameCmp!cs(filename1.byDchar, filename2.byDchar);
3241 else static if (isSomeString!Range1 && C1.sizeof < 4 ||
3242 isSomeString!Range2 && C2.sizeof < 4)
3244 // Avoid autodecoding
3245 import std.utf : byCodeUnit;
3246 return filenameCmp!cs(filename1.byCodeUnit, filename2.byCodeUnit);
3248 else
3250 for (;;)
3252 if (filename1.empty) return -(cast(int) !filename2.empty);
3253 if (filename2.empty) return 1;
3254 const c = filenameCharCmp!cs(filename1.front, filename2.front);
3255 if (c != 0) return c;
3256 filename1.popFront();
3257 filename2.popFront();
3263 @safe unittest
3265 assert(filenameCmp("abc", "abc") == 0);
3266 assert(filenameCmp("abc", "abd") < 0);
3267 assert(filenameCmp("abc", "abb") > 0);
3268 assert(filenameCmp("abc", "abcd") < 0);
3269 assert(filenameCmp("abcd", "abc") > 0);
3271 version (linux)
3273 // Same as calling filenameCmp!(CaseSensitive.yes)(filename1, filename2)
3274 assert(filenameCmp("Abc", "abc") < 0);
3275 assert(filenameCmp("abc", "Abc") > 0);
3277 version (Windows)
3279 // Same as calling filenameCmp!(CaseSensitive.no)(filename1, filename2)
3280 assert(filenameCmp("Abc", "abc") == 0);
3281 assert(filenameCmp("abc", "Abc") == 0);
3282 assert(filenameCmp("Abc", "abD") < 0);
3283 assert(filenameCmp("abc", "AbB") > 0);
3287 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2)
3288 (auto ref Range1 filename1, auto ref Range2 filename2)
3289 if (isConvertibleToString!Range1 || isConvertibleToString!Range2)
3291 import std.meta : staticMap;
3292 alias Types = staticMap!(convertToString, Range1, Range2);
3293 return filenameCmp!(cs, Types)(filename1, filename2);
3296 @safe unittest
3298 assert(filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), "abc") < 0);
3299 assert(filenameCmp!(CaseSensitive.yes)("Abc", TestAliasedString("abc")) < 0);
3300 assert(filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), TestAliasedString("abc")) < 0);
3303 @safe unittest
3305 assert(filenameCmp!(CaseSensitive.yes)("Abc", "abc") < 0);
3306 assert(filenameCmp!(CaseSensitive.yes)("abc", "Abc") > 0);
3308 assert(filenameCmp!(CaseSensitive.no)("abc", "abc") == 0);
3309 assert(filenameCmp!(CaseSensitive.no)("abc", "abd") < 0);
3310 assert(filenameCmp!(CaseSensitive.no)("abc", "abb") > 0);
3311 assert(filenameCmp!(CaseSensitive.no)("abc", "abcd") < 0);
3312 assert(filenameCmp!(CaseSensitive.no)("abcd", "abc") > 0);
3313 assert(filenameCmp!(CaseSensitive.no)("Abc", "abc") == 0);
3314 assert(filenameCmp!(CaseSensitive.no)("abc", "Abc") == 0);
3315 assert(filenameCmp!(CaseSensitive.no)("Abc", "abD") < 0);
3316 assert(filenameCmp!(CaseSensitive.no)("abc", "AbB") > 0);
3318 version (Posix) assert(filenameCmp(`abc\def`, `abc/def`) != 0);
3319 version (Windows) assert(filenameCmp(`abc\def`, `abc/def`) == 0);
3322 /** Matches a pattern against a path.
3324 Some characters of pattern have a special meaning (they are
3325 $(I meta-characters)) and can't be escaped. These are:
3327 $(BOOKTABLE,
3328 $(TR $(TD `*`)
3329 $(TD Matches 0 or more instances of any character.))
3330 $(TR $(TD `?`)
3331 $(TD Matches exactly one instance of any character.))
3332 $(TR $(TD `[`$(I chars)`]`)
3333 $(TD Matches one instance of any character that appears
3334 between the brackets.))
3335 $(TR $(TD `[!`$(I chars)`]`)
3336 $(TD Matches one instance of any character that does not
3337 appear between the brackets after the exclamation mark.))
3338 $(TR $(TD `{`$(I string1)`,`$(I string2)`,`&hellip;`}`)
3339 $(TD Matches either of the specified strings.))
3342 Individual characters are compared using `filenameCharCmp!cs`,
3343 where `cs` is an optional template parameter determining whether
3344 the comparison is case sensitive or not. See the
3345 $(LREF filenameCharCmp) documentation for details.
3347 Note that directory
3348 separators and dots don't stop a meta-character from matching
3349 further portions of the path.
3351 Params:
3352 cs = Whether the matching should be case-sensitive
3353 path = The path to be matched against
3354 pattern = The glob pattern
3356 Returns:
3357 `true` if pattern matches path, `false` otherwise.
3359 See_also:
3360 $(LINK2 http://en.wikipedia.org/wiki/Glob_%28programming%29,Wikipedia: _glob (programming))
3362 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range)
3363 (Range path, const(C)[] pattern)
3364 @safe pure nothrow
3365 if (isForwardRange!Range && !isInfinite!Range &&
3366 isSomeChar!(ElementEncodingType!Range) && !isConvertibleToString!Range &&
3367 isSomeChar!C && is(immutable C == immutable ElementEncodingType!Range))
3370 // Verify that pattern[] is valid
3371 import std.algorithm.searching : balancedParens;
3372 import std.utf : byUTF;
3374 assert(balancedParens(pattern.byUTF!C, '[', ']', 0));
3375 assert(balancedParens(pattern.byUTF!C, '{', '}', 0));
3379 alias RC = Unqual!(ElementEncodingType!Range);
3381 static if (RC.sizeof == 1 && isSomeString!Range)
3383 import std.utf : byChar;
3384 return globMatch!cs(path.byChar, pattern);
3386 else static if (RC.sizeof == 2 && isSomeString!Range)
3388 import std.utf : byWchar;
3389 return globMatch!cs(path.byWchar, pattern);
3391 else
3393 C[] pattmp;
3394 foreach (ref pi; 0 .. pattern.length)
3396 const pc = pattern[pi];
3397 switch (pc)
3399 case '*':
3400 if (pi + 1 == pattern.length)
3401 return true;
3402 for (; !path.empty; path.popFront())
3404 auto p = path.save;
3405 if (globMatch!(cs, C)(p,
3406 pattern[pi + 1 .. pattern.length]))
3407 return true;
3409 return false;
3411 case '?':
3412 if (path.empty)
3413 return false;
3414 path.popFront();
3415 break;
3417 case '[':
3418 if (path.empty)
3419 return false;
3420 auto nc = path.front;
3421 path.popFront();
3422 auto not = false;
3423 ++pi;
3424 if (pattern[pi] == '!')
3426 not = true;
3427 ++pi;
3429 auto anymatch = false;
3430 while (1)
3432 const pc2 = pattern[pi];
3433 if (pc2 == ']')
3434 break;
3435 if (!anymatch && (filenameCharCmp!cs(nc, pc2) == 0))
3436 anymatch = true;
3437 ++pi;
3439 if (anymatch == not)
3440 return false;
3441 break;
3443 case '{':
3444 // find end of {} section
3445 auto piRemain = pi;
3446 for (; piRemain < pattern.length
3447 && pattern[piRemain] != '}'; ++piRemain)
3450 if (piRemain < pattern.length)
3451 ++piRemain;
3452 ++pi;
3454 while (pi < pattern.length)
3456 const pi0 = pi;
3457 C pc3 = pattern[pi];
3458 // find end of current alternative
3459 for (; pi < pattern.length && pc3 != '}' && pc3 != ','; ++pi)
3461 pc3 = pattern[pi];
3464 auto p = path.save;
3465 if (pi0 == pi)
3467 if (globMatch!(cs, C)(p, pattern[piRemain..$]))
3469 return true;
3471 ++pi;
3473 else
3475 /* Match for:
3476 * pattern[pi0 .. pi-1] ~ pattern[piRemain..$]
3478 if (pattmp is null)
3479 // Allocate this only once per function invocation.
3480 // Should do it with malloc/free, but that would make it impure.
3481 pattmp = new C[pattern.length];
3483 const len1 = pi - 1 - pi0;
3484 pattmp[0 .. len1] = pattern[pi0 .. pi - 1];
3486 const len2 = pattern.length - piRemain;
3487 pattmp[len1 .. len1 + len2] = pattern[piRemain .. $];
3489 if (globMatch!(cs, C)(p, pattmp[0 .. len1 + len2]))
3491 return true;
3494 if (pc3 == '}')
3496 break;
3499 return false;
3501 default:
3502 if (path.empty)
3503 return false;
3504 if (filenameCharCmp!cs(pc, path.front) != 0)
3505 return false;
3506 path.popFront();
3507 break;
3510 return path.empty;
3515 @safe unittest
3517 assert(globMatch("foo.bar", "*"));
3518 assert(globMatch("foo.bar", "*.*"));
3519 assert(globMatch(`foo/foo\bar`, "f*b*r"));
3520 assert(globMatch("foo.bar", "f???bar"));
3521 assert(globMatch("foo.bar", "[fg]???bar"));
3522 assert(globMatch("foo.bar", "[!gh]*bar"));
3523 assert(globMatch("bar.fooz", "bar.{foo,bif}z"));
3524 assert(globMatch("bar.bifz", "bar.{foo,bif}z"));
3526 version (Windows)
3528 // Same as calling globMatch!(CaseSensitive.no)(path, pattern)
3529 assert(globMatch("foo", "Foo"));
3530 assert(globMatch("Goo.bar", "[fg]???bar"));
3532 version (linux)
3534 // Same as calling globMatch!(CaseSensitive.yes)(path, pattern)
3535 assert(!globMatch("foo", "Foo"));
3536 assert(!globMatch("Goo.bar", "[fg]???bar"));
3540 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range)
3541 (auto ref Range path, const(C)[] pattern)
3542 @safe pure nothrow
3543 if (isConvertibleToString!Range)
3545 return globMatch!(cs, C, StringTypeOf!Range)(path, pattern);
3548 @safe unittest
3550 assert(testAliasedString!globMatch("foo.bar", "*"));
3553 @safe unittest
3555 assert(globMatch!(CaseSensitive.no)("foo", "Foo"));
3556 assert(!globMatch!(CaseSensitive.yes)("foo", "Foo"));
3558 assert(globMatch("foo", "*"));
3559 assert(globMatch("foo.bar"w, "*"w));
3560 assert(globMatch("foo.bar"d, "*.*"d));
3561 assert(globMatch("foo.bar", "foo*"));
3562 assert(globMatch("foo.bar"w, "f*bar"w));
3563 assert(globMatch("foo.bar"d, "f*b*r"d));
3564 assert(globMatch("foo.bar", "f???bar"));
3565 assert(globMatch("foo.bar"w, "[fg]???bar"w));
3566 assert(globMatch("foo.bar"d, "[!gh]*bar"d));
3568 assert(!globMatch("foo", "bar"));
3569 assert(!globMatch("foo"w, "*.*"w));
3570 assert(!globMatch("foo.bar"d, "f*baz"d));
3571 assert(!globMatch("foo.bar", "f*b*x"));
3572 assert(!globMatch("foo.bar", "[gh]???bar"));
3573 assert(!globMatch("foo.bar"w, "[!fg]*bar"w));
3574 assert(!globMatch("foo.bar"d, "[fg]???baz"d));
3575 // https://issues.dlang.org/show_bug.cgi?id=6634
3576 assert(!globMatch("foo.di", "*.d")); // triggered bad assertion
3578 assert(globMatch("foo.bar", "{foo,bif}.bar"));
3579 assert(globMatch("bif.bar"w, "{foo,bif}.bar"w));
3581 assert(globMatch("bar.foo"d, "bar.{foo,bif}"d));
3582 assert(globMatch("bar.bif", "bar.{foo,bif}"));
3584 assert(globMatch("bar.fooz"w, "bar.{foo,bif}z"w));
3585 assert(globMatch("bar.bifz"d, "bar.{foo,bif}z"d));
3587 assert(globMatch("bar.foo", "bar.{biz,,baz}foo"));
3588 assert(globMatch("bar.foo"w, "bar.{biz,}foo"w));
3589 assert(globMatch("bar.foo"d, "bar.{,biz}foo"d));
3590 assert(globMatch("bar.foo", "bar.{}foo"));
3592 assert(globMatch("bar.foo"w, "bar.{ar,,fo}o"w));
3593 assert(globMatch("bar.foo"d, "bar.{,ar,fo}o"d));
3594 assert(globMatch("bar.o", "bar.{,ar,fo}o"));
3596 assert(!globMatch("foo", "foo?"));
3597 assert(!globMatch("foo", "foo[]"));
3598 assert(!globMatch("foo", "foob"));
3599 assert(!globMatch("foo", "foo{b}"));
3602 static assert(globMatch("foo.bar", "[!gh]*bar"));
3608 /** Checks that the given file or directory name is valid.
3610 The maximum length of `filename` is given by the constant
3611 `core.stdc.stdio.FILENAME_MAX`. (On Windows, this number is
3612 defined as the maximum number of UTF-16 code points, and the
3613 test will therefore only yield strictly correct results when
3614 `filename` is a string of `wchar`s.)
3616 On Windows, the following criteria must be satisfied
3617 ($(LINK2 http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx,source)):
3618 $(UL
3619 $(LI `filename` must not contain any characters whose integer
3620 representation is in the range 0-31.)
3621 $(LI `filename` must not contain any of the following $(I reserved
3622 characters): `<>:"/\|?*`)
3623 $(LI `filename` may not end with a space ($(D ' ')) or a period
3624 (`'.'`).)
3627 On POSIX, `filename` may not contain a forward slash (`'/'`) or
3628 the null character (`'\0'`).
3630 Params:
3631 filename = string to check
3633 Returns:
3634 `true` if and only if `filename` is not
3635 empty, not too long, and does not contain invalid characters.
3638 bool isValidFilename(Range)(Range filename)
3639 if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) ||
3640 isNarrowString!Range) &&
3641 !isConvertibleToString!Range)
3643 import core.stdc.stdio : FILENAME_MAX;
3644 if (filename.length == 0 || filename.length >= FILENAME_MAX) return false;
3645 foreach (c; filename)
3647 version (Windows)
3649 switch (c)
3651 case 0:
3653 case 31:
3654 case '<':
3655 case '>':
3656 case ':':
3657 case '"':
3658 case '/':
3659 case '\\':
3660 case '|':
3661 case '?':
3662 case '*':
3663 return false;
3665 default:
3666 break;
3669 else version (Posix)
3671 if (c == 0 || c == '/') return false;
3673 else static assert(0);
3675 version (Windows)
3677 auto last = filename[filename.length - 1];
3678 if (last == '.' || last == ' ') return false;
3681 // All criteria passed
3682 return true;
3686 @safe pure @nogc nothrow
3687 unittest
3689 import std.utf : byCodeUnit;
3691 assert(isValidFilename("hello.exe".byCodeUnit));
3694 bool isValidFilename(Range)(auto ref Range filename)
3695 if (isConvertibleToString!Range)
3697 return isValidFilename!(StringTypeOf!Range)(filename);
3700 @safe unittest
3702 assert(testAliasedString!isValidFilename("hello.exe"));
3705 @safe pure
3706 unittest
3708 import std.conv;
3709 auto valid = ["foo"];
3710 auto invalid = ["", "foo\0bar", "foo/bar"];
3711 auto pfdep = [`foo\bar`, "*.txt"];
3712 version (Windows) invalid ~= pfdep;
3713 else version (Posix) valid ~= pfdep;
3714 else static assert(0);
3716 import std.meta : AliasSeq;
3717 static foreach (T; AliasSeq!(char[], const(char)[], string, wchar[],
3718 const(wchar)[], wstring, dchar[], const(dchar)[], dstring))
3720 foreach (fn; valid)
3721 assert(isValidFilename(to!T(fn)));
3722 foreach (fn; invalid)
3723 assert(!isValidFilename(to!T(fn)));
3727 auto r = MockRange!(immutable(char))(`dir/file.d`);
3728 assert(!isValidFilename(r));
3731 static struct DirEntry { string s; alias s this; }
3732 assert(isValidFilename(DirEntry("file.ext")));
3734 version (Windows)
3736 immutable string cases = "<>:\"/\\|?*";
3737 foreach (i; 0 .. 31 + cases.length)
3739 char[3] buf;
3740 buf[0] = 'a';
3741 buf[1] = i <= 31 ? cast(char) i : cases[i - 32];
3742 buf[2] = 'b';
3743 assert(!isValidFilename(buf[]));
3750 /** Checks whether `path` is a valid path.
3752 Generally, this function checks that `path` is not empty, and that
3753 each component of the path either satisfies $(LREF isValidFilename)
3754 or is equal to `"."` or `".."`.
3756 $(B It does $(I not) check whether the path points to an existing file
3757 or directory; use $(REF exists, std,file) for this purpose.)
3759 On Windows, some special rules apply:
3760 $(UL
3761 $(LI If the second character of `path` is a colon (`':'`),
3762 the first character is interpreted as a drive letter, and
3763 must be in the range A-Z (case insensitive).)
3764 $(LI If `path` is on the form $(D `\\$(I server)\$(I share)\...`)
3765 (UNC path), $(LREF isValidFilename) is applied to $(I server)
3766 and $(I share) as well.)
3767 $(LI If `path` starts with $(D `\\?\`) (long UNC path), the
3768 only requirement for the rest of the string is that it does
3769 not contain the null character.)
3770 $(LI If `path` starts with $(D `\\.\`) (Win32 device namespace)
3771 this function returns `false`; such paths are beyond the scope
3772 of this module.)
3775 Params:
3776 path = string or Range of characters to check
3778 Returns:
3779 true if `path` is a valid path.
3781 bool isValidPath(Range)(Range path)
3782 if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) ||
3783 isNarrowString!Range) &&
3784 !isConvertibleToString!Range)
3786 alias C = Unqual!(ElementEncodingType!Range);
3788 if (path.empty) return false;
3790 // Check whether component is "." or "..", or whether it satisfies
3791 // isValidFilename.
3792 bool isValidComponent(Range component)
3794 assert(component.length > 0);
3795 if (component[0] == '.')
3797 if (component.length == 1) return true;
3798 else if (component.length == 2 && component[1] == '.') return true;
3800 return isValidFilename(component);
3803 if (path.length == 1)
3804 return isDirSeparator(path[0]) || isValidComponent(path);
3806 Range remainder;
3807 version (Windows)
3809 if (isDirSeparator(path[0]) && isDirSeparator(path[1]))
3811 // Some kind of UNC path
3812 if (path.length < 5)
3814 // All valid UNC paths must have at least 5 characters
3815 return false;
3817 else if (path[2] == '?')
3819 // Long UNC path
3820 if (!isDirSeparator(path[3])) return false;
3821 foreach (c; path[4 .. $])
3823 if (c == '\0') return false;
3825 return true;
3827 else if (path[2] == '.')
3829 // Win32 device namespace not supported
3830 return false;
3832 else
3834 // Normal UNC path, i.e. \\server\share\...
3835 size_t i = 2;
3836 while (i < path.length && !isDirSeparator(path[i])) ++i;
3837 if (i == path.length || !isValidFilename(path[2 .. i]))
3838 return false;
3839 ++i; // Skip a single dir separator
3840 size_t j = i;
3841 while (j < path.length && !isDirSeparator(path[j])) ++j;
3842 if (!isValidFilename(path[i .. j])) return false;
3843 remainder = path[j .. $];
3846 else if (isDriveSeparator(path[1]))
3848 import std.ascii : isAlpha;
3849 if (!isAlpha(path[0])) return false;
3850 remainder = path[2 .. $];
3852 else
3854 remainder = path;
3857 else version (Posix)
3859 remainder = path;
3861 else static assert(0);
3862 remainder = ltrimDirSeparators(remainder);
3864 // Check that each component satisfies isValidComponent.
3865 while (!remainder.empty)
3867 size_t i = 0;
3868 while (i < remainder.length && !isDirSeparator(remainder[i])) ++i;
3869 assert(i > 0);
3870 if (!isValidComponent(remainder[0 .. i])) return false;
3871 remainder = ltrimDirSeparators(remainder[i .. $]);
3874 // All criteria passed
3875 return true;
3879 @safe pure @nogc nothrow
3880 unittest
3882 assert(isValidPath("/foo/bar"));
3883 assert(!isValidPath("/foo\0/bar"));
3884 assert(isValidPath("/"));
3885 assert(isValidPath("a"));
3887 version (Windows)
3889 assert(isValidPath(`c:\`));
3890 assert(isValidPath(`c:\foo`));
3891 assert(isValidPath(`c:\foo\.\bar\\\..\`));
3892 assert(!isValidPath(`!:\foo`));
3893 assert(!isValidPath(`c::\foo`));
3894 assert(!isValidPath(`c:\foo?`));
3895 assert(!isValidPath(`c:\foo.`));
3897 assert(isValidPath(`\\server\share`));
3898 assert(isValidPath(`\\server\share\foo`));
3899 assert(isValidPath(`\\server\share\\foo`));
3900 assert(!isValidPath(`\\\server\share\foo`));
3901 assert(!isValidPath(`\\server\\share\foo`));
3902 assert(!isValidPath(`\\ser*er\share\foo`));
3903 assert(!isValidPath(`\\server\sha?e\foo`));
3904 assert(!isValidPath(`\\server\share\|oo`));
3906 assert(isValidPath(`\\?\<>:"?*|/\..\.`));
3907 assert(!isValidPath("\\\\?\\foo\0bar"));
3909 assert(!isValidPath(`\\.\PhysicalDisk1`));
3910 assert(!isValidPath(`\\`));
3913 import std.utf : byCodeUnit;
3914 assert(isValidPath("/foo/bar".byCodeUnit));
3917 bool isValidPath(Range)(auto ref Range path)
3918 if (isConvertibleToString!Range)
3920 return isValidPath!(StringTypeOf!Range)(path);
3923 @safe unittest
3925 assert(testAliasedString!isValidPath("/foo/bar"));
3928 /** Performs tilde expansion in paths on POSIX systems.
3929 On Windows, this function does nothing.
3931 There are two ways of using tilde expansion in a path. One
3932 involves using the tilde alone or followed by a path separator. In
3933 this case, the tilde will be expanded with the value of the
3934 environment variable `HOME`. The second way is putting
3935 a username after the tilde (i.e. `~john/Mail`). Here,
3936 the username will be searched for in the user database
3937 (i.e. `/etc/passwd` on Unix systems) and will expand to
3938 whatever path is stored there. The username is considered the
3939 string after the tilde ending at the first instance of a path
3940 separator.
3942 Note that using the `~user` syntax may give different
3943 values from just `~` if the environment variable doesn't
3944 match the value stored in the user database.
3946 When the environment variable version is used, the path won't
3947 be modified if the environment variable doesn't exist or it
3948 is empty. When the database version is used, the path won't be
3949 modified if the user doesn't exist in the database or there is
3950 not enough memory to perform the query.
3952 This function performs several memory allocations.
3954 Params:
3955 inputPath = The path name to expand.
3957 Returns:
3958 `inputPath` with the tilde expanded, or just `inputPath`
3959 if it could not be expanded.
3960 For Windows, `expandTilde` merely returns its argument `inputPath`.
3962 Example:
3963 -----
3964 void processFile(string path)
3966 // Allow calling this function with paths such as ~/foo
3967 auto fullPath = expandTilde(path);
3970 -----
3972 string expandTilde(return scope const string inputPath) @safe nothrow
3974 version (Posix)
3976 import core.exception : onOutOfMemoryError;
3977 import core.stdc.errno : errno, EBADF, ENOENT, EPERM, ERANGE, ESRCH;
3978 import core.stdc.stdlib : malloc, free, realloc;
3980 /* Joins a path from a C string to the remainder of path.
3982 The last path separator from c_path is discarded. The result
3983 is joined to path[char_pos .. length] if char_pos is smaller
3984 than length, otherwise path is not appended to c_path.
3986 static string combineCPathWithDPath(char* c_path, string path, size_t char_pos) @trusted nothrow
3988 import core.stdc.string : strlen;
3989 import std.exception : assumeUnique;
3991 assert(c_path != null);
3992 assert(path.length > 0);
3993 assert(char_pos >= 0);
3995 // Search end of C string
3996 size_t end = strlen(c_path);
3998 const cPathEndsWithDirSep = end && isDirSeparator(c_path[end - 1]);
4000 string cp;
4001 if (char_pos < path.length)
4003 // Remove trailing path separator, if any (with special care for root /)
4004 if (cPathEndsWithDirSep && (end > 1 || isDirSeparator(path[char_pos])))
4005 end--;
4007 // Append something from path
4008 cp = assumeUnique(c_path[0 .. end] ~ path[char_pos .. $]);
4010 else
4012 // Remove trailing path separator, if any (except for root /)
4013 if (cPathEndsWithDirSep && end > 1)
4014 end--;
4016 // Create our own copy, as lifetime of c_path is undocumented
4017 cp = c_path[0 .. end].idup;
4020 return cp;
4023 // Replaces the tilde from path with the environment variable HOME.
4024 static string expandFromEnvironment(string path) @safe nothrow
4026 import core.stdc.stdlib : getenv;
4028 assert(path.length >= 1);
4029 assert(path[0] == '~');
4031 // Get HOME and use that to replace the tilde.
4032 auto home = () @trusted { return getenv("HOME"); } ();
4033 if (home == null)
4034 return path;
4036 return combineCPathWithDPath(home, path, 1);
4039 // Replaces the tilde from path with the path from the user database.
4040 static string expandFromDatabase(string path) @safe nothrow
4042 // bionic doesn't really support this, as getpwnam_r
4043 // isn't provided and getpwnam is basically just a stub
4044 version (CRuntime_Bionic)
4046 return path;
4048 else
4050 import core.sys.posix.pwd : passwd, getpwnam_r;
4051 import std.string : indexOf;
4053 assert(path.length > 2 || (path.length == 2 && !isDirSeparator(path[1])));
4054 assert(path[0] == '~');
4056 // Extract username, searching for path separator.
4057 auto last_char = indexOf(path, dirSeparator[0]);
4059 size_t username_len = (last_char == -1) ? path.length : last_char;
4060 char[] username = new char[username_len * char.sizeof];
4062 if (last_char == -1)
4064 username[0 .. username_len - 1] = path[1 .. $];
4065 last_char = path.length + 1;
4067 else
4069 username[0 .. username_len - 1] = path[1 .. last_char];
4071 username[username_len - 1] = 0;
4073 assert(last_char > 1);
4075 // Reserve C memory for the getpwnam_r() function.
4076 version (StdUnittest)
4077 uint extra_memory_size = 2;
4078 else
4079 uint extra_memory_size = 5 * 1024;
4080 char[] extra_memory;
4082 passwd result;
4083 loop: while (1)
4085 extra_memory.length += extra_memory_size;
4087 // Obtain info from database.
4088 passwd *verify;
4089 errno = 0;
4090 auto passResult = () @trusted { return getpwnam_r(
4091 &username[0],
4092 &result,
4093 &extra_memory[0],
4094 extra_memory.length,
4095 &verify
4096 ); } ();
4097 if (passResult == 0)
4099 // Succeeded if verify points at result
4100 if (verify == () @trusted { return &result; } ())
4101 // username is found
4102 path = combineCPathWithDPath(result.pw_dir, path, last_char);
4103 break;
4106 switch (errno)
4108 case ERANGE:
4109 // On BSD and OSX, errno can be left at 0 instead of set to ERANGE
4110 case 0:
4111 break;
4113 case ENOENT:
4114 case ESRCH:
4115 case EBADF:
4116 case EPERM:
4117 // The given name or uid was not found.
4118 break loop;
4120 default:
4121 onOutOfMemoryError();
4124 // extra_memory isn't large enough
4125 import core.checkedint : mulu;
4126 bool overflow;
4127 extra_memory_size = mulu(extra_memory_size, 2, overflow);
4128 if (overflow) assert(0);
4130 return path;
4134 // Return early if there is no tilde in path.
4135 if (inputPath.length < 1 || inputPath[0] != '~')
4136 return inputPath;
4138 if (inputPath.length == 1 || isDirSeparator(inputPath[1]))
4139 return expandFromEnvironment(inputPath);
4140 else
4141 return expandFromDatabase(inputPath);
4143 else version (Windows)
4145 // Put here real windows implementation.
4146 return inputPath;
4148 else
4150 static assert(0); // Guard. Implement on other platforms.
4155 @safe unittest
4157 version (Posix)
4159 import std.process : environment;
4161 auto oldHome = environment["HOME"];
4162 scope(exit) environment["HOME"] = oldHome;
4164 environment["HOME"] = "dmd/test";
4165 assert(expandTilde("~/") == "dmd/test/");
4166 assert(expandTilde("~") == "dmd/test");
4170 @safe unittest
4172 version (Posix)
4174 static if (__traits(compiles, { import std.process : executeShell; }))
4175 import std.process : executeShell;
4177 import std.process : environment;
4178 import std.string : strip;
4180 // Retrieve the current home variable.
4181 auto oldHome = environment.get("HOME");
4183 // Testing when there is no environment variable.
4184 environment.remove("HOME");
4185 assert(expandTilde("~/") == "~/");
4186 assert(expandTilde("~") == "~");
4188 // Testing when an environment variable is set.
4189 environment["HOME"] = "dmd/test";
4190 assert(expandTilde("~/") == "dmd/test/");
4191 assert(expandTilde("~") == "dmd/test");
4193 // The same, but with a variable ending in a slash.
4194 environment["HOME"] = "dmd/test/";
4195 assert(expandTilde("~/") == "dmd/test/");
4196 assert(expandTilde("~") == "dmd/test");
4198 // The same, but with a variable set to root.
4199 environment["HOME"] = "/";
4200 assert(expandTilde("~/") == "/");
4201 assert(expandTilde("~") == "/");
4203 // Recover original HOME variable before continuing.
4204 if (oldHome !is null) environment["HOME"] = oldHome;
4205 else environment.remove("HOME");
4207 static if (is(typeof(executeShell)))
4209 immutable tildeUser = "~" ~ environment.get("USER");
4210 immutable path = executeShell("echo " ~ tildeUser).output.strip();
4211 immutable expTildeUser = expandTilde(tildeUser);
4212 assert(expTildeUser == path, expTildeUser);
4213 immutable expTildeUserSlash = expandTilde(tildeUser ~ "/");
4214 immutable pathSlash = path[$-1] == '/' ? path : path ~ "/";
4215 assert(expTildeUserSlash == pathSlash, expTildeUserSlash);
4218 assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey");
4222 @safe unittest
4224 version (Posix)
4226 import std.process : environment;
4228 string testPath(scope const string source_path) {
4229 return source_path.expandTilde;
4232 auto oldHome = environment["HOME"];
4233 scope(exit) environment["HOME"] = oldHome;
4235 environment["HOME"] = "dmd/test";
4236 assert(testPath("~/") == "dmd/test/");
4237 assert(testPath("~") == "dmd/test");
4242 version (StdUnittest)
4244 private:
4245 /* Define a mock RandomAccessRange to use for unittesting.
4248 struct MockRange(C)
4250 this(C[] array) { this.array = array; }
4251 const
4253 @property size_t length() { return array.length; }
4254 @property bool empty() { return array.length == 0; }
4255 @property C front() { return array[0]; }
4256 @property C back() { return array[$ - 1]; }
4257 alias opDollar = length;
4258 C opIndex(size_t i) { return array[i]; }
4260 void popFront() { array = array[1 .. $]; }
4261 void popBack() { array = array[0 .. $-1]; }
4262 MockRange!C opSlice( size_t lwr, size_t upr) const
4264 return MockRange!C(array[lwr .. upr]);
4266 @property MockRange save() { return this; }
4267 private:
4268 C[] array;
4271 /* Define a mock BidirectionalRange to use for unittesting.
4274 struct MockBiRange(C)
4276 this(const(C)[] array) { this.array = array; }
4277 const
4279 @property bool empty() { return array.length == 0; }
4280 @property C front() { return array[0]; }
4281 @property C back() { return array[$ - 1]; }
4282 @property size_t opDollar() { return array.length; }
4284 void popFront() { array = array[1 .. $]; }
4285 void popBack() { array = array[0 .. $-1]; }
4286 @property MockBiRange save() { return this; }
4287 private:
4288 const(C)[] array;
4293 @safe unittest
4295 static assert( isRandomAccessRange!(MockRange!(const(char))) );
4296 static assert( isBidirectionalRange!(MockBiRange!(const(char))) );
4299 private template BaseOf(R)
4301 static if (isRandomAccessRange!R && isSomeChar!(ElementType!R))
4302 alias BaseOf = R;
4303 else
4304 alias BaseOf = StringTypeOf!R;