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 : RefCounted, RefCountedAutoInitialize; 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 = RefCounted!(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 @property bool empty() 2194 { 2195 return impl.refCountedPayload.empty; 2196 } 2197 2198 @property Char[] front() 2199 { 2200 return impl.refCountedPayload.front; 2201 } 2202 2203 void popFront() 2204 { 2205 impl.refCountedPayload.popFront(); 2206 } 2207 2208 private: 2209 struct Impl 2210 { 2211 private: 2212 File file; 2213 Char[] line; 2214 Char[] buffer; 2215 Terminator terminator; 2216 KeepTerminator keepTerminator; 2217 bool haveLine; 2218 2219 public: 2220 this(File f, KeepTerminator kt, Terminator terminator) 2221 { 2222 file = f; 2223 this.terminator = terminator; 2224 keepTerminator = kt; 2225 } 2226 2227 // Range primitive implementations. 2228 @property bool empty() 2229 { 2230 needLine(); 2231 return line is null; 2232 } 2233 2234 @property Char[] front() 2235 { 2236 needLine(); 2237 return line; 2238 } 2239 2240 void popFront() 2241 { 2242 needLine(); 2243 haveLine = false; 2244 } 2245 2246 private: 2247 void needLine() 2248 { 2249 if (haveLine) 2250 return; 2251 import std.algorithm.searching : endsWith; 2252 assert(file.isOpen); 2253 line = buffer; 2254 file.readln(line, terminator); 2255 if (line.length > buffer.length) 2256 { 2257 buffer = line; 2258 } 2259 if (line.empty) 2260 { 2261 file.detach(); 2262 line = null; 2263 } 2264 else if (keepTerminator == No.keepTerminator 2265 && endsWith(line, terminator)) 2266 { 2267 static if (isScalarType!Terminator) 2268 enum tlen = 1; 2269 else static if (isArray!Terminator) 2270 { 2271 static assert( 2272 is(immutable ElementEncodingType!Terminator == immutable Char)); 2273 const tlen = terminator.length; 2274 } 2275 else 2276 static assert(false); 2277 line = line[0 .. line.length - tlen]; 2278 } 2279 haveLine = true; 2280 } 2281 } 2282 } 2283 2284 /** 2285 Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2286 set up to read from the file handle one line at a time. 2287 2288 The element type for the range will be `Char[]`. Range primitives 2289 may throw `StdioException` on I/O error. 2290 2291 Note: 2292 Each `front` will not persist after $(D 2293 popFront) is called, so the caller must copy its contents (e.g. by 2294 calling `to!string`) when retention is needed. If the caller needs 2295 to retain a copy of every line, use the $(LREF byLineCopy) function 2296 instead. 2297 2298 Params: 2299 Char = Character type for each line, defaulting to `char`. 2300 keepTerminator = Use `Yes.keepTerminator` to include the 2301 terminator at the end of each line. 2302 terminator = Line separator (`'\n'` by default). Use 2303 $(REF newline, std,ascii) for portability (unless the file was opened in 2304 text mode). 2305 2306 Example: 2307 ---- 2308 import std.algorithm, std.stdio, std.string; 2309 // Count words in a file using ranges. 2310 void main() 2311 { 2312 auto file = File("file.txt"); // Open for reading 2313 const wordCount = file.byLine() // Read lines 2314 .map!split // Split into words 2315 .map!(a => a.length) // Count words per line 2316 .sum(); // Total word count 2317 writeln(wordCount); 2318 } 2319 ---- 2320 2321 Example: 2322 ---- 2323 import std.range, std.stdio; 2324 // Read lines using foreach. 2325 void main() 2326 { 2327 auto file = File("file.txt"); // Open for reading 2328 auto range = file.byLine(); 2329 // Print first three lines 2330 foreach (line; range.take(3)) 2331 writeln(line); 2332 // Print remaining lines beginning with '#' 2333 foreach (line; range) 2334 { 2335 if (!line.empty && line[0] == '#') 2336 writeln(line); 2337 } 2338 } 2339 ---- 2340 Notice that neither example accesses the line data returned by 2341 `front` after the corresponding `popFront` call is made (because 2342 the contents may well have changed). 2343 */ 2344 auto byLine(Terminator = char, Char = char) 2345 (KeepTerminator keepTerminator = No.keepTerminator, 2346 Terminator terminator = '\n') 2347 if (isScalarType!Terminator) 2348 { 2349 return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); 2350 } 2351 2352 /// ditto 2353 auto byLine(Terminator, Char = char) 2354 (KeepTerminator keepTerminator, Terminator terminator) 2355 if (is(immutable ElementEncodingType!Terminator == immutable Char)) 2356 { 2357 return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); 2358 } 2359 2360 @system unittest 2361 { 2362 static import std.file; 2363 auto deleteme = testFilename(); 2364 std.file.write(deleteme, "hi"); 2365 scope(success) std.file.remove(deleteme); 2366 2367 import std.meta : AliasSeq; 2368 static foreach (T; AliasSeq!(char, wchar, dchar)) 2369 {{ 2370 auto blc = File(deleteme).byLine!(T, T); 2371 assert(blc.front == "hi"); 2372 // check front is cached 2373 assert(blc.front is blc.front); 2374 }} 2375 } 2376 2377 // https://issues.dlang.org/show_bug.cgi?id=19980 2378 @system unittest 2379 { 2380 static import std.file; 2381 auto deleteme = testFilename(); 2382 std.file.write(deleteme, "Line 1\nLine 2\nLine 3\n"); 2383 scope(success) std.file.remove(deleteme); 2384 2385 auto f = File(deleteme); 2386 f.byLine(); 2387 f.byLine(); 2388 assert(f.byLine().front == "Line 1"); 2389 } 2390 2391 private struct ByLineCopy(Char, Terminator) 2392 { 2393 private: 2394 import std.typecons : RefCounted, RefCountedAutoInitialize; 2395 2396 /* Ref-counting stops the source range's ByLineCopyImpl 2397 * from getting out of sync after the range is copied, e.g. 2398 * when accessing range.front, then using std.range.take, 2399 * then accessing range.front again. */ 2400 alias Impl = RefCounted!(ByLineCopyImpl!(Char, Terminator), 2401 RefCountedAutoInitialize.no); 2402 Impl impl; 2403 2404 public: 2405 this(File f, KeepTerminator kt, Terminator terminator) 2406 { 2407 impl = Impl(f, kt, terminator); 2408 } 2409 2410 @property bool empty() 2411 { 2412 return impl.refCountedPayload.empty; 2413 } 2414 2415 @property Char[] front() 2416 { 2417 return impl.refCountedPayload.front; 2418 } 2419 2420 void popFront() 2421 { 2422 impl.refCountedPayload.popFront(); 2423 } 2424 } 2425 2426 private struct ByLineCopyImpl(Char, Terminator) 2427 { 2428 ByLineImpl!(Unqual!Char, Terminator).Impl impl; 2429 bool gotFront; 2430 Char[] line; 2431 2432 public: 2433 this(File f, KeepTerminator kt, Terminator terminator) 2434 { 2435 impl = ByLineImpl!(Unqual!Char, Terminator).Impl(f, kt, terminator); 2436 } 2437 2438 @property bool empty() 2439 { 2440 return impl.empty; 2441 } 2442 2443 @property front() 2444 { 2445 if (!gotFront) 2446 { 2447 line = impl.front.dup; 2448 gotFront = true; 2449 } 2450 return line; 2451 } 2452 2453 void popFront() 2454 { 2455 impl.popFront(); 2456 gotFront = false; 2457 } 2458 } 2459 2460 /** 2461 Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2462 set up to read from the file handle one line 2463 at a time. Each line will be newly allocated. `front` will cache 2464 its value to allow repeated calls without unnecessary allocations. 2465 2466 Note: Due to caching byLineCopy can be more memory-efficient than 2467 `File.byLine.map!idup`. 2468 2469 The element type for the range will be `Char[]`. Range 2470 primitives may throw `StdioException` on I/O error. 2471 2472 Params: 2473 Char = Character type for each line, defaulting to $(D immutable char). 2474 keepTerminator = Use `Yes.keepTerminator` to include the 2475 terminator at the end of each line. 2476 terminator = Line separator (`'\n'` by default). Use 2477 $(REF newline, std,ascii) for portability (unless the file was opened in 2478 text mode). 2479 2480 Example: 2481 ---- 2482 import std.algorithm, std.array, std.stdio; 2483 // Print sorted lines of a file. 2484 void main() 2485 { 2486 auto sortedLines = File("file.txt") // Open for reading 2487 .byLineCopy() // Read persistent lines 2488 .array() // into an array 2489 .sort(); // then sort them 2490 foreach (line; sortedLines) 2491 writeln(line); 2492 } 2493 ---- 2494 See_Also: 2495 $(REF readText, std,file) 2496 */ 2497 auto byLineCopy(Terminator = char, Char = immutable char) 2498 (KeepTerminator keepTerminator = No.keepTerminator, 2499 Terminator terminator = '\n') 2500 if (isScalarType!Terminator) 2501 { 2502 return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); 2503 } 2504 2505 /// ditto 2506 auto byLineCopy(Terminator, Char = immutable char) 2507 (KeepTerminator keepTerminator, Terminator terminator) 2508 if (is(immutable ElementEncodingType!Terminator == immutable Char)) 2509 { 2510 return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); 2511 } 2512 2513 @safe unittest 2514 { 2515 static assert(is(typeof(File("").byLine.front) == char[])); 2516 static assert(is(typeof(File("").byLineCopy.front) == string)); 2517 static assert( 2518 is(typeof(File("").byLineCopy!(char, char).front) == char[])); 2519 } 2520 2521 @system unittest 2522 { 2523 import std.algorithm.comparison : equal; 2524 static import std.file; 2525 2526 scope(failure) printf("Failed test at line %d\n", __LINE__); 2527 auto deleteme = testFilename(); 2528 std.file.write(deleteme, ""); 2529 scope(success) std.file.remove(deleteme); 2530 2531 // Test empty file 2532 auto f = File(deleteme); 2533 foreach (line; f.byLine()) 2534 { 2535 assert(false); 2536 } 2537 f.detach(); 2538 assert(!f.isOpen); 2539 2540 void test(Terminator)(string txt, in string[] witness, 2541 KeepTerminator kt, Terminator term, bool popFirstLine = false) 2542 { 2543 import std.algorithm.sorting : sort; 2544 import std.array : array; 2545 import std.conv : text; 2546 import std.range.primitives : walkLength; 2547 2548 uint i; 2549 std.file.write(deleteme, txt); 2550 auto f = File(deleteme); 2551 scope(exit) 2552 { 2553 f.close(); 2554 assert(!f.isOpen); 2555 } 2556 auto lines = f.byLine(kt, term); 2557 if (popFirstLine) 2558 { 2559 lines.popFront(); 2560 i = 1; 2561 } 2562 assert(lines.empty || lines.front is lines.front); 2563 foreach (line; lines) 2564 { 2565 assert(line == witness[i++]); 2566 } 2567 assert(i == witness.length, text(i, " != ", witness.length)); 2568 2569 // https://issues.dlang.org/show_bug.cgi?id=11830 2570 auto walkedLength = File(deleteme).byLine(kt, term).walkLength; 2571 assert(walkedLength == witness.length, text(walkedLength, " != ", witness.length)); 2572 2573 // test persistent lines 2574 assert(File(deleteme).byLineCopy(kt, term).array.sort() == witness.dup.sort()); 2575 } 2576 2577 KeepTerminator kt = No.keepTerminator; 2578 test("", null, kt, '\n'); 2579 test("\n", [ "" ], kt, '\n'); 2580 test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n'); 2581 test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n', true); 2582 test("asd\ndef\nasdf\n", [ "asd", "def", "asdf" ], kt, '\n'); 2583 test("foo", [ "foo" ], kt, '\n', true); 2584 test("bob\r\nmarge\r\nsteve\r\n", ["bob", "marge", "steve"], 2585 kt, "\r\n"); 2586 test("sue\r", ["sue"], kt, '\r'); 2587 2588 kt = Yes.keepTerminator; 2589 test("", null, kt, '\n'); 2590 test("\n", [ "\n" ], kt, '\n'); 2591 test("asd\ndef\nasdf", [ "asd\n", "def\n", "asdf" ], kt, '\n'); 2592 test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n'); 2593 test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n', true); 2594 test("foo", [ "foo" ], kt, '\n'); 2595 test("bob\r\nmarge\r\nsteve\r\n", ["bob\r\n", "marge\r\n", "steve\r\n"], 2596 kt, "\r\n"); 2597 test("sue\r", ["sue\r"], kt, '\r'); 2598 } 2599 2600 @system unittest 2601 { 2602 import std.algorithm.comparison : equal; 2603 import std.range : drop, take; 2604 2605 version (Win64) 2606 { 2607 static import std.file; 2608 2609 /* the C function tmpfile doesn't seem to work, even when called from C */ 2610 auto deleteme = testFilename(); 2611 auto file = File(deleteme, "w+"); 2612 scope(success) std.file.remove(deleteme); 2613 } 2614 else version (CRuntime_Bionic) 2615 { 2616 static import std.file; 2617 2618 /* the C function tmpfile doesn't work when called from a shared 2619 library apk: 2620 https://code.google.com/p/android/issues/detail?id=66815 */ 2621 auto deleteme = testFilename(); 2622 auto file = File(deleteme, "w+"); 2623 scope(success) std.file.remove(deleteme); 2624 } 2625 else 2626 auto file = File.tmpfile(); 2627 file.write("1\n2\n3\n"); 2628 2629 // https://issues.dlang.org/show_bug.cgi?id=9599 2630 file.rewind(); 2631 File.ByLineImpl!(char, char) fbl = file.byLine(); 2632 auto fbl2 = fbl; 2633 assert(fbl.front == "1"); 2634 assert(fbl.front is fbl2.front); 2635 assert(fbl.take(1).equal(["1"])); 2636 assert(fbl.equal(["2", "3"])); 2637 assert(fbl.empty); 2638 assert(file.isOpen); // we still have a valid reference 2639 2640 file.rewind(); 2641 fbl = file.byLine(); 2642 assert(!fbl.drop(2).empty); 2643 assert(fbl.equal(["3"])); 2644 assert(fbl.empty); 2645 assert(file.isOpen); 2646 2647 file.detach(); 2648 assert(!file.isOpen); 2649 } 2650 2651 @system unittest 2652 { 2653 static import std.file; 2654 auto deleteme = testFilename(); 2655 std.file.write(deleteme, "hi"); 2656 scope(success) std.file.remove(deleteme); 2657 2658 auto blc = File(deleteme).byLineCopy; 2659 assert(!blc.empty); 2660 // check front is cached 2661 assert(blc.front is blc.front); 2662 } 2663 2664 /** 2665 Creates an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2666 set up to parse one line at a time from the file into a tuple. 2667 2668 Range primitives may throw `StdioException` on I/O error. 2669 2670 Params: 2671 format = tuple record $(REF_ALTTEXT _format, formattedRead, std, _format) 2672 2673 Returns: 2674 The input range set up to parse one line at a time into a record tuple. 2675 2676 See_Also: 2677 2678 It is similar to $(LREF byLine) and uses 2679 $(REF_ALTTEXT _format, formattedRead, std, _format) under the hood. 2680 */ 2681 template byRecord(Fields...) 2682 { 2683 auto byRecord(string format) 2684 { 2685 return ByRecordImpl!(Fields)(this, format); 2686 } 2687 } 2688 2689 /// 2690 @system unittest 2691 { 2692 static import std.file; 2693 import std.typecons : tuple; 2694 2695 // prepare test file 2696 auto testFile = std.file.deleteme(); 2697 scope(failure) printf("Failed test at line %d\n", __LINE__); 2698 std.file.write(testFile, "1 2\n4 1\n5 100"); 2699 scope(exit) std.file.remove(testFile); 2700 2701 File f = File(testFile); 2702 scope(exit) f.close(); 2703 2704 auto expected = [tuple(1, 2), tuple(4, 1), tuple(5, 100)]; 2705 uint i; 2706 foreach (e; f.byRecord!(int, int)("%s %s")) 2707 { 2708 assert(e == expected[i++]); 2709 } 2710 } 2711 2712 // Note: This was documented until 2013/08 2713 /* 2714 * Range that reads a chunk at a time. 2715 */ 2716 private struct ByChunkImpl 2717 { 2718 private: 2719 File file_; 2720 ubyte[] chunk_; 2721 2722 void prime() 2723 { 2724 chunk_ = file_.rawRead(chunk_); 2725 if (chunk_.length == 0) 2726 file_.detach(); 2727 } 2728 2729 public: 2730 this(File file, size_t size) 2731 { 2732 this(file, new ubyte[](size)); 2733 } 2734 2735 this(File file, ubyte[] buffer) 2736 { 2737 import std.exception : enforce; 2738 enforce(buffer.length, "size must be larger than 0"); 2739 file_ = file; 2740 chunk_ = buffer; 2741 prime(); 2742 } 2743 2744 // `ByChunk`'s input range primitive operations. 2745 @property nothrow 2746 bool empty() const 2747 { 2748 return !file_.isOpen; 2749 } 2750 2751 /// Ditto 2752 @property nothrow 2753 ubyte[] front() 2754 { 2755 version (assert) 2756 { 2757 import core.exception : RangeError; 2758 if (empty) 2759 throw new RangeError(); 2760 } 2761 return chunk_; 2762 } 2763 2764 /// Ditto 2765 void popFront() 2766 { 2767 version (assert) 2768 { 2769 import core.exception : RangeError; 2770 if (empty) 2771 throw new RangeError(); 2772 } 2773 prime(); 2774 } 2775 } 2776 2777 /** 2778 Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 2779 set up to read from the file handle a chunk at a time. 2780 2781 The element type for the range will be `ubyte[]`. Range primitives 2782 may throw `StdioException` on I/O error. 2783 2784 Example: 2785 --------- 2786 void main() 2787 { 2788 // Read standard input 4KB at a time 2789 foreach (ubyte[] buffer; stdin.byChunk(4096)) 2790 { 2791 ... use buffer ... 2792 } 2793 } 2794 --------- 2795 2796 The parameter may be a number (as shown in the example above) dictating the 2797 size of each chunk. Alternatively, `byChunk` accepts a 2798 user-provided buffer that it uses directly. 2799 2800 Example: 2801 --------- 2802 void main() 2803 { 2804 // Read standard input 4KB at a time 2805 foreach (ubyte[] buffer; stdin.byChunk(new ubyte[4096])) 2806 { 2807 ... use buffer ... 2808 } 2809 } 2810 --------- 2811 2812 In either case, the content of the buffer is reused across calls. That means 2813 `front` will not persist after `popFront` is called, so if retention is 2814 needed, the caller must copy its contents (e.g. by calling `buffer.dup`). 2815 2816 In the example above, `buffer.length` is 4096 for all iterations, except 2817 for the last one, in which case `buffer.length` may be less than 4096 (but 2818 always greater than zero). 2819 2820 With the mentioned limitations, `byChunk` works with any algorithm 2821 compatible with input ranges. 2822 2823 Example: 2824 --- 2825 // Efficient file copy, 1MB at a time. 2826 import std.algorithm, std.stdio; 2827 void main() 2828 { 2829 stdin.byChunk(1024 * 1024).copy(stdout.lockingTextWriter()); 2830 } 2831 --- 2832 2833 $(REF joiner, std,algorithm,iteration) can be used to join chunks together into 2834 a single range lazily. 2835 Example: 2836 --- 2837 import std.algorithm, std.stdio; 2838 void main() 2839 { 2840 //Range of ranges 2841 static assert(is(typeof(stdin.byChunk(4096).front) == ubyte[])); 2842 //Range of elements 2843 static assert(is(typeof(stdin.byChunk(4096).joiner.front) == ubyte)); 2844 } 2845 --- 2846 2847 Returns: A call to `byChunk` returns a range initialized with the `File` 2848 object and the appropriate buffer. 2849 2850 Throws: If the user-provided size is zero or the user-provided buffer 2851 is empty, throws an `Exception`. In case of an I/O error throws 2852 `StdioException`. 2853 */ 2854 auto byChunk(size_t chunkSize) 2855 { 2856 return ByChunkImpl(this, chunkSize); 2857 } 2858 /// Ditto 2859 auto byChunk(ubyte[] buffer) 2860 { 2861 return ByChunkImpl(this, buffer); 2862 } 2863 2864 @system unittest 2865 { 2866 static import std.file; 2867 2868 scope(failure) printf("Failed test at line %d\n", __LINE__); 2869 2870 auto deleteme = testFilename(); 2871 std.file.write(deleteme, "asd\ndef\nasdf"); 2872 2873 auto witness = ["asd\n", "def\n", "asdf" ]; 2874 auto f = File(deleteme); 2875 scope(exit) 2876 { 2877 f.close(); 2878 assert(!f.isOpen); 2879 std.file.remove(deleteme); 2880 } 2881 2882 uint i; 2883 foreach (chunk; f.byChunk(4)) 2884 assert(chunk == cast(ubyte[]) witness[i++]); 2885 2886 assert(i == witness.length); 2887 } 2888 2889 @system unittest 2890 { 2891 static import std.file; 2892 2893 scope(failure) printf("Failed test at line %d\n", __LINE__); 2894 2895 auto deleteme = testFilename(); 2896 std.file.write(deleteme, "asd\ndef\nasdf"); 2897 2898 auto witness = ["asd\n", "def\n", "asdf" ]; 2899 auto f = File(deleteme); 2900 scope(exit) 2901 { 2902 f.close(); 2903 assert(!f.isOpen); 2904 std.file.remove(deleteme); 2905 } 2906 2907 uint i; 2908 foreach (chunk; f.byChunk(new ubyte[4])) 2909 assert(chunk == cast(ubyte[]) witness[i++]); 2910 2911 assert(i == witness.length); 2912 } 2913 2914 // Note: This was documented until 2013/08 2915 /* 2916 `Range` that locks the file and allows fast writing to it. 2917 */ 2918 struct LockingTextWriter 2919 { 2920 private: 2921 import std.range.primitives : ElementType, isInfinite, isInputRange; 2922 // Access the FILE* handle through the 'file_' member 2923 // to keep the object alive through refcounting 2924 File file_; 2925 2926 // the unshared version of FILE* handle, extracted from the File object 2927 @property _iobuf* handle_() @trusted { return cast(_iobuf*) file_._p.handle; } 2928 2929 // the file's orientation (byte- or wide-oriented) 2930 int orientation_; 2931 2932 // Buffers for when we need to transcode. 2933 wchar highSurrogate = '\0'; // '\0' indicates empty 2934 void highSurrogateShouldBeEmpty() @safe 2935 { 2936 import std.utf : UTFException; 2937 if (highSurrogate != '\0') 2938 throw new UTFException("unpaired surrogate UTF-16 value"); 2939 } 2940 char[4] rbuf8; 2941 size_t rbuf8Filled = 0; 2942 public: 2943 2944 this(ref File f) @trusted 2945 { 2946 import std.exception : enforce; 2947 2948 enforce(f._p && f._p.handle, "Attempting to write to closed File"); 2949 file_ = f; 2950 FILE* fps = f._p.handle; 2951 2952 version (CRuntime_Microsoft) 2953 { 2954 // Microsoft doesn't implement fwide. Instead, there's the 2955 // concept of ANSI/UNICODE mode. fputc doesn't work in UNICODE 2956 // mode; fputwc has to be used. So that essentially means 2957 // "wide-oriented" for us. 2958 immutable int mode = _setmode(f.fileno, _O_TEXT); 2959 // Set some arbitrary mode to obtain the previous one. 2960 if (mode != -1) // _setmode() succeeded 2961 { 2962 _setmode(f.fileno, mode); // Restore previous mode. 2963 if (mode & (_O_WTEXT | _O_U16TEXT | _O_U8TEXT)) 2964 { 2965 orientation_ = 1; // wide 2966 } 2967 } 2968 } 2969 else 2970 { 2971 import core.stdc.wchar_ : fwide; 2972 orientation_ = fwide(fps, 0); 2973 } 2974 2975 _FLOCK(fps); 2976 } 2977 2978 ~this() @trusted 2979 { 2980 if (auto p = file_._p) 2981 { 2982 if (p.handle) _FUNLOCK(p.handle); 2983 } 2984 file_ = File.init; 2985 /* Destroy file_ before possibly throwing. Else it wouldn't be 2986 destroyed, and its reference count would be wrong. */ 2987 highSurrogateShouldBeEmpty(); 2988 } 2989 2990 this(this) @trusted 2991 { 2992 if (auto p = file_._p) 2993 { 2994 if (p.handle) _FLOCK(p.handle); 2995 } 2996 } 2997 2998 /// Range primitive implementations. 2999 void put(A)(scope A writeme) 3000 if ((isSomeChar!(ElementType!A) || 3001 is(ElementType!A : const(ubyte))) && 3002 isInputRange!A && 3003 !isInfinite!A) 3004 { 3005 import std.exception : errnoEnforce; 3006 3007 alias C = ElementEncodingType!A; 3008 static assert(!is(C == void)); 3009 static if (isSomeString!A && C.sizeof == 1 || is(A : const(ubyte)[])) 3010 { 3011 if (orientation_ <= 0) 3012 { 3013 //file.write(writeme); causes infinite recursion!!! 3014 //file.rawWrite(writeme); 3015 auto result = trustedFwrite(file_._p.handle, writeme); 3016 if (result != writeme.length) errnoEnforce(0); 3017 return; 3018 } 3019 } 3020 3021 // put each element in turn. 3022 foreach (c; writeme) 3023 { 3024 put(c); 3025 } 3026 } 3027 3028 /// ditto 3029 void put(C)(scope C c) @safe if (isSomeChar!C || is(C : const(ubyte))) 3030 { 3031 import std.utf : decodeFront, encode, stride; 3032 3033 static if (c.sizeof == 1) 3034 { 3035 highSurrogateShouldBeEmpty(); 3036 if (orientation_ <= 0) trustedFPUTC(c, handle_); 3037 else if (c <= 0x7F) trustedFPUTWC(c, handle_); 3038 else if (c >= 0b1100_0000) // start byte of multibyte sequence 3039 { 3040 rbuf8[0] = c; 3041 rbuf8Filled = 1; 3042 } 3043 else // continuation byte of multibyte sequence 3044 { 3045 rbuf8[rbuf8Filled] = c; 3046 ++rbuf8Filled; 3047 if (stride(rbuf8[]) == rbuf8Filled) // sequence is complete 3048 { 3049 char[] str = rbuf8[0 .. rbuf8Filled]; 3050 immutable dchar d = decodeFront(str); 3051 wchar_t[4 / wchar_t.sizeof] wbuf; 3052 immutable size = encode(wbuf, d); 3053 foreach (i; 0 .. size) 3054 trustedFPUTWC(wbuf[i], handle_); 3055 rbuf8Filled = 0; 3056 } 3057 } 3058 } 3059 else static if (c.sizeof == 2) 3060 { 3061 import std.utf : decode; 3062 3063 if (c <= 0x7F) 3064 { 3065 highSurrogateShouldBeEmpty(); 3066 if (orientation_ <= 0) trustedFPUTC(c, handle_); 3067 else trustedFPUTWC(c, handle_); 3068 } 3069 else if (0xD800 <= c && c <= 0xDBFF) // high surrogate 3070 { 3071 highSurrogateShouldBeEmpty(); 3072 highSurrogate = c; 3073 } 3074 else // standalone or low surrogate 3075 { 3076 dchar d = c; 3077 if (highSurrogate != '\0') 3078 { 3079 immutable wchar[2] rbuf = [highSurrogate, c]; 3080 size_t index = 0; 3081 d = decode(rbuf[], index); 3082 highSurrogate = 0; 3083 } 3084 if (orientation_ <= 0) 3085 { 3086 char[4] wbuf; 3087 immutable size = encode(wbuf, d); 3088 foreach (i; 0 .. size) 3089 trustedFPUTC(wbuf[i], handle_); 3090 } 3091 else 3092 { 3093 wchar_t[4 / wchar_t.sizeof] wbuf; 3094 immutable size = encode(wbuf, d); 3095 foreach (i; 0 .. size) 3096 trustedFPUTWC(wbuf[i], handle_); 3097 } 3098 rbuf8Filled = 0; 3099 } 3100 } 3101 else // 32-bit characters 3102 { 3103 import std.utf : encode; 3104 3105 highSurrogateShouldBeEmpty(); 3106 if (orientation_ <= 0) 3107 { 3108 if (c <= 0x7F) 3109 { 3110 trustedFPUTC(c, handle_); 3111 } 3112 else 3113 { 3114 char[4] buf = void; 3115 immutable len = encode(buf, c); 3116 foreach (i ; 0 .. len) 3117 trustedFPUTC(buf[i], handle_); 3118 } 3119 } 3120 else 3121 { 3122 version (Windows) 3123 { 3124 import std.utf : isValidDchar; 3125 3126 assert(isValidDchar(c)); 3127 if (c <= 0xFFFF) 3128 { 3129 trustedFPUTWC(cast(wchar_t) c, handle_); 3130 } 3131 else 3132 { 3133 trustedFPUTWC(cast(wchar_t) 3134 ((((c - 0x10000) >> 10) & 0x3FF) 3135 + 0xD800), handle_); 3136 trustedFPUTWC(cast(wchar_t) 3137 (((c - 0x10000) & 0x3FF) + 0xDC00), 3138 handle_); 3139 } 3140 } 3141 else version (Posix) 3142 { 3143 trustedFPUTWC(cast(wchar_t) c, handle_); 3144 } 3145 else 3146 { 3147 static assert(0); 3148 } 3149 } 3150 } 3151 } 3152 } 3153 3154 /** 3155 * Output range which locks the file when created, and unlocks the file when it goes 3156 * out of scope. 3157 * 3158 * Returns: An $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) 3159 * which accepts string types, `ubyte[]`, individual character types, and 3160 * individual `ubyte`s. 3161 * 3162 * Note: Writing either arrays of `char`s or `ubyte`s is faster than 3163 * writing each character individually from a range. For large amounts of data, 3164 * writing the contents in chunks using an intermediary array can result 3165 * in a speed increase. 3166 * 3167 * Throws: $(REF UTFException, std, utf) if the data given is a `char` range 3168 * and it contains malformed UTF data. 3169 * 3170 * See_Also: $(LREF byChunk) for an example. 3171 */ 3172 auto lockingTextWriter() @safe 3173 { 3174 return LockingTextWriter(this); 3175 } 3176 3177 // An output range which optionally locks the file and puts it into 3178 // binary mode (similar to rawWrite). Because it needs to restore 3179 // the file mode on destruction, it is RefCounted on Windows. 3180 struct BinaryWriterImpl(bool locking) 3181 { 3182 import std.traits : hasIndirections; 3183 private: 3184 // Access the FILE* handle through the 'file_' member 3185 // to keep the object alive through refcounting 3186 File file_; 3187 string name; 3188 3189 version (Windows) 3190 { 3191 fileno_t fd; 3192 int oldMode; 3193 } 3194 3195 public: 3196 // Don't use this, but `File.lockingBinaryWriter()` instead. 3197 // Must be public for RefCounted and emplace() in druntime. 3198 this(scope ref File f) 3199 { 3200 import std.exception : enforce; 3201 file_ = f; 3202 enforce(f._p && f._p.handle); 3203 name = f._name; 3204 FILE* fps = f._p.handle; 3205 static if (locking) 3206 _FLOCK(fps); 3207 3208 version (Windows) 3209 { 3210 .fflush(fps); // before changing translation mode 3211 fd = .fileno(fps); 3212 oldMode = ._setmode(fd, _O_BINARY); 3213 } 3214 } 3215 3216 ~this() 3217 { 3218 if (!file_._p || !file_._p.handle) 3219 return; 3220 3221 FILE* fps = file_._p.handle; 3222 3223 version (Windows) 3224 { 3225 .fflush(fps); // before restoring translation mode 3226 ._setmode(fd, oldMode); 3227 } 3228 3229 _FUNLOCK(fps); 3230 } 3231 3232 void rawWrite(T)(in T[] buffer) 3233 { 3234 import std.conv : text; 3235 import std.exception : errnoEnforce; 3236 3237 auto result = trustedFwrite(file_._p.handle, buffer); 3238 if (result == result.max) result = 0; 3239 errnoEnforce(result == buffer.length, 3240 text("Wrote ", result, " instead of ", buffer.length, 3241 " objects of type ", T.stringof, " to file `", 3242 name, "'")); 3243 } 3244 3245 version (Windows) 3246 { 3247 @disable this(this); 3248 } 3249 else 3250 { 3251 this(this) 3252 { 3253 if (auto p = file_._p) 3254 { 3255 if (p.handle) _FLOCK(p.handle); 3256 } 3257 } 3258 } 3259 3260 void put(T)(auto ref scope const T value) 3261 if (!hasIndirections!T && 3262 !isInputRange!T) 3263 { 3264 rawWrite((&value)[0 .. 1]); 3265 } 3266 3267 void put(T)(scope const(T)[] array) 3268 if (!hasIndirections!T && 3269 !isInputRange!T) 3270 { 3271 rawWrite(array); 3272 } 3273 } 3274 3275 /** Returns an output range that locks the file and allows fast writing to it. 3276 3277 Example: 3278 Produce a grayscale image of the $(LINK2 https://en.wikipedia.org/wiki/Mandelbrot_set, Mandelbrot set) 3279 in binary $(LINK2 https://en.wikipedia.org/wiki/Netpbm_format, Netpbm format) to standard output. 3280 --- 3281 import std.algorithm, std.complex, std.range, std.stdio; 3282 3283 void main() 3284 { 3285 enum size = 500; 3286 writef("P5\n%d %d %d\n", size, size, ubyte.max); 3287 3288 iota(-1, 3, 2.0/size).map!(y => 3289 iota(-1.5, 0.5, 2.0/size).map!(x => 3290 cast(ubyte)(1+ 3291 recurrence!((a, n) => x + y * complex(0, 1) + a[n-1]^^2)(complex(0)) 3292 .take(ubyte.max) 3293 .countUntil!(z => z.re^^2 + z.im^^2 > 4)) 3294 ) 3295 ) 3296 .copy(stdout.lockingBinaryWriter); 3297 } 3298 --- 3299 */ 3300 auto lockingBinaryWriter() 3301 { 3302 alias LockingBinaryWriterImpl = BinaryWriterImpl!true; 3303 3304 version (Windows) 3305 { 3306 import std.typecons : RefCounted; 3307 alias LockingBinaryWriter = RefCounted!LockingBinaryWriterImpl; 3308 } 3309 else 3310 alias LockingBinaryWriter = LockingBinaryWriterImpl; 3311 3312 return LockingBinaryWriter(this); 3313 } 3314 3315 @system unittest 3316 { 3317 import std.algorithm.mutation : reverse; 3318 import std.exception : collectException; 3319 static import std.file; 3320 import std.range : only, retro; 3321 import std.string : format; 3322 3323 auto deleteme = testFilename(); 3324 scope(exit) collectException(std.file.remove(deleteme)); 3325 3326 { 3327 auto writer = File(deleteme, "wb").lockingBinaryWriter(); 3328 auto input = File(deleteme, "rb"); 3329 3330 ubyte[1] byteIn = [42]; 3331 writer.rawWrite(byteIn); 3332 destroy(writer); 3333 3334 ubyte[1] byteOut = input.rawRead(new ubyte[1]); 3335 assert(byteIn[0] == byteOut[0]); 3336 } 3337 3338 auto output = File(deleteme, "wb"); 3339 auto writer = output.lockingBinaryWriter(); 3340 auto input = File(deleteme, "rb"); 3341 3342 T[] readExact(T)(T[] buf) 3343 { 3344 auto result = input.rawRead(buf); 3345 assert(result.length == buf.length, 3346 "Read %d out of %d bytes" 3347 .format(result.length, buf.length)); 3348 return result; 3349 } 3350 3351 // test raw values 3352 ubyte byteIn = 42; 3353 byteIn.only.copy(writer); output.flush(); 3354 ubyte byteOut = readExact(new ubyte[1])[0]; 3355 assert(byteIn == byteOut); 3356 3357 // test arrays 3358 ubyte[] bytesIn = [1, 2, 3, 4, 5]; 3359 bytesIn.copy(writer); output.flush(); 3360 ubyte[] bytesOut = readExact(new ubyte[bytesIn.length]); 3361 scope(failure) .writeln(bytesOut); 3362 assert(bytesIn == bytesOut); 3363 3364 // test ranges of values 3365 bytesIn.retro.copy(writer); output.flush(); 3366 bytesOut = readExact(bytesOut); 3367 bytesOut.reverse(); 3368 assert(bytesIn == bytesOut); 3369 3370 // test string 3371 "foobar".copy(writer); output.flush(); 3372 char[] charsOut = readExact(new char[6]); 3373 assert(charsOut == "foobar"); 3374 3375 // test ranges of arrays 3376 only("foo", "bar").copy(writer); output.flush(); 3377 charsOut = readExact(charsOut); 3378 assert(charsOut == "foobar"); 3379 3380 // test that we are writing arrays as is, 3381 // without UTF-8 transcoding 3382 "foo"d.copy(writer); output.flush(); 3383 dchar[] dcharsOut = readExact(new dchar[3]); 3384 assert(dcharsOut == "foo"); 3385 } 3386 3387 /** Returns the size of the file in bytes, ulong.max if file is not searchable or throws if the operation fails. 3388 Example: 3389 --- 3390 import std.stdio, std.file; 3391 3392 void main() 3393 { 3394 string deleteme = "delete.me"; 3395 auto file_handle = File(deleteme, "w"); 3396 file_handle.write("abc"); //create temporary file 3397 scope(exit) deleteme.remove; //remove temporary file at scope exit 3398 3399 assert(file_handle.size() == 3); //check if file size is 3 bytes 3400 } 3401 --- 3402 */ 3403 @property ulong size() @safe 3404 { 3405 import std.exception : collectException; 3406 3407 ulong pos = void; 3408 if (collectException(pos = tell)) return ulong.max; 3409 scope(exit) seek(pos); 3410 seek(0, SEEK_END); 3411 return tell; 3412 } 3413 } 3414 3415 @system unittest 3416 { 3417 @system struct SystemToString 3418 { 3419 string toString() 3420 { 3421 return "system"; 3422 } 3423 } 3424 3425 @trusted struct TrustedToString 3426 { 3427 string toString() 3428 { 3429 return "trusted"; 3430 } 3431 } 3432 3433 @safe struct SafeToString 3434 { 3435 string toString() 3436 { 3437 return "safe"; 3438 } 3439 } 3440 3441 @system void systemTests() 3442 { 3443 //system code can write to files/stdout with anything! 3444 if (false) 3445 { 3446 auto f = File(); 3447 3448 f.write("just a string"); 3449 f.write("string with arg: ", 47); 3450 f.write(SystemToString()); 3451 f.write(TrustedToString()); 3452 f.write(SafeToString()); 3453 3454 write("just a string"); 3455 write("string with arg: ", 47); 3456 write(SystemToString()); 3457 write(TrustedToString()); 3458 write(SafeToString()); 3459 3460 f.writeln("just a string"); 3461 f.writeln("string with arg: ", 47); 3462 f.writeln(SystemToString()); 3463 f.writeln(TrustedToString()); 3464 f.writeln(SafeToString()); 3465 3466 writeln("just a string"); 3467 writeln("string with arg: ", 47); 3468 writeln(SystemToString()); 3469 writeln(TrustedToString()); 3470 writeln(SafeToString()); 3471 3472 f.writef("string with arg: %s", 47); 3473 f.writef("%s", SystemToString()); 3474 f.writef("%s", TrustedToString()); 3475 f.writef("%s", SafeToString()); 3476 3477 writef("string with arg: %s", 47); 3478 writef("%s", SystemToString()); 3479 writef("%s", TrustedToString()); 3480 writef("%s", SafeToString()); 3481 3482 f.writefln("string with arg: %s", 47); 3483 f.writefln("%s", SystemToString()); 3484 f.writefln("%s", TrustedToString()); 3485 f.writefln("%s", SafeToString()); 3486 3487 writefln("string with arg: %s", 47); 3488 writefln("%s", SystemToString()); 3489 writefln("%s", TrustedToString()); 3490 writefln("%s", SafeToString()); 3491 } 3492 } 3493 3494 @safe void safeTests() 3495 { 3496 auto f = File(); 3497 3498 //safe code can write to files only with @safe and @trusted code... 3499 if (false) 3500 { 3501 f.write("just a string"); 3502 f.write("string with arg: ", 47); 3503 f.write(TrustedToString()); 3504 f.write(SafeToString()); 3505 3506 write("just a string"); 3507 write("string with arg: ", 47); 3508 write(TrustedToString()); 3509 write(SafeToString()); 3510 3511 f.writeln("just a string"); 3512 f.writeln("string with arg: ", 47); 3513 f.writeln(TrustedToString()); 3514 f.writeln(SafeToString()); 3515 3516 writeln("just a string"); 3517 writeln("string with arg: ", 47); 3518 writeln(TrustedToString()); 3519 writeln(SafeToString()); 3520 3521 f.writef("string with arg: %s", 47); 3522 f.writef("%s", TrustedToString()); 3523 f.writef("%s", SafeToString()); 3524 3525 writef("string with arg: %s", 47); 3526 writef("%s", TrustedToString()); 3527 writef("%s", SafeToString()); 3528 3529 f.writefln("string with arg: %s", 47); 3530 f.writefln("%s", TrustedToString()); 3531 f.writefln("%s", SafeToString()); 3532 3533 writefln("string with arg: %s", 47); 3534 writefln("%s", TrustedToString()); 3535 writefln("%s", SafeToString()); 3536 } 3537 3538 static assert(!__traits(compiles, f.write(SystemToString().toString()))); 3539 static assert(!__traits(compiles, f.writeln(SystemToString()))); 3540 static assert(!__traits(compiles, f.writef("%s", SystemToString()))); 3541 static assert(!__traits(compiles, f.writefln("%s", SystemToString()))); 3542 3543 static assert(!__traits(compiles, write(SystemToString().toString()))); 3544 static assert(!__traits(compiles, writeln(SystemToString()))); 3545 static assert(!__traits(compiles, writef("%s", SystemToString()))); 3546 static assert(!__traits(compiles, writefln("%s", SystemToString()))); 3547 } 3548 3549 systemTests(); 3550 safeTests(); 3551 } 3552 3553 @safe unittest 3554 { 3555 import std.exception : collectException; 3556 static import std.file; 3557 3558 auto deleteme = testFilename(); 3559 scope(exit) collectException(std.file.remove(deleteme)); 3560 std.file.write(deleteme, "1 2 3"); 3561 auto f = File(deleteme); 3562 assert(f.size == 5); 3563 assert(f.tell == 0); 3564 } 3565 3566 @safe unittest 3567 { 3568 static import std.file; 3569 import std.range : chain, only, repeat; 3570 import std.range.primitives : isOutputRange; 3571 3572 auto deleteme = testFilename(); 3573 scope(exit) std.file.remove(deleteme); 3574 3575 { 3576 auto writer = File(deleteme, "w").lockingTextWriter(); 3577 static assert(isOutputRange!(typeof(writer), dchar)); 3578 writer.put("日本語"); 3579 writer.put("日本語"w); 3580 writer.put("日本語"d); 3581 writer.put('日'); 3582 writer.put(chain(only('本'), only('語'))); 3583 // https://issues.dlang.org/show_bug.cgi?id=11945 3584 writer.put(repeat('#', 12)); 3585 // https://issues.dlang.org/show_bug.cgi?id=17229 3586 writer.put(cast(immutable(ubyte)[])"日本語"); 3587 } 3588 assert(File(deleteme).readln() == "日本語日本語日本語日本語############日本語"); 3589 } 3590 3591 @safe unittest // wchar -> char 3592 { 3593 static import std.file; 3594 import std.exception : assertThrown; 3595 import std.utf : UTFException; 3596 3597 auto deleteme = testFilename(); 3598 scope(exit) std.file.remove(deleteme); 3599 3600 { 3601 auto writer = File(deleteme, "w").lockingTextWriter(); 3602 writer.put("\U0001F608"w); 3603 } 3604 assert(std.file.readText!string(deleteme) == "\U0001F608"); 3605 3606 // Test invalid input: unpaired high surrogate 3607 { 3608 immutable wchar surr = "\U0001F608"w[0]; 3609 auto f = File(deleteme, "w"); 3610 assertThrown!UTFException(() { 3611 auto writer = f.lockingTextWriter(); 3612 writer.put('x'); 3613 writer.put(surr); 3614 assertThrown!UTFException(writer.put(char('y'))); 3615 assertThrown!UTFException(writer.put(wchar('y'))); 3616 assertThrown!UTFException(writer.put(dchar('y'))); 3617 assertThrown!UTFException(writer.put(surr)); 3618 // First `surr` is still unpaired at this point. `writer` gets 3619 // destroyed now, and the destructor throws a UTFException for 3620 // the unpaired surrogate. 3621 } ()); 3622 } 3623 assert(std.file.readText!string(deleteme) == "x"); 3624 3625 // Test invalid input: unpaired low surrogate 3626 { 3627 immutable wchar surr = "\U0001F608"w[1]; 3628 auto writer = File(deleteme, "w").lockingTextWriter(); 3629 assertThrown!UTFException(writer.put(surr)); 3630 writer.put('y'); 3631 assertThrown!UTFException(writer.put(surr)); 3632 } 3633 assert(std.file.readText!string(deleteme) == "y"); 3634 } 3635 3636 @safe unittest // https://issues.dlang.org/show_bug.cgi?id=18801 3637 { 3638 static import std.file; 3639 import std.string : stripLeft; 3640 3641 auto deleteme = testFilename(); 3642 scope(exit) std.file.remove(deleteme); 3643 3644 { 3645 auto writer = File(deleteme, "w,ccs=UTF-8").lockingTextWriter(); 3646 writer.put("foo"); 3647 } 3648 assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foo"); 3649 3650 { 3651 auto writer = File(deleteme, "a,ccs=UTF-8").lockingTextWriter(); 3652 writer.put("bar"); 3653 } 3654 assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foobar"); 3655 } 3656 @safe unittest // char/wchar -> wchar_t 3657 { 3658 import core.stdc.locale : LC_CTYPE, setlocale; 3659 import core.stdc.wchar_ : fwide; 3660 import core.stdc.string : strlen; 3661 import std.algorithm.searching : any, endsWith; 3662 import std.conv : text; 3663 import std.meta : AliasSeq; 3664 import std.string : fromStringz, stripLeft; 3665 static import std.file; 3666 auto deleteme = testFilename(); 3667 scope(exit) std.file.remove(deleteme); 3668 const char* oldCt = () @trusted { 3669 const(char)* p = setlocale(LC_CTYPE, null); 3670 // Subsequent calls to `setlocale` might invalidate this return value, 3671 // so duplicate it. 3672 // See: https://github.com/dlang/phobos/pull/7660 3673 return p ? p[0 .. strlen(p) + 1].idup.ptr : null; 3674 }(); 3675 const utf8 = ["en_US.UTF-8", "C.UTF-8", ".65001"].any!((loc) @trusted { 3676 return setlocale(LC_CTYPE, loc.ptr).fromStringz.endsWith(loc); 3677 }); 3678 scope(exit) () @trusted { setlocale(LC_CTYPE, oldCt); } (); 3679 alias strs = AliasSeq!("xä\U0001F607", "yö\U0001F608"w); 3680 { 3681 auto f = File(deleteme, "w"); 3682 version (CRuntime_Microsoft) 3683 { 3684 () @trusted { _setmode(fileno(f.getFP()), _O_U8TEXT); } (); 3685 } 3686 else 3687 { 3688 assert(fwide(f.getFP(), 1) == 1); 3689 } 3690 auto writer = f.lockingTextWriter(); 3691 assert(writer.orientation_ == 1); 3692 static foreach (s; strs) writer.put(s); 3693 } 3694 assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == 3695 text(strs)); 3696 } 3697 @safe unittest // https://issues.dlang.org/show_bug.cgi?id=18789 3698 { 3699 static import std.file; 3700 auto deleteme = testFilename(); 3701 scope(exit) std.file.remove(deleteme); 3702 // converting to char 3703 { 3704 auto f = File(deleteme, "w"); 3705 f.writeln("\U0001F608"w); // UTFException 3706 } 3707 // converting to wchar_t 3708 { 3709 auto f = File(deleteme, "w,ccs=UTF-16LE"); 3710 // from char 3711 f.writeln("ö"); // writes garbage 3712 f.writeln("\U0001F608"); // ditto 3713 // from wchar 3714 f.writeln("\U0001F608"w); // leads to ErrnoException 3715 } 3716 } 3717 3718 @safe unittest 3719 { 3720 import std.exception : collectException; 3721 auto e = collectException({ File f; f.writeln("Hello!"); }()); 3722 assert(e && e.msg == "Attempting to write to closed File"); 3723 } 3724 3725 @safe unittest // https://issues.dlang.org/show_bug.cgi?id=21592 3726 { 3727 import std.exception : collectException; 3728 import std.utf : UTFException; 3729 static import std.file; 3730 auto deleteme = testFilename(); 3731 scope(exit) std.file.remove(deleteme); 3732 auto f = File(deleteme, "w"); 3733 auto e = collectException!UTFException(f.writeln(wchar(0xD801))); 3734 assert(e.next is null); 3735 } 3736 3737 version (StdStressTest) 3738 { 3739 // https://issues.dlang.org/show_bug.cgi?id=15768 3740 @system unittest 3741 { 3742 import std.parallelism : parallel; 3743 import std.range : iota; 3744 3745 auto deleteme = testFilename(); 3746 stderr = File(deleteme, "w"); 3747 3748 foreach (t; 1_000_000.iota.parallel) 3749 { 3750 stderr.write("aaa"); 3751 } 3752 } 3753 } 3754 3755 /// Used to specify the lock type for `File.lock` and `File.tryLock`. 3756 enum LockType 3757 { 3758 /** 3759 * Specifies a _read (shared) lock. A _read lock denies all processes 3760 * write access to the specified region of the file, including the 3761 * process that first locks the region. All processes can _read the 3762 * locked region. Multiple simultaneous _read locks are allowed, as 3763 * long as there are no exclusive locks. 3764 */ 3765 read, 3766 3767 /** 3768 * Specifies a read/write (exclusive) lock. A read/write lock denies all 3769 * other processes both read and write access to the locked file region. 3770 * If a segment has an exclusive lock, it may not have any shared locks 3771 * or other exclusive locks. 3772 */ 3773 readWrite 3774 } 3775 3776 struct LockingTextReader 3777 { 3778 private File _f; 3779 private char _front; 3780 private bool _hasChar; 3781 3782 this(File f) 3783 { 3784 import std.exception : enforce; 3785 enforce(f.isOpen, "LockingTextReader: File must be open"); 3786 _f = f; 3787 _FLOCK(_f._p.handle); 3788 } 3789 3790 this(this) 3791 { 3792 _FLOCK(_f._p.handle); 3793 } 3794 3795 ~this() 3796 { 3797 if (_hasChar) 3798 ungetc(_front, cast(FILE*)_f._p.handle); 3799 3800 // File locking has its own reference count 3801 if (_f.isOpen) _FUNLOCK(_f._p.handle); 3802 } 3803 3804 void opAssign(LockingTextReader r) 3805 { 3806 import std.algorithm.mutation : swap; 3807 swap(this, r); 3808 } 3809 3810 @property bool empty() 3811 { 3812 if (!_hasChar) 3813 { 3814 if (!_f.isOpen || _f.eof) 3815 return true; 3816 immutable int c = _FGETC(cast(_iobuf*) _f._p.handle); 3817 if (c == EOF) 3818 { 3819 .destroy(_f); 3820 return true; 3821 } 3822 _front = cast(char) c; 3823 _hasChar = true; 3824 } 3825 return false; 3826 } 3827 3828 @property char front() 3829 { 3830 if (!_hasChar) 3831 { 3832 version (assert) 3833 { 3834 import core.exception : RangeError; 3835 if (empty) 3836 throw new RangeError(); 3837 } 3838 else 3839 { 3840 empty; 3841 } 3842 } 3843 return _front; 3844 } 3845 3846 void popFront() 3847 { 3848 if (!_hasChar) 3849 empty; 3850 _hasChar = false; 3851 } 3852 } 3853 3854 @system unittest 3855 { 3856 // @system due to readf 3857 static import std.file; 3858 import std.range.primitives : isInputRange; 3859 3860 static assert(isInputRange!LockingTextReader); 3861 auto deleteme = testFilename(); 3862 std.file.write(deleteme, "1 2 3"); 3863 scope(exit) std.file.remove(deleteme); 3864 int x; 3865 auto f = File(deleteme); 3866 f.readf("%s ", &x); 3867 assert(x == 1); 3868 f.readf("%d ", &x); 3869 assert(x == 2); 3870 f.readf("%d ", &x); 3871 assert(x == 3); 3872 } 3873 3874 // https://issues.dlang.org/show_bug.cgi?id=13686 3875 @system unittest 3876 { 3877 import std.algorithm.comparison : equal; 3878 static import std.file; 3879 import std.utf : byDchar; 3880 3881 auto deleteme = testFilename(); 3882 std.file.write(deleteme, "Тест"); 3883 scope(exit) std.file.remove(deleteme); 3884 3885 string s; 3886 File(deleteme).readf("%s", &s); 3887 assert(s == "Тест"); 3888 3889 auto ltr = LockingTextReader(File(deleteme)).byDchar; 3890 assert(equal(ltr, "Тест".byDchar)); 3891 } 3892 3893 // https://issues.dlang.org/show_bug.cgi?id=12320 3894 @system unittest 3895 { 3896 static import std.file; 3897 auto deleteme = testFilename(); 3898 std.file.write(deleteme, "ab"); 3899 scope(exit) std.file.remove(deleteme); 3900 auto ltr = LockingTextReader(File(deleteme)); 3901 assert(ltr.front == 'a'); 3902 ltr.popFront(); 3903 assert(ltr.front == 'b'); 3904 ltr.popFront(); 3905 assert(ltr.empty); 3906 } 3907 3908 // https://issues.dlang.org/show_bug.cgi?id=14861 3909 @system unittest 3910 { 3911 // @system due to readf 3912 static import std.file; 3913 auto deleteme = testFilename(); 3914 File fw = File(deleteme, "w"); 3915 for (int i; i != 5000; i++) 3916 fw.writeln(i, ";", "Иванов;Пётр;Петрович"); 3917 fw.close(); 3918 scope(exit) std.file.remove(deleteme); 3919 // Test read 3920 File fr = File(deleteme, "r"); 3921 scope (exit) fr.close(); 3922 int nom; string fam, nam, ot; 3923 // Error format read 3924 while (!fr.eof) 3925 fr.readf("%s;%s;%s;%s\n", &nom, &fam, &nam, &ot); 3926 } 3927 3928 /** 3929 * Indicates whether `T` is a file handle, i.e. the type 3930 * is implicitly convertable to $(LREF File) or a pointer to a 3931 * $(REF FILE, core,stdc,stdio). 3932 * 3933 * Returns: 3934 * `true` if `T` is a file handle, `false` otherwise. 3935 */ 3936 template isFileHandle(T) 3937 { 3938 enum isFileHandle = is(T : FILE*) || 3939 is(T : File); 3940 } 3941 3942 /// 3943 @safe unittest 3944 { 3945 static assert(isFileHandle!(FILE*)); 3946 static assert(isFileHandle!(File)); 3947 } 3948 3949 /** 3950 * Property used by writeln/etc. so it can infer @safe since stdout is __gshared 3951 */ 3952 private @property File trustedStdout() @trusted 3953 { 3954 return stdout; 3955 } 3956 3957 /*********************************** 3958 Writes its arguments in text format to standard output (without a trailing newline). 3959 3960 Params: 3961 args = the items to write to `stdout` 3962 3963 Throws: In case of an I/O error, throws an `StdioException`. 3964 3965 Example: 3966 Reads `stdin` and writes it to `stdout` with an argument 3967 counter. 3968 --- 3969 import std.stdio; 3970 3971 void main() 3972 { 3973 string line; 3974 3975 for (size_t count = 0; (line = readln) !is null; count++) 3976 { 3977 write("Input ", count, ": ", line, "\n"); 3978 } 3979 } 3980 --- 3981 */ 3982 void write(T...)(T args) 3983 if (!is(T[0] : File)) 3984 { 3985 trustedStdout.write(args); 3986 } 3987 3988 @system unittest 3989 { 3990 static import std.file; 3991 3992 scope(failure) printf("Failed test at line %d\n", __LINE__); 3993 void[] buf; 3994 if (false) write(buf); 3995 // test write 3996 auto deleteme = testFilename(); 3997 auto f = File(deleteme, "w"); 3998 f.write("Hello, ", "world number ", 42, "!"); 3999 f.close(); 4000 scope(exit) { std.file.remove(deleteme); } 4001 assert(cast(char[]) std.file.read(deleteme) == "Hello, world number 42!"); 4002 } 4003 4004 /*********************************** 4005 * Equivalent to `write(args, '\n')`. Calling `writeln` without 4006 * arguments is valid and just prints a newline to the standard 4007 * output. 4008 * 4009 * Params: 4010 * args = the items to write to `stdout` 4011 * 4012 * Throws: 4013 * In case of an I/O error, throws an $(LREF StdioException). 4014 * Example: 4015 * Reads `stdin` and writes it to `stdout` with an argument 4016 * counter. 4017 --- 4018 import std.stdio; 4019 4020 void main() 4021 { 4022 string line; 4023 4024 for (size_t count = 0; (line = readln) !is null; count++) 4025 { 4026 writeln("Input ", count, ": ", line); 4027 } 4028 } 4029 --- 4030 */ 4031 void writeln(T...)(T args) 4032 { 4033 static if (T.length == 0) 4034 { 4035 import std.exception : enforce; 4036 4037 enforce(fputc('\n', .trustedStdout._p.handle) != EOF, "fputc failed"); 4038 } 4039 else static if (T.length == 1 && 4040 is(T[0] : const(char)[]) && 4041 (is(T[0] == U[], U) || __traits(isStaticArray, T[0]))) 4042 { 4043 // Specialization for strings - a very frequent case 4044 auto w = .trustedStdout.lockingTextWriter(); 4045 4046 static if (__traits(isStaticArray, T[0])) 4047 { 4048 w.put(args[0][]); 4049 } 4050 else 4051 { 4052 w.put(args[0]); 4053 } 4054 w.put('\n'); 4055 } 4056 else 4057 { 4058 // Most general instance 4059 trustedStdout.write(args, '\n'); 4060 } 4061 } 4062 4063 @safe unittest 4064 { 4065 // Just make sure the call compiles 4066 if (false) writeln(); 4067 4068 if (false) writeln("wyda"); 4069 4070 // https://issues.dlang.org/show_bug.cgi?id=8040 4071 if (false) writeln(null); 4072 if (false) writeln(">", null, "<"); 4073 4074 // https://issues.dlang.org/show_bug.cgi?id=14041 4075 if (false) 4076 { 4077 char[8] a; 4078 writeln(a); 4079 immutable b = a; 4080 b.writeln; 4081 const c = a[]; 4082 c.writeln; 4083 } 4084 } 4085 4086 @system unittest 4087 { 4088 static import std.file; 4089 4090 scope(failure) printf("Failed test at line %d\n", __LINE__); 4091 4092 // test writeln 4093 auto deleteme = testFilename(); 4094 auto f = File(deleteme, "w"); 4095 scope(exit) { std.file.remove(deleteme); } 4096 f.writeln("Hello, ", "world number ", 42, "!"); 4097 f.close(); 4098 version (Windows) 4099 assert(cast(char[]) std.file.read(deleteme) == 4100 "Hello, world number 42!\r\n"); 4101 else 4102 assert(cast(char[]) std.file.read(deleteme) == 4103 "Hello, world number 42!\n"); 4104 4105 // test writeln on stdout 4106 auto saveStdout = stdout; 4107 scope(exit) stdout = saveStdout; 4108 stdout.open(deleteme, "w"); 4109 writeln("Hello, ", "world number ", 42, "!"); 4110 stdout.close(); 4111 version (Windows) 4112 assert(cast(char[]) std.file.read(deleteme) == 4113 "Hello, world number 42!\r\n"); 4114 else 4115 assert(cast(char[]) std.file.read(deleteme) == 4116 "Hello, world number 42!\n"); 4117 4118 stdout.open(deleteme, "w"); 4119 writeln("Hello!"c); 4120 writeln("Hello!"w); // https://issues.dlang.org/show_bug.cgi?id=8386 4121 writeln("Hello!"d); // https://issues.dlang.org/show_bug.cgi?id=8386 4122 writeln("embedded\0null"c); // https://issues.dlang.org/show_bug.cgi?id=8730 4123 stdout.close(); 4124 version (Windows) 4125 assert(cast(char[]) std.file.read(deleteme) == 4126 "Hello!\r\nHello!\r\nHello!\r\nembedded\0null\r\n"); 4127 else 4128 assert(cast(char[]) std.file.read(deleteme) == 4129 "Hello!\nHello!\nHello!\nembedded\0null\n"); 4130 } 4131 4132 @system unittest 4133 { 4134 static import std.file; 4135 4136 auto deleteme = testFilename(); 4137 auto f = File(deleteme, "w"); 4138 scope(exit) { std.file.remove(deleteme); } 4139 4140 enum EI : int { A, B } 4141 enum ED : double { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle 4142 enum EC : char { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle 4143 enum ES : string { A = "aaa", B = "bbb" } 4144 4145 f.writeln(EI.A); // false, but A on 2.058 4146 f.writeln(EI.B); // true, but B on 2.058 4147 4148 f.writeln(ED.A); // A 4149 f.writeln(ED.B); // B 4150 4151 f.writeln(EC.A); // A 4152 f.writeln(EC.B); // B 4153 4154 f.writeln(ES.A); // A 4155 f.writeln(ES.B); // B 4156 4157 f.close(); 4158 version (Windows) 4159 assert(cast(char[]) std.file.read(deleteme) == 4160 "A\r\nB\r\nA\r\nB\r\nA\r\nB\r\nA\r\nB\r\n"); 4161 else 4162 assert(cast(char[]) std.file.read(deleteme) == 4163 "A\nB\nA\nB\nA\nB\nA\nB\n"); 4164 } 4165 4166 @system unittest 4167 { 4168 static auto useInit(T)(T ltw) 4169 { 4170 T val; 4171 val = ltw; 4172 val = T.init; 4173 return val; 4174 } 4175 useInit(stdout.lockingTextWriter()); 4176 } 4177 4178 @system unittest 4179 { 4180 // https://issues.dlang.org/show_bug.cgi?id=21920 4181 void function(string) printer = &writeln!string; 4182 if (false) printer("Hello"); 4183 } 4184 4185 4186 /*********************************** 4187 Writes formatted data to standard output (without a trailing newline). 4188 4189 Params: 4190 fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). 4191 When passed as a compile-time argument, the string will be statically checked 4192 against the argument types passed. 4193 args = Items to write. 4194 4195 Note: In older versions of Phobos, it used to be possible to write: 4196 4197 ------ 4198 writef(stderr, "%s", "message"); 4199 ------ 4200 4201 to print a message to `stderr`. This syntax is no longer supported, and has 4202 been superceded by: 4203 4204 ------ 4205 stderr.writef("%s", "message"); 4206 ------ 4207 4208 */ 4209 void writef(alias fmt, A...)(A args) 4210 if (isSomeString!(typeof(fmt))) 4211 { 4212 import std.format : checkFormatException; 4213 4214 alias e = checkFormatException!(fmt, A); 4215 static assert(!e, e); 4216 return .writef(fmt, args); 4217 } 4218 4219 /// ditto 4220 void writef(Char, A...)(in Char[] fmt, A args) 4221 { 4222 trustedStdout.writef(fmt, args); 4223 } 4224 4225 @system unittest 4226 { 4227 static import std.file; 4228 4229 scope(failure) printf("Failed test at line %d\n", __LINE__); 4230 4231 // test writef 4232 auto deleteme = testFilename(); 4233 auto f = File(deleteme, "w"); 4234 scope(exit) { std.file.remove(deleteme); } 4235 f.writef!"Hello, %s world number %s!"("nice", 42); 4236 f.close(); 4237 assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); 4238 // test write on stdout 4239 auto saveStdout = stdout; 4240 scope(exit) stdout = saveStdout; 4241 stdout.open(deleteme, "w"); 4242 writef!"Hello, %s world number %s!"("nice", 42); 4243 stdout.close(); 4244 assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); 4245 } 4246 4247 /*********************************** 4248 * Equivalent to $(D writef(fmt, args, '\n')). 4249 */ 4250 void writefln(alias fmt, A...)(A args) 4251 if (isSomeString!(typeof(fmt))) 4252 { 4253 import std.format : checkFormatException; 4254 4255 alias e = checkFormatException!(fmt, A); 4256 static assert(!e, e); 4257 return .writefln(fmt, args); 4258 } 4259 4260 /// ditto 4261 void writefln(Char, A...)(in Char[] fmt, A args) 4262 { 4263 trustedStdout.writefln(fmt, args); 4264 } 4265 4266 @system unittest 4267 { 4268 static import std.file; 4269 4270 scope(failure) printf("Failed test at line %d\n", __LINE__); 4271 4272 // test File.writefln 4273 auto deleteme = testFilename(); 4274 auto f = File(deleteme, "w"); 4275 scope(exit) { std.file.remove(deleteme); } 4276 f.writefln!"Hello, %s world number %s!"("nice", 42); 4277 f.close(); 4278 version (Windows) 4279 assert(cast(char[]) std.file.read(deleteme) == 4280 "Hello, nice world number 42!\r\n"); 4281 else 4282 assert(cast(char[]) std.file.read(deleteme) == 4283 "Hello, nice world number 42!\n", 4284 cast(char[]) std.file.read(deleteme)); 4285 4286 // test writefln 4287 auto saveStdout = stdout; 4288 scope(exit) stdout = saveStdout; 4289 stdout.open(deleteme, "w"); 4290 writefln!"Hello, %s world number %s!"("nice", 42); 4291 stdout.close(); 4292 version (Windows) 4293 assert(cast(char[]) std.file.read(deleteme) == 4294 "Hello, nice world number 42!\r\n"); 4295 else 4296 assert(cast(char[]) std.file.read(deleteme) == 4297 "Hello, nice world number 42!\n"); 4298 } 4299 4300 /** 4301 * Reads formatted data from `stdin` using $(REF formattedRead, std,_format). 4302 * Params: 4303 * format = The $(REF_ALTTEXT format string, formattedWrite, std, _format). 4304 * When passed as a compile-time argument, the string will be statically checked 4305 * against the argument types passed. 4306 * args = Items to be read. 4307 * Returns: 4308 * Same as `formattedRead`: The number of variables filled. If the input range `r` ends early, 4309 * this number will be less than the number of variables provided. 4310 * Example: 4311 ---- 4312 // test.d 4313 void main() 4314 { 4315 import std.stdio; 4316 foreach (_; 0 .. 3) 4317 { 4318 int a; 4319 readf!" %d"(a); 4320 writeln(++a); 4321 } 4322 } 4323 ---- 4324 $(CONSOLE 4325 % echo "1 2 3" | rdmd test.d 4326 2 4327 3 4328 4 4329 ) 4330 */ 4331 uint readf(alias format, A...)(auto ref A args) 4332 if (isSomeString!(typeof(format))) 4333 { 4334 import std.format : checkFormatException; 4335 4336 alias e = checkFormatException!(format, A); 4337 static assert(!e, e); 4338 return .readf(format, args); 4339 } 4340 4341 /// ditto 4342 uint readf(A...)(scope const(char)[] format, auto ref A args) 4343 { 4344 return stdin.readf(format, args); 4345 } 4346 4347 @system unittest 4348 { 4349 float f; 4350 if (false) readf("%s", &f); 4351 4352 char a; 4353 wchar b; 4354 dchar c; 4355 if (false) readf("%s %s %s", a, b, c); 4356 // backwards compatibility with pointers 4357 if (false) readf("%s %s %s", a, &b, c); 4358 if (false) readf("%s %s %s", &a, &b, &c); 4359 } 4360 4361 /********************************** 4362 * Read line from `stdin`. 4363 * 4364 * This version manages its own read buffer, which means one memory allocation per call. If you are not 4365 * retaining a reference to the read data, consider the `readln(buf)` version, which may offer 4366 * better performance as it can reuse its read buffer. 4367 * 4368 * Returns: 4369 * The line that was read, including the line terminator character. 4370 * Params: 4371 * S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. 4372 * terminator = Line terminator (by default, `'\n'`). 4373 * Note: 4374 * String terminators are not supported due to ambiguity with readln(buf) below. 4375 * Throws: 4376 * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. 4377 * Example: 4378 * Reads `stdin` and writes it to `stdout`. 4379 --- 4380 import std.stdio; 4381 4382 void main() 4383 { 4384 string line; 4385 while ((line = readln()) !is null) 4386 write(line); 4387 } 4388 --- 4389 */ 4390 S readln(S = string)(dchar terminator = '\n') 4391 if (isSomeString!S) 4392 { 4393 return stdin.readln!S(terminator); 4394 } 4395 4396 /********************************** 4397 * Read line from `stdin` and write it to buf[], including terminating character. 4398 * 4399 * This can be faster than $(D line = readln()) because you can reuse 4400 * the buffer for each call. Note that reusing the buffer means that you 4401 * must copy the previous contents if you wish to retain them. 4402 * 4403 * Returns: 4404 * `size_t` 0 for end of file, otherwise number of characters read 4405 * Params: 4406 * buf = Buffer used to store the resulting line data. buf is resized as necessary. 4407 * terminator = Line terminator (by default, `'\n'`). Use $(REF newline, std,ascii) 4408 * for portability (unless the file was opened in text mode). 4409 * Throws: 4410 * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. 4411 * Example: 4412 * Reads `stdin` and writes it to `stdout`. 4413 --- 4414 import std.stdio; 4415 4416 void main() 4417 { 4418 char[] buf; 4419 while (readln(buf)) 4420 write(buf); 4421 } 4422 --- 4423 */ 4424 size_t readln(C)(ref C[] buf, dchar terminator = '\n') 4425 if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) 4426 { 4427 return stdin.readln(buf, terminator); 4428 } 4429 4430 /** ditto */ 4431 size_t readln(C, R)(ref C[] buf, R terminator) 4432 if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && 4433 isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) 4434 { 4435 return stdin.readln(buf, terminator); 4436 } 4437 4438 @safe unittest 4439 { 4440 import std.meta : AliasSeq; 4441 4442 //we can't actually test readln, so at the very least, 4443 //we test compilability 4444 void foo() 4445 { 4446 readln(); 4447 readln('\t'); 4448 static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) 4449 { 4450 readln!String(); 4451 readln!String('\t'); 4452 } 4453 static foreach (String; AliasSeq!(char[], wchar[], dchar[])) 4454 {{ 4455 String buf; 4456 readln(buf); 4457 readln(buf, '\t'); 4458 readln(buf, "<br />"); 4459 }} 4460 } 4461 } 4462 4463 /* 4464 * Convenience function that forwards to `core.sys.posix.stdio.fopen` 4465 * (to `_wfopen` on Windows) 4466 * with appropriately-constructed C-style strings. 4467 */ 4468 private FILE* _fopen(R1, R2)(R1 name, R2 mode = "r") 4469 if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) && 4470 (isSomeFiniteCharInputRange!R2 || isSomeString!R2)) 4471 { 4472 import std.internal.cstring : tempCString; 4473 4474 auto namez = name.tempCString!FSChar(); 4475 auto modez = mode.tempCString!FSChar(); 4476 4477 static _fopenImpl(scope const(FSChar)* namez, scope const(FSChar)* modez) @trusted nothrow @nogc 4478 { 4479 version (Windows) 4480 { 4481 return _wfopen(namez, modez); 4482 } 4483 else version (Posix) 4484 { 4485 /* 4486 * The new opengroup large file support API is transparently 4487 * included in the normal C bindings. https://www.opengroup.org/platform/lfs.html#1.0 4488 * if _FILE_OFFSET_BITS in druntime is 64, off_t is 64 bit and 4489 * the normal functions work fine. If not, then large file support 4490 * probably isn't available. Do not use the old transitional API 4491 * (the native extern(C) fopen64, https://unix.org/version2/whatsnew/lfs20mar.html#3.0) 4492 */ 4493 import core.sys.posix.stdio : fopen; 4494 return fopen(namez, modez); 4495 } 4496 else 4497 { 4498 return fopen(namez, modez); 4499 } 4500 } 4501 return _fopenImpl(namez, modez); 4502 } 4503 4504 version (Posix) 4505 { 4506 /*********************************** 4507 * Convenience function that forwards to `core.sys.posix.stdio.popen` 4508 * with appropriately-constructed C-style strings. 4509 */ 4510 FILE* _popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc 4511 if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) && 4512 (isSomeFiniteCharInputRange!R2 || isSomeString!R2)) 4513 { 4514 import std.internal.cstring : tempCString; 4515 4516 auto namez = name.tempCString!FSChar(); 4517 auto modez = mode.tempCString!FSChar(); 4518 4519 static popenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc 4520 { 4521 import core.sys.posix.stdio : popen; 4522 return popen(namez, modez); 4523 } 4524 return popenImpl(namez, modez); 4525 } 4526 } 4527 4528 /* 4529 * Convenience function that forwards to `core.stdc.stdio.fwrite` 4530 */ 4531 private auto trustedFwrite(T)(FILE* f, const T[] obj) @trusted 4532 { 4533 return fwrite(obj.ptr, T.sizeof, obj.length, f); 4534 } 4535 4536 /* 4537 * Convenience function that forwards to `core.stdc.stdio.fread` 4538 */ 4539 private auto trustedFread(T)(FILE* f, T[] obj) @trusted 4540 if (!imported!"std.traits".hasIndirections!T) 4541 { 4542 return fread(obj.ptr, T.sizeof, obj.length, f); 4543 } 4544 4545 private auto trustedFread(T)(FILE* f, T[] obj) @system 4546 if (imported!"std.traits".hasIndirections!T) 4547 { 4548 return fread(obj.ptr, T.sizeof, obj.length, f); 4549 } 4550 4551 /** 4552 * Iterates through the lines of a file by using `foreach`. 4553 * 4554 * Example: 4555 * 4556 --------- 4557 void main() 4558 { 4559 foreach (string line; lines(stdin)) 4560 { 4561 ... use line ... 4562 } 4563 } 4564 --------- 4565 The line terminator (`'\n'` by default) is part of the string read (it 4566 could be missing in the last line of the file). Several types are 4567 supported for `line`, and the behavior of `lines` 4568 changes accordingly: 4569 4570 $(OL $(LI If `line` has type `string`, $(D 4571 wstring), or `dstring`, a new string of the respective type 4572 is allocated every read.) $(LI If `line` has type $(D 4573 char[]), `wchar[]`, `dchar[]`, the line's content 4574 will be reused (overwritten) across reads.) $(LI If `line` 4575 has type `immutable(ubyte)[]`, the behavior is similar to 4576 case (1), except that no UTF checking is attempted upon input.) $(LI 4577 If `line` has type `ubyte[]`, the behavior is 4578 similar to case (2), except that no UTF checking is attempted upon 4579 input.)) 4580 4581 In all cases, a two-symbols versions is also accepted, in which case 4582 the first symbol (of integral type, e.g. `ulong` or $(D 4583 uint)) tracks the zero-based number of the current line. 4584 4585 Example: 4586 ---- 4587 foreach (ulong i, string line; lines(stdin)) 4588 { 4589 ... use line ... 4590 } 4591 ---- 4592 4593 In case of an I/O error, an `StdioException` is thrown. 4594 4595 See_Also: 4596 $(LREF byLine) 4597 */ 4598 4599 struct lines 4600 { 4601 private File f; 4602 private dchar terminator = '\n'; 4603 4604 /** 4605 Constructor. 4606 Params: 4607 f = File to read lines from. 4608 terminator = Line separator (`'\n'` by default). 4609 */ 4610 this(File f, dchar terminator = '\n') 4611 { 4612 this.f = f; 4613 this.terminator = terminator; 4614 } 4615 4616 int opApply(D)(scope D dg) 4617 { 4618 import std.traits : Parameters; 4619 alias Parms = Parameters!(dg); 4620 static if (isSomeString!(Parms[$ - 1])) 4621 { 4622 int result = 0; 4623 static if (is(Parms[$ - 1] : const(char)[])) 4624 alias C = char; 4625 else static if (is(Parms[$ - 1] : const(wchar)[])) 4626 alias C = wchar; 4627 else static if (is(Parms[$ - 1] : const(dchar)[])) 4628 alias C = dchar; 4629 C[] line; 4630 static if (Parms.length == 2) 4631 Parms[0] i = 0; 4632 for (;;) 4633 { 4634 import std.conv : to; 4635 4636 if (!f.readln(line, terminator)) break; 4637 auto copy = to!(Parms[$ - 1])(line); 4638 static if (Parms.length == 2) 4639 { 4640 result = dg(i, copy); 4641 ++i; 4642 } 4643 else 4644 { 4645 result = dg(copy); 4646 } 4647 if (result != 0) break; 4648 } 4649 return result; 4650 } 4651 else 4652 { 4653 // raw read 4654 return opApplyRaw(dg); 4655 } 4656 } 4657 // no UTF checking 4658 int opApplyRaw(D)(scope D dg) 4659 { 4660 import std.conv : to; 4661 import std.exception : assumeUnique; 4662 import std.traits : Parameters; 4663 4664 alias Parms = Parameters!(dg); 4665 enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]); 4666 int result = 1; 4667 int c = void; 4668 _FLOCK(f._p.handle); 4669 scope(exit) _FUNLOCK(f._p.handle); 4670 ubyte[] buffer; 4671 static if (Parms.length == 2) 4672 Parms[0] line = 0; 4673 while ((c = _FGETC(cast(_iobuf*) f._p.handle)) != -1) 4674 { 4675 buffer ~= to!(ubyte)(c); 4676 if (c == terminator) 4677 { 4678 static if (duplicate) 4679 auto arg = assumeUnique(buffer); 4680 else 4681 alias arg = buffer; 4682 // unlock the file while calling the delegate 4683 _FUNLOCK(f._p.handle); 4684 scope(exit) _FLOCK(f._p.handle); 4685 static if (Parms.length == 1) 4686 { 4687 result = dg(arg); 4688 } 4689 else 4690 { 4691 result = dg(line, arg); 4692 ++line; 4693 } 4694 if (result) break; 4695 static if (!duplicate) 4696 buffer.length = 0; 4697 } 4698 } 4699 // can only reach when _FGETC returned -1 4700 if (!f.eof) throw new StdioException("Error in reading file"); // error occured 4701 return result; 4702 } 4703 } 4704 4705 @system unittest 4706 { 4707 static import std.file; 4708 import std.meta : AliasSeq; 4709 4710 scope(failure) printf("Failed test at line %d\n", __LINE__); 4711 4712 auto deleteme = testFilename(); 4713 scope(exit) { std.file.remove(deleteme); } 4714 4715 alias TestedWith = 4716 AliasSeq!(string, wstring, dstring, 4717 char[], wchar[], dchar[]); 4718 foreach (T; TestedWith) 4719 { 4720 // test looping with an empty file 4721 std.file.write(deleteme, ""); 4722 auto f = File(deleteme, "r"); 4723 foreach (T line; lines(f)) 4724 { 4725 assert(false); 4726 } 4727 f.close(); 4728 4729 // test looping with a file with three lines 4730 std.file.write(deleteme, "Line one\nline two\nline three\n"); 4731 f.open(deleteme, "r"); 4732 uint i = 0; 4733 foreach (T line; lines(f)) 4734 { 4735 if (i == 0) assert(line == "Line one\n"); 4736 else if (i == 1) assert(line == "line two\n"); 4737 else if (i == 2) assert(line == "line three\n"); 4738 else assert(false); 4739 ++i; 4740 } 4741 f.close(); 4742 4743 // test looping with a file with three lines, last without a newline 4744 std.file.write(deleteme, "Line one\nline two\nline three"); 4745 f.open(deleteme, "r"); 4746 i = 0; 4747 foreach (T line; lines(f)) 4748 { 4749 if (i == 0) assert(line == "Line one\n"); 4750 else if (i == 1) assert(line == "line two\n"); 4751 else if (i == 2) assert(line == "line three"); 4752 else assert(false); 4753 ++i; 4754 } 4755 f.close(); 4756 } 4757 4758 // test with ubyte[] inputs 4759 alias TestedWith2 = AliasSeq!(immutable(ubyte)[], ubyte[]); 4760 foreach (T; TestedWith2) 4761 { 4762 // test looping with an empty file 4763 std.file.write(deleteme, ""); 4764 auto f = File(deleteme, "r"); 4765 foreach (T line; lines(f)) 4766 { 4767 assert(false); 4768 } 4769 f.close(); 4770 4771 // test looping with a file with three lines 4772 std.file.write(deleteme, "Line one\nline two\nline three\n"); 4773 f.open(deleteme, "r"); 4774 uint i = 0; 4775 foreach (T line; lines(f)) 4776 { 4777 if (i == 0) assert(cast(char[]) line == "Line one\n"); 4778 else if (i == 1) assert(cast(char[]) line == "line two\n", 4779 T.stringof ~ " " ~ cast(char[]) line); 4780 else if (i == 2) assert(cast(char[]) line == "line three\n"); 4781 else assert(false); 4782 ++i; 4783 } 4784 f.close(); 4785 4786 // test looping with a file with three lines, last without a newline 4787 std.file.write(deleteme, "Line one\nline two\nline three"); 4788 f.open(deleteme, "r"); 4789 i = 0; 4790 foreach (T line; lines(f)) 4791 { 4792 if (i == 0) assert(cast(char[]) line == "Line one\n"); 4793 else if (i == 1) assert(cast(char[]) line == "line two\n"); 4794 else if (i == 2) assert(cast(char[]) line == "line three"); 4795 else assert(false); 4796 ++i; 4797 } 4798 f.close(); 4799 4800 } 4801 4802 static foreach (T; AliasSeq!(ubyte[])) 4803 { 4804 // test looping with a file with three lines, last without a newline 4805 // using a counter too this time 4806 std.file.write(deleteme, "Line one\nline two\nline three"); 4807 auto f = File(deleteme, "r"); 4808 uint i = 0; 4809 foreach (ulong j, T line; lines(f)) 4810 { 4811 if (i == 0) assert(cast(char[]) line == "Line one\n"); 4812 else if (i == 1) assert(cast(char[]) line == "line two\n"); 4813 else if (i == 2) assert(cast(char[]) line == "line three"); 4814 else assert(false); 4815 ++i; 4816 } 4817 f.close(); 4818 } 4819 } 4820 4821 /** 4822 Iterates through a file a chunk at a time by using `foreach`. 4823 4824 Example: 4825 4826 --------- 4827 void main() 4828 { 4829 foreach (ubyte[] buffer; chunks(stdin, 4096)) 4830 { 4831 ... use buffer ... 4832 } 4833 } 4834 --------- 4835 4836 The content of `buffer` is reused across calls. In the 4837 example above, `buffer.length` is 4096 for all iterations, 4838 except for the last one, in which case `buffer.length` may 4839 be less than 4096 (but always greater than zero). 4840 4841 In case of an I/O error, an `StdioException` is thrown. 4842 */ 4843 auto chunks(File f, size_t size) 4844 { 4845 return ChunksImpl(f, size); 4846 } 4847 private struct ChunksImpl 4848 { 4849 private File f; 4850 private size_t size; 4851 // private string fileName; // Currently, no use 4852 4853 this(File f, size_t size) 4854 in 4855 { 4856 assert(size, "size must be larger than 0"); 4857 } 4858 do 4859 { 4860 this.f = f; 4861 this.size = size; 4862 } 4863 4864 int opApply(D)(scope D dg) 4865 { 4866 import core.stdc.stdlib : alloca; 4867 import std.exception : enforce; 4868 4869 enforce(f.isOpen, "Attempting to read from an unopened file"); 4870 enum maxStackSize = 1024 * 16; 4871 ubyte[] buffer = void; 4872 if (size < maxStackSize) 4873 buffer = (cast(ubyte*) alloca(size))[0 .. size]; 4874 else 4875 buffer = new ubyte[size]; 4876 size_t r = void; 4877 int result = 1; 4878 uint tally = 0; 4879 while ((r = trustedFread(f._p.handle, buffer)) > 0) 4880 { 4881 assert(r <= size); 4882 if (r != size) 4883 { 4884 // error occured 4885 if (!f.eof) throw new StdioException(null); 4886 buffer.length = r; 4887 } 4888 static if (is(typeof(dg(tally, buffer)))) 4889 { 4890 if ((result = dg(tally, buffer)) != 0) break; 4891 } 4892 else 4893 { 4894 if ((result = dg(buffer)) != 0) break; 4895 } 4896 ++tally; 4897 } 4898 return result; 4899 } 4900 } 4901 4902 @system unittest 4903 { 4904 static import std.file; 4905 4906 scope(failure) printf("Failed test at line %d\n", __LINE__); 4907 4908 auto deleteme = testFilename(); 4909 scope(exit) { std.file.remove(deleteme); } 4910 4911 // test looping with an empty file 4912 std.file.write(deleteme, ""); 4913 auto f = File(deleteme, "r"); 4914 foreach (ubyte[] line; chunks(f, 4)) 4915 { 4916 assert(false); 4917 } 4918 f.close(); 4919 4920 // test looping with a file with three lines 4921 std.file.write(deleteme, "Line one\nline two\nline three\n"); 4922 f = File(deleteme, "r"); 4923 uint i = 0; 4924 foreach (ubyte[] line; chunks(f, 3)) 4925 { 4926 if (i == 0) assert(cast(char[]) line == "Lin"); 4927 else if (i == 1) assert(cast(char[]) line == "e o"); 4928 else if (i == 2) assert(cast(char[]) line == "ne\n"); 4929 else break; 4930 ++i; 4931 } 4932 f.close(); 4933 } 4934 4935 // Issue 21730 - null ptr dereferenced in ChunksImpl.opApply (SIGSEGV) 4936 @system unittest 4937 { 4938 import std.exception : assertThrown; 4939 static import std.file; 4940 4941 auto deleteme = testFilename(); 4942 scope(exit) { if (std.file.exists(deleteme)) std.file.remove(deleteme); } 4943 4944 auto err1 = File(deleteme, "w+x"); 4945 err1.close; 4946 std.file.remove(deleteme); 4947 assertThrown(() {foreach (ubyte[] buf; chunks(err1, 4096)) {}}()); 4948 } 4949 4950 /** 4951 Writes an array or range to a file. 4952 Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)). 4953 Similar to $(REF write, std,file), strings are written as-is, 4954 rather than encoded according to the `File`'s $(HTTP 4955 en.cppreference.com/w/c/io#Narrow_and_wide_orientation, 4956 orientation). 4957 */ 4958 void toFile(T)(T data, string fileName) 4959 if (is(typeof(copy(data, stdout.lockingBinaryWriter)))) 4960 { 4961 copy(data, File(fileName, "wb").lockingBinaryWriter); 4962 } 4963 4964 @system unittest 4965 { 4966 static import std.file; 4967 4968 auto deleteme = testFilename(); 4969 scope(exit) { std.file.remove(deleteme); } 4970 4971 "Test".toFile(deleteme); 4972 assert(std.file.readText(deleteme) == "Test"); 4973 } 4974 4975 /********************* 4976 * Thrown if I/O errors happen. 4977 */ 4978 class StdioException : Exception 4979 { 4980 static import core.stdc.errno; 4981 /// Operating system error code. 4982 uint errno; 4983 4984 /** 4985 Initialize with a message and an error code. 4986 */ 4987 this(string message, uint e = core.stdc.errno.errno) @trusted 4988 { 4989 import std.exception : errnoString; 4990 errno = e; 4991 auto sysmsg = errnoString(errno); 4992 // If e is 0, we don't use the system error message. (The message 4993 // is "Success", which is rather pointless for an exception.) 4994 super(e == 0 ? message 4995 : (message ? message ~ " (" ~ sysmsg ~ ")" : sysmsg)); 4996 } 4997 4998 /** Convenience functions that throw an `StdioException`. */ 4999 static void opCall(string msg) @safe 5000 { 5001 throw new StdioException(msg); 5002 } 5003 5004 /// ditto 5005 static void opCall() @safe 5006 { 5007 throw new StdioException(null, core.stdc.errno.errno); 5008 } 5009 } 5010 5011 enum StdFileHandle: string 5012 { 5013 stdin = "core.stdc.stdio.stdin", 5014 stdout = "core.stdc.stdio.stdout", 5015 stderr = "core.stdc.stdio.stderr", 5016 } 5017 5018 // Undocumented but public because the std* handles are aliasing it. 5019 @property ref File makeGlobal(StdFileHandle _iob)() 5020 { 5021 __gshared File.Impl impl; 5022 __gshared File result; 5023 5024 // Use an inline spinlock to make sure the initializer is only run once. 5025 // We assume there will be at most uint.max / 2 threads trying to initialize 5026 // `handle` at once and steal the high bit to indicate that the globals have 5027 // been initialized. 5028 static shared uint spinlock; 5029 import core.atomic : atomicLoad, atomicOp, MemoryOrder; 5030 if (atomicLoad!(MemoryOrder.acq)(spinlock) <= uint.max / 2) 5031 { 5032 for (;;) 5033 { 5034 if (atomicLoad!(MemoryOrder.acq)(spinlock) > uint.max / 2) 5035 break; 5036 if (atomicOp!"+="(spinlock, 1) == 1) 5037 { 5038 with (StdFileHandle) 5039 assert(_iob == stdin || _iob == stdout || _iob == stderr); 5040 impl.handle = cast() mixin(_iob); 5041 result._p = &impl; 5042 atomicOp!"+="(spinlock, uint.max / 2); 5043 break; 5044 } 5045 atomicOp!"-="(spinlock, 1); 5046 } 5047 } 5048 return result; 5049 } 5050 5051 /** The standard input stream. 5052 5053 Returns: 5054 stdin as a $(LREF File). 5055 5056 Note: 5057 The returned $(LREF File) wraps $(REF stdin,core,stdc,stdio), and 5058 is therefore thread global. Reassigning `stdin` to a different 5059 `File` must be done in a single-threaded or locked context in 5060 order to avoid race conditions. 5061 5062 All reading from `stdin` automatically locks the file globally, 5063 and will cause all other threads calling `read` to wait until 5064 the lock is released. 5065 */ 5066 alias stdin = makeGlobal!(StdFileHandle.stdin); 5067 5068 /// 5069 @safe unittest 5070 { 5071 // Read stdin, sort lines, write to stdout 5072 import std.algorithm.mutation : copy; 5073 import std.algorithm.sorting : sort; 5074 import std.array : array; 5075 import std.typecons : Yes; 5076 5077 void main() 5078 { 5079 stdin // read from stdin 5080 .byLineCopy(Yes.keepTerminator) // copying each line 5081 .array() // convert to array of lines 5082 .sort() // sort the lines 5083 .copy( // copy output of .sort to an OutputRange 5084 stdout.lockingTextWriter()); // the OutputRange 5085 } 5086 } 5087 5088 /** 5089 The standard output stream. 5090 5091 Returns: 5092 stdout as a $(LREF File). 5093 5094 Note: 5095 The returned $(LREF File) wraps $(REF stdout,core,stdc,stdio), and 5096 is therefore thread global. Reassigning `stdout` to a different 5097 `File` must be done in a single-threaded or locked context in 5098 order to avoid race conditions. 5099 5100 All writing to `stdout` automatically locks the file globally, 5101 and will cause all other threads calling `write` to wait until 5102 the lock is released. 5103 */ 5104 alias stdout = makeGlobal!(StdFileHandle.stdout); 5105 5106 /// 5107 @safe unittest 5108 { 5109 void main() 5110 { 5111 stdout.writeln("Write a message to stdout."); 5112 } 5113 } 5114 5115 /// 5116 @safe unittest 5117 { 5118 void main() 5119 { 5120 import std.algorithm.iteration : filter, map, sum; 5121 import std.format : format; 5122 import std.range : iota, tee; 5123 5124 int len; 5125 const r = 6.iota 5126 .filter!(a => a % 2) // 1 3 5 5127 .map!(a => a * 2) // 2 6 10 5128 .tee!(_ => stdout.writefln("len: %d", len++)) 5129 .sum; 5130 5131 assert(r == 18); 5132 } 5133 } 5134 5135 /// 5136 @safe unittest 5137 { 5138 void main() 5139 { 5140 import std.algorithm.mutation : copy; 5141 import std.algorithm.iteration : map; 5142 import std.format : format; 5143 import std.range : iota; 5144 5145 10.iota 5146 .map!(e => "N: %d".format(e)) 5147 .copy(stdout.lockingTextWriter()); // the OutputRange 5148 } 5149 } 5150 5151 /** 5152 The standard error stream. 5153 5154 Returns: 5155 stderr as a $(LREF File). 5156 5157 Note: 5158 The returned $(LREF File) wraps $(REF stderr,core,stdc,stdio), and 5159 is therefore thread global. Reassigning `stderr` to a different 5160 `File` must be done in a single-threaded or locked context in 5161 order to avoid race conditions. 5162 5163 All writing to `stderr` automatically locks the file globally, 5164 and will cause all other threads calling `write` to wait until 5165 the lock is released. 5166 */ 5167 alias stderr = makeGlobal!(StdFileHandle.stderr); 5168 5169 /// 5170 @safe unittest 5171 { 5172 void main() 5173 { 5174 stderr.writeln("Write a message to stderr."); 5175 } 5176 } 5177 5178 @system unittest 5179 { 5180 static import std.file; 5181 import std.typecons : tuple; 5182 5183 scope(failure) printf("Failed test at line %d\n", __LINE__); 5184 auto deleteme = testFilename(); 5185 5186 std.file.write(deleteme, "1 2\n4 1\n5 100"); 5187 scope(exit) std.file.remove(deleteme); 5188 { 5189 File f = File(deleteme); 5190 scope(exit) f.close(); 5191 auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ]; 5192 uint i; 5193 foreach (e; f.byRecord!(int, int)("%s %s")) 5194 { 5195 //writeln(e); 5196 assert(e == t[i++]); 5197 } 5198 assert(i == 3); 5199 } 5200 } 5201 5202 @safe unittest 5203 { 5204 // Retain backwards compatibility 5205 // https://issues.dlang.org/show_bug.cgi?id=17472 5206 static assert(is(typeof(stdin) == File)); 5207 static assert(is(typeof(stdout) == File)); 5208 static assert(is(typeof(stderr) == File)); 5209 } 5210 5211 // roll our own appender, but with "safe" arrays 5212 private struct ReadlnAppender 5213 { 5214 char[] buf; 5215 size_t pos; 5216 bool safeAppend = false; 5217 5218 void initialize(char[] b) @safe 5219 { 5220 buf = b; 5221 pos = 0; 5222 } 5223 @property char[] data() @trusted 5224 { 5225 if (safeAppend) 5226 assumeSafeAppend(buf.ptr[0 .. pos]); 5227 return buf.ptr[0 .. pos]; 5228 } 5229 5230 bool reserveWithoutAllocating(size_t n) 5231 { 5232 if (buf.length >= pos + n) // buf is already large enough 5233 return true; 5234 5235 immutable curCap = buf.capacity; 5236 if (curCap >= pos + n) 5237 { 5238 buf.length = curCap; 5239 /* Any extra capacity we end up not using can safely be claimed 5240 by someone else. */ 5241 safeAppend = true; 5242 return true; 5243 } 5244 5245 return false; 5246 } 5247 void reserve(size_t n) @trusted 5248 { 5249 import core.stdc.string : memcpy; 5250 if (!reserveWithoutAllocating(n)) 5251 { 5252 size_t ncap = buf.length * 2 + 128 + n; 5253 char[] nbuf = new char[ncap]; 5254 memcpy(nbuf.ptr, buf.ptr, pos); 5255 buf = nbuf; 5256 // Allocated a new buffer. No one else knows about it. 5257 safeAppend = true; 5258 } 5259 } 5260 void putchar(char c) @trusted 5261 { 5262 reserve(1); 5263 buf.ptr[pos++] = c; 5264 } 5265 void putdchar(dchar dc) @trusted 5266 { 5267 import std.utf : encode, UseReplacementDchar; 5268 5269 char[4] ubuf; 5270 immutable size = encode!(UseReplacementDchar.yes)(ubuf, dc); 5271 reserve(size); 5272 foreach (c; ubuf) 5273 buf.ptr[pos++] = c; 5274 } 5275 void putonly(const char[] b) @trusted 5276 { 5277 import core.stdc.string : memcpy; 5278 assert(pos == 0); // assume this is the only put call 5279 if (reserveWithoutAllocating(b.length)) 5280 memcpy(buf.ptr + pos, b.ptr, b.length); 5281 else 5282 buf = b.dup; 5283 pos = b.length; 5284 } 5285 } 5286 5287 private struct LockedFile 5288 { 5289 private @system _iobuf* fp; 5290 5291 this(FILE* fps) @trusted 5292 { 5293 _FLOCK(fps); 5294 // Since fps is now locked, we can cast away shared 5295 fp = cast(_iobuf*) fps; 5296 } 5297 5298 @disable this(); 5299 @disable this(this); 5300 @disable void opAssign(LockedFile); 5301 5302 // these use unlocked fgetc calls 5303 @trusted fgetc() { return _FGETC(fp); } 5304 @trusted fgetwc() { return _FGETWC(fp); } 5305 5306 ~this() @trusted 5307 { 5308 _FUNLOCK(cast(FILE*) fp); 5309 } 5310 } 5311 5312 @safe unittest 5313 { 5314 void f() @safe 5315 { 5316 FILE* fps; 5317 auto lf = LockedFile(fps); 5318 static assert(!__traits(compiles, lf = LockedFile(fps))); 5319 version (ShouldFail) 5320 { 5321 lf.fps = null; // error with -preview=systemVariables 5322 } 5323 } 5324 } 5325 5326 // Private implementation of readln 5327 private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation) @safe 5328 { 5329 version (CRuntime_Microsoft) 5330 { 5331 auto lf = LockedFile(fps); 5332 5333 ReadlnAppender app; 5334 app.initialize(buf); 5335 5336 int c; 5337 while ((c = lf.fgetc()) != -1) 5338 { 5339 app.putchar(cast(char) c); 5340 if (c == terminator) 5341 { 5342 buf = app.data; 5343 return buf.length; 5344 } 5345 5346 } 5347 5348 if (ferror(fps)) 5349 StdioException(); 5350 buf = app.data; 5351 return buf.length; 5352 } 5353 else static if (__traits(compiles, core.sys.posix.stdio.getdelim)) 5354 { 5355 if (orientation == File.Orientation.wide) 5356 { 5357 import core.stdc.wchar_ : fwide; 5358 5359 auto lf = LockedFile(fps); 5360 /* Stream is in wide characters. 5361 * Read them and convert to chars. 5362 */ 5363 version (Windows) 5364 { 5365 buf.length = 0; 5366 for (int c = void; (c = lf.fgetwc()) != -1; ) 5367 { 5368 if ((c & ~0x7F) == 0) 5369 { buf ~= c; 5370 if (c == terminator) 5371 break; 5372 } 5373 else 5374 { 5375 if (c >= 0xD800 && c <= 0xDBFF) 5376 { 5377 int c2 = void; 5378 if ((c2 = lf.fgetwc()) != -1 || 5379 c2 < 0xDC00 && c2 > 0xDFFF) 5380 { 5381 StdioException("unpaired UTF-16 surrogate"); 5382 } 5383 c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); 5384 } 5385 import std.utf : encode; 5386 encode(buf, c); 5387 } 5388 } 5389 if (ferror(fps)) 5390 StdioException(); 5391 return buf.length; 5392 } 5393 else version (Posix) 5394 { 5395 buf.length = 0; 5396 for (int c; (c = lf.fgetwc()) != -1; ) 5397 { 5398 import std.utf : encode; 5399 5400 if ((c & ~0x7F) == 0) 5401 buf ~= cast(char) c; 5402 else 5403 encode(buf, cast(dchar) c); 5404 if (c == terminator) 5405 break; 5406 } 5407 if (ferror(fps)) 5408 StdioException(); 5409 return buf.length; 5410 } 5411 else 5412 { 5413 static assert(0); 5414 } 5415 } 5416 return () @trusted { 5417 import core.stdc.stdlib : free; 5418 5419 static char *lineptr = null; 5420 static size_t n = 0; 5421 scope(exit) 5422 { 5423 if (n > 128 * 1024) 5424 { 5425 // Bound memory used by readln 5426 free(lineptr); 5427 lineptr = null; 5428 n = 0; 5429 } 5430 } 5431 5432 const s = core.sys.posix.stdio.getdelim(&lineptr, &n, terminator, fps); 5433 if (s < 0) 5434 { 5435 if (ferror(fps)) 5436 StdioException(); 5437 buf.length = 0; // end of file 5438 return 0; 5439 } 5440 5441 const line = lineptr[0 .. s]; 5442 if (s <= buf.length) 5443 { 5444 buf = buf[0 .. s]; 5445 buf[] = line; 5446 } 5447 else 5448 { 5449 buf = line.dup; 5450 } 5451 return s; 5452 }(); 5453 } 5454 else // version (NO_GETDELIM) 5455 { 5456 import core.stdc.wchar_ : fwide; 5457 5458 auto lf = LockedFile(fps); 5459 if (orientation == File.Orientation.wide) 5460 { 5461 /* Stream is in wide characters. 5462 * Read them and convert to chars. 5463 */ 5464 version (Windows) 5465 { 5466 buf.length = 0; 5467 for (int c; (c = lf.fgetwc()) != -1; ) 5468 { 5469 if ((c & ~0x7F) == 0) 5470 { buf ~= c; 5471 if (c == terminator) 5472 break; 5473 } 5474 else 5475 { 5476 if (c >= 0xD800 && c <= 0xDBFF) 5477 { 5478 int c2 = void; 5479 if ((c2 = lf.fgetwc()) != -1 || 5480 c2 < 0xDC00 && c2 > 0xDFFF) 5481 { 5482 StdioException("unpaired UTF-16 surrogate"); 5483 } 5484 c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); 5485 } 5486 import std.utf : encode; 5487 encode(buf, c); 5488 } 5489 } 5490 if (ferror(fps)) 5491 StdioException(); 5492 return buf.length; 5493 } 5494 else version (Posix) 5495 { 5496 import std.utf : encode; 5497 buf.length = 0; 5498 for (int c; (c = lf.fgetwc()) != -1; ) 5499 { 5500 if ((c & ~0x7F) == 0) 5501 buf ~= cast(char) c; 5502 else 5503 encode(buf, cast(dchar) c); 5504 if (c == terminator) 5505 break; 5506 } 5507 if (ferror(fps)) 5508 StdioException(); 5509 return buf.length; 5510 } 5511 else 5512 { 5513 static assert(0); 5514 } 5515 } 5516 5517 // Narrow stream 5518 // First, fill the existing buffer 5519 for (size_t bufPos = 0; bufPos < buf.length; ) 5520 { 5521 immutable c = lf.fgetc(); 5522 if (c == -1) 5523 { 5524 buf.length = bufPos; 5525 goto endGame; 5526 } 5527 buf[bufPos++] = cast(char) c; 5528 if (c == terminator) 5529 { 5530 // No need to test for errors in file 5531 buf.length = bufPos; 5532 return bufPos; 5533 } 5534 } 5535 // Then, append to it 5536 for (int c; (c = lf.fgetc()) != -1; ) 5537 { 5538 buf ~= cast(char) c; 5539 if (c == terminator) 5540 { 5541 // No need to test for errors in file 5542 return buf.length; 5543 } 5544 } 5545 5546 endGame: 5547 if (ferror(fps)) 5548 StdioException(); 5549 return buf.length; 5550 } 5551 } 5552 5553 @system unittest 5554 { 5555 static import std.file; 5556 auto deleteme = testFilename(); 5557 scope(exit) std.file.remove(deleteme); 5558 5559 std.file.write(deleteme, "abcd\n0123456789abcde\n1234\n"); 5560 File f = File(deleteme, "rb"); 5561 5562 char[] ln = new char[2]; 5563 f.readln(ln); 5564 5565 assert(ln == "abcd\n"); 5566 char[] t = ln[0 .. 2]; 5567 t ~= 't'; 5568 assert(t == "abt"); 5569 // https://issues.dlang.org/show_bug.cgi?id=13856: ln stomped to "abtd" 5570 assert(ln == "abcd\n"); 5571 5572 // it can also stomp the array length 5573 ln = new char[4]; 5574 f.readln(ln); 5575 assert(ln == "0123456789abcde\n"); 5576 5577 char[100] buf; 5578 ln = buf[]; 5579 f.readln(ln); 5580 assert(ln == "1234\n"); 5581 assert(ln.ptr == buf.ptr); // avoid allocation, buffer is good enough 5582 } 5583 5584 /** Experimental network access via the File interface 5585 5586 Opens a TCP connection to the given host and port, then returns 5587 a File struct with read and write access through the same interface 5588 as any other file (meaning writef and the byLine ranges work!). 5589 5590 Authors: 5591 Adam D. Ruppe 5592 5593 Bugs: 5594 Only works on Linux 5595 */ 5596 version (linux) 5597 { 5598 File openNetwork(string host, ushort port) 5599 { 5600 import core.stdc.string : memcpy; 5601 import core.sys.posix.arpa.inet : htons; 5602 import core.sys.posix.netdb : gethostbyname; 5603 import core.sys.posix.netinet.in_ : sockaddr_in; 5604 static import core.sys.posix.unistd; 5605 static import sock = core.sys.posix.sys.socket; 5606 import std.conv : to; 5607 import std.exception : enforce; 5608 import std.internal.cstring : tempCString; 5609 5610 auto h = enforce( gethostbyname(host.tempCString()), 5611 new StdioException("gethostbyname")); 5612 5613 int s = sock.socket(sock.AF_INET, sock.SOCK_STREAM, 0); 5614 enforce(s != -1, new StdioException("socket")); 5615 5616 scope(failure) 5617 { 5618 // want to make sure it doesn't dangle if something throws. Upon 5619 // normal exit, the File struct's reference counting takes care of 5620 // closing, so we don't need to worry about success 5621 core.sys.posix.unistd.close(s); 5622 } 5623 5624 sockaddr_in addr; 5625 5626 addr.sin_family = sock.AF_INET; 5627 addr.sin_port = htons(port); 5628 memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length); 5629 5630 enforce(sock.connect(s, cast(sock.sockaddr*) &addr, addr.sizeof) != -1, 5631 new StdioException("Connect failed")); 5632 5633 File f; 5634 f.fdopen(s, "w+", host ~ ":" ~ to!string(port)); 5635 return f; 5636 } 5637 } 5638 5639 version (StdUnittest) private string testFilename(string file = __FILE__, size_t line = __LINE__) @safe 5640 { 5641 import std.conv : text; 5642 import std.file : deleteme; 5643 import std.path : baseName; 5644 5645 // filename intentionally contains non-ASCII (Russian) characters for 5646 // https://issues.dlang.org/show_bug.cgi?id=7648 5647 return text(deleteme, "-детка.", baseName(file), ".", line); 5648 }