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