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