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