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