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