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