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