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