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