1 // Written in the D programming language. 2 3 /** 4 Standard I/O functions that extend $(B core.stdc.stdio). $(B core.stdc.stdio) 5 is $(D_PARAM public)ally imported when importing $(B std.stdio). 6 7 Source: $(PHOBOSSRC std/stdio.d) 8 Copyright: Copyright The D Language Foundation 2007-. 9 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 10 Authors: $(HTTP digitalmars.com, Walter Bright), 11 $(HTTP erdani.org, Andrei Alexandrescu), 12 Alex Rønne Petersen 13 */ 14 module std.stdio; 15 16 import core.stdc.stddef : wchar_t; 17 public import core.stdc.stdio; 18 import std.algorithm.mutation : copy; 19 import std.meta : allSatisfy; 20 import std.range.primitives : ElementEncodingType, empty, front, 21 isBidirectionalRange, isInputRange, put; 22 import std.traits : isSomeChar, isSomeString, Unqual, isPointer; 23 import std.typecons : Flag, No, Yes; 24 25 /++ 26 If flag `KeepTerminator` is set to `KeepTerminator.yes`, then the delimiter 27 is included in the strings returned. 28 +/ 29 alias KeepTerminator = Flag!"keepTerminator"; 30 31 version (CRuntime_Microsoft) 32 { 33 version = MICROSOFT_STDIO; 34 } 35 else version (CRuntime_DigitalMars) 36 { 37 // Specific to the way Digital Mars C does stdio 38 version = DIGITAL_MARS_STDIO; 39 } 40 41 version (CRuntime_Glibc) 42 { 43 // Specific to the way Gnu C does stdio 44 version = GCC_IO; 45 version = HAS_GETDELIM; 46 } 47 else version (CRuntime_Bionic) 48 { 49 version = GENERIC_IO; 50 version = HAS_GETDELIM; 51 } 52 else version (CRuntime_Musl) 53 { 54 version = GENERIC_IO; 55 version = HAS_GETDELIM; 56 } 57 else version (CRuntime_UClibc) 58 { 59 // uClibc supports GCC IO 60 version = GCC_IO; 61 version = HAS_GETDELIM; 62 } 63 64 version (OSX) 65 { 66 version = GENERIC_IO; 67 version = HAS_GETDELIM; 68 } 69 else version (iOS) 70 { 71 version = GENERIC_IO; 72 version = HAS_GETDELIM; 73 } 74 else version (TVOS) 75 { 76 version = GENERIC_IO; 77 version = HAS_GETDELIM; 78 } 79 else version (WatchOS) 80 { 81 version = GENERIC_IO; 82 version = HAS_GETDELIM; 83 } 84 else version (FreeBSD) 85 { 86 version = GENERIC_IO; 87 version = HAS_GETDELIM; 88 } 89 else version (NetBSD) 90 { 91 version = GENERIC_IO; 92 version = HAS_GETDELIM; 93 } 94 else version (DragonFlyBSD) 95 { 96 version = GENERIC_IO; 97 version = HAS_GETDELIM; 98 } 99 else version (Solaris) 100 { 101 version = GENERIC_IO; 102 version = NO_GETDELIM; 103 } 104 105 // Character type used for operating system filesystem APIs 106 version (Windows) 107 { 108 private alias FSChar = wchar; 109 } 110 else 111 { 112 private alias FSChar = char; 113 } 114 115 116 version (Windows) 117 { 118 // core.stdc.stdio.fopen expects file names to be 119 // encoded in CP_ACP on Windows instead of UTF-8. 120 /+ Waiting for druntime pull 299 121 +/ 122 extern (C) nothrow @nogc FILE* _wfopen(in wchar* filename, in wchar* mode); 123 extern (C) nothrow @nogc FILE* _wfreopen(in wchar* filename, in wchar* mode, FILE* fp); 124 125 import core.sys.windows.basetsd : HANDLE; 126 } 127 128 version (DIGITAL_MARS_STDIO) 129 { 130 extern (C) 131 { 132 /* ** 133 * Digital Mars under-the-hood C I/O functions. 134 * Use _iobuf* for the unshared version of FILE*, 135 * usable when the FILE is locked. 136 */ 137 nothrow: 138 @nogc: 139 int _fputc_nlock(int, _iobuf*); 140 int _fputwc_nlock(int, _iobuf*); 141 int _fgetc_nlock(_iobuf*); 142 int _fgetwc_nlock(_iobuf*); 143 int __fp_lock(FILE*); 144 void __fp_unlock(FILE*); 145 146 int setmode(int, int); 147 } 148 alias FPUTC = _fputc_nlock; 149 alias FPUTWC = _fputwc_nlock; 150 alias FGETC = _fgetc_nlock; 151 alias FGETWC = _fgetwc_nlock; 152 153 alias FLOCK = __fp_lock; 154 alias FUNLOCK = __fp_unlock; 155 156 alias _setmode = setmode; 157 int _fileno(FILE* f) { return f._file; } 158 alias fileno = _fileno; 159 } 160 else version (MICROSOFT_STDIO) 161 { 162 extern (C) 163 { 164 /* ** 165 * Microsoft under-the-hood C I/O functions 166 */ 167 nothrow: 168 @nogc: 169 int _fputc_nolock(int, _iobuf*); 170 int _fputwc_nolock(int, _iobuf*); 171 int _fgetc_nolock(_iobuf*); 172 int _fgetwc_nolock(_iobuf*); 173 void _lock_file(FILE*); 174 void _unlock_file(FILE*); 175 int _setmode(int, int); 176 int _fileno(FILE*); 177 FILE* _fdopen(int, const (char)*); 178 int _fseeki64(FILE*, long, int); 179 long _ftelli64(FILE*); 180 } 181 alias FPUTC = _fputc_nolock; 182 alias FPUTWC = _fputwc_nolock; 183 alias FGETC = _fgetc_nolock; 184 alias FGETWC = _fgetwc_nolock; 185 186 alias FLOCK = _lock_file; 187 alias FUNLOCK = _unlock_file; 188 189 alias setmode = _setmode; 190 alias fileno = _fileno; 191 } 192 else version (GCC_IO) 193 { 194 /* ** 195 * Gnu under-the-hood C I/O functions; see 196 * http://gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html 197 */ 198 extern (C) 199 { 200 nothrow: 201 @nogc: 202 int fputc_unlocked(int, _iobuf*); 203 int fputwc_unlocked(wchar_t, _iobuf*); 204 int fgetc_unlocked(_iobuf*); 205 int fgetwc_unlocked(_iobuf*); 206 void flockfile(FILE*); 207 void funlockfile(FILE*); 208 209 private size_t fwrite_unlocked(const(void)* ptr, 210 size_t size, size_t n, _iobuf *stream); 211 } 212 213 alias FPUTC = fputc_unlocked; 214 alias FPUTWC = fputwc_unlocked; 215 alias FGETC = fgetc_unlocked; 216 alias FGETWC = fgetwc_unlocked; 217 218 alias FLOCK = flockfile; 219 alias FUNLOCK = funlockfile; 220 } 221 else version (GENERIC_IO) 222 { 223 nothrow: 224 @nogc: 225 226 extern (C) 227 { 228 void flockfile(FILE*); 229 void funlockfile(FILE*); 230 } 231 232 int fputc_unlocked(int c, _iobuf* fp) { return fputc(c, cast(shared) fp); } 233 int fputwc_unlocked(wchar_t c, _iobuf* fp) 234 { 235 import core.stdc.wchar_ : fputwc; 236 return fputwc(c, cast(shared) fp); 237 } 238 int fgetc_unlocked(_iobuf* fp) { return fgetc(cast(shared) fp); } 239 int fgetwc_unlocked(_iobuf* fp) 240 { 241 import core.stdc.wchar_ : fgetwc; 242 return fgetwc(cast(shared) fp); 243 } 244 245 alias FPUTC = fputc_unlocked; 246 alias FPUTWC = fputwc_unlocked; 247 alias FGETC = fgetc_unlocked; 248 alias FGETWC = fgetwc_unlocked; 249 250 alias FLOCK = flockfile; 251 alias FUNLOCK = funlockfile; 252 } 253 else 254 { 255 static assert(0, "unsupported C I/O system"); 256 } 257 258 version (HAS_GETDELIM) extern(C) nothrow @nogc 259 { 260 ptrdiff_t getdelim(char**, size_t*, int, FILE*); 261 // getline() always comes together with getdelim() 262 ptrdiff_t getline(char**, size_t*, FILE*); 263 } 264 265 //------------------------------------------------------------------------------ 266 private struct ByRecordImpl(Fields...) 267 { 268 private: 269 import std.typecons : Tuple; 270 271 File file; 272 char[] line; 273 Tuple!(Fields) current; 274 string format; 275 276 public: 277 this(File f, string format) 278 { 279 assert(f.isOpen); 280 file = f; 281 this.format = format; 282 popFront(); // prime the range 283 } 284 285 /// Range primitive implementations. 286 @property bool empty() 287 { 288 return !file.isOpen; 289 } 290 291 /// Ditto 292 @property ref Tuple!(Fields) front() 293 { 294 return current; 295 } 296 297 /// Ditto 298 void popFront() 299 { 300 import std.conv : text; 301 import std.exception : enforce; 302 import std.format : formattedRead; 303 import std..string : chomp; 304 305 enforce(file.isOpen, "ByRecord: File must be open"); 306 file.readln(line); 307 if (!line.length) 308 { 309 file.detach(); 310 } 311 else 312 { 313 line = chomp(line); 314 formattedRead(line, format, ¤t); 315 enforce(line.empty, text("Leftover characters in record: `", 316 line, "'")); 317 } 318 } 319 } 320 321 template byRecord(Fields...) 322 { 323 auto byRecord(File f, string format) 324 { 325 return typeof(return)(f, format); 326 } 327 } 328 329 /** 330 Encapsulates a `FILE*`. Generally D does not attempt to provide 331 thin wrappers over equivalent functions in the C standard library, but 332 manipulating `FILE*` values directly is unsafe and error-prone in 333 many ways. The `File` type ensures safe manipulation, automatic 334 file closing, and a lot of convenience. 335 336 The underlying `FILE*` handle is maintained in a reference-counted 337 manner, such that as soon as the last `File` variable bound to a 338 given `FILE*` goes out of scope, the underlying `FILE*` is 339 automatically closed. 340 341 Example: 342 ---- 343 // test.d 344 import std.stdio; 345 346 void main(string[] args) 347 { 348 auto f = File("test.txt", "w"); // open for writing 349 f.write("Hello"); 350 if (args.length > 1) 351 { 352 auto g = f; // now g and f write to the same file 353 // internal reference count is 2 354 g.write(", ", args[1]); 355 // g exits scope, reference count decreases to 1 356 } 357 f.writeln("!"); 358 // f exits scope, reference count falls to zero, 359 // underlying `FILE*` is closed. 360 } 361 ---- 362 $(CONSOLE 363 % rdmd test.d Jimmy 364 % cat test.txt 365 Hello, Jimmy! 366 % __ 367 ) 368 */ 369 struct File 370 { 371 import core.atomic : atomicOp, atomicStore, atomicLoad; 372 import std.range.primitives : ElementEncodingType; 373 import std.traits : isScalarType, isArray; 374 enum Orientation { unknown, narrow, wide } 375 376 private struct Impl 377 { 378 FILE * handle = null; // Is null iff this Impl is closed by another File 379 shared uint refs = uint.max / 2; 380 bool isPopened; // true iff the stream has been created by popen() 381 Orientation orientation; 382 } 383 private Impl* _p; 384 private string _name; 385 386 package this(FILE* handle, string name, uint refs = 1, bool isPopened = false) @trusted 387 { 388 import core.stdc.stdlib : malloc; 389 import std.exception : enforce; 390 391 assert(!_p); 392 _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); 393 initImpl(handle, name, refs, isPopened); 394 } 395 396 private void initImpl(FILE* handle, string name, uint refs = 1, bool isPopened = false) 397 { 398 assert(_p); 399 _p.handle = handle; 400 atomicStore(_p.refs, refs); 401 _p.isPopened = isPopened; 402 _p.orientation = Orientation.unknown; 403 _name = name; 404 } 405 406 /** 407 Constructor taking the name of the file to open and the open mode. 408 409 Copying one `File` object to another results in the two `File` 410 objects referring to the same underlying file. 411 412 The destructor automatically closes the file as soon as no `File` 413 object refers to it anymore. 414 415 Params: 416 name = range or string representing the file _name 417 stdioOpenmode = range or string represting the open mode 418 (with the same semantics as in the C standard library 419 $(HTTP cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) 420 function) 421 422 Throws: `ErrnoException` if the file could not be opened. 423 */ 424 this(string name, scope const(char)[] stdioOpenmode = "rb") @safe 425 { 426 import std.conv : text; 427 import std.exception : errnoEnforce; 428 429 this(errnoEnforce(_fopen(name, stdioOpenmode), 430 text("Cannot open file `", name, "' in mode `", 431 stdioOpenmode, "'")), 432 name); 433 434 // MSVCRT workaround (issue 14422) 435 version (MICROSOFT_STDIO) 436 { 437 setAppendWin(stdioOpenmode); 438 } 439 } 440 441 /// ditto 442 this(R1, R2)(R1 name) 443 if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1)) 444 { 445 import std.conv : to; 446 this(name.to!string, "rb"); 447 } 448 449 /// ditto 450 this(R1, R2)(R1 name, R2 mode) 451 if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) && 452 isInputRange!R2 && isSomeChar!(ElementEncodingType!R2)) 453 { 454 import std.conv : to; 455 this(name.to!string, mode.to!string); 456 } 457 458 @safe unittest 459 { 460 static import std.file; 461 import std.utf : byChar; 462 auto deleteme = testFilename(); 463 auto f = File(deleteme.byChar, "w".byChar); 464 f.close(); 465 std.file.remove(deleteme); 466 } 467 468 ~this() @safe 469 { 470 detach(); 471 } 472 473 this(this) @safe nothrow 474 { 475 if (!_p) return; 476 assert(atomicLoad(_p.refs)); 477 atomicOp!"+="(_p.refs, 1); 478 } 479 480 /** 481 Assigns a file to another. The target of the assignment gets detached 482 from whatever file it was attached to, and attaches itself to the new 483 file. 484 */ 485 ref File opAssign(File rhs) @safe return 486 { 487 import std.algorithm.mutation : swap; 488 489 swap(this, rhs); 490 return this; 491 } 492 493 // https://issues.dlang.org/show_bug.cgi?id=20129 494 @safe unittest 495 { 496 File[int] aa; 497 aa.require(0, File.init); 498 } 499 500 /** 501 Detaches from the current file (throwing on failure), and then attempts to 502 _open file `name` with mode `stdioOpenmode`. The mode has the 503 same semantics as in the C standard library $(HTTP 504 cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) function. 505 506 Throws: `ErrnoException` in case of error. 507 */ 508 void open(string name, scope const(char)[] stdioOpenmode = "rb") @trusted 509 { 510 resetFile(name, stdioOpenmode, false); 511 } 512 513 // https://issues.dlang.org/show_bug.cgi?id=20585 514 @system unittest 515 { 516 File f; 517 try 518 f.open("doesn't exist"); 519 catch (Exception _e) 520 { 521 } 522 523 assert(!f.isOpen); 524 525 f.close(); // to check not crash here 526 } 527 528 private void resetFile(string name, scope const(char)[] stdioOpenmode, bool isPopened) @trusted 529 { 530 import core.stdc.stdlib : malloc; 531 import std.exception : enforce; 532 import std.conv : text; 533 import std.exception : errnoEnforce; 534 535 if (_p !is null) 536 { 537 detach(); 538 } 539 540 FILE* handle; 541 version (Posix) 542 { 543 if (isPopened) 544 { 545 errnoEnforce(handle = _popen(name, stdioOpenmode), 546 "Cannot run command `"~name~"'"); 547 } 548 else 549 { 550 errnoEnforce(handle = _fopen(name, stdioOpenmode), 551 text("Cannot open file `", name, "' in mode `", 552 stdioOpenmode, "'")); 553 } 554 } 555 else 556 { 557 assert(isPopened == false); 558 errnoEnforce(handle = _fopen(name, stdioOpenmode), 559 text("Cannot open file `", name, "' in mode `", 560 stdioOpenmode, "'")); 561 } 562 _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); 563 initImpl(handle, name, 1, isPopened); 564 version (MICROSOFT_STDIO) 565 { 566 setAppendWin(stdioOpenmode); 567 } 568 } 569 570 private void closeHandles() @trusted 571 { 572 assert(_p); 573 import std.exception : errnoEnforce; 574 575 version (Posix) 576 { 577 import core.sys.posix.stdio : pclose; 578 import std.format : format; 579 580 if (_p.isPopened) 581 { 582 auto res = pclose(_p.handle); 583 errnoEnforce(res != -1, 584 "Could not close pipe `"~_name~"'"); 585 _p.handle = null; 586 return; 587 } 588 } 589 if (_p.handle) 590 { 591 auto handle = _p.handle; 592 _p.handle = null; 593 // fclose disassociates the FILE* even in case of error (issue 19751) 594 errnoEnforce(.fclose(handle) == 0, 595 "Could not close file `"~_name~"'"); 596 } 597 } 598 599 version (MICROSOFT_STDIO) 600 { 601 private void setAppendWin(scope const(char)[] stdioOpenmode) @safe 602 { 603 bool append, update; 604 foreach (c; stdioOpenmode) 605 if (c == 'a') 606 append = true; 607 else 608 if (c == '+') 609 update = true; 610 if (append && !update) 611 seek(size); 612 } 613 } 614 615 /** 616 Reuses the `File` object to either open a different file, or change 617 the file mode. If `name` is `null`, the mode of the currently open 618 file is changed; otherwise, a new file is opened, reusing the C 619 `FILE*`. The function has the same semantics as in the C standard 620 library $(HTTP cplusplus.com/reference/cstdio/freopen/, freopen) 621 function. 622 623 Note: Calling `reopen` with a `null` `name` is not implemented 624 in all C runtimes. 625 626 Throws: `ErrnoException` in case of error. 627 */ 628 void reopen(string name, scope const(char)[] stdioOpenmode = "rb") @trusted 629 { 630 import std.conv : text; 631 import std.exception : enforce, errnoEnforce; 632 import std.internal.cstring : tempCString; 633 634 enforce(isOpen, "Attempting to reopen() an unopened file"); 635 636 auto namez = (name == null ? _name : name).tempCString!FSChar(); 637 auto modez = stdioOpenmode.tempCString!FSChar(); 638 639 FILE* fd = _p.handle; 640 version (Windows) 641 fd = _wfreopen(namez, modez, fd); 642 else 643 fd = freopen(namez, modez, fd); 644 645 errnoEnforce(fd, name 646 ? text("Cannot reopen file `", name, "' in mode `", stdioOpenmode, "'") 647 : text("Cannot reopen file in mode `", stdioOpenmode, "'")); 648 649 if (name !is null) 650 _name = name; 651 } 652 653 @system unittest // Test changing filename 654 { 655 import std.exception : assertThrown, assertNotThrown; 656 static import std.file; 657 658 auto deleteme = testFilename(); 659 std.file.write(deleteme, "foo"); 660 scope(exit) std.file.remove(deleteme); 661 auto f = File(deleteme); 662 assert(f.readln() == "foo"); 663 664 auto deleteme2 = testFilename(); 665 std.file.write(deleteme2, "bar"); 666 scope(exit) std.file.remove(deleteme2); 667 f.reopen(deleteme2); 668 assert(f.name == deleteme2); 669 assert(f.readln() == "bar"); 670 f.close(); 671 } 672 673 version (CRuntime_DigitalMars) {} else // Not implemented 674 version (CRuntime_Microsoft) {} else // Not implemented 675 @system unittest // Test changing mode 676 { 677 import std.exception : assertThrown, assertNotThrown; 678 static import std.file; 679 680 auto deleteme = testFilename(); 681 std.file.write(deleteme, "foo"); 682 scope(exit) std.file.remove(deleteme); 683 auto f = File(deleteme, "r+"); 684 assert(f.readln() == "foo"); 685 f.reopen(null, "w"); 686 f.write("bar"); 687 f.seek(0); 688 f.reopen(null, "a"); 689 f.write("baz"); 690 assert(f.name == deleteme); 691 f.close(); 692 assert(std.file.readText(deleteme) == "barbaz"); 693 } 694 695 /** 696 Detaches from the current file (throwing on failure), and then runs a command 697 by calling the C standard library function $(HTTP 698 opengroup.org/onlinepubs/007908799/xsh/_popen.html, _popen). 699 700 Throws: `ErrnoException` in case of error. 701 */ 702 version (Posix) void popen(string command, scope const(char)[] stdioOpenmode = "r") @safe 703 { 704 resetFile(command, stdioOpenmode ,true); 705 } 706 707 /** 708 First calls `detach` (throwing on failure), then attempts to 709 associate the given file descriptor with the `File`, and sets the file's name to `null`. 710 711 The mode must be compatible with the mode of the file descriptor. 712 713 Throws: `ErrnoException` in case of error. 714 Params: 715 fd = File descriptor to associate with this `File`. 716 stdioOpenmode = Mode to associate with this File. The mode has the same semantics 717 semantics as in the C standard library 718 $(HTTP cplusplus.com/reference/cstdio/fopen/, fdopen) function, and must be compatible with `fd`. 719 */ 720 void fdopen(int fd, scope const(char)[] stdioOpenmode = "rb") @safe 721 { 722 fdopen(fd, stdioOpenmode, null); 723 } 724 725 package void fdopen(int fd, scope const(char)[] stdioOpenmode, string name) @trusted 726 { 727 import std.exception : errnoEnforce; 728 import std.internal.cstring : tempCString; 729 730 auto modez = stdioOpenmode.tempCString(); 731 detach(); 732 733 version (DIGITAL_MARS_STDIO) 734 { 735 // This is a re-implementation of DMC's fdopen, but without the 736 // mucking with the file descriptor. POSIX standard requires the 737 // new fdopen'd file to retain the given file descriptor's 738 // position. 739 auto fp = fopen("NUL", modez); 740 errnoEnforce(fp, "Cannot open placeholder NUL stream"); 741 FLOCK(fp); 742 auto iob = cast(_iobuf*) fp; 743 .close(iob._file); 744 iob._file = fd; 745 iob._flag &= ~_IOTRAN; 746 FUNLOCK(fp); 747 } 748 else 749 { 750 version (Windows) // MSVCRT 751 auto fp = _fdopen(fd, modez); 752 else version (Posix) 753 { 754 import core.sys.posix.stdio : fdopen; 755 auto fp = fdopen(fd, modez); 756 } 757 errnoEnforce(fp); 758 } 759 this = File(fp, name); 760 } 761 762 // Declare a dummy HANDLE to allow generating documentation 763 // for Windows-only methods. 764 version (StdDdoc) { version (Windows) {} else alias HANDLE = int; } 765 766 /** 767 First calls `detach` (throwing on failure), and then attempts to 768 associate the given Windows `HANDLE` with the `File`. The mode must 769 be compatible with the access attributes of the handle. Windows only. 770 771 Throws: `ErrnoException` in case of error. 772 */ 773 version (StdDdoc) 774 void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode); 775 776 version (Windows) 777 void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode) 778 { 779 import core.stdc.stdint : intptr_t; 780 import std.exception : errnoEnforce; 781 import std.format : format; 782 783 // Create file descriptors from the handles 784 version (DIGITAL_MARS_STDIO) 785 auto fd = _handleToFD(handle, FHND_DEVICE); 786 else // MSVCRT 787 { 788 int mode; 789 modeLoop: 790 foreach (c; stdioOpenmode) 791 switch (c) 792 { 793 case 'r': mode |= _O_RDONLY; break; 794 case '+': mode &=~_O_RDONLY; break; 795 case 'a': mode |= _O_APPEND; break; 796 case 'b': mode |= _O_BINARY; break; 797 case 't': mode |= _O_TEXT; break; 798 case ',': break modeLoop; 799 default: break; 800 } 801 802 auto fd = _open_osfhandle(cast(intptr_t) handle, mode); 803 } 804 805 errnoEnforce(fd >= 0, "Cannot open Windows HANDLE"); 806 fdopen(fd, stdioOpenmode, "HANDLE(%s)".format(handle)); 807 } 808 809 810 /** Returns `true` if the file is opened. */ 811 @property bool isOpen() const @safe pure nothrow 812 { 813 return _p !is null && _p.handle; 814 } 815 816 /** 817 Returns `true` if the file is at end (see $(HTTP 818 cplusplus.com/reference/clibrary/cstdio/feof.html, feof)). 819 820 Throws: `Exception` if the file is not opened. 821 */ 822 @property bool eof() const @trusted pure 823 { 824 import std.exception : enforce; 825 826 enforce(_p && _p.handle, "Calling eof() against an unopened file."); 827 return .feof(cast(FILE*) _p.handle) != 0; 828 } 829 830 /** 831 Returns the name last used to initialize this `File`, if any. 832 833 Some functions that create or initialize the `File` set the name field to `null`. 834 Examples include $(LREF tmpfile), $(LREF wrapFile), and $(LREF fdopen). See the 835 documentation of those functions for details. 836 837 Returns: The name last used to initialize this this file, or `null` otherwise. 838 */ 839 @property string name() const @safe pure nothrow 840 { 841 return _name; 842 } 843 844 /** 845 If the file is not opened, returns `true`. Otherwise, returns 846 $(HTTP cplusplus.com/reference/clibrary/cstdio/ferror.html, ferror) for 847 the file handle. 848 */ 849 @property bool error() const @trusted pure nothrow 850 { 851 return !isOpen || .ferror(cast(FILE*) _p.handle); 852 } 853 854 @safe unittest 855 { 856 // https://issues.dlang.org/show_bug.cgi?id=12349 857 static import std.file; 858 auto deleteme = testFilename(); 859 auto f = File(deleteme, "w"); 860 scope(exit) std.file.remove(deleteme); 861 862 f.close(); 863 assert(f.error); 864 } 865 866 /** 867 Detaches from the underlying file. If the sole owner, calls `close`. 868 869 Throws: `ErrnoException` on failure if closing the file. 870 */ 871 void detach() @trusted 872 { 873 import core.stdc.stdlib : free; 874 875 if (!_p) return; 876 scope(exit) _p = null; 877 878 if (atomicOp!"-="(_p.refs, 1) == 0) 879 { 880 scope(exit) free(_p); 881 closeHandles(); 882 } 883 } 884 885 @safe unittest 886 { 887 static import std.file; 888 889 auto deleteme = testFilename(); 890 scope(exit) std.file.remove(deleteme); 891 auto f = File(deleteme, "w"); 892 { 893 auto f2 = f; 894 f2.detach(); 895 } 896 assert(f._p.refs == 1); 897 f.close(); 898 } 899 900 /** 901 If the file was unopened, succeeds vacuously. Otherwise closes the 902 file (by calling $(HTTP 903 cplusplus.com/reference/clibrary/cstdio/fclose.html, fclose)), 904 throwing on error. Even if an exception is thrown, afterwards the $(D 905 File) object is empty. This is different from `detach` in that it 906 always closes the file; consequently, all other `File` objects 907 referring to the same handle will see a closed file henceforth. 908 909 Throws: `ErrnoException` on error. 910 */ 911 void close() @trusted 912 { 913 import core.stdc.stdlib : free; 914 import std.exception : errnoEnforce; 915 916 if (!_p) return; // succeed vacuously 917 scope(exit) 918 { 919 if (atomicOp!"-="(_p.refs, 1) == 0) 920 free(_p); 921 _p = null; // start a new life 922 } 923 if (!_p.handle) return; // Impl is closed by another File 924 925 scope(exit) _p.handle = null; // nullify the handle anyway 926 closeHandles(); 927 } 928 929 /** 930 If the file is not opened, succeeds vacuously. Otherwise, returns 931 $(HTTP cplusplus.com/reference/clibrary/cstdio/_clearerr.html, 932 _clearerr) for the file handle. 933 */ 934 void clearerr() @safe pure nothrow 935 { 936 _p is null || _p.handle is null || 937 .clearerr(_p.handle); 938 } 939 940 /** 941 Flushes the C `FILE` buffers. 942 943 Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_fflush.html, _fflush) 944 for the file handle. 945 946 Throws: `Exception` if the file is not opened or if the call to `fflush` fails. 947 */ 948 void flush() @trusted 949 { 950 import std.exception : enforce, errnoEnforce; 951 952 enforce(isOpen, "Attempting to flush() in an unopened file"); 953 errnoEnforce(.fflush(_p.handle) == 0); 954 } 955 956 @safe unittest 957 { 958 // https://issues.dlang.org/show_bug.cgi?id=12349 959 import std.exception : assertThrown; 960 static import std.file; 961 962 auto deleteme = testFilename(); 963 auto f = File(deleteme, "w"); 964 scope(exit) std.file.remove(deleteme); 965 966 f.close(); 967 assertThrown(f.flush()); 968 } 969 970 /** 971 Forces any data buffered by the OS to be written to disk. 972 Call $(LREF flush) before calling this function to flush the C `FILE` buffers first. 973 974 This function calls 975 $(HTTP msdn.microsoft.com/en-us/library/windows/desktop/aa364439%28v=vs.85%29.aspx, 976 `FlushFileBuffers`) on Windows and 977 $(HTTP pubs.opengroup.org/onlinepubs/7908799/xsh/fsync.html, 978 `fsync`) on POSIX for the file handle. 979 980 Throws: `Exception` if the file is not opened or if the OS call fails. 981 */ 982 void sync() @trusted 983 { 984 import std.exception : enforce; 985 986 enforce(isOpen, "Attempting to sync() an unopened file"); 987 988 version (Windows) 989 { 990 import core.sys.windows.winbase : FlushFileBuffers; 991 wenforce(FlushFileBuffers(windowsHandle), "FlushFileBuffers failed"); 992 } 993 else 994 { 995 import core.sys.posix.unistd : fsync; 996 import std.exception : errnoEnforce; 997 errnoEnforce(fsync(fileno) == 0, "fsync failed"); 998 } 999 } 1000 1001 /** 1002 Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fread.html, fread) for the 1003 file handle. The number of items to read and the size of 1004 each item is inferred from the size and type of the input array, respectively. 1005 1006 Returns: The slice of `buffer` containing the data that was actually read. 1007 This will be shorter than `buffer` if EOF was reached before the buffer 1008 could be filled. 1009 1010 Throws: `Exception` if `buffer` is empty. 1011 `ErrnoException` if the file is not opened or the call to `fread` fails. 1012 1013 `rawRead` always reads in binary mode on Windows. 1014 */ 1015 T[] rawRead(T)(T[] buffer) 1016 { 1017 import std.exception : errnoEnforce; 1018 1019 if (!buffer.length) 1020 throw new Exception("rawRead must take a non-empty buffer"); 1021 version (Windows) 1022 { 1023 immutable fd = ._fileno(_p.handle); 1024 immutable mode = ._setmode(fd, _O_BINARY); 1025 scope(exit) ._setmode(fd, mode); 1026 version (DIGITAL_MARS_STDIO) 1027 { 1028 import core.atomic : atomicOp; 1029 1030 // https://issues.dlang.org/show_bug.cgi?id=4243 1031 immutable info = __fhnd_info[fd]; 1032 atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); 1033 scope(exit) __fhnd_info[fd] = info; 1034 } 1035 } 1036 immutable freadResult = trustedFread(_p.handle, buffer); 1037 assert(freadResult <= buffer.length); // fread return guarantee 1038 if (freadResult != buffer.length) // error or eof 1039 { 1040 errnoEnforce(!error); 1041 return buffer[0 .. freadResult]; 1042 } 1043 return buffer; 1044 } 1045 1046 /// 1047 @system unittest 1048 { 1049 static import std.file; 1050 1051 auto testFile = std.file.deleteme(); 1052 std.file.write(testFile, "\r\n\n\r\n"); 1053 scope(exit) std.file.remove(testFile); 1054 1055 auto f = File(testFile, "r"); 1056 auto buf = f.rawRead(new char[5]); 1057 f.close(); 1058 assert(buf == "\r\n\n\r\n"); 1059 } 1060 1061 /** 1062 Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fwrite.html, fwrite) for the file 1063 handle. The number of items to write and the size of each 1064 item is inferred from the size and type of the input array, respectively. An 1065 error is thrown if the buffer could not be written in its entirety. 1066 1067 `rawWrite` always writes in binary mode on Windows. 1068 1069 Throws: `ErrnoException` if the file is not opened or if the call to `fwrite` fails. 1070 */ 1071 void rawWrite(T)(in T[] buffer) 1072 { 1073 import std.conv : text; 1074 import std.exception : errnoEnforce; 1075 1076 version (Windows) 1077 { 1078 immutable fd = ._fileno(_p.handle); 1079 immutable oldMode = ._setmode(fd, _O_BINARY); 1080 1081 if (oldMode != _O_BINARY) 1082 { 1083 // need to flush the data that was written with the original mode 1084 ._setmode(fd, oldMode); 1085 flush(); // before changing translation mode ._setmode(fd, _O_BINARY); 1086 .setmode(fd, _O_BINARY); 1087 } 1088 1089 version (DIGITAL_MARS_STDIO) 1090 { 1091 import core.atomic : atomicOp; 1092 1093 // https://issues.dlang.org/show_bug.cgi?id=4243 1094 immutable info = __fhnd_info[fd]; 1095 atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); 1096 scope (exit) __fhnd_info[fd] = info; 1097 } 1098 1099 scope (exit) 1100 { 1101 if (oldMode != _O_BINARY) 1102 { 1103 flush(); 1104 ._setmode(fd, oldMode); 1105 } 1106 } 1107 } 1108 1109 auto result = trustedFwrite(_p.handle, buffer); 1110 if (result == result.max) result = 0; 1111 errnoEnforce(result == buffer.length, 1112 text("Wrote ", result, " instead of ", buffer.length, 1113 " objects of type ", T.stringof, " to file `", 1114 _name, "'")); 1115 } 1116 1117 /// 1118 @system unittest 1119 { 1120 static import std.file; 1121 1122 auto testFile = std.file.deleteme(); 1123 auto f = File(testFile, "w"); 1124 scope(exit) std.file.remove(testFile); 1125 1126 f.rawWrite("\r\n\n\r\n"); 1127 f.close(); 1128 assert(std.file.read(testFile) == "\r\n\n\r\n"); 1129 } 1130 1131 /** 1132 Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fseek.html, fseek) 1133 for the file handle to move its position indicator. 1134 1135 Params: 1136 offset = Binary files: Number of bytes to offset from origin.$(BR) 1137 Text files: Either zero, or a value returned by $(LREF tell). 1138 origin = Binary files: Position used as reference for the offset, must be 1139 one of $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio), 1140 $(REF_ALTTEXT SEEK_CUR, SEEK_CUR, core,stdc,stdio) or 1141 $(REF_ALTTEXT SEEK_END, SEEK_END, core,stdc,stdio).$(BR) 1142 Text files: Shall necessarily be 1143 $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio). 1144 1145 Throws: `Exception` if the file is not opened. 1146 `ErrnoException` if the call to `fseek` fails. 1147 */ 1148 void seek(long offset, int origin = SEEK_SET) @trusted 1149 { 1150 import std.conv : to, text; 1151 import std.exception : enforce, errnoEnforce; 1152 1153 // Some libc sanitize the whence input (e.g. glibc), but some don't, 1154 // e.g. Microsoft runtime crashes on an invalid origin, 1155 // and Musl additionally accept SEEK_DATA & SEEK_HOLE (Linux extension). 1156 // To provide a consistent behavior cross platform, we use the glibc check 1157 // See also https://issues.dlang.org/show_bug.cgi?id=19797 1158 enforce(origin == SEEK_SET || origin == SEEK_CUR || origin == SEEK_END, 1159 "Invalid `origin` argument passed to `seek`, must be one of: SEEK_SET, SEEK_CUR, SEEK_END"); 1160 1161 enforce(isOpen, "Attempting to seek() in an unopened file"); 1162 version (Windows) 1163 { 1164 version (CRuntime_Microsoft) 1165 { 1166 alias fseekFun = _fseeki64; 1167 alias off_t = long; 1168 } 1169 else 1170 { 1171 alias fseekFun = fseek; 1172 alias off_t = int; 1173 } 1174 } 1175 else version (Posix) 1176 { 1177 import core.sys.posix.stdio : fseeko, off_t; 1178 alias fseekFun = fseeko; 1179 } 1180 errnoEnforce(fseekFun(_p.handle, to!off_t(offset), origin) == 0, 1181 "Could not seek in file `"~_name~"'"); 1182 } 1183 1184 @system unittest 1185 { 1186 import std.conv : text; 1187 static import std.file; 1188 import std.exception; 1189 1190 auto deleteme = testFilename(); 1191 auto f = File(deleteme, "w+"); 1192 scope(exit) { f.close(); std.file.remove(deleteme); } 1193 f.rawWrite("abcdefghijklmnopqrstuvwxyz"); 1194 f.seek(7); 1195 assert(f.readln() == "hijklmnopqrstuvwxyz"); 1196 1197 version (CRuntime_DigitalMars) 1198 auto bigOffset = int.max - 100; 1199 else 1200 version (CRuntime_Bionic) 1201 auto bigOffset = int.max - 100; 1202 else 1203 auto bigOffset = cast(ulong) int.max + 100; 1204 f.seek(bigOffset); 1205 assert(f.tell == bigOffset, text(f.tell)); 1206 // Uncomment the tests below only if you want to wait for 1207 // a long time 1208 // f.rawWrite("abcdefghijklmnopqrstuvwxyz"); 1209 // f.seek(-3, SEEK_END); 1210 // assert(f.readln() == "xyz"); 1211 1212 assertThrown(f.seek(0, ushort.max)); 1213 } 1214 1215 /** 1216 Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/ftell.html, ftell) for the 1217 managed file handle. 1218 1219 Throws: `Exception` if the file is not opened. 1220 `ErrnoException` if the call to `ftell` fails. 1221 */ 1222 @property ulong tell() const @trusted 1223 { 1224 import std.exception : enforce, errnoEnforce; 1225 1226 enforce(isOpen, "Attempting to tell() in an unopened file"); 1227 version (Windows) 1228 { 1229 version (CRuntime_Microsoft) 1230 immutable result = _ftelli64(cast(FILE*) _p.handle); 1231 else 1232 immutable result = ftell(cast(FILE*) _p.handle); 1233 } 1234 else version (Posix) 1235 { 1236 import core.sys.posix.stdio : ftello; 1237 immutable result = ftello(cast(FILE*) _p.handle); 1238 } 1239 errnoEnforce(result != -1, 1240 "Query ftell() failed for file `"~_name~"'"); 1241 return result; 1242 } 1243 1244 /// 1245 @system unittest 1246 { 1247 import std.conv : text; 1248 static import std.file; 1249 1250 auto testFile = std.file.deleteme(); 1251 std.file.write(testFile, "abcdefghijklmnopqrstuvwqxyz"); 1252 scope(exit) { std.file.remove(testFile); } 1253 1254 auto f = File(testFile); 1255 auto a = new ubyte[4]; 1256 f.rawRead(a); 1257 assert(f.tell == 4, text(f.tell)); 1258 } 1259 1260 /** 1261 Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_rewind.html, _rewind) 1262 for the file handle. 1263 1264 Throws: `Exception` if the file is not opened. 1265 */ 1266 void rewind() @safe 1267 { 1268 import std.exception : enforce; 1269 1270 enforce(isOpen, "Attempting to rewind() an unopened file"); 1271 .rewind(_p.handle); 1272 } 1273 1274 /** 1275 Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, _setvbuf) for 1276 the file handle. 1277 1278 Throws: `Exception` if the file is not opened. 1279 `ErrnoException` if the call to `setvbuf` fails. 1280 */ 1281 void setvbuf(size_t size, int mode = _IOFBF) @trusted 1282 { 1283 import std.exception : enforce, errnoEnforce; 1284 1285 enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); 1286 errnoEnforce(.setvbuf(_p.handle, null, mode, size) == 0, 1287 "Could not set buffering for file `"~_name~"'"); 1288 } 1289 1290 /** 1291 Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, 1292 _setvbuf) for the file handle. 1293 1294 Throws: `Exception` if the file is not opened. 1295 `ErrnoException` if the call to `setvbuf` fails. 1296 */ 1297 void setvbuf(void[] buf, int mode = _IOFBF) @trusted 1298 { 1299 import std.exception : enforce, errnoEnforce; 1300 1301 enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); 1302 errnoEnforce(.setvbuf(_p.handle, 1303 cast(char*) buf.ptr, mode, buf.length) == 0, 1304 "Could not set buffering for file `"~_name~"'"); 1305 } 1306 1307 1308 version (Windows) 1309 { 1310 import core.sys.windows.winbase : OVERLAPPED; 1311 import core.sys.windows.winnt : BOOL, ULARGE_INTEGER; 1312 1313 private BOOL lockImpl(alias F, Flags...)(ulong start, ulong length, 1314 Flags flags) 1315 { 1316 if (!start && !length) 1317 length = ulong.max; 1318 ULARGE_INTEGER liStart = void, liLength = void; 1319 liStart.QuadPart = start; 1320 liLength.QuadPart = length; 1321 OVERLAPPED overlapped; 1322 overlapped.Offset = liStart.LowPart; 1323 overlapped.OffsetHigh = liStart.HighPart; 1324 overlapped.hEvent = null; 1325 return F(windowsHandle, flags, 0, liLength.LowPart, 1326 liLength.HighPart, &overlapped); 1327 } 1328 1329 private static T wenforce(T)(T cond, string str) 1330 { 1331 import core.sys.windows.winbase : GetLastError; 1332 import std.windows.syserror : sysErrorString; 1333 1334 if (cond) return cond; 1335 throw new Exception(str ~ ": " ~ sysErrorString(GetLastError())); 1336 } 1337 } 1338 version (Posix) 1339 { 1340 private int lockImpl(int operation, short l_type, 1341 ulong start, ulong length) 1342 { 1343 import core.sys.posix.fcntl : fcntl, flock, off_t; 1344 import core.sys.posix.unistd : getpid; 1345 import std.conv : to; 1346 1347 flock fl = void; 1348 fl.l_type = l_type; 1349 fl.l_whence = SEEK_SET; 1350 fl.l_start = to!off_t(start); 1351 fl.l_len = to!off_t(length); 1352 fl.l_pid = getpid(); 1353 return fcntl(fileno, operation, &fl); 1354 } 1355 } 1356 1357 /** 1358 Locks the specified file segment. If the file segment is already locked 1359 by another process, waits until the existing lock is released. 1360 If both `start` and `length` are zero, the entire file is locked. 1361 1362 Locks created using `lock` and `tryLock` have the following properties: 1363 $(UL 1364 $(LI All locks are automatically released when the process terminates.) 1365 $(LI Locks are not inherited by child processes.) 1366 $(LI Closing a file will release all locks associated with the file. On POSIX, 1367 even locks acquired via a different `File` will be released as well.) 1368 $(LI Not all NFS implementations correctly implement file locking.) 1369 ) 1370 */ 1371 void lock(LockType lockType = LockType.readWrite, 1372 ulong start = 0, ulong length = 0) 1373 { 1374 import std.exception : enforce; 1375 1376 enforce(isOpen, "Attempting to call lock() on an unopened file"); 1377 version (Posix) 1378 { 1379 import core.sys.posix.fcntl : F_RDLCK, F_SETLKW, F_WRLCK; 1380 import std.exception : errnoEnforce; 1381 immutable short type = lockType == LockType.readWrite 1382 ? F_WRLCK : F_RDLCK; 1383 errnoEnforce(lockImpl(F_SETLKW, type, start, length) != -1, 1384 "Could not set lock for file `"~_name~"'"); 1385 } 1386 else 1387 version (Windows) 1388 { 1389 import core.sys.windows.winbase : LockFileEx, LOCKFILE_EXCLUSIVE_LOCK; 1390 immutable type = lockType == LockType.readWrite ? 1391 LOCKFILE_EXCLUSIVE_LOCK : 0; 1392 wenforce(lockImpl!LockFileEx(start, length, type), 1393 "Could not set lock for file `"~_name~"'"); 1394 } 1395 else 1396 static assert(false); 1397 } 1398 1399 /** 1400 Attempts to lock the specified file segment. 1401 If both `start` and `length` are zero, the entire file is locked. 1402 Returns: `true` if the lock was successful, and `false` if the 1403 specified file segment was already locked. 1404 */ 1405 bool tryLock(LockType lockType = LockType.readWrite, 1406 ulong start = 0, ulong length = 0) 1407 { 1408 import std.exception : enforce; 1409 1410 enforce(isOpen, "Attempting to call tryLock() on an unopened file"); 1411 version (Posix) 1412 { 1413 import core.stdc.errno : EACCES, EAGAIN, errno; 1414 import core.sys.posix.fcntl : F_RDLCK, F_SETLK, F_WRLCK; 1415 import std.exception : errnoEnforce; 1416 immutable short type = lockType == LockType.readWrite 1417 ? F_WRLCK : F_RDLCK; 1418 immutable res = lockImpl(F_SETLK, type, start, length); 1419 if (res == -1 && (errno == EACCES || errno == EAGAIN)) 1420 return false; 1421 errnoEnforce(res != -1, "Could not set lock for file `"~_name~"'"); 1422 return true; 1423 } 1424 else 1425 version (Windows) 1426 { 1427 import core.sys.windows.winbase : GetLastError, LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, 1428 LOCKFILE_FAIL_IMMEDIATELY; 1429 import core.sys.windows.winerror : ERROR_IO_PENDING, ERROR_LOCK_VIOLATION; 1430 immutable type = lockType == LockType.readWrite 1431 ? LOCKFILE_EXCLUSIVE_LOCK : 0; 1432 immutable res = lockImpl!LockFileEx(start, length, 1433 type | LOCKFILE_FAIL_IMMEDIATELY); 1434 if (!res && (GetLastError() == ERROR_IO_PENDING 1435 || GetLastError() == ERROR_LOCK_VIOLATION)) 1436 return false; 1437 wenforce(res, "Could not set lock for file `"~_name~"'"); 1438 return true; 1439 } 1440 else 1441 static assert(false); 1442 } 1443 1444 /** 1445 Removes the lock over the specified file segment. 1446 */ 1447 void unlock(ulong start = 0, ulong length = 0) 1448 { 1449 import std.exception : enforce; 1450 1451 enforce(isOpen, "Attempting to call unlock() on an unopened file"); 1452 version (Posix) 1453 { 1454 import core.sys.posix.fcntl : F_SETLK, F_UNLCK; 1455 import std.exception : errnoEnforce; 1456 errnoEnforce(lockImpl(F_SETLK, F_UNLCK, start, length) != -1, 1457 "Could not remove lock for file `"~_name~"'"); 1458 } 1459 else 1460 version (Windows) 1461 { 1462 import core.sys.windows.winbase : UnlockFileEx; 1463 wenforce(lockImpl!UnlockFileEx(start, length), 1464 "Could not remove lock for file `"~_name~"'"); 1465 } 1466 else 1467 static assert(false); 1468 } 1469 1470 version (Windows) 1471 @system unittest 1472 { 1473 static import std.file; 1474 auto deleteme = testFilename(); 1475 scope(exit) std.file.remove(deleteme); 1476 auto f = File(deleteme, "wb"); 1477 assert(f.tryLock()); 1478 auto g = File(deleteme, "wb"); 1479 assert(!g.tryLock()); 1480 assert(!g.tryLock(LockType.read)); 1481 f.unlock(); 1482 f.lock(LockType.read); 1483 assert(!g.tryLock()); 1484 assert(g.tryLock(LockType.read)); 1485 f.unlock(); 1486 g.unlock(); 1487 } 1488 1489 version (Posix) 1490 static if (__traits(compiles, { import std.process : spawnProcess; })) 1491 @system unittest 1492 { 1493 static import std.file; 1494 auto deleteme = testFilename(); 1495 scope(exit) std.file.remove(deleteme); 1496 1497 // Since locks are per-process, we cannot test lock failures within 1498 // the same process. fork() is used to create a second process. 1499 static void runForked(void delegate() code) 1500 { 1501 import core.stdc.stdlib : exit; 1502 import core.sys.posix.sys.wait : waitpid; 1503 import core.sys.posix.unistd : fork; 1504 int child, status; 1505 if ((child = fork()) == 0) 1506 { 1507 code(); 1508 exit(0); 1509 } 1510 else 1511 { 1512 assert(waitpid(child, &status, 0) != -1); 1513 assert(status == 0, "Fork crashed"); 1514 } 1515 } 1516 1517 auto f = File(deleteme, "w+b"); 1518 1519 runForked 1520 ({ 1521 auto g = File(deleteme, "a+b"); 1522 assert(g.tryLock()); 1523 g.unlock(); 1524 assert(g.tryLock(LockType.read)); 1525 }); 1526 1527 assert(f.tryLock()); 1528 runForked 1529 ({ 1530 auto g = File(deleteme, "a+b"); 1531 assert(!g.tryLock()); 1532 assert(!g.tryLock(LockType.read)); 1533 }); 1534 f.unlock(); 1535 1536 f.lock(LockType.read); 1537 runForked 1538 ({ 1539 auto g = File(deleteme, "a+b"); 1540 assert(!g.tryLock()); 1541 assert(g.tryLock(LockType.read)); 1542 g.unlock(); 1543 }); 1544 f.unlock(); 1545 } 1546 1547 1548 /** 1549 Writes its arguments in text format to the file. 1550 1551 Throws: `Exception` if the file is not opened. 1552 `ErrnoException` on an error writing to the file. 1553 */ 1554 void write(S...)(S args) 1555 { 1556 import std.traits : isBoolean, isIntegral, isAggregateType; 1557 auto w = lockingTextWriter(); 1558 foreach (arg; args) 1559 { 1560 alias A = typeof(arg); 1561 static if (isAggregateType!A || is(A == enum)) 1562 { 1563 import std.format : formattedWrite; 1564 1565 formattedWrite(w, "%s", arg); 1566 } 1567 else static if (isSomeString!A) 1568 { 1569 put(w, arg); 1570 } 1571 else static if (isIntegral!A) 1572 { 1573 import std.conv : toTextRange; 1574 1575 toTextRange(arg, w); 1576 } 1577 else static if (isBoolean!A) 1578 { 1579 put(w, arg ? "true" : "false"); 1580 } 1581 else static if (isSomeChar!A) 1582 { 1583 put(w, arg); 1584 } 1585 else 1586 { 1587 import std.format : formattedWrite; 1588 1589 // Most general case 1590 formattedWrite(w, "%s", arg); 1591 } 1592 } 1593 } 1594 1595 /** 1596 Writes its arguments in text format to the file, followed by a newline. 1597 1598 Throws: `Exception` if the file is not opened. 1599 `ErrnoException` on an error writing to the file. 1600 */ 1601 void writeln(S...)(S args) 1602 { 1603 write(args, '\n'); 1604 } 1605 1606 /** 1607 Writes its arguments in text format to the file, according to the 1608 format string fmt. 1609 1610 Params: 1611 fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). 1612 When passed as a compile-time argument, the string will be statically checked 1613 against the argument types passed. 1614 args = Items to write. 1615 1616 Throws: `Exception` if the file is not opened. 1617 `ErrnoException` on an error writing to the file. 1618 */ 1619 void writef(alias fmt, A...)(A args) 1620 if (isSomeString!(typeof(fmt))) 1621 { 1622 import std.format : checkFormatException; 1623 1624 alias e = checkFormatException!(fmt, A); 1625 static assert(!e, e.msg); 1626 return this.writef(fmt, args); 1627 } 1628 1629 /// ditto 1630 void writef(Char, A...)(in Char[] fmt, A args) 1631 { 1632 import std.format : formattedWrite; 1633 1634 formattedWrite(lockingTextWriter(), fmt, args); 1635 } 1636 1637 /// Equivalent to `file.writef(fmt, args, '\n')`. 1638 void writefln(alias fmt, A...)(A args) 1639 if (isSomeString!(typeof(fmt))) 1640 { 1641 import std.format : checkFormatException; 1642 1643 alias e = checkFormatException!(fmt, A); 1644 static assert(!e, e.msg); 1645 return this.writefln(fmt, args); 1646 } 1647 1648 /// ditto 1649 void writefln(Char, A...)(in Char[] fmt, A args) 1650 { 1651 import std.format : formattedWrite; 1652 1653 auto w = lockingTextWriter(); 1654 formattedWrite(w, fmt, args); 1655 w.put('\n'); 1656 } 1657 1658 /** 1659 Read line from the file handle and return it as a specified type. 1660 1661 This version manages its own read buffer, which means one memory allocation per call. If you are not 1662 retaining a reference to the read data, consider the `File.readln(buf)` version, which may offer 1663 better performance as it can reuse its read buffer. 1664 1665 Params: 1666 S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. 1667 terminator = Line terminator (by default, `'\n'`). 1668 1669 Note: 1670 String terminators are not supported due to ambiguity with readln(buf) below. 1671 1672 Returns: 1673 The line that was read, including the line terminator character. 1674 1675 Throws: 1676 `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. 1677 1678 Example: 1679 --- 1680 // Reads `stdin` and writes it to `stdout`. 1681 import std.stdio; 1682 1683 void main() 1684 { 1685 string line; 1686 while ((line = stdin.readln()) !is null) 1687 write(line); 1688 } 1689 --- 1690 */ 1691 S readln(S = string)(dchar terminator = '\n') 1692 if (isSomeString!S) 1693 { 1694 Unqual!(ElementEncodingType!S)[] buf; 1695 readln(buf, terminator); 1696 return cast(S) buf; 1697 } 1698 1699 @system unittest 1700 { 1701 import std.algorithm.comparison : equal; 1702 static import std.file; 1703 import std.meta : AliasSeq; 1704 1705 auto deleteme = testFilename(); 1706 std.file.write(deleteme, "hello\nworld\n"); 1707 scope(exit) std.file.remove(deleteme); 1708 static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) 1709 {{ 1710 auto witness = [ "hello\n", "world\n" ]; 1711 auto f = File(deleteme); 1712 uint i = 0; 1713 String buf; 1714 while ((buf = f.readln!String()).length) 1715 { 1716 assert(i < witness.length); 1717 assert(equal(buf, witness[i++])); 1718 } 1719 assert(i == witness.length); 1720 }} 1721 } 1722 1723 @system unittest 1724 { 1725 static import std.file; 1726 import std.typecons : Tuple; 1727 1728 auto deleteme = testFilename(); 1729 std.file.write(deleteme, "cześć \U0002000D"); 1730 scope(exit) std.file.remove(deleteme); 1731 uint[] lengths = [12,8,7]; 1732 static foreach (uint i, C; Tuple!(char, wchar, dchar).Types) 1733 {{ 1734 immutable(C)[] witness = "cześć \U0002000D"; 1735 auto buf = File(deleteme).readln!(immutable(C)[])(); 1736 assert(buf.length == lengths[i]); 1737 assert(buf == witness); 1738 }} 1739 } 1740 1741 /** 1742 Read line from the file handle and write it to `buf[]`, including 1743 terminating character. 1744 1745 This can be faster than $(D line = File.readln()) because you can reuse 1746 the buffer for each call. Note that reusing the buffer means that you 1747 must copy the previous contents if you wish to retain them. 1748 1749 Params: 1750 buf = Buffer used to store the resulting line data. buf is 1751 enlarged if necessary, then set to the slice exactly containing the line. 1752 terminator = Line terminator (by default, `'\n'`). Use 1753 $(REF newline, std,ascii) for portability (unless the file was opened in 1754 text mode). 1755 1756 Returns: 1757 0 for end of file, otherwise number of characters read. 1758 The return value will always be equal to `buf.length`. 1759 1760 Throws: `StdioException` on I/O error, or `UnicodeException` on Unicode 1761 conversion error. 1762 1763 Example: 1764 --- 1765 // Read lines from `stdin` into a string 1766 // Ignore lines starting with '#' 1767 // Write the string to `stdout` 1768 import std.stdio; 1769 1770 void main() 1771 { 1772 string output; 1773 char[] buf; 1774 1775 while (stdin.readln(buf)) 1776 { 1777 if (buf[0] == '#') 1778 continue; 1779 1780 output ~= buf; 1781 } 1782 1783 write(output); 1784 } 1785 --- 1786 1787 This method can be more efficient than the one in the previous example 1788 because `stdin.readln(buf)` reuses (if possible) memory allocated 1789 for `buf`, whereas $(D line = stdin.readln()) makes a new memory allocation 1790 for every line. 1791 1792 For even better performance you can help `readln` by passing in a 1793 large buffer to avoid memory reallocations. This can be done by reusing the 1794 largest buffer returned by `readln`: 1795 1796 Example: 1797 --- 1798 // Read lines from `stdin` and count words 1799 import std.array, std.stdio; 1800 1801 void main() 1802 { 1803 char[] buf; 1804 size_t words = 0; 1805 1806 while (!stdin.eof) 1807 { 1808 char[] line = buf; 1809 stdin.readln(line); 1810 if (line.length > buf.length) 1811 buf = line; 1812 1813 words += line.split.length; 1814 } 1815 1816 writeln(words); 1817 } 1818 --- 1819 This is actually what $(LREF byLine) does internally, so its usage 1820 is recommended if you want to process a complete file. 1821 */ 1822 size_t readln(C)(ref C[] buf, dchar terminator = '\n') 1823 if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) 1824 { 1825 import std.exception : enforce; 1826 1827 static if (is(C == char)) 1828 { 1829 enforce(_p && _p.handle, "Attempt to read from an unopened file."); 1830 if (_p.orientation == Orientation.unknown) 1831 { 1832 import core.stdc.wchar_ : fwide; 1833 auto w = fwide(_p.handle, 0); 1834 if (w < 0) _p.orientation = Orientation.narrow; 1835 else if (w > 0) _p.orientation = Orientation.wide; 1836 } 1837 return readlnImpl(_p.handle, buf, terminator, _p.orientation); 1838 } 1839 else 1840 { 1841 // TODO: optimize this 1842 string s = readln(terminator); 1843 buf.length = 0; 1844 if (!s.length) return 0; 1845 foreach (C c; s) 1846 { 1847 buf ~= c; 1848 } 1849 return buf.length; 1850 } 1851 } 1852 1853 @system unittest 1854 { 1855 // @system due to readln 1856 static import std.file; 1857 auto deleteme = testFilename(); 1858 std.file.write(deleteme, "123\n456789"); 1859 scope(exit) std.file.remove(deleteme); 1860 1861 auto file = File(deleteme); 1862 char[] buffer = new char[10]; 1863 char[] line = buffer; 1864 file.readln(line); 1865 auto beyond = line.length; 1866 buffer[beyond] = 'a'; 1867 file.readln(line); // should not write buffer beyond line 1868 assert(buffer[beyond] == 'a'); 1869 } 1870 1871 // https://issues.dlang.org/show_bug.cgi?id=15293 1872 @system unittest 1873 { 1874 // @system due to readln 1875 static import std.file; 1876 auto deleteme = testFilename(); 1877 std.file.write(deleteme, "a\n\naa"); 1878 scope(exit) std.file.remove(deleteme); 1879 1880 auto file = File(deleteme); 1881 char[] buffer; 1882 char[] line; 1883 1884 file.readln(buffer, '\n'); 1885 1886 line = buffer; 1887 file.readln(line, '\n'); 1888 1889 line = buffer; 1890 file.readln(line, '\n'); 1891 1892 assert(line[0 .. 1].capacity == 0); 1893 } 1894 1895 /** ditto */ 1896 size_t readln(C, R)(ref C[] buf, R terminator) 1897 if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && 1898 isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) 1899 { 1900 import std.algorithm.mutation : swap; 1901 import std.algorithm.searching : endsWith; 1902 import std.range.primitives : back; 1903 1904 auto last = terminator.back; 1905 C[] buf2; 1906 swap(buf, buf2); 1907 for (;;) 1908 { 1909 if (!readln(buf2, last) || endsWith(buf2, terminator)) 1910 { 1911 if (buf.empty) 1912 { 1913 buf = buf2; 1914 } 1915 else 1916 { 1917 buf ~= buf2; 1918 } 1919 break; 1920 } 1921 buf ~= buf2; 1922 } 1923 return buf.length; 1924 } 1925 1926 @system unittest 1927 { 1928 static import std.file; 1929 import std.typecons : Tuple; 1930 1931 auto deleteme = testFilename(); 1932 std.file.write(deleteme, "hello\n\rworld\nhow\n\rare ya"); 1933 scope(exit) std.file.remove(deleteme); 1934 foreach (C; Tuple!(char, wchar, dchar).Types) 1935 { 1936 immutable(C)[][] witness = [ "hello\n\r", "world\nhow\n\r", "are ya" ]; 1937 auto f = File(deleteme); 1938 uint i = 0; 1939 C[] buf; 1940 while (f.readln(buf, "\n\r")) 1941 { 1942 assert(i < witness.length); 1943 assert(buf == witness[i++]); 1944 } 1945 assert(buf.length == 0); 1946 } 1947 } 1948 1949 /** 1950 * Reads formatted _data from the file using $(REF formattedRead, std,_format). 1951 * Params: 1952 * format = The $(REF_ALTTEXT format string, formattedWrite, std _format). 1953 * When passed as a compile-time argument, the string will be statically checked 1954 * against the argument types passed. 1955 * data = Items to be read. 1956 * Example: 1957 ---- 1958 // test.d 1959 void main() 1960 { 1961 import std.stdio; 1962 auto f = File("input"); 1963 foreach (_; 0 .. 3) 1964 { 1965 int a; 1966 f.readf!" %d"(a); 1967 writeln(++a); 1968 } 1969 } 1970 ---- 1971 $(CONSOLE 1972 % echo "1 2 3" > input 1973 % rdmd test.d 1974 2 1975 3 1976 4 1977 ) 1978 */ 1979 uint readf(alias format, Data...)(auto ref Data data) 1980 if (isSomeString!(typeof(format))) 1981 { 1982 import std.format : checkFormatException; 1983 1984 alias e = checkFormatException!(format, Data); 1985 static assert(!e, e.msg); 1986 return this.readf(format, data); 1987 } 1988 1989 /// ditto 1990 uint readf(Data...)(scope const(char)[] format, auto ref Data data) 1991 { 1992 import std.format : formattedRead; 1993 1994 assert(isOpen); 1995 auto input = LockingTextReader(this); 1996 return formattedRead(input, format, data); 1997 } 1998 1999 /// 2000 @system unittest 2001 { 2002 static import std.file; 2003 2004 auto deleteme = std.file.deleteme(); 2005 std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); 2006 scope(exit) std.file.remove(deleteme); 2007 string s; 2008 auto f = File(deleteme); 2009 f.readf!"%s\n"(s); 2010 assert(s == "hello", "["~s~"]"); 2011 f.readf("%s\n", s); 2012 assert(s == "world", "["~s~"]"); 2013 2014 bool b1, b2; 2015 f.readf("%s\n%s\n", b1, b2); 2016 assert(b1 == true && b2 == false); 2017 } 2018 2019 // backwards compatibility with pointers 2020 @system unittest 2021 { 2022 // @system due to readf 2023 static import std.file; 2024 2025 auto deleteme = testFilename(); 2026 std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); 2027 scope(exit) std.file.remove(deleteme); 2028 string s; 2029 auto f = File(deleteme); 2030 f.readf("%s\n", &s); 2031 assert(s == "hello", "["~s~"]"); 2032 f.readf("%s\n", &s); 2033 assert(s == "world", "["~s~"]"); 2034 2035 // https://issues.dlang.org/show_bug.cgi?id=11698 2036 bool b1, b2; 2037 f.readf("%s\n%s\n", &b1, &b2); 2038 assert(b1 == true && b2 == false); 2039 } 2040 2041 // backwards compatibility (mixed) 2042 @system unittest 2043 { 2044 // @system due to readf 2045 static import std.file; 2046 2047 auto deleteme = testFilename(); 2048 std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); 2049 scope(exit) std.file.remove(deleteme); 2050 string s1, s2; 2051 auto f = File(deleteme); 2052 f.readf("%s\n%s\n", s1, &s2); 2053 assert(s1 == "hello"); 2054 assert(s2 == "world"); 2055 2056 // https://issues.dlang.org/show_bug.cgi?id=11698 2057 bool b1, b2; 2058 f.readf("%s\n%s\n", &b1, b2); 2059 assert(b1 == true && b2 == false); 2060 } 2061 2062 // Nice error of std.stdio.readf with newlines 2063 // https://issues.dlang.org/show_bug.cgi?id=12260 2064 @system unittest 2065 { 2066 static import std.file; 2067 2068 auto deleteme = testFilename(); 2069 std.file.write(deleteme, "1\n2"); 2070 scope(exit) std.file.remove(deleteme); 2071 int input; 2072 auto f = File(deleteme); 2073 f.readf("%s", &input); 2074 2075 import std.conv : ConvException; 2076 import std.exception : collectException; 2077 assert(collectException!ConvException(f.readf("%s", &input)).msg == 2078 "Unexpected '\\n' when converting from type LockingTextReader to type int"); 2079 } 2080 2081 /** 2082 Returns a temporary file by calling 2083 $(HTTP cplusplus.com/reference/clibrary/cstdio/_tmpfile.html, _tmpfile). 2084 Note that the created file has no $(LREF name).*/ 2085 static File tmpfile() @safe 2086 { 2087 import std.exception : errnoEnforce; 2088 2089 return File(errnoEnforce(.tmpfile(), 2090 "Could not create temporary file with tmpfile()"), 2091 null); 2092 } 2093 2094 /** 2095 Unsafe function that wraps an existing `FILE*`. The resulting $(D 2096 File) never takes the initiative in closing the file. 2097 Note that the created file has no $(LREF name)*/ 2098 /*private*/ static File wrapFile(FILE* f) @safe 2099 { 2100 import std.exception : enforce; 2101 2102 return File(enforce(f, "Could not wrap null FILE*"), 2103 null, /*uint.max / 2*/ 9999); 2104 } 2105 2106 /** 2107 Returns the `FILE*` corresponding to this object. 2108 */ 2109 FILE* getFP() @safe pure 2110 { 2111 import std.exception : enforce; 2112 2113 enforce(_p && _p.handle, 2114 "Attempting to call getFP() on an unopened file"); 2115 return _p.handle; 2116 } 2117 2118 @system unittest 2119 { 2120 static import core.stdc.stdio; 2121 assert(stdout.getFP() == core.stdc.stdio.stdout); 2122 } 2123 2124 /** 2125 Returns the file number corresponding to this object. 2126 */ 2127 @property int fileno() const @trusted 2128 { 2129 import std.exception : enforce; 2130 2131 enforce(isOpen, "Attempting to call fileno() on an unopened file"); 2132 return .fileno(cast(FILE*) _p.handle); 2133 } 2134 2135 /** 2136 Returns the underlying operating system `HANDLE` (Windows only). 2137 */ 2138 version (StdDdoc) 2139 @property HANDLE windowsHandle(); 2140 2141 version (Windows) 2142 @property HANDLE windowsHandle() 2143 { 2144 version (DIGITAL_MARS_STDIO) 2145 return _fdToHandle(fileno); 2146 else 2147 return cast(HANDLE)_get_osfhandle(fileno); 2148 } 2149 2150 2151 // Note: This was documented until 2013/08 2152 /* 2153 Range that reads one line at a time. Returned by $(LREF byLine). 2154 2155 Allows to directly use range operations on lines of a file. 2156 */ 2157 private struct ByLineImpl(Char, Terminator) 2158 { 2159 private: 2160 import std.typecons : RefCounted, RefCountedAutoInitialize; 2161 2162 /* Ref-counting stops the source range's Impl 2163 * from getting out of sync after the range is copied, e.g. 2164 * when accessing range.front, then using std.range.take, 2165 * then accessing range.front again. */ 2166 alias PImpl = RefCounted!(Impl, RefCountedAutoInitialize.no); 2167 PImpl impl; 2168 2169 static if (isScalarType!Terminator) 2170 enum defTerm = '\n'; 2171 else 2172 enum defTerm = cast(Terminator)"\n"; 2173 2174 public: 2175 this(File f, KeepTerminator kt = No.keepTerminator, 2176 Terminator terminator = defTerm) 2177 { 2178 impl = PImpl(f, kt, terminator); 2179 } 2180 2181 @property bool empty() 2182 { 2183 return impl.refCountedPayload.empty; 2184 } 2185 2186 @property Char[] front() 2187 { 2188 return impl.refCountedPayload.front; 2189 } 2190 2191 void popFront() 2192 { 2193 impl.refCountedPayload.popFront(); 2194 } 2195 2196 private: 2197 struct Impl 2198 { 2199 private: 2200 File file; 2201 Char[] line; 2202 Char[] buffer; 2203 Terminator terminator; 2204 KeepTerminator keepTerminator; 2205 bool haveLine; 2206 2207 public: 2208 this(File f, KeepTerminator kt, Terminator terminator) 2209 { 2210 file = f; 2211 this.terminator = terminator; 2212 keepTerminator = kt; 2213 } 2214 2215 // Range primitive implementations. 2216 @property bool empty() 2217 { 2218 needLine(); 2219 return line is null; 2220 } 2221 2222 @property Char[] front() 2223 { 2224 needLine(); 2225 return line; 2226 } 2227 2228 void popFront() 2229 { 2230 needLine(); 2231 haveLine = false; 2232 } 2233 2234 private: 2235 void needLine() 2236 { 2237 if (haveLine) 2238 return; 2239 import std.algorithm.searching : endsWith; 2240 assert(file.isOpen); 2241 line = buffer; 2242 file.readln(line, terminator); 2243 if (line.length > buffer.length) 2244 { 2245 buffer = line; 2246 } 2247 if (line.empty) 2248 { 2249 file.detach(); 2250 line = null; 2251 } 2252 else if (keepTerminator == No.keepTerminator 2253 && endsWith(line, terminator)) 2254 { 2255 static if (isScalarType!Terminator) 2256 enum tlen = 1; 2257 else static if (isArray!Terminator) 2258 { 2259 static assert( 2260 is(immutable ElementEncodingType!Terminator == immutable Char)); 2261 const tlen = terminator.length; 2262 } 2263 else 2264 static assert(false); 2265 line = line[0 .. line.length - tlen]; 2266 } 2267 haveLine = true; 2268 } 2269 } 2270 } 2271 2272 /** 2273 Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2274 set up to read from the file handle one line at a time. 2275 2276 The element type for the range will be `Char[]`. Range primitives 2277 may throw `StdioException` on I/O error. 2278 2279 Note: 2280 Each `front` will not persist after $(D 2281 popFront) is called, so the caller must copy its contents (e.g. by 2282 calling `to!string`) when retention is needed. If the caller needs 2283 to retain a copy of every line, use the $(LREF byLineCopy) function 2284 instead. 2285 2286 Params: 2287 Char = Character type for each line, defaulting to `char`. 2288 keepTerminator = Use `Yes.keepTerminator` to include the 2289 terminator at the end of each line. 2290 terminator = Line separator (`'\n'` by default). Use 2291 $(REF newline, std,ascii) for portability (unless the file was opened in 2292 text mode). 2293 2294 Example: 2295 ---- 2296 import std.algorithm, std.stdio, std.string; 2297 // Count words in a file using ranges. 2298 void main() 2299 { 2300 auto file = File("file.txt"); // Open for reading 2301 const wordCount = file.byLine() // Read lines 2302 .map!split // Split into words 2303 .map!(a => a.length) // Count words per line 2304 .sum(); // Total word count 2305 writeln(wordCount); 2306 } 2307 ---- 2308 2309 Example: 2310 ---- 2311 import std.range, std.stdio; 2312 // Read lines using foreach. 2313 void main() 2314 { 2315 auto file = File("file.txt"); // Open for reading 2316 auto range = file.byLine(); 2317 // Print first three lines 2318 foreach (line; range.take(3)) 2319 writeln(line); 2320 // Print remaining lines beginning with '#' 2321 foreach (line; range) 2322 { 2323 if (!line.empty && line[0] == '#') 2324 writeln(line); 2325 } 2326 } 2327 ---- 2328 Notice that neither example accesses the line data returned by 2329 `front` after the corresponding `popFront` call is made (because 2330 the contents may well have changed). 2331 */ 2332 auto byLine(Terminator = char, Char = char) 2333 (KeepTerminator keepTerminator = No.keepTerminator, 2334 Terminator terminator = '\n') 2335 if (isScalarType!Terminator) 2336 { 2337 return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); 2338 } 2339 2340 /// ditto 2341 auto byLine(Terminator, Char = char) 2342 (KeepTerminator keepTerminator, Terminator terminator) 2343 if (is(immutable ElementEncodingType!Terminator == immutable Char)) 2344 { 2345 return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); 2346 } 2347 2348 @system unittest 2349 { 2350 static import std.file; 2351 auto deleteme = testFilename(); 2352 std.file.write(deleteme, "hi"); 2353 scope(success) std.file.remove(deleteme); 2354 2355 import std.meta : AliasSeq; 2356 static foreach (T; AliasSeq!(char, wchar, dchar)) 2357 {{ 2358 auto blc = File(deleteme).byLine!(T, T); 2359 assert(blc.front == "hi"); 2360 // check front is cached 2361 assert(blc.front is blc.front); 2362 }} 2363 } 2364 2365 // https://issues.dlang.org/show_bug.cgi?id=19980 2366 @system unittest 2367 { 2368 static import std.file; 2369 auto deleteme = testFilename(); 2370 std.file.write(deleteme, "Line 1\nLine 2\nLine 3\n"); 2371 scope(success) std.file.remove(deleteme); 2372 2373 auto f = File(deleteme); 2374 f.byLine(); 2375 f.byLine(); 2376 assert(f.byLine().front == "Line 1"); 2377 } 2378 2379 private struct ByLineCopy(Char, Terminator) 2380 { 2381 private: 2382 import std.typecons : RefCounted, RefCountedAutoInitialize; 2383 2384 /* Ref-counting stops the source range's ByLineCopyImpl 2385 * from getting out of sync after the range is copied, e.g. 2386 * when accessing range.front, then using std.range.take, 2387 * then accessing range.front again. */ 2388 alias Impl = RefCounted!(ByLineCopyImpl!(Char, Terminator), 2389 RefCountedAutoInitialize.no); 2390 Impl impl; 2391 2392 public: 2393 this(File f, KeepTerminator kt, Terminator terminator) 2394 { 2395 impl = Impl(f, kt, terminator); 2396 } 2397 2398 @property bool empty() 2399 { 2400 return impl.refCountedPayload.empty; 2401 } 2402 2403 @property Char[] front() 2404 { 2405 return impl.refCountedPayload.front; 2406 } 2407 2408 void popFront() 2409 { 2410 impl.refCountedPayload.popFront(); 2411 } 2412 } 2413 2414 private struct ByLineCopyImpl(Char, Terminator) 2415 { 2416 ByLineImpl!(Unqual!Char, Terminator).Impl impl; 2417 bool gotFront; 2418 Char[] line; 2419 2420 public: 2421 this(File f, KeepTerminator kt, Terminator terminator) 2422 { 2423 impl = ByLineImpl!(Unqual!Char, Terminator).Impl(f, kt, terminator); 2424 } 2425 2426 @property bool empty() 2427 { 2428 return impl.empty; 2429 } 2430 2431 @property front() 2432 { 2433 if (!gotFront) 2434 { 2435 line = impl.front.dup; 2436 gotFront = true; 2437 } 2438 return line; 2439 } 2440 2441 void popFront() 2442 { 2443 impl.popFront(); 2444 gotFront = false; 2445 } 2446 } 2447 2448 /** 2449 Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2450 set up to read from the file handle one line 2451 at a time. Each line will be newly allocated. `front` will cache 2452 its value to allow repeated calls without unnecessary allocations. 2453 2454 Note: Due to caching byLineCopy can be more memory-efficient than 2455 `File.byLine.map!idup`. 2456 2457 The element type for the range will be `Char[]`. Range 2458 primitives may throw `StdioException` on I/O error. 2459 2460 Params: 2461 Char = Character type for each line, defaulting to $(D immutable char). 2462 keepTerminator = Use `Yes.keepTerminator` to include the 2463 terminator at the end of each line. 2464 terminator = Line separator (`'\n'` by default). Use 2465 $(REF newline, std,ascii) for portability (unless the file was opened in 2466 text mode). 2467 2468 Example: 2469 ---- 2470 import std.algorithm, std.array, std.stdio; 2471 // Print sorted lines of a file. 2472 void main() 2473 { 2474 auto sortedLines = File("file.txt") // Open for reading 2475 .byLineCopy() // Read persistent lines 2476 .array() // into an array 2477 .sort(); // then sort them 2478 foreach (line; sortedLines) 2479 writeln(line); 2480 } 2481 ---- 2482 See_Also: 2483 $(REF readText, std,file) 2484 */ 2485 auto byLineCopy(Terminator = char, Char = immutable char) 2486 (KeepTerminator keepTerminator = No.keepTerminator, 2487 Terminator terminator = '\n') 2488 if (isScalarType!Terminator) 2489 { 2490 return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); 2491 } 2492 2493 /// ditto 2494 auto byLineCopy(Terminator, Char = immutable char) 2495 (KeepTerminator keepTerminator, Terminator terminator) 2496 if (is(immutable ElementEncodingType!Terminator == immutable Char)) 2497 { 2498 return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); 2499 } 2500 2501 @safe unittest 2502 { 2503 static assert(is(typeof(File("").byLine.front) == char[])); 2504 static assert(is(typeof(File("").byLineCopy.front) == string)); 2505 static assert( 2506 is(typeof(File("").byLineCopy!(char, char).front) == char[])); 2507 } 2508 2509 @system unittest 2510 { 2511 import std.algorithm.comparison : equal; 2512 static import std.file; 2513 2514 scope(failure) printf("Failed test at line %d\n", __LINE__); 2515 auto deleteme = testFilename(); 2516 std.file.write(deleteme, ""); 2517 scope(success) std.file.remove(deleteme); 2518 2519 // Test empty file 2520 auto f = File(deleteme); 2521 foreach (line; f.byLine()) 2522 { 2523 assert(false); 2524 } 2525 f.detach(); 2526 assert(!f.isOpen); 2527 2528 void test(Terminator)(string txt, in string[] witness, 2529 KeepTerminator kt, Terminator term, bool popFirstLine = false) 2530 { 2531 import std.algorithm.sorting : sort; 2532 import std.array : array; 2533 import std.conv : text; 2534 import std.range.primitives : walkLength; 2535 2536 uint i; 2537 std.file.write(deleteme, txt); 2538 auto f = File(deleteme); 2539 scope(exit) 2540 { 2541 f.close(); 2542 assert(!f.isOpen); 2543 } 2544 auto lines = f.byLine(kt, term); 2545 if (popFirstLine) 2546 { 2547 lines.popFront(); 2548 i = 1; 2549 } 2550 assert(lines.empty || lines.front is lines.front); 2551 foreach (line; lines) 2552 { 2553 assert(line == witness[i++]); 2554 } 2555 assert(i == witness.length, text(i, " != ", witness.length)); 2556 2557 // https://issues.dlang.org/show_bug.cgi?id=11830 2558 auto walkedLength = File(deleteme).byLine(kt, term).walkLength; 2559 assert(walkedLength == witness.length, text(walkedLength, " != ", witness.length)); 2560 2561 // test persistent lines 2562 assert(File(deleteme).byLineCopy(kt, term).array.sort() == witness.dup.sort()); 2563 } 2564 2565 KeepTerminator kt = No.keepTerminator; 2566 test("", null, kt, '\n'); 2567 test("\n", [ "" ], kt, '\n'); 2568 test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n'); 2569 test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n', true); 2570 test("asd\ndef\nasdf\n", [ "asd", "def", "asdf" ], kt, '\n'); 2571 test("foo", [ "foo" ], kt, '\n', true); 2572 test("bob\r\nmarge\r\nsteve\r\n", ["bob", "marge", "steve"], 2573 kt, "\r\n"); 2574 test("sue\r", ["sue"], kt, '\r'); 2575 2576 kt = Yes.keepTerminator; 2577 test("", null, kt, '\n'); 2578 test("\n", [ "\n" ], kt, '\n'); 2579 test("asd\ndef\nasdf", [ "asd\n", "def\n", "asdf" ], kt, '\n'); 2580 test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n'); 2581 test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n', true); 2582 test("foo", [ "foo" ], kt, '\n'); 2583 test("bob\r\nmarge\r\nsteve\r\n", ["bob\r\n", "marge\r\n", "steve\r\n"], 2584 kt, "\r\n"); 2585 test("sue\r", ["sue\r"], kt, '\r'); 2586 } 2587 2588 @system unittest 2589 { 2590 import std.algorithm.comparison : equal; 2591 import std.range : drop, take; 2592 2593 version (Win64) 2594 { 2595 static import std.file; 2596 2597 /* the C function tmpfile doesn't seem to work, even when called from C */ 2598 auto deleteme = testFilename(); 2599 auto file = File(deleteme, "w+"); 2600 scope(success) std.file.remove(deleteme); 2601 } 2602 else version (CRuntime_Bionic) 2603 { 2604 static import std.file; 2605 2606 /* the C function tmpfile doesn't work when called from a shared 2607 library apk: 2608 https://code.google.com/p/android/issues/detail?id=66815 */ 2609 auto deleteme = testFilename(); 2610 auto file = File(deleteme, "w+"); 2611 scope(success) std.file.remove(deleteme); 2612 } 2613 else 2614 auto file = File.tmpfile(); 2615 file.write("1\n2\n3\n"); 2616 2617 // https://issues.dlang.org/show_bug.cgi?id=9599 2618 file.rewind(); 2619 File.ByLineImpl!(char, char) fbl = file.byLine(); 2620 auto fbl2 = fbl; 2621 assert(fbl.front == "1"); 2622 assert(fbl.front is fbl2.front); 2623 assert(fbl.take(1).equal(["1"])); 2624 assert(fbl.equal(["2", "3"])); 2625 assert(fbl.empty); 2626 assert(file.isOpen); // we still have a valid reference 2627 2628 file.rewind(); 2629 fbl = file.byLine(); 2630 assert(!fbl.drop(2).empty); 2631 assert(fbl.equal(["3"])); 2632 assert(fbl.empty); 2633 assert(file.isOpen); 2634 2635 file.detach(); 2636 assert(!file.isOpen); 2637 } 2638 2639 @system unittest 2640 { 2641 static import std.file; 2642 auto deleteme = testFilename(); 2643 std.file.write(deleteme, "hi"); 2644 scope(success) std.file.remove(deleteme); 2645 2646 auto blc = File(deleteme).byLineCopy; 2647 assert(!blc.empty); 2648 // check front is cached 2649 assert(blc.front is blc.front); 2650 } 2651 2652 /** 2653 Creates an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2654 set up to parse one line at a time from the file into a tuple. 2655 2656 Range primitives may throw `StdioException` on I/O error. 2657 2658 Params: 2659 format = tuple record $(REF_ALTTEXT _format, formattedRead, std, _format) 2660 2661 Returns: 2662 The input range set up to parse one line at a time into a record tuple. 2663 2664 See_Also: 2665 2666 It is similar to $(LREF byLine) and uses 2667 $(REF_ALTTEXT _format, formattedRead, std, _format) under the hood. 2668 */ 2669 template byRecord(Fields...) 2670 { 2671 auto byRecord(string format) 2672 { 2673 return ByRecordImpl!(Fields)(this, format); 2674 } 2675 } 2676 2677 /// 2678 @system unittest 2679 { 2680 static import std.file; 2681 import std.typecons : tuple; 2682 2683 // prepare test file 2684 auto testFile = std.file.deleteme(); 2685 scope(failure) printf("Failed test at line %d\n", __LINE__); 2686 std.file.write(testFile, "1 2\n4 1\n5 100"); 2687 scope(exit) std.file.remove(testFile); 2688 2689 File f = File(testFile); 2690 scope(exit) f.close(); 2691 2692 auto expected = [tuple(1, 2), tuple(4, 1), tuple(5, 100)]; 2693 uint i; 2694 foreach (e; f.byRecord!(int, int)("%s %s")) 2695 { 2696 assert(e == expected[i++]); 2697 } 2698 } 2699 2700 // Note: This was documented until 2013/08 2701 /* 2702 * Range that reads a chunk at a time. 2703 */ 2704 private struct ByChunkImpl 2705 { 2706 private: 2707 File file_; 2708 ubyte[] chunk_; 2709 2710 void prime() 2711 { 2712 chunk_ = file_.rawRead(chunk_); 2713 if (chunk_.length == 0) 2714 file_.detach(); 2715 } 2716 2717 public: 2718 this(File file, size_t size) 2719 { 2720 this(file, new ubyte[](size)); 2721 } 2722 2723 this(File file, ubyte[] buffer) 2724 { 2725 import std.exception : enforce; 2726 enforce(buffer.length, "size must be larger than 0"); 2727 file_ = file; 2728 chunk_ = buffer; 2729 prime(); 2730 } 2731 2732 // `ByChunk`'s input range primitive operations. 2733 @property nothrow 2734 bool empty() const 2735 { 2736 return !file_.isOpen; 2737 } 2738 2739 /// Ditto 2740 @property nothrow 2741 ubyte[] front() 2742 { 2743 version (assert) 2744 { 2745 import core.exception : RangeError; 2746 if (empty) 2747 throw new RangeError(); 2748 } 2749 return chunk_; 2750 } 2751 2752 /// Ditto 2753 void popFront() 2754 { 2755 version (assert) 2756 { 2757 import core.exception : RangeError; 2758 if (empty) 2759 throw new RangeError(); 2760 } 2761 prime(); 2762 } 2763 } 2764 2765 /** 2766 Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2767 set up to read from the file handle a chunk at a time. 2768 2769 The element type for the range will be `ubyte[]`. Range primitives 2770 may throw `StdioException` on I/O error. 2771 2772 Example: 2773 --------- 2774 void main() 2775 { 2776 // Read standard input 4KB at a time 2777 foreach (ubyte[] buffer; stdin.byChunk(4096)) 2778 { 2779 ... use buffer ... 2780 } 2781 } 2782 --------- 2783 2784 The parameter may be a number (as shown in the example above) dictating the 2785 size of each chunk. Alternatively, `byChunk` accepts a 2786 user-provided buffer that it uses directly. 2787 2788 Example: 2789 --------- 2790 void main() 2791 { 2792 // Read standard input 4KB at a time 2793 foreach (ubyte[] buffer; stdin.byChunk(new ubyte[4096])) 2794 { 2795 ... use buffer ... 2796 } 2797 } 2798 --------- 2799 2800 In either case, the content of the buffer is reused across calls. That means 2801 `front` will not persist after `popFront` is called, so if retention is 2802 needed, the caller must copy its contents (e.g. by calling `buffer.dup`). 2803 2804 In the example above, `buffer.length` is 4096 for all iterations, except 2805 for the last one, in which case `buffer.length` may be less than 4096 (but 2806 always greater than zero). 2807 2808 With the mentioned limitations, `byChunk` works with any algorithm 2809 compatible with input ranges. 2810 2811 Example: 2812 --- 2813 // Efficient file copy, 1MB at a time. 2814 import std.algorithm, std.stdio; 2815 void main() 2816 { 2817 stdin.byChunk(1024 * 1024).copy(stdout.lockingTextWriter()); 2818 } 2819 --- 2820 2821 $(REF joiner, std,algorithm,iteration) can be used to join chunks together into 2822 a single range lazily. 2823 Example: 2824 --- 2825 import std.algorithm, std.stdio; 2826 void main() 2827 { 2828 //Range of ranges 2829 static assert(is(typeof(stdin.byChunk(4096).front) == ubyte[])); 2830 //Range of elements 2831 static assert(is(typeof(stdin.byChunk(4096).joiner.front) == ubyte)); 2832 } 2833 --- 2834 2835 Returns: A call to `byChunk` returns a range initialized with the `File` 2836 object and the appropriate buffer. 2837 2838 Throws: If the user-provided size is zero or the user-provided buffer 2839 is empty, throws an `Exception`. In case of an I/O error throws 2840 `StdioException`. 2841 */ 2842 auto byChunk(size_t chunkSize) 2843 { 2844 return ByChunkImpl(this, chunkSize); 2845 } 2846 /// Ditto 2847 auto byChunk(ubyte[] buffer) 2848 { 2849 return ByChunkImpl(this, buffer); 2850 } 2851 2852 @system unittest 2853 { 2854 static import std.file; 2855 2856 scope(failure) printf("Failed test at line %d\n", __LINE__); 2857 2858 auto deleteme = testFilename(); 2859 std.file.write(deleteme, "asd\ndef\nasdf"); 2860 2861 auto witness = ["asd\n", "def\n", "asdf" ]; 2862 auto f = File(deleteme); 2863 scope(exit) 2864 { 2865 f.close(); 2866 assert(!f.isOpen); 2867 std.file.remove(deleteme); 2868 } 2869 2870 uint i; 2871 foreach (chunk; f.byChunk(4)) 2872 assert(chunk == cast(ubyte[]) witness[i++]); 2873 2874 assert(i == witness.length); 2875 } 2876 2877 @system unittest 2878 { 2879 static import std.file; 2880 2881 scope(failure) printf("Failed test at line %d\n", __LINE__); 2882 2883 auto deleteme = testFilename(); 2884 std.file.write(deleteme, "asd\ndef\nasdf"); 2885 2886 auto witness = ["asd\n", "def\n", "asdf" ]; 2887 auto f = File(deleteme); 2888 scope(exit) 2889 { 2890 f.close(); 2891 assert(!f.isOpen); 2892 std.file.remove(deleteme); 2893 } 2894 2895 uint i; 2896 foreach (chunk; f.byChunk(new ubyte[4])) 2897 assert(chunk == cast(ubyte[]) witness[i++]); 2898 2899 assert(i == witness.length); 2900 } 2901 2902 // Note: This was documented until 2013/08 2903 /* 2904 `Range` that locks the file and allows fast writing to it. 2905 */ 2906 struct LockingTextWriter 2907 { 2908 private: 2909 import std.range.primitives : ElementType, isInfinite, isInputRange; 2910 // Access the FILE* handle through the 'file_' member 2911 // to keep the object alive through refcounting 2912 File file_; 2913 2914 // the unshared version of FILE* handle, extracted from the File object 2915 @property _iobuf* handle_() @trusted { return cast(_iobuf*) file_._p.handle; } 2916 2917 // the file's orientation (byte- or wide-oriented) 2918 int orientation_; 2919 2920 // A buffer for when we need to transcode. 2921 wchar highSurrogate = '\0'; // '\0' indicates empty 2922 void highSurrogateShouldBeEmpty() @safe 2923 { 2924 import std.utf : UTFException; 2925 if (highSurrogate != '\0') 2926 throw new UTFException("unpaired surrogate UTF-16 value"); 2927 } 2928 public: 2929 2930 this(ref File f) @trusted 2931 { 2932 import core.stdc.wchar_ : fwide; 2933 import std.exception : enforce; 2934 2935 enforce(f._p && f._p.handle, "Attempting to write to closed File"); 2936 file_ = f; 2937 FILE* fps = f._p.handle; 2938 orientation_ = fwide(fps, 0); 2939 FLOCK(fps); 2940 } 2941 2942 ~this() @trusted 2943 { 2944 if (auto p = file_._p) 2945 { 2946 if (p.handle) FUNLOCK(p.handle); 2947 } 2948 file_ = File.init; 2949 /* Destroy file_ before possibly throwing. Else it wouldn't be 2950 destroyed, and its reference count would be wrong. */ 2951 highSurrogateShouldBeEmpty(); 2952 } 2953 2954 this(this) @trusted 2955 { 2956 if (auto p = file_._p) 2957 { 2958 if (p.handle) FLOCK(p.handle); 2959 } 2960 } 2961 2962 /// Range primitive implementations. 2963 void put(A)(scope A writeme) 2964 if ((isSomeChar!(Unqual!(ElementType!A)) || 2965 is(ElementType!A : const(ubyte))) && 2966 isInputRange!A && 2967 !isInfinite!A) 2968 { 2969 import std.exception : errnoEnforce; 2970 2971 alias C = ElementEncodingType!A; 2972 static assert(!is(C == void)); 2973 static if (isSomeString!A && C.sizeof == 1 || is(A : const(ubyte)[])) 2974 { 2975 if (orientation_ <= 0) 2976 { 2977 //file.write(writeme); causes infinite recursion!!! 2978 //file.rawWrite(writeme); 2979 auto result = trustedFwrite(file_._p.handle, writeme); 2980 if (result != writeme.length) errnoEnforce(0); 2981 return; 2982 } 2983 } 2984 2985 // put each element in turn. 2986 foreach (c; writeme) 2987 { 2988 put(c); 2989 } 2990 } 2991 2992 /// ditto 2993 void put(C)(scope C c) @safe if (isSomeChar!C || is(C : const(ubyte))) 2994 { 2995 import std.traits : Parameters; 2996 static auto trustedFPUTC(int ch, _iobuf* h) @trusted 2997 { 2998 return FPUTC(ch, h); 2999 } 3000 static auto trustedFPUTWC(Parameters!FPUTWC[0] ch, _iobuf* h) @trusted 3001 { 3002 return FPUTWC(ch, h); 3003 } 3004 3005 static if (c.sizeof == 1) 3006 { 3007 // simple char 3008 highSurrogateShouldBeEmpty(); 3009 if (orientation_ <= 0) trustedFPUTC(c, handle_); 3010 else trustedFPUTWC(c, handle_); 3011 } 3012 else static if (c.sizeof == 2) 3013 { 3014 import std.utf : encode, decode; 3015 3016 if (orientation_ <= 0) 3017 { 3018 if (c <= 0x7F) 3019 { 3020 highSurrogateShouldBeEmpty(); 3021 trustedFPUTC(c, handle_); 3022 } 3023 else if (0xD800 <= c && c <= 0xDBFF) // high surrogate 3024 { 3025 highSurrogateShouldBeEmpty(); 3026 highSurrogate = c; 3027 } 3028 else // standalone or low surrogate 3029 { 3030 dchar d = c; 3031 if (highSurrogate != '\0') 3032 { 3033 immutable wchar[2] rbuf = [highSurrogate, c]; 3034 size_t index = 0; 3035 d = decode(rbuf[], index); 3036 highSurrogate = 0; 3037 } 3038 char[4] wbuf; 3039 immutable size = encode(wbuf, d); 3040 foreach (i; 0 .. size) 3041 trustedFPUTC(wbuf[i], handle_); 3042 } 3043 } 3044 else 3045 { 3046 trustedFPUTWC(c, handle_); 3047 } 3048 } 3049 else // 32-bit characters 3050 { 3051 import std.utf : encode; 3052 3053 highSurrogateShouldBeEmpty(); 3054 if (orientation_ <= 0) 3055 { 3056 if (c <= 0x7F) 3057 { 3058 trustedFPUTC(c, handle_); 3059 } 3060 else 3061 { 3062 char[4] buf = void; 3063 immutable len = encode(buf, c); 3064 foreach (i ; 0 .. len) 3065 trustedFPUTC(buf[i], handle_); 3066 } 3067 } 3068 else 3069 { 3070 version (Windows) 3071 { 3072 import std.utf : isValidDchar; 3073 3074 assert(isValidDchar(c)); 3075 if (c <= 0xFFFF) 3076 { 3077 trustedFPUTWC(c, handle_); 3078 } 3079 else 3080 { 3081 trustedFPUTWC(cast(wchar) 3082 ((((c - 0x10000) >> 10) & 0x3FF) 3083 + 0xD800), handle_); 3084 trustedFPUTWC(cast(wchar) 3085 (((c - 0x10000) & 0x3FF) + 0xDC00), 3086 handle_); 3087 } 3088 } 3089 else version (Posix) 3090 { 3091 trustedFPUTWC(c, handle_); 3092 } 3093 else 3094 { 3095 static assert(0); 3096 } 3097 } 3098 } 3099 } 3100 } 3101 3102 /** 3103 * Output range which locks the file when created, and unlocks the file when it goes 3104 * out of scope. 3105 * 3106 * Returns: An $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) 3107 * which accepts string types, `ubyte[]`, individual character types, and 3108 * individual `ubyte`s. 3109 * 3110 * Note: Writing either arrays of `char`s or `ubyte`s is faster than 3111 * writing each character individually from a range. For large amounts of data, 3112 * writing the contents in chunks using an intermediary array can result 3113 * in a speed increase. 3114 * 3115 * Throws: $(REF UTFException, std, utf) if the data given is a `char` range 3116 * and it contains malformed UTF data. 3117 * 3118 * See_Also: $(LREF byChunk) for an example. 3119 */ 3120 auto lockingTextWriter() @safe 3121 { 3122 return LockingTextWriter(this); 3123 } 3124 3125 // An output range which optionally locks the file and puts it into 3126 // binary mode (similar to rawWrite). Because it needs to restore 3127 // the file mode on destruction, it is RefCounted on Windows. 3128 struct BinaryWriterImpl(bool locking) 3129 { 3130 import std.traits : hasIndirections; 3131 private: 3132 // Access the FILE* handle through the 'file_' member 3133 // to keep the object alive through refcounting 3134 File file_; 3135 string name; 3136 3137 version (Windows) 3138 { 3139 int fd, oldMode; 3140 version (DIGITAL_MARS_STDIO) 3141 ubyte oldInfo; 3142 } 3143 3144 package: 3145 this(ref File f) 3146 { 3147 import std.exception : enforce; 3148 file_ = f; 3149 enforce(f._p && f._p.handle); 3150 name = f._name; 3151 FILE* fps = f._p.handle; 3152 static if (locking) 3153 FLOCK(fps); 3154 3155 version (Windows) 3156 { 3157 .fflush(fps); // before changing translation mode 3158 fd = ._fileno(fps); 3159 oldMode = ._setmode(fd, _O_BINARY); 3160 version (DIGITAL_MARS_STDIO) 3161 { 3162 import core.atomic : atomicOp; 3163 3164 // https://issues.dlang.org/show_bug.cgi?id=4243 3165 oldInfo = __fhnd_info[fd]; 3166 atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); 3167 } 3168 } 3169 } 3170 3171 public: 3172 ~this() 3173 { 3174 if (!file_._p || !file_._p.handle) 3175 return; 3176 3177 FILE* fps = file_._p.handle; 3178 3179 version (Windows) 3180 { 3181 .fflush(fps); // before restoring translation mode 3182 version (DIGITAL_MARS_STDIO) 3183 { 3184 // https://issues.dlang.org/show_bug.cgi?id=4243 3185 __fhnd_info[fd] = oldInfo; 3186 } 3187 ._setmode(fd, oldMode); 3188 } 3189 3190 FUNLOCK(fps); 3191 } 3192 3193 void rawWrite(T)(in T[] buffer) 3194 { 3195 import std.conv : text; 3196 import std.exception : errnoEnforce; 3197 3198 auto result = trustedFwrite(file_._p.handle, buffer); 3199 if (result == result.max) result = 0; 3200 errnoEnforce(result == buffer.length, 3201 text("Wrote ", result, " instead of ", buffer.length, 3202 " objects of type ", T.stringof, " to file `", 3203 name, "'")); 3204 } 3205 3206 version (Windows) 3207 { 3208 @disable this(this); 3209 } 3210 else 3211 { 3212 this(this) 3213 { 3214 if (auto p = file_._p) 3215 { 3216 if (p.handle) FLOCK(p.handle); 3217 } 3218 } 3219 } 3220 3221 void put(T)(auto ref scope const T value) 3222 if (!hasIndirections!T && 3223 !isInputRange!T) 3224 { 3225 rawWrite((&value)[0 .. 1]); 3226 } 3227 3228 void put(T)(scope const(T)[] array) 3229 if (!hasIndirections!T && 3230 !isInputRange!T) 3231 { 3232 rawWrite(array); 3233 } 3234 } 3235 3236 /** Returns an output range that locks the file and allows fast writing to it. 3237 3238 Example: 3239 Produce a grayscale image of the $(LINK2 https://en.wikipedia.org/wiki/Mandelbrot_set, Mandelbrot set) 3240 in binary $(LINK2 https://en.wikipedia.org/wiki/Netpbm_format, Netpbm format) to standard output. 3241 --- 3242 import std.algorithm, std.complex, std.range, std.stdio; 3243 3244 void main() 3245 { 3246 enum size = 500; 3247 writef("P5\n%d %d %d\n", size, size, ubyte.max); 3248 3249 iota(-1, 3, 2.0/size).map!(y => 3250 iota(-1.5, 0.5, 2.0/size).map!(x => 3251 cast(ubyte)(1+ 3252 recurrence!((a, n) => x + y * complex(0, 1) + a[n-1]^^2)(complex(0)) 3253 .take(ubyte.max) 3254 .countUntil!(z => z.re^^2 + z.im^^2 > 4)) 3255 ) 3256 ) 3257 .copy(stdout.lockingBinaryWriter); 3258 } 3259 --- 3260 */ 3261 auto lockingBinaryWriter() 3262 { 3263 alias LockingBinaryWriterImpl = BinaryWriterImpl!true; 3264 3265 version (Windows) 3266 { 3267 import std.typecons : RefCounted; 3268 alias LockingBinaryWriter = RefCounted!LockingBinaryWriterImpl; 3269 } 3270 else 3271 alias LockingBinaryWriter = LockingBinaryWriterImpl; 3272 3273 return LockingBinaryWriter(this); 3274 } 3275 3276 @system unittest 3277 { 3278 import std.algorithm.mutation : reverse; 3279 import std.exception : collectException; 3280 static import std.file; 3281 import std.range : only, retro; 3282 import std..string : format; 3283 3284 auto deleteme = testFilename(); 3285 scope(exit) collectException(std.file.remove(deleteme)); 3286 3287 { 3288 auto writer = File(deleteme, "wb").lockingBinaryWriter(); 3289 auto input = File(deleteme, "rb"); 3290 3291 ubyte[1] byteIn = [42]; 3292 writer.rawWrite(byteIn); 3293 destroy(writer); 3294 3295 ubyte[1] byteOut = input.rawRead(new ubyte[1]); 3296 assert(byteIn[0] == byteOut[0]); 3297 } 3298 3299 auto output = File(deleteme, "wb"); 3300 auto writer = output.lockingBinaryWriter(); 3301 auto input = File(deleteme, "rb"); 3302 3303 T[] readExact(T)(T[] buf) 3304 { 3305 auto result = input.rawRead(buf); 3306 assert(result.length == buf.length, 3307 "Read %d out of %d bytes" 3308 .format(result.length, buf.length)); 3309 return result; 3310 } 3311 3312 // test raw values 3313 ubyte byteIn = 42; 3314 byteIn.only.copy(writer); output.flush(); 3315 ubyte byteOut = readExact(new ubyte[1])[0]; 3316 assert(byteIn == byteOut); 3317 3318 // test arrays 3319 ubyte[] bytesIn = [1, 2, 3, 4, 5]; 3320 bytesIn.copy(writer); output.flush(); 3321 ubyte[] bytesOut = readExact(new ubyte[bytesIn.length]); 3322 scope(failure) .writeln(bytesOut); 3323 assert(bytesIn == bytesOut); 3324 3325 // test ranges of values 3326 bytesIn.retro.copy(writer); output.flush(); 3327 bytesOut = readExact(bytesOut); 3328 bytesOut.reverse(); 3329 assert(bytesIn == bytesOut); 3330 3331 // test string 3332 "foobar".copy(writer); output.flush(); 3333 char[] charsOut = readExact(new char[6]); 3334 assert(charsOut == "foobar"); 3335 3336 // test ranges of arrays 3337 only("foo", "bar").copy(writer); output.flush(); 3338 charsOut = readExact(charsOut); 3339 assert(charsOut == "foobar"); 3340 3341 // test that we are writing arrays as is, 3342 // without UTF-8 transcoding 3343 "foo"d.copy(writer); output.flush(); 3344 dchar[] dcharsOut = readExact(new dchar[3]); 3345 assert(dcharsOut == "foo"); 3346 } 3347 3348 /** Returns the size of the file in bytes, ulong.max if file is not searchable or throws if the operation fails. 3349 Example: 3350 --- 3351 import std.stdio, std.file; 3352 3353 void main() 3354 { 3355 string deleteme = "delete.me"; 3356 auto file_handle = File(deleteme, "w"); 3357 file_handle.write("abc"); //create temporary file 3358 scope(exit) deleteme.remove; //remove temporary file at scope exit 3359 3360 assert(file_handle.size() == 3); //check if file size is 3 bytes 3361 } 3362 --- 3363 */ 3364 @property ulong size() @safe 3365 { 3366 import std.exception : collectException; 3367 3368 ulong pos = void; 3369 if (collectException(pos = tell)) return ulong.max; 3370 scope(exit) seek(pos); 3371 seek(0, SEEK_END); 3372 return tell; 3373 } 3374 } 3375 3376 @system unittest 3377 { 3378 @system struct SystemToString 3379 { 3380 string toString() 3381 { 3382 return "system"; 3383 } 3384 } 3385 3386 @trusted struct TrustedToString 3387 { 3388 string toString() 3389 { 3390 return "trusted"; 3391 } 3392 } 3393 3394 @safe struct SafeToString 3395 { 3396 string toString() 3397 { 3398 return "safe"; 3399 } 3400 } 3401 3402 @system void systemTests() 3403 { 3404 //system code can write to files/stdout with anything! 3405 if (false) 3406 { 3407 auto f = File(); 3408 3409 f.write("just a string"); 3410 f.write("string with arg: ", 47); 3411 f.write(SystemToString()); 3412 f.write(TrustedToString()); 3413 f.write(SafeToString()); 3414 3415 write("just a string"); 3416 write("string with arg: ", 47); 3417 write(SystemToString()); 3418 write(TrustedToString()); 3419 write(SafeToString()); 3420 3421 f.writeln("just a string"); 3422 f.writeln("string with arg: ", 47); 3423 f.writeln(SystemToString()); 3424 f.writeln(TrustedToString()); 3425 f.writeln(SafeToString()); 3426 3427 writeln("just a string"); 3428 writeln("string with arg: ", 47); 3429 writeln(SystemToString()); 3430 writeln(TrustedToString()); 3431 writeln(SafeToString()); 3432 3433 f.writef("string with arg: %s", 47); 3434 f.writef("%s", SystemToString()); 3435 f.writef("%s", TrustedToString()); 3436 f.writef("%s", SafeToString()); 3437 3438 writef("string with arg: %s", 47); 3439 writef("%s", SystemToString()); 3440 writef("%s", TrustedToString()); 3441 writef("%s", SafeToString()); 3442 3443 f.writefln("string with arg: %s", 47); 3444 f.writefln("%s", SystemToString()); 3445 f.writefln("%s", TrustedToString()); 3446 f.writefln("%s", SafeToString()); 3447 3448 writefln("string with arg: %s", 47); 3449 writefln("%s", SystemToString()); 3450 writefln("%s", TrustedToString()); 3451 writefln("%s", SafeToString()); 3452 } 3453 } 3454 3455 @safe void safeTests() 3456 { 3457 auto f = File(); 3458 3459 //safe code can write to files only with @safe and @trusted code... 3460 if (false) 3461 { 3462 f.write("just a string"); 3463 f.write("string with arg: ", 47); 3464 f.write(TrustedToString()); 3465 f.write(SafeToString()); 3466 3467 write("just a string"); 3468 write("string with arg: ", 47); 3469 write(TrustedToString()); 3470 write(SafeToString()); 3471 3472 f.writeln("just a string"); 3473 f.writeln("string with arg: ", 47); 3474 f.writeln(TrustedToString()); 3475 f.writeln(SafeToString()); 3476 3477 writeln("just a string"); 3478 writeln("string with arg: ", 47); 3479 writeln(TrustedToString()); 3480 writeln(SafeToString()); 3481 3482 f.writef("string with arg: %s", 47); 3483 f.writef("%s", TrustedToString()); 3484 f.writef("%s", SafeToString()); 3485 3486 writef("string with arg: %s", 47); 3487 writef("%s", TrustedToString()); 3488 writef("%s", SafeToString()); 3489 3490 f.writefln("string with arg: %s", 47); 3491 f.writefln("%s", TrustedToString()); 3492 f.writefln("%s", SafeToString()); 3493 3494 writefln("string with arg: %s", 47); 3495 writefln("%s", TrustedToString()); 3496 writefln("%s", SafeToString()); 3497 } 3498 3499 static assert(!__traits(compiles, f.write(SystemToString().toString()))); 3500 static assert(!__traits(compiles, f.writeln(SystemToString()))); 3501 static assert(!__traits(compiles, f.writef("%s", SystemToString()))); 3502 static assert(!__traits(compiles, f.writefln("%s", SystemToString()))); 3503 3504 static assert(!__traits(compiles, write(SystemToString().toString()))); 3505 static assert(!__traits(compiles, writeln(SystemToString()))); 3506 static assert(!__traits(compiles, writef("%s", SystemToString()))); 3507 static assert(!__traits(compiles, writefln("%s", SystemToString()))); 3508 } 3509 3510 systemTests(); 3511 safeTests(); 3512 } 3513 3514 @safe unittest 3515 { 3516 import std.exception : collectException; 3517 static import std.file; 3518 3519 auto deleteme = testFilename(); 3520 scope(exit) collectException(std.file.remove(deleteme)); 3521 std.file.write(deleteme, "1 2 3"); 3522 auto f = File(deleteme); 3523 assert(f.size == 5); 3524 assert(f.tell == 0); 3525 } 3526 3527 @system unittest 3528 { 3529 // @system due to readln 3530 static import std.file; 3531 import std.range : chain, only, repeat; 3532 import std.range.primitives : isOutputRange; 3533 3534 auto deleteme = testFilename(); 3535 scope(exit) std.file.remove(deleteme); 3536 3537 { 3538 auto writer = File(deleteme, "w").lockingTextWriter(); 3539 static assert(isOutputRange!(typeof(writer), dchar)); 3540 writer.put("日本語"); 3541 writer.put("日本語"w); 3542 writer.put("日本語"d); 3543 writer.put('日'); 3544 writer.put(chain(only('本'), only('語'))); 3545 // https://issues.dlang.org/show_bug.cgi?id=11945 3546 writer.put(repeat('#', 12)); 3547 // https://issues.dlang.org/show_bug.cgi?id=17229 3548 writer.put(cast(immutable(ubyte)[])"日本語"); 3549 } 3550 assert(File(deleteme).readln() == "日本語日本語日本語日本語############日本語"); 3551 } 3552 3553 @safe unittest // wchar -> char 3554 { 3555 static import std.file; 3556 import std.exception : assertThrown; 3557 import std.utf : UTFException; 3558 3559 auto deleteme = testFilename(); 3560 scope(exit) std.file.remove(deleteme); 3561 3562 { 3563 auto writer = File(deleteme, "w").lockingTextWriter(); 3564 writer.put("\U0001F608"w); 3565 } 3566 assert(std.file.readText!string(deleteme) == "\U0001F608"); 3567 3568 // Test invalid input: unpaired high surrogate 3569 { 3570 immutable wchar surr = "\U0001F608"w[0]; 3571 auto f = File(deleteme, "w"); 3572 assertThrown!UTFException(() { 3573 auto writer = f.lockingTextWriter(); 3574 writer.put('x'); 3575 writer.put(surr); 3576 assertThrown!UTFException(writer.put(char('y'))); 3577 assertThrown!UTFException(writer.put(wchar('y'))); 3578 assertThrown!UTFException(writer.put(dchar('y'))); 3579 assertThrown!UTFException(writer.put(surr)); 3580 // First `surr` is still unpaired at this point. `writer` gets 3581 // destroyed now, and the destructor throws a UTFException for 3582 // the unpaired surrogate. 3583 } ()); 3584 } 3585 assert(std.file.readText!string(deleteme) == "x"); 3586 3587 // Test invalid input: unpaired low surrogate 3588 { 3589 immutable wchar surr = "\U0001F608"w[1]; 3590 auto writer = File(deleteme, "w").lockingTextWriter(); 3591 assertThrown!UTFException(writer.put(surr)); 3592 writer.put('y'); 3593 assertThrown!UTFException(writer.put(surr)); 3594 } 3595 assert(std.file.readText!string(deleteme) == "y"); 3596 } 3597 3598 @safe unittest 3599 { 3600 import std.exception : collectException; 3601 auto e = collectException({ File f; f.writeln("Hello!"); }()); 3602 assert(e && e.msg == "Attempting to write to closed File"); 3603 } 3604 3605 version (StdStressTest) 3606 { 3607 // https://issues.dlang.org/show_bug.cgi?id=15768 3608 @system unittest 3609 { 3610 import std.parallelism : parallel; 3611 import std.range : iota; 3612 3613 auto deleteme = testFilename(); 3614 stderr = File(deleteme, "w"); 3615 3616 foreach (t; 1_000_000.iota.parallel) 3617 { 3618 stderr.write("aaa"); 3619 } 3620 } 3621 } 3622 3623 /// Used to specify the lock type for `File.lock` and `File.tryLock`. 3624 enum LockType 3625 { 3626 /** 3627 * Specifies a _read (shared) lock. A _read lock denies all processes 3628 * write access to the specified region of the file, including the 3629 * process that first locks the region. All processes can _read the 3630 * locked region. Multiple simultaneous _read locks are allowed, as 3631 * long as there are no exclusive locks. 3632 */ 3633 read, 3634 3635 /** 3636 * Specifies a read/write (exclusive) lock. A read/write lock denies all 3637 * other processes both read and write access to the locked file region. 3638 * If a segment has an exclusive lock, it may not have any shared locks 3639 * or other exclusive locks. 3640 */ 3641 readWrite 3642 } 3643 3644 struct LockingTextReader 3645 { 3646 private File _f; 3647 private char _front; 3648 private bool _hasChar; 3649 3650 this(File f) 3651 { 3652 import std.exception : enforce; 3653 enforce(f.isOpen, "LockingTextReader: File must be open"); 3654 _f = f; 3655 FLOCK(_f._p.handle); 3656 } 3657 3658 this(this) 3659 { 3660 FLOCK(_f._p.handle); 3661 } 3662 3663 ~this() 3664 { 3665 if (_hasChar) 3666 ungetc(_front, cast(FILE*)_f._p.handle); 3667 3668 // File locking has its own reference count 3669 if (_f.isOpen) FUNLOCK(_f._p.handle); 3670 } 3671 3672 void opAssign(LockingTextReader r) 3673 { 3674 import std.algorithm.mutation : swap; 3675 swap(this, r); 3676 } 3677 3678 @property bool empty() 3679 { 3680 if (!_hasChar) 3681 { 3682 if (!_f.isOpen || _f.eof) 3683 return true; 3684 immutable int c = FGETC(cast(_iobuf*) _f._p.handle); 3685 if (c == EOF) 3686 { 3687 .destroy(_f); 3688 return true; 3689 } 3690 _front = cast(char) c; 3691 _hasChar = true; 3692 } 3693 return false; 3694 } 3695 3696 @property char front() 3697 { 3698 if (!_hasChar) 3699 { 3700 version (assert) 3701 { 3702 import core.exception : RangeError; 3703 if (empty) 3704 throw new RangeError(); 3705 } 3706 else 3707 { 3708 empty; 3709 } 3710 } 3711 return _front; 3712 } 3713 3714 void popFront() 3715 { 3716 if (!_hasChar) 3717 empty; 3718 _hasChar = false; 3719 } 3720 } 3721 3722 @system unittest 3723 { 3724 // @system due to readf 3725 static import std.file; 3726 import std.range.primitives : isInputRange; 3727 3728 static assert(isInputRange!LockingTextReader); 3729 auto deleteme = testFilename(); 3730 std.file.write(deleteme, "1 2 3"); 3731 scope(exit) std.file.remove(deleteme); 3732 int x; 3733 auto f = File(deleteme); 3734 f.readf("%s ", &x); 3735 assert(x == 1); 3736 f.readf("%d ", &x); 3737 assert(x == 2); 3738 f.readf("%d ", &x); 3739 assert(x == 3); 3740 } 3741 3742 // https://issues.dlang.org/show_bug.cgi?id=13686 3743 @system unittest 3744 { 3745 import std.algorithm.comparison : equal; 3746 static import std.file; 3747 import std.utf : byDchar; 3748 3749 auto deleteme = testFilename(); 3750 std.file.write(deleteme, "Тест"); 3751 scope(exit) std.file.remove(deleteme); 3752 3753 string s; 3754 File(deleteme).readf("%s", &s); 3755 assert(s == "Тест"); 3756 3757 auto ltr = LockingTextReader(File(deleteme)).byDchar; 3758 assert(equal(ltr, "Тест".byDchar)); 3759 } 3760 3761 // https://issues.dlang.org/show_bug.cgi?id=12320 3762 @system unittest 3763 { 3764 static import std.file; 3765 auto deleteme = testFilename(); 3766 std.file.write(deleteme, "ab"); 3767 scope(exit) std.file.remove(deleteme); 3768 auto ltr = LockingTextReader(File(deleteme)); 3769 assert(ltr.front == 'a'); 3770 ltr.popFront(); 3771 assert(ltr.front == 'b'); 3772 ltr.popFront(); 3773 assert(ltr.empty); 3774 } 3775 3776 // https://issues.dlang.org/show_bug.cgi?id=14861 3777 @system unittest 3778 { 3779 // @system due to readf 3780 static import std.file; 3781 auto deleteme = testFilename(); 3782 File fw = File(deleteme, "w"); 3783 for (int i; i != 5000; i++) 3784 fw.writeln(i, ";", "Иванов;Пётр;Петрович"); 3785 fw.close(); 3786 scope(exit) std.file.remove(deleteme); 3787 // Test read 3788 File fr = File(deleteme, "r"); 3789 scope (exit) fr.close(); 3790 int nom; string fam, nam, ot; 3791 // Error format read 3792 while (!fr.eof) 3793 fr.readf("%s;%s;%s;%s\n", &nom, &fam, &nam, &ot); 3794 } 3795 3796 /** 3797 * Indicates whether `T` is a file handle, i.e. the type 3798 * is implicitly convertable to $(LREF File) or a pointer to a 3799 * $(REF FILE, core,stdc,stdio). 3800 * 3801 * Returns: 3802 * `true` if `T` is a file handle, `false` otherwise. 3803 */ 3804 template isFileHandle(T) 3805 { 3806 enum isFileHandle = is(T : FILE*) || 3807 is(T : File); 3808 } 3809 3810 /// 3811 @safe unittest 3812 { 3813 static assert(isFileHandle!(FILE*)); 3814 static assert(isFileHandle!(File)); 3815 } 3816 3817 /** 3818 * Property used by writeln/etc. so it can infer @safe since stdout is __gshared 3819 */ 3820 private @property File trustedStdout() @trusted 3821 { 3822 return stdout; 3823 } 3824 3825 /*********************************** 3826 Writes its arguments in text format to standard output (without a trailing newline). 3827 3828 Params: 3829 args = the items to write to `stdout` 3830 3831 Throws: In case of an I/O error, throws an `StdioException`. 3832 3833 Example: 3834 Reads `stdin` and writes it to `stdout` with an argument 3835 counter. 3836 --- 3837 import std.stdio; 3838 3839 void main() 3840 { 3841 string line; 3842 3843 for (size_t count = 0; (line = readln) !is null; count++) 3844 { 3845 write("Input ", count, ": ", line, "\n"); 3846 } 3847 } 3848 --- 3849 */ 3850 void write(T...)(T args) 3851 if (!is(T[0] : File)) 3852 { 3853 trustedStdout.write(args); 3854 } 3855 3856 @system unittest 3857 { 3858 static import std.file; 3859 3860 scope(failure) printf("Failed test at line %d\n", __LINE__); 3861 void[] buf; 3862 if (false) write(buf); 3863 // test write 3864 auto deleteme = testFilename(); 3865 auto f = File(deleteme, "w"); 3866 f.write("Hello, ", "world number ", 42, "!"); 3867 f.close(); 3868 scope(exit) { std.file.remove(deleteme); } 3869 assert(cast(char[]) std.file.read(deleteme) == "Hello, world number 42!"); 3870 } 3871 3872 /*********************************** 3873 * Equivalent to `write(args, '\n')`. Calling `writeln` without 3874 * arguments is valid and just prints a newline to the standard 3875 * output. 3876 * 3877 * Params: 3878 * args = the items to write to `stdout` 3879 * 3880 * Throws: 3881 * In case of an I/O error, throws an $(LREF StdioException). 3882 * Example: 3883 * Reads `stdin` and writes it to `stdout` with an argument 3884 * counter. 3885 --- 3886 import std.stdio; 3887 3888 void main() 3889 { 3890 string line; 3891 3892 for (size_t count = 0; (line = readln) !is null; count++) 3893 { 3894 writeln("Input ", count, ": ", line); 3895 } 3896 } 3897 --- 3898 */ 3899 void writeln(T...)(T args) 3900 { 3901 static if (T.length == 0) 3902 { 3903 import std.exception : enforce; 3904 3905 enforce(fputc('\n', .trustedStdout._p.handle) != EOF, "fputc failed"); 3906 } 3907 else static if (T.length == 1 && 3908 is(T[0] : const(char)[]) && 3909 (is(T[0] == U[], U) || __traits(isStaticArray, T[0]))) 3910 { 3911 // Specialization for strings - a very frequent case 3912 auto w = .trustedStdout.lockingTextWriter(); 3913 3914 static if (__traits(isStaticArray, T[0])) 3915 { 3916 w.put(args[0][]); 3917 } 3918 else 3919 { 3920 w.put(args[0]); 3921 } 3922 w.put('\n'); 3923 } 3924 else 3925 { 3926 // Most general instance 3927 trustedStdout.write(args, '\n'); 3928 } 3929 } 3930 3931 @safe unittest 3932 { 3933 // Just make sure the call compiles 3934 if (false) writeln(); 3935 3936 if (false) writeln("wyda"); 3937 3938 // https://issues.dlang.org/show_bug.cgi?id=8040 3939 if (false) writeln(null); 3940 if (false) writeln(">", null, "<"); 3941 3942 // https://issues.dlang.org/show_bug.cgi?id=14041 3943 if (false) 3944 { 3945 char[8] a; 3946 writeln(a); 3947 immutable b = a; 3948 b.writeln; 3949 const c = a[]; 3950 c.writeln; 3951 } 3952 } 3953 3954 @system unittest 3955 { 3956 static import std.file; 3957 3958 scope(failure) printf("Failed test at line %d\n", __LINE__); 3959 3960 // test writeln 3961 auto deleteme = testFilename(); 3962 auto f = File(deleteme, "w"); 3963 scope(exit) { std.file.remove(deleteme); } 3964 f.writeln("Hello, ", "world number ", 42, "!"); 3965 f.close(); 3966 version (Windows) 3967 assert(cast(char[]) std.file.read(deleteme) == 3968 "Hello, world number 42!\r\n"); 3969 else 3970 assert(cast(char[]) std.file.read(deleteme) == 3971 "Hello, world number 42!\n"); 3972 3973 // test writeln on stdout 3974 auto saveStdout = stdout; 3975 scope(exit) stdout = saveStdout; 3976 stdout.open(deleteme, "w"); 3977 writeln("Hello, ", "world number ", 42, "!"); 3978 stdout.close(); 3979 version (Windows) 3980 assert(cast(char[]) std.file.read(deleteme) == 3981 "Hello, world number 42!\r\n"); 3982 else 3983 assert(cast(char[]) std.file.read(deleteme) == 3984 "Hello, world number 42!\n"); 3985 3986 stdout.open(deleteme, "w"); 3987 writeln("Hello!"c); 3988 writeln("Hello!"w); // https://issues.dlang.org/show_bug.cgi?id=8386 3989 writeln("Hello!"d); // https://issues.dlang.org/show_bug.cgi?id=8386 3990 writeln("embedded\0null"c); // https://issues.dlang.org/show_bug.cgi?id=8730 3991 stdout.close(); 3992 version (Windows) 3993 assert(cast(char[]) std.file.read(deleteme) == 3994 "Hello!\r\nHello!\r\nHello!\r\nembedded\0null\r\n"); 3995 else 3996 assert(cast(char[]) std.file.read(deleteme) == 3997 "Hello!\nHello!\nHello!\nembedded\0null\n"); 3998 } 3999 4000 @system unittest 4001 { 4002 static import std.file; 4003 4004 auto deleteme = testFilename(); 4005 auto f = File(deleteme, "w"); 4006 scope(exit) { std.file.remove(deleteme); } 4007 4008 enum EI : int { A, B } 4009 enum ED : double { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle 4010 enum EC : char { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle 4011 enum ES : string { A = "aaa", B = "bbb" } 4012 4013 f.writeln(EI.A); // false, but A on 2.058 4014 f.writeln(EI.B); // true, but B on 2.058 4015 4016 f.writeln(ED.A); // A 4017 f.writeln(ED.B); // B 4018 4019 f.writeln(EC.A); // A 4020 f.writeln(EC.B); // B 4021 4022 f.writeln(ES.A); // A 4023 f.writeln(ES.B); // B 4024 4025 f.close(); 4026 version (Windows) 4027 assert(cast(char[]) std.file.read(deleteme) == 4028 "A\r\nB\r\nA\r\nB\r\nA\r\nB\r\nA\r\nB\r\n"); 4029 else 4030 assert(cast(char[]) std.file.read(deleteme) == 4031 "A\nB\nA\nB\nA\nB\nA\nB\n"); 4032 } 4033 4034 @system unittest 4035 { 4036 static auto useInit(T)(T ltw) 4037 { 4038 T val; 4039 val = ltw; 4040 val = T.init; 4041 return val; 4042 } 4043 useInit(stdout.lockingTextWriter()); 4044 } 4045 4046 4047 /*********************************** 4048 Writes formatted data to standard output (without a trailing newline). 4049 4050 Params: 4051 fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). 4052 When passed as a compile-time argument, the string will be statically checked 4053 against the argument types passed. 4054 args = Items to write. 4055 4056 Note: In older versions of Phobos, it used to be possible to write: 4057 4058 ------ 4059 writef(stderr, "%s", "message"); 4060 ------ 4061 4062 to print a message to `stderr`. This syntax is no longer supported, and has 4063 been superceded by: 4064 4065 ------ 4066 stderr.writef("%s", "message"); 4067 ------ 4068 4069 */ 4070 void writef(alias fmt, A...)(A args) 4071 if (isSomeString!(typeof(fmt))) 4072 { 4073 import std.format : checkFormatException; 4074 4075 alias e = checkFormatException!(fmt, A); 4076 static assert(!e, e.msg); 4077 return .writef(fmt, args); 4078 } 4079 4080 /// ditto 4081 void writef(Char, A...)(in Char[] fmt, A args) 4082 { 4083 trustedStdout.writef(fmt, args); 4084 } 4085 4086 @system unittest 4087 { 4088 static import std.file; 4089 4090 scope(failure) printf("Failed test at line %d\n", __LINE__); 4091 4092 // test writef 4093 auto deleteme = testFilename(); 4094 auto f = File(deleteme, "w"); 4095 scope(exit) { std.file.remove(deleteme); } 4096 f.writef!"Hello, %s world number %s!"("nice", 42); 4097 f.close(); 4098 assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); 4099 // test write on stdout 4100 auto saveStdout = stdout; 4101 scope(exit) stdout = saveStdout; 4102 stdout.open(deleteme, "w"); 4103 writef!"Hello, %s world number %s!"("nice", 42); 4104 stdout.close(); 4105 assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); 4106 } 4107 4108 /*********************************** 4109 * Equivalent to $(D writef(fmt, args, '\n')). 4110 */ 4111 void writefln(alias fmt, A...)(A args) 4112 if (isSomeString!(typeof(fmt))) 4113 { 4114 import std.format : checkFormatException; 4115 4116 alias e = checkFormatException!(fmt, A); 4117 static assert(!e, e.msg); 4118 return .writefln(fmt, args); 4119 } 4120 4121 /// ditto 4122 void writefln(Char, A...)(in Char[] fmt, A args) 4123 { 4124 trustedStdout.writefln(fmt, args); 4125 } 4126 4127 @system unittest 4128 { 4129 static import std.file; 4130 4131 scope(failure) printf("Failed test at line %d\n", __LINE__); 4132 4133 // test File.writefln 4134 auto deleteme = testFilename(); 4135 auto f = File(deleteme, "w"); 4136 scope(exit) { std.file.remove(deleteme); } 4137 f.writefln!"Hello, %s world number %s!"("nice", 42); 4138 f.close(); 4139 version (Windows) 4140 assert(cast(char[]) std.file.read(deleteme) == 4141 "Hello, nice world number 42!\r\n"); 4142 else 4143 assert(cast(char[]) std.file.read(deleteme) == 4144 "Hello, nice world number 42!\n", 4145 cast(char[]) std.file.read(deleteme)); 4146 4147 // test writefln 4148 auto saveStdout = stdout; 4149 scope(exit) stdout = saveStdout; 4150 stdout.open(deleteme, "w"); 4151 writefln!"Hello, %s world number %s!"("nice", 42); 4152 stdout.close(); 4153 version (Windows) 4154 assert(cast(char[]) std.file.read(deleteme) == 4155 "Hello, nice world number 42!\r\n"); 4156 else 4157 assert(cast(char[]) std.file.read(deleteme) == 4158 "Hello, nice world number 42!\n"); 4159 } 4160 4161 /** 4162 * Reads formatted data from `stdin` using $(REF formattedRead, std,_format). 4163 * Params: 4164 * format = The $(REF_ALTTEXT format string, formattedWrite, std, _format). 4165 * When passed as a compile-time argument, the string will be statically checked 4166 * against the argument types passed. 4167 * args = Items to be read. 4168 * Example: 4169 ---- 4170 // test.d 4171 void main() 4172 { 4173 import std.stdio; 4174 foreach (_; 0 .. 3) 4175 { 4176 int a; 4177 readf!" %d"(a); 4178 writeln(++a); 4179 } 4180 } 4181 ---- 4182 $(CONSOLE 4183 % echo "1 2 3" | rdmd test.d 4184 2 4185 3 4186 4 4187 ) 4188 */ 4189 uint readf(alias format, A...)(auto ref A args) 4190 if (isSomeString!(typeof(format))) 4191 { 4192 import std.format : checkFormatException; 4193 4194 alias e = checkFormatException!(format, A); 4195 static assert(!e, e.msg); 4196 return .readf(format, args); 4197 } 4198 4199 /// ditto 4200 uint readf(A...)(scope const(char)[] format, auto ref A args) 4201 { 4202 return stdin.readf(format, args); 4203 } 4204 4205 @system unittest 4206 { 4207 float f; 4208 if (false) readf("%s", &f); 4209 4210 char a; 4211 wchar b; 4212 dchar c; 4213 if (false) readf("%s %s %s", a, b, c); 4214 // backwards compatibility with pointers 4215 if (false) readf("%s %s %s", a, &b, c); 4216 if (false) readf("%s %s %s", &a, &b, &c); 4217 } 4218 4219 /********************************** 4220 * Read line from `stdin`. 4221 * 4222 * This version manages its own read buffer, which means one memory allocation per call. If you are not 4223 * retaining a reference to the read data, consider the `readln(buf)` version, which may offer 4224 * better performance as it can reuse its read buffer. 4225 * 4226 * Returns: 4227 * The line that was read, including the line terminator character. 4228 * Params: 4229 * S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. 4230 * terminator = Line terminator (by default, `'\n'`). 4231 * Note: 4232 * String terminators are not supported due to ambiguity with readln(buf) below. 4233 * Throws: 4234 * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. 4235 * Example: 4236 * Reads `stdin` and writes it to `stdout`. 4237 --- 4238 import std.stdio; 4239 4240 void main() 4241 { 4242 string line; 4243 while ((line = readln()) !is null) 4244 write(line); 4245 } 4246 --- 4247 */ 4248 S readln(S = string)(dchar terminator = '\n') 4249 if (isSomeString!S) 4250 { 4251 return stdin.readln!S(terminator); 4252 } 4253 4254 /********************************** 4255 * Read line from `stdin` and write it to buf[], including terminating character. 4256 * 4257 * This can be faster than $(D line = readln()) because you can reuse 4258 * the buffer for each call. Note that reusing the buffer means that you 4259 * must copy the previous contents if you wish to retain them. 4260 * 4261 * Returns: 4262 * `size_t` 0 for end of file, otherwise number of characters read 4263 * Params: 4264 * buf = Buffer used to store the resulting line data. buf is resized as necessary. 4265 * terminator = Line terminator (by default, `'\n'`). Use $(REF newline, std,ascii) 4266 * for portability (unless the file was opened in text mode). 4267 * Throws: 4268 * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. 4269 * Example: 4270 * Reads `stdin` and writes it to `stdout`. 4271 --- 4272 import std.stdio; 4273 4274 void main() 4275 { 4276 char[] buf; 4277 while (readln(buf)) 4278 write(buf); 4279 } 4280 --- 4281 */ 4282 size_t readln(C)(ref C[] buf, dchar terminator = '\n') 4283 if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) 4284 { 4285 return stdin.readln(buf, terminator); 4286 } 4287 4288 /** ditto */ 4289 size_t readln(C, R)(ref C[] buf, R terminator) 4290 if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && 4291 isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) 4292 { 4293 return stdin.readln(buf, terminator); 4294 } 4295 4296 @safe unittest 4297 { 4298 import std.meta : AliasSeq; 4299 4300 //we can't actually test readln, so at the very least, 4301 //we test compilability 4302 void foo() 4303 { 4304 readln(); 4305 readln('\t'); 4306 static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) 4307 { 4308 readln!String(); 4309 readln!String('\t'); 4310 } 4311 static foreach (String; AliasSeq!(char[], wchar[], dchar[])) 4312 {{ 4313 String buf; 4314 readln(buf); 4315 readln(buf, '\t'); 4316 readln(buf, "<br />"); 4317 }} 4318 } 4319 } 4320 4321 /* 4322 * Convenience function that forwards to `core.sys.posix.stdio.fopen` 4323 * (to `_wfopen` on Windows) 4324 * with appropriately-constructed C-style strings. 4325 */ 4326 private FILE* _fopen(R1, R2)(R1 name, R2 mode = "r") 4327 if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) && 4328 (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2)) 4329 { 4330 import std.internal.cstring : tempCString; 4331 4332 auto namez = name.tempCString!FSChar(); 4333 auto modez = mode.tempCString!FSChar(); 4334 4335 static _fopenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc 4336 { 4337 version (Windows) 4338 { 4339 return _wfopen(namez, modez); 4340 } 4341 else version (Posix) 4342 { 4343 /* 4344 * The new opengroup large file support API is transparently 4345 * included in the normal C bindings. http://opengroup.org/platform/lfs.html#1.0 4346 * if _FILE_OFFSET_BITS in druntime is 64, off_t is 64 bit and 4347 * the normal functions work fine. If not, then large file support 4348 * probably isn't available. Do not use the old transitional API 4349 * (the native extern(C) fopen64, http://www.unix.org/version2/whatsnew/lfs20mar.html#3.0) 4350 */ 4351 import core.sys.posix.stdio : fopen; 4352 return fopen(namez, modez); 4353 } 4354 else 4355 { 4356 return fopen(namez, modez); 4357 } 4358 } 4359 return _fopenImpl(namez, modez); 4360 } 4361 4362 version (Posix) 4363 { 4364 /*********************************** 4365 * Convenience function that forwards to `core.sys.posix.stdio.popen` 4366 * with appropriately-constructed C-style strings. 4367 */ 4368 FILE* _popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc 4369 if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) && 4370 (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2)) 4371 { 4372 import std.internal.cstring : tempCString; 4373 4374 auto namez = name.tempCString!FSChar(); 4375 auto modez = mode.tempCString!FSChar(); 4376 4377 static popenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc 4378 { 4379 import core.sys.posix.stdio : popen; 4380 return popen(namez, modez); 4381 } 4382 return popenImpl(namez, modez); 4383 } 4384 } 4385 4386 /* 4387 * Convenience function that forwards to `core.stdc.stdio.fwrite` 4388 */ 4389 private auto trustedFwrite(T)(FILE* f, const T[] obj) @trusted 4390 { 4391 return fwrite(obj.ptr, T.sizeof, obj.length, f); 4392 } 4393 4394 /* 4395 * Convenience function that forwards to `core.stdc.stdio.fread` 4396 */ 4397 private auto trustedFread(T)(FILE* f, T[] obj) @trusted 4398 { 4399 return fread(obj.ptr, T.sizeof, obj.length, f); 4400 } 4401 4402 /** 4403 * Iterates through the lines of a file by using `foreach`. 4404 * 4405 * Example: 4406 * 4407 --------- 4408 void main() 4409 { 4410 foreach (string line; lines(stdin)) 4411 { 4412 ... use line ... 4413 } 4414 } 4415 --------- 4416 The line terminator (`'\n'` by default) is part of the string read (it 4417 could be missing in the last line of the file). Several types are 4418 supported for `line`, and the behavior of `lines` 4419 changes accordingly: 4420 4421 $(OL $(LI If `line` has type `string`, $(D 4422 wstring), or `dstring`, a new string of the respective type 4423 is allocated every read.) $(LI If `line` has type $(D 4424 char[]), `wchar[]`, `dchar[]`, the line's content 4425 will be reused (overwritten) across reads.) $(LI If `line` 4426 has type `immutable(ubyte)[]`, the behavior is similar to 4427 case (1), except that no UTF checking is attempted upon input.) $(LI 4428 If `line` has type `ubyte[]`, the behavior is 4429 similar to case (2), except that no UTF checking is attempted upon 4430 input.)) 4431 4432 In all cases, a two-symbols versions is also accepted, in which case 4433 the first symbol (of integral type, e.g. `ulong` or $(D 4434 uint)) tracks the zero-based number of the current line. 4435 4436 Example: 4437 ---- 4438 foreach (ulong i, string line; lines(stdin)) 4439 { 4440 ... use line ... 4441 } 4442 ---- 4443 4444 In case of an I/O error, an `StdioException` is thrown. 4445 4446 See_Also: 4447 $(LREF byLine) 4448 */ 4449 4450 struct lines 4451 { 4452 private File f; 4453 private dchar terminator = '\n'; 4454 4455 /** 4456 Constructor. 4457 Params: 4458 f = File to read lines from. 4459 terminator = Line separator (`'\n'` by default). 4460 */ 4461 this(File f, dchar terminator = '\n') 4462 { 4463 this.f = f; 4464 this.terminator = terminator; 4465 } 4466 4467 int opApply(D)(scope D dg) 4468 { 4469 import std.traits : Parameters; 4470 alias Parms = Parameters!(dg); 4471 static if (isSomeString!(Parms[$ - 1])) 4472 { 4473 int result = 0; 4474 static if (is(Parms[$ - 1] : const(char)[])) 4475 alias C = char; 4476 else static if (is(Parms[$ - 1] : const(wchar)[])) 4477 alias C = wchar; 4478 else static if (is(Parms[$ - 1] : const(dchar)[])) 4479 alias C = dchar; 4480 C[] line; 4481 static if (Parms.length == 2) 4482 Parms[0] i = 0; 4483 for (;;) 4484 { 4485 import std.conv : to; 4486 4487 if (!f.readln(line, terminator)) break; 4488 auto copy = to!(Parms[$ - 1])(line); 4489 static if (Parms.length == 2) 4490 { 4491 result = dg(i, copy); 4492 ++i; 4493 } 4494 else 4495 { 4496 result = dg(copy); 4497 } 4498 if (result != 0) break; 4499 } 4500 return result; 4501 } 4502 else 4503 { 4504 // raw read 4505 return opApplyRaw(dg); 4506 } 4507 } 4508 // no UTF checking 4509 int opApplyRaw(D)(scope D dg) 4510 { 4511 import std.conv : to; 4512 import std.exception : assumeUnique; 4513 import std.traits : Parameters; 4514 4515 alias Parms = Parameters!(dg); 4516 enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]); 4517 int result = 1; 4518 int c = void; 4519 FLOCK(f._p.handle); 4520 scope(exit) FUNLOCK(f._p.handle); 4521 ubyte[] buffer; 4522 static if (Parms.length == 2) 4523 Parms[0] line = 0; 4524 while ((c = FGETC(cast(_iobuf*) f._p.handle)) != -1) 4525 { 4526 buffer ~= to!(ubyte)(c); 4527 if (c == terminator) 4528 { 4529 static if (duplicate) 4530 auto arg = assumeUnique(buffer); 4531 else 4532 alias arg = buffer; 4533 // unlock the file while calling the delegate 4534 FUNLOCK(f._p.handle); 4535 scope(exit) FLOCK(f._p.handle); 4536 static if (Parms.length == 1) 4537 { 4538 result = dg(arg); 4539 } 4540 else 4541 { 4542 result = dg(line, arg); 4543 ++line; 4544 } 4545 if (result) break; 4546 static if (!duplicate) 4547 buffer.length = 0; 4548 } 4549 } 4550 // can only reach when FGETC returned -1 4551 if (!f.eof) throw new StdioException("Error in reading file"); // error occured 4552 return result; 4553 } 4554 } 4555 4556 @system unittest 4557 { 4558 static import std.file; 4559 import std.meta : AliasSeq; 4560 4561 scope(failure) printf("Failed test at line %d\n", __LINE__); 4562 4563 auto deleteme = testFilename(); 4564 scope(exit) { std.file.remove(deleteme); } 4565 4566 alias TestedWith = 4567 AliasSeq!(string, wstring, dstring, 4568 char[], wchar[], dchar[]); 4569 foreach (T; TestedWith) 4570 { 4571 // test looping with an empty file 4572 std.file.write(deleteme, ""); 4573 auto f = File(deleteme, "r"); 4574 foreach (T line; lines(f)) 4575 { 4576 assert(false); 4577 } 4578 f.close(); 4579 4580 // test looping with a file with three lines 4581 std.file.write(deleteme, "Line one\nline two\nline three\n"); 4582 f.open(deleteme, "r"); 4583 uint i = 0; 4584 foreach (T line; lines(f)) 4585 { 4586 if (i == 0) assert(line == "Line one\n"); 4587 else if (i == 1) assert(line == "line two\n"); 4588 else if (i == 2) assert(line == "line three\n"); 4589 else assert(false); 4590 ++i; 4591 } 4592 f.close(); 4593 4594 // test looping with a file with three lines, last without a newline 4595 std.file.write(deleteme, "Line one\nline two\nline three"); 4596 f.open(deleteme, "r"); 4597 i = 0; 4598 foreach (T line; lines(f)) 4599 { 4600 if (i == 0) assert(line == "Line one\n"); 4601 else if (i == 1) assert(line == "line two\n"); 4602 else if (i == 2) assert(line == "line three"); 4603 else assert(false); 4604 ++i; 4605 } 4606 f.close(); 4607 } 4608 4609 // test with ubyte[] inputs 4610 alias TestedWith2 = AliasSeq!(immutable(ubyte)[], ubyte[]); 4611 foreach (T; TestedWith2) 4612 { 4613 // test looping with an empty file 4614 std.file.write(deleteme, ""); 4615 auto f = File(deleteme, "r"); 4616 foreach (T line; lines(f)) 4617 { 4618 assert(false); 4619 } 4620 f.close(); 4621 4622 // test looping with a file with three lines 4623 std.file.write(deleteme, "Line one\nline two\nline three\n"); 4624 f.open(deleteme, "r"); 4625 uint i = 0; 4626 foreach (T line; lines(f)) 4627 { 4628 if (i == 0) assert(cast(char[]) line == "Line one\n"); 4629 else if (i == 1) assert(cast(char[]) line == "line two\n", 4630 T.stringof ~ " " ~ cast(char[]) line); 4631 else if (i == 2) assert(cast(char[]) line == "line three\n"); 4632 else assert(false); 4633 ++i; 4634 } 4635 f.close(); 4636 4637 // test looping with a file with three lines, last without a newline 4638 std.file.write(deleteme, "Line one\nline two\nline three"); 4639 f.open(deleteme, "r"); 4640 i = 0; 4641 foreach (T line; lines(f)) 4642 { 4643 if (i == 0) assert(cast(char[]) line == "Line one\n"); 4644 else if (i == 1) assert(cast(char[]) line == "line two\n"); 4645 else if (i == 2) assert(cast(char[]) line == "line three"); 4646 else assert(false); 4647 ++i; 4648 } 4649 f.close(); 4650 4651 } 4652 4653 static foreach (T; AliasSeq!(ubyte[])) 4654 { 4655 // test looping with a file with three lines, last without a newline 4656 // using a counter too this time 4657 std.file.write(deleteme, "Line one\nline two\nline three"); 4658 auto f = File(deleteme, "r"); 4659 uint i = 0; 4660 foreach (ulong j, T line; lines(f)) 4661 { 4662 if (i == 0) assert(cast(char[]) line == "Line one\n"); 4663 else if (i == 1) assert(cast(char[]) line == "line two\n"); 4664 else if (i == 2) assert(cast(char[]) line == "line three"); 4665 else assert(false); 4666 ++i; 4667 } 4668 f.close(); 4669 } 4670 } 4671 4672 /** 4673 Iterates through a file a chunk at a time by using `foreach`. 4674 4675 Example: 4676 4677 --------- 4678 void main() 4679 { 4680 foreach (ubyte[] buffer; chunks(stdin, 4096)) 4681 { 4682 ... use buffer ... 4683 } 4684 } 4685 --------- 4686 4687 The content of `buffer` is reused across calls. In the 4688 example above, `buffer.length` is 4096 for all iterations, 4689 except for the last one, in which case `buffer.length` may 4690 be less than 4096 (but always greater than zero). 4691 4692 In case of an I/O error, an `StdioException` is thrown. 4693 */ 4694 auto chunks(File f, size_t size) 4695 { 4696 return ChunksImpl(f, size); 4697 } 4698 private struct ChunksImpl 4699 { 4700 private File f; 4701 private size_t size; 4702 // private string fileName; // Currently, no use 4703 4704 this(File f, size_t size) 4705 in 4706 { 4707 assert(size, "size must be larger than 0"); 4708 } 4709 do 4710 { 4711 this.f = f; 4712 this.size = size; 4713 } 4714 4715 int opApply(D)(scope D dg) 4716 { 4717 import core.stdc.stdlib : alloca; 4718 enum maxStackSize = 1024 * 16; 4719 ubyte[] buffer = void; 4720 if (size < maxStackSize) 4721 buffer = (cast(ubyte*) alloca(size))[0 .. size]; 4722 else 4723 buffer = new ubyte[size]; 4724 size_t r = void; 4725 int result = 1; 4726 uint tally = 0; 4727 while ((r = trustedFread(f._p.handle, buffer)) > 0) 4728 { 4729 assert(r <= size); 4730 if (r != size) 4731 { 4732 // error occured 4733 if (!f.eof) throw new StdioException(null); 4734 buffer.length = r; 4735 } 4736 static if (is(typeof(dg(tally, buffer)))) 4737 { 4738 if ((result = dg(tally, buffer)) != 0) break; 4739 } 4740 else 4741 { 4742 if ((result = dg(buffer)) != 0) break; 4743 } 4744 ++tally; 4745 } 4746 return result; 4747 } 4748 } 4749 4750 @system unittest 4751 { 4752 static import std.file; 4753 4754 scope(failure) printf("Failed test at line %d\n", __LINE__); 4755 4756 auto deleteme = testFilename(); 4757 scope(exit) { std.file.remove(deleteme); } 4758 4759 // test looping with an empty file 4760 std.file.write(deleteme, ""); 4761 auto f = File(deleteme, "r"); 4762 foreach (ubyte[] line; chunks(f, 4)) 4763 { 4764 assert(false); 4765 } 4766 f.close(); 4767 4768 // test looping with a file with three lines 4769 std.file.write(deleteme, "Line one\nline two\nline three\n"); 4770 f = File(deleteme, "r"); 4771 uint i = 0; 4772 foreach (ubyte[] line; chunks(f, 3)) 4773 { 4774 if (i == 0) assert(cast(char[]) line == "Lin"); 4775 else if (i == 1) assert(cast(char[]) line == "e o"); 4776 else if (i == 2) assert(cast(char[]) line == "ne\n"); 4777 else break; 4778 ++i; 4779 } 4780 f.close(); 4781 } 4782 4783 4784 /** 4785 Writes an array or range to a file. 4786 Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)). 4787 Similar to $(REF write, std,file), strings are written as-is, 4788 rather than encoded according to the `File`'s $(HTTP 4789 en.cppreference.com/w/c/io#Narrow_and_wide_orientation, 4790 orientation). 4791 */ 4792 void toFile(T)(T data, string fileName) 4793 if (is(typeof(copy(data, stdout.lockingBinaryWriter)))) 4794 { 4795 copy(data, File(fileName, "wb").lockingBinaryWriter); 4796 } 4797 4798 @system unittest 4799 { 4800 static import std.file; 4801 4802 auto deleteme = testFilename(); 4803 scope(exit) { std.file.remove(deleteme); } 4804 4805 "Test".toFile(deleteme); 4806 assert(std.file.readText(deleteme) == "Test"); 4807 } 4808 4809 /********************* 4810 * Thrown if I/O errors happen. 4811 */ 4812 class StdioException : Exception 4813 { 4814 static import core.stdc.errno; 4815 /// Operating system error code. 4816 uint errno; 4817 4818 /** 4819 Initialize with a message and an error code. 4820 */ 4821 this(string message, uint e = core.stdc.errno.errno) @trusted 4822 { 4823 import std.exception : errnoString; 4824 errno = e; 4825 auto sysmsg = errnoString(errno); 4826 // If e is 0, we don't use the system error message. (The message 4827 // is "Success", which is rather pointless for an exception.) 4828 super(e == 0 ? message 4829 : (message ? message ~ " (" ~ sysmsg ~ ")" : sysmsg)); 4830 } 4831 4832 /** Convenience functions that throw an `StdioException`. */ 4833 static void opCall(string msg) 4834 { 4835 throw new StdioException(msg); 4836 } 4837 4838 /// ditto 4839 static void opCall() 4840 { 4841 throw new StdioException(null, core.stdc.errno.errno); 4842 } 4843 } 4844 4845 enum StdFileHandle: string 4846 { 4847 stdin = "core.stdc.stdio.stdin", 4848 stdout = "core.stdc.stdio.stdout", 4849 stderr = "core.stdc.stdio.stderr", 4850 } 4851 4852 // Undocumented but public because the std* handles are aliasing it. 4853 @property ref File makeGlobal(StdFileHandle _iob)() 4854 { 4855 __gshared File.Impl impl; 4856 __gshared File result; 4857 4858 // Use an inline spinlock to make sure the initializer is only run once. 4859 // We assume there will be at most uint.max / 2 threads trying to initialize 4860 // `handle` at once and steal the high bit to indicate that the globals have 4861 // been initialized. 4862 static shared uint spinlock; 4863 import core.atomic : atomicLoad, atomicOp, MemoryOrder; 4864 if (atomicLoad!(MemoryOrder.acq)(spinlock) <= uint.max / 2) 4865 { 4866 for (;;) 4867 { 4868 if (atomicLoad!(MemoryOrder.acq)(spinlock) > uint.max / 2) 4869 break; 4870 if (atomicOp!"+="(spinlock, 1) == 1) 4871 { 4872 with (StdFileHandle) 4873 assert(_iob == stdin || _iob == stdout || _iob == stderr); 4874 impl.handle = mixin(_iob); 4875 result._p = &impl; 4876 atomicOp!"+="(spinlock, uint.max / 2); 4877 break; 4878 } 4879 atomicOp!"-="(spinlock, 1); 4880 } 4881 } 4882 return result; 4883 } 4884 4885 /** The standard input stream. 4886 4887 Returns: 4888 stdin as a $(LREF File). 4889 4890 Note: 4891 The returned $(LREF File) wraps $(REF stdin,core,stdc,stdio), and 4892 is therefore thread global. Reassigning `stdin` to a different 4893 `File` must be done in a single-threaded or locked context in 4894 order to avoid race conditions. 4895 4896 All reading from `stdin` automatically locks the file globally, 4897 and will cause all other threads calling `read` to wait until 4898 the lock is released. 4899 */ 4900 alias stdin = makeGlobal!(StdFileHandle.stdin); 4901 4902 /// 4903 @safe unittest 4904 { 4905 // Read stdin, sort lines, write to stdout 4906 import std.algorithm.mutation : copy; 4907 import std.algorithm.sorting : sort; 4908 import std.array : array; 4909 import std.typecons : Yes; 4910 4911 void main() 4912 { 4913 stdin // read from stdin 4914 .byLineCopy(Yes.keepTerminator) // copying each line 4915 .array() // convert to array of lines 4916 .sort() // sort the lines 4917 .copy( // copy output of .sort to an OutputRange 4918 stdout.lockingTextWriter()); // the OutputRange 4919 } 4920 } 4921 4922 /** 4923 The standard output stream. 4924 4925 Returns: 4926 stdout as a $(LREF File). 4927 4928 Note: 4929 The returned $(LREF File) wraps $(REF stdout,core,stdc,stdio), and 4930 is therefore thread global. Reassigning `stdout` to a different 4931 `File` must be done in a single-threaded or locked context in 4932 order to avoid race conditions. 4933 4934 All writing to `stdout` automatically locks the file globally, 4935 and will cause all other threads calling `write` to wait until 4936 the lock is released. 4937 */ 4938 alias stdout = makeGlobal!(StdFileHandle.stdout); 4939 4940 /// 4941 @safe unittest 4942 { 4943 void main() 4944 { 4945 stdout.writeln("Write a message to stdout."); 4946 } 4947 } 4948 4949 /// 4950 @safe unittest 4951 { 4952 void main() 4953 { 4954 import std.algorithm.iteration : filter, map, sum; 4955 import std.format : format; 4956 import std.range : iota, tee; 4957 4958 int len; 4959 const r = 6.iota 4960 .filter!(a => a % 2) // 1 3 5 4961 .map!(a => a * 2) // 2 6 10 4962 .tee!(_ => stdout.writefln("len: %d", len++)) 4963 .sum; 4964 4965 assert(r == 18); 4966 } 4967 } 4968 4969 /// 4970 @safe unittest 4971 { 4972 void main() 4973 { 4974 import std.algorithm.mutation : copy; 4975 import std.algorithm.iteration : map; 4976 import std.format : format; 4977 import std.range : iota; 4978 4979 10.iota 4980 .map!(e => "N: %d".format(e)) 4981 .copy(stdout.lockingTextWriter()); // the OutputRange 4982 } 4983 } 4984 4985 /** 4986 The standard error stream. 4987 4988 Returns: 4989 stderr as a $(LREF File). 4990 4991 Note: 4992 The returned $(LREF File) wraps $(REF stderr,core,stdc,stdio), and 4993 is therefore thread global. Reassigning `stderr` to a different 4994 `File` must be done in a single-threaded or locked context in 4995 order to avoid race conditions. 4996 4997 All writing to `stderr` automatically locks the file globally, 4998 and will cause all other threads calling `write` to wait until 4999 the lock is released. 5000 */ 5001 alias stderr = makeGlobal!(StdFileHandle.stderr); 5002 5003 /// 5004 @safe unittest 5005 { 5006 void main() 5007 { 5008 stderr.writeln("Write a message to stderr."); 5009 } 5010 } 5011 5012 @system unittest 5013 { 5014 static import std.file; 5015 import std.typecons : tuple; 5016 5017 scope(failure) printf("Failed test at line %d\n", __LINE__); 5018 auto deleteme = testFilename(); 5019 5020 std.file.write(deleteme, "1 2\n4 1\n5 100"); 5021 scope(exit) std.file.remove(deleteme); 5022 { 5023 File f = File(deleteme); 5024 scope(exit) f.close(); 5025 auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ]; 5026 uint i; 5027 foreach (e; f.byRecord!(int, int)("%s %s")) 5028 { 5029 //writeln(e); 5030 assert(e == t[i++]); 5031 } 5032 assert(i == 3); 5033 } 5034 } 5035 5036 @safe unittest 5037 { 5038 // Retain backwards compatibility 5039 // https://issues.dlang.org/show_bug.cgi?id=17472 5040 static assert(is(typeof(stdin) == File)); 5041 static assert(is(typeof(stdout) == File)); 5042 static assert(is(typeof(stderr) == File)); 5043 } 5044 5045 // roll our own appender, but with "safe" arrays 5046 private struct ReadlnAppender 5047 { 5048 char[] buf; 5049 size_t pos; 5050 bool safeAppend = false; 5051 5052 void initialize(char[] b) 5053 { 5054 buf = b; 5055 pos = 0; 5056 } 5057 @property char[] data() @trusted 5058 { 5059 if (safeAppend) 5060 assumeSafeAppend(buf.ptr[0 .. pos]); 5061 return buf.ptr[0 .. pos]; 5062 } 5063 5064 bool reserveWithoutAllocating(size_t n) 5065 { 5066 if (buf.length >= pos + n) // buf is already large enough 5067 return true; 5068 5069 immutable curCap = buf.capacity; 5070 if (curCap >= pos + n) 5071 { 5072 buf.length = curCap; 5073 /* Any extra capacity we end up not using can safely be claimed 5074 by someone else. */ 5075 safeAppend = true; 5076 return true; 5077 } 5078 5079 return false; 5080 } 5081 void reserve(size_t n) @trusted 5082 { 5083 import core.stdc..string : memcpy; 5084 if (!reserveWithoutAllocating(n)) 5085 { 5086 size_t ncap = buf.length * 2 + 128 + n; 5087 char[] nbuf = new char[ncap]; 5088 memcpy(nbuf.ptr, buf.ptr, pos); 5089 buf = nbuf; 5090 // Allocated a new buffer. No one else knows about it. 5091 safeAppend = true; 5092 } 5093 } 5094 void putchar(char c) @trusted 5095 { 5096 reserve(1); 5097 buf.ptr[pos++] = c; 5098 } 5099 void putdchar(dchar dc) @trusted 5100 { 5101 import std.utf : encode, UseReplacementDchar; 5102 5103 char[4] ubuf; 5104 immutable size = encode!(UseReplacementDchar.yes)(ubuf, dc); 5105 reserve(size); 5106 foreach (c; ubuf) 5107 buf.ptr[pos++] = c; 5108 } 5109 void putonly(char[] b) @trusted 5110 { 5111 import core.stdc..string : memcpy; 5112 assert(pos == 0); // assume this is the only put call 5113 if (reserveWithoutAllocating(b.length)) 5114 memcpy(buf.ptr + pos, b.ptr, b.length); 5115 else 5116 buf = b.dup; 5117 pos = b.length; 5118 } 5119 } 5120 5121 // Private implementation of readln 5122 version (DIGITAL_MARS_STDIO) 5123 private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation /*ignored*/) 5124 { 5125 FLOCK(fps); 5126 scope(exit) FUNLOCK(fps); 5127 5128 /* Since fps is now locked, we can create an "unshared" version 5129 * of fp. 5130 */ 5131 auto fp = cast(_iobuf*) fps; 5132 5133 ReadlnAppender app; 5134 app.initialize(buf); 5135 5136 if (__fhnd_info[fp._file] & FHND_WCHAR) 5137 { /* Stream is in wide characters. 5138 * Read them and convert to chars. 5139 */ 5140 static assert(wchar_t.sizeof == 2); 5141 for (int c = void; (c = FGETWC(fp)) != -1; ) 5142 { 5143 if ((c & ~0x7F) == 0) 5144 { 5145 app.putchar(cast(char) c); 5146 if (c == terminator) 5147 break; 5148 } 5149 else 5150 { 5151 if (c >= 0xD800 && c <= 0xDBFF) 5152 { 5153 int c2 = void; 5154 if ((c2 = FGETWC(fp)) != -1 || 5155 c2 < 0xDC00 && c2 > 0xDFFF) 5156 { 5157 StdioException("unpaired UTF-16 surrogate"); 5158 } 5159 c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); 5160 } 5161 app.putdchar(cast(dchar) c); 5162 } 5163 } 5164 if (ferror(fps)) 5165 StdioException(); 5166 } 5167 5168 else if (fp._flag & _IONBF) 5169 { 5170 /* Use this for unbuffered I/O, when running 5171 * across buffer boundaries, or for any but the common 5172 * cases. 5173 */ 5174 L1: 5175 int c; 5176 while ((c = FGETC(fp)) != -1) 5177 { 5178 app.putchar(cast(char) c); 5179 if (c == terminator) 5180 { 5181 buf = app.data; 5182 return buf.length; 5183 } 5184 5185 } 5186 5187 if (ferror(fps)) 5188 StdioException(); 5189 } 5190 else 5191 { 5192 int u = fp._cnt; 5193 char* p = fp._ptr; 5194 int i; 5195 if (fp._flag & _IOTRAN) 5196 { /* Translated mode ignores \r and treats ^Z as end-of-file 5197 */ 5198 char c; 5199 while (1) 5200 { 5201 if (i == u) // if end of buffer 5202 goto L1; // give up 5203 c = p[i]; 5204 i++; 5205 if (c != '\r') 5206 { 5207 if (c == terminator) 5208 break; 5209 if (c != 0x1A) 5210 continue; 5211 goto L1; 5212 } 5213 else 5214 { if (i != u && p[i] == terminator) 5215 break; 5216 goto L1; 5217 } 5218 } 5219 app.putonly(p[0 .. i]); 5220 app.buf[i - 1] = cast(char) terminator; 5221 if (terminator == '\n' && c == '\r') 5222 i++; 5223 } 5224 else 5225 { 5226 while (1) 5227 { 5228 if (i == u) // if end of buffer 5229 goto L1; // give up 5230 auto c = p[i]; 5231 i++; 5232 if (c == terminator) 5233 break; 5234 } 5235 app.putonly(p[0 .. i]); 5236 } 5237 fp._cnt -= i; 5238 fp._ptr += i; 5239 } 5240 5241 buf = app.data; 5242 return buf.length; 5243 } 5244 5245 version (MICROSOFT_STDIO) 5246 private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation /*ignored*/) 5247 { 5248 FLOCK(fps); 5249 scope(exit) FUNLOCK(fps); 5250 5251 /* Since fps is now locked, we can create an "unshared" version 5252 * of fp. 5253 */ 5254 auto fp = cast(_iobuf*) fps; 5255 5256 ReadlnAppender app; 5257 app.initialize(buf); 5258 5259 int c; 5260 while ((c = FGETC(fp)) != -1) 5261 { 5262 app.putchar(cast(char) c); 5263 if (c == terminator) 5264 { 5265 buf = app.data; 5266 return buf.length; 5267 } 5268 5269 } 5270 5271 if (ferror(fps)) 5272 StdioException(); 5273 buf = app.data; 5274 return buf.length; 5275 } 5276 5277 version (HAS_GETDELIM) 5278 private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation) 5279 { 5280 import core.stdc.stdlib : free; 5281 import core.stdc.wchar_ : fwide; 5282 5283 if (orientation == File.Orientation.wide) 5284 { 5285 /* Stream is in wide characters. 5286 * Read them and convert to chars. 5287 */ 5288 FLOCK(fps); 5289 scope(exit) FUNLOCK(fps); 5290 auto fp = cast(_iobuf*) fps; 5291 version (Windows) 5292 { 5293 buf.length = 0; 5294 for (int c = void; (c = FGETWC(fp)) != -1; ) 5295 { 5296 if ((c & ~0x7F) == 0) 5297 { buf ~= c; 5298 if (c == terminator) 5299 break; 5300 } 5301 else 5302 { 5303 if (c >= 0xD800 && c <= 0xDBFF) 5304 { 5305 int c2 = void; 5306 if ((c2 = FGETWC(fp)) != -1 || 5307 c2 < 0xDC00 && c2 > 0xDFFF) 5308 { 5309 StdioException("unpaired UTF-16 surrogate"); 5310 } 5311 c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); 5312 } 5313 import std.utf : encode; 5314 encode(buf, c); 5315 } 5316 } 5317 if (ferror(fp)) 5318 StdioException(); 5319 return buf.length; 5320 } 5321 else version (Posix) 5322 { 5323 buf.length = 0; 5324 for (int c; (c = FGETWC(fp)) != -1; ) 5325 { 5326 import std.utf : encode; 5327 5328 if ((c & ~0x7F) == 0) 5329 buf ~= cast(char) c; 5330 else 5331 encode(buf, cast(dchar) c); 5332 if (c == terminator) 5333 break; 5334 } 5335 if (ferror(fps)) 5336 StdioException(); 5337 return buf.length; 5338 } 5339 else 5340 { 5341 static assert(0); 5342 } 5343 } 5344 5345 static char *lineptr = null; 5346 static size_t n = 0; 5347 scope(exit) 5348 { 5349 if (n > 128 * 1024) 5350 { 5351 // Bound memory used by readln 5352 free(lineptr); 5353 lineptr = null; 5354 n = 0; 5355 } 5356 } 5357 5358 auto s = getdelim(&lineptr, &n, terminator, fps); 5359 if (s < 0) 5360 { 5361 if (ferror(fps)) 5362 StdioException(); 5363 buf.length = 0; // end of file 5364 return 0; 5365 } 5366 5367 if (s <= buf.length) 5368 { 5369 buf = buf[0 .. s]; 5370 buf[] = lineptr[0 .. s]; 5371 } 5372 else 5373 { 5374 buf = lineptr[0 .. s].dup; 5375 } 5376 return s; 5377 } 5378 5379 version (NO_GETDELIM) 5380 private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation) 5381 { 5382 import core.stdc.wchar_ : fwide; 5383 5384 FLOCK(fps); 5385 scope(exit) FUNLOCK(fps); 5386 auto fp = cast(_iobuf*) fps; 5387 if (orientation == File.Orientation.wide) 5388 { 5389 /* Stream is in wide characters. 5390 * Read them and convert to chars. 5391 */ 5392 version (Windows) 5393 { 5394 buf.length = 0; 5395 for (int c; (c = FGETWC(fp)) != -1; ) 5396 { 5397 if ((c & ~0x7F) == 0) 5398 { buf ~= c; 5399 if (c == terminator) 5400 break; 5401 } 5402 else 5403 { 5404 if (c >= 0xD800 && c <= 0xDBFF) 5405 { 5406 int c2 = void; 5407 if ((c2 = FGETWC(fp)) != -1 || 5408 c2 < 0xDC00 && c2 > 0xDFFF) 5409 { 5410 StdioException("unpaired UTF-16 surrogate"); 5411 } 5412 c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); 5413 } 5414 import std.utf : encode; 5415 encode(buf, c); 5416 } 5417 } 5418 if (ferror(fp)) 5419 StdioException(); 5420 return buf.length; 5421 } 5422 else version (Posix) 5423 { 5424 import std.utf : encode; 5425 buf.length = 0; 5426 for (int c; (c = FGETWC(fp)) != -1; ) 5427 { 5428 if ((c & ~0x7F) == 0) 5429 buf ~= cast(char) c; 5430 else 5431 encode(buf, cast(dchar) c); 5432 if (c == terminator) 5433 break; 5434 } 5435 if (ferror(fps)) 5436 StdioException(); 5437 return buf.length; 5438 } 5439 else 5440 { 5441 static assert(0); 5442 } 5443 } 5444 5445 // Narrow stream 5446 // First, fill the existing buffer 5447 for (size_t bufPos = 0; bufPos < buf.length; ) 5448 { 5449 immutable c = FGETC(fp); 5450 if (c == -1) 5451 { 5452 buf.length = bufPos; 5453 goto endGame; 5454 } 5455 buf[bufPos++] = cast(char) c; 5456 if (c == terminator) 5457 { 5458 // No need to test for errors in file 5459 buf.length = bufPos; 5460 return bufPos; 5461 } 5462 } 5463 // Then, append to it 5464 for (int c; (c = FGETC(fp)) != -1; ) 5465 { 5466 buf ~= cast(char) c; 5467 if (c == terminator) 5468 { 5469 // No need to test for errors in file 5470 return buf.length; 5471 } 5472 } 5473 5474 endGame: 5475 if (ferror(fps)) 5476 StdioException(); 5477 return buf.length; 5478 } 5479 5480 @system unittest 5481 { 5482 static import std.file; 5483 auto deleteme = testFilename(); 5484 scope(exit) std.file.remove(deleteme); 5485 5486 std.file.write(deleteme, "abcd\n0123456789abcde\n1234\n"); 5487 File f = File(deleteme, "rb"); 5488 5489 char[] ln = new char[2]; 5490 f.readln(ln); 5491 5492 assert(ln == "abcd\n"); 5493 char[] t = ln[0 .. 2]; 5494 t ~= 't'; 5495 assert(t == "abt"); 5496 // https://issues.dlang.org/show_bug.cgi?id=13856: ln stomped to "abtd" 5497 assert(ln == "abcd\n"); 5498 5499 // it can also stomp the array length 5500 ln = new char[4]; 5501 f.readln(ln); 5502 assert(ln == "0123456789abcde\n"); 5503 5504 char[100] buf; 5505 ln = buf[]; 5506 f.readln(ln); 5507 assert(ln == "1234\n"); 5508 assert(ln.ptr == buf.ptr); // avoid allocation, buffer is good enough 5509 } 5510 5511 /** Experimental network access via the File interface 5512 5513 Opens a TCP connection to the given host and port, then returns 5514 a File struct with read and write access through the same interface 5515 as any other file (meaning writef and the byLine ranges work!). 5516 5517 Authors: 5518 Adam D. Ruppe 5519 5520 Bugs: 5521 Only works on Linux 5522 */ 5523 version (linux) 5524 { 5525 File openNetwork(string host, ushort port) 5526 { 5527 import core.stdc..string : memcpy; 5528 import core.sys.posix.arpa.inet : htons; 5529 import core.sys.posix.netdb : gethostbyname; 5530 import core.sys.posix.netinet.in_ : sockaddr_in; 5531 static import core.sys.posix.unistd; 5532 static import sock = core.sys.posix.sys.socket; 5533 import std.conv : to; 5534 import std.exception : enforce; 5535 import std.internal.cstring : tempCString; 5536 5537 auto h = enforce( gethostbyname(host.tempCString()), 5538 new StdioException("gethostbyname")); 5539 5540 int s = sock.socket(sock.AF_INET, sock.SOCK_STREAM, 0); 5541 enforce(s != -1, new StdioException("socket")); 5542 5543 scope(failure) 5544 { 5545 // want to make sure it doesn't dangle if something throws. Upon 5546 // normal exit, the File struct's reference counting takes care of 5547 // closing, so we don't need to worry about success 5548 core.sys.posix.unistd.close(s); 5549 } 5550 5551 sockaddr_in addr; 5552 5553 addr.sin_family = sock.AF_INET; 5554 addr.sin_port = htons(port); 5555 memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length); 5556 5557 enforce(sock.connect(s, cast(sock.sockaddr*) &addr, addr.sizeof) != -1, 5558 new StdioException("Connect failed")); 5559 5560 File f; 5561 f.fdopen(s, "w+", host ~ ":" ~ to!string(port)); 5562 return f; 5563 } 5564 } 5565 5566 version (StdUnittest) private string testFilename(string file = __FILE__, size_t line = __LINE__) @safe 5567 { 5568 import std.conv : text; 5569 import std.file : deleteme; 5570 import std.path : baseName; 5571 5572 // filename intentionally contains non-ASCII (Russian) characters for 5573 // https://issues.dlang.org/show_bug.cgi?id=7648 5574 return text(deleteme, "-детка.", baseName(file), ".", line); 5575 }