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