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