Fix build on sparc64-linux-gnu.
[official-gcc.git] / libphobos / src / std / path.d
blob32870ea118dff950d62b068f0ae58e6c777ba4cd
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 // FIXME
100 import std.file; //: getcwd;
101 static import std.meta;
102 import std.range.primitives;
103 import std.traits;
105 version (unittest)
107 private:
108 struct TestAliasedString
110 string get() @safe @nogc pure nothrow { return _s; }
111 alias get this;
112 @disable this(this);
113 string _s;
116 bool testAliasedString(alias func, Args...)(string s, Args args)
118 return func(TestAliasedString(s), args) == func(s, args);
122 /** String used to separate directory names in a path. Under
123 POSIX this is a slash, under Windows a backslash.
125 version (Posix) enum string dirSeparator = "/";
126 else version (Windows) enum string dirSeparator = "\\";
127 else static assert(0, "unsupported platform");
132 /** Path separator string. A colon under POSIX, a semicolon
133 under Windows.
135 version (Posix) enum string pathSeparator = ":";
136 else version (Windows) enum string pathSeparator = ";";
137 else static assert(0, "unsupported platform");
142 /** Determines whether the given character is a directory separator.
144 On Windows, this includes both $(D `\`) and $(D `/`).
145 On POSIX, it's just $(D `/`).
147 bool isDirSeparator(dchar c) @safe pure nothrow @nogc
149 if (c == '/') return true;
150 version (Windows) if (c == '\\') return true;
151 return false;
155 /* Determines whether the given character is a drive separator.
157 On Windows, this is true if c is the ':' character that separates
158 the drive letter from the rest of the path. On POSIX, this always
159 returns false.
161 private bool isDriveSeparator(dchar c) @safe pure nothrow @nogc
163 version (Windows) return c == ':';
164 else return false;
168 /* Combines the isDirSeparator and isDriveSeparator tests. */
169 version (Windows) private bool isSeparator(dchar c) @safe pure nothrow @nogc
171 return isDirSeparator(c) || isDriveSeparator(c);
173 version (Posix) private alias isSeparator = isDirSeparator;
176 /* Helper function that determines the position of the last
177 drive/directory separator in a string. Returns -1 if none
178 is found.
180 private ptrdiff_t lastSeparator(R)(R path)
181 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
182 isNarrowString!R)
184 auto i = (cast(ptrdiff_t) path.length) - 1;
185 while (i >= 0 && !isSeparator(path[i])) --i;
186 return i;
190 version (Windows)
192 private bool isUNC(R)(R path)
193 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
194 isNarrowString!R)
196 return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1])
197 && !isDirSeparator(path[2]);
200 private ptrdiff_t uncRootLength(R)(R path)
201 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
202 isNarrowString!R)
203 in { assert(isUNC(path)); }
204 body
206 ptrdiff_t i = 3;
207 while (i < path.length && !isDirSeparator(path[i])) ++i;
208 if (i < path.length)
210 auto j = i;
211 do { ++j; } while (j < path.length && isDirSeparator(path[j]));
212 if (j < path.length)
214 do { ++j; } while (j < path.length && !isDirSeparator(path[j]));
215 i = j;
218 return i;
221 private bool hasDrive(R)(R path)
222 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
223 isNarrowString!R)
225 return path.length >= 2 && isDriveSeparator(path[1]);
228 private bool isDriveRoot(R)(R path)
229 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
230 isNarrowString!R)
232 return path.length >= 3 && isDriveSeparator(path[1])
233 && isDirSeparator(path[2]);
238 /* Helper functions that strip leading/trailing slashes and backslashes
239 from a path.
241 private auto ltrimDirSeparators(R)(R path)
242 if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementType!R) ||
243 isNarrowString!R)
245 static if (isRandomAccessRange!R && hasSlicing!R || isNarrowString!R)
247 int i = 0;
248 while (i < path.length && isDirSeparator(path[i]))
249 ++i;
250 return path[i .. path.length];
252 else
254 while (!path.empty && isDirSeparator(path.front))
255 path.popFront();
256 return path;
260 @system unittest
262 import std.array;
263 import std.utf : byDchar;
265 assert(ltrimDirSeparators("//abc//").array == "abc//");
266 assert(ltrimDirSeparators("//abc//"d).array == "abc//"d);
267 assert(ltrimDirSeparators("//abc//".byDchar).array == "abc//"d);
270 private auto rtrimDirSeparators(R)(R path)
271 if (isBidirectionalRange!R && isSomeChar!(ElementType!R) ||
272 isNarrowString!R)
274 static if (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R)
276 auto i = (cast(ptrdiff_t) path.length) - 1;
277 while (i >= 0 && isDirSeparator(path[i]))
278 --i;
279 return path[0 .. i+1];
281 else
283 while (!path.empty && isDirSeparator(path.back))
284 path.popBack();
285 return path;
289 @system unittest
291 import std.array;
292 import std.utf : byDchar;
294 assert(rtrimDirSeparators("//abc//").array == "//abc");
295 assert(rtrimDirSeparators("//abc//"d).array == "//abc"d);
297 assert(rtrimDirSeparators(MockBiRange!char("//abc//")).array == "//abc");
300 private auto trimDirSeparators(R)(R path)
301 if (isBidirectionalRange!R && isSomeChar!(ElementType!R) ||
302 isNarrowString!R)
304 return ltrimDirSeparators(rtrimDirSeparators(path));
307 @system unittest
309 import std.array;
310 import std.utf : byDchar;
312 assert(trimDirSeparators("//abc//").array == "abc");
313 assert(trimDirSeparators("//abc//"d).array == "abc"d);
315 assert(trimDirSeparators(MockBiRange!char("//abc//")).array == "abc");
321 /** This $(D enum) is used as a template argument to functions which
322 compare file names, and determines whether the comparison is
323 case sensitive or not.
325 enum CaseSensitive : bool
327 /// File names are case insensitive
328 no = false,
330 /// File names are case sensitive
331 yes = true,
333 /** The default (or most common) setting for the current platform.
334 That is, $(D no) on Windows and Mac OS X, and $(D yes) on all
335 POSIX systems except OS X (Linux, *BSD, etc.).
337 osDefault = osDefaultCaseSensitivity
339 version (Windows) private enum osDefaultCaseSensitivity = false;
340 else version (OSX) private enum osDefaultCaseSensitivity = false;
341 else version (Posix) private enum osDefaultCaseSensitivity = true;
342 else static assert(0);
348 Params:
349 cs = Whether or not suffix matching is case-sensitive.
350 path = A path name. It can be a string, or any random-access range of
351 characters.
352 suffix = An optional suffix to be removed from the file name.
353 Returns: The name of the file in the path name, without any leading
354 directory and with an optional suffix chopped off.
356 If $(D suffix) is specified, it will be compared to $(D path)
357 using $(D filenameCmp!cs),
358 where $(D cs) is an optional template parameter determining whether
359 the comparison is case sensitive or not. See the
360 $(LREF filenameCmp) documentation for details.
362 Example:
364 assert(baseName("dir/file.ext") == "file.ext");
365 assert(baseName("dir/file.ext", ".ext") == "file");
366 assert(baseName("dir/file.ext", ".xyz") == "file.ext");
367 assert(baseName("dir/filename", "name") == "file");
368 assert(baseName("dir/subdir/") == "subdir");
370 version (Windows)
372 assert(baseName(`d:file.ext`) == "file.ext");
373 assert(baseName(`d:\dir\file.ext`) == "file.ext");
377 Note:
378 This function $(I only) strips away the specified suffix, which
379 doesn't necessarily have to represent an extension.
380 To remove the extension from a path, regardless of what the extension
381 is, use $(LREF stripExtension).
382 To obtain the filename without leading directories and without
383 an extension, combine the functions like this:
385 assert(baseName(stripExtension("dir/file.ext")) == "file");
388 Standards:
389 This function complies with
390 $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html,
391 the POSIX requirements for the 'basename' shell utility)
392 (with suitable adaptations for Windows paths).
394 auto baseName(R)(R path)
395 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!R)
397 return _baseName(path);
400 /// ditto
401 auto baseName(C)(C[] path)
402 if (isSomeChar!C)
404 return _baseName(path);
407 private R _baseName(R)(R path)
408 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || isNarrowString!R)
410 auto p1 = stripDrive(path);
411 if (p1.empty)
413 version (Windows) if (isUNC(path))
414 return path[0 .. 1];
415 static if (isSomeString!R)
416 return null;
417 else
418 return p1; // which is empty
421 auto p2 = rtrimDirSeparators(p1);
422 if (p2.empty) return p1[0 .. 1];
424 return p2[lastSeparator(p2)+1 .. p2.length];
427 /// ditto
428 inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1)
429 (inout(C)[] path, in C1[] suffix)
430 @safe pure //TODO: nothrow (because of filenameCmp())
431 if (isSomeChar!C && isSomeChar!C1)
433 auto p = baseName(path);
434 if (p.length > suffix.length
435 && filenameCmp!cs(cast(const(C)[])p[$-suffix.length .. $], suffix) == 0)
437 return p[0 .. $-suffix.length];
439 else return p;
442 @safe unittest
444 assert(baseName("").empty);
445 assert(baseName("file.ext"w) == "file.ext");
446 assert(baseName("file.ext"d, ".ext") == "file");
447 assert(baseName("file", "file"w.dup) == "file");
448 assert(baseName("dir/file.ext"d.dup) == "file.ext");
449 assert(baseName("dir/file.ext", ".ext"d) == "file");
450 assert(baseName("dir/file"w, "file"d) == "file");
451 assert(baseName("dir///subdir////") == "subdir");
452 assert(baseName("dir/subdir.ext/", ".ext") == "subdir");
453 assert(baseName("dir/subdir/".dup, "subdir") == "subdir");
454 assert(baseName("/"w.dup) == "/");
455 assert(baseName("//"d.dup) == "/");
456 assert(baseName("///") == "/");
458 assert(baseName!(CaseSensitive.yes)("file.ext", ".EXT") == "file.ext");
459 assert(baseName!(CaseSensitive.no)("file.ext", ".EXT") == "file");
462 auto r = MockRange!(immutable(char))(`dir/file.ext`);
463 auto s = r.baseName();
464 foreach (i, c; `file`)
465 assert(s[i] == c);
468 version (Windows)
470 assert(baseName(`dir\file.ext`) == `file.ext`);
471 assert(baseName(`dir\file.ext`, `.ext`) == `file`);
472 assert(baseName(`dir\file`, `file`) == `file`);
473 assert(baseName(`d:file.ext`) == `file.ext`);
474 assert(baseName(`d:file.ext`, `.ext`) == `file`);
475 assert(baseName(`d:file`, `file`) == `file`);
476 assert(baseName(`dir\\subdir\\\`) == `subdir`);
477 assert(baseName(`dir\subdir.ext\`, `.ext`) == `subdir`);
478 assert(baseName(`dir\subdir\`, `subdir`) == `subdir`);
479 assert(baseName(`\`) == `\`);
480 assert(baseName(`\\`) == `\`);
481 assert(baseName(`\\\`) == `\`);
482 assert(baseName(`d:\`) == `\`);
483 assert(baseName(`d:`).empty);
484 assert(baseName(`\\server\share\file`) == `file`);
485 assert(baseName(`\\server\share\`) == `\`);
486 assert(baseName(`\\server\share`) == `\`);
488 auto r = MockRange!(immutable(char))(`\\server\share`);
489 auto s = r.baseName();
490 foreach (i, c; `\`)
491 assert(s[i] == c);
494 assert(baseName(stripExtension("dir/file.ext")) == "file");
496 static assert(baseName("dir/file.ext") == "file.ext");
497 static assert(baseName("dir/file.ext", ".ext") == "file");
499 static struct DirEntry { string s; alias s this; }
500 assert(baseName(DirEntry("dir/file.ext")) == "file.ext");
503 @safe unittest
505 assert(testAliasedString!baseName("file"));
507 enum S : string { a = "file/path/to/test" }
508 assert(S.a.baseName == "test");
510 char[S.a.length] sa = S.a[];
511 assert(sa.baseName == "test");
514 /** Returns the directory part of a path. On Windows, this
515 includes the drive letter if present.
517 Params:
518 path = A path name.
520 Returns:
521 A slice of $(D path) or ".".
523 Standards:
524 This function complies with
525 $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html,
526 the POSIX requirements for the 'dirname' shell utility)
527 (with suitable adaptations for Windows paths).
529 auto dirName(R)(R path)
530 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R)
532 return _dirName(path);
535 /// ditto
536 auto dirName(C)(C[] path)
537 if (isSomeChar!C)
539 return _dirName(path);
542 private auto _dirName(R)(R path)
543 if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
544 isNarrowString!R)
546 static auto result(bool dot, typeof(path[0 .. 1]) p)
548 static if (isSomeString!R)
549 return dot ? "." : p;
550 else
552 import std.range : choose, only;
553 return choose(dot, only(cast(ElementEncodingType!R)'.'), p);
557 if (path.empty)
558 return result(true, path[0 .. 0]);
560 auto p = rtrimDirSeparators(path);
561 if (p.empty)
562 return result(false, path[0 .. 1]);
564 version (Windows)
566 if (isUNC(p) && uncRootLength(p) == p.length)
567 return result(false, p);
569 if (p.length == 2 && isDriveSeparator(p[1]) && path.length > 2)
570 return result(false, path[0 .. 3]);
573 auto i = lastSeparator(p);
574 if (i == -1)
575 return result(true, p);
576 if (i == 0)
577 return result(false, p[0 .. 1]);
579 version (Windows)
581 // If the directory part is either d: or d:\
582 // do not chop off the last symbol.
583 if (isDriveSeparator(p[i]) || isDriveSeparator(p[i-1]))
584 return result(false, p[0 .. i+1]);
586 // Remove any remaining trailing (back)slashes.
587 return result(false, rtrimDirSeparators(p[0 .. i]));
591 @safe unittest
593 assert(dirName("") == ".");
594 assert(dirName("file"w) == ".");
595 assert(dirName("dir/"d) == ".");
596 assert(dirName("dir///") == ".");
597 assert(dirName("dir/file"w.dup) == "dir");
598 assert(dirName("dir///file"d.dup) == "dir");
599 assert(dirName("dir/subdir/") == "dir");
600 assert(dirName("/dir/file"w) == "/dir");
601 assert(dirName("/file"d) == "/");
602 assert(dirName("/") == "/");
603 assert(dirName("///") == "/");
605 version (Windows)
607 assert(dirName(`dir\`) == `.`);
608 assert(dirName(`dir\\\`) == `.`);
609 assert(dirName(`dir\file`) == `dir`);
610 assert(dirName(`dir\\\file`) == `dir`);
611 assert(dirName(`dir\subdir\`) == `dir`);
612 assert(dirName(`\dir\file`) == `\dir`);
613 assert(dirName(`\file`) == `\`);
614 assert(dirName(`\`) == `\`);
615 assert(dirName(`\\\`) == `\`);
616 assert(dirName(`d:`) == `d:`);
617 assert(dirName(`d:file`) == `d:`);
618 assert(dirName(`d:\`) == `d:\`);
619 assert(dirName(`d:\file`) == `d:\`);
620 assert(dirName(`d:\dir\file`) == `d:\dir`);
621 assert(dirName(`\\server\share\dir\file`) == `\\server\share\dir`);
622 assert(dirName(`\\server\share\file`) == `\\server\share`);
623 assert(dirName(`\\server\share\`) == `\\server\share`);
624 assert(dirName(`\\server\share`) == `\\server\share`);
628 @safe unittest
630 assert(testAliasedString!dirName("file"));
632 enum S : string { a = "file/path/to/test" }
633 assert(S.a.dirName == "file/path/to");
635 char[S.a.length] sa = S.a[];
636 assert(sa.dirName == "file/path/to");
639 @system unittest
641 static assert(dirName("dir/file") == "dir");
643 import std.array;
644 import std.utf : byChar, byWchar, byDchar;
646 assert(dirName("".byChar).array == ".");
647 assert(dirName("file"w.byWchar).array == "."w);
648 assert(dirName("dir/"d.byDchar).array == "."d);
649 assert(dirName("dir///".byChar).array == ".");
650 assert(dirName("dir/subdir/".byChar).array == "dir");
651 assert(dirName("/dir/file"w.byWchar).array == "/dir"w);
652 assert(dirName("/file"d.byDchar).array == "/"d);
653 assert(dirName("/".byChar).array == "/");
654 assert(dirName("///".byChar).array == "/");
656 version (Windows)
658 assert(dirName(`dir\`.byChar).array == `.`);
659 assert(dirName(`dir\\\`.byChar).array == `.`);
660 assert(dirName(`dir\file`.byChar).array == `dir`);
661 assert(dirName(`dir\\\file`.byChar).array == `dir`);
662 assert(dirName(`dir\subdir\`.byChar).array == `dir`);
663 assert(dirName(`\dir\file`.byChar).array == `\dir`);
664 assert(dirName(`\file`.byChar).array == `\`);
665 assert(dirName(`\`.byChar).array == `\`);
666 assert(dirName(`\\\`.byChar).array == `\`);
667 assert(dirName(`d:`.byChar).array == `d:`);
668 assert(dirName(`d:file`.byChar).array == `d:`);
669 assert(dirName(`d:\`.byChar).array == `d:\`);
670 assert(dirName(`d:\file`.byChar).array == `d:\`);
671 assert(dirName(`d:\dir\file`.byChar).array == `d:\dir`);
672 assert(dirName(`\\server\share\dir\file`.byChar).array == `\\server\share\dir`);
673 assert(dirName(`\\server\share\file`) == `\\server\share`);
674 assert(dirName(`\\server\share\`.byChar).array == `\\server\share`);
675 assert(dirName(`\\server\share`.byChar).array == `\\server\share`);
678 //static assert(dirName("dir/file".byChar).array == "dir");
684 /** Returns the root directory of the specified path, or $(D null) if the
685 path is not rooted.
687 Params:
688 path = A path name.
690 Returns:
691 A slice of $(D path).
693 auto rootName(R)(R path)
694 if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
695 isNarrowString!R) &&
696 !isConvertibleToString!R)
698 if (path.empty)
699 goto Lnull;
701 version (Posix)
703 if (isDirSeparator(path[0])) return path[0 .. 1];
705 else version (Windows)
707 if (isDirSeparator(path[0]))
709 if (isUNC(path)) return path[0 .. uncRootLength(path)];
710 else return path[0 .. 1];
712 else if (path.length >= 3 && isDriveSeparator(path[1]) &&
713 isDirSeparator(path[2]))
715 return path[0 .. 3];
718 else static assert(0, "unsupported platform");
720 assert(!isRooted(path));
721 Lnull:
722 static if (is(StringTypeOf!R))
723 return null; // legacy code may rely on null return rather than slice
724 else
725 return path[0 .. 0];
729 @safe unittest
731 assert(rootName("") is null);
732 assert(rootName("foo") is null);
733 assert(rootName("/") == "/");
734 assert(rootName("/foo/bar") == "/");
736 version (Windows)
738 assert(rootName("d:foo") is null);
739 assert(rootName(`d:\foo`) == `d:\`);
740 assert(rootName(`\\server\share\foo`) == `\\server\share`);
741 assert(rootName(`\\server\share`) == `\\server\share`);
745 @safe unittest
747 assert(testAliasedString!rootName("/foo/bar"));
750 @safe unittest
752 import std.array;
753 import std.utf : byChar;
755 assert(rootName("".byChar).array == "");
756 assert(rootName("foo".byChar).array == "");
757 assert(rootName("/".byChar).array == "/");
758 assert(rootName("/foo/bar".byChar).array == "/");
760 version (Windows)
762 assert(rootName("d:foo".byChar).array == "");
763 assert(rootName(`d:\foo`.byChar).array == `d:\`);
764 assert(rootName(`\\server\share\foo`.byChar).array == `\\server\share`);
765 assert(rootName(`\\server\share`.byChar).array == `\\server\share`);
769 auto rootName(R)(R path)
770 if (isConvertibleToString!R)
772 return rootName!(StringTypeOf!R)(path);
777 Get the drive portion of a path.
779 Params:
780 path = string or range of characters
782 Returns:
783 A slice of $(D _path) that is the drive, or an empty range if the drive
784 is not specified. In the case of UNC paths, the network share
785 is returned.
787 Always returns an empty range on POSIX.
789 auto driveName(R)(R path)
790 if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
791 isNarrowString!R) &&
792 !isConvertibleToString!R)
794 version (Windows)
796 if (hasDrive(path))
797 return path[0 .. 2];
798 else if (isUNC(path))
799 return path[0 .. uncRootLength(path)];
801 static if (isSomeString!R)
802 return cast(ElementEncodingType!R[]) null; // legacy code may rely on null return rather than slice
803 else
804 return path[0 .. 0];
808 @safe unittest
810 import std.range : empty;
811 version (Posix) assert(driveName("c:/foo").empty);
812 version (Windows)
814 assert(driveName(`dir\file`).empty);
815 assert(driveName(`d:file`) == "d:");
816 assert(driveName(`d:\file`) == "d:");
817 assert(driveName("d:") == "d:");
818 assert(driveName(`\\server\share\file`) == `\\server\share`);
819 assert(driveName(`\\server\share\`) == `\\server\share`);
820 assert(driveName(`\\server\share`) == `\\server\share`);
822 static assert(driveName(`d:\file`) == "d:");
826 auto driveName(R)(auto ref R path)
827 if (isConvertibleToString!R)
829 return driveName!(StringTypeOf!R)(path);
832 @safe unittest
834 assert(testAliasedString!driveName(`d:\file`));
837 @safe unittest
839 import std.array;
840 import std.utf : byChar;
842 version (Posix) assert(driveName("c:/foo".byChar).empty);
843 version (Windows)
845 assert(driveName(`dir\file`.byChar).empty);
846 assert(driveName(`d:file`.byChar).array == "d:");
847 assert(driveName(`d:\file`.byChar).array == "d:");
848 assert(driveName("d:".byChar).array == "d:");
849 assert(driveName(`\\server\share\file`.byChar).array == `\\server\share`);
850 assert(driveName(`\\server\share\`.byChar).array == `\\server\share`);
851 assert(driveName(`\\server\share`.byChar).array == `\\server\share`);
853 static assert(driveName(`d:\file`).array == "d:");
858 /** Strips the drive from a Windows path. On POSIX, the path is returned
859 unaltered.
861 Params:
862 path = A pathname
864 Returns: A slice of path without the drive component.
866 auto stripDrive(R)(R path)
867 if ((isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) ||
868 isNarrowString!R) &&
869 !isConvertibleToString!R)
871 version (Windows)
873 if (hasDrive!(BaseOf!R)(path)) return path[2 .. path.length];
874 else if (isUNC!(BaseOf!R)(path)) return path[uncRootLength!(BaseOf!R)(path) .. path.length];
876 return path;
880 @safe unittest
882 version (Windows)
884 assert(stripDrive(`d:\dir\file`) == `\dir\file`);
885 assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`);
889 auto stripDrive(R)(auto ref R path)
890 if (isConvertibleToString!R)
892 return stripDrive!(StringTypeOf!R)(path);
895 @safe unittest
897 assert(testAliasedString!stripDrive(`d:\dir\file`));
900 @safe unittest
902 version (Windows)
904 assert(stripDrive(`d:\dir\file`) == `\dir\file`);
905 assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`);
906 static assert(stripDrive(`d:\dir\file`) == `\dir\file`);
908 auto r = MockRange!(immutable(char))(`d:\dir\file`);
909 auto s = r.stripDrive();
910 foreach (i, c; `\dir\file`)
911 assert(s[i] == c);
913 version (Posix)
915 assert(stripDrive(`d:\dir\file`) == `d:\dir\file`);
917 auto r = MockRange!(immutable(char))(`d:\dir\file`);
918 auto s = r.stripDrive();
919 foreach (i, c; `d:\dir\file`)
920 assert(s[i] == c);
925 /* Helper function that returns the position of the filename/extension
926 separator dot in path.
928 Params:
929 path = file spec as string or indexable range
930 Returns:
931 index of extension separator (the dot), or -1 if not found
933 private ptrdiff_t extSeparatorPos(R)(const R path)
934 if (isRandomAccessRange!R && hasLength!R && isSomeChar!(ElementType!R) ||
935 isNarrowString!R)
937 for (auto i = path.length; i-- > 0 && !isSeparator(path[i]); )
939 if (path[i] == '.' && i > 0 && !isSeparator(path[i-1]))
940 return i;
942 return -1;
945 @safe unittest
947 assert(extSeparatorPos("file") == -1);
948 assert(extSeparatorPos("file.ext"w) == 4);
949 assert(extSeparatorPos("file.ext1.ext2"d) == 9);
950 assert(extSeparatorPos(".foo".dup) == -1);
951 assert(extSeparatorPos(".foo.ext"w.dup) == 4);
954 @safe unittest
956 assert(extSeparatorPos("dir/file"d.dup) == -1);
957 assert(extSeparatorPos("dir/file.ext") == 8);
958 assert(extSeparatorPos("dir/file.ext1.ext2"w) == 13);
959 assert(extSeparatorPos("dir/.foo"d) == -1);
960 assert(extSeparatorPos("dir/.foo.ext".dup) == 8);
962 version (Windows)
964 assert(extSeparatorPos("dir\\file") == -1);
965 assert(extSeparatorPos("dir\\file.ext") == 8);
966 assert(extSeparatorPos("dir\\file.ext1.ext2") == 13);
967 assert(extSeparatorPos("dir\\.foo") == -1);
968 assert(extSeparatorPos("dir\\.foo.ext") == 8);
970 assert(extSeparatorPos("d:file") == -1);
971 assert(extSeparatorPos("d:file.ext") == 6);
972 assert(extSeparatorPos("d:file.ext1.ext2") == 11);
973 assert(extSeparatorPos("d:.foo") == -1);
974 assert(extSeparatorPos("d:.foo.ext") == 6);
977 static assert(extSeparatorPos("file") == -1);
978 static assert(extSeparatorPos("file.ext"w) == 4);
983 Params: path = A path name.
984 Returns: The _extension part of a file name, including the dot.
986 If there is no _extension, $(D null) is returned.
988 auto extension(R)(R path)
989 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) ||
990 is(StringTypeOf!R))
992 auto i = extSeparatorPos!(BaseOf!R)(path);
993 if (i == -1)
995 static if (is(StringTypeOf!R))
996 return StringTypeOf!R.init[]; // which is null
997 else
998 return path[0 .. 0];
1000 else return path[i .. path.length];
1004 @safe unittest
1006 import std.range : empty;
1007 assert(extension("file").empty);
1008 assert(extension("file.") == ".");
1009 assert(extension("file.ext"w) == ".ext");
1010 assert(extension("file.ext1.ext2"d) == ".ext2");
1011 assert(extension(".foo".dup).empty);
1012 assert(extension(".foo.ext"w.dup) == ".ext");
1014 static assert(extension("file").empty);
1015 static assert(extension("file.ext") == ".ext");
1018 @safe unittest
1021 auto r = MockRange!(immutable(char))(`file.ext1.ext2`);
1022 auto s = r.extension();
1023 foreach (i, c; `.ext2`)
1024 assert(s[i] == c);
1027 static struct DirEntry { string s; alias s this; }
1028 assert(extension(DirEntry("file")).empty);
1032 /** Remove extension from path.
1034 Params:
1035 path = string or range to be sliced
1037 Returns:
1038 slice of path with the extension (if any) stripped off
1040 auto stripExtension(R)(R path)
1041 if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
1042 isNarrowString!R) &&
1043 !isConvertibleToString!R)
1045 auto i = extSeparatorPos(path);
1046 return (i == -1) ? path : path[0 .. i];
1050 @safe unittest
1052 assert(stripExtension("file") == "file");
1053 assert(stripExtension("file.ext") == "file");
1054 assert(stripExtension("file.ext1.ext2") == "file.ext1");
1055 assert(stripExtension("file.") == "file");
1056 assert(stripExtension(".file") == ".file");
1057 assert(stripExtension(".file.ext") == ".file");
1058 assert(stripExtension("dir/file.ext") == "dir/file");
1061 auto stripExtension(R)(auto ref R path)
1062 if (isConvertibleToString!R)
1064 return stripExtension!(StringTypeOf!R)(path);
1067 @safe unittest
1069 assert(testAliasedString!stripExtension("file"));
1072 @safe unittest
1074 assert(stripExtension("file.ext"w) == "file");
1075 assert(stripExtension("file.ext1.ext2"d) == "file.ext1");
1077 import std.array;
1078 import std.utf : byChar, byWchar, byDchar;
1080 assert(stripExtension("file".byChar).array == "file");
1081 assert(stripExtension("file.ext"w.byWchar).array == "file");
1082 assert(stripExtension("file.ext1.ext2"d.byDchar).array == "file.ext1");
1086 /** Sets or replaces an extension.
1088 If the filename already has an extension, it is replaced. If not, the
1089 extension is simply appended to the filename. Including a leading dot
1090 in $(D ext) is optional.
1092 If the extension is empty, this function is equivalent to
1093 $(LREF stripExtension).
1095 This function normally allocates a new string (the possible exception
1096 being the case when path is immutable and doesn't already have an
1097 extension).
1099 Params:
1100 path = A path name
1101 ext = The new extension
1103 Returns: A string containing the _path given by $(D path), but where
1104 the extension has been set to $(D ext).
1106 See_Also:
1107 $(LREF withExtension) which does not allocate and returns a lazy range.
1109 immutable(Unqual!C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext)
1110 if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2))
1114 import std.conv : to;
1115 return withExtension(path, ext).to!(typeof(return));
1117 catch (Exception e)
1119 assert(0);
1123 ///ditto
1124 immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext)
1125 if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
1127 if (ext.length == 0)
1128 return stripExtension(path);
1132 import std.conv : to;
1133 return withExtension(path, ext).to!(typeof(return));
1135 catch (Exception e)
1137 assert(0);
1142 @safe unittest
1144 assert(setExtension("file", "ext") == "file.ext");
1145 assert(setExtension("file"w, ".ext"w) == "file.ext");
1146 assert(setExtension("file."d, "ext"d) == "file.ext");
1147 assert(setExtension("file.", ".ext") == "file.ext");
1148 assert(setExtension("file.old"w, "new"w) == "file.new");
1149 assert(setExtension("file.old"d, ".new"d) == "file.new");
1152 @safe unittest
1154 assert(setExtension("file"w.dup, "ext"w) == "file.ext");
1155 assert(setExtension("file"w.dup, ".ext"w) == "file.ext");
1156 assert(setExtension("file."w, "ext"w.dup) == "file.ext");
1157 assert(setExtension("file."w, ".ext"w.dup) == "file.ext");
1158 assert(setExtension("file.old"d.dup, "new"d) == "file.new");
1159 assert(setExtension("file.old"d.dup, ".new"d) == "file.new");
1161 static assert(setExtension("file", "ext") == "file.ext");
1162 static assert(setExtension("file.old", "new") == "file.new");
1164 static assert(setExtension("file"w.dup, "ext"w) == "file.ext");
1165 static assert(setExtension("file.old"d.dup, "new"d) == "file.new");
1167 // Issue 10601
1168 assert(setExtension("file", "") == "file");
1169 assert(setExtension("file.ext", "") == "file");
1172 /************
1173 * Replace existing extension on filespec with new one.
1175 * Params:
1176 * path = string or random access range representing a filespec
1177 * ext = the new extension
1178 * Returns:
1179 * Range with $(D path)'s extension (if any) replaced with $(D ext).
1180 * The element encoding type of the returned range will be the same as $(D path)'s.
1181 * See_Also:
1182 * $(LREF setExtension)
1184 auto withExtension(R, C)(R path, C[] ext)
1185 if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
1186 isNarrowString!R) &&
1187 !isConvertibleToString!R &&
1188 isSomeChar!C)
1190 import std.range : only, chain;
1191 import std.utf : byUTF;
1193 alias CR = Unqual!(ElementEncodingType!R);
1194 auto dot = only(CR('.'));
1195 if (ext.length == 0 || ext[0] == '.')
1196 dot.popFront(); // so dot is an empty range, too
1197 return chain(stripExtension(path).byUTF!CR, dot, ext.byUTF!CR);
1201 @safe unittest
1203 import std.array;
1204 assert(withExtension("file", "ext").array == "file.ext");
1205 assert(withExtension("file"w, ".ext"w).array == "file.ext");
1206 assert(withExtension("file.ext"w, ".").array == "file.");
1208 import std.utf : byChar, byWchar;
1209 assert(withExtension("file".byChar, "ext").array == "file.ext");
1210 assert(withExtension("file"w.byWchar, ".ext"w).array == "file.ext"w);
1211 assert(withExtension("file.ext"w.byWchar, ".").array == "file."w);
1214 auto withExtension(R, C)(auto ref R path, C[] ext)
1215 if (isConvertibleToString!R)
1217 return withExtension!(StringTypeOf!R)(path, ext);
1220 @safe unittest
1222 assert(testAliasedString!withExtension("file", "ext"));
1225 /** Params:
1226 path = A path name.
1227 ext = The default extension to use.
1229 Returns: The _path given by $(D path), with the extension given by $(D ext)
1230 appended if the path doesn't already have one.
1232 Including the dot in the extension is optional.
1234 This function always allocates a new string, except in the case when
1235 path is immutable and already has an extension.
1237 immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext)
1238 if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2))
1240 import std.conv : to;
1241 return withDefaultExtension(path, ext).to!(typeof(return));
1245 @safe unittest
1247 assert(defaultExtension("file", "ext") == "file.ext");
1248 assert(defaultExtension("file", ".ext") == "file.ext");
1249 assert(defaultExtension("file.", "ext") == "file.");
1250 assert(defaultExtension("file.old", "new") == "file.old");
1251 assert(defaultExtension("file.old", ".new") == "file.old");
1254 @safe unittest
1256 assert(defaultExtension("file"w.dup, "ext"w) == "file.ext");
1257 assert(defaultExtension("file.old"d.dup, "new"d) == "file.old");
1259 static assert(defaultExtension("file", "ext") == "file.ext");
1260 static assert(defaultExtension("file.old", "new") == "file.old");
1262 static assert(defaultExtension("file"w.dup, "ext"w) == "file.ext");
1263 static assert(defaultExtension("file.old"d.dup, "new"d) == "file.old");
1267 /********************************
1268 * Set the extension of $(D path) to $(D ext) if $(D path) doesn't have one.
1270 * Params:
1271 * path = filespec as string or range
1272 * ext = extension, may have leading '.'
1273 * Returns:
1274 * range with the result
1276 auto withDefaultExtension(R, C)(R path, C[] ext)
1277 if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) ||
1278 isNarrowString!R) &&
1279 !isConvertibleToString!R &&
1280 isSomeChar!C)
1282 import std.range : only, chain;
1283 import std.utf : byUTF;
1285 alias CR = Unqual!(ElementEncodingType!R);
1286 auto dot = only(CR('.'));
1287 auto i = extSeparatorPos(path);
1288 if (i == -1)
1290 if (ext.length > 0 && ext[0] == '.')
1291 ext = ext[1 .. $]; // remove any leading . from ext[]
1293 else
1295 // path already has an extension, so make these empty
1296 ext = ext[0 .. 0];
1297 dot.popFront();
1299 return chain(path.byUTF!CR, dot, ext.byUTF!CR);
1303 @safe unittest
1305 import std.array;
1306 assert(withDefaultExtension("file", "ext").array == "file.ext");
1307 assert(withDefaultExtension("file"w, ".ext").array == "file.ext"w);
1308 assert(withDefaultExtension("file.", "ext").array == "file.");
1309 assert(withDefaultExtension("file", "").array == "file.");
1311 import std.utf : byChar, byWchar;
1312 assert(withDefaultExtension("file".byChar, "ext").array == "file.ext");
1313 assert(withDefaultExtension("file"w.byWchar, ".ext").array == "file.ext"w);
1314 assert(withDefaultExtension("file.".byChar, "ext"d).array == "file.");
1315 assert(withDefaultExtension("file".byChar, "").array == "file.");
1318 auto withDefaultExtension(R, C)(auto ref R path, C[] ext)
1319 if (isConvertibleToString!R)
1321 return withDefaultExtension!(StringTypeOf!R, C)(path, ext);
1324 @safe unittest
1326 assert(testAliasedString!withDefaultExtension("file", "ext"));
1329 /** Combines one or more path segments.
1331 This function takes a set of path segments, given as an input
1332 range of string elements or as a set of string arguments,
1333 and concatenates them with each other. Directory separators
1334 are inserted between segments if necessary. If any of the
1335 path segments are absolute (as defined by $(LREF isAbsolute)), the
1336 preceding segments will be dropped.
1338 On Windows, if one of the path segments are rooted, but not absolute
1339 (e.g. $(D `\foo`)), all preceding path segments down to the previous
1340 root will be dropped. (See below for an example.)
1342 This function always allocates memory to hold the resulting path.
1343 The variadic overload is guaranteed to only perform a single
1344 allocation, as is the range version if $(D paths) is a forward
1345 range.
1347 Params:
1348 segments = An input range of segments to assemble the path from.
1349 Returns: The assembled path.
1351 immutable(ElementEncodingType!(ElementType!Range))[]
1352 buildPath(Range)(Range segments)
1353 if (isInputRange!Range && !isInfinite!Range && isSomeString!(ElementType!Range))
1355 if (segments.empty) return null;
1357 // If this is a forward range, we can pre-calculate a maximum length.
1358 static if (isForwardRange!Range)
1360 auto segments2 = segments.save;
1361 size_t precalc = 0;
1362 foreach (segment; segments2) precalc += segment.length + 1;
1364 // Otherwise, just venture a guess and resize later if necessary.
1365 else size_t precalc = 255;
1367 auto buf = new Unqual!(ElementEncodingType!(ElementType!Range))[](precalc);
1368 size_t pos = 0;
1369 foreach (segment; segments)
1371 if (segment.empty) continue;
1372 static if (!isForwardRange!Range)
1374 immutable neededLength = pos + segment.length + 1;
1375 if (buf.length < neededLength)
1376 buf.length = reserve(buf, neededLength + buf.length/2);
1378 auto r = chainPath(buf[0 .. pos], segment);
1379 size_t i;
1380 foreach (c; r)
1382 buf[i] = c;
1383 ++i;
1385 pos = i;
1387 static U trustedCast(U, V)(V v) @trusted pure nothrow { return cast(U) v; }
1388 return trustedCast!(typeof(return))(buf[0 .. pos]);
1391 /// ditto
1392 immutable(C)[] buildPath(C)(const(C)[][] paths...)
1393 @safe pure nothrow
1394 if (isSomeChar!C)
1396 return buildPath!(typeof(paths))(paths);
1400 @safe unittest
1402 version (Posix)
1404 assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1405 assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
1406 assert(buildPath("/foo", "/bar") == "/bar");
1409 version (Windows)
1411 assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1412 assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1413 assert(buildPath("foo", `d:\bar`) == `d:\bar`);
1414 assert(buildPath("foo", `\bar`) == `\bar`);
1415 assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
1419 @system unittest // non-documented
1421 import std.range;
1422 // ir() wraps an array in a plain (i.e. non-forward) input range, so that
1423 // we can test both code paths
1424 InputRange!(C[]) ir(C)(C[][] p...) { return inputRangeObject(p); }
1425 version (Posix)
1427 assert(buildPath("foo") == "foo");
1428 assert(buildPath("/foo/") == "/foo/");
1429 assert(buildPath("foo", "bar") == "foo/bar");
1430 assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1431 assert(buildPath("foo/".dup, "bar") == "foo/bar");
1432 assert(buildPath("foo///", "bar".dup) == "foo///bar");
1433 assert(buildPath("/foo"w, "bar"w) == "/foo/bar");
1434 assert(buildPath("foo"w.dup, "/bar"w) == "/bar");
1435 assert(buildPath("foo"w, "bar/"w.dup) == "foo/bar/");
1436 assert(buildPath("/"d, "foo"d) == "/foo");
1437 assert(buildPath(""d.dup, "foo"d) == "foo");
1438 assert(buildPath("foo"d, ""d.dup) == "foo");
1439 assert(buildPath("foo", "bar".dup, "baz") == "foo/bar/baz");
1440 assert(buildPath("foo"w, "/bar"w, "baz"w.dup) == "/bar/baz");
1442 static assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1443 static assert(buildPath("foo", "/bar", "baz") == "/bar/baz");
1445 // The following are mostly duplicates of the above, except that the
1446 // range version does not accept mixed constness.
1447 assert(buildPath(ir("foo")) == "foo");
1448 assert(buildPath(ir("/foo/")) == "/foo/");
1449 assert(buildPath(ir("foo", "bar")) == "foo/bar");
1450 assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1451 assert(buildPath(ir("foo/".dup, "bar".dup)) == "foo/bar");
1452 assert(buildPath(ir("foo///".dup, "bar".dup)) == "foo///bar");
1453 assert(buildPath(ir("/foo"w, "bar"w)) == "/foo/bar");
1454 assert(buildPath(ir("foo"w.dup, "/bar"w.dup)) == "/bar");
1455 assert(buildPath(ir("foo"w.dup, "bar/"w.dup)) == "foo/bar/");
1456 assert(buildPath(ir("/"d, "foo"d)) == "/foo");
1457 assert(buildPath(ir(""d.dup, "foo"d.dup)) == "foo");
1458 assert(buildPath(ir("foo"d, ""d)) == "foo");
1459 assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1460 assert(buildPath(ir("foo"w.dup, "/bar"w.dup, "baz"w.dup)) == "/bar/baz");
1462 version (Windows)
1464 assert(buildPath("foo") == "foo");
1465 assert(buildPath(`\foo/`) == `\foo/`);
1466 assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1467 assert(buildPath("foo", `\bar`) == `\bar`);
1468 assert(buildPath(`c:\foo`, "bar") == `c:\foo\bar`);
1469 assert(buildPath("foo"w, `d:\bar`w.dup) == `d:\bar`);
1470 assert(buildPath(`c:\foo\bar`, `\baz`) == `c:\baz`);
1471 assert(buildPath(`\\foo\bar\baz`d, `foo`d, `\bar`d) == `\\foo\bar\bar`d);
1473 static assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1474 static assert(buildPath("foo", `c:\bar`, "baz") == `c:\bar\baz`);
1476 assert(buildPath(ir("foo")) == "foo");
1477 assert(buildPath(ir(`\foo/`)) == `\foo/`);
1478 assert(buildPath(ir("foo", "bar", "baz")) == `foo\bar\baz`);
1479 assert(buildPath(ir("foo", `\bar`)) == `\bar`);
1480 assert(buildPath(ir(`c:\foo`, "bar")) == `c:\foo\bar`);
1481 assert(buildPath(ir("foo"w.dup, `d:\bar`w.dup)) == `d:\bar`);
1482 assert(buildPath(ir(`c:\foo\bar`, `\baz`)) == `c:\baz`);
1483 assert(buildPath(ir(`\\foo\bar\baz`d, `foo`d, `\bar`d)) == `\\foo\bar\bar`d);
1486 // Test that allocation works as it should.
1487 auto manyShort = "aaa".repeat(1000).array();
1488 auto manyShortCombined = join(manyShort, dirSeparator);
1489 assert(buildPath(manyShort) == manyShortCombined);
1490 assert(buildPath(ir(manyShort)) == manyShortCombined);
1492 auto fewLong = 'b'.repeat(500).array().repeat(10).array();
1493 auto fewLongCombined = join(fewLong, dirSeparator);
1494 assert(buildPath(fewLong) == fewLongCombined);
1495 assert(buildPath(ir(fewLong)) == fewLongCombined);
1498 @safe unittest
1500 // Test for issue 7397
1501 string[] ary = ["a", "b"];
1502 version (Posix)
1504 assert(buildPath(ary) == "a/b");
1506 else version (Windows)
1508 assert(buildPath(ary) == `a\b`);
1514 * Concatenate path segments together to form one path.
1516 * Params:
1517 * r1 = first segment
1518 * r2 = second segment
1519 * ranges = 0 or more segments
1520 * Returns:
1521 * Lazy range which is the concatenation of r1, r2 and ranges with path separators.
1522 * The resulting element type is that of r1.
1523 * See_Also:
1524 * $(LREF buildPath)
1526 auto chainPath(R1, R2, Ranges...)(R1 r1, R2 r2, Ranges ranges)
1527 if ((isRandomAccessRange!R1 && hasSlicing!R1 && hasLength!R1 && isSomeChar!(ElementType!R1) ||
1528 isNarrowString!R1 &&
1529 !isConvertibleToString!R1) &&
1530 (isRandomAccessRange!R2 && hasSlicing!R2 && hasLength!R2 && isSomeChar!(ElementType!R2) ||
1531 isNarrowString!R2 &&
1532 !isConvertibleToString!R2) &&
1533 (Ranges.length == 0 || is(typeof(chainPath(r2, ranges))))
1536 static if (Ranges.length)
1538 return chainPath(chainPath(r1, r2), ranges);
1540 else
1542 import std.range : only, chain;
1543 import std.utf : byUTF;
1545 alias CR = Unqual!(ElementEncodingType!R1);
1546 auto sep = only(CR(dirSeparator[0]));
1547 bool usesep = false;
1549 auto pos = r1.length;
1551 if (pos)
1553 if (isRooted(r2))
1555 version (Posix)
1557 pos = 0;
1559 else version (Windows)
1561 if (isAbsolute(r2))
1562 pos = 0;
1563 else
1565 pos = rootName(r1).length;
1566 if (pos > 0 && isDirSeparator(r1[pos - 1]))
1567 --pos;
1570 else
1571 static assert(0);
1573 else if (!isDirSeparator(r1[pos - 1]))
1574 usesep = true;
1576 if (!usesep)
1577 sep.popFront();
1578 // Return r1 ~ '/' ~ r2
1579 return chain(r1[0 .. pos].byUTF!CR, sep, r2.byUTF!CR);
1584 @safe unittest
1586 import std.array;
1587 version (Posix)
1589 assert(chainPath("foo", "bar", "baz").array == "foo/bar/baz");
1590 assert(chainPath("/foo/", "bar/baz").array == "/foo/bar/baz");
1591 assert(chainPath("/foo", "/bar").array == "/bar");
1594 version (Windows)
1596 assert(chainPath("foo", "bar", "baz").array == `foo\bar\baz`);
1597 assert(chainPath(`c:\foo`, `bar\baz`).array == `c:\foo\bar\baz`);
1598 assert(chainPath("foo", `d:\bar`).array == `d:\bar`);
1599 assert(chainPath("foo", `\bar`).array == `\bar`);
1600 assert(chainPath(`c:\foo`, `\bar`).array == `c:\bar`);
1603 import std.utf : byChar;
1604 version (Posix)
1606 assert(chainPath("foo", "bar", "baz").array == "foo/bar/baz");
1607 assert(chainPath("/foo/".byChar, "bar/baz").array == "/foo/bar/baz");
1608 assert(chainPath("/foo", "/bar".byChar).array == "/bar");
1611 version (Windows)
1613 assert(chainPath("foo", "bar", "baz").array == `foo\bar\baz`);
1614 assert(chainPath(`c:\foo`.byChar, `bar\baz`).array == `c:\foo\bar\baz`);
1615 assert(chainPath("foo", `d:\bar`).array == `d:\bar`);
1616 assert(chainPath("foo", `\bar`.byChar).array == `\bar`);
1617 assert(chainPath(`c:\foo`, `\bar`w).array == `c:\bar`);
1621 auto chainPath(Ranges...)(auto ref Ranges ranges)
1622 if (Ranges.length >= 2 &&
1623 std.meta.anySatisfy!(isConvertibleToString, Ranges))
1625 import std.meta : staticMap;
1626 alias Types = staticMap!(convertToString, Ranges);
1627 return chainPath!Types(ranges);
1630 @safe unittest
1632 assert(chainPath(TestAliasedString(null), TestAliasedString(null), TestAliasedString(null)).empty);
1633 assert(chainPath(TestAliasedString(null), TestAliasedString(null), "").empty);
1634 assert(chainPath(TestAliasedString(null), "", TestAliasedString(null)).empty);
1635 static struct S { string s; }
1636 static assert(!__traits(compiles, chainPath(TestAliasedString(null), S(""), TestAliasedString(null))));
1639 /** Performs the same task as $(LREF buildPath),
1640 while at the same time resolving current/parent directory
1641 symbols ($(D ".") and $(D "..")) and removing superfluous
1642 directory separators.
1643 It will return "." if the path leads to the starting directory.
1644 On Windows, slashes are replaced with backslashes.
1646 Using buildNormalizedPath on null paths will always return null.
1648 Note that this function does not resolve symbolic links.
1650 This function always allocates memory to hold the resulting path.
1651 Use $(LREF asNormalizedPath) to not allocate memory.
1653 Params:
1654 paths = An array of paths to assemble.
1656 Returns: The assembled path.
1658 immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...)
1659 @trusted pure nothrow
1660 if (isSomeChar!C)
1662 import std.array : array;
1664 const(C)[] result;
1665 foreach (path; paths)
1667 if (result)
1668 result = chainPath(result, path).array;
1669 else
1670 result = path;
1672 result = asNormalizedPath(result).array;
1673 return cast(typeof(return)) result;
1677 @safe unittest
1679 assert(buildNormalizedPath("foo", "..") == ".");
1681 version (Posix)
1683 assert(buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz");
1684 assert(buildNormalizedPath("../foo/.") == "../foo");
1685 assert(buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
1686 assert(buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
1687 assert(buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
1688 assert(buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
1691 version (Windows)
1693 assert(buildNormalizedPath(`c:\foo\.\bar/..\\baz\`) == `c:\foo\baz`);
1694 assert(buildNormalizedPath(`..\foo\.`) == `..\foo`);
1695 assert(buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
1696 assert(buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
1697 assert(buildNormalizedPath(`\\server\share\foo`, `..\bar`) ==
1698 `\\server\share\bar`);
1702 @safe unittest
1704 assert(buildNormalizedPath(".", ".") == ".");
1705 assert(buildNormalizedPath("foo", "..") == ".");
1706 assert(buildNormalizedPath("", "") is null);
1707 assert(buildNormalizedPath("", ".") == ".");
1708 assert(buildNormalizedPath(".", "") == ".");
1709 assert(buildNormalizedPath(null, "foo") == "foo");
1710 assert(buildNormalizedPath("", "foo") == "foo");
1711 assert(buildNormalizedPath("", "") == "");
1712 assert(buildNormalizedPath("", null) == "");
1713 assert(buildNormalizedPath(null, "") == "");
1714 assert(buildNormalizedPath!(char)(null, null) == "");
1716 version (Posix)
1718 assert(buildNormalizedPath("/", "foo", "bar") == "/foo/bar");
1719 assert(buildNormalizedPath("foo", "bar", "baz") == "foo/bar/baz");
1720 assert(buildNormalizedPath("foo", "bar/baz") == "foo/bar/baz");
1721 assert(buildNormalizedPath("foo", "bar//baz///") == "foo/bar/baz");
1722 assert(buildNormalizedPath("/foo", "bar/baz") == "/foo/bar/baz");
1723 assert(buildNormalizedPath("/foo", "/bar/baz") == "/bar/baz");
1724 assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1725 assert(buildNormalizedPath("/foo/..", "bar/baz") == "/bar/baz");
1726 assert(buildNormalizedPath("/foo/../../", "bar/baz") == "/bar/baz");
1727 assert(buildNormalizedPath("/foo/bar", "../baz") == "/foo/baz");
1728 assert(buildNormalizedPath("/foo/bar", "../../baz") == "/baz");
1729 assert(buildNormalizedPath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee");
1730 assert(buildNormalizedPath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee");
1731 static assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1733 else version (Windows)
1735 assert(buildNormalizedPath(`\`, `foo`, `bar`) == `\foo\bar`);
1736 assert(buildNormalizedPath(`foo`, `bar`, `baz`) == `foo\bar\baz`);
1737 assert(buildNormalizedPath(`foo`, `bar\baz`) == `foo\bar\baz`);
1738 assert(buildNormalizedPath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`);
1739 assert(buildNormalizedPath(`\foo`, `bar\baz`) == `\foo\bar\baz`);
1740 assert(buildNormalizedPath(`\foo`, `\bar\baz`) == `\bar\baz`);
1741 assert(buildNormalizedPath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`);
1742 assert(buildNormalizedPath(`\foo\..`, `bar\baz`) == `\bar\baz`);
1743 assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1744 assert(buildNormalizedPath(`\foo\bar`, `..\baz`) == `\foo\baz`);
1745 assert(buildNormalizedPath(`\foo\bar`, `../../baz`) == `\baz`);
1746 assert(buildNormalizedPath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`);
1748 assert(buildNormalizedPath(`c:\`, `foo`, `bar`) == `c:\foo\bar`);
1749 assert(buildNormalizedPath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`);
1750 assert(buildNormalizedPath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`);
1751 assert(buildNormalizedPath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`);
1752 assert(buildNormalizedPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1753 assert(buildNormalizedPath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`);
1754 assert(buildNormalizedPath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`);
1755 assert(buildNormalizedPath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`);
1756 assert(buildNormalizedPath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`);
1757 assert(buildNormalizedPath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`);
1758 assert(buildNormalizedPath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`);
1759 assert(buildNormalizedPath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`);
1761 assert(buildNormalizedPath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`);
1762 assert(buildNormalizedPath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`);
1763 assert(buildNormalizedPath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`);
1764 assert(buildNormalizedPath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`);
1765 assert(buildNormalizedPath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`);
1766 assert(buildNormalizedPath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`);
1767 assert(buildNormalizedPath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`);
1768 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`);
1769 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`);
1770 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`);
1772 static assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1774 else static assert(0);
1777 @safe unittest
1779 // Test for issue 7397
1780 string[] ary = ["a", "b"];
1781 version (Posix)
1783 assert(buildNormalizedPath(ary) == "a/b");
1785 else version (Windows)
1787 assert(buildNormalizedPath(ary) == `a\b`);
1792 /** Normalize a path by resolving current/parent directory
1793 symbols ($(D ".") and $(D "..")) and removing superfluous
1794 directory separators.
1795 It will return "." if the path leads to the starting directory.
1796 On Windows, slashes are replaced with backslashes.
1798 Using asNormalizedPath on empty paths will always return an empty path.
1800 Does not resolve symbolic links.
1802 This function always allocates memory to hold the resulting path.
1803 Use $(LREF buildNormalizedPath) to allocate memory and return a string.
1805 Params:
1806 path = string or random access range representing the _path to normalize
1808 Returns:
1809 normalized path as a forward range
1812 auto asNormalizedPath(R)(R path)
1813 if (isSomeChar!(ElementEncodingType!R) &&
1814 (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R) &&
1815 !isConvertibleToString!R)
1817 alias C = Unqual!(ElementEncodingType!R);
1818 alias S = typeof(path[0 .. 0]);
1820 static struct Result
1822 @property bool empty()
1824 return c == c.init;
1827 @property C front()
1829 return c;
1832 void popFront()
1834 C lastc = c;
1835 c = c.init;
1836 if (!element.empty)
1838 c = getElement0();
1839 return;
1842 while (1)
1844 if (elements.empty)
1846 element = element[0 .. 0];
1847 return;
1849 element = elements.front;
1850 elements.popFront();
1851 if (isDot(element) || (rooted && isDotDot(element)))
1852 continue;
1854 if (rooted || !isDotDot(element))
1856 int n = 1;
1857 auto elements2 = elements.save;
1858 while (!elements2.empty)
1860 auto e = elements2.front;
1861 elements2.popFront();
1862 if (isDot(e))
1863 continue;
1864 if (isDotDot(e))
1866 --n;
1867 if (n == 0)
1869 elements = elements2;
1870 element = element[0 .. 0];
1871 continue L1;
1874 else
1875 ++n;
1878 break;
1881 static assert(dirSeparator.length == 1);
1882 if (lastc == dirSeparator[0] || lastc == lastc.init)
1883 c = getElement0();
1884 else
1885 c = dirSeparator[0];
1888 static if (isForwardRange!R)
1890 @property auto save()
1892 auto result = this;
1893 result.element = element.save;
1894 result.elements = elements.save;
1895 return result;
1899 private:
1900 this(R path)
1902 element = rootName(path);
1903 auto i = element.length;
1904 while (i < path.length && isDirSeparator(path[i]))
1905 ++i;
1906 rooted = i > 0;
1907 elements = pathSplitter(path[i .. $]);
1908 popFront();
1909 if (c == c.init && path.length)
1910 c = C('.');
1913 C getElement0()
1915 static if (isNarrowString!S) // avoid autodecode
1917 C c = element[0];
1918 element = element[1 .. $];
1920 else
1922 C c = element.front;
1923 element.popFront();
1925 version (Windows)
1927 if (c == '/') // can appear in root element
1928 c = '\\'; // use native Windows directory separator
1930 return c;
1933 // See if elem is "."
1934 static bool isDot(S elem)
1936 return elem.length == 1 && elem[0] == '.';
1939 // See if elem is ".."
1940 static bool isDotDot(S elem)
1942 return elem.length == 2 && elem[0] == '.' && elem[1] == '.';
1945 bool rooted; // the path starts with a root directory
1946 C c;
1947 S element;
1948 typeof(pathSplitter(path[0 .. 0])) elements;
1951 return Result(path);
1955 @safe unittest
1957 import std.array;
1958 assert(asNormalizedPath("foo/..").array == ".");
1960 version (Posix)
1962 assert(asNormalizedPath("/foo/./bar/..//baz/").array == "/foo/baz");
1963 assert(asNormalizedPath("../foo/.").array == "../foo");
1964 assert(asNormalizedPath("/foo/bar/baz/").array == "/foo/bar/baz");
1965 assert(asNormalizedPath("/foo/./bar/../../baz").array == "/baz");
1968 version (Windows)
1970 assert(asNormalizedPath(`c:\foo\.\bar/..\\baz\`).array == `c:\foo\baz`);
1971 assert(asNormalizedPath(`..\foo\.`).array == `..\foo`);
1972 assert(asNormalizedPath(`c:\foo\bar\baz\`).array == `c:\foo\bar\baz`);
1973 assert(asNormalizedPath(`c:\foo\bar/..`).array == `c:\foo`);
1974 assert(asNormalizedPath(`\\server\share\foo\..\bar`).array ==
1975 `\\server\share\bar`);
1979 auto asNormalizedPath(R)(auto ref R path)
1980 if (isConvertibleToString!R)
1982 return asNormalizedPath!(StringTypeOf!R)(path);
1985 @safe unittest
1987 assert(testAliasedString!asNormalizedPath(null));
1990 @safe unittest
1992 import std.array;
1993 import std.utf : byChar;
1995 assert(asNormalizedPath("").array is null);
1996 assert(asNormalizedPath("foo").array == "foo");
1997 assert(asNormalizedPath(".").array == ".");
1998 assert(asNormalizedPath("./.").array == ".");
1999 assert(asNormalizedPath("foo/..").array == ".");
2001 auto save = asNormalizedPath("fob").save;
2002 save.popFront();
2003 assert(save.front == 'o');
2005 version (Posix)
2007 assert(asNormalizedPath("/foo/bar").array == "/foo/bar");
2008 assert(asNormalizedPath("foo/bar/baz").array == "foo/bar/baz");
2009 assert(asNormalizedPath("foo/bar/baz").array == "foo/bar/baz");
2010 assert(asNormalizedPath("foo/bar//baz///").array == "foo/bar/baz");
2011 assert(asNormalizedPath("/foo/bar/baz").array == "/foo/bar/baz");
2012 assert(asNormalizedPath("/foo/../bar/baz").array == "/bar/baz");
2013 assert(asNormalizedPath("/foo/../..//bar/baz").array == "/bar/baz");
2014 assert(asNormalizedPath("/foo/bar/../baz").array == "/foo/baz");
2015 assert(asNormalizedPath("/foo/bar/../../baz").array == "/baz");
2016 assert(asNormalizedPath("/foo/bar/.././/baz/../wee/").array == "/foo/wee");
2017 assert(asNormalizedPath("//foo/bar/baz///wee").array == "/foo/bar/baz/wee");
2019 assert(asNormalizedPath("foo//bar").array == "foo/bar");
2020 assert(asNormalizedPath("foo/bar").array == "foo/bar");
2022 //Curent dir path
2023 assert(asNormalizedPath("./").array == ".");
2024 assert(asNormalizedPath("././").array == ".");
2025 assert(asNormalizedPath("./foo/..").array == ".");
2026 assert(asNormalizedPath("foo/..").array == ".");
2028 else version (Windows)
2030 assert(asNormalizedPath(`\foo\bar`).array == `\foo\bar`);
2031 assert(asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`);
2032 assert(asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`);
2033 assert(asNormalizedPath(`foo\bar\\baz\\\`).array == `foo\bar\baz`);
2034 assert(asNormalizedPath(`\foo\bar\baz`).array == `\foo\bar\baz`);
2035 assert(asNormalizedPath(`\foo\..\\bar\.\baz`).array == `\bar\baz`);
2036 assert(asNormalizedPath(`\foo\..\bar\baz`).array == `\bar\baz`);
2037 assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`);
2039 assert(asNormalizedPath(`\foo\bar\..\baz`).array == `\foo\baz`);
2040 assert(asNormalizedPath(`\foo\bar\../../baz`).array == `\baz`);
2041 assert(asNormalizedPath(`\foo\bar\..\.\/baz\..\wee\`).array == `\foo\wee`);
2043 assert(asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`);
2044 assert(asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`);
2045 assert(asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`);
2046 assert(asNormalizedPath(`c:foo\bar\\baz\\\`).array == `c:foo\bar\baz`);
2047 assert(asNormalizedPath(`c:\foo\bar\baz`).array == `c:\foo\bar\baz`);
2049 assert(asNormalizedPath(`c:\foo\..\\bar\.\baz`).array == `c:\bar\baz`);
2050 assert(asNormalizedPath(`c:\foo\..\bar\baz`).array == `c:\bar\baz`);
2051 assert(asNormalizedPath(`c:\foo\..\..\\bar\baz`).array == `c:\bar\baz`);
2052 assert(asNormalizedPath(`c:\foo\bar\..\baz`).array == `c:\foo\baz`);
2053 assert(asNormalizedPath(`c:\foo\bar\..\..\baz`).array == `c:\baz`);
2054 assert(asNormalizedPath(`c:\foo\bar\..\.\\baz\..\wee\`).array == `c:\foo\wee`);
2055 assert(asNormalizedPath(`\\server\share\foo\bar`).array == `\\server\share\foo\bar`);
2056 assert(asNormalizedPath(`\\server\share\\foo\bar`).array == `\\server\share\foo\bar`);
2057 assert(asNormalizedPath(`\\server\share\foo\bar\baz`).array == `\\server\share\foo\bar\baz`);
2058 assert(asNormalizedPath(`\\server\share\foo\..\\bar\.\baz`).array == `\\server\share\bar\baz`);
2059 assert(asNormalizedPath(`\\server\share\foo\..\bar\baz`).array == `\\server\share\bar\baz`);
2060 assert(asNormalizedPath(`\\server\share\foo\..\..\\bar\baz`).array == `\\server\share\bar\baz`);
2061 assert(asNormalizedPath(`\\server\share\foo\bar\..\baz`).array == `\\server\share\foo\baz`);
2062 assert(asNormalizedPath(`\\server\share\foo\bar\..\..\baz`).array == `\\server\share\baz`);
2063 assert(asNormalizedPath(`\\server\share\foo\bar\..\.\\baz\..\wee\`).array == `\\server\share\foo\wee`);
2065 static assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`);
2067 assert(asNormalizedPath("foo//bar").array == `foo\bar`);
2069 //Curent dir path
2070 assert(asNormalizedPath(`.\`).array == ".");
2071 assert(asNormalizedPath(`.\.\`).array == ".");
2072 assert(asNormalizedPath(`.\foo\..`).array == ".");
2073 assert(asNormalizedPath(`foo\..`).array == ".");
2075 else static assert(0);
2078 @safe unittest
2080 import std.array;
2082 version (Posix)
2084 // Trivial
2085 assert(asNormalizedPath("").empty);
2086 assert(asNormalizedPath("foo/bar").array == "foo/bar");
2088 // Correct handling of leading slashes
2089 assert(asNormalizedPath("/").array == "/");
2090 assert(asNormalizedPath("///").array == "/");
2091 assert(asNormalizedPath("////").array == "/");
2092 assert(asNormalizedPath("/foo/bar").array == "/foo/bar");
2093 assert(asNormalizedPath("//foo/bar").array == "/foo/bar");
2094 assert(asNormalizedPath("///foo/bar").array == "/foo/bar");
2095 assert(asNormalizedPath("////foo/bar").array == "/foo/bar");
2097 // Correct handling of single-dot symbol (current directory)
2098 assert(asNormalizedPath("/./foo").array == "/foo");
2099 assert(asNormalizedPath("/foo/./bar").array == "/foo/bar");
2101 assert(asNormalizedPath("./foo").array == "foo");
2102 assert(asNormalizedPath("././foo").array == "foo");
2103 assert(asNormalizedPath("foo/././bar").array == "foo/bar");
2105 // Correct handling of double-dot symbol (previous directory)
2106 assert(asNormalizedPath("/foo/../bar").array == "/bar");
2107 assert(asNormalizedPath("/foo/../../bar").array == "/bar");
2108 assert(asNormalizedPath("/../foo").array == "/foo");
2109 assert(asNormalizedPath("/../../foo").array == "/foo");
2110 assert(asNormalizedPath("/foo/..").array == "/");
2111 assert(asNormalizedPath("/foo/../..").array == "/");
2113 assert(asNormalizedPath("foo/../bar").array == "bar");
2114 assert(asNormalizedPath("foo/../../bar").array == "../bar");
2115 assert(asNormalizedPath("../foo").array == "../foo");
2116 assert(asNormalizedPath("../../foo").array == "../../foo");
2117 assert(asNormalizedPath("../foo/../bar").array == "../bar");
2118 assert(asNormalizedPath(".././../foo").array == "../../foo");
2119 assert(asNormalizedPath("foo/bar/..").array == "foo");
2120 assert(asNormalizedPath("/foo/../..").array == "/");
2122 // The ultimate path
2123 assert(asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz");
2124 static assert(asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz");
2126 else version (Windows)
2128 // Trivial
2129 assert(asNormalizedPath("").empty);
2130 assert(asNormalizedPath(`foo\bar`).array == `foo\bar`);
2131 assert(asNormalizedPath("foo/bar").array == `foo\bar`);
2133 // Correct handling of absolute paths
2134 assert(asNormalizedPath("/").array == `\`);
2135 assert(asNormalizedPath(`\`).array == `\`);
2136 assert(asNormalizedPath(`\\\`).array == `\`);
2137 assert(asNormalizedPath(`\\\\`).array == `\`);
2138 assert(asNormalizedPath(`\foo\bar`).array == `\foo\bar`);
2139 assert(asNormalizedPath(`\\foo`).array == `\\foo`);
2140 assert(asNormalizedPath(`\\foo\\`).array == `\\foo`);
2141 assert(asNormalizedPath(`\\foo/bar`).array == `\\foo\bar`);
2142 assert(asNormalizedPath(`\\\foo\bar`).array == `\foo\bar`);
2143 assert(asNormalizedPath(`\\\\foo\bar`).array == `\foo\bar`);
2144 assert(asNormalizedPath(`c:\`).array == `c:\`);
2145 assert(asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`);
2146 assert(asNormalizedPath(`c:\\foo\bar`).array == `c:\foo\bar`);
2148 // Correct handling of single-dot symbol (current directory)
2149 assert(asNormalizedPath(`\./foo`).array == `\foo`);
2150 assert(asNormalizedPath(`\foo/.\bar`).array == `\foo\bar`);
2152 assert(asNormalizedPath(`.\foo`).array == `foo`);
2153 assert(asNormalizedPath(`./.\foo`).array == `foo`);
2154 assert(asNormalizedPath(`foo\.\./bar`).array == `foo\bar`);
2156 // Correct handling of double-dot symbol (previous directory)
2157 assert(asNormalizedPath(`\foo\..\bar`).array == `\bar`);
2158 assert(asNormalizedPath(`\foo\../..\bar`).array == `\bar`);
2159 assert(asNormalizedPath(`\..\foo`).array == `\foo`);
2160 assert(asNormalizedPath(`\..\..\foo`).array == `\foo`);
2161 assert(asNormalizedPath(`\foo\..`).array == `\`);
2162 assert(asNormalizedPath(`\foo\../..`).array == `\`);
2164 assert(asNormalizedPath(`foo\..\bar`).array == `bar`);
2165 assert(asNormalizedPath(`foo\..\../bar`).array == `..\bar`);
2167 assert(asNormalizedPath(`..\foo`).array == `..\foo`);
2168 assert(asNormalizedPath(`..\..\foo`).array == `..\..\foo`);
2169 assert(asNormalizedPath(`..\foo\..\bar`).array == `..\bar`);
2170 assert(asNormalizedPath(`..\.\..\foo`).array == `..\..\foo`);
2171 assert(asNormalizedPath(`foo\bar\..`).array == `foo`);
2172 assert(asNormalizedPath(`\foo\..\..`).array == `\`);
2173 assert(asNormalizedPath(`c:\foo\..\..`).array == `c:\`);
2175 // Correct handling of non-root path with drive specifier
2176 assert(asNormalizedPath(`c:foo`).array == `c:foo`);
2177 assert(asNormalizedPath(`c:..\foo\.\..\bar`).array == `c:..\bar`);
2179 // The ultimate path
2180 assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`);
2181 static assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`);
2183 else static assert(false);
2186 /** Slice up a path into its elements.
2188 Params:
2189 path = string or slicable random access range
2191 Returns:
2192 bidirectional range of slices of `path`
2194 auto pathSplitter(R)(R path)
2195 if ((isRandomAccessRange!R && hasSlicing!R ||
2196 isNarrowString!R) &&
2197 !isConvertibleToString!R)
2199 static struct PathSplitter
2201 @property bool empty() const { return pe == 0; }
2203 @property R front()
2205 assert(!empty);
2206 return _path[fs .. fe];
2209 void popFront()
2211 assert(!empty);
2212 if (ps == pe)
2214 if (fs == bs && fe == be)
2216 pe = 0;
2218 else
2220 fs = bs;
2221 fe = be;
2224 else
2226 fs = ps;
2227 fe = fs;
2228 while (fe < pe && !isDirSeparator(_path[fe]))
2229 ++fe;
2230 ps = ltrim(fe, pe);
2234 @property R back()
2236 assert(!empty);
2237 return _path[bs .. be];
2240 void popBack()
2242 assert(!empty);
2243 if (ps == pe)
2245 if (fs == bs && fe == be)
2247 pe = 0;
2249 else
2251 bs = fs;
2252 be = fe;
2255 else
2257 bs = pe;
2258 be = bs;
2259 while (bs > ps && !isDirSeparator(_path[bs - 1]))
2260 --bs;
2261 pe = rtrim(ps, bs);
2264 @property auto save() { return this; }
2267 private:
2268 R _path;
2269 size_t ps, pe;
2270 size_t fs, fe;
2271 size_t bs, be;
2273 this(R p)
2275 if (p.empty)
2277 pe = 0;
2278 return;
2280 _path = p;
2282 ps = 0;
2283 pe = _path.length;
2285 // If path is rooted, first element is special
2286 version (Windows)
2288 if (isUNC(_path))
2290 auto i = uncRootLength(_path);
2291 fs = 0;
2292 fe = i;
2293 ps = ltrim(fe, pe);
2295 else if (isDriveRoot(_path))
2297 fs = 0;
2298 fe = 3;
2299 ps = ltrim(fe, pe);
2301 else if (_path.length >= 1 && isDirSeparator(_path[0]))
2303 fs = 0;
2304 fe = 1;
2305 ps = ltrim(fe, pe);
2307 else
2309 assert(!isRooted(_path));
2310 popFront();
2313 else version (Posix)
2315 if (_path.length >= 1 && isDirSeparator(_path[0]))
2317 fs = 0;
2318 fe = 1;
2319 ps = ltrim(fe, pe);
2321 else
2323 popFront();
2326 else static assert(0);
2328 if (ps == pe)
2330 bs = fs;
2331 be = fe;
2333 else
2335 pe = rtrim(ps, pe);
2336 popBack();
2340 size_t ltrim(size_t s, size_t e)
2342 while (s < e && isDirSeparator(_path[s]))
2343 ++s;
2344 return s;
2347 size_t rtrim(size_t s, size_t e)
2349 while (s < e && isDirSeparator(_path[e - 1]))
2350 --e;
2351 return e;
2355 return PathSplitter(path);
2359 @safe unittest
2361 import std.algorithm.comparison : equal;
2362 import std.conv : to;
2364 assert(equal(pathSplitter("/"), ["/"]));
2365 assert(equal(pathSplitter("/foo/bar"), ["/", "foo", "bar"]));
2366 assert(equal(pathSplitter("foo/../bar//./"), ["foo", "..", "bar", "."]));
2368 version (Posix)
2370 assert(equal(pathSplitter("//foo/bar"), ["/", "foo", "bar"]));
2373 version (Windows)
2375 assert(equal(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
2376 assert(equal(pathSplitter("c:"), ["c:"]));
2377 assert(equal(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
2378 assert(equal(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
2382 auto pathSplitter(R)(auto ref R path)
2383 if (isConvertibleToString!R)
2385 return pathSplitter!(StringTypeOf!R)(path);
2388 @safe unittest
2390 import std.algorithm.comparison : equal;
2391 assert(testAliasedString!pathSplitter("/"));
2394 @safe unittest
2396 // equal2 verifies that the range is the same both ways, i.e.
2397 // through front/popFront and back/popBack.
2398 import std.algorithm;
2399 import std.range;
2400 bool equal2(R1, R2)(R1 r1, R2 r2)
2402 static assert(isBidirectionalRange!R1);
2403 return equal(r1, r2) && equal(retro(r1), retro(r2));
2406 assert(pathSplitter("").empty);
2408 // Root directories
2409 assert(equal2(pathSplitter("/"), ["/"]));
2410 assert(equal2(pathSplitter("//"), ["/"]));
2411 assert(equal2(pathSplitter("///"w), ["/"w]));
2413 // Absolute paths
2414 assert(equal2(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"]));
2416 // General
2417 assert(equal2(pathSplitter("foo/bar"d.dup), ["foo"d, "bar"d]));
2418 assert(equal2(pathSplitter("foo//bar"), ["foo", "bar"]));
2419 assert(equal2(pathSplitter("foo/bar//"w), ["foo"w, "bar"w]));
2420 assert(equal2(pathSplitter("foo/../bar//./"d), ["foo"d, ".."d, "bar"d, "."d]));
2422 // save()
2423 auto ps1 = pathSplitter("foo/bar/baz");
2424 auto ps2 = ps1.save;
2425 ps1.popFront();
2426 assert(equal2(ps1, ["bar", "baz"]));
2427 assert(equal2(ps2, ["foo", "bar", "baz"]));
2429 // Platform specific
2430 version (Posix)
2432 assert(equal2(pathSplitter("//foo/bar"w.dup), ["/"w, "foo"w, "bar"w]));
2434 version (Windows)
2436 assert(equal2(pathSplitter(`\`), [`\`]));
2437 assert(equal2(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
2438 assert(equal2(pathSplitter("c:"), ["c:"]));
2439 assert(equal2(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
2440 assert(equal2(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
2441 assert(equal2(pathSplitter(`\\foo\bar`), [`\\foo\bar`]));
2442 assert(equal2(pathSplitter(`\\foo\bar\\`), [`\\foo\bar`]));
2443 assert(equal2(pathSplitter(`\\foo\bar\baz`), [`\\foo\bar`, "baz"]));
2446 import std.exception;
2447 assertCTFEable!(
2449 assert(equal(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"]));
2452 static assert(is(typeof(pathSplitter!(const(char)[])(null).front) == const(char)[]));
2454 import std.utf : byDchar;
2455 assert(equal2(pathSplitter("foo/bar"d.byDchar), ["foo"d, "bar"d]));
2461 /** Determines whether a path starts at a root directory.
2463 Params: path = A path name.
2464 Returns: Whether a path starts at a root directory.
2466 On POSIX, this function returns true if and only if the path starts
2467 with a slash (/).
2469 version (Posix)
2471 assert(isRooted("/"));
2472 assert(isRooted("/foo"));
2473 assert(!isRooted("foo"));
2474 assert(!isRooted("../foo"));
2478 On Windows, this function returns true if the path starts at
2479 the root directory of the current drive, of some other drive,
2480 or of a network drive.
2482 version (Windows)
2484 assert(isRooted(`\`));
2485 assert(isRooted(`\foo`));
2486 assert(isRooted(`d:\foo`));
2487 assert(isRooted(`\\foo\bar`));
2488 assert(!isRooted("foo"));
2489 assert(!isRooted("d:foo"));
2493 bool isRooted(R)(R path)
2494 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2495 is(StringTypeOf!R))
2497 if (path.length >= 1 && isDirSeparator(path[0])) return true;
2498 version (Posix) return false;
2499 else version (Windows) return isAbsolute!(BaseOf!R)(path);
2503 @safe unittest
2505 assert(isRooted("/"));
2506 assert(isRooted("/foo"));
2507 assert(!isRooted("foo"));
2508 assert(!isRooted("../foo"));
2510 version (Windows)
2512 assert(isRooted(`\`));
2513 assert(isRooted(`\foo`));
2514 assert(isRooted(`d:\foo`));
2515 assert(isRooted(`\\foo\bar`));
2516 assert(!isRooted("foo"));
2517 assert(!isRooted("d:foo"));
2520 static assert(isRooted("/foo"));
2521 static assert(!isRooted("foo"));
2523 static struct DirEntry { string s; alias s this; }
2524 assert(!isRooted(DirEntry("foo")));
2530 /** Determines whether a path is absolute or not.
2532 Params: path = A path name.
2534 Returns: Whether a path is absolute or not.
2536 Example:
2537 On POSIX, an absolute path starts at the root directory.
2538 (In fact, $(D _isAbsolute) is just an alias for $(LREF isRooted).)
2540 version (Posix)
2542 assert(isAbsolute("/"));
2543 assert(isAbsolute("/foo"));
2544 assert(!isAbsolute("foo"));
2545 assert(!isAbsolute("../foo"));
2549 On Windows, an absolute path starts at the root directory of
2550 a specific drive. Hence, it must start with $(D `d:\`) or $(D `d:/`),
2551 where $(D d) is the drive letter. Alternatively, it may be a
2552 network path, i.e. a path starting with a double (back)slash.
2554 version (Windows)
2556 assert(isAbsolute(`d:\`));
2557 assert(isAbsolute(`d:\foo`));
2558 assert(isAbsolute(`\\foo\bar`));
2559 assert(!isAbsolute(`\`));
2560 assert(!isAbsolute(`\foo`));
2561 assert(!isAbsolute("d:foo"));
2565 version (StdDdoc)
2567 bool isAbsolute(R)(R path) pure nothrow @safe
2568 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2569 is(StringTypeOf!R));
2571 else version (Windows)
2573 bool isAbsolute(R)(R path)
2574 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2575 is(StringTypeOf!R))
2577 return isDriveRoot!(BaseOf!R)(path) || isUNC!(BaseOf!R)(path);
2580 else version (Posix)
2582 alias isAbsolute = isRooted;
2586 @safe unittest
2588 assert(!isAbsolute("foo"));
2589 assert(!isAbsolute("../foo"w));
2590 static assert(!isAbsolute("foo"));
2592 version (Posix)
2594 assert(isAbsolute("/"d));
2595 assert(isAbsolute("/foo".dup));
2596 static assert(isAbsolute("/foo"));
2599 version (Windows)
2601 assert(isAbsolute("d:\\"w));
2602 assert(isAbsolute("d:\\foo"d));
2603 assert(isAbsolute("\\\\foo\\bar"));
2604 assert(!isAbsolute("\\"w.dup));
2605 assert(!isAbsolute("\\foo"d.dup));
2606 assert(!isAbsolute("d:"));
2607 assert(!isAbsolute("d:foo"));
2608 static assert(isAbsolute(`d:\foo`));
2612 auto r = MockRange!(immutable(char))(`../foo`);
2613 assert(!r.isAbsolute());
2616 static struct DirEntry { string s; alias s this; }
2617 assert(!isAbsolute(DirEntry("foo")));
2623 /** Transforms $(D path) into an absolute _path.
2625 The following algorithm is used:
2626 $(OL
2627 $(LI If $(D path) is empty, return $(D null).)
2628 $(LI If $(D path) is already absolute, return it.)
2629 $(LI Otherwise, append $(D path) to $(D base) and return
2630 the result. If $(D base) is not specified, the current
2631 working directory is used.)
2633 The function allocates memory if and only if it gets to the third stage
2634 of this algorithm.
2636 Params:
2637 path = the relative path to transform
2638 base = the base directory of the relative path
2640 Returns:
2641 string of transformed path
2643 Throws:
2644 $(D Exception) if the specified _base directory is not absolute.
2646 See_Also:
2647 $(LREF asAbsolutePath) which does not allocate
2649 string absolutePath(string path, lazy string base = getcwd())
2650 @safe pure
2652 import std.array : array;
2653 if (path.empty) return null;
2654 if (isAbsolute(path)) return path;
2655 auto baseVar = base;
2656 if (!isAbsolute(baseVar)) throw new Exception("Base directory must be absolute");
2657 return chainPath(baseVar, path).array;
2661 @safe unittest
2663 version (Posix)
2665 assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file");
2666 assert(absolutePath("../file", "/foo/bar") == "/foo/bar/../file");
2667 assert(absolutePath("/some/file", "/foo/bar") == "/some/file");
2670 version (Windows)
2672 assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2673 assert(absolutePath(`..\file`, `c:\foo\bar`) == `c:\foo\bar\..\file`);
2674 assert(absolutePath(`c:\some\file`, `c:\foo\bar`) == `c:\some\file`);
2675 assert(absolutePath(`\`, `c:\`) == `c:\`);
2676 assert(absolutePath(`\some\file`, `c:\foo\bar`) == `c:\some\file`);
2680 @safe unittest
2682 version (Posix)
2684 static assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file");
2687 version (Windows)
2689 static assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2692 import std.exception;
2693 assertThrown(absolutePath("bar", "foo"));
2696 /** Transforms $(D path) into an absolute _path.
2698 The following algorithm is used:
2699 $(OL
2700 $(LI If $(D path) is empty, return $(D null).)
2701 $(LI If $(D path) is already absolute, return it.)
2702 $(LI Otherwise, append $(D path) to the current working directory,
2703 which allocates memory.)
2706 Params:
2707 path = the relative path to transform
2709 Returns:
2710 the transformed path as a lazy range
2712 See_Also:
2713 $(LREF absolutePath) which returns an allocated string
2715 auto asAbsolutePath(R)(R path)
2716 if ((isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
2717 isNarrowString!R) &&
2718 !isConvertibleToString!R)
2720 import std.file : getcwd;
2721 string base = null;
2722 if (!path.empty && !isAbsolute(path))
2723 base = getcwd();
2724 return chainPath(base, path);
2728 @system unittest
2730 import std.array;
2731 assert(asAbsolutePath(cast(string) null).array == "");
2732 version (Posix)
2734 assert(asAbsolutePath("/foo").array == "/foo");
2736 version (Windows)
2738 assert(asAbsolutePath("c:/foo").array == "c:/foo");
2740 asAbsolutePath("foo");
2743 auto asAbsolutePath(R)(auto ref R path)
2744 if (isConvertibleToString!R)
2746 return asAbsolutePath!(StringTypeOf!R)(path);
2749 @system unittest
2751 assert(testAliasedString!asAbsolutePath(null));
2754 /** Translates $(D path) into a relative _path.
2756 The returned _path is relative to $(D base), which is by default
2757 taken to be the current working directory. If specified,
2758 $(D base) must be an absolute _path, and it is always assumed
2759 to refer to a directory. If $(D path) and $(D base) refer to
2760 the same directory, the function returns $(D `.`).
2762 The following algorithm is used:
2763 $(OL
2764 $(LI If $(D path) is a relative directory, return it unaltered.)
2765 $(LI Find a common root between $(D path) and $(D base).
2766 If there is no common root, return $(D path) unaltered.)
2767 $(LI Prepare a string with as many $(D `../`) or $(D `..\`) as
2768 necessary to reach the common root from base path.)
2769 $(LI Append the remaining segments of $(D path) to the string
2770 and return.)
2773 In the second step, path components are compared using $(D filenameCmp!cs),
2774 where $(D cs) is an optional template parameter determining whether
2775 the comparison is case sensitive or not. See the
2776 $(LREF filenameCmp) documentation for details.
2778 This function allocates memory.
2780 Params:
2781 cs = Whether matching path name components against the base path should
2782 be case-sensitive or not.
2783 path = A path name.
2784 base = The base path to construct the relative path from.
2786 Returns: The relative path.
2788 See_Also:
2789 $(LREF asRelativePath) which does not allocate memory
2791 Throws:
2792 $(D Exception) if the specified _base directory is not absolute.
2794 string relativePath(CaseSensitive cs = CaseSensitive.osDefault)
2795 (string path, lazy string base = getcwd())
2797 if (!isAbsolute(path))
2798 return path;
2799 auto baseVar = base;
2800 if (!isAbsolute(baseVar))
2801 throw new Exception("Base directory must be absolute");
2803 import std.conv : to;
2804 return asRelativePath!cs(path, baseVar).to!string;
2808 @system unittest
2810 assert(relativePath("foo") == "foo");
2812 version (Posix)
2814 assert(relativePath("foo", "/bar") == "foo");
2815 assert(relativePath("/foo/bar", "/foo/bar") == ".");
2816 assert(relativePath("/foo/bar", "/foo/baz") == "../bar");
2817 assert(relativePath("/foo/bar/baz", "/foo/woo/wee") == "../../bar/baz");
2818 assert(relativePath("/foo/bar/baz", "/foo/bar") == "baz");
2820 version (Windows)
2822 assert(relativePath("foo", `c:\bar`) == "foo");
2823 assert(relativePath(`c:\foo\bar`, `c:\foo\bar`) == ".");
2824 assert(relativePath(`c:\foo\bar`, `c:\foo\baz`) == `..\bar`);
2825 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`) == `..\..\bar\baz`);
2826 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
2827 assert(relativePath(`c:\foo\bar`, `d:\foo`) == `c:\foo\bar`);
2831 @system unittest
2833 import std.exception;
2834 assert(relativePath("foo") == "foo");
2835 version (Posix)
2837 relativePath("/foo");
2838 assert(relativePath("/foo/bar", "/foo/baz") == "../bar");
2839 assertThrown(relativePath("/foo", "bar"));
2841 else version (Windows)
2843 relativePath(`\foo`);
2844 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
2845 assertThrown(relativePath(`c:\foo`, "bar"));
2847 else static assert(0);
2850 /** Transforms `path` into a _path relative to `base`.
2852 The returned _path is relative to `base`, which is usually
2853 the current working directory.
2854 `base` must be an absolute _path, and it is always assumed
2855 to refer to a directory. If `path` and `base` refer to
2856 the same directory, the function returns `'.'`.
2858 The following algorithm is used:
2859 $(OL
2860 $(LI If `path` is a relative directory, return it unaltered.)
2861 $(LI Find a common root between `path` and `base`.
2862 If there is no common root, return `path` unaltered.)
2863 $(LI Prepare a string with as many `../` or `..\` as
2864 necessary to reach the common root from base path.)
2865 $(LI Append the remaining segments of `path` to the string
2866 and return.)
2869 In the second step, path components are compared using `filenameCmp!cs`,
2870 where `cs` is an optional template parameter determining whether
2871 the comparison is case sensitive or not. See the
2872 $(LREF filenameCmp) documentation for details.
2874 Params:
2875 path = _path to transform
2876 base = absolute path
2877 cs = whether filespec comparisons are sensitive or not; defaults to
2878 `CaseSensitive.osDefault`
2880 Returns:
2881 a random access range of the transformed _path
2883 See_Also:
2884 $(LREF relativePath)
2886 auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2)
2887 (R1 path, R2 base)
2888 if ((isNarrowString!R1 ||
2889 (isRandomAccessRange!R1 && hasSlicing!R1 && isSomeChar!(ElementType!R1)) &&
2890 !isConvertibleToString!R1) &&
2891 (isNarrowString!R2 ||
2892 (isRandomAccessRange!R2 && hasSlicing!R2 && isSomeChar!(ElementType!R2)) &&
2893 !isConvertibleToString!R2))
2895 bool choosePath = !isAbsolute(path);
2897 // Find common root with current working directory
2899 auto basePS = pathSplitter(base);
2900 auto pathPS = pathSplitter(path);
2901 choosePath |= filenameCmp!cs(basePS.front, pathPS.front) != 0;
2903 basePS.popFront();
2904 pathPS.popFront();
2906 import std.algorithm.comparison : mismatch;
2907 import std.algorithm.iteration : joiner;
2908 import std.array : array;
2909 import std.range.primitives : walkLength;
2910 import std.range : repeat, chain, choose;
2911 import std.utf : byCodeUnit, byChar;
2913 // Remove matching prefix from basePS and pathPS
2914 auto tup = mismatch!((a, b) => filenameCmp!cs(a, b) == 0)(basePS, pathPS);
2915 basePS = tup[0];
2916 pathPS = tup[1];
2918 string sep;
2919 if (basePS.empty && pathPS.empty)
2920 sep = "."; // if base == path, this is the return
2921 else if (!basePS.empty && !pathPS.empty)
2922 sep = dirSeparator;
2924 // Append as many "../" as necessary to reach common base from path
2925 auto r1 = ".."
2926 .byChar
2927 .repeat(basePS.walkLength())
2928 .joiner(dirSeparator.byChar);
2930 auto r2 = pathPS
2931 .joiner(dirSeparator.byChar)
2932 .byChar;
2934 // Return (r1 ~ sep ~ r2)
2935 return choose(choosePath, path.byCodeUnit, chain(r1, sep.byChar, r2));
2939 @system unittest
2941 import std.array;
2942 version (Posix)
2944 assert(asRelativePath("foo", "/bar").array == "foo");
2945 assert(asRelativePath("/foo/bar", "/foo/bar").array == ".");
2946 assert(asRelativePath("/foo/bar", "/foo/baz").array == "../bar");
2947 assert(asRelativePath("/foo/bar/baz", "/foo/woo/wee").array == "../../bar/baz");
2948 assert(asRelativePath("/foo/bar/baz", "/foo/bar").array == "baz");
2950 else version (Windows)
2952 assert(asRelativePath("foo", `c:\bar`).array == "foo");
2953 assert(asRelativePath(`c:\foo\bar`, `c:\foo\bar`).array == ".");
2954 assert(asRelativePath(`c:\foo\bar`, `c:\foo\baz`).array == `..\bar`);
2955 assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`);
2956 assert(asRelativePath(`c:/foo/bar/baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`);
2957 assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\bar`).array == "baz");
2958 assert(asRelativePath(`c:\foo\bar`, `d:\foo`).array == `c:\foo\bar`);
2959 assert(asRelativePath(`\\foo\bar`, `c:\foo`).array == `\\foo\bar`);
2961 else
2962 static assert(0);
2965 auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2)
2966 (auto ref R1 path, auto ref R2 base)
2967 if (isConvertibleToString!R1 || isConvertibleToString!R2)
2969 import std.meta : staticMap;
2970 alias Types = staticMap!(convertToString, R1, R2);
2971 return asRelativePath!(cs, Types)(path, base);
2974 @system unittest
2976 import std.array;
2977 version (Posix)
2978 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("/bar")).array == "foo");
2979 else version (Windows)
2980 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString(`c:\bar`)).array == "foo");
2981 assert(asRelativePath(TestAliasedString("foo"), "bar").array == "foo");
2982 assert(asRelativePath("foo", TestAliasedString("bar")).array == "foo");
2983 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("bar")).array == "foo");
2984 import std.utf : byDchar;
2985 assert(asRelativePath("foo"d.byDchar, TestAliasedString("bar")).array == "foo");
2988 @system unittest
2990 import std.array, std.utf : bCU=byCodeUnit;
2991 version (Posix)
2993 assert(asRelativePath("/foo/bar/baz".bCU, "/foo/bar".bCU).array == "baz");
2994 assert(asRelativePath("/foo/bar/baz"w.bCU, "/foo/bar"w.bCU).array == "baz"w);
2995 assert(asRelativePath("/foo/bar/baz"d.bCU, "/foo/bar"d.bCU).array == "baz"d);
2997 else version (Windows)
2999 assert(asRelativePath(`\\foo\bar`.bCU, `c:\foo`.bCU).array == `\\foo\bar`);
3000 assert(asRelativePath(`\\foo\bar`w.bCU, `c:\foo`w.bCU).array == `\\foo\bar`w);
3001 assert(asRelativePath(`\\foo\bar`d.bCU, `c:\foo`d.bCU).array == `\\foo\bar`d);
3005 /** Compares filename characters.
3007 This function can perform a case-sensitive or a case-insensitive
3008 comparison. This is controlled through the $(D cs) template parameter
3009 which, if not specified, is given by $(LREF CaseSensitive)$(D .osDefault).
3011 On Windows, the backslash and slash characters ($(D `\`) and $(D `/`))
3012 are considered equal.
3014 Params:
3015 cs = Case-sensitivity of the comparison.
3016 a = A filename character.
3017 b = A filename character.
3019 Returns:
3020 $(D < 0) if $(D a < b),
3021 $(D 0) if $(D a == b), and
3022 $(D > 0) if $(D a > b).
3024 int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b)
3025 @safe pure nothrow
3027 if (isDirSeparator(a) && isDirSeparator(b)) return 0;
3028 static if (!cs)
3030 import std.uni : toLower;
3031 a = toLower(a);
3032 b = toLower(b);
3034 return cast(int)(a - b);
3038 @safe unittest
3040 assert(filenameCharCmp('a', 'a') == 0);
3041 assert(filenameCharCmp('a', 'b') < 0);
3042 assert(filenameCharCmp('b', 'a') > 0);
3044 version (linux)
3046 // Same as calling filenameCharCmp!(CaseSensitive.yes)(a, b)
3047 assert(filenameCharCmp('A', 'a') < 0);
3048 assert(filenameCharCmp('a', 'A') > 0);
3050 version (Windows)
3052 // Same as calling filenameCharCmp!(CaseSensitive.no)(a, b)
3053 assert(filenameCharCmp('a', 'A') == 0);
3054 assert(filenameCharCmp('a', 'B') < 0);
3055 assert(filenameCharCmp('A', 'b') < 0);
3059 @safe unittest
3061 assert(filenameCharCmp!(CaseSensitive.yes)('A', 'a') < 0);
3062 assert(filenameCharCmp!(CaseSensitive.yes)('a', 'A') > 0);
3064 assert(filenameCharCmp!(CaseSensitive.no)('a', 'a') == 0);
3065 assert(filenameCharCmp!(CaseSensitive.no)('a', 'b') < 0);
3066 assert(filenameCharCmp!(CaseSensitive.no)('b', 'a') > 0);
3067 assert(filenameCharCmp!(CaseSensitive.no)('A', 'a') == 0);
3068 assert(filenameCharCmp!(CaseSensitive.no)('a', 'A') == 0);
3069 assert(filenameCharCmp!(CaseSensitive.no)('a', 'B') < 0);
3070 assert(filenameCharCmp!(CaseSensitive.no)('B', 'a') > 0);
3071 assert(filenameCharCmp!(CaseSensitive.no)('A', 'b') < 0);
3072 assert(filenameCharCmp!(CaseSensitive.no)('b', 'A') > 0);
3074 version (Posix) assert(filenameCharCmp('\\', '/') != 0);
3075 version (Windows) assert(filenameCharCmp('\\', '/') == 0);
3079 /** Compares file names and returns
3081 Individual characters are compared using $(D filenameCharCmp!cs),
3082 where $(D cs) is an optional template parameter determining whether
3083 the comparison is case sensitive or not.
3085 Treatment of invalid UTF encodings is implementation defined.
3087 Params:
3088 cs = case sensitivity
3089 filename1 = range for first file name
3090 filename2 = range for second file name
3092 Returns:
3093 $(D < 0) if $(D filename1 < filename2),
3094 $(D 0) if $(D filename1 == filename2) and
3095 $(D > 0) if $(D filename1 > filename2).
3097 See_Also:
3098 $(LREF filenameCharCmp)
3100 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2)
3101 (Range1 filename1, Range2 filename2)
3102 if (isInputRange!Range1 && !isInfinite!Range1 &&
3103 isSomeChar!(ElementEncodingType!Range1) &&
3104 !isConvertibleToString!Range1 &&
3105 isInputRange!Range2 && !isInfinite!Range2 &&
3106 isSomeChar!(ElementEncodingType!Range2) &&
3107 !isConvertibleToString!Range2)
3109 alias C1 = Unqual!(ElementEncodingType!Range1);
3110 alias C2 = Unqual!(ElementEncodingType!Range2);
3112 static if (!cs && (C1.sizeof < 4 || C2.sizeof < 4) ||
3113 C1.sizeof != C2.sizeof)
3115 // Case insensitive - decode so case is checkable
3116 // Different char sizes - decode to bring to common type
3117 import std.utf : byDchar;
3118 return filenameCmp!cs(filename1.byDchar, filename2.byDchar);
3120 else static if (isSomeString!Range1 && C1.sizeof < 4 ||
3121 isSomeString!Range2 && C2.sizeof < 4)
3123 // Avoid autodecoding
3124 import std.utf : byCodeUnit;
3125 return filenameCmp!cs(filename1.byCodeUnit, filename2.byCodeUnit);
3127 else
3129 for (;;)
3131 if (filename1.empty) return -(cast(int) !filename2.empty);
3132 if (filename2.empty) return 1;
3133 const c = filenameCharCmp!cs(filename1.front, filename2.front);
3134 if (c != 0) return c;
3135 filename1.popFront();
3136 filename2.popFront();
3142 @safe unittest
3144 assert(filenameCmp("abc", "abc") == 0);
3145 assert(filenameCmp("abc", "abd") < 0);
3146 assert(filenameCmp("abc", "abb") > 0);
3147 assert(filenameCmp("abc", "abcd") < 0);
3148 assert(filenameCmp("abcd", "abc") > 0);
3150 version (linux)
3152 // Same as calling filenameCmp!(CaseSensitive.yes)(filename1, filename2)
3153 assert(filenameCmp("Abc", "abc") < 0);
3154 assert(filenameCmp("abc", "Abc") > 0);
3156 version (Windows)
3158 // Same as calling filenameCmp!(CaseSensitive.no)(filename1, filename2)
3159 assert(filenameCmp("Abc", "abc") == 0);
3160 assert(filenameCmp("abc", "Abc") == 0);
3161 assert(filenameCmp("Abc", "abD") < 0);
3162 assert(filenameCmp("abc", "AbB") > 0);
3166 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2)
3167 (auto ref Range1 filename1, auto ref Range2 filename2)
3168 if (isConvertibleToString!Range1 || isConvertibleToString!Range2)
3170 import std.meta : staticMap;
3171 alias Types = staticMap!(convertToString, Range1, Range2);
3172 return filenameCmp!(cs, Types)(filename1, filename2);
3175 @safe unittest
3177 assert(filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), "abc") < 0);
3178 assert(filenameCmp!(CaseSensitive.yes)("Abc", TestAliasedString("abc")) < 0);
3179 assert(filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), TestAliasedString("abc")) < 0);
3182 @safe unittest
3184 assert(filenameCmp!(CaseSensitive.yes)("Abc", "abc") < 0);
3185 assert(filenameCmp!(CaseSensitive.yes)("abc", "Abc") > 0);
3187 assert(filenameCmp!(CaseSensitive.no)("abc", "abc") == 0);
3188 assert(filenameCmp!(CaseSensitive.no)("abc", "abd") < 0);
3189 assert(filenameCmp!(CaseSensitive.no)("abc", "abb") > 0);
3190 assert(filenameCmp!(CaseSensitive.no)("abc", "abcd") < 0);
3191 assert(filenameCmp!(CaseSensitive.no)("abcd", "abc") > 0);
3192 assert(filenameCmp!(CaseSensitive.no)("Abc", "abc") == 0);
3193 assert(filenameCmp!(CaseSensitive.no)("abc", "Abc") == 0);
3194 assert(filenameCmp!(CaseSensitive.no)("Abc", "abD") < 0);
3195 assert(filenameCmp!(CaseSensitive.no)("abc", "AbB") > 0);
3197 version (Posix) assert(filenameCmp(`abc\def`, `abc/def`) != 0);
3198 version (Windows) assert(filenameCmp(`abc\def`, `abc/def`) == 0);
3201 /** Matches a pattern against a path.
3203 Some characters of pattern have a special meaning (they are
3204 $(I meta-characters)) and can't be escaped. These are:
3206 $(BOOKTABLE,
3207 $(TR $(TD $(D *))
3208 $(TD Matches 0 or more instances of any character.))
3209 $(TR $(TD $(D ?))
3210 $(TD Matches exactly one instance of any character.))
3211 $(TR $(TD $(D [)$(I chars)$(D ]))
3212 $(TD Matches one instance of any character that appears
3213 between the brackets.))
3214 $(TR $(TD $(D [!)$(I chars)$(D ]))
3215 $(TD Matches one instance of any character that does not
3216 appear between the brackets after the exclamation mark.))
3217 $(TR $(TD $(D {)$(I string1)$(D ,)$(I string2)$(D ,)&hellip;$(D }))
3218 $(TD Matches either of the specified strings.))
3221 Individual characters are compared using $(D filenameCharCmp!cs),
3222 where $(D cs) is an optional template parameter determining whether
3223 the comparison is case sensitive or not. See the
3224 $(LREF filenameCharCmp) documentation for details.
3226 Note that directory
3227 separators and dots don't stop a meta-character from matching
3228 further portions of the path.
3230 Params:
3231 cs = Whether the matching should be case-sensitive
3232 path = The path to be matched against
3233 pattern = The glob pattern
3235 Returns:
3236 $(D true) if pattern matches path, $(D false) otherwise.
3238 See_also:
3239 $(LINK2 http://en.wikipedia.org/wiki/Glob_%28programming%29,Wikipedia: _glob (programming))
3241 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range)
3242 (Range path, const(C)[] pattern)
3243 @safe pure nothrow
3244 if (isForwardRange!Range && !isInfinite!Range &&
3245 isSomeChar!(ElementEncodingType!Range) && !isConvertibleToString!Range &&
3246 isSomeChar!C && is(Unqual!C == Unqual!(ElementEncodingType!Range)))
3249 // Verify that pattern[] is valid
3250 import std.algorithm.searching : balancedParens;
3251 assert(balancedParens(pattern, '[', ']', 0));
3252 assert(balancedParens(pattern, '{', '}', 0));
3254 body
3256 alias RC = Unqual!(ElementEncodingType!Range);
3258 static if (RC.sizeof == 1 && isSomeString!Range)
3260 import std.utf : byChar;
3261 return globMatch!cs(path.byChar, pattern);
3263 else static if (RC.sizeof == 2 && isSomeString!Range)
3265 import std.utf : byWchar;
3266 return globMatch!cs(path.byWchar, pattern);
3268 else
3270 C[] pattmp;
3271 foreach (ref pi; 0 .. pattern.length)
3273 const pc = pattern[pi];
3274 switch (pc)
3276 case '*':
3277 if (pi + 1 == pattern.length)
3278 return true;
3279 for (; !path.empty; path.popFront())
3281 auto p = path.save;
3282 if (globMatch!(cs, C)(p,
3283 pattern[pi + 1 .. pattern.length]))
3284 return true;
3286 return false;
3288 case '?':
3289 if (path.empty)
3290 return false;
3291 path.popFront();
3292 break;
3294 case '[':
3295 if (path.empty)
3296 return false;
3297 auto nc = path.front;
3298 path.popFront();
3299 auto not = false;
3300 ++pi;
3301 if (pattern[pi] == '!')
3303 not = true;
3304 ++pi;
3306 auto anymatch = false;
3307 while (1)
3309 const pc2 = pattern[pi];
3310 if (pc2 == ']')
3311 break;
3312 if (!anymatch && (filenameCharCmp!cs(nc, pc2) == 0))
3313 anymatch = true;
3314 ++pi;
3316 if (anymatch == not)
3317 return false;
3318 break;
3320 case '{':
3321 // find end of {} section
3322 auto piRemain = pi;
3323 for (; piRemain < pattern.length
3324 && pattern[piRemain] != '}'; ++piRemain)
3327 if (piRemain < pattern.length)
3328 ++piRemain;
3329 ++pi;
3331 while (pi < pattern.length)
3333 const pi0 = pi;
3334 C pc3 = pattern[pi];
3335 // find end of current alternative
3336 for (; pi < pattern.length && pc3 != '}' && pc3 != ','; ++pi)
3338 pc3 = pattern[pi];
3341 auto p = path.save;
3342 if (pi0 == pi)
3344 if (globMatch!(cs, C)(p, pattern[piRemain..$]))
3346 return true;
3348 ++pi;
3350 else
3352 /* Match for:
3353 * pattern[pi0 .. pi-1] ~ pattern[piRemain..$]
3355 if (pattmp is null)
3356 // Allocate this only once per function invocation.
3357 // Should do it with malloc/free, but that would make it impure.
3358 pattmp = new C[pattern.length];
3360 const len1 = pi - 1 - pi0;
3361 pattmp[0 .. len1] = pattern[pi0 .. pi - 1];
3363 const len2 = pattern.length - piRemain;
3364 pattmp[len1 .. len1 + len2] = pattern[piRemain .. $];
3366 if (globMatch!(cs, C)(p, pattmp[0 .. len1 + len2]))
3368 return true;
3371 if (pc3 == '}')
3373 break;
3376 return false;
3378 default:
3379 if (path.empty)
3380 return false;
3381 if (filenameCharCmp!cs(pc, path.front) != 0)
3382 return false;
3383 path.popFront();
3384 break;
3387 return path.empty;
3392 @safe unittest
3394 assert(globMatch("foo.bar", "*"));
3395 assert(globMatch("foo.bar", "*.*"));
3396 assert(globMatch(`foo/foo\bar`, "f*b*r"));
3397 assert(globMatch("foo.bar", "f???bar"));
3398 assert(globMatch("foo.bar", "[fg]???bar"));
3399 assert(globMatch("foo.bar", "[!gh]*bar"));
3400 assert(globMatch("bar.fooz", "bar.{foo,bif}z"));
3401 assert(globMatch("bar.bifz", "bar.{foo,bif}z"));
3403 version (Windows)
3405 // Same as calling globMatch!(CaseSensitive.no)(path, pattern)
3406 assert(globMatch("foo", "Foo"));
3407 assert(globMatch("Goo.bar", "[fg]???bar"));
3409 version (linux)
3411 // Same as calling globMatch!(CaseSensitive.yes)(path, pattern)
3412 assert(!globMatch("foo", "Foo"));
3413 assert(!globMatch("Goo.bar", "[fg]???bar"));
3417 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range)
3418 (auto ref Range path, const(C)[] pattern)
3419 @safe pure nothrow
3420 if (isConvertibleToString!Range)
3422 return globMatch!(cs, C, StringTypeOf!Range)(path, pattern);
3425 @safe unittest
3427 assert(testAliasedString!globMatch("foo.bar", "*"));
3430 @safe unittest
3432 assert(globMatch!(CaseSensitive.no)("foo", "Foo"));
3433 assert(!globMatch!(CaseSensitive.yes)("foo", "Foo"));
3435 assert(globMatch("foo", "*"));
3436 assert(globMatch("foo.bar"w, "*"w));
3437 assert(globMatch("foo.bar"d, "*.*"d));
3438 assert(globMatch("foo.bar", "foo*"));
3439 assert(globMatch("foo.bar"w, "f*bar"w));
3440 assert(globMatch("foo.bar"d, "f*b*r"d));
3441 assert(globMatch("foo.bar", "f???bar"));
3442 assert(globMatch("foo.bar"w, "[fg]???bar"w));
3443 assert(globMatch("foo.bar"d, "[!gh]*bar"d));
3445 assert(!globMatch("foo", "bar"));
3446 assert(!globMatch("foo"w, "*.*"w));
3447 assert(!globMatch("foo.bar"d, "f*baz"d));
3448 assert(!globMatch("foo.bar", "f*b*x"));
3449 assert(!globMatch("foo.bar", "[gh]???bar"));
3450 assert(!globMatch("foo.bar"w, "[!fg]*bar"w));
3451 assert(!globMatch("foo.bar"d, "[fg]???baz"d));
3452 assert(!globMatch("foo.di", "*.d")); // test issue 6634: triggered bad assertion
3454 assert(globMatch("foo.bar", "{foo,bif}.bar"));
3455 assert(globMatch("bif.bar"w, "{foo,bif}.bar"w));
3457 assert(globMatch("bar.foo"d, "bar.{foo,bif}"d));
3458 assert(globMatch("bar.bif", "bar.{foo,bif}"));
3460 assert(globMatch("bar.fooz"w, "bar.{foo,bif}z"w));
3461 assert(globMatch("bar.bifz"d, "bar.{foo,bif}z"d));
3463 assert(globMatch("bar.foo", "bar.{biz,,baz}foo"));
3464 assert(globMatch("bar.foo"w, "bar.{biz,}foo"w));
3465 assert(globMatch("bar.foo"d, "bar.{,biz}foo"d));
3466 assert(globMatch("bar.foo", "bar.{}foo"));
3468 assert(globMatch("bar.foo"w, "bar.{ar,,fo}o"w));
3469 assert(globMatch("bar.foo"d, "bar.{,ar,fo}o"d));
3470 assert(globMatch("bar.o", "bar.{,ar,fo}o"));
3472 assert(!globMatch("foo", "foo?"));
3473 assert(!globMatch("foo", "foo[]"));
3474 assert(!globMatch("foo", "foob"));
3475 assert(!globMatch("foo", "foo{b}"));
3478 static assert(globMatch("foo.bar", "[!gh]*bar"));
3484 /** Checks that the given file or directory name is valid.
3486 The maximum length of $(D filename) is given by the constant
3487 $(D core.stdc.stdio.FILENAME_MAX). (On Windows, this number is
3488 defined as the maximum number of UTF-16 code points, and the
3489 test will therefore only yield strictly correct results when
3490 $(D filename) is a string of $(D wchar)s.)
3492 On Windows, the following criteria must be satisfied
3493 ($(LINK2 http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx,source)):
3494 $(UL
3495 $(LI $(D filename) must not contain any characters whose integer
3496 representation is in the range 0-31.)
3497 $(LI $(D filename) must not contain any of the following $(I reserved
3498 characters): <>:"/\|?*)
3499 $(LI $(D filename) may not end with a space ($(D ' ')) or a period
3500 ($(D '.')).)
3503 On POSIX, $(D filename) may not contain a forward slash ($(D '/')) or
3504 the null character ($(D '\0')).
3506 Params:
3507 filename = string to check
3509 Returns:
3510 $(D true) if and only if $(D filename) is not
3511 empty, not too long, and does not contain invalid characters.
3514 bool isValidFilename(Range)(Range filename)
3515 if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) ||
3516 isNarrowString!Range) &&
3517 !isConvertibleToString!Range)
3519 import core.stdc.stdio : FILENAME_MAX;
3520 if (filename.length == 0 || filename.length >= FILENAME_MAX) return false;
3521 foreach (c; filename)
3523 version (Windows)
3525 switch (c)
3527 case 0:
3529 case 31:
3530 case '<':
3531 case '>':
3532 case ':':
3533 case '"':
3534 case '/':
3535 case '\\':
3536 case '|':
3537 case '?':
3538 case '*':
3539 return false;
3541 default:
3542 break;
3545 else version (Posix)
3547 if (c == 0 || c == '/') return false;
3549 else static assert(0);
3551 version (Windows)
3553 auto last = filename[filename.length - 1];
3554 if (last == '.' || last == ' ') return false;
3557 // All criteria passed
3558 return true;
3562 @safe pure @nogc nothrow
3563 unittest
3565 import std.utf : byCodeUnit;
3567 assert(isValidFilename("hello.exe".byCodeUnit));
3570 bool isValidFilename(Range)(auto ref Range filename)
3571 if (isConvertibleToString!Range)
3573 return isValidFilename!(StringTypeOf!Range)(filename);
3576 @safe unittest
3578 assert(testAliasedString!isValidFilename("hello.exe"));
3581 @safe pure
3582 unittest
3584 import std.conv;
3585 auto valid = ["foo"];
3586 auto invalid = ["", "foo\0bar", "foo/bar"];
3587 auto pfdep = [`foo\bar`, "*.txt"];
3588 version (Windows) invalid ~= pfdep;
3589 else version (Posix) valid ~= pfdep;
3590 else static assert(0);
3592 import std.meta : AliasSeq;
3593 foreach (T; AliasSeq!(char[], const(char)[], string, wchar[],
3594 const(wchar)[], wstring, dchar[], const(dchar)[], dstring))
3596 foreach (fn; valid)
3597 assert(isValidFilename(to!T(fn)));
3598 foreach (fn; invalid)
3599 assert(!isValidFilename(to!T(fn)));
3603 auto r = MockRange!(immutable(char))(`dir/file.d`);
3604 assert(!isValidFilename(r));
3607 static struct DirEntry { string s; alias s this; }
3608 assert(isValidFilename(DirEntry("file.ext")));
3610 version (Windows)
3612 immutable string cases = "<>:\"/\\|?*";
3613 foreach (i; 0 .. 31 + cases.length)
3615 char[3] buf;
3616 buf[0] = 'a';
3617 buf[1] = i <= 31 ? cast(char) i : cases[i - 32];
3618 buf[2] = 'b';
3619 assert(!isValidFilename(buf[]));
3626 /** Checks whether $(D path) is a valid _path.
3628 Generally, this function checks that $(D path) is not empty, and that
3629 each component of the path either satisfies $(LREF isValidFilename)
3630 or is equal to $(D ".") or $(D "..").
3632 $(B It does $(I not) check whether the _path points to an existing file
3633 or directory; use $(REF exists, std,file) for this purpose.)
3635 On Windows, some special rules apply:
3636 $(UL
3637 $(LI If the second character of $(D path) is a colon ($(D ':')),
3638 the first character is interpreted as a drive letter, and
3639 must be in the range A-Z (case insensitive).)
3640 $(LI If $(D path) is on the form $(D `\\$(I server)\$(I share)\...`)
3641 (UNC path), $(LREF isValidFilename) is applied to $(I server)
3642 and $(I share) as well.)
3643 $(LI If $(D path) starts with $(D `\\?\`) (long UNC path), the
3644 only requirement for the rest of the string is that it does
3645 not contain the null character.)
3646 $(LI If $(D path) starts with $(D `\\.\`) (Win32 device namespace)
3647 this function returns $(D false); such paths are beyond the scope
3648 of this module.)
3651 Params:
3652 path = string or Range of characters to check
3654 Returns:
3655 true if $(D path) is a valid _path.
3657 bool isValidPath(Range)(Range path)
3658 if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) ||
3659 isNarrowString!Range) &&
3660 !isConvertibleToString!Range)
3662 alias C = Unqual!(ElementEncodingType!Range);
3664 if (path.empty) return false;
3666 // Check whether component is "." or "..", or whether it satisfies
3667 // isValidFilename.
3668 bool isValidComponent(Range component)
3670 assert(component.length > 0);
3671 if (component[0] == '.')
3673 if (component.length == 1) return true;
3674 else if (component.length == 2 && component[1] == '.') return true;
3676 return isValidFilename(component);
3679 if (path.length == 1)
3680 return isDirSeparator(path[0]) || isValidComponent(path);
3682 Range remainder;
3683 version (Windows)
3685 if (isDirSeparator(path[0]) && isDirSeparator(path[1]))
3687 // Some kind of UNC path
3688 if (path.length < 5)
3690 // All valid UNC paths must have at least 5 characters
3691 return false;
3693 else if (path[2] == '?')
3695 // Long UNC path
3696 if (!isDirSeparator(path[3])) return false;
3697 foreach (c; path[4 .. $])
3699 if (c == '\0') return false;
3701 return true;
3703 else if (path[2] == '.')
3705 // Win32 device namespace not supported
3706 return false;
3708 else
3710 // Normal UNC path, i.e. \\server\share\...
3711 size_t i = 2;
3712 while (i < path.length && !isDirSeparator(path[i])) ++i;
3713 if (i == path.length || !isValidFilename(path[2 .. i]))
3714 return false;
3715 ++i; // Skip a single dir separator
3716 size_t j = i;
3717 while (j < path.length && !isDirSeparator(path[j])) ++j;
3718 if (!isValidFilename(path[i .. j])) return false;
3719 remainder = path[j .. $];
3722 else if (isDriveSeparator(path[1]))
3724 import std.ascii : isAlpha;
3725 if (!isAlpha(path[0])) return false;
3726 remainder = path[2 .. $];
3728 else
3730 remainder = path;
3733 else version (Posix)
3735 remainder = path;
3737 else static assert(0);
3738 remainder = ltrimDirSeparators(remainder);
3740 // Check that each component satisfies isValidComponent.
3741 while (!remainder.empty)
3743 size_t i = 0;
3744 while (i < remainder.length && !isDirSeparator(remainder[i])) ++i;
3745 assert(i > 0);
3746 if (!isValidComponent(remainder[0 .. i])) return false;
3747 remainder = ltrimDirSeparators(remainder[i .. $]);
3750 // All criteria passed
3751 return true;
3755 @safe pure @nogc nothrow
3756 unittest
3758 assert(isValidPath("/foo/bar"));
3759 assert(!isValidPath("/foo\0/bar"));
3760 assert(isValidPath("/"));
3761 assert(isValidPath("a"));
3763 version (Windows)
3765 assert(isValidPath(`c:\`));
3766 assert(isValidPath(`c:\foo`));
3767 assert(isValidPath(`c:\foo\.\bar\\\..\`));
3768 assert(!isValidPath(`!:\foo`));
3769 assert(!isValidPath(`c::\foo`));
3770 assert(!isValidPath(`c:\foo?`));
3771 assert(!isValidPath(`c:\foo.`));
3773 assert(isValidPath(`\\server\share`));
3774 assert(isValidPath(`\\server\share\foo`));
3775 assert(isValidPath(`\\server\share\\foo`));
3776 assert(!isValidPath(`\\\server\share\foo`));
3777 assert(!isValidPath(`\\server\\share\foo`));
3778 assert(!isValidPath(`\\ser*er\share\foo`));
3779 assert(!isValidPath(`\\server\sha?e\foo`));
3780 assert(!isValidPath(`\\server\share\|oo`));
3782 assert(isValidPath(`\\?\<>:"?*|/\..\.`));
3783 assert(!isValidPath("\\\\?\\foo\0bar"));
3785 assert(!isValidPath(`\\.\PhysicalDisk1`));
3786 assert(!isValidPath(`\\`));
3789 import std.utf : byCodeUnit;
3790 assert(isValidPath("/foo/bar".byCodeUnit));
3793 bool isValidPath(Range)(auto ref Range path)
3794 if (isConvertibleToString!Range)
3796 return isValidPath!(StringTypeOf!Range)(path);
3799 @safe unittest
3801 assert(testAliasedString!isValidPath("/foo/bar"));
3804 /** Performs tilde expansion in paths on POSIX systems.
3805 On Windows, this function does nothing.
3807 There are two ways of using tilde expansion in a path. One
3808 involves using the tilde alone or followed by a path separator. In
3809 this case, the tilde will be expanded with the value of the
3810 environment variable $(D HOME). The second way is putting
3811 a username after the tilde (i.e. $(D ~john/Mail)). Here,
3812 the username will be searched for in the user database
3813 (i.e. $(D /etc/passwd) on Unix systems) and will expand to
3814 whatever path is stored there. The username is considered the
3815 string after the tilde ending at the first instance of a path
3816 separator.
3818 Note that using the $(D ~user) syntax may give different
3819 values from just $(D ~) if the environment variable doesn't
3820 match the value stored in the user database.
3822 When the environment variable version is used, the path won't
3823 be modified if the environment variable doesn't exist or it
3824 is empty. When the database version is used, the path won't be
3825 modified if the user doesn't exist in the database or there is
3826 not enough memory to perform the query.
3828 This function performs several memory allocations.
3830 Params:
3831 inputPath = The path name to expand.
3833 Returns:
3834 $(D inputPath) with the tilde expanded, or just $(D inputPath)
3835 if it could not be expanded.
3836 For Windows, $(D expandTilde) merely returns its argument $(D inputPath).
3838 Example:
3839 -----
3840 void processFile(string path)
3842 // Allow calling this function with paths such as ~/foo
3843 auto fullPath = expandTilde(path);
3846 -----
3848 string expandTilde(string inputPath) nothrow
3850 version (Posix)
3852 import core.exception : onOutOfMemoryError;
3853 import core.stdc.errno : errno, ERANGE;
3854 import core.stdc.stdlib : malloc, free, realloc;
3856 /* Joins a path from a C string to the remainder of path.
3858 The last path separator from c_path is discarded. The result
3859 is joined to path[char_pos .. length] if char_pos is smaller
3860 than length, otherwise path is not appended to c_path.
3862 static string combineCPathWithDPath(char* c_path, string path, size_t char_pos) nothrow
3864 import core.stdc.string : strlen;
3866 assert(c_path != null);
3867 assert(path.length > 0);
3868 assert(char_pos >= 0);
3870 // Search end of C string
3871 size_t end = strlen(c_path);
3873 // Remove trailing path separator, if any
3874 if (end && isDirSeparator(c_path[end - 1]))
3875 end--;
3877 // (this is the only GC allocation done in expandTilde())
3878 string cp;
3879 if (char_pos < path.length)
3880 // Append something from path
3881 cp = cast(string)(c_path[0 .. end] ~ path[char_pos .. $]);
3882 else
3883 // Create our own copy, as lifetime of c_path is undocumented
3884 cp = c_path[0 .. end].idup;
3886 return cp;
3889 // Replaces the tilde from path with the environment variable HOME.
3890 static string expandFromEnvironment(string path) nothrow
3892 import core.stdc.stdlib : getenv;
3894 assert(path.length >= 1);
3895 assert(path[0] == '~');
3897 // Get HOME and use that to replace the tilde.
3898 auto home = getenv("HOME");
3899 if (home == null)
3900 return path;
3902 return combineCPathWithDPath(home, path, 1);
3905 // Replaces the tilde from path with the path from the user database.
3906 static string expandFromDatabase(string path) nothrow
3908 // bionic doesn't really support this, as getpwnam_r
3909 // isn't provided and getpwnam is basically just a stub
3910 version (CRuntime_Bionic)
3912 return path;
3914 else
3916 import core.sys.posix.pwd : passwd, getpwnam_r;
3917 import std.string : indexOf;
3919 assert(path.length > 2 || (path.length == 2 && !isDirSeparator(path[1])));
3920 assert(path[0] == '~');
3922 // Extract username, searching for path separator.
3923 auto last_char = indexOf(path, dirSeparator[0]);
3925 size_t username_len = (last_char == -1) ? path.length : last_char;
3926 char* username = cast(char*) malloc(username_len * char.sizeof);
3927 if (!username)
3928 onOutOfMemoryError();
3929 scope(exit) free(username);
3931 if (last_char == -1)
3933 username[0 .. username_len - 1] = path[1 .. $];
3934 last_char = path.length + 1;
3936 else
3938 username[0 .. username_len - 1] = path[1 .. last_char];
3940 username[username_len - 1] = 0;
3942 assert(last_char > 1);
3944 // Reserve C memory for the getpwnam_r() function.
3945 version (unittest)
3946 uint extra_memory_size = 2;
3947 else
3948 uint extra_memory_size = 5 * 1024;
3949 char* extra_memory;
3950 scope(exit) free(extra_memory);
3952 passwd result;
3953 while (1)
3955 extra_memory = cast(char*) realloc(extra_memory, extra_memory_size * char.sizeof);
3956 if (extra_memory == null)
3957 onOutOfMemoryError();
3959 // Obtain info from database.
3960 passwd *verify;
3961 errno = 0;
3962 if (getpwnam_r(username, &result, extra_memory, extra_memory_size,
3963 &verify) == 0)
3965 // Succeeded if verify points at result
3966 if (verify == &result)
3967 // username is found
3968 path = combineCPathWithDPath(result.pw_dir, path, last_char);
3969 break;
3972 if (errno != ERANGE &&
3973 // On FreeBSD and OSX, errno can be left at 0 instead of set to ERANGE
3974 errno != 0)
3975 onOutOfMemoryError();
3977 // extra_memory isn't large enough
3978 import core.checkedint : mulu;
3979 bool overflow;
3980 extra_memory_size = mulu(extra_memory_size, 2, overflow);
3981 if (overflow) assert(0);
3983 return path;
3987 // Return early if there is no tilde in path.
3988 if (inputPath.length < 1 || inputPath[0] != '~')
3989 return inputPath;
3991 if (inputPath.length == 1 || isDirSeparator(inputPath[1]))
3992 return expandFromEnvironment(inputPath);
3993 else
3994 return expandFromDatabase(inputPath);
3996 else version (Windows)
3998 // Put here real windows implementation.
3999 return inputPath;
4001 else
4003 static assert(0); // Guard. Implement on other platforms.
4008 version (unittest) import std.process : environment;
4009 @system unittest
4011 version (Posix)
4013 // Retrieve the current home variable.
4014 auto oldHome = environment.get("HOME");
4016 // Testing when there is no environment variable.
4017 environment.remove("HOME");
4018 assert(expandTilde("~/") == "~/");
4019 assert(expandTilde("~") == "~");
4021 // Testing when an environment variable is set.
4022 environment["HOME"] = "dmd/test";
4023 assert(expandTilde("~/") == "dmd/test/");
4024 assert(expandTilde("~") == "dmd/test");
4026 // The same, but with a variable ending in a slash.
4027 environment["HOME"] = "dmd/test/";
4028 assert(expandTilde("~/") == "dmd/test/");
4029 assert(expandTilde("~") == "dmd/test");
4031 // Recover original HOME variable before continuing.
4032 if (oldHome !is null) environment["HOME"] = oldHome;
4033 else environment.remove("HOME");
4035 // Test user expansion for root, no /root on Android
4036 version (OSX)
4038 assert(expandTilde("~root") == "/var/root", expandTilde("~root"));
4039 assert(expandTilde("~root/") == "/var/root/", expandTilde("~root/"));
4041 else version (Android)
4044 else
4046 assert(expandTilde("~root") == "/root", expandTilde("~root"));
4047 assert(expandTilde("~root/") == "/root/", expandTilde("~root/"));
4049 assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey");
4053 version (unittest)
4055 /* Define a mock RandomAccessRange to use for unittesting.
4058 struct MockRange(C)
4060 this(C[] array) { this.array = array; }
4061 const
4063 @property size_t length() { return array.length; }
4064 @property bool empty() { return array.length == 0; }
4065 @property C front() { return array[0]; }
4066 @property C back() { return array[$ - 1]; }
4067 @property size_t opDollar() { return length; }
4068 C opIndex(size_t i) { return array[i]; }
4070 void popFront() { array = array[1 .. $]; }
4071 void popBack() { array = array[0 .. $-1]; }
4072 MockRange!C opSlice( size_t lwr, size_t upr) const
4074 return MockRange!C(array[lwr .. upr]);
4076 @property MockRange save() { return this; }
4077 private:
4078 C[] array;
4081 static assert( isRandomAccessRange!(MockRange!(const(char))) );
4084 version (unittest)
4086 /* Define a mock BidirectionalRange to use for unittesting.
4089 struct MockBiRange(C)
4091 this(const(C)[] array) { this.array = array; }
4092 const
4094 @property bool empty() { return array.length == 0; }
4095 @property C front() { return array[0]; }
4096 @property C back() { return array[$ - 1]; }
4097 @property size_t opDollar() { return array.length; }
4099 void popFront() { array = array[1 .. $]; }
4100 void popBack() { array = array[0 .. $-1]; }
4101 @property MockBiRange save() { return this; }
4102 private:
4103 const(C)[] array;
4106 static assert( isBidirectionalRange!(MockBiRange!(const(char))) );
4109 private template BaseOf(R)
4111 static if (isRandomAccessRange!R && isSomeChar!(ElementType!R))
4112 alias BaseOf = R;
4113 else
4114 alias BaseOf = StringTypeOf!R;