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