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
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;)
38 $(TR $(TH Category) $(TH Functions))
39 $(TR $(TD Normalization) $(TD
41 $(LREF asAbsolutePath)
42 $(LREF asNormalizedPath)
43 $(LREF asRelativePath)
44 $(LREF buildNormalizedPath)
49 $(TR $(TD Partitioning) $(TD
60 $(TR $(TD Validation) $(TD
62 $(LREF isDirSeparator)
64 $(LREF isValidFilename)
67 $(TR $(TD Extension) $(TD
68 $(LREF defaultExtension)
71 $(LREF stripExtension)
72 $(LREF withDefaultExtension)
76 $(LREF filenameCharCmp)
84 Lars Tandle Kyllingstad,
85 $(HTTP digitalmars.com, Walter Bright),
86 Grzegorz Adam Hankiewicz,
88 $(HTTP erdani.org, Andrei Alexandrescu)
90 Copyright (c) 2000-2014, the authors. All rights reserved.
92 $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0)
94 $(PHOBOSSRC std/path.d)
99 import std
.file
: getcwd
;
100 static import std
.meta
;
110 else version (WatchOS
)
113 version (StdUnittest
)
116 struct TestAliasedString
118 string
get() @safe @nogc pure nothrow return scope { return _s
; }
124 bool testAliasedString(alias func
, Args
...)(scope string s
, scope Args args
)
126 return func(TestAliasedString(s
), args
) == func(s
, args
);
130 /** String used to separate directory names in a path. Under
131 POSIX this is a slash, under Windows a backslash.
133 version (Posix
) enum string dirSeparator
= "/";
134 else version (Windows
) enum string dirSeparator
= "\\";
135 else static assert(0, "unsupported platform");
140 /** Path separator string. A colon under POSIX, a semicolon
143 version (Posix
) enum string pathSeparator
= ":";
144 else version (Windows
) enum string pathSeparator
= ";";
145 else static assert(0, "unsupported platform");
150 /** Determines whether the given character is a directory separator.
152 On Windows, this includes both $(D `\`) and $(D `/`).
153 On POSIX, it's just $(D `/`).
155 bool isDirSeparator(dchar c
) @safe pure nothrow @nogc
157 if (c
== '/') return true;
158 version (Windows
) if (c
== '\\') return true;
163 @safe pure nothrow @nogc unittest
167 assert( '/'.isDirSeparator
);
168 assert( '\\'.isDirSeparator
);
172 assert( '/'.isDirSeparator
);
173 assert(!'\\'.isDirSeparator
);
178 /* Determines whether the given character is a drive separator.
180 On Windows, this is true if c is the ':' character that separates
181 the drive letter from the rest of the path. On POSIX, this always
184 private bool isDriveSeparator(dchar c
) @safe pure nothrow @nogc
186 version (Windows
) return c
== ':';
191 /* Combines the isDirSeparator and isDriveSeparator tests. */
192 version (Windows
) private bool isSeparator(dchar c
) @safe pure nothrow @nogc
194 return isDirSeparator(c
) ||
isDriveSeparator(c
);
196 version (Posix
) private alias isSeparator
= isDirSeparator
;
199 /* Helper function that determines the position of the last
200 drive/directory separator in a string. Returns -1 if none
203 private ptrdiff_t
lastSeparator(R
)(R path
)
204 if (isRandomAccessRange
!R
&& isSomeChar
!(ElementType
!R
) ||
207 auto i
= (cast(ptrdiff_t
) path
.length
) - 1;
208 while (i
>= 0 && !isSeparator(path
[i
])) --i
;
215 private bool isUNC(R
)(R path
)
216 if (isRandomAccessRange
!R
&& isSomeChar
!(ElementType
!R
) ||
219 return path
.length
>= 3 && isDirSeparator(path
[0]) && isDirSeparator(path
[1])
220 && !isDirSeparator(path
[2]);
223 private ptrdiff_t
uncRootLength(R
)(R path
)
224 if (isRandomAccessRange
!R
&& isSomeChar
!(ElementType
!R
) ||
226 in { assert(isUNC(path
)); }
230 while (i
< path
.length
&& !isDirSeparator(path
[i
])) ++i
;
234 do { ++j
; } while (j
< path
.length
&& isDirSeparator(path
[j
]));
237 do { ++j
; } while (j
< path
.length
&& !isDirSeparator(path
[j
]));
244 private bool hasDrive(R
)(R path
)
245 if (isRandomAccessRange
!R
&& isSomeChar
!(ElementType
!R
) ||
248 return path
.length
>= 2 && isDriveSeparator(path
[1]);
251 private bool isDriveRoot(R
)(R path
)
252 if (isRandomAccessRange
!R
&& isSomeChar
!(ElementType
!R
) ||
255 return path
.length
>= 3 && isDriveSeparator(path
[1])
256 && isDirSeparator(path
[2]);
261 /* Helper functions that strip leading/trailing slashes and backslashes
264 private auto ltrimDirSeparators(R
)(R path
)
265 if (isSomeFiniteCharInputRange
!R || isNarrowString
!R
)
267 static if (isRandomAccessRange
!R
&& hasSlicing
!R || isNarrowString
!R
)
270 while (i
< path
.length
&& isDirSeparator(path
[i
]))
272 return path
[i
.. path
.length
];
276 while (!path
.empty
&& isDirSeparator(path
.front
))
285 import std
.utf
: byDchar
;
287 assert(ltrimDirSeparators("//abc//").array
== "abc//");
288 assert(ltrimDirSeparators("//abc//"d
).array
== "abc//"d
);
289 assert(ltrimDirSeparators("//abc//".byDchar
).array
== "abc//"d
);
292 private auto rtrimDirSeparators(R
)(R path
)
293 if (isBidirectionalRange
!R
&& isSomeChar
!(ElementType
!R
) ||
296 static if (isRandomAccessRange
!R
&& hasSlicing
!R
&& hasLength
!R || isNarrowString
!R
)
298 auto i
= (cast(ptrdiff_t
) path
.length
) - 1;
299 while (i
>= 0 && isDirSeparator(path
[i
]))
301 return path
[0 .. i
+1];
305 while (!path
.empty
&& isDirSeparator(path
.back
))
315 assert(rtrimDirSeparators("//abc//").array
== "//abc");
316 assert(rtrimDirSeparators("//abc//"d
).array
== "//abc"d
);
318 assert(rtrimDirSeparators(MockBiRange
!char("//abc//")).array
== "//abc");
321 private auto trimDirSeparators(R
)(R path
)
322 if (isBidirectionalRange
!R
&& isSomeChar
!(ElementType
!R
) ||
325 return ltrimDirSeparators(rtrimDirSeparators(path
));
332 assert(trimDirSeparators("//abc//").array
== "abc");
333 assert(trimDirSeparators("//abc//"d
).array
== "abc"d
);
335 assert(trimDirSeparators(MockBiRange
!char("//abc//")).array
== "abc");
338 /** This `enum` is used as a template argument to functions which
339 compare file names, and determines whether the comparison is
340 case sensitive or not.
342 enum CaseSensitive
: bool
344 /// File names are case insensitive
347 /// File names are case sensitive
350 /** The default (or most common) setting for the current platform.
351 That is, `no` on Windows and Mac OS X, and `yes` on all
352 POSIX systems except Darwin (Linux, *BSD, etc.).
354 osDefault
= osDefaultCaseSensitivity
360 assert(baseName
!(CaseSensitive
.no
)("dir/file.EXT", ".ext") == "file");
361 assert(baseName
!(CaseSensitive
.yes
)("dir/file.EXT", ".ext") != "file");
364 assert(relativePath
!(CaseSensitive
.no
)("/FOO/bar", "/foo/baz") == "../bar");
366 assert(relativePath
!(CaseSensitive
.no
)(`c:\FOO\bar`, `c:\foo\baz`) == `..\bar`);
369 version (Windows
) private enum osDefaultCaseSensitivity
= false;
370 else version (Darwin
) private enum osDefaultCaseSensitivity
= false;
371 else version (Posix
) private enum osDefaultCaseSensitivity
= true;
372 else static assert(0);
376 cs = Whether or not suffix matching is case-sensitive.
377 path = A path name. It can be a string, or any random-access range of
379 suffix = An optional suffix to be removed from the file name.
380 Returns: The name of the file in the path name, without any leading
381 directory and with an optional suffix chopped off.
383 If `suffix` is specified, it will be compared to `path`
384 using `filenameCmp!cs`,
385 where `cs` is an optional template parameter determining whether
386 the comparison is case sensitive or not. See the
387 $(LREF filenameCmp) documentation for details.
390 This function $(I only) strips away the specified suffix, which
391 doesn't necessarily have to represent an extension.
392 To remove the extension from a path, regardless of what the extension
393 is, use $(LREF stripExtension).
394 To obtain the filename without leading directories and without
395 an extension, combine the functions like this:
397 assert(baseName(stripExtension("dir/file.ext")) == "file");
401 This function complies with
402 $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html,
403 the POSIX requirements for the 'basename' shell utility)
404 (with suitable adaptations for Windows paths).
406 auto baseName(R
)(return scope R path
)
407 if (isRandomAccessRange
!R
&& hasSlicing
!R
&& isSomeChar
!(ElementType
!R
) && !isSomeString
!R
)
409 return _baseName(path
);
413 auto baseName(C
)(return scope C
[] path
)
416 return _baseName(path
);
420 inout(C
)[] baseName(CaseSensitive cs
= CaseSensitive
.osDefault
, C
, C1
)
421 (return scope inout(C
)[] path
, in C1
[] suffix
)
422 @safe pure //TODO: nothrow (because of filenameCmp())
423 if (isSomeChar
!C
&& isSomeChar
!C1
)
425 auto p
= baseName(path
);
426 if (p
.length
> suffix
.length
427 && filenameCmp
!cs(cast(const(C
)[])p
[$-suffix
.length
.. $], suffix
) == 0)
429 return p
[0 .. $-suffix
.length
];
437 assert(baseName("dir/file.ext") == "file.ext");
438 assert(baseName("dir/file.ext", ".ext") == "file");
439 assert(baseName("dir/file.ext", ".xyz") == "file.ext");
440 assert(baseName("dir/filename", "name") == "file");
441 assert(baseName("dir/subdir/") == "subdir");
445 assert(baseName(`d:file.ext`) == "file.ext");
446 assert(baseName(`d:\dir\file.ext`) == "file.ext");
452 assert(baseName("").empty
);
453 assert(baseName("file.ext"w
) == "file.ext");
454 assert(baseName("file.ext"d
, ".ext") == "file");
455 assert(baseName("file", "file"w
.dup
) == "file");
456 assert(baseName("dir/file.ext"d
.dup
) == "file.ext");
457 assert(baseName("dir/file.ext", ".ext"d
) == "file");
458 assert(baseName("dir/file"w
, "file"d
) == "file");
459 assert(baseName("dir///subdir////") == "subdir");
460 assert(baseName("dir/subdir.ext/", ".ext") == "subdir");
461 assert(baseName("dir/subdir/".dup
, "subdir") == "subdir");
462 assert(baseName("/"w
.dup
) == "/");
463 assert(baseName("//"d
.dup
) == "/");
464 assert(baseName("///") == "/");
466 assert(baseName
!(CaseSensitive
.yes
)("file.ext", ".EXT") == "file.ext");
467 assert(baseName
!(CaseSensitive
.no
)("file.ext", ".EXT") == "file");
470 auto r
= MockRange
!(immutable(char))(`dir/file.ext`);
471 auto s
= r
.baseName();
472 foreach (i
, c
; `file`)
478 assert(baseName(`dir\file.ext`) == `file.ext`);
479 assert(baseName(`dir\file.ext`, `.ext`) == `file`);
480 assert(baseName(`dir\file`, `file`) == `file`);
481 assert(baseName(`d:file.ext`) == `file.ext`);
482 assert(baseName(`d:file.ext`, `.ext`) == `file`);
483 assert(baseName(`d:file`, `file`) == `file`);
484 assert(baseName(`dir\\subdir\\\`) == `subdir`);
485 assert(baseName(`dir\subdir.ext\`, `.ext`) == `subdir`);
486 assert(baseName(`dir\subdir\`, `subdir`) == `subdir`);
487 assert(baseName(`\`) == `\`);
488 assert(baseName(`\\`) == `\`);
489 assert(baseName(`\\\`) == `\`);
490 assert(baseName(`d:\`) == `\`);
491 assert(baseName(`d:`).empty
);
492 assert(baseName(`\\server\share\file`) == `file`);
493 assert(baseName(`\\server\share\`) == `\`);
494 assert(baseName(`\\server\share`) == `\`);
496 auto r
= MockRange
!(immutable(char))(`\\server\share`);
497 auto s
= r
.baseName();
502 assert(baseName(stripExtension("dir/file.ext")) == "file");
504 static assert(baseName("dir/file.ext") == "file.ext");
505 static assert(baseName("dir/file.ext", ".ext") == "file");
507 static struct DirEntry
{ string s
; alias s
this; }
508 assert(baseName(DirEntry("dir/file.ext")) == "file.ext");
513 assert(testAliasedString
!baseName("file"));
515 enum S
: string
{ a
= "file/path/to/test" }
516 assert(S
.a
.baseName
== "test");
518 char[S
.a
.length
] sa
= S
.a
[];
519 assert(sa
.baseName
== "test");
522 private R
_baseName(R
)(return scope R path
)
523 if (isRandomAccessRange
!R
&& hasSlicing
!R
&& isSomeChar
!(ElementType
!R
) || isNarrowString
!R
)
525 auto p1
= stripDrive(path
);
528 version (Windows
) if (isUNC(path
))
530 static if (isSomeString
!R
)
533 return p1
; // which is empty
536 auto p2
= rtrimDirSeparators(p1
);
537 if (p2
.empty
) return p1
[0 .. 1];
539 return p2
[lastSeparator(p2
)+1 .. p2
.length
];
542 /** Returns the parent directory of `path`. On Windows, this
543 includes the drive letter if present. If `path` is a relative path and
544 the parent directory is the current working directory, returns `"."`.
550 A slice of `path` or `"."`.
553 This function complies with
554 $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html,
555 the POSIX requirements for the 'dirname' shell utility)
556 (with suitable adaptations for Windows paths).
558 auto dirName(R
)(return scope R path
)
559 if (isRandomAccessRange
!R
&& hasSlicing
!R
&& hasLength
!R
&& isSomeChar
!(ElementType
!R
) && !isSomeString
!R
)
561 return _dirName(path
);
565 auto dirName(C
)(return scope C
[] path
)
568 return _dirName(path
);
574 assert(dirName("") == ".");
575 assert(dirName("file"w
) == ".");
576 assert(dirName("dir/"d
) == ".");
577 assert(dirName("dir///") == ".");
578 assert(dirName("dir/file"w
.dup
) == "dir");
579 assert(dirName("dir///file"d
.dup
) == "dir");
580 assert(dirName("dir/subdir/") == "dir");
581 assert(dirName("/dir/file"w
) == "/dir");
582 assert(dirName("/file"d
) == "/");
583 assert(dirName("/") == "/");
584 assert(dirName("///") == "/");
588 assert(dirName(`dir\`) == `.`);
589 assert(dirName(`dir\\\`) == `.`);
590 assert(dirName(`dir\file`) == `dir`);
591 assert(dirName(`dir\\\file`) == `dir`);
592 assert(dirName(`dir\subdir\`) == `dir`);
593 assert(dirName(`\dir\file`) == `\dir`);
594 assert(dirName(`\file`) == `\`);
595 assert(dirName(`\`) == `\`);
596 assert(dirName(`\\\`) == `\`);
597 assert(dirName(`d:`) == `d:`);
598 assert(dirName(`d:file`) == `d:`);
599 assert(dirName(`d:\`) == `d:\`);
600 assert(dirName(`d:\file`) == `d:\`);
601 assert(dirName(`d:\dir\file`) == `d:\dir`);
602 assert(dirName(`\\server\share\dir\file`) == `\\server\share\dir`);
603 assert(dirName(`\\server\share\file`) == `\\server\share`);
604 assert(dirName(`\\server\share\`) == `\\server\share`);
605 assert(dirName(`\\server\share`) == `\\server\share`);
611 assert(testAliasedString
!dirName("file"));
613 enum S
: string
{ a
= "file/path/to/test" }
614 assert(S
.a
.dirName
== "file/path/to");
616 char[S
.a
.length
] sa
= S
.a
[];
617 assert(sa
.dirName
== "file/path/to");
622 static assert(dirName("dir/file") == "dir");
625 import std
.utf
: byChar
, byWchar
, byDchar
;
627 assert(dirName("".byChar
).array
== ".");
628 assert(dirName("file"w
.byWchar
).array
== "."w
);
629 assert(dirName("dir/"d
.byDchar
).array
== "."d
);
630 assert(dirName("dir///".byChar
).array
== ".");
631 assert(dirName("dir/subdir/".byChar
).array
== "dir");
632 assert(dirName("/dir/file"w
.byWchar
).array
== "/dir"w
);
633 assert(dirName("/file"d
.byDchar
).array
== "/"d
);
634 assert(dirName("/".byChar
).array
== "/");
635 assert(dirName("///".byChar
).array
== "/");
639 assert(dirName(`dir\`.byChar
).array
== `.`);
640 assert(dirName(`dir\\\`.byChar
).array
== `.`);
641 assert(dirName(`dir\file`.byChar
).array
== `dir`);
642 assert(dirName(`dir\\\file`.byChar
).array
== `dir`);
643 assert(dirName(`dir\subdir\`.byChar
).array
== `dir`);
644 assert(dirName(`\dir\file`.byChar
).array
== `\dir`);
645 assert(dirName(`\file`.byChar
).array
== `\`);
646 assert(dirName(`\`.byChar
).array
== `\`);
647 assert(dirName(`\\\`.byChar
).array
== `\`);
648 assert(dirName(`d:`.byChar
).array
== `d:`);
649 assert(dirName(`d:file`.byChar
).array
== `d:`);
650 assert(dirName(`d:\`.byChar
).array
== `d:\`);
651 assert(dirName(`d:\file`.byChar
).array
== `d:\`);
652 assert(dirName(`d:\dir\file`.byChar
).array
== `d:\dir`);
653 assert(dirName(`\\server\share\dir\file`.byChar
).array
== `\\server\share\dir`);
654 assert(dirName(`\\server\share\file`) == `\\server\share`);
655 assert(dirName(`\\server\share\`.byChar
).array
== `\\server\share`);
656 assert(dirName(`\\server\share`.byChar
).array
== `\\server\share`);
659 //static assert(dirName("dir/file".byChar).array == "dir");
662 private auto _dirName(R
)(return scope R path
)
664 static auto result(bool dot
, typeof(path
[0 .. 1]) p
)
666 static if (isSomeString
!R
)
667 return dot ?
"." : p
;
670 import std
.range
: choose
, only
;
671 return choose(dot
, only(cast(ElementEncodingType
!R
)'.'), p
);
676 return result(true, path
[0 .. 0]);
678 auto p
= rtrimDirSeparators(path
);
680 return result(false, path
[0 .. 1]);
684 if (isUNC(p
) && uncRootLength(p
) == p
.length
)
685 return result(false, p
);
687 if (p
.length
== 2 && isDriveSeparator(p
[1]) && path
.length
> 2)
688 return result(false, path
[0 .. 3]);
691 auto i
= lastSeparator(p
);
693 return result(true, p
);
695 return result(false, p
[0 .. 1]);
699 // If the directory part is either d: or d:\
700 // do not chop off the last symbol.
701 if (isDriveSeparator(p
[i
]) ||
isDriveSeparator(p
[i
-1]))
702 return result(false, p
[0 .. i
+1]);
704 // Remove any remaining trailing (back)slashes.
705 return result(false, rtrimDirSeparators(p
[0 .. i
]));
708 /** Returns the root directory of the specified path, or `null` if the
717 auto rootName(R
)(R path
)
718 if (isRandomAccessRange
!R
&& hasSlicing
!R
&& hasLength
!R
&& isSomeChar
!(ElementType
!R
) && !isSomeString
!R
)
720 return _rootName(path
);
724 auto rootName(C
)(C
[] path
)
727 return _rootName(path
);
733 assert(rootName("") is null);
734 assert(rootName("foo") is null);
735 assert(rootName("/") == "/");
736 assert(rootName("/foo/bar") == "/");
740 assert(rootName("d:foo") is null);
741 assert(rootName(`d:\foo`) == `d:\`);
742 assert(rootName(`\\server\share\foo`) == `\\server\share`);
743 assert(rootName(`\\server\share`) == `\\server\share`);
749 assert(testAliasedString
!rootName("/foo/bar"));
751 enum S
: string
{ a
= "/foo/bar" }
752 assert(S
.a
.rootName
== "/");
754 char[S
.a
.length
] sa
= S
.a
[];
755 assert(sa
.rootName
== "/");
761 import std
.utf
: byChar
;
763 assert(rootName("".byChar
).array
== "");
764 assert(rootName("foo".byChar
).array
== "");
765 assert(rootName("/".byChar
).array
== "/");
766 assert(rootName("/foo/bar".byChar
).array
== "/");
770 assert(rootName("d:foo".byChar
).array
== "");
771 assert(rootName(`d:\foo`.byChar
).array
== `d:\`);
772 assert(rootName(`\\server\share\foo`.byChar
).array
== `\\server\share`);
773 assert(rootName(`\\server\share`.byChar
).array
== `\\server\share`);
777 private auto _rootName(R
)(R path
)
784 if (isDirSeparator(path
[0])) return path
[0 .. 1];
786 else version (Windows
)
788 if (isDirSeparator(path
[0]))
790 if (isUNC(path
)) return path
[0 .. uncRootLength(path
)];
791 else return path
[0 .. 1];
793 else if (path
.length
>= 3 && isDriveSeparator(path
[1]) &&
794 isDirSeparator(path
[2]))
799 else static assert(0, "unsupported platform");
801 assert(!isRooted(path
));
803 static if (is(StringTypeOf
!R
))
804 return null; // legacy code may rely on null return rather than slice
810 Get the drive portion of a path.
813 path = string or range of characters
816 A slice of `path` that is the drive, or an empty range if the drive
817 is not specified. In the case of UNC paths, the network share
820 Always returns an empty range on POSIX.
822 auto driveName(R
)(R path
)
823 if (isRandomAccessRange
!R
&& hasSlicing
!R
&& hasLength
!R
&& isSomeChar
!(ElementType
!R
) && !isSomeString
!R
)
825 return _driveName(path
);
829 auto driveName(C
)(C
[] path
)
832 return _driveName(path
);
838 import std
.range
: empty
;
839 version (Posix
) assert(driveName("c:/foo").empty
);
842 assert(driveName(`dir\file`).empty
);
843 assert(driveName(`d:file`) == "d:");
844 assert(driveName(`d:\file`) == "d:");
845 assert(driveName("d:") == "d:");
846 assert(driveName(`\\server\share\file`) == `\\server\share`);
847 assert(driveName(`\\server\share\`) == `\\server\share`);
848 assert(driveName(`\\server\share`) == `\\server\share`);
850 static assert(driveName(`d:\file`) == "d:");
856 assert(testAliasedString
!driveName("d:/file"));
859 immutable result
= "";
860 else version (Windows
)
861 immutable result
= "d:";
863 enum S
: string
{ a
= "d:/file" }
864 assert(S
.a
.driveName
== result
);
866 char[S
.a
.length
] sa
= S
.a
[];
867 assert(sa
.driveName
== result
);
873 import std
.utf
: byChar
;
875 version (Posix
) assert(driveName("c:/foo".byChar
).empty
);
878 assert(driveName(`dir\file`.byChar
).empty
);
879 assert(driveName(`d:file`.byChar
).array
== "d:");
880 assert(driveName(`d:\file`.byChar
).array
== "d:");
881 assert(driveName("d:".byChar
).array
== "d:");
882 assert(driveName(`\\server\share\file`.byChar
).array
== `\\server\share`);
883 assert(driveName(`\\server\share\`.byChar
).array
== `\\server\share`);
884 assert(driveName(`\\server\share`.byChar
).array
== `\\server\share`);
886 static assert(driveName(`d:\file`).array
== "d:");
890 private auto _driveName(R
)(R path
)
896 else if (isUNC(path
))
897 return path
[0 .. uncRootLength(path
)];
899 static if (isSomeString
!R
)
900 return cast(ElementEncodingType
!R
[]) null; // legacy code may rely on null return rather than slice
905 /** Strips the drive from a Windows path. On POSIX, the path is returned
911 Returns: A slice of path without the drive component.
913 auto stripDrive(R
)(R path
)
914 if (isRandomAccessRange
!R
&& hasSlicing
!R
&& isSomeChar
!(ElementType
!R
) && !isSomeString
!R
)
916 return _stripDrive(path
);
920 auto stripDrive(C
)(C
[] path
)
923 return _stripDrive(path
);
931 assert(stripDrive(`d:\dir\file`) == `\dir\file`);
932 assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`);
938 assert(testAliasedString
!stripDrive("d:/dir/file"));
941 immutable result
= "d:/dir/file";
942 else version (Windows
)
943 immutable result
= "/dir/file";
945 enum S
: string
{ a
= "d:/dir/file" }
946 assert(S
.a
.stripDrive
== result
);
948 char[S
.a
.length
] sa
= S
.a
[];
949 assert(sa
.stripDrive
== result
);
956 assert(stripDrive(`d:\dir\file`) == `\dir\file`);
957 assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`);
958 static assert(stripDrive(`d:\dir\file`) == `\dir\file`);
960 auto r
= MockRange
!(immutable(char))(`d:\dir\file`);
961 auto s
= r
.stripDrive();
962 foreach (i
, c
; `\dir\file`)
967 assert(stripDrive(`d:\dir\file`) == `d:\dir\file`);
969 auto r
= MockRange
!(immutable(char))(`d:\dir\file`);
970 auto s
= r
.stripDrive();
971 foreach (i
, c
; `d:\dir\file`)
976 private auto _stripDrive(R
)(R path
)
980 if (hasDrive
!(BaseOf
!R
)(path
)) return path
[2 .. path
.length
];
981 else if (isUNC
!(BaseOf
!R
)(path
)) return path
[uncRootLength
!(BaseOf
!R
)(path
) .. path
.length
];
987 /* Helper function that returns the position of the filename/extension
988 separator dot in path.
991 path = file spec as string or indexable range
993 index of extension separator (the dot), or -1 if not found
995 private ptrdiff_t
extSeparatorPos(R
)(const R path
)
996 if (isRandomAccessRange
!R
&& hasLength
!R
&& isSomeChar
!(ElementType
!R
) ||
999 for (auto i
= path
.length
; i
-- > 0 && !isSeparator(path
[i
]); )
1001 if (path
[i
] == '.' && i
> 0 && !isSeparator(path
[i
-1]))
1009 assert(extSeparatorPos("file") == -1);
1010 assert(extSeparatorPos("file.ext"w
) == 4);
1011 assert(extSeparatorPos("file.ext1.ext2"d
) == 9);
1012 assert(extSeparatorPos(".foo".dup
) == -1);
1013 assert(extSeparatorPos(".foo.ext"w
.dup
) == 4);
1018 assert(extSeparatorPos("dir/file"d
.dup
) == -1);
1019 assert(extSeparatorPos("dir/file.ext") == 8);
1020 assert(extSeparatorPos("dir/file.ext1.ext2"w
) == 13);
1021 assert(extSeparatorPos("dir/.foo"d
) == -1);
1022 assert(extSeparatorPos("dir/.foo.ext".dup
) == 8);
1026 assert(extSeparatorPos("dir\\file") == -1);
1027 assert(extSeparatorPos("dir\\file.ext") == 8);
1028 assert(extSeparatorPos("dir\\file.ext1.ext2") == 13);
1029 assert(extSeparatorPos("dir\\.foo") == -1);
1030 assert(extSeparatorPos("dir\\.foo.ext") == 8);
1032 assert(extSeparatorPos("d:file") == -1);
1033 assert(extSeparatorPos("d:file.ext") == 6);
1034 assert(extSeparatorPos("d:file.ext1.ext2") == 11);
1035 assert(extSeparatorPos("d:.foo") == -1);
1036 assert(extSeparatorPos("d:.foo.ext") == 6);
1039 static assert(extSeparatorPos("file") == -1);
1040 static assert(extSeparatorPos("file.ext"w
) == 4);
1045 Params: path = A path name.
1046 Returns: The _extension part of a file name, including the dot.
1048 If there is no _extension, `null` is returned.
1050 auto extension(R
)(R path
)
1051 if (isRandomAccessRange
!R
&& hasSlicing
!R
&& isSomeChar
!(ElementType
!R
) ||
1054 auto i
= extSeparatorPos
!(BaseOf
!R
)(path
);
1057 static if (is(StringTypeOf
!R
))
1058 return StringTypeOf
!R
.init
[]; // which is null
1060 return path
[0 .. 0];
1062 else return path
[i
.. path
.length
];
1068 import std
.range
: empty
;
1069 assert(extension("file").empty
);
1070 assert(extension("file.") == ".");
1071 assert(extension("file.ext"w
) == ".ext");
1072 assert(extension("file.ext1.ext2"d
) == ".ext2");
1073 assert(extension(".foo".dup
).empty
);
1074 assert(extension(".foo.ext"w
.dup
) == ".ext");
1076 static assert(extension("file").empty
);
1077 static assert(extension("file.ext") == ".ext");
1083 auto r
= MockRange
!(immutable(char))(`file.ext1.ext2`);
1084 auto s
= r
.extension();
1085 foreach (i
, c
; `.ext2`)
1089 static struct DirEntry
{ string s
; alias s
this; }
1090 assert(extension(DirEntry("file")).empty
);
1094 /** Remove extension from path.
1097 path = string or range to be sliced
1100 slice of path with the extension (if any) stripped off
1102 auto stripExtension(R
)(R path
)
1103 if (isRandomAccessRange
!R
&& hasSlicing
!R
&& hasLength
!R
&& isSomeChar
!(ElementType
!R
) && !isSomeString
!R
)
1105 return _stripExtension(path
);
1109 auto stripExtension(C
)(C
[] path
)
1112 return _stripExtension(path
);
1118 assert(stripExtension("file") == "file");
1119 assert(stripExtension("file.ext") == "file");
1120 assert(stripExtension("file.ext1.ext2") == "file.ext1");
1121 assert(stripExtension("file.") == "file");
1122 assert(stripExtension(".file") == ".file");
1123 assert(stripExtension(".file.ext") == ".file");
1124 assert(stripExtension("dir/file.ext") == "dir/file");
1129 assert(testAliasedString
!stripExtension("file"));
1131 enum S
: string
{ a
= "foo.bar" }
1132 assert(S
.a
.stripExtension
== "foo");
1134 char[S
.a
.length
] sa
= S
.a
[];
1135 assert(sa
.stripExtension
== "foo");
1140 assert(stripExtension("file.ext"w
) == "file");
1141 assert(stripExtension("file.ext1.ext2"d
) == "file.ext1");
1144 import std
.utf
: byChar
, byWchar
, byDchar
;
1146 assert(stripExtension("file".byChar
).array
== "file");
1147 assert(stripExtension("file.ext"w
.byWchar
).array
== "file");
1148 assert(stripExtension("file.ext1.ext2"d
.byDchar
).array
== "file.ext1");
1151 private auto _stripExtension(R
)(R path
)
1153 immutable i
= extSeparatorPos(path
);
1154 return i
== -1 ? path
: path
[0 .. i
];
1157 /** Sets or replaces an extension.
1159 If the filename already has an extension, it is replaced. If not, the
1160 extension is simply appended to the filename. Including a leading dot
1161 in `ext` is optional.
1163 If the extension is empty, this function is equivalent to
1164 $(LREF stripExtension).
1166 This function normally allocates a new string (the possible exception
1167 being the case when path is immutable and doesn't already have an
1172 ext = The new extension
1174 Returns: A string containing the path given by `path`, but where
1175 the extension has been set to `ext`.
1178 $(LREF withExtension) which does not allocate and returns a lazy range.
1180 immutable(C1
)[] setExtension(C1
, C2
)(in C1
[] path
, in C2
[] ext
)
1181 if (isSomeChar
!C1
&& !is(C1
== immutable) && is(immutable C1
== immutable C2
))
1185 import std
.conv
: to
;
1186 return withExtension(path
, ext
).to
!(typeof(return));
1195 immutable(C1
)[] setExtension(C1
, C2
)(immutable(C1
)[] path
, const(C2
)[] ext
)
1196 if (isSomeChar
!C1
&& is(immutable C1
== immutable C2
))
1198 if (ext
.length
== 0)
1199 return stripExtension(path
);
1203 import std
.conv
: to
;
1204 return withExtension(path
, ext
).to
!(typeof(return));
1215 assert(setExtension("file", "ext") == "file.ext");
1216 assert(setExtension("file"w
, ".ext"w
) == "file.ext");
1217 assert(setExtension("file."d
, "ext"d
) == "file.ext");
1218 assert(setExtension("file.", ".ext") == "file.ext");
1219 assert(setExtension("file.old"w
, "new"w
) == "file.new");
1220 assert(setExtension("file.old"d
, ".new"d
) == "file.new");
1225 assert(setExtension("file"w
.dup
, "ext"w
) == "file.ext");
1226 assert(setExtension("file"w
.dup
, ".ext"w
) == "file.ext");
1227 assert(setExtension("file."w
, "ext"w
.dup
) == "file.ext");
1228 assert(setExtension("file."w
, ".ext"w
.dup
) == "file.ext");
1229 assert(setExtension("file.old"d
.dup
, "new"d
) == "file.new");
1230 assert(setExtension("file.old"d
.dup
, ".new"d
) == "file.new");
1232 static assert(setExtension("file", "ext") == "file.ext");
1233 static assert(setExtension("file.old", "new") == "file.new");
1235 static assert(setExtension("file"w
.dup
, "ext"w
) == "file.ext");
1236 static assert(setExtension("file.old"d
.dup
, "new"d
) == "file.new");
1238 // https://issues.dlang.org/show_bug.cgi?id=10601
1239 assert(setExtension("file", "") == "file");
1240 assert(setExtension("file.ext", "") == "file");
1244 * Replace existing extension on filespec with new one.
1247 * path = string or random access range representing a filespec
1248 * ext = the new extension
1250 * Range with `path`'s extension (if any) replaced with `ext`.
1251 * The element encoding type of the returned range will be the same as `path`'s.
1253 * $(LREF setExtension)
1255 auto withExtension(R
, C
)(R path
, C
[] ext
)
1256 if (isRandomAccessRange
!R
&& hasSlicing
!R
&& hasLength
!R
&& isSomeChar
!(ElementType
!R
) &&
1257 !isSomeString
!R
&& isSomeChar
!C
)
1259 return _withExtension(path
, ext
);
1263 auto withExtension(C1
, C2
)(C1
[] path
, C2
[] ext
)
1264 if (isSomeChar
!C1
&& isSomeChar
!C2
)
1266 return _withExtension(path
, ext
);
1273 assert(withExtension("file", "ext").array
== "file.ext");
1274 assert(withExtension("file"w
, ".ext"w
).array
== "file.ext");
1275 assert(withExtension("file.ext"w
, ".").array
== "file.");
1277 import std
.utf
: byChar
, byWchar
;
1278 assert(withExtension("file".byChar
, "ext").array
== "file.ext");
1279 assert(withExtension("file"w
.byWchar
, ".ext"w
).array
== "file.ext"w
);
1280 assert(withExtension("file.ext"w
.byWchar
, ".").array
== "file."w
);
1285 import std
.algorithm
.comparison
: equal
;
1287 assert(testAliasedString
!withExtension("file", "ext"));
1289 enum S
: string
{ a
= "foo.bar" }
1290 assert(equal(S
.a
.withExtension(".txt"), "foo.txt"));
1292 char[S
.a
.length
] sa
= S
.a
[];
1293 assert(equal(sa
.withExtension(".txt"), "foo.txt"));
1296 private auto _withExtension(R
, C
)(R path
, C
[] ext
)
1298 import std
.range
: only
, chain
;
1299 import std
.utf
: byUTF
;
1301 alias CR
= Unqual
!(ElementEncodingType
!R
);
1302 auto dot
= only(CR('.'));
1303 if (ext
.length
== 0 || ext
[0] == '.')
1304 dot
.popFront(); // so dot is an empty range, too
1305 return chain(stripExtension(path
).byUTF
!CR
, dot
, ext
.byUTF
!CR
);
1310 ext = The default extension to use.
1312 Returns: The path given by `path`, with the extension given by `ext`
1313 appended if the path doesn't already have one.
1315 Including the dot in the extension is optional.
1317 This function always allocates a new string, except in the case when
1318 path is immutable and already has an extension.
1320 immutable(C1
)[] defaultExtension(C1
, C2
)(in C1
[] path
, in C2
[] ext
)
1321 if (isSomeChar
!C1
&& is(immutable C1
== immutable C2
))
1323 import std
.conv
: to
;
1324 return withDefaultExtension(path
, ext
).to
!(typeof(return));
1330 assert(defaultExtension("file", "ext") == "file.ext");
1331 assert(defaultExtension("file", ".ext") == "file.ext");
1332 assert(defaultExtension("file.", "ext") == "file.");
1333 assert(defaultExtension("file.old", "new") == "file.old");
1334 assert(defaultExtension("file.old", ".new") == "file.old");
1339 assert(defaultExtension("file"w
.dup
, "ext"w
) == "file.ext");
1340 assert(defaultExtension("file.old"d
.dup
, "new"d
) == "file.old");
1342 static assert(defaultExtension("file", "ext") == "file.ext");
1343 static assert(defaultExtension("file.old", "new") == "file.old");
1345 static assert(defaultExtension("file"w
.dup
, "ext"w
) == "file.ext");
1346 static assert(defaultExtension("file.old"d
.dup
, "new"d
) == "file.old");
1350 /********************************
1351 * Set the extension of `path` to `ext` if `path` doesn't have one.
1354 * path = filespec as string or range
1355 * ext = extension, may have leading '.'
1357 * range with the result
1359 auto withDefaultExtension(R
, C
)(R path
, C
[] ext
)
1360 if (isRandomAccessRange
!R
&& hasSlicing
!R
&& hasLength
!R
&& isSomeChar
!(ElementType
!R
) &&
1361 !isSomeString
!R
&& isSomeChar
!C
)
1363 return _withDefaultExtension(path
, ext
);
1367 auto withDefaultExtension(C1
, C2
)(C1
[] path
, C2
[] ext
)
1368 if (isSomeChar
!C1
&& isSomeChar
!C2
)
1370 return _withDefaultExtension(path
, ext
);
1377 assert(withDefaultExtension("file", "ext").array
== "file.ext");
1378 assert(withDefaultExtension("file"w
, ".ext").array
== "file.ext"w
);
1379 assert(withDefaultExtension("file.", "ext").array
== "file.");
1380 assert(withDefaultExtension("file", "").array
== "file.");
1382 import std
.utf
: byChar
, byWchar
;
1383 assert(withDefaultExtension("file".byChar
, "ext").array
== "file.ext");
1384 assert(withDefaultExtension("file"w
.byWchar
, ".ext").array
== "file.ext"w
);
1385 assert(withDefaultExtension("file.".byChar
, "ext"d
).array
== "file.");
1386 assert(withDefaultExtension("file".byChar
, "").array
== "file.");
1391 import std
.algorithm
.comparison
: equal
;
1393 assert(testAliasedString
!withDefaultExtension("file", "ext"));
1395 enum S
: string
{ a
= "foo" }
1396 assert(equal(S
.a
.withDefaultExtension(".txt"), "foo.txt"));
1398 char[S
.a
.length
] sa
= S
.a
[];
1399 assert(equal(sa
.withDefaultExtension(".txt"), "foo.txt"));
1402 private auto _withDefaultExtension(R
, C
)(R path
, C
[] ext
)
1404 import std
.range
: only
, chain
;
1405 import std
.utf
: byUTF
;
1407 alias CR
= Unqual
!(ElementEncodingType
!R
);
1408 auto dot
= only(CR('.'));
1409 immutable i
= extSeparatorPos(path
);
1412 if (ext
.length
> 0 && ext
[0] == '.')
1413 ext
= ext
[1 .. $]; // remove any leading . from ext[]
1417 // path already has an extension, so make these empty
1421 return chain(path
.byUTF
!CR
, dot
, ext
.byUTF
!CR
);
1424 /** Combines one or more path segments.
1426 This function takes a set of path segments, given as an input
1427 range of string elements or as a set of string arguments,
1428 and concatenates them with each other. Directory separators
1429 are inserted between segments if necessary. If any of the
1430 path segments are absolute (as defined by $(LREF isAbsolute)), the
1431 preceding segments will be dropped.
1433 On Windows, if one of the path segments are rooted, but not absolute
1434 (e.g. $(D `\foo`)), all preceding path segments down to the previous
1435 root will be dropped. (See below for an example.)
1437 This function always allocates memory to hold the resulting path.
1438 The variadic overload is guaranteed to only perform a single
1439 allocation, as is the range version if `paths` is a forward
1443 segments = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
1444 of segments to assemble the path from.
1445 Returns: The assembled path.
1447 immutable(ElementEncodingType
!(ElementType
!Range
))[] buildPath(Range
)(scope Range segments
)
1448 if (isInputRange
!Range
&& !isInfinite
!Range
&& isSomeString
!(ElementType
!Range
))
1450 if (segments
.empty
) return null;
1452 // If this is a forward range, we can pre-calculate a maximum length.
1453 static if (isForwardRange
!Range
)
1455 auto segments2
= segments
.save
;
1457 foreach (segment
; segments2
) precalc
+= segment
.length
+ 1;
1459 // Otherwise, just venture a guess and resize later if necessary.
1460 else size_t precalc
= 255;
1462 auto buf
= new Unqual
!(ElementEncodingType
!(ElementType
!Range
))[](precalc
);
1464 foreach (segment
; segments
)
1466 if (segment
.empty
) continue;
1467 static if (!isForwardRange
!Range
)
1469 immutable neededLength
= pos
+ segment
.length
+ 1;
1470 if (buf
.length
< neededLength
)
1471 buf
.length
= reserve(buf
, neededLength
+ buf
.length
/2);
1473 auto r
= chainPath(buf
[0 .. pos
], segment
);
1482 static U
trustedCast(U
, V
)(V v
) @trusted pure nothrow { return cast(U
) v
; }
1483 return trustedCast
!(typeof(return))(buf
[0 .. pos
]);
1487 immutable(C
)[] buildPath(C
)(const(C
)[][] paths
...)
1491 return buildPath
!(typeof(paths
))(paths
);
1499 assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1500 assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz");
1501 assert(buildPath("/foo", "/bar") == "/bar");
1506 assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1507 assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1508 assert(buildPath("foo", `d:\bar`) == `d:\bar`);
1509 assert(buildPath("foo", `\bar`) == `\bar`);
1510 assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`);
1514 @system unittest // non-documented
1517 // ir() wraps an array in a plain (i.e. non-forward) input range, so that
1518 // we can test both code paths
1519 InputRange
!(C
[]) ir(C
)(C
[][] p
...) { return inputRangeObject(p
.dup
); }
1522 assert(buildPath("foo") == "foo");
1523 assert(buildPath("/foo/") == "/foo/");
1524 assert(buildPath("foo", "bar") == "foo/bar");
1525 assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1526 assert(buildPath("foo/".dup
, "bar") == "foo/bar");
1527 assert(buildPath("foo///", "bar".dup
) == "foo///bar");
1528 assert(buildPath("/foo"w
, "bar"w
) == "/foo/bar");
1529 assert(buildPath("foo"w
.dup
, "/bar"w
) == "/bar");
1530 assert(buildPath("foo"w
, "bar/"w
.dup
) == "foo/bar/");
1531 assert(buildPath("/"d
, "foo"d
) == "/foo");
1532 assert(buildPath(""d
.dup
, "foo"d
) == "foo");
1533 assert(buildPath("foo"d
, ""d
.dup
) == "foo");
1534 assert(buildPath("foo", "bar".dup
, "baz") == "foo/bar/baz");
1535 assert(buildPath("foo"w
, "/bar"w
, "baz"w
.dup
) == "/bar/baz");
1537 static assert(buildPath("foo", "bar", "baz") == "foo/bar/baz");
1538 static assert(buildPath("foo", "/bar", "baz") == "/bar/baz");
1540 // The following are mostly duplicates of the above, except that the
1541 // range version does not accept mixed constness.
1542 assert(buildPath(ir("foo")) == "foo");
1543 assert(buildPath(ir("/foo/")) == "/foo/");
1544 assert(buildPath(ir("foo", "bar")) == "foo/bar");
1545 assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1546 assert(buildPath(ir("foo/".dup
, "bar".dup
)) == "foo/bar");
1547 assert(buildPath(ir("foo///".dup
, "bar".dup
)) == "foo///bar");
1548 assert(buildPath(ir("/foo"w
, "bar"w
)) == "/foo/bar");
1549 assert(buildPath(ir("foo"w
.dup
, "/bar"w
.dup
)) == "/bar");
1550 assert(buildPath(ir("foo"w
.dup
, "bar/"w
.dup
)) == "foo/bar/");
1551 assert(buildPath(ir("/"d
, "foo"d
)) == "/foo");
1552 assert(buildPath(ir(""d
.dup
, "foo"d
.dup
)) == "foo");
1553 assert(buildPath(ir("foo"d
, ""d
)) == "foo");
1554 assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz");
1555 assert(buildPath(ir("foo"w
.dup
, "/bar"w
.dup
, "baz"w
.dup
)) == "/bar/baz");
1559 assert(buildPath("foo") == "foo");
1560 assert(buildPath(`\foo/`) == `\foo/`);
1561 assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1562 assert(buildPath("foo", `\bar`) == `\bar`);
1563 assert(buildPath(`c:\foo`, "bar") == `c:\foo\bar`);
1564 assert(buildPath("foo"w
, `d:\bar`w
.dup
) == `d:\bar`);
1565 assert(buildPath(`c:\foo\bar`, `\baz`) == `c:\baz`);
1566 assert(buildPath(`\\foo\bar\baz`d
, `foo`d
, `\bar`d
) == `\\foo\bar\bar`d
);
1568 static assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`);
1569 static assert(buildPath("foo", `c:\bar`, "baz") == `c:\bar\baz`);
1571 assert(buildPath(ir("foo")) == "foo");
1572 assert(buildPath(ir(`\foo/`)) == `\foo/`);
1573 assert(buildPath(ir("foo", "bar", "baz")) == `foo\bar\baz`);
1574 assert(buildPath(ir("foo", `\bar`)) == `\bar`);
1575 assert(buildPath(ir(`c:\foo`, "bar")) == `c:\foo\bar`);
1576 assert(buildPath(ir("foo"w
.dup
, `d:\bar`w
.dup
)) == `d:\bar`);
1577 assert(buildPath(ir(`c:\foo\bar`, `\baz`)) == `c:\baz`);
1578 assert(buildPath(ir(`\\foo\bar\baz`d
, `foo`d
, `\bar`d
)) == `\\foo\bar\bar`d
);
1581 // Test that allocation works as it should.
1582 auto manyShort
= "aaa".repeat(1000).array();
1583 auto manyShortCombined
= join(manyShort
, dirSeparator
);
1584 assert(buildPath(manyShort
) == manyShortCombined
);
1585 assert(buildPath(ir(manyShort
)) == manyShortCombined
);
1587 auto fewLong
= 'b'.repeat(500).array().repeat(10).array();
1588 auto fewLongCombined
= join(fewLong
, dirSeparator
);
1589 assert(buildPath(fewLong
) == fewLongCombined
);
1590 assert(buildPath(ir(fewLong
)) == fewLongCombined
);
1595 // Test for https://issues.dlang.org/show_bug.cgi?id=7397
1596 string
[] ary
= ["a", "b"];
1599 assert(buildPath(ary
) == "a/b");
1601 else version (Windows
)
1603 assert(buildPath(ary
) == `a\b`);
1609 * Concatenate path segments together to form one path.
1612 * r1 = first segment
1613 * r2 = second segment
1614 * ranges = 0 or more segments
1616 * Lazy range which is the concatenation of r1, r2 and ranges with path separators.
1617 * The resulting element type is that of r1.
1621 auto chainPath(R1
, R2
, Ranges
...)(R1 r1
, R2 r2
, Ranges ranges
)
1622 if ((isRandomAccessRange
!R1
&& hasSlicing
!R1
&& hasLength
!R1
&& isSomeChar
!(ElementType
!R1
) ||
1623 isNarrowString
!R1
&&
1624 !isConvertibleToString
!R1
) &&
1625 (isRandomAccessRange
!R2
&& hasSlicing
!R2
&& hasLength
!R2
&& isSomeChar
!(ElementType
!R2
) ||
1626 isNarrowString
!R2
&&
1627 !isConvertibleToString
!R2
) &&
1628 (Ranges
.length
== 0 ||
is(typeof(chainPath(r2
, ranges
))))
1631 static if (Ranges
.length
)
1633 return chainPath(chainPath(r1
, r2
), ranges
);
1637 import std
.range
: only
, chain
;
1638 import std
.utf
: byUTF
;
1640 alias CR
= Unqual
!(ElementEncodingType
!R1
);
1641 auto sep
= only(CR(dirSeparator
[0]));
1642 bool usesep
= false;
1644 auto pos
= r1
.length
;
1654 else version (Windows
)
1660 pos
= rootName(r1
).length
;
1661 if (pos
> 0 && isDirSeparator(r1
[pos
- 1]))
1668 else if (!isDirSeparator(r1
[pos
- 1]))
1673 // Return r1 ~ '/' ~ r2
1674 return chain(r1
[0 .. pos
].byUTF
!CR
, sep
, r2
.byUTF
!CR
);
1684 assert(chainPath("foo", "bar", "baz").array
== "foo/bar/baz");
1685 assert(chainPath("/foo/", "bar/baz").array
== "/foo/bar/baz");
1686 assert(chainPath("/foo", "/bar").array
== "/bar");
1691 assert(chainPath("foo", "bar", "baz").array
== `foo\bar\baz`);
1692 assert(chainPath(`c:\foo`, `bar\baz`).array
== `c:\foo\bar\baz`);
1693 assert(chainPath("foo", `d:\bar`).array
== `d:\bar`);
1694 assert(chainPath("foo", `\bar`).array
== `\bar`);
1695 assert(chainPath(`c:\foo`, `\bar`).array
== `c:\bar`);
1698 import std
.utf
: byChar
;
1701 assert(chainPath("foo", "bar", "baz").array
== "foo/bar/baz");
1702 assert(chainPath("/foo/".byChar
, "bar/baz").array
== "/foo/bar/baz");
1703 assert(chainPath("/foo", "/bar".byChar
).array
== "/bar");
1708 assert(chainPath("foo", "bar", "baz").array
== `foo\bar\baz`);
1709 assert(chainPath(`c:\foo`.byChar
, `bar\baz`).array
== `c:\foo\bar\baz`);
1710 assert(chainPath("foo", `d:\bar`).array
== `d:\bar`);
1711 assert(chainPath("foo", `\bar`.byChar
).array
== `\bar`);
1712 assert(chainPath(`c:\foo`, `\bar`w
).array
== `c:\bar`);
1716 auto chainPath(Ranges
...)(auto ref Ranges ranges
)
1717 if (Ranges
.length
>= 2 &&
1718 std
.meta
.anySatisfy
!(isConvertibleToString
, Ranges
))
1720 import std
.meta
: staticMap
;
1721 alias Types
= staticMap
!(convertToString
, Ranges
);
1722 return chainPath
!Types(ranges
);
1727 assert(chainPath(TestAliasedString(null), TestAliasedString(null), TestAliasedString(null)).empty
);
1728 assert(chainPath(TestAliasedString(null), TestAliasedString(null), "").empty
);
1729 assert(chainPath(TestAliasedString(null), "", TestAliasedString(null)).empty
);
1730 static struct S
{ string s
; }
1731 static assert(!__traits(compiles
, chainPath(TestAliasedString(null), S(""), TestAliasedString(null))));
1734 /** Performs the same task as $(LREF buildPath),
1735 while at the same time resolving current/parent directory
1736 symbols (`"."` and `".."`) and removing superfluous
1737 directory separators.
1738 It will return "." if the path leads to the starting directory.
1739 On Windows, slashes are replaced with backslashes.
1741 Using buildNormalizedPath on null paths will always return null.
1743 Note that this function does not resolve symbolic links.
1745 This function always allocates memory to hold the resulting path.
1746 Use $(LREF asNormalizedPath) to not allocate memory.
1749 paths = An array of paths to assemble.
1751 Returns: The assembled path.
1753 immutable(C
)[] buildNormalizedPath(C
)(const(C
[])[] paths
...)
1757 import std
.array
: array
;
1760 foreach (path
; paths
)
1763 chained
= chainPath(chained
, path
).array
;
1767 auto result
= asNormalizedPath(chained
);
1768 // .array returns a copy, so it is unique
1769 return result
.array
;
1775 assert(buildNormalizedPath("foo", "..") == ".");
1779 assert(buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz");
1780 assert(buildNormalizedPath("../foo/.") == "../foo");
1781 assert(buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz");
1782 assert(buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz");
1783 assert(buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz");
1784 assert(buildNormalizedPath("/foo/./bar", "../../baz") == "/baz");
1789 assert(buildNormalizedPath(`c:\foo\.\bar/..\\baz\`) == `c:\foo\baz`);
1790 assert(buildNormalizedPath(`..\foo\.`) == `..\foo`);
1791 assert(buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`);
1792 assert(buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`);
1793 assert(buildNormalizedPath(`\\server\share\foo`, `..\bar`) ==
1794 `\\server\share\bar`);
1800 assert(buildNormalizedPath(".", ".") == ".");
1801 assert(buildNormalizedPath("foo", "..") == ".");
1802 assert(buildNormalizedPath("", "") is null);
1803 assert(buildNormalizedPath("", ".") == ".");
1804 assert(buildNormalizedPath(".", "") == ".");
1805 assert(buildNormalizedPath(null, "foo") == "foo");
1806 assert(buildNormalizedPath("", "foo") == "foo");
1807 assert(buildNormalizedPath("", "") == "");
1808 assert(buildNormalizedPath("", null) == "");
1809 assert(buildNormalizedPath(null, "") == "");
1810 assert(buildNormalizedPath
!(char)(null, null) == "");
1814 assert(buildNormalizedPath("/", "foo", "bar") == "/foo/bar");
1815 assert(buildNormalizedPath("foo", "bar", "baz") == "foo/bar/baz");
1816 assert(buildNormalizedPath("foo", "bar/baz") == "foo/bar/baz");
1817 assert(buildNormalizedPath("foo", "bar//baz///") == "foo/bar/baz");
1818 assert(buildNormalizedPath("/foo", "bar/baz") == "/foo/bar/baz");
1819 assert(buildNormalizedPath("/foo", "/bar/baz") == "/bar/baz");
1820 assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1821 assert(buildNormalizedPath("/foo/..", "bar/baz") == "/bar/baz");
1822 assert(buildNormalizedPath("/foo/../../", "bar/baz") == "/bar/baz");
1823 assert(buildNormalizedPath("/foo/bar", "../baz") == "/foo/baz");
1824 assert(buildNormalizedPath("/foo/bar", "../../baz") == "/baz");
1825 assert(buildNormalizedPath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee");
1826 assert(buildNormalizedPath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee");
1827 static assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz");
1829 else version (Windows
)
1831 assert(buildNormalizedPath(`\`, `foo`, `bar`) == `\foo\bar`);
1832 assert(buildNormalizedPath(`foo`, `bar`, `baz`) == `foo\bar\baz`);
1833 assert(buildNormalizedPath(`foo`, `bar\baz`) == `foo\bar\baz`);
1834 assert(buildNormalizedPath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`);
1835 assert(buildNormalizedPath(`\foo`, `bar\baz`) == `\foo\bar\baz`);
1836 assert(buildNormalizedPath(`\foo`, `\bar\baz`) == `\bar\baz`);
1837 assert(buildNormalizedPath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`);
1838 assert(buildNormalizedPath(`\foo\..`, `bar\baz`) == `\bar\baz`);
1839 assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1840 assert(buildNormalizedPath(`\foo\bar`, `..\baz`) == `\foo\baz`);
1841 assert(buildNormalizedPath(`\foo\bar`, `../../baz`) == `\baz`);
1842 assert(buildNormalizedPath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`);
1844 assert(buildNormalizedPath(`c:\`, `foo`, `bar`) == `c:\foo\bar`);
1845 assert(buildNormalizedPath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`);
1846 assert(buildNormalizedPath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`);
1847 assert(buildNormalizedPath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`);
1848 assert(buildNormalizedPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`);
1849 assert(buildNormalizedPath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`);
1850 assert(buildNormalizedPath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`);
1851 assert(buildNormalizedPath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`);
1852 assert(buildNormalizedPath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`);
1853 assert(buildNormalizedPath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`);
1854 assert(buildNormalizedPath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`);
1855 assert(buildNormalizedPath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`);
1857 assert(buildNormalizedPath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`);
1858 assert(buildNormalizedPath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`);
1859 assert(buildNormalizedPath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`);
1860 assert(buildNormalizedPath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`);
1861 assert(buildNormalizedPath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`);
1862 assert(buildNormalizedPath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`);
1863 assert(buildNormalizedPath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`);
1864 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`);
1865 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`);
1866 assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`);
1868 static assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`);
1870 else static assert(0);
1875 // Test for https://issues.dlang.org/show_bug.cgi?id=7397
1876 string
[] ary
= ["a", "b"];
1879 assert(buildNormalizedPath(ary
) == "a/b");
1881 else version (Windows
)
1883 assert(buildNormalizedPath(ary
) == `a\b`);
1888 /** Normalize a path by resolving current/parent directory
1889 symbols (`"."` and `".."`) and removing superfluous
1890 directory separators.
1891 It will return "." if the path leads to the starting directory.
1892 On Windows, slashes are replaced with backslashes.
1894 Using asNormalizedPath on empty paths will always return an empty path.
1896 Does not resolve symbolic links.
1898 This function always allocates memory to hold the resulting path.
1899 Use $(LREF buildNormalizedPath) to allocate memory and return a string.
1902 path = string or random access range representing the path to normalize
1905 normalized path as a forward range
1908 auto asNormalizedPath(R
)(return scope R path
)
1909 if (isSomeChar
!(ElementEncodingType
!R
) &&
1910 (isRandomAccessRange
!R
&& hasSlicing
!R
&& hasLength
!R || isNarrowString
!R
) &&
1911 !isConvertibleToString
!R
)
1913 alias C
= Unqual
!(ElementEncodingType
!R
);
1914 alias S
= typeof(path
[0 .. 0]);
1916 static struct Result
1918 @property bool empty()
1942 element
= element
[0 .. 0];
1945 element
= elements
.front
;
1946 elements
.popFront();
1947 if (isDot(element
) ||
(rooted
&& isDotDot(element
)))
1950 if (rooted ||
!isDotDot(element
))
1953 auto elements2
= elements
.save
;
1954 while (!elements2
.empty
)
1956 auto e
= elements2
.front
;
1957 elements2
.popFront();
1965 elements
= elements2
;
1966 element
= element
[0 .. 0];
1977 static assert(dirSeparator
.length
== 1);
1978 if (lastc
== dirSeparator
[0] || lastc
== lastc
.init
)
1981 c
= dirSeparator
[0];
1984 static if (isForwardRange
!R
)
1986 @property auto save()
1989 result
.element
= element
.save
;
1990 result
.elements
= elements
.save
;
1998 element
= rootName(path
);
1999 auto i
= element
.length
;
2000 while (i
< path
.length
&& isDirSeparator(path
[i
]))
2003 elements
= pathSplitter(path
[i
.. $]);
2005 if (c
== c
.init
&& path
.length
)
2011 static if (isNarrowString
!S
) // avoid autodecode
2014 element
= element
[1 .. $];
2018 C c
= element
.front
;
2023 if (c
== '/') // can appear in root element
2024 c
= '\\'; // use native Windows directory separator
2029 // See if elem is "."
2030 static bool isDot(S elem
)
2032 return elem
.length
== 1 && elem
[0] == '.';
2035 // See if elem is ".."
2036 static bool isDotDot(S elem
)
2038 return elem
.length
== 2 && elem
[0] == '.' && elem
[1] == '.';
2041 bool rooted
; // the path starts with a root directory
2044 typeof(pathSplitter(path
[0 .. 0])) elements
;
2047 return Result(path
);
2054 assert(asNormalizedPath("foo/..").array
== ".");
2058 assert(asNormalizedPath("/foo/./bar/..//baz/").array
== "/foo/baz");
2059 assert(asNormalizedPath("../foo/.").array
== "../foo");
2060 assert(asNormalizedPath("/foo/bar/baz/").array
== "/foo/bar/baz");
2061 assert(asNormalizedPath("/foo/./bar/../../baz").array
== "/baz");
2066 assert(asNormalizedPath(`c:\foo\.\bar/..\\baz\`).array
== `c:\foo\baz`);
2067 assert(asNormalizedPath(`..\foo\.`).array
== `..\foo`);
2068 assert(asNormalizedPath(`c:\foo\bar\baz\`).array
== `c:\foo\bar\baz`);
2069 assert(asNormalizedPath(`c:\foo\bar/..`).array
== `c:\foo`);
2070 assert(asNormalizedPath(`\\server\share\foo\..\bar`).array
==
2071 `\\server\share\bar`);
2075 auto asNormalizedPath(R
)(return scope auto ref R path
)
2076 if (isConvertibleToString
!R
)
2078 return asNormalizedPath
!(StringTypeOf
!R
)(path
);
2083 assert(testAliasedString
!asNormalizedPath(null));
2089 import std
.utf
: byChar
;
2091 assert(asNormalizedPath("").array
is null);
2092 assert(asNormalizedPath("foo").array
== "foo");
2093 assert(asNormalizedPath(".").array
== ".");
2094 assert(asNormalizedPath("./.").array
== ".");
2095 assert(asNormalizedPath("foo/..").array
== ".");
2097 auto save
= asNormalizedPath("fob").save
;
2099 assert(save
.front
== 'o');
2103 assert(asNormalizedPath("/foo/bar").array
== "/foo/bar");
2104 assert(asNormalizedPath("foo/bar/baz").array
== "foo/bar/baz");
2105 assert(asNormalizedPath("foo/bar/baz").array
== "foo/bar/baz");
2106 assert(asNormalizedPath("foo/bar//baz///").array
== "foo/bar/baz");
2107 assert(asNormalizedPath("/foo/bar/baz").array
== "/foo/bar/baz");
2108 assert(asNormalizedPath("/foo/../bar/baz").array
== "/bar/baz");
2109 assert(asNormalizedPath("/foo/../..//bar/baz").array
== "/bar/baz");
2110 assert(asNormalizedPath("/foo/bar/../baz").array
== "/foo/baz");
2111 assert(asNormalizedPath("/foo/bar/../../baz").array
== "/baz");
2112 assert(asNormalizedPath("/foo/bar/.././/baz/../wee/").array
== "/foo/wee");
2113 assert(asNormalizedPath("//foo/bar/baz///wee").array
== "/foo/bar/baz/wee");
2115 assert(asNormalizedPath("foo//bar").array
== "foo/bar");
2116 assert(asNormalizedPath("foo/bar").array
== "foo/bar");
2119 assert(asNormalizedPath("./").array
== ".");
2120 assert(asNormalizedPath("././").array
== ".");
2121 assert(asNormalizedPath("./foo/..").array
== ".");
2122 assert(asNormalizedPath("foo/..").array
== ".");
2124 else version (Windows
)
2126 assert(asNormalizedPath(`\foo\bar`).array
== `\foo\bar`);
2127 assert(asNormalizedPath(`foo\bar\baz`).array
== `foo\bar\baz`);
2128 assert(asNormalizedPath(`foo\bar\baz`).array
== `foo\bar\baz`);
2129 assert(asNormalizedPath(`foo\bar\\baz\\\`).array
== `foo\bar\baz`);
2130 assert(asNormalizedPath(`\foo\bar\baz`).array
== `\foo\bar\baz`);
2131 assert(asNormalizedPath(`\foo\..\\bar\.\baz`).array
== `\bar\baz`);
2132 assert(asNormalizedPath(`\foo\..\bar\baz`).array
== `\bar\baz`);
2133 assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array
== `\bar\baz`);
2135 assert(asNormalizedPath(`\foo\bar\..\baz`).array
== `\foo\baz`);
2136 assert(asNormalizedPath(`\foo\bar\../../baz`).array
== `\baz`);
2137 assert(asNormalizedPath(`\foo\bar\..\.\/baz\..\wee\`).array
== `\foo\wee`);
2139 assert(asNormalizedPath(`c:\foo\bar`).array
== `c:\foo\bar`);
2140 assert(asNormalizedPath(`c:foo\bar\baz`).array
== `c:foo\bar\baz`);
2141 assert(asNormalizedPath(`c:foo\bar\baz`).array
== `c:foo\bar\baz`);
2142 assert(asNormalizedPath(`c:foo\bar\\baz\\\`).array
== `c:foo\bar\baz`);
2143 assert(asNormalizedPath(`c:\foo\bar\baz`).array
== `c:\foo\bar\baz`);
2145 assert(asNormalizedPath(`c:\foo\..\\bar\.\baz`).array
== `c:\bar\baz`);
2146 assert(asNormalizedPath(`c:\foo\..\bar\baz`).array
== `c:\bar\baz`);
2147 assert(asNormalizedPath(`c:\foo\..\..\\bar\baz`).array
== `c:\bar\baz`);
2148 assert(asNormalizedPath(`c:\foo\bar\..\baz`).array
== `c:\foo\baz`);
2149 assert(asNormalizedPath(`c:\foo\bar\..\..\baz`).array
== `c:\baz`);
2150 assert(asNormalizedPath(`c:\foo\bar\..\.\\baz\..\wee\`).array
== `c:\foo\wee`);
2151 assert(asNormalizedPath(`\\server\share\foo\bar`).array
== `\\server\share\foo\bar`);
2152 assert(asNormalizedPath(`\\server\share\\foo\bar`).array
== `\\server\share\foo\bar`);
2153 assert(asNormalizedPath(`\\server\share\foo\bar\baz`).array
== `\\server\share\foo\bar\baz`);
2154 assert(asNormalizedPath(`\\server\share\foo\..\\bar\.\baz`).array
== `\\server\share\bar\baz`);
2155 assert(asNormalizedPath(`\\server\share\foo\..\bar\baz`).array
== `\\server\share\bar\baz`);
2156 assert(asNormalizedPath(`\\server\share\foo\..\..\\bar\baz`).array
== `\\server\share\bar\baz`);
2157 assert(asNormalizedPath(`\\server\share\foo\bar\..\baz`).array
== `\\server\share\foo\baz`);
2158 assert(asNormalizedPath(`\\server\share\foo\bar\..\..\baz`).array
== `\\server\share\baz`);
2159 assert(asNormalizedPath(`\\server\share\foo\bar\..\.\\baz\..\wee\`).array
== `\\server\share\foo\wee`);
2161 static assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array
== `\bar\baz`);
2163 assert(asNormalizedPath("foo//bar").array
== `foo\bar`);
2166 assert(asNormalizedPath(`.\`).array
== ".");
2167 assert(asNormalizedPath(`.\.\`).array
== ".");
2168 assert(asNormalizedPath(`.\foo\..`).array
== ".");
2169 assert(asNormalizedPath(`foo\..`).array
== ".");
2171 else static assert(0);
2181 assert(asNormalizedPath("").empty
);
2182 assert(asNormalizedPath("foo/bar").array
== "foo/bar");
2184 // Correct handling of leading slashes
2185 assert(asNormalizedPath("/").array
== "/");
2186 assert(asNormalizedPath("///").array
== "/");
2187 assert(asNormalizedPath("////").array
== "/");
2188 assert(asNormalizedPath("/foo/bar").array
== "/foo/bar");
2189 assert(asNormalizedPath("//foo/bar").array
== "/foo/bar");
2190 assert(asNormalizedPath("///foo/bar").array
== "/foo/bar");
2191 assert(asNormalizedPath("////foo/bar").array
== "/foo/bar");
2193 // Correct handling of single-dot symbol (current directory)
2194 assert(asNormalizedPath("/./foo").array
== "/foo");
2195 assert(asNormalizedPath("/foo/./bar").array
== "/foo/bar");
2197 assert(asNormalizedPath("./foo").array
== "foo");
2198 assert(asNormalizedPath("././foo").array
== "foo");
2199 assert(asNormalizedPath("foo/././bar").array
== "foo/bar");
2201 // Correct handling of double-dot symbol (previous directory)
2202 assert(asNormalizedPath("/foo/../bar").array
== "/bar");
2203 assert(asNormalizedPath("/foo/../../bar").array
== "/bar");
2204 assert(asNormalizedPath("/../foo").array
== "/foo");
2205 assert(asNormalizedPath("/../../foo").array
== "/foo");
2206 assert(asNormalizedPath("/foo/..").array
== "/");
2207 assert(asNormalizedPath("/foo/../..").array
== "/");
2209 assert(asNormalizedPath("foo/../bar").array
== "bar");
2210 assert(asNormalizedPath("foo/../../bar").array
== "../bar");
2211 assert(asNormalizedPath("../foo").array
== "../foo");
2212 assert(asNormalizedPath("../../foo").array
== "../../foo");
2213 assert(asNormalizedPath("../foo/../bar").array
== "../bar");
2214 assert(asNormalizedPath(".././../foo").array
== "../../foo");
2215 assert(asNormalizedPath("foo/bar/..").array
== "foo");
2216 assert(asNormalizedPath("/foo/../..").array
== "/");
2218 // The ultimate path
2219 assert(asNormalizedPath("/foo/../bar//./../...///baz//").array
== "/.../baz");
2220 static assert(asNormalizedPath("/foo/../bar//./../...///baz//").array
== "/.../baz");
2222 else version (Windows
)
2225 assert(asNormalizedPath("").empty
);
2226 assert(asNormalizedPath(`foo\bar`).array
== `foo\bar`);
2227 assert(asNormalizedPath("foo/bar").array
== `foo\bar`);
2229 // Correct handling of absolute paths
2230 assert(asNormalizedPath("/").array
== `\`);
2231 assert(asNormalizedPath(`\`).array
== `\`);
2232 assert(asNormalizedPath(`\\\`).array
== `\`);
2233 assert(asNormalizedPath(`\\\\`).array
== `\`);
2234 assert(asNormalizedPath(`\foo\bar`).array
== `\foo\bar`);
2235 assert(asNormalizedPath(`\\foo`).array
== `\\foo`);
2236 assert(asNormalizedPath(`\\foo\\`).array
== `\\foo`);
2237 assert(asNormalizedPath(`\\foo/bar`).array
== `\\foo\bar`);
2238 assert(asNormalizedPath(`\\\foo\bar`).array
== `\foo\bar`);
2239 assert(asNormalizedPath(`\\\\foo\bar`).array
== `\foo\bar`);
2240 assert(asNormalizedPath(`c:\`).array
== `c:\`);
2241 assert(asNormalizedPath(`c:\foo\bar`).array
== `c:\foo\bar`);
2242 assert(asNormalizedPath(`c:\\foo\bar`).array
== `c:\foo\bar`);
2244 // Correct handling of single-dot symbol (current directory)
2245 assert(asNormalizedPath(`\./foo`).array
== `\foo`);
2246 assert(asNormalizedPath(`\foo/.\bar`).array
== `\foo\bar`);
2248 assert(asNormalizedPath(`.\foo`).array
== `foo`);
2249 assert(asNormalizedPath(`./.\foo`).array
== `foo`);
2250 assert(asNormalizedPath(`foo\.\./bar`).array
== `foo\bar`);
2252 // Correct handling of double-dot symbol (previous directory)
2253 assert(asNormalizedPath(`\foo\..\bar`).array
== `\bar`);
2254 assert(asNormalizedPath(`\foo\../..\bar`).array
== `\bar`);
2255 assert(asNormalizedPath(`\..\foo`).array
== `\foo`);
2256 assert(asNormalizedPath(`\..\..\foo`).array
== `\foo`);
2257 assert(asNormalizedPath(`\foo\..`).array
== `\`);
2258 assert(asNormalizedPath(`\foo\../..`).array
== `\`);
2260 assert(asNormalizedPath(`foo\..\bar`).array
== `bar`);
2261 assert(asNormalizedPath(`foo\..\../bar`).array
== `..\bar`);
2263 assert(asNormalizedPath(`..\foo`).array
== `..\foo`);
2264 assert(asNormalizedPath(`..\..\foo`).array
== `..\..\foo`);
2265 assert(asNormalizedPath(`..\foo\..\bar`).array
== `..\bar`);
2266 assert(asNormalizedPath(`..\.\..\foo`).array
== `..\..\foo`);
2267 assert(asNormalizedPath(`foo\bar\..`).array
== `foo`);
2268 assert(asNormalizedPath(`\foo\..\..`).array
== `\`);
2269 assert(asNormalizedPath(`c:\foo\..\..`).array
== `c:\`);
2271 // Correct handling of non-root path with drive specifier
2272 assert(asNormalizedPath(`c:foo`).array
== `c:foo`);
2273 assert(asNormalizedPath(`c:..\foo\.\..\bar`).array
== `c:..\bar`);
2275 // The ultimate path
2276 assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array
== `c:\...\baz`);
2277 static assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array
== `c:\...\baz`);
2279 else static assert(false);
2282 /** Slice up a path into its elements.
2285 path = string or slicable random access range
2288 bidirectional range of slices of `path`
2290 auto pathSplitter(R
)(R path
)
2291 if ((isRandomAccessRange
!R
&& hasSlicing
!R ||
2292 isNarrowString
!R
) &&
2293 !isConvertibleToString
!R
)
2295 static struct PathSplitter
2297 @property bool empty() const { return pe
== 0; }
2302 return _path
[fs
.. fe
];
2310 if (fs
== bs
&& fe
== be
)
2324 while (fe
< pe
&& !isDirSeparator(_path
[fe
]))
2333 return _path
[bs
.. be
];
2341 if (fs
== bs
&& fe
== be
)
2355 while (bs
> ps
&& !isDirSeparator(_path
[bs
- 1]))
2360 @property auto save() { return this; }
2381 // If path is rooted, first element is special
2386 auto i
= uncRootLength(_path
);
2391 else if (isDriveRoot(_path
))
2397 else if (_path
.length
>= 1 && isDirSeparator(_path
[0]))
2405 assert(!isRooted(_path
));
2409 else version (Posix
)
2411 if (_path
.length
>= 1 && isDirSeparator(_path
[0]))
2422 else static assert(0);
2436 size_t
ltrim(size_t s
, size_t e
)
2438 while (s
< e
&& isDirSeparator(_path
[s
]))
2443 size_t
rtrim(size_t s
, size_t e
)
2445 while (s
< e
&& isDirSeparator(_path
[e
- 1]))
2451 return PathSplitter(path
);
2457 import std
.algorithm
.comparison
: equal
;
2458 import std
.conv
: to
;
2460 assert(equal(pathSplitter("/"), ["/"]));
2461 assert(equal(pathSplitter("/foo/bar"), ["/", "foo", "bar"]));
2462 assert(equal(pathSplitter("foo/../bar//./"), ["foo", "..", "bar", "."]));
2466 assert(equal(pathSplitter("//foo/bar"), ["/", "foo", "bar"]));
2471 assert(equal(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
2472 assert(equal(pathSplitter("c:"), ["c:"]));
2473 assert(equal(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
2474 assert(equal(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
2478 auto pathSplitter(R
)(auto ref R path
)
2479 if (isConvertibleToString
!R
)
2481 return pathSplitter
!(StringTypeOf
!R
)(path
);
2486 import std
.algorithm
.comparison
: equal
;
2487 assert(testAliasedString
!pathSplitter("/"));
2492 // equal2 verifies that the range is the same both ways, i.e.
2493 // through front/popFront and back/popBack.
2494 import std
.algorithm
;
2496 bool equal2(R1
, R2
)(R1 r1
, R2 r2
)
2498 static assert(isBidirectionalRange
!R1
);
2499 return equal(r1
, r2
) && equal(retro(r1
), retro(r2
));
2502 assert(pathSplitter("").empty
);
2505 assert(equal2(pathSplitter("/"), ["/"]));
2506 assert(equal2(pathSplitter("//"), ["/"]));
2507 assert(equal2(pathSplitter("///"w
), ["/"w
]));
2510 assert(equal2(pathSplitter("/foo/bar".dup
), ["/", "foo", "bar"]));
2513 assert(equal2(pathSplitter("foo/bar"d
.dup
), ["foo"d
, "bar"d
]));
2514 assert(equal2(pathSplitter("foo//bar"), ["foo", "bar"]));
2515 assert(equal2(pathSplitter("foo/bar//"w
), ["foo"w
, "bar"w
]));
2516 assert(equal2(pathSplitter("foo/../bar//./"d
), ["foo"d
, ".."d
, "bar"d
, "."d
]));
2519 auto ps1
= pathSplitter("foo/bar/baz");
2520 auto ps2
= ps1
.save
;
2522 assert(equal2(ps1
, ["bar", "baz"]));
2523 assert(equal2(ps2
, ["foo", "bar", "baz"]));
2525 // Platform specific
2528 assert(equal2(pathSplitter("//foo/bar"w
.dup
), ["/"w
, "foo"w
, "bar"w
]));
2532 assert(equal2(pathSplitter(`\`), [`\`]));
2533 assert(equal2(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."]));
2534 assert(equal2(pathSplitter("c:"), ["c:"]));
2535 assert(equal2(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"]));
2536 assert(equal2(pathSplitter(`c:foo\bar`), ["c:foo", "bar"]));
2537 assert(equal2(pathSplitter(`\\foo\bar`), [`\\foo\bar`]));
2538 assert(equal2(pathSplitter(`\\foo\bar\\`), [`\\foo\bar`]));
2539 assert(equal2(pathSplitter(`\\foo\bar\baz`), [`\\foo\bar`, "baz"]));
2542 import std
.exception
;
2545 assert(equal(pathSplitter("/foo/bar".dup
), ["/", "foo", "bar"]));
2548 static assert(is(typeof(pathSplitter
!(const(char)[])(null).front
) == const(char)[]));
2550 import std
.utf
: byDchar
;
2551 assert(equal2(pathSplitter("foo/bar"d
.byDchar
), ["foo"d
, "bar"d
]));
2557 /** Determines whether a path starts at a root directory.
2562 Whether a path starts at a root directory.
2564 On POSIX, this function returns true if and only if the path starts
2567 On Windows, this function returns true if the path starts at
2568 the root directory of the current drive, of some other drive,
2569 or of a network drive.
2571 bool isRooted(R
)(R path
)
2572 if (isRandomAccessRange
!R
&& isSomeChar
!(ElementType
!R
) ||
2575 if (path
.length
>= 1 && isDirSeparator(path
[0])) return true;
2576 version (Posix
) return false;
2577 else version (Windows
) return isAbsolute
!(BaseOf
!R
)(path
);
2585 assert( isRooted("/"));
2586 assert( isRooted("/foo"));
2587 assert(!isRooted("foo"));
2588 assert(!isRooted("../foo"));
2593 assert( isRooted(`\`));
2594 assert( isRooted(`\foo`));
2595 assert( isRooted(`d:\foo`));
2596 assert( isRooted(`\\foo\bar`));
2597 assert(!isRooted("foo"));
2598 assert(!isRooted("d:foo"));
2604 assert(isRooted("/"));
2605 assert(isRooted("/foo"));
2606 assert(!isRooted("foo"));
2607 assert(!isRooted("../foo"));
2611 assert(isRooted(`\`));
2612 assert(isRooted(`\foo`));
2613 assert(isRooted(`d:\foo`));
2614 assert(isRooted(`\\foo\bar`));
2615 assert(!isRooted("foo"));
2616 assert(!isRooted("d:foo"));
2619 static assert(isRooted("/foo"));
2620 static assert(!isRooted("foo"));
2622 static struct DirEntry
{ string s
; alias s
this; }
2623 assert(!isRooted(DirEntry("foo")));
2626 /** Determines whether a path is absolute or not.
2628 Params: path = A path name.
2630 Returns: Whether a path is absolute or not.
2633 On POSIX, an absolute path starts at the root directory.
2634 (In fact, `_isAbsolute` is just an alias for $(LREF isRooted).)
2638 assert(isAbsolute("/"));
2639 assert(isAbsolute("/foo"));
2640 assert(!isAbsolute("foo"));
2641 assert(!isAbsolute("../foo"));
2645 On Windows, an absolute path starts at the root directory of
2646 a specific drive. Hence, it must start with $(D `d:\`) or $(D `d:/`),
2647 where `d` is the drive letter. Alternatively, it may be a
2648 network path, i.e. a path starting with a double (back)slash.
2652 assert(isAbsolute(`d:\`));
2653 assert(isAbsolute(`d:\foo`));
2654 assert(isAbsolute(`\\foo\bar`));
2655 assert(!isAbsolute(`\`));
2656 assert(!isAbsolute(`\foo`));
2657 assert(!isAbsolute("d:foo"));
2663 bool isAbsolute(R
)(R path
) pure nothrow @safe
2664 if (isRandomAccessRange
!R
&& isSomeChar
!(ElementType
!R
) ||
2665 is(StringTypeOf
!R
));
2667 else version (Windows
)
2669 bool isAbsolute(R
)(R path
)
2670 if (isRandomAccessRange
!R
&& isSomeChar
!(ElementType
!R
) ||
2673 return isDriveRoot
!(BaseOf
!R
)(path
) || isUNC
!(BaseOf
!R
)(path
);
2676 else version (Posix
)
2678 alias isAbsolute
= isRooted
;
2684 assert(!isAbsolute("foo"));
2685 assert(!isAbsolute("../foo"w
));
2686 static assert(!isAbsolute("foo"));
2690 assert(isAbsolute("/"d
));
2691 assert(isAbsolute("/foo".dup
));
2692 static assert(isAbsolute("/foo"));
2697 assert(isAbsolute("d:\\"w
));
2698 assert(isAbsolute("d:\\foo"d
));
2699 assert(isAbsolute("\\\\foo\\bar"));
2700 assert(!isAbsolute("\\"w
.dup
));
2701 assert(!isAbsolute("\\foo"d
.dup
));
2702 assert(!isAbsolute("d:"));
2703 assert(!isAbsolute("d:foo"));
2704 static assert(isAbsolute(`d:\foo`));
2708 auto r
= MockRange
!(immutable(char))(`../foo`);
2709 assert(!r
.isAbsolute());
2712 static struct DirEntry
{ string s
; alias s
this; }
2713 assert(!isAbsolute(DirEntry("foo")));
2719 /** Transforms `path` into an absolute path.
2721 The following algorithm is used:
2723 $(LI If `path` is empty, return `null`.)
2724 $(LI If `path` is already absolute, return it.)
2725 $(LI Otherwise, append `path` to `base` and return
2726 the result. If `base` is not specified, the current
2727 working directory is used.)
2729 The function allocates memory if and only if it gets to the third stage
2732 Note that `absolutePath` will not normalize `..` segments.
2733 Use `buildNormalizedPath(absolutePath(path))` if that is desired.
2736 path = the relative path to transform
2737 base = the base directory of the relative path
2740 string of transformed path
2743 `Exception` if the specified _base directory is not absolute.
2746 $(LREF asAbsolutePath) which does not allocate
2748 string
absolutePath(return scope const string path
, lazy string base
= getcwd())
2751 import std
.array
: array
;
2752 if (path
.empty
) return null;
2753 if (isAbsolute(path
)) return path
;
2754 auto baseVar
= base
;
2755 if (!isAbsolute(baseVar
)) throw new Exception("Base directory must be absolute");
2756 return chainPath(baseVar
, path
).array
;
2764 assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file");
2765 assert(absolutePath("../file", "/foo/bar") == "/foo/bar/../file");
2766 assert(absolutePath("/some/file", "/foo/bar") == "/some/file");
2771 assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2772 assert(absolutePath(`..\file`, `c:\foo\bar`) == `c:\foo\bar\..\file`);
2773 assert(absolutePath(`c:\some\file`, `c:\foo\bar`) == `c:\some\file`);
2774 assert(absolutePath(`\`, `c:\`) == `c:\`);
2775 assert(absolutePath(`\some\file`, `c:\foo\bar`) == `c:\some\file`);
2783 static assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file");
2788 static assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2791 import std
.exception
;
2792 assertThrown(absolutePath("bar", "foo"));
2795 // Ensure that we can call absolute path with scope paramaters
2798 string
testAbsPath(scope const string path
, scope const string base
) {
2799 return absolutePath(path
, base
);
2803 assert(testAbsPath("some/file", "/foo/bar") == "/foo/bar/some/file");
2805 assert(testAbsPath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`);
2808 /** Transforms `path` into an absolute path.
2810 The following algorithm is used:
2812 $(LI If `path` is empty, return `null`.)
2813 $(LI If `path` is already absolute, return it.)
2814 $(LI Otherwise, append `path` to the current working directory,
2815 which allocates memory.)
2818 Note that `asAbsolutePath` will not normalize `..` segments.
2819 Use `asNormalizedPath(asAbsolutePath(path))` if that is desired.
2822 path = the relative path to transform
2825 the transformed path as a lazy range
2828 $(LREF absolutePath) which returns an allocated string
2830 auto asAbsolutePath(R
)(R path
)
2831 if ((isRandomAccessRange
!R
&& isSomeChar
!(ElementType
!R
) ||
2832 isNarrowString
!R
) &&
2833 !isConvertibleToString
!R
)
2835 import std
.file
: getcwd
;
2837 if (!path
.empty
&& !isAbsolute(path
))
2839 return chainPath(base
, path
);
2846 assert(asAbsolutePath(cast(string
) null).array
== "");
2849 assert(asAbsolutePath("/foo").array
== "/foo");
2853 assert(asAbsolutePath("c:/foo").array
== "c:/foo");
2855 asAbsolutePath("foo");
2858 auto asAbsolutePath(R
)(auto ref R path
)
2859 if (isConvertibleToString
!R
)
2861 return asAbsolutePath
!(StringTypeOf
!R
)(path
);
2866 assert(testAliasedString
!asAbsolutePath(null));
2869 /** Translates `path` into a relative path.
2871 The returned path is relative to `base`, which is by default
2872 taken to be the current working directory. If specified,
2873 `base` must be an absolute path, and it is always assumed
2874 to refer to a directory. If `path` and `base` refer to
2875 the same directory, the function returns $(D `.`).
2877 The following algorithm is used:
2879 $(LI If `path` is a relative directory, return it unaltered.)
2880 $(LI Find a common root between `path` and `base`.
2881 If there is no common root, return `path` unaltered.)
2882 $(LI Prepare a string with as many $(D `../`) or $(D `..\`) as
2883 necessary to reach the common root from base path.)
2884 $(LI Append the remaining segments of `path` to the string
2888 In the second step, path components are compared using `filenameCmp!cs`,
2889 where `cs` is an optional template parameter determining whether
2890 the comparison is case sensitive or not. See the
2891 $(LREF filenameCmp) documentation for details.
2893 This function allocates memory.
2896 cs = Whether matching path name components against the base path should
2897 be case-sensitive or not.
2899 base = The base path to construct the relative path from.
2901 Returns: The relative path.
2904 $(LREF asRelativePath) which does not allocate memory
2907 `Exception` if the specified _base directory is not absolute.
2909 string
relativePath(CaseSensitive cs
= CaseSensitive
.osDefault
)
2910 (string path
, lazy string base
= getcwd())
2912 if (!isAbsolute(path
))
2914 auto baseVar
= base
;
2915 if (!isAbsolute(baseVar
))
2916 throw new Exception("Base directory must be absolute");
2918 import std
.conv
: to
;
2919 return asRelativePath
!cs(path
, baseVar
).to
!string
;
2925 assert(relativePath("foo") == "foo");
2929 assert(relativePath("foo", "/bar") == "foo");
2930 assert(relativePath("/foo/bar", "/foo/bar") == ".");
2931 assert(relativePath("/foo/bar", "/foo/baz") == "../bar");
2932 assert(relativePath("/foo/bar/baz", "/foo/woo/wee") == "../../bar/baz");
2933 assert(relativePath("/foo/bar/baz", "/foo/bar") == "baz");
2937 assert(relativePath("foo", `c:\bar`) == "foo");
2938 assert(relativePath(`c:\foo\bar`, `c:\foo\bar`) == ".");
2939 assert(relativePath(`c:\foo\bar`, `c:\foo\baz`) == `..\bar`);
2940 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`) == `..\..\bar\baz`);
2941 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
2942 assert(relativePath(`c:\foo\bar`, `d:\foo`) == `c:\foo\bar`);
2948 import std
.exception
;
2949 assert(relativePath("foo") == "foo");
2952 relativePath("/foo");
2953 assert(relativePath("/foo/bar", "/foo/baz") == "../bar");
2954 assertThrown(relativePath("/foo", "bar"));
2956 else version (Windows
)
2958 relativePath(`\foo`);
2959 assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz");
2960 assertThrown(relativePath(`c:\foo`, "bar"));
2962 else static assert(0);
2965 /** Transforms `path` into a path relative to `base`.
2967 The returned path is relative to `base`, which is usually
2968 the current working directory.
2969 `base` must be an absolute path, and it is always assumed
2970 to refer to a directory. If `path` and `base` refer to
2971 the same directory, the function returns `'.'`.
2973 The following algorithm is used:
2975 $(LI If `path` is a relative directory, return it unaltered.)
2976 $(LI Find a common root between `path` and `base`.
2977 If there is no common root, return `path` unaltered.)
2978 $(LI Prepare a string with as many `../` or `..\` as
2979 necessary to reach the common root from base path.)
2980 $(LI Append the remaining segments of `path` to the string
2984 In the second step, path components are compared using `filenameCmp!cs`,
2985 where `cs` is an optional template parameter determining whether
2986 the comparison is case sensitive or not. See the
2987 $(LREF filenameCmp) documentation for details.
2990 path = path to transform
2991 base = absolute path
2992 cs = whether filespec comparisons are sensitive or not; defaults to
2993 `CaseSensitive.osDefault`
2996 a random access range of the transformed path
2999 $(LREF relativePath)
3001 auto asRelativePath(CaseSensitive cs
= CaseSensitive
.osDefault
, R1
, R2
)
3003 if ((isNarrowString
!R1 ||
3004 (isRandomAccessRange
!R1
&& hasSlicing
!R1
&& isSomeChar
!(ElementType
!R1
)) &&
3005 !isConvertibleToString
!R1
) &&
3006 (isNarrowString
!R2 ||
3007 (isRandomAccessRange
!R2
&& hasSlicing
!R2
&& isSomeChar
!(ElementType
!R2
)) &&
3008 !isConvertibleToString
!R2
))
3010 bool choosePath
= !isAbsolute(path
);
3012 // Find common root with current working directory
3014 auto basePS
= pathSplitter(base
);
3015 auto pathPS
= pathSplitter(path
);
3016 choosePath |
= filenameCmp
!cs(basePS
.front
, pathPS
.front
) != 0;
3021 import std
.algorithm
.comparison
: mismatch
;
3022 import std
.algorithm
.iteration
: joiner
;
3023 import std
.array
: array
;
3024 import std
.range
.primitives
: walkLength
;
3025 import std
.range
: repeat
, chain
, choose
;
3026 import std
.utf
: byCodeUnit
, byChar
;
3028 // Remove matching prefix from basePS and pathPS
3029 auto tup
= mismatch
!((a
, b
) => filenameCmp
!cs(a
, b
) == 0)(basePS
, pathPS
);
3034 if (basePS
.empty
&& pathPS
.empty
)
3035 sep
= "."; // if base == path, this is the return
3036 else if (!basePS
.empty
&& !pathPS
.empty
)
3039 // Append as many "../" as necessary to reach common base from path
3042 .repeat(basePS
.walkLength())
3043 .joiner(dirSeparator
.byChar
);
3046 .joiner(dirSeparator
.byChar
)
3049 // Return (r1 ~ sep ~ r2)
3050 return choose(choosePath
, path
.byCodeUnit
, chain(r1
, sep
.byChar
, r2
));
3059 assert(asRelativePath("foo", "/bar").array
== "foo");
3060 assert(asRelativePath("/foo/bar", "/foo/bar").array
== ".");
3061 assert(asRelativePath("/foo/bar", "/foo/baz").array
== "../bar");
3062 assert(asRelativePath("/foo/bar/baz", "/foo/woo/wee").array
== "../../bar/baz");
3063 assert(asRelativePath("/foo/bar/baz", "/foo/bar").array
== "baz");
3065 else version (Windows
)
3067 assert(asRelativePath("foo", `c:\bar`).array
== "foo");
3068 assert(asRelativePath(`c:\foo\bar`, `c:\foo\bar`).array
== ".");
3069 assert(asRelativePath(`c:\foo\bar`, `c:\foo\baz`).array
== `..\bar`);
3070 assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`).array
== `..\..\bar\baz`);
3071 assert(asRelativePath(`c:/foo/bar/baz`, `c:\foo\woo\wee`).array
== `..\..\bar\baz`);
3072 assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\bar`).array
== "baz");
3073 assert(asRelativePath(`c:\foo\bar`, `d:\foo`).array
== `c:\foo\bar`);
3074 assert(asRelativePath(`\\foo\bar`, `c:\foo`).array
== `\\foo\bar`);
3084 assert(isBidirectionalRange
!(typeof(asRelativePath("foo/bar/baz", "/foo/woo/wee"))));
3089 assert(isBidirectionalRange
!(typeof(asRelativePath(`c:\foo\bar`, `c:\foo\baz`))));
3093 auto asRelativePath(CaseSensitive cs
= CaseSensitive
.osDefault
, R1
, R2
)
3094 (auto ref R1 path
, auto ref R2 base
)
3095 if (isConvertibleToString
!R1 || isConvertibleToString
!R2
)
3097 import std
.meta
: staticMap
;
3098 alias Types
= staticMap
!(convertToString
, R1
, R2
);
3099 return asRelativePath
!(cs
, Types
)(path
, base
);
3106 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("/bar")).array
== "foo");
3107 else version (Windows
)
3108 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString(`c:\bar`)).array
== "foo");
3109 assert(asRelativePath(TestAliasedString("foo"), "bar").array
== "foo");
3110 assert(asRelativePath("foo", TestAliasedString("bar")).array
== "foo");
3111 assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("bar")).array
== "foo");
3112 import std
.utf
: byDchar
;
3113 assert(asRelativePath("foo"d
.byDchar
, TestAliasedString("bar")).array
== "foo");
3118 import std
.array
, std
.utf
: bCU
=byCodeUnit
;
3121 assert(asRelativePath("/foo/bar/baz".bCU
, "/foo/bar".bCU
).array
== "baz");
3122 assert(asRelativePath("/foo/bar/baz"w
.bCU
, "/foo/bar"w
.bCU
).array
== "baz"w
);
3123 assert(asRelativePath("/foo/bar/baz"d
.bCU
, "/foo/bar"d
.bCU
).array
== "baz"d
);
3125 else version (Windows
)
3127 assert(asRelativePath(`\\foo\bar`.bCU
, `c:\foo`.bCU
).array
== `\\foo\bar`);
3128 assert(asRelativePath(`\\foo\bar`w
.bCU
, `c:\foo`w
.bCU
).array
== `\\foo\bar`w
);
3129 assert(asRelativePath(`\\foo\bar`d
.bCU
, `c:\foo`d
.bCU
).array
== `\\foo\bar`d
);
3133 /** Compares filename characters.
3135 This function can perform a case-sensitive or a case-insensitive
3136 comparison. This is controlled through the `cs` template parameter
3137 which, if not specified, is given by $(LREF CaseSensitive)`.osDefault`.
3139 On Windows, the backslash and slash characters ($(D `\`) and $(D `/`))
3140 are considered equal.
3143 cs = Case-sensitivity of the comparison.
3144 a = A filename character.
3145 b = A filename character.
3148 $(D < 0) if $(D a < b),
3149 `0` if $(D a == b), and
3150 $(D > 0) if $(D a > b).
3152 int filenameCharCmp(CaseSensitive cs
= CaseSensitive
.osDefault
)(dchar a
, dchar b
)
3155 if (isDirSeparator(a
) && isDirSeparator(b
)) return 0;
3158 import std
.uni
: toLower
;
3162 return cast(int)(a
- b
);
3168 assert(filenameCharCmp('a', 'a') == 0);
3169 assert(filenameCharCmp('a', 'b') < 0);
3170 assert(filenameCharCmp('b', 'a') > 0);
3174 // Same as calling filenameCharCmp!(CaseSensitive.yes)(a, b)
3175 assert(filenameCharCmp('A', 'a') < 0);
3176 assert(filenameCharCmp('a', 'A') > 0);
3180 // Same as calling filenameCharCmp!(CaseSensitive.no)(a, b)
3181 assert(filenameCharCmp('a', 'A') == 0);
3182 assert(filenameCharCmp('a', 'B') < 0);
3183 assert(filenameCharCmp('A', 'b') < 0);
3189 assert(filenameCharCmp
!(CaseSensitive
.yes
)('A', 'a') < 0);
3190 assert(filenameCharCmp
!(CaseSensitive
.yes
)('a', 'A') > 0);
3192 assert(filenameCharCmp
!(CaseSensitive
.no
)('a', 'a') == 0);
3193 assert(filenameCharCmp
!(CaseSensitive
.no
)('a', 'b') < 0);
3194 assert(filenameCharCmp
!(CaseSensitive
.no
)('b', 'a') > 0);
3195 assert(filenameCharCmp
!(CaseSensitive
.no
)('A', 'a') == 0);
3196 assert(filenameCharCmp
!(CaseSensitive
.no
)('a', 'A') == 0);
3197 assert(filenameCharCmp
!(CaseSensitive
.no
)('a', 'B') < 0);
3198 assert(filenameCharCmp
!(CaseSensitive
.no
)('B', 'a') > 0);
3199 assert(filenameCharCmp
!(CaseSensitive
.no
)('A', 'b') < 0);
3200 assert(filenameCharCmp
!(CaseSensitive
.no
)('b', 'A') > 0);
3202 version (Posix
) assert(filenameCharCmp('\\', '/') != 0);
3203 version (Windows
) assert(filenameCharCmp('\\', '/') == 0);
3207 /** Compares file names and returns
3209 Individual characters are compared using `filenameCharCmp!cs`,
3210 where `cs` is an optional template parameter determining whether
3211 the comparison is case sensitive or not.
3213 Treatment of invalid UTF encodings is implementation defined.
3216 cs = case sensitivity
3217 filename1 = range for first file name
3218 filename2 = range for second file name
3221 $(D < 0) if $(D filename1 < filename2),
3222 `0` if $(D filename1 == filename2) and
3223 $(D > 0) if $(D filename1 > filename2).
3226 $(LREF filenameCharCmp)
3228 int filenameCmp(CaseSensitive cs
= CaseSensitive
.osDefault
, Range1
, Range2
)
3229 (Range1 filename1
, Range2 filename2
)
3230 if (isSomeFiniteCharInputRange
!Range1
&& !isConvertibleToString
!Range1
&&
3231 isSomeFiniteCharInputRange
!Range2
&& !isConvertibleToString
!Range2
)
3233 alias C1
= Unqual
!(ElementEncodingType
!Range1
);
3234 alias C2
= Unqual
!(ElementEncodingType
!Range2
);
3236 static if (!cs
&& (C1
.sizeof
< 4 || C2
.sizeof
< 4) ||
3237 C1
.sizeof
!= C2
.sizeof
)
3239 // Case insensitive - decode so case is checkable
3240 // Different char sizes - decode to bring to common type
3241 import std
.utf
: byDchar
;
3242 return filenameCmp
!cs(filename1
.byDchar
, filename2
.byDchar
);
3244 else static if (isSomeString
!Range1
&& C1
.sizeof
< 4 ||
3245 isSomeString
!Range2
&& C2
.sizeof
< 4)
3247 // Avoid autodecoding
3248 import std
.utf
: byCodeUnit
;
3249 return filenameCmp
!cs(filename1
.byCodeUnit
, filename2
.byCodeUnit
);
3255 if (filename1
.empty
) return -(cast(int) !filename2
.empty
);
3256 if (filename2
.empty
) return 1;
3257 const c
= filenameCharCmp
!cs(filename1
.front
, filename2
.front
);
3258 if (c
!= 0) return c
;
3259 filename1
.popFront();
3260 filename2
.popFront();
3268 assert(filenameCmp("abc", "abc") == 0);
3269 assert(filenameCmp("abc", "abd") < 0);
3270 assert(filenameCmp("abc", "abb") > 0);
3271 assert(filenameCmp("abc", "abcd") < 0);
3272 assert(filenameCmp("abcd", "abc") > 0);
3276 // Same as calling filenameCmp!(CaseSensitive.yes)(filename1, filename2)
3277 assert(filenameCmp("Abc", "abc") < 0);
3278 assert(filenameCmp("abc", "Abc") > 0);
3282 // Same as calling filenameCmp!(CaseSensitive.no)(filename1, filename2)
3283 assert(filenameCmp("Abc", "abc") == 0);
3284 assert(filenameCmp("abc", "Abc") == 0);
3285 assert(filenameCmp("Abc", "abD") < 0);
3286 assert(filenameCmp("abc", "AbB") > 0);
3290 int filenameCmp(CaseSensitive cs
= CaseSensitive
.osDefault
, Range1
, Range2
)
3291 (auto ref Range1 filename1
, auto ref Range2 filename2
)
3292 if (isConvertibleToString
!Range1 || isConvertibleToString
!Range2
)
3294 import std
.meta
: staticMap
;
3295 alias Types
= staticMap
!(convertToString
, Range1
, Range2
);
3296 return filenameCmp
!(cs
, Types
)(filename1
, filename2
);
3301 assert(filenameCmp
!(CaseSensitive
.yes
)(TestAliasedString("Abc"), "abc") < 0);
3302 assert(filenameCmp
!(CaseSensitive
.yes
)("Abc", TestAliasedString("abc")) < 0);
3303 assert(filenameCmp
!(CaseSensitive
.yes
)(TestAliasedString("Abc"), TestAliasedString("abc")) < 0);
3308 assert(filenameCmp
!(CaseSensitive
.yes
)("Abc", "abc") < 0);
3309 assert(filenameCmp
!(CaseSensitive
.yes
)("abc", "Abc") > 0);
3311 assert(filenameCmp
!(CaseSensitive
.no
)("abc", "abc") == 0);
3312 assert(filenameCmp
!(CaseSensitive
.no
)("abc", "abd") < 0);
3313 assert(filenameCmp
!(CaseSensitive
.no
)("abc", "abb") > 0);
3314 assert(filenameCmp
!(CaseSensitive
.no
)("abc", "abcd") < 0);
3315 assert(filenameCmp
!(CaseSensitive
.no
)("abcd", "abc") > 0);
3316 assert(filenameCmp
!(CaseSensitive
.no
)("Abc", "abc") == 0);
3317 assert(filenameCmp
!(CaseSensitive
.no
)("abc", "Abc") == 0);
3318 assert(filenameCmp
!(CaseSensitive
.no
)("Abc", "abD") < 0);
3319 assert(filenameCmp
!(CaseSensitive
.no
)("abc", "AbB") > 0);
3321 version (Posix
) assert(filenameCmp(`abc\def`, `abc/def`) != 0);
3322 version (Windows
) assert(filenameCmp(`abc\def`, `abc/def`) == 0);
3325 /** Matches a pattern against a path.
3327 Some characters of pattern have a special meaning (they are
3328 $(I meta-characters)) and can't be escaped. These are:
3332 $(TD Matches 0 or more instances of any character.))
3334 $(TD Matches exactly one instance of any character.))
3335 $(TR $(TD `[`$(I chars)`]`)
3336 $(TD Matches one instance of any character that appears
3337 between the brackets.))
3338 $(TR $(TD `[!`$(I chars)`]`)
3339 $(TD Matches one instance of any character that does not
3340 appear between the brackets after the exclamation mark.))
3341 $(TR $(TD `{`$(I string1)`,`$(I string2)`,`…`}`)
3342 $(TD Matches either of the specified strings.))
3345 Individual characters are compared using `filenameCharCmp!cs`,
3346 where `cs` is an optional template parameter determining whether
3347 the comparison is case sensitive or not. See the
3348 $(LREF filenameCharCmp) documentation for details.
3351 separators and dots don't stop a meta-character from matching
3352 further portions of the path.
3355 cs = Whether the matching should be case-sensitive
3356 path = The path to be matched against
3357 pattern = The glob pattern
3360 `true` if pattern matches path, `false` otherwise.
3363 $(LINK2 http://en.wikipedia.org/wiki/Glob_%28programming%29,Wikipedia: _glob (programming))
3365 bool globMatch(CaseSensitive cs
= CaseSensitive
.osDefault
, C
, Range
)
3366 (Range path
, const(C
)[] pattern
)
3368 if (isForwardRange
!Range
&& !isInfinite
!Range
&&
3369 isSomeChar
!(ElementEncodingType
!Range
) && !isConvertibleToString
!Range
&&
3370 isSomeChar
!C
&& is(immutable C
== immutable ElementEncodingType
!Range
))
3373 // Verify that pattern[] is valid
3374 import std
.algorithm
.searching
: balancedParens
;
3375 import std
.utf
: byUTF
;
3377 assert(balancedParens(pattern
.byUTF
!C
, '[', ']', 0));
3378 assert(balancedParens(pattern
.byUTF
!C
, '{', '}', 0));
3382 alias RC
= Unqual
!(ElementEncodingType
!Range
);
3384 static if (RC
.sizeof
== 1 && isSomeString
!Range
)
3386 import std
.utf
: byChar
;
3387 return globMatch
!cs(path
.byChar
, pattern
);
3389 else static if (RC
.sizeof
== 2 && isSomeString
!Range
)
3391 import std
.utf
: byWchar
;
3392 return globMatch
!cs(path
.byWchar
, pattern
);
3396 import core
.memory
: pureMalloc
, pureFree
;
3398 scope(exit
) if (pattmp
!is null) (() @trusted => pureFree(pattmp
.ptr
))();
3400 for (size_t pi
= 0; pi
< pattern
.length
; pi
++)
3402 const pc
= pattern
[pi
];
3406 if (pi
+ 1 == pattern
.length
)
3408 for (; !path
.empty
; path
.popFront())
3411 if (globMatch
!(cs
, C
)(p
,
3412 pattern
[pi
+ 1 .. pattern
.length
]))
3426 auto nc
= path
.front
;
3430 if (pattern
[pi
] == '!')
3435 auto anymatch
= false;
3438 const pc2
= pattern
[pi
];
3441 if (!anymatch
&& (filenameCharCmp
!cs(nc
, pc2
) == 0))
3445 if (anymatch
== not)
3450 // find end of {} section
3452 for (; piRemain
< pattern
.length
3453 && pattern
[piRemain
] != '}'; ++piRemain
)
3456 if (piRemain
< pattern
.length
)
3460 while (pi
< pattern
.length
)
3463 C pc3
= pattern
[pi
];
3464 // find end of current alternative
3465 for (; pi
< pattern
.length
&& pc3
!= '}' && pc3
!= ','; ++pi
)
3473 if (globMatch
!(cs
, C
)(p
, pattern
[piRemain
..$]))
3482 * pattern[pi0 .. pi-1] ~ pattern[piRemain..$]
3486 // Allocate this only once per function invocation.
3487 pattmp
= (() @trusted =>
3488 (cast(C
*) pureMalloc(C
.sizeof
* pattern
.length
))[0 .. pattern
.length
])
3492 const len1
= pi
- 1 - pi0
;
3493 pattmp
[0 .. len1
] = pattern
[pi0
.. pi
- 1];
3495 const len2
= pattern
.length
- piRemain
;
3496 pattmp
[len1
.. len1
+ len2
] = pattern
[piRemain
.. $];
3498 if (globMatch
!(cs
, C
)(p
, pattmp
[0 .. len1
+ len2
]))
3513 if (filenameCharCmp
!cs(pc
, path
.front
) != 0)
3524 @safe @nogc unittest
3526 assert(globMatch("foo.bar", "*"));
3527 assert(globMatch("foo.bar", "*.*"));
3528 assert(globMatch(`foo/foo\bar`, "f*b*r"));
3529 assert(globMatch("foo.bar", "f???bar"));
3530 assert(globMatch("foo.bar", "[fg]???bar"));
3531 assert(globMatch("foo.bar", "[!gh]*bar"));
3532 assert(globMatch("bar.fooz", "bar.{foo,bif}z"));
3533 assert(globMatch("bar.bifz", "bar.{foo,bif}z"));
3537 // Same as calling globMatch!(CaseSensitive.no)(path, pattern)
3538 assert(globMatch("foo", "Foo"));
3539 assert(globMatch("Goo.bar", "[fg]???bar"));
3543 // Same as calling globMatch!(CaseSensitive.yes)(path, pattern)
3544 assert(!globMatch("foo", "Foo"));
3545 assert(!globMatch("Goo.bar", "[fg]???bar"));
3549 bool globMatch(CaseSensitive cs
= CaseSensitive
.osDefault
, C
, Range
)
3550 (auto ref Range path
, const(C
)[] pattern
)
3552 if (isConvertibleToString
!Range
)
3554 return globMatch
!(cs
, C
, StringTypeOf
!Range
)(path
, pattern
);
3559 assert(testAliasedString
!globMatch("foo.bar", "*"));
3564 assert(globMatch
!(CaseSensitive
.no
)("foo", "Foo"));
3565 assert(!globMatch
!(CaseSensitive
.yes
)("foo", "Foo"));
3567 assert(globMatch("foo", "*"));
3568 assert(globMatch("foo.bar"w
, "*"w
));
3569 assert(globMatch("foo.bar"d
, "*.*"d
));
3570 assert(globMatch("foo.bar", "foo*"));
3571 assert(globMatch("foo.bar"w
, "f*bar"w
));
3572 assert(globMatch("foo.bar"d
, "f*b*r"d
));
3573 assert(globMatch("foo.bar", "f???bar"));
3574 assert(globMatch("foo.bar"w
, "[fg]???bar"w
));
3575 assert(globMatch("foo.bar"d
, "[!gh]*bar"d
));
3577 assert(!globMatch("foo", "bar"));
3578 assert(!globMatch("foo"w
, "*.*"w
));
3579 assert(!globMatch("foo.bar"d
, "f*baz"d
));
3580 assert(!globMatch("foo.bar", "f*b*x"));
3581 assert(!globMatch("foo.bar", "[gh]???bar"));
3582 assert(!globMatch("foo.bar"w
, "[!fg]*bar"w
));
3583 assert(!globMatch("foo.bar"d
, "[fg]???baz"d
));
3584 // https://issues.dlang.org/show_bug.cgi?id=6634
3585 assert(!globMatch("foo.di", "*.d")); // triggered bad assertion
3587 assert(globMatch("foo.bar", "{foo,bif}.bar"));
3588 assert(globMatch("bif.bar"w
, "{foo,bif}.bar"w
));
3590 assert(globMatch("bar.foo"d
, "bar.{foo,bif}"d
));
3591 assert(globMatch("bar.bif", "bar.{foo,bif}"));
3593 assert(globMatch("bar.fooz"w
, "bar.{foo,bif}z"w
));
3594 assert(globMatch("bar.bifz"d
, "bar.{foo,bif}z"d
));
3596 assert(globMatch("bar.foo", "bar.{biz,,baz}foo"));
3597 assert(globMatch("bar.foo"w
, "bar.{biz,}foo"w
));
3598 assert(globMatch("bar.foo"d
, "bar.{,biz}foo"d
));
3599 assert(globMatch("bar.foo", "bar.{}foo"));
3601 assert(globMatch("bar.foo"w
, "bar.{ar,,fo}o"w
));
3602 assert(globMatch("bar.foo"d
, "bar.{,ar,fo}o"d
));
3603 assert(globMatch("bar.o", "bar.{,ar,fo}o"));
3605 assert(!globMatch("foo", "foo?"));
3606 assert(!globMatch("foo", "foo[]"));
3607 assert(!globMatch("foo", "foob"));
3608 assert(!globMatch("foo", "foo{b}"));
3611 static assert(globMatch("foo.bar", "[!gh]*bar"));
3617 /** Checks that the given file or directory name is valid.
3619 The maximum length of `filename` is given by the constant
3620 `core.stdc.stdio.FILENAME_MAX`. (On Windows, this number is
3621 defined as the maximum number of UTF-16 code points, and the
3622 test will therefore only yield strictly correct results when
3623 `filename` is a string of `wchar`s.)
3625 On Windows, the following criteria must be satisfied
3626 ($(LINK2 http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx,source)):
3628 $(LI `filename` must not contain any characters whose integer
3629 representation is in the range 0-31.)
3630 $(LI `filename` must not contain any of the following $(I reserved
3631 characters): `<>:"/\|?*`)
3632 $(LI `filename` may not end with a space ($(D ' ')) or a period
3636 On POSIX, `filename` may not contain a forward slash (`'/'`) or
3637 the null character (`'\0'`).
3640 filename = string to check
3643 `true` if and only if `filename` is not
3644 empty, not too long, and does not contain invalid characters.
3647 bool isValidFilename(Range
)(Range filename
)
3648 if ((isRandomAccessRange
!Range
&& hasLength
!Range
&& hasSlicing
!Range
&& isSomeChar
!(ElementEncodingType
!Range
) ||
3649 isNarrowString
!Range
) &&
3650 !isConvertibleToString
!Range
)
3652 import core
.stdc
.stdio
: FILENAME_MAX
;
3653 if (filename
.length
== 0 || filename
.length
>= FILENAME_MAX
) return false;
3654 foreach (c
; filename
)
3678 else version (Posix
)
3680 if (c
== 0 || c
== '/') return false;
3682 else static assert(0);
3686 auto last
= filename
[filename
.length
- 1];
3687 if (last
== '.' || last
== ' ') return false;
3690 // All criteria passed
3695 @safe pure @nogc nothrow
3698 import std
.utf
: byCodeUnit
;
3700 assert(isValidFilename("hello.exe".byCodeUnit
));
3703 bool isValidFilename(Range
)(auto ref Range filename
)
3704 if (isConvertibleToString
!Range
)
3706 return isValidFilename
!(StringTypeOf
!Range
)(filename
);
3711 assert(testAliasedString
!isValidFilename("hello.exe"));
3718 auto valid
= ["foo"];
3719 auto invalid
= ["", "foo\0bar", "foo/bar"];
3720 auto pfdep
= [`foo\bar`, "*.txt"];
3721 version (Windows
) invalid
~= pfdep
;
3722 else version (Posix
) valid
~= pfdep
;
3723 else static assert(0);
3725 import std
.meta
: AliasSeq
;
3726 static foreach (T
; AliasSeq
!(char[], const(char)[], string
, wchar[],
3727 const(wchar)[], wstring
, dchar[], const(dchar)[], dstring
))
3730 assert(isValidFilename(to
!T(fn
)));
3731 foreach (fn
; invalid
)
3732 assert(!isValidFilename(to
!T(fn
)));
3736 auto r
= MockRange
!(immutable(char))(`dir/file.d`);
3737 assert(!isValidFilename(r
));
3740 static struct DirEntry
{ string s
; alias s
this; }
3741 assert(isValidFilename(DirEntry("file.ext")));
3745 immutable string cases
= "<>:\"/\\|?*";
3746 foreach (i
; 0 .. 31 + cases
.length
)
3750 buf
[1] = i
<= 31 ?
cast(char) i
: cases
[i
- 32];
3752 assert(!isValidFilename(buf
[]));
3759 /** Checks whether `path` is a valid path.
3761 Generally, this function checks that `path` is not empty, and that
3762 each component of the path either satisfies $(LREF isValidFilename)
3763 or is equal to `"."` or `".."`.
3765 $(B It does $(I not) check whether the path points to an existing file
3766 or directory; use $(REF exists, std,file) for this purpose.)
3768 On Windows, some special rules apply:
3770 $(LI If the second character of `path` is a colon (`':'`),
3771 the first character is interpreted as a drive letter, and
3772 must be in the range A-Z (case insensitive).)
3773 $(LI If `path` is on the form $(D `\\$(I server)\$(I share)\...`)
3774 (UNC path), $(LREF isValidFilename) is applied to $(I server)
3775 and $(I share) as well.)
3776 $(LI If `path` starts with $(D `\\?\`) (long UNC path), the
3777 only requirement for the rest of the string is that it does
3778 not contain the null character.)
3779 $(LI If `path` starts with $(D `\\.\`) (Win32 device namespace)
3780 this function returns `false`; such paths are beyond the scope
3785 path = string or Range of characters to check
3788 true if `path` is a valid path.
3790 bool isValidPath(Range
)(Range path
)
3791 if ((isRandomAccessRange
!Range
&& hasLength
!Range
&& hasSlicing
!Range
&& isSomeChar
!(ElementEncodingType
!Range
) ||
3792 isNarrowString
!Range
) &&
3793 !isConvertibleToString
!Range
)
3795 alias C
= Unqual
!(ElementEncodingType
!Range
);
3797 if (path
.empty
) return false;
3799 // Check whether component is "." or "..", or whether it satisfies
3801 bool isValidComponent(Range component
)
3803 assert(component
.length
> 0);
3804 if (component
[0] == '.')
3806 if (component
.length
== 1) return true;
3807 else if (component
.length
== 2 && component
[1] == '.') return true;
3809 return isValidFilename(component
);
3812 if (path
.length
== 1)
3813 return isDirSeparator(path
[0]) ||
isValidComponent(path
);
3818 if (isDirSeparator(path
[0]) && isDirSeparator(path
[1]))
3820 // Some kind of UNC path
3821 if (path
.length
< 5)
3823 // All valid UNC paths must have at least 5 characters
3826 else if (path
[2] == '?')
3829 if (!isDirSeparator(path
[3])) return false;
3830 foreach (c
; path
[4 .. $])
3832 if (c
== '\0') return false;
3836 else if (path
[2] == '.')
3838 // Win32 device namespace not supported
3843 // Normal UNC path, i.e. \\server\share\...
3845 while (i
< path
.length
&& !isDirSeparator(path
[i
])) ++i
;
3846 if (i
== path
.length ||
!isValidFilename(path
[2 .. i
]))
3848 ++i
; // Skip a single dir separator
3850 while (j
< path
.length
&& !isDirSeparator(path
[j
])) ++j
;
3851 if (!isValidFilename(path
[i
.. j
])) return false;
3852 remainder
= path
[j
.. $];
3855 else if (isDriveSeparator(path
[1]))
3857 import std
.ascii
: isAlpha
;
3858 if (!isAlpha(path
[0])) return false;
3859 remainder
= path
[2 .. $];
3866 else version (Posix
)
3870 else static assert(0);
3871 remainder
= ltrimDirSeparators(remainder
);
3873 // Check that each component satisfies isValidComponent.
3874 while (!remainder
.empty
)
3877 while (i
< remainder
.length
&& !isDirSeparator(remainder
[i
])) ++i
;
3879 if (!isValidComponent(remainder
[0 .. i
])) return false;
3880 remainder
= ltrimDirSeparators(remainder
[i
.. $]);
3883 // All criteria passed
3888 @safe pure @nogc nothrow
3891 assert(isValidPath("/foo/bar"));
3892 assert(!isValidPath("/foo\0/bar"));
3893 assert(isValidPath("/"));
3894 assert(isValidPath("a"));
3898 assert(isValidPath(`c:\`));
3899 assert(isValidPath(`c:\foo`));
3900 assert(isValidPath(`c:\foo\.\bar\\\..\`));
3901 assert(!isValidPath(`!:\foo`));
3902 assert(!isValidPath(`c::\foo`));
3903 assert(!isValidPath(`c:\foo?`));
3904 assert(!isValidPath(`c:\foo.`));
3906 assert(isValidPath(`\\server\share`));
3907 assert(isValidPath(`\\server\share\foo`));
3908 assert(isValidPath(`\\server\share\\foo`));
3909 assert(!isValidPath(`\\\server\share\foo`));
3910 assert(!isValidPath(`\\server\\share\foo`));
3911 assert(!isValidPath(`\\ser*er\share\foo`));
3912 assert(!isValidPath(`\\server\sha?e\foo`));
3913 assert(!isValidPath(`\\server\share\|oo`));
3915 assert(isValidPath(`\\?\<>:"?*|/\..\.`));
3916 assert(!isValidPath("\\\\?\\foo\0bar"));
3918 assert(!isValidPath(`\\.\PhysicalDisk1`));
3919 assert(!isValidPath(`\\`));
3922 import std
.utf
: byCodeUnit
;
3923 assert(isValidPath("/foo/bar".byCodeUnit
));
3926 bool isValidPath(Range
)(auto ref Range path
)
3927 if (isConvertibleToString
!Range
)
3929 return isValidPath
!(StringTypeOf
!Range
)(path
);
3934 assert(testAliasedString
!isValidPath("/foo/bar"));
3937 /** Performs tilde expansion in paths on POSIX systems.
3938 On Windows, this function does nothing.
3940 There are two ways of using tilde expansion in a path. One
3941 involves using the tilde alone or followed by a path separator. In
3942 this case, the tilde will be expanded with the value of the
3943 environment variable `HOME`. The second way is putting
3944 a username after the tilde (i.e. `~john/Mail`). Here,
3945 the username will be searched for in the user database
3946 (i.e. `/etc/passwd` on Unix systems) and will expand to
3947 whatever path is stored there. The username is considered the
3948 string after the tilde ending at the first instance of a path
3951 Note that using the `~user` syntax may give different
3952 values from just `~` if the environment variable doesn't
3953 match the value stored in the user database.
3955 When the environment variable version is used, the path won't
3956 be modified if the environment variable doesn't exist or it
3957 is empty. When the database version is used, the path won't be
3958 modified if the user doesn't exist in the database or there is
3959 not enough memory to perform the query.
3961 This function performs several memory allocations.
3964 inputPath = The path name to expand.
3967 `inputPath` with the tilde expanded, or just `inputPath`
3968 if it could not be expanded.
3969 For Windows, `expandTilde` merely returns its argument `inputPath`.
3973 void processFile(string path)
3975 // Allow calling this function with paths such as ~/foo
3976 auto fullPath = expandTilde(path);
3981 string
expandTilde(return scope const string inputPath
) @safe nothrow
3985 import core
.exception
: onOutOfMemoryError
;
3986 import core
.stdc
.errno
: errno
, EBADF
, ENOENT
, EPERM
, ERANGE
, ESRCH
;
3987 import core
.stdc
.stdlib
: malloc
, free
, realloc
;
3989 /* Joins a path from a C string to the remainder of path.
3991 The last path separator from c_path is discarded. The result
3992 is joined to path[char_pos .. length] if char_pos is smaller
3993 than length, otherwise path is not appended to c_path.
3995 static string
combineCPathWithDPath(char* c_path
, string path
, size_t char_pos
) @trusted nothrow
3997 import core
.stdc
.string
: strlen
;
3998 import std
.exception
: assumeUnique
;
4000 assert(c_path
!= null);
4001 assert(path
.length
> 0);
4002 assert(char_pos
>= 0);
4004 // Search end of C string
4005 size_t end
= strlen(c_path
);
4007 const cPathEndsWithDirSep
= end
&& isDirSeparator(c_path
[end
- 1]);
4010 if (char_pos
< path
.length
)
4012 // Remove trailing path separator, if any (with special care for root /)
4013 if (cPathEndsWithDirSep
&& (end
> 1 ||
isDirSeparator(path
[char_pos
])))
4016 // Append something from path
4017 cp
= assumeUnique(c_path
[0 .. end
] ~ path
[char_pos
.. $]);
4021 // Remove trailing path separator, if any (except for root /)
4022 if (cPathEndsWithDirSep
&& end
> 1)
4025 // Create our own copy, as lifetime of c_path is undocumented
4026 cp
= c_path
[0 .. end
].idup
;
4032 // Replaces the tilde from path with the environment variable HOME.
4033 static string
expandFromEnvironment(string path
) @safe nothrow
4035 import core
.stdc
.stdlib
: getenv
;
4037 assert(path
.length
>= 1);
4038 assert(path
[0] == '~');
4040 // Get HOME and use that to replace the tilde.
4041 auto home
= () @trusted { return getenv("HOME"); } ();
4045 return combineCPathWithDPath(home
, path
, 1);
4048 // Replaces the tilde from path with the path from the user database.
4049 static string
expandFromDatabase(string path
) @safe nothrow
4051 // bionic doesn't really support this, as getpwnam_r
4052 // isn't provided and getpwnam is basically just a stub
4053 version (CRuntime_Bionic
)
4059 import core
.sys
.posix
.pwd
: passwd
, getpwnam_r
;
4060 import std
.string
: indexOf
;
4062 assert(path
.length
> 2 ||
(path
.length
== 2 && !isDirSeparator(path
[1])));
4063 assert(path
[0] == '~');
4065 // Extract username, searching for path separator.
4066 auto last_char
= indexOf(path
, dirSeparator
[0]);
4068 size_t username_len
= (last_char
== -1) ? path
.length
: last_char
;
4069 char[] username
= new char[username_len
* char.sizeof
];
4071 if (last_char
== -1)
4073 username
[0 .. username_len
- 1] = path
[1 .. $];
4074 last_char
= path
.length
+ 1;
4078 username
[0 .. username_len
- 1] = path
[1 .. last_char
];
4080 username
[username_len
- 1] = 0;
4082 assert(last_char
> 1);
4084 // Reserve C memory for the getpwnam_r() function.
4085 version (StdUnittest
)
4086 uint extra_memory_size
= 2;
4088 uint extra_memory_size
= 5 * 1024;
4089 char[] extra_memory
;
4094 extra_memory
.length
+= extra_memory_size
;
4096 // Obtain info from database.
4099 auto passResult
= () @trusted { return getpwnam_r(
4103 extra_memory
.length
,
4106 if (passResult
== 0)
4108 // Succeeded if verify points at result
4109 if (verify
== () @trusted { return &result
; } ())
4110 // username is found
4111 path
= combineCPathWithDPath(result
.pw_dir
, path
, last_char
);
4118 // On BSD and OSX, errno can be left at 0 instead of set to ERANGE
4126 // The given name or uid was not found.
4130 onOutOfMemoryError();
4133 // extra_memory isn't large enough
4134 import core
.checkedint
: mulu
;
4136 extra_memory_size
= mulu(extra_memory_size
, 2, overflow
);
4137 if (overflow
) assert(0);
4143 // Return early if there is no tilde in path.
4144 if (inputPath
.length
< 1 || inputPath
[0] != '~')
4147 if (inputPath
.length
== 1 ||
isDirSeparator(inputPath
[1]))
4148 return expandFromEnvironment(inputPath
);
4150 return expandFromDatabase(inputPath
);
4152 else version (Windows
)
4154 // Put here real windows implementation.
4159 static assert(0); // Guard. Implement on other platforms.
4168 import std
.process
: environment
;
4170 auto oldHome
= environment
["HOME"];
4171 scope(exit
) environment
["HOME"] = oldHome
;
4173 environment
["HOME"] = "dmd/test";
4174 assert(expandTilde("~/") == "dmd/test/");
4175 assert(expandTilde("~") == "dmd/test");
4183 static if (__traits(compiles
, { import std
.process
: executeShell
; }))
4184 import std
.process
: executeShell
;
4186 import std
.process
: environment
;
4187 import std
.string
: strip
;
4189 // Retrieve the current home variable.
4190 auto oldHome
= environment
.get("HOME");
4192 // Testing when there is no environment variable.
4193 environment
.remove("HOME");
4194 assert(expandTilde("~/") == "~/");
4195 assert(expandTilde("~") == "~");
4197 // Testing when an environment variable is set.
4198 environment
["HOME"] = "dmd/test";
4199 assert(expandTilde("~/") == "dmd/test/");
4200 assert(expandTilde("~") == "dmd/test");
4202 // The same, but with a variable ending in a slash.
4203 environment
["HOME"] = "dmd/test/";
4204 assert(expandTilde("~/") == "dmd/test/");
4205 assert(expandTilde("~") == "dmd/test");
4207 // The same, but with a variable set to root.
4208 environment
["HOME"] = "/";
4209 assert(expandTilde("~/") == "/");
4210 assert(expandTilde("~") == "/");
4212 // Recover original HOME variable before continuing.
4213 if (oldHome
!is null) environment
["HOME"] = oldHome
;
4214 else environment
.remove("HOME");
4216 static if (is(typeof(executeShell
)))
4218 immutable tildeUser
= "~" ~ environment
.get("USER");
4219 immutable path
= executeShell("echo " ~ tildeUser
).output
.strip();
4220 immutable expTildeUser
= expandTilde(tildeUser
);
4221 assert(expTildeUser
== path
, expTildeUser
);
4222 immutable expTildeUserSlash
= expandTilde(tildeUser
~ "/");
4223 immutable pathSlash
= path
[$-1] == '/' ? path
: path
~ "/";
4224 assert(expTildeUserSlash
== pathSlash
, expTildeUserSlash
);
4227 assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey");
4235 import std
.process
: environment
;
4237 string
testPath(scope const string source_path
) {
4238 return source_path
.expandTilde
;
4241 auto oldHome
= environment
["HOME"];
4242 scope(exit
) environment
["HOME"] = oldHome
;
4244 environment
["HOME"] = "dmd/test";
4245 assert(testPath("~/") == "dmd/test/");
4246 assert(testPath("~") == "dmd/test");
4251 version (StdUnittest
)
4254 /* Define a mock RandomAccessRange to use for unittesting.
4259 this(C
[] array
) { this.array
= array
; }
4262 @property size_t
length() { return array
.length
; }
4263 @property bool empty() { return array
.length
== 0; }
4264 @property C
front() { return array
[0]; }
4265 @property C
back() { return array
[$ - 1]; }
4266 alias opDollar
= length
;
4267 C
opIndex(size_t i
) { return array
[i
]; }
4269 void popFront() { array
= array
[1 .. $]; }
4270 void popBack() { array
= array
[0 .. $-1]; }
4271 MockRange
!C
opSlice( size_t lwr
, size_t upr
) const
4273 return MockRange
!C(array
[lwr
.. upr
]);
4275 @property MockRange
save() { return this; }
4280 /* Define a mock BidirectionalRange to use for unittesting.
4283 struct MockBiRange(C
)
4285 this(const(C
)[] array
) { this.array
= array
; }
4288 @property bool empty() { return array
.length
== 0; }
4289 @property C
front() { return array
[0]; }
4290 @property C
back() { return array
[$ - 1]; }
4291 @property size_t
opDollar() { return array
.length
; }
4293 void popFront() { array
= array
[1 .. $]; }
4294 void popBack() { array
= array
[0 .. $-1]; }
4295 @property MockBiRange
save() { return this; }
4304 static assert( isRandomAccessRange
!(MockRange
!(const(char))) );
4305 static assert( isBidirectionalRange
!(MockBiRange
!(const(char))) );
4308 private template BaseOf(R
)
4310 static if (isRandomAccessRange
!R
&& isSomeChar
!(ElementType
!R
))
4313 alias BaseOf
= StringTypeOf
!R
;