1 /++ 2 Utility to aid in script-like programs. 3 4 This is deliberately created as one file for easier usage 5 in script-like programs. 6 7 Written in the D programming language. 8 Tested with DMD 2.064.2 9 Licensed under The zlib/libpng License 10 11 Homepage: 12 https://github.com/abscissa/scriptlike 13 14 API Reference: 15 http://semitwist.com/scriptlike 16 +/ 17 18 module scriptlike; 19 20 // Import privately because this module wraps all the members. 21 import std.file; 22 import std.path; 23 24 // Automatically pull in anything likely to be useful for scripts. 25 // curl is deliberately left out here because it involves an extra 26 // link dependency. 27 public import std.algorithm; 28 public import std.array; 29 public import std.bigint; 30 public import std.conv; 31 public import std.datetime; 32 public import std.exception; 33 public import std.getopt; 34 public import std.math; 35 public import std.process; 36 public import std.random; 37 public import std.range; 38 public import std.regex; 39 public import std.stdio; 40 public import std.string; 41 public import std.system; 42 public import std.traits; 43 public import std.typecons; 44 public import std.typetuple; 45 public import std.uni; 46 public import std.variant; 47 48 /++ 49 In your main(), catch this Fail exception, then output Fail.msg and 50 return an error code. 51 52 Example: 53 54 int main() 55 { 56 try 57 { 58 // Your code here 59 } 60 catch(Fail e) 61 { 62 writeln("mytool: ERROR: ", e.msg); 63 return 1; 64 } 65 66 return 0; 67 } 68 +/ 69 class Fail : Exception 70 { 71 this(string msg, string file=__FILE__, int line=__LINE__) 72 { 73 super(msg, file, line); 74 } 75 } 76 77 /// If you've set up your main() to handle the Fail exception (as shown in 78 /// Fail's documentation, then call this to end your program with an error 79 /// message in an exception-safe way. 80 void fail(string msg, string file=__FILE__, int line=__LINE__) 81 { 82 throw new Fail(msg, file, line); 83 } 84 85 //TODO: Support optional OutputRange sink as an alternative to stdout 86 /// If true, all commands will be echoed to stdout 87 bool scriptlikeTraceCommands = false; 88 89 string escapeShellPath(T)(T str) if(isSomeString!T) 90 { 91 string s = str.to!string(); 92 93 if(str.canFind(' ')) 94 { 95 version(Windows) 96 return escapeWindowsArgument(s); 97 else version(Posix) 98 return escapeShellFileName(s); 99 else 100 static assert(0, "This platform not supported."); 101 } 102 else 103 return s; 104 } 105 106 /// Helper for creating an Ext. 107 /// 108 /// Returns either Ext!char, Ext!wchar or Ext!dchar depending on the 109 /// type of string given. 110 auto ext(T)(T str) if(isSomeString!T) 111 { 112 return Ext!( Unqual!(ElementEncodingType!T) )(str); 113 } 114 115 /// Represents a file extension. 116 struct Ext(C = char) if( is(C==char) || is(C==wchar) || is(C==dchar) ) 117 { 118 private immutable(C)[] str; 119 120 /// Main constructor. 121 this(immutable(C)[] extension = null) 122 { 123 this.str = extension; 124 } 125 126 /// Convert to string. 127 string toString() 128 { 129 return str.to!string(); 130 } 131 132 /// Convert to string, wstring or dstring, depending on the type of Ext. 133 immutable(C)[] toRawString() 134 { 135 return str; 136 } 137 138 /// Compare using OS-specific case-sensitivity rules. If you want to force 139 /// case-sensitive or case-insensistive, then call filenameCmp instead. 140 int opCmp(ref const Ext!C other) const 141 { 142 return filenameCmp(this.str, other.str); 143 } 144 145 ///ditto 146 int opCmp(Ext!C other) const 147 { 148 return filenameCmp(this.str, other.str); 149 } 150 151 ///ditto 152 int opCmp(string other) const 153 { 154 return filenameCmp(this.str, other); 155 } 156 157 /// Compare using OS-specific case-sensitivity rules. If you want to force 158 /// case-sensitive or case-insensistive, then call filenameCmp instead. 159 int opEquals(ref const Ext!C other) const 160 { 161 return opCmp(other) == 0; 162 } 163 164 ///ditto 165 int opEquals(Ext!C other) const 166 { 167 return opCmp(other) == 0; 168 } 169 170 ///ditto 171 int opEquals(string other) const 172 { 173 return opCmp(other) == 0; 174 } 175 } 176 177 /// Helper for creating a Path. 178 /// 179 /// Returns either Path!char, Path!wchar or Path!dchar depending on the 180 /// type of string given. 181 auto path(T)(T str = ".") if(isSomeString!T) 182 { 183 return Path!( Unqual!(ElementEncodingType!T) )(str); 184 } 185 186 /// Represents a filesystem path. The path is always kept normalized 187 /// automatically (as performed by buildNormalizedPathFixed). 188 /// 189 /// wchar and dchar versions not yet supported, blocked by DMD issue #12112 190 struct Path(C = char) if( is(C==char) /+|| is(C==wchar) || is(C==dchar)+/ ) 191 { 192 private immutable(C)[] str = "."; 193 194 /// Main constructor. 195 this()(const(C)[] path = ".") @safe pure nothrow 196 { 197 this.str = buildNormalizedPathFixed(path); 198 } 199 200 /++ 201 Convert from one type of Path to another. 202 Example: 203 auto pathStr = Path("foo"); 204 auto pathWStr = Path!wchar(pathStr); 205 auto pathDStr = Path!dchar(pathStr); 206 pathStr = Path(pathWStr); 207 pathStr = Path(pathDStr); 208 +/ 209 this(T)(T[] path = ".") if(isSomeChar!T) 210 { 211 this.str = to!(immutable(C)[])( buildNormalizedPathFixed(path.to!string()) ); 212 } 213 214 @trusted pure nothrow invariant() 215 { 216 assert(str == buildNormalizedPathFixed(str)); 217 } 218 219 /// Convert to string, quoting or escaping spaces if necessary. 220 string toString() 221 { 222 return escapeShellPath(str); 223 } 224 225 /// Convert to string, wstring or dstring, depending on the type of Path. 226 /// Does NOT do any escaping, even if path contains spaces. 227 immutable(C)[] toRawString() 228 { 229 return str; 230 } 231 232 /// Concatenates two paths, with a directory separator in between. 233 Path!C opBinary(string op)(Path!C rhs) if(op=="~") 234 { 235 Path!C newPath; 236 newPath.str = buildNormalizedPathFixed(this.str, rhs.str); 237 return newPath; 238 } 239 240 ///ditto 241 Path!C opBinary(string op)(const(C)[] rhs) if(op=="~") 242 { 243 Path!C newPath; 244 newPath.str = buildNormalizedPathFixed(this.str, rhs); 245 return newPath; 246 } 247 248 ///ditto 249 Path!C opBinaryRight(string op)(const(C)[] lhs) if(op=="~") 250 { 251 Path!C newPath; 252 newPath.str = buildNormalizedPathFixed(lhs, this.str); 253 return newPath; 254 } 255 256 /// Appends an extension to a path. Naturally, a directory separator 257 /// is NOT inserted in between. 258 Path!C opBinary(string op)(Ext!C rhs) if(op=="~") 259 { 260 Path!C newPath; 261 newPath.str = this.str.setExtension(rhs.str); 262 return newPath; 263 } 264 265 /// Appends a path to this one, with a directory separator in between. 266 Path!C opOpAssign(string op)(Path!C rhs) if(op=="~") 267 { 268 str = buildNormalizedPathFixed(str, rhs.str); 269 return this; 270 } 271 272 ///ditto 273 Path!C opOpAssign(string op)(const(C)[] rhs) if(op=="~") 274 { 275 str = buildNormalizedPathFixed(str, rhs); 276 return this; 277 } 278 279 /// Appends an extension to this path. Naturally, a directory separator 280 /// is NOT inserted in between. 281 Path!C opOpAssign(string op)(Ext!C rhs) if(op=="~") 282 { 283 str = str.setExtension(rhs.str); 284 return this; 285 } 286 287 /// Compare using OS-specific case-sensitivity rules. If you want to force 288 /// case-sensitive or case-insensistive, then call filenameCmp instead. 289 int opCmp(ref const Path!C other) const 290 { 291 return filenameCmp(this.str, other.str); 292 } 293 294 ///ditto 295 int opCmp(Path!C other) const 296 { 297 return filenameCmp(this.str, other.str); 298 } 299 300 ///ditto 301 int opCmp(string other) const 302 { 303 return filenameCmp(this.str, other); 304 } 305 306 /// Compare using OS-specific case-sensitivity rules. If you want to force 307 /// case-sensitive or case-insensistive, then call filenameCmp instead. 308 int opEquals(ref const Path!C other) const 309 { 310 return opCmp(other) == 0; 311 } 312 313 ///ditto 314 int opEquals(Path!C other) const 315 { 316 return opCmp(other) == 0; 317 } 318 319 ///ditto 320 int opEquals(string other) const 321 { 322 return opCmp(other) == 0; 323 } 324 325 /// Returns the parent path, according to std.path.dirName. 326 @property Path!C up() 327 { 328 return this.dirName(); 329 } 330 331 /// Is this path equal to empty string? 332 @property bool empty() 333 { 334 return str == ""; 335 } 336 } 337 338 /// Convenience aliases 339 alias extOf = extension; 340 alias stripExt = stripExtension; ///ditto 341 alias setExt = setExtension; ///ditto 342 alias defaultExt = defaultExtension; ///ditto 343 344 /// Checks if the path exists as a directory. 345 /// 346 /// This is like std.file.isDir, but returns false instead of throwing if the 347 /// path doesn't exist. 348 bool existsAsDir()(in char[] path) @trusted 349 { 350 return exists(path) && isDir(path); 351 } 352 ///ditto 353 bool existsAsDir(C)(in Path!C path) @trusted if(isSomeChar!C) 354 { 355 return existsAsDir(path.str.to!string()); 356 } 357 358 /// Checks if the path exists as a file. 359 /// 360 /// This is like std.file.isFile, but returns false instead of throwing if the 361 /// path doesn't exist. 362 bool existsAsFile()(in char[] path) @trusted 363 { 364 return exists(path) && isFile(path); 365 } 366 ///ditto 367 bool existsAsFile(C)(in Path!C path) @trusted if(isSomeChar!C) 368 { 369 return existsAsFile(path.str.to!string()); 370 } 371 372 /// Checks if the path exists as a symlink. 373 /// 374 /// This is like std.file.isSymlink, but returns false instead of throwing if the 375 /// path doesn't exist. 376 bool existsAsSymlink()(in char[] path) @trusted 377 { 378 return exists(path) && isSymlink(path); 379 } 380 ///ditto 381 bool existsAsSymlink(C)(in Path!C path) @trusted if(isSomeChar!C) 382 { 383 return existsAsSymlink(path.str.to!string()); 384 } 385 386 /// Like buildNormalizedPath, but if the result is the current directory, 387 /// this returns "." instead of "". However, if all the inputs are "", or there 388 /// are no inputs, this still returns "" just like buildNormalizedPath. 389 immutable(C)[] buildNormalizedPathFixed(C)(const(C[])[] paths...) 390 @trusted pure nothrow 391 if(isSomeChar!C) 392 { 393 if(all!`a==""`(paths)) 394 return ""; 395 396 auto result = buildNormalizedPath(paths); 397 return result==""? "." : result; 398 } 399 400 private void echoCommand(lazy string command) 401 { 402 if(scriptlikeTraceCommands) 403 writeln(command); 404 } 405 406 /// Runs a command, through the system's command shell interpreter, 407 /// in typical shell-script style: Synchronously, with the command's 408 /// stdout/in/err automatically forwarded through your 409 /// program's stdout/in/err. 410 /// 411 /// Optionally takes a working directory to run the command from. 412 /// 413 /// The command is echoed if scriptlikeTraceCommands is true. 414 int runShell()(string command) 415 { 416 echoCommand(command); 417 return system(command); 418 } 419 420 ///ditto 421 int runShell(C)(Path!C workingDirectory, string command) 422 { 423 auto saveDir = getcwd(); 424 workingDirectory.chdir(); 425 scope(exit) saveDir.chdir(); 426 427 return runShell(command); 428 } 429 430 // -- Wrappers for std.path -------------------- 431 432 /// Part of workaround for DMD Issue #12111 433 alias dirSeparator = std.path.dirSeparator; 434 alias pathSeparator = std.path.pathSeparator; ///ditto 435 alias isDirSeparator = std.path.isDirSeparator; ///ditto 436 alias CaseSensitive = std.path.CaseSensitive; ///ditto 437 alias osDefaultCaseSensitivity = std.path.osDefaultCaseSensitivity; ///ditto 438 alias buildPath = std.path.buildPath; ///ditto 439 alias buildNormalizedPath = std.path.buildNormalizedPath; ///ditto 440 441 /// Just like std.path.baseName, but operates on Path. 442 Path!C baseName(C)(Path!C path) 443 @trusted pure 444 { 445 return Path!C( path.str.baseName() ); 446 } 447 448 ///ditto 449 Path!C baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1) 450 (Path!C path, in C1[] suffix) 451 @safe pure 452 if(isSomeChar!C1) 453 { 454 return Path!C( path.str.baseName!cs(suffix) ); 455 } 456 457 /// Part of workaround for DMD Issue #12111 458 inout(C)[] baseName(C)(inout(C)[] path) 459 @trusted pure 460 if (isSomeChar!C) 461 { 462 return std.path.baseName(path); 463 } 464 465 ///ditto 466 inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1) 467 (inout(C)[] path, in C1[] suffix) 468 @safe pure 469 if (isSomeChar!C && isSomeChar!C1) 470 { 471 return std.path.baseName(path, suffix); 472 } 473 474 /// Just like std.path.dirName, but operates on Path. 475 Path!C dirName(C)(Path!C path) if(isSomeChar!C) 476 { 477 return Path!C( path.str.dirName() ); 478 } 479 480 /// Part of workaround for DMD Issue #12111 481 C[] dirName(C)(C[] path) 482 if (isSomeChar!C) 483 { 484 return std.path.dirName(path); 485 } 486 487 /// Just like std.path.rootName, but operates on Path. 488 Path!C rootName(C)(Path!C path) @safe pure nothrow 489 { 490 return Path!C( path.str.rootName() ); 491 } 492 493 /// Part of workaround for DMD Issue #12111 494 inout(C)[] rootName(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C) 495 { 496 return std.path.rootName(path); 497 } 498 499 /// Just like std.path.driveName, but operates on Path. 500 Path!C driveName(C)(Path!C path) @safe pure nothrow 501 { 502 return Path!C( path.str.driveName() ); 503 } 504 505 /// Part of workaround for DMD Issue #12111 506 inout(C)[] driveName(C)(inout(C)[] path) @safe pure nothrow 507 if (isSomeChar!C) 508 { 509 return std.path.driveName(path); 510 } 511 512 /// Just like std.path.stripDrive, but operates on Path. 513 Path!C stripDrive(C)(Path!C path) @safe pure nothrow 514 { 515 return Path!C( path.str.stripDrive() ); 516 } 517 518 /// Part of workaround for DMD Issue #12111 519 inout(C)[] stripDrive(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C) 520 { 521 return std.path.stripDrive(path); 522 } 523 524 /// Just like std.path.extension, but takes a Path and returns an Ext. 525 Ext!C extension(C)(in Path!C path) @safe pure nothrow 526 { 527 return Ext!C( path.str.extension() ); 528 } 529 530 /// Part of workaround for DMD Issue #12111 531 inout(C)[] extension(C)(inout(C)[] path) @safe pure nothrow if (isSomeChar!C) 532 { 533 return std.path.extension(path); 534 } 535 536 /// Just like std.path.stripExtension, but operates on Path. 537 Path!C stripExtension(C)(Path!C path) @safe pure nothrow 538 { 539 return Path!C( path.str.stripExtension() ); 540 } 541 542 /// Part of workaround for DMD Issue #12111 543 inout(C)[] stripExtension(C)(inout(C)[] path) @safe pure nothrow 544 if (isSomeChar!C) 545 { 546 return std.path.stripExtension(path); 547 } 548 549 /// Just like std.path.setExtension, but operates on Path. 550 Path!C setExtension(C, C2)(Path!C path, const(C2)[] ext) 551 @trusted pure nothrow 552 if(is(C == Unqual!C2)) 553 { 554 return Path!C( path.str.setExtension(ext) ); 555 } 556 557 ///ditto 558 Path!C setExtension(C)(Path!C path, Ext!C ext) 559 @trusted pure nothrow 560 { 561 return path.setExtension(ext.toString()); 562 } 563 564 /// Part of workaround for DMD Issue #12111 565 immutable(Unqual!C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext) 566 @trusted pure nothrow 567 if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2)) 568 { 569 return std.path.setExtension(path, ext); 570 } 571 572 /// Part of workaround for DMD Issue #12111 573 immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext) 574 @trusted pure nothrow 575 if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) 576 { 577 return std.path.setExtension(path, ext); 578 } 579 580 /// Just like std.path.defaultExtension, but operates on Path and optionally Ext. 581 Path!C defaultExtension(C, C2)(Path!C path, in C2[] ext) 582 @trusted pure 583 if(is(C == Unqual!C2)) 584 { 585 return Path!C( path.str.defaultExtension(ext) ); 586 } 587 588 ///ditto 589 Path!C defaultExtension(C)(Path!C path, Ext!C ext) 590 @trusted pure 591 { 592 return path.defaultExtension(ext.toString()); 593 } 594 595 /// Part of workaround for DMD Issue #12111 596 immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext) 597 @trusted pure 598 if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) 599 { 600 return std.path.defaultExtension(path, ext); 601 } 602 603 /// Just like std.path.pathSplitter. Note this returns a range of strings, 604 /// not a range of Path. 605 auto pathSplitter(C)(Path!C path) @safe pure nothrow 606 { 607 return pathSplitter(path.str); 608 } 609 610 /// Part of workaround for DMD Issue #12111 611 ReturnType!(std.path.pathSplitter!C) pathSplitter(C)(const(C)[] path) @safe pure nothrow 612 if (isSomeChar!C) 613 { 614 return std.path.pathSplitter(path); 615 } 616 617 /// Just like std.path.isRooted, but operates on Path. 618 bool isRooted(C)(in Path!C path) @safe pure nothrow 619 { 620 return path.str.isRooted(); 621 } 622 623 /// Part of workaround for DMD Issue #12111 624 bool isRooted(C)(in C[] path) @safe pure nothrow if (isSomeChar!C) 625 { 626 return std.path.isRooted(path); 627 } 628 629 /// Just like std.path.isAbsolute, but operates on Path. 630 bool isAbsolute(C)(in Path!C path) @safe pure nothrow 631 { 632 return path.str.isAbsolute(); 633 } 634 635 /// Part of workaround for DMD Issue #12111 636 bool isAbsolute(C)(in C[] path) @safe pure nothrow 637 if (isSomeChar!C) 638 { 639 return std.path.isAbsolute(path); 640 } 641 642 /// Just like std.path.absolutePath, but operates on Path. 643 Path!C absolutePath(C)(Path!C path, lazy string base = getcwd()) 644 @safe pure 645 { 646 return Path!C( path.str.absolutePath(base) ); 647 } 648 649 ///ditto 650 Path!C absolutePath(C)(Path!C path, Path!C base) 651 @safe pure 652 { 653 return Path!C( path.str.absolutePath(base.str.to!string()) ); 654 } 655 656 /// Part of workaround for DMD Issue #12111 657 string absolutePath(string path, lazy string base = getcwd()) 658 @safe pure 659 { 660 return std.path.absolutePath(path, base); 661 } 662 663 /// Just like std.path.relativePath, but operates on Path. 664 Path!C relativePath(CaseSensitive cs = CaseSensitive.osDefault, C) 665 (Path!C path, lazy string base = getcwd()) 666 { 667 return Path!C( path.str.relativePath!cs(base) ); 668 } 669 670 ///ditto 671 Path!C relativePath(CaseSensitive cs = CaseSensitive.osDefault, C) 672 (Path!C path, Path!C base) 673 { 674 return Path!C( path.str.relativePath!cs(base.str.to!string()) ); 675 } 676 677 /// Part of workaround for DMD Issue #12111 678 string relativePath(CaseSensitive cs = CaseSensitive.osDefault) 679 (string path, lazy string base = getcwd()) 680 { 681 return std.path.relativePath(path, base); 682 } 683 684 /// Part of workaround for DMD Issue #12111 685 int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b) 686 @safe pure nothrow 687 { 688 return std.path.filenameCharCmp(a, b); 689 } 690 691 /// Just like std.path.filenameCmp, but operates on Path. 692 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C, C2) 693 (Path!C path, Path!C2 filename2) 694 @safe pure 695 { 696 return path.str.filenameCmp(filename2.str); 697 } 698 699 ///ditto 700 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C, C2) 701 (Path!C path, const(C2)[] filename2) 702 @safe pure 703 if(isSomeChar!C2) 704 { 705 return path.str.filenameCmp(filename2); 706 } 707 708 ///ditto 709 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C, C2) 710 (const(C)[] path, Path!C2[] filename2) 711 @safe pure 712 if(isSomeChar!C) 713 { 714 return path.filenameCmp(filename2.str); 715 } 716 717 /// Part of workaround for DMD Issue #12111 718 int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, C1, C2) 719 (const(C1)[] filename1, const(C2)[] filename2) 720 @safe pure 721 if (isSomeChar!C1 && isSomeChar!C2) 722 { 723 return std.path.filenameCmp(filename1, filename2); 724 } 725 726 /// Just like std.path.globMatch, but operates on Path. 727 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C) 728 (Path!C path, const(C)[] pattern) 729 @safe pure nothrow 730 { 731 return path.str.globMatch!cs(pattern); 732 } 733 734 /// Part of workaround for DMD Issue #12111 735 bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C) 736 (const(C)[] path, const(C)[] pattern) 737 @safe pure nothrow 738 if (isSomeChar!C) 739 { 740 return std.path.globMatch(path, pattern); 741 } 742 743 /// Just like std.path.isValidFilename, but operates on Path. 744 bool isValidFilename(C)(in Path!C path) @safe pure nothrow 745 { 746 return path.str.isValidFilename(); 747 } 748 749 /// Part of workaround for DMD Issue #12111 750 bool isValidFilename(C)(in C[] filename) @safe pure nothrow if (isSomeChar!C) 751 { 752 return std.path.isValidFilename(filename); 753 } 754 755 /// Just like std.path.isValidPath, but operates on Path. 756 bool isValidPath(C)(in Path!C path) @safe pure nothrow 757 { 758 return path.str.isValidPath(); 759 } 760 761 /// Part of workaround for DMD Issue #12111 762 bool isValidPath(C)(in C[] path) @safe pure nothrow if (isSomeChar!C) 763 { 764 return std.path.isValidPath(path); 765 } 766 767 /// Just like std.path.expandTilde, but operates on Path. 768 Path!C expandTilde(C)(Path!C path) 769 { 770 static if( is(C == char) ) 771 return Path!C( path.str.expandTilde() ); 772 else 773 return Path!C( path.to!string().expandTilde().to!(C[])() ); 774 } 775 776 /// Part of workaround for DMD Issue #12111 777 string expandTilde(string inputPath) 778 { 779 return std.path.expandTilde(inputPath); 780 } 781 782 // -- Wrappers for std.file -------------------- 783 784 /// Part of workaround for DMD Issue #12111 785 alias FileException = std.file.FileException; 786 alias SpanMode = std.file.SpanMode; 787 alias attrIsDir = std.file.attrIsDir; 788 alias attrIsFile = std.file.attrIsFile; 789 alias attrIsSymlink = std.file.attrIsSymlink; 790 alias getcwd = std.file.getcwd; 791 alias thisExePath = std.file.thisExePath; 792 alias tempDir = std.file.tempDir; 793 794 /// Just like std.file.read, but takes a Path. 795 void[] read(C)(in Path!C name, size_t upTo = size_t.max) if(isSomeChar!C) 796 { 797 return read(name.str.to!string(), upTo); 798 } 799 800 /// Part of workaround for DMD Issue #12111 801 void[] read(in char[] name, size_t upTo = size_t.max) 802 { 803 return std.file.read(name, upTo); 804 } 805 806 /// Just like std.file.readText, but takes a Path. 807 template readText(S = string) 808 { 809 S readText(C)(in Path!C name) if(isSomeChar!C) 810 { 811 return std.file.readText(name.str.to!string()); 812 } 813 } 814 815 /// Part of workaround for DMD Issue #12111 816 S readText(S = string)(in char[] name) 817 { 818 return std.file.readText(name); 819 } 820 821 /// Just like std.file.write, but takes a Path. 822 void write(C)(in Path!C name, const void[] buffer) if(isSomeChar!C) 823 { 824 write(name.str.to!string(), buffer); 825 } 826 827 /// Part of workaround for DMD Issue #12111 828 void write(in char[] name, const void[] buffer) 829 { 830 std.file.write(name, buffer); 831 } 832 833 /// Just like std.file.append, but takes a Path. 834 void append(C)(in Path!C name, in void[] buffer) if(isSomeChar!C) 835 { 836 append(name.str.to!string(), buffer); 837 } 838 839 /// Part of workaround for DMD Issue #12111 840 void append(in char[] name, in void[] buffer) 841 { 842 std.file.append(name, buffer); 843 } 844 845 /// Just like std.file.rename, but takes Path, and echoes if scriptlikeTraceCommands is true. 846 void rename(C)(in Path!C from, in Path!C to) if(isSomeChar!C) 847 { 848 rename(from.str.to!string(), to.str.to!string()); 849 } 850 851 ///ditto 852 void rename(C)(in char[] from, in Path!C to) if(isSomeChar!C) 853 { 854 rename(from, to.str.to!string()); 855 } 856 857 ///ditto 858 void rename(C)(in Path!C from, in char[] to) if(isSomeChar!C) 859 { 860 rename(from.str.to!string(), to); 861 } 862 863 /// Just like std.file.rename, but echoes if scriptlikeTraceCommands is true. 864 void rename(in char[] from, in char[] to) 865 { 866 echoCommand("rename: "~from.escapeShellPath()~" -> "~to.escapeShellPath()); 867 std.file.rename(from, to); 868 } 869 870 /// If 'from' exists, then rename. Otherwise do nothing. 871 /// Returns: Success? 872 bool tryRename(T1, T2)(T1 from, T2 to) 873 { 874 if(from.exists()) 875 { 876 rename(from, to); 877 return true; 878 } 879 880 return false; 881 } 882 883 /// Just like std.file.remove, but takes a Path, and echoes if scriptlikeTraceCommands is true. 884 void remove(C)(in Path!C name) if(isSomeChar!C) 885 { 886 remove(name.str.to!string()); 887 } 888 889 /// Just like std.file.remove, but echoes if scriptlikeTraceCommands is true. 890 void remove(in char[] name) 891 { 892 echoCommand("remove: "~name.escapeShellPath()); 893 std.file.remove(name); 894 } 895 896 /// If 'name' exists, then remove. Otherwise do nothing. 897 /// Returns: Success? 898 bool tryRemove(T)(T name) 899 { 900 if(name.exists()) 901 { 902 remove(name); 903 return true; 904 } 905 906 return false; 907 } 908 909 /// Just like std.file.getSize, but takes a Path. 910 ulong getSize(C)(in Path!C name) if(isSomeChar!C) 911 { 912 return getSize(name.str.to!string()); 913 } 914 915 /// Part of workaround for DMD Issue #12111 916 ulong getSize(in char[] name) 917 { 918 return std.file.getSize(name); 919 } 920 921 /// Just like std.file.getTimes, but takes a Path. 922 void getTimes(C)(in Path!C name, 923 out SysTime accessTime, 924 out SysTime modificationTime) if(isSomeChar!C) 925 { 926 getTimes(name.str.to!string(), accessTime, modificationTime); 927 } 928 929 /// Part of workaround for DMD Issue #12111 930 void getTimes(in char[] name, 931 out SysTime accessTime, 932 out SysTime modificationTime) 933 { 934 std.file.getTimes(name, accessTime, modificationTime); 935 } 936 937 /// Windows-only. Just like std.file.getTimesWin, but takes a Path. 938 version(Windows) void getTimesWin(C)(in Path!C name, 939 out SysTime fileCreationTime, 940 out SysTime fileAccessTime, 941 out SysTime fileModificationTime) if(isSomeChar!C) 942 { 943 getTimesWin(name.str.to!string(), fileCreationTime, fileAccessTime, fileModificationTime); 944 } 945 946 /// Part of workaround for DMD Issue #12111 947 version(Windows) void getTimesWin(in char[] name, 948 out SysTime fileCreationTime, 949 out SysTime fileAccessTime, 950 out SysTime fileModificationTime) 951 { 952 std.file.getTimesWin(name, fileCreationTime, fileAccessTime, fileModificationTime); 953 } 954 955 /// Just like std.file.setTimes, but takes a Path. 956 void setTimes(C)(in Path!C name, 957 SysTime accessTime, 958 SysTime modificationTime) if(isSomeChar!C) 959 { 960 setTimes(name.str.to!string(), accessTime, modificationTime); 961 } 962 963 /// Part of workaround for DMD Issue #12111 964 void setTimes(in char[] name, 965 SysTime accessTime, 966 SysTime modificationTime) 967 { 968 return std.file.setTimes(name, accessTime, modificationTime); 969 } 970 971 /// Just like std.file.timeLastModified, but takes a Path. 972 SysTime timeLastModified(C)(in Path!C name) if(isSomeChar!C) 973 { 974 return timeLastModified(name.str.to!string()); 975 } 976 977 /// Just like std.file.timeLastModified, but takes a Path. 978 SysTime timeLastModified(C)(in Path!C name, SysTime returnIfMissing) if(isSomeChar!C) 979 { 980 return timeLastModified(name.str.to!string(), returnIfMissing); 981 } 982 983 /// Part of workaround for DMD Issue #12111 984 SysTime timeLastModified(in char[] name) 985 { 986 return std.file.timeLastModified(name); 987 } 988 989 ///ditto 990 SysTime timeLastModified(in char[] name, SysTime returnIfMissing) 991 { 992 return std.file.timeLastModified(name, returnIfMissing); 993 } 994 995 /// Just like std.file.exists, but takes a Path. 996 bool exists(C)(in Path!C name) @trusted if(isSomeChar!C) 997 { 998 return exists(name.str.to!string()); 999 } 1000 1001 /// Part of workaround for DMD Issue #12111 1002 bool exists(in char[] name) @trusted 1003 { 1004 return std.file.exists(name); 1005 } 1006 1007 /// Just like std.file.getAttributes, but takes a Path. 1008 uint getAttributes(C)(in Path!C name) if(isSomeChar!C) 1009 { 1010 return getAttributes(name.str.to!string()); 1011 } 1012 1013 /// Part of workaround for DMD Issue #12111 1014 uint getAttributes(in char[] name) 1015 { 1016 return std.file.getAttributes(name); 1017 } 1018 1019 /// Just like std.file.getLinkAttributes, but takes a Path. 1020 uint getLinkAttributes(C)(in Path!C name) if(isSomeChar!C) 1021 { 1022 return getLinkAttributes(name.str.to!string()); 1023 } 1024 1025 /// Part of workaround for DMD Issue #12111 1026 uint getLinkAttributes(in char[] name) 1027 { 1028 return std.file.getLinkAttributes(name); 1029 } 1030 1031 /// Just like std.file.isDir, but takes a Path. 1032 @property bool isDir(C)(in Path!C name) if(isSomeChar!C) 1033 { 1034 return isDir(name.str.to!string()); 1035 } 1036 1037 /// Part of workaround for DMD Issue #12111 1038 @property bool isDir(in char[] name) 1039 { 1040 return std.file.isDir(name); 1041 } 1042 1043 /// Just like std.file.isFile, but takes a Path. 1044 @property bool isFile(C)(in Path!C name) if(isSomeChar!C) 1045 { 1046 return isFile(name.str.to!string()); 1047 } 1048 1049 /// Part of workaround for DMD Issue #12111 1050 @property bool isFile(in char[] name) 1051 { 1052 return std.file.isFile(name); 1053 } 1054 1055 /// Just like std.file.isSymlink, but takes a Path. 1056 @property bool isSymlink(C)(Path!C name) if(isSomeChar!C) 1057 { 1058 return isSymlink(name.str.to!string()); 1059 } 1060 1061 /// Part of workaround for DMD Issue #12111 1062 @property bool isSymlink(C)(const(C)[] name) 1063 { 1064 return std.file.isSymlink(name); 1065 } 1066 1067 /// Just like std.file.chdir, but takes a Path, and echoes if scriptlikeTraceCommands is true. 1068 void chdir(C)(in Path!C pathname) if(isSomeChar!C) 1069 { 1070 chdir(pathname.str.to!string()); 1071 } 1072 1073 /// Just like std.file.chdir, but echoes if scriptlikeTraceCommands is true. 1074 void chdir(in char[] pathname) 1075 { 1076 echoCommand("chdir: "~pathname.escapeShellPath()); 1077 std.file.chdir(pathname); 1078 } 1079 1080 /// Just like std.file.mkdir, but takes a Path, and echoes if scriptlikeTraceCommands is true. 1081 void mkdir(C)(in Path!C pathname) if(isSomeChar!C) 1082 { 1083 mkdir(pathname.str.to!string()); 1084 } 1085 1086 /// Just like std.file.mkdir, but echoes if scriptlikeTraceCommands is true. 1087 void mkdir(in char[] pathname) 1088 { 1089 echoCommand("mkdir: "~pathname.escapeShellPath()); 1090 std.file.mkdir(pathname); 1091 } 1092 1093 /// If 'name' doesn't already exist, then mkdir. Otherwise do nothing. 1094 /// Returns: Success? 1095 bool tryMkdir(T)(T name) 1096 { 1097 if(!name.exists()) 1098 { 1099 mkdir(name); 1100 return true; 1101 } 1102 1103 return false; 1104 } 1105 1106 /// Just like std.file.mkdirRecurse, but takes a Path, and echoes if scriptlikeTraceCommands is true. 1107 void mkdirRecurse(C)(in Path!C pathname) if(isSomeChar!C) 1108 { 1109 mkdirRecurse(pathname.str.to!string()); 1110 } 1111 1112 /// Just like std.file.mkdirRecurse, but echoes if scriptlikeTraceCommands is true. 1113 void mkdirRecurse(in char[] pathname) 1114 { 1115 echoCommand("mkdirRecurse: "~pathname.escapeShellPath()); 1116 std.file.mkdirRecurse(pathname); 1117 } 1118 1119 /// If 'name' doesn't already exist, then mkdirRecurse. Otherwise do nothing. 1120 /// Returns: Success? 1121 bool tryMkdirRecurse(T)(T name) 1122 { 1123 if(!name.exists()) 1124 { 1125 mkdirRecurse(name); 1126 return true; 1127 } 1128 1129 return false; 1130 } 1131 1132 /// Just like std.file.rmdir, but takes a Path, and echoes if scriptlikeTraceCommands is true. 1133 void rmdir(C)(in Path!C pathname) if(isSomeChar!C) 1134 { 1135 rmdir(pathname.str.to!string()); 1136 } 1137 1138 /// Just like std.file.rmdir, but echoes if scriptlikeTraceCommands is true. 1139 void rmdir(in char[] pathname) 1140 { 1141 echoCommand("rmdir: "~pathname.escapeShellPath()); 1142 std.file.rmdir(pathname); 1143 } 1144 1145 /// If 'name' exists, then rmdir. Otherwise do nothing. 1146 /// Returns: Success? 1147 bool tryRmdir(T)(T name) 1148 { 1149 if(name.exists()) 1150 { 1151 rmdir(name); 1152 return true; 1153 } 1154 1155 return false; 1156 } 1157 1158 /// Posix-only. Just like std.file.symlink, but takes Path, and echoes if scriptlikeTraceCommands is true. 1159 version(Posix) void symlink(C1, C2)(Path!C1 original, Path!C2 link) if(isSomeChar!C1 && isSomeChar!C2) 1160 { 1161 symlink(original.str.to!string(), link.str.to!string()); 1162 } 1163 1164 ///ditto 1165 version(Posix) void symlink(C1, C2)(const(C1)[] original, Path!C2 link) if(isSomeChar!C1 && isSomeChar!C2) 1166 { 1167 symlink(original, link.str.to!string()); 1168 } 1169 1170 ///ditto 1171 version(Posix) void symlink(C1, C2)(Path!C1 original, const(C2)[] link) if(isSomeChar!C1 && isSomeChar!C2) 1172 { 1173 symlink(original.str.to!string(), link); 1174 } 1175 1176 /// Just like std.file.symlink, but echoes if scriptlikeTraceCommands is true. 1177 version(Posix) void symlink(C1, C2)(const(C1)[] original, const(C2)[] link) 1178 { 1179 echoCommand("symlink: [original] "~original.escapeShellPath()~" : [symlink] "~link.escapeShellPath()); 1180 std.file.symlink(original, link); 1181 } 1182 1183 /// If 'original' exists, then symlink. Otherwise do nothing. 1184 /// Returns: Success? 1185 version(Posix) bool trySymlink(T1, T2)(T1 original, T2 link) 1186 { 1187 if(original.exists()) 1188 { 1189 symlink(original, link); 1190 return true; 1191 } 1192 1193 return false; 1194 } 1195 1196 /// Posix-only. Just like std.file.readLink, but operates on Path. 1197 version(Posix) Path!C readLink(C)(Path!C link) if(isSomeChar!C) 1198 { 1199 return Path!C( readLink(link.str.to!string()) ); 1200 } 1201 1202 /// Part of workaround for DMD Issue #12111 1203 version(Posix) string readLink(C)(const(C)[] link) 1204 { 1205 return std.file.readLink(link); 1206 } 1207 1208 /// Just like std.file.copy, but takes Path, and echoes if scriptlikeTraceCommands is true. 1209 void copy(C)(in Path!C from, in Path!C to) if(isSomeChar!C) 1210 { 1211 copy(from.str.to!string(), to.str.to!string()); 1212 } 1213 1214 ///ditto 1215 void copy(C)(in char[] from, in Path!C to) if(isSomeChar!C) 1216 { 1217 copy(from, to.str.to!string()); 1218 } 1219 1220 ///ditto 1221 void copy(C)(in Path!C from, in char[] to) if(isSomeChar!C) 1222 { 1223 copy(from.str.to!string(), to); 1224 } 1225 1226 /// Just like std.file.copy, but echoes if scriptlikeTraceCommands is true. 1227 void copy(in char[] from, in char[] to) 1228 { 1229 echoCommand("copy: "~from.escapeShellPath()~" -> "~to.escapeShellPath()); 1230 std.file.copy(from, to); 1231 } 1232 1233 /// If 'from' exists, then copy. Otherwise do nothing. 1234 /// Returns: Success? 1235 bool tryCopy(T1, T2)(T1 from, T2 to) 1236 { 1237 if(from.exists()) 1238 { 1239 copy(from, to); 1240 return true; 1241 } 1242 1243 return false; 1244 } 1245 1246 /// Just like std.file.rmdirRecurse, but takes a Path, and echoes if scriptlikeTraceCommands is true. 1247 void rmdirRecurse(C)(in Path!C pathname) if(isSomeChar!C) 1248 { 1249 rmdirRecurse(pathname.str.to!string()); 1250 } 1251 1252 /// Just like std.file.rmdirRecurse, but echoes if scriptlikeTraceCommands is true. 1253 void rmdirRecurse(in char[] pathname) 1254 { 1255 echoCommand("rmdirRecurse: "~pathname.escapeShellPath()); 1256 std.file.rmdirRecurse(pathname); 1257 } 1258 1259 /// If 'name' exists, then rmdirRecurse. Otherwise do nothing. 1260 /// Returns: Success? 1261 bool tryRmdirRecurse(T)(T name) 1262 { 1263 if(name.exists()) 1264 { 1265 rmdirRecurse(name); 1266 return true; 1267 } 1268 1269 return false; 1270 } 1271 1272 /// Just like std.file.dirEntries, but takes a Path. 1273 auto dirEntries(C)(Path!C path, SpanMode mode, bool followSymlink = true) if(isSomeChar!C) 1274 { 1275 return dirEntries(path.str.to!string(), mode, followSymlink); 1276 } 1277 1278 /// Just like std.file.dirEntries, but takes a Path. 1279 auto dirEntries(C)(Path!C path, string pattern, SpanMode mode, 1280 bool followSymlink = true) if(isSomeChar!C) 1281 { 1282 return dirEntries(path.str.to!string(), pattern, mode, followSymlink); 1283 } 1284 1285 /// Part of workaround for DMD Issue #12111 1286 auto dirEntries(string path, SpanMode mode, bool followSymlink = true) 1287 { 1288 return std.file.dirEntries(path, mode, followSymlink); 1289 } 1290 1291 ///ditto 1292 auto dirEntries(string path, string pattern, SpanMode mode, 1293 bool followSymlink = true) 1294 { 1295 return std.file.dirEntries(path, pattern, mode, followSymlink); 1296 } 1297 1298 /// Just like std.file.slurp, but takes a Path. 1299 template slurp(Types...) 1300 { 1301 auto slurp(C)(Path!C filename, in char[] format) if(isSomeChar!C) 1302 { 1303 return std.file.slurp!Types(filename.str.to!string(), format); 1304 } 1305 } 1306 1307 /// Part of workaround for DMD Issue #12111 1308 Select!(Types.length == 1, Types[0][], Tuple!(Types)[]) 1309 slurp(Types...)(string filename, in char[] format) 1310 { 1311 return std.file.slurp(filename, format); 1312 } 1313 1314 // The unittests in this module mainly check that all the templates compile 1315 // correctly and that the appropriate Phobos functions are correctly called. 1316 // 1317 // A completely thorough testing of the behavior of such functions is 1318 // occasionally left to Phobos itself as it is outside the scope of these tests. 1319 1320 version(unittest_scriptlike_d) 1321 unittest 1322 { 1323 import std.stdio : writeln; 1324 writeln("Running 'scriptlike.d' unittests: std.path wrappers"); 1325 1326 alias dirSep = dirSeparator; 1327 1328 foreach(C; TypeTuple!(char/+, wchar, dchar+/)) 1329 { 1330 //pragma(msg, "==="~C.stringof); 1331 1332 { 1333 auto e = ext(".txt"); 1334 assert(e != ext(".dat")); 1335 assert(e == ext(".txt")); 1336 version(Windows) 1337 assert(e == ext(".TXT")); 1338 else version(OSX) 1339 assert(e == ext(".TXT")); 1340 else version(Posix) 1341 assert(e != ext(".TXT")); 1342 else 1343 static assert(0, "This platform not supported."); 1344 1345 // Test the other comparison overloads 1346 assert(e != Ext!C(".dat")); 1347 assert(e == Ext!C(".txt")); 1348 assert(Ext!C(".dat") != e); 1349 assert(Ext!C(".txt") == e); 1350 assert(".dat" != e); 1351 assert(".txt" == e); 1352 } 1353 1354 auto p = Path!C(); 1355 assert(p.str == "."); 1356 assert(!p.empty); 1357 1358 assert(Path!C("").empty); 1359 assert(path(cast(immutable(C)[])"").empty); 1360 1361 p = path(cast(immutable(C)[])"."); 1362 assert(!p.empty); 1363 1364 version(Windows) 1365 auto testStrings = [cast(immutable(C)[])"/foo/bar", "/foo/bar/", `\foo\bar`, `\foo\bar\`]; 1366 else version(Posix) 1367 auto testStrings = [cast(immutable(C)[])"/foo/bar", "/foo/bar/"]; 1368 else 1369 static assert(0, "This platform not supported."); 1370 1371 foreach(str; testStrings) 1372 { 1373 writeln(" testing str: ", str); 1374 1375 p = Path!C(str); 1376 assert(!p.empty); 1377 assert(p.str == dirSep~"foo"~dirSep~"bar"); 1378 1379 p = path(str); 1380 assert(p.str == dirSep~"foo"~dirSep~"bar"); 1381 assert(p.toRawString() == p.str); 1382 assert(p.toString() == p.str.to!string()); 1383 1384 assert(p.up.toString() == dirSep~"foo"); 1385 assert(p.up.up.toString() == dirSep); 1386 1387 assert((p~"sub").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub"); 1388 assert((p~"sub"~"2").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub"~dirSep~"2"); 1389 assert((p~path("sub")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub"); 1390 1391 version(Windows) 1392 assert((p~"sub dir").toString() == `"`~dirSep~"foo"~dirSep~"bar"~dirSep~"sub dir"~`"`); 1393 else version(Posix) 1394 assert((p~"sub dir").toString() == `'`~dirSep~"foo"~dirSep~"bar"~dirSep~`sub dir'`); 1395 else 1396 static assert(0, "This platform not supported."); 1397 1398 assert(("dir"~p).toString() == dirSep~"foo"~dirSep~"bar"); 1399 assert(("dir"~path(str[1..$])).toString() == "dir"~dirSep~"foo"~dirSep~"bar"); 1400 1401 p ~= "blah"; 1402 assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah"); 1403 1404 p ~= path("more"); 1405 assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah"~dirSep~"more"); 1406 1407 p ~= ".."; 1408 assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah"); 1409 1410 p ~= path(".."); 1411 assert(p.toString() == dirSep~"foo"~dirSep~"bar"); 1412 1413 p ~= "sub dir"; 1414 p ~= ".."; 1415 assert(p.toString() == dirSep~"foo"~dirSep~"bar"); 1416 1417 p ~= "filename"; 1418 assert((p~Ext!C(".txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1419 assert((p~Ext!C("txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1420 assert((p~Ext!C("")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename"); 1421 1422 p ~= ext(".ext"); 1423 assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1424 assert(p.baseName().toString() == "filename.ext"); 1425 assert(p.dirName().toString() == dirSep~"foo"~dirSep~"bar"); 1426 assert(p.rootName().toString() == dirSep); 1427 assert(p.driveName().toString() == ""); 1428 assert(p.stripDrive().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1429 version(Windows) 1430 { 1431 assert(( path("C:"~p.toRawString()) ).toString() == "C:"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1432 assert(( path("C:"~p.toRawString()) ).stripDrive().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1433 } 1434 assert(p.extension().toString() == ".ext"); 1435 assert(p.stripExtension().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename"); 1436 assert(p.setExtension(".txt").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1437 assert(p.setExtension("txt").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1438 assert(p.setExtension("").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename"); 1439 assert(p.setExtension(Ext!C(".txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1440 assert(p.setExtension(Ext!C("txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt"); 1441 assert(p.setExtension(Ext!C("")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename"); 1442 1443 assert(p.defaultExtension(".dat").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1444 assert(p.stripExtension().defaultExtension(".dat").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.dat"); 1445 1446 assert(equal(p.pathSplitter(), [dirSep, "foo", "bar", "filename.ext"])); 1447 1448 assert(p.isRooted()); 1449 version(Windows) 1450 assert(!p.isAbsolute()); 1451 else version(Posix) 1452 assert(p.isAbsolute()); 1453 else 1454 static assert(0, "This platform not supported."); 1455 1456 assert(!( path("dir"~p.toRawString()) ).isRooted()); 1457 assert(!( path("dir"~p.toRawString()) ).isAbsolute()); 1458 1459 version(Windows) 1460 { 1461 assert(( path("dir"~p.toRawString()) ).absolutePath("C:/main").toString() == "C:"~dirSep~"main"~dirSep~"dir"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1462 assert(( path("C:"~p.toRawString()) ).relativePath("C:/foo").toString() == "bar"~dirSep~"filename.ext"); 1463 assert(( path("C:"~p.toRawString()) ).relativePath("C:/foo/bar").toString() == "filename.ext"); 1464 } 1465 else version(Posix) 1466 { 1467 assert(( path("dir"~p.toRawString()) ).absolutePath("/main").toString() == dirSep~"main"~dirSep~"dir"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1468 assert(p.relativePath("/foo").toString() == "bar"~dirSep~"filename.ext"); 1469 assert(p.relativePath("/foo/bar").toString() == "filename.ext"); 1470 } 1471 else 1472 static assert(0, "This platform not supported."); 1473 1474 assert(p.filenameCmp(dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext") == 0); 1475 assert(p.filenameCmp(dirSep~"faa"~dirSep~"bat"~dirSep~"filename.ext") != 0); 1476 assert(p.globMatch("*foo*name.ext")); 1477 assert(!p.globMatch("*foo*Bname.ext")); 1478 1479 assert(!p.isValidFilename()); 1480 assert(p.baseName().isValidFilename()); 1481 assert(p.isValidPath()); 1482 1483 assert(p.expandTilde().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext"); 1484 1485 assert(p != path("/dir/subdir/filename.ext")); 1486 assert(p == path("/foo/bar/filename.ext")); 1487 version(Windows) 1488 assert(p == path("/FOO/BAR/FILENAME.EXT")); 1489 else version(OSX) 1490 assert(p == path("/FOO/BAR/FILENAME.EXT")); 1491 else version(Posix) 1492 assert(p != path("/FOO/BAR/FILENAME.EXT")); 1493 else 1494 static assert(0, "This platform not supported."); 1495 1496 // Test the other comparison overloads 1497 assert(p != Path!C("/dir/subdir/filename.ext")); 1498 assert(p == Path!C("/foo/bar/filename.ext")); 1499 assert(Path!C("/dir/subdir/filename.ext") != p); 1500 assert(Path!C("/foo/bar/filename.ext") == p); 1501 assert("/dir/subdir/filename.ext" != p); 1502 assert("/foo/bar/filename.ext" == p); 1503 } 1504 } 1505 } 1506 1507 version(unittest_scriptlike_d) 1508 unittest 1509 { 1510 // std.file.slurp seems to randomly trigger an internal std.algorithm 1511 // assert failure on DMD 2.064.2, so don't test it there. Seems 1512 // to be fixed in DMD 2.065. 1513 static import std.compiler; 1514 static if( 1515 std.compiler.vendor == std.compiler.Vendor.digitalMars || 1516 (std.compiler.version_major == 2 && std.compiler.version_minor == 64) 1517 ) 1518 enum testSlurp = false; 1519 else 1520 enum testSlurp = true; 1521 1522 import std.stdio : writeln; 1523 import core.thread; 1524 1525 writeln("Running 'scriptlike.d' unittests: std.file wrappers"); 1526 1527 immutable tempname = buildPath(tempDir(), "deleteme.script like.unit test.pid" ~ to!string(thisProcessID)); 1528 immutable tempname2 = buildPath(tempDir(), "deleteme.script like.unit test2.pid" ~ to!string(thisProcessID)); 1529 immutable tempname3 = buildPath(tempDir(), "deleteme.script like.unit test3.pid" ~ to!string(thisProcessID), "somefile"); 1530 auto tempPath = path(tempname); 1531 auto tempPath2 = path(tempname2); 1532 auto tempPath3 = path(tempname3); 1533 assert(!tempname.exists()); 1534 assert(!tempname2.exists()); 1535 assert(!tempname3.dirName().exists()); 1536 assert(!tempname3.exists()); 1537 1538 { 1539 scope(exit) 1540 { 1541 if(exists(tempname)) remove(tempname); 1542 } 1543 1544 tempPath.write("stuff"); 1545 1546 tempPath.append(" more"); 1547 assert(tempPath.read(3) == "stu"); 1548 assert(tempPath.read() == "stuff more"); 1549 assert(tempPath.readText() == "stuff more"); 1550 assert(tempPath.getSize() == 10); 1551 1552 if(testSlurp) 1553 { 1554 auto parsed = tempPath.slurp!(string, string)("%s %s"); 1555 assert(equal(parsed, [tuple("stuff", "more")])); 1556 } 1557 1558 SysTime timeA, timeB, timeC; 1559 tempPath.getTimes(timeA, timeB); 1560 version(Windows) 1561 tempPath.getTimesWin(timeA, timeB, timeC); 1562 tempPath.setTimes(timeA, timeB); 1563 timeA = tempPath.timeLastModified(); 1564 timeA = tempPath.timeLastModified(timeB); 1565 1566 uint attr; 1567 attr = tempPath.getAttributes(); 1568 attr = tempPath.getLinkAttributes(); 1569 1570 assert(tempPath.exists()); 1571 assert(tempPath.isFile()); 1572 assert(tempPath.existsAsFile()); 1573 assert(!tempPath.isDir()); 1574 assert(!tempPath.existsAsDir()); 1575 assert(!tempPath.isSymlink()); 1576 assert(!tempPath.existsAsSymlink()); 1577 tempPath.remove(); 1578 assert(!tempPath.exists()); 1579 assert(!tempPath.existsAsFile()); 1580 assert(!tempPath.existsAsDir()); 1581 assert(!tempPath.existsAsSymlink()); 1582 } 1583 1584 { 1585 assert(!tempPath.exists()); 1586 assert(!tempPath2.exists()); 1587 1588 scope(exit) 1589 { 1590 if(exists(tempname)) remove(tempname); 1591 if(exists(tempname2)) remove(tempname2); 1592 } 1593 tempPath.write("ABC"); 1594 1595 assert(tempPath.existsAsFile()); 1596 assert(!tempPath2.exists()); 1597 1598 tempPath.rename(tempPath2); 1599 1600 assert(!tempPath.exists()); 1601 assert(tempPath2.existsAsFile()); 1602 1603 tempPath2.copy(tempPath); 1604 1605 assert(tempPath.existsAsFile()); 1606 assert(tempPath2.existsAsFile()); 1607 } 1608 1609 { 1610 scope(exit) 1611 { 1612 if(exists(tempname)) rmdir(tempname); 1613 if(exists(tempname3)) rmdir(tempname3); 1614 if(exists(tempname3.dirName())) rmdir(tempname3.dirName()); 1615 } 1616 1617 assert(!tempPath.exists()); 1618 assert(!tempPath3.exists()); 1619 1620 tempPath.mkdir(); 1621 assert(tempPath.exists()); 1622 assert(!tempPath.isFile()); 1623 assert(!tempPath.existsAsFile()); 1624 assert(tempPath.isDir()); 1625 assert(tempPath.existsAsDir()); 1626 assert(!tempPath.isSymlink()); 1627 assert(!tempPath.existsAsSymlink()); 1628 1629 tempPath3.mkdirRecurse(); 1630 assert(tempPath3.exists()); 1631 assert(!tempPath3.isFile()); 1632 assert(!tempPath3.existsAsFile()); 1633 assert(tempPath3.isDir()); 1634 assert(tempPath3.existsAsDir()); 1635 assert(!tempPath3.isSymlink()); 1636 assert(!tempPath3.existsAsSymlink()); 1637 1638 auto saveDirName = getcwd(); 1639 auto saveDir = path(saveDirName); 1640 scope(exit) chdir(saveDirName); 1641 1642 tempPath.chdir(); 1643 assert(getcwd() == tempname); 1644 saveDir.chdir(); 1645 assert(getcwd() == saveDirName); 1646 1647 auto entries1 = (tempPath3~"..").dirEntries(SpanMode.shallow); 1648 assert(!entries1.empty); 1649 auto entries2 = (tempPath3~"..").dirEntries("*", SpanMode.shallow); 1650 assert(!entries2.empty); 1651 auto entries3 = (tempPath3~"..").dirEntries("TUNA TUNA THIS DOES NOT EXIST TUNA WHEE", SpanMode.shallow); 1652 assert(entries3.empty); 1653 1654 tempPath.rmdir(); 1655 assert(!tempPath.exists()); 1656 assert(!tempPath.existsAsFile()); 1657 assert(!tempPath.existsAsDir()); 1658 assert(!tempPath.existsAsSymlink()); 1659 1660 tempPath3.rmdirRecurse(); 1661 assert(!tempPath.exists()); 1662 assert(!tempPath.existsAsFile()); 1663 assert(!tempPath.existsAsDir()); 1664 assert(!tempPath.existsAsSymlink()); 1665 } 1666 1667 { 1668 version(Posix) 1669 { 1670 assert(!tempPath.exists()); 1671 assert(!tempPath2.exists()); 1672 1673 scope(exit) 1674 { 1675 if(exists(tempname2)) remove(tempname2); 1676 if(exists(tempname)) remove(tempname); 1677 } 1678 tempPath.write("DEF"); 1679 1680 tempPath.symlink(tempPath2); 1681 assert(tempPath2.exists()); 1682 assert(tempPath2.isFile()); 1683 assert(tempPath2.existsAsFile()); 1684 assert(!tempPath2.isDir()); 1685 assert(!tempPath2.existsAsDir()); 1686 assert(tempPath2.isSymlink()); 1687 assert(tempPath2.existsAsSymlink()); 1688 1689 auto linkTarget = tempPath2.readLink(); 1690 assert(linkTarget.toRawString() == tempname); 1691 } 1692 } 1693 1694 { 1695 assert(!tempPath.exists()); 1696 1697 scope(exit) 1698 { 1699 if(exists(tempname)) remove(tempname); 1700 } 1701 1702 runShell(`echo TestScriptStuff > `~tempPath.to!string()); 1703 assert(tempPath.exists()); 1704 assert(tempPath.isFile()); 1705 assert((cast(string)tempPath.read()).strip() == "TestScriptStuff"); 1706 } 1707 1708 { 1709 assert(!tempPath3.exists()); 1710 assert(!tempPath3.up.exists()); 1711 1712 scope(exit) 1713 { 1714 if(exists(tempname3)) remove(tempname3); 1715 if(exists(tempname3.dirName())) rmdir(tempname3.dirName()); 1716 } 1717 1718 tempPath3.up.mkdir(); 1719 assert(tempPath3.up.exists()); 1720 assert(tempPath3.up.isDir()); 1721 1722 tempPath3.up.runShell(`echo MoreTestStuff > `~tempPath3.baseName().to!string()); 1723 assert(tempPath3.exists()); 1724 assert(tempPath3.isFile()); 1725 assert((cast(string)tempPath3.read()).strip() == "MoreTestStuff"); 1726 } 1727 1728 { 1729 scope(exit) 1730 { 1731 if(exists(tempname)) rmdir(tempname); 1732 if(exists(tempname3)) rmdir(tempname3); 1733 if(exists(tempname3.dirName())) rmdir(tempname3.dirName()); 1734 } 1735 1736 assert(!tempPath.exists()); 1737 assert(!tempPath3.exists()); 1738 1739 assert(!tempPath.tryRmdir()); 1740 assert(!tempPath.tryRmdirRecurse()); 1741 assert(!tempPath.tryRemove()); 1742 assert(!tempPath.tryRename(tempPath3)); 1743 version(Posix) assert(!tempPath.trySymlink(tempPath3)); 1744 assert(!tempPath.tryCopy(tempPath3)); 1745 1746 assert(tempPath.tryMkdir()); 1747 assert(tempPath.exists()); 1748 assert(!tempPath.tryMkdir()); 1749 assert(!tempPath.tryMkdirRecurse()); 1750 1751 assert(tempPath.tryRmdir()); 1752 assert(!tempPath.exists()); 1753 1754 assert(tempPath.tryMkdirRecurse()); 1755 assert(tempPath.exists()); 1756 assert(!tempPath.tryMkdirRecurse()); 1757 } 1758 }