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