| // Written in the D programming language. | |
| /** | |
| $(SCRIPT inhibitQuickIndex = 1;) | |
| $(DIVC quickindex, | |
| $(BOOKTABLE, | |
| $(TR $(TH Category) $(TH Symbols)) | |
| $(TR $(TD File handles) $(TD | |
| $(MYREF __popen) | |
| $(MYREF File) | |
| $(MYREF isFileHandle) | |
| $(MYREF openNetwork) | |
| $(MYREF stderr) | |
| $(MYREF stdin) | |
| $(MYREF stdout) | |
| )) | |
| $(TR $(TD Reading) $(TD | |
| $(MYREF chunks) | |
| $(MYREF lines) | |
| $(MYREF readf) | |
| $(MYREF readfln) | |
| $(MYREF readln) | |
| )) | |
| $(TR $(TD Writing) $(TD | |
| $(MYREF toFile) | |
| $(MYREF write) | |
| $(MYREF writef) | |
| $(MYREF writefln) | |
| $(MYREF writeln) | |
| )) | |
| $(TR $(TD Misc) $(TD | |
| $(MYREF KeepTerminator) | |
| $(MYREF LockType) | |
| $(MYREF StdioException) | |
| )) | |
| )) | |
| Standard I/O functions that extend $(LINK2 https://dlang.org/phobos/core_stdc_stdio.html, core.stdc.stdio). $(B core.stdc.stdio) | |
| is $(D_PARAM public)ally imported when importing $(B std.stdio). | |
| There are three layers of I/O: | |
| $(OL | |
| $(LI The lowest layer is the operating system layer. The two main schemes are Windows and Posix.) | |
| $(LI C's $(TT stdio.h) which unifies the two operating system schemes.) | |
| $(LI $(TT std.stdio), this module, unifies the various $(TT stdio.h) implementations into | |
| a high level package for D programs.) | |
| ) | |
| Source: $(PHOBOSSRC std/stdio.d) | |
| Copyright: Copyright The D Language Foundation 2007-. | |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
| Authors: $(HTTP digitalmars.com, Walter Bright), | |
| $(HTTP erdani.org, Andrei Alexandrescu), | |
| Alex Rønne Petersen | |
| Macros: | |
| CSTDIO=$(HTTP cplusplus.com/reference/cstdio/$1/, $1) | |
| */ | |
| module std.stdio; | |
| /* | |
| # Glossary | |
| The three layers have many terms for their data structures and types. | |
| Here we try to bring some sanity to them for the intrepid code spelunker. | |
| ## Windows | |
| Handle | |
| A Windows handle is an opaque object of type HANDLE. | |
| The `HANDLE` for standard devices can be retrieved with | |
| Windows `GetStdHandle()`. | |
| ## Posix | |
| file descriptor, aka fileno, aka fildes | |
| An int from 0..`FOPEN_MAX`, which is an index into some internal data | |
| structure. | |
| 0 is for `stdin`, 1 for `stdout`, 2 for `stderr`. | |
| Negative values usually indicate an error. | |
| ## stdio.h | |
| `FILE` | |
| A struct that encapsulates the C library's view of the operating system | |
| files. A `FILE` should only be referred to via a pointer. | |
| `fileno` | |
| A field of `FILE` which is the Posix file descriptor for Posix systems, and | |
| and an index into an array of file `HANDLE`s for Windows. | |
| This array is how Posix behavior is emulated on Windows. | |
| For Digital Mars C, that array is `__osfhnd[]`, and is initialized | |
| at program start by the C runtime library. | |
| In this module, they are typed as `fileno_t`. | |
| `stdin`, `stdout`, `stderr` | |
| Global pointers to `FILE` representing standard input, output, and error streams. | |
| Being global means there are synchronization issues when multiple threads | |
| are doing I/O on the same streams. | |
| ## std.stdio | |
| */ | |
| import core.stdc.stddef : wchar_t; | |
| public import core.stdc.stdio; | |
| import std.algorithm.mutation : copy; | |
| import std.meta : allSatisfy; | |
| import std.range : ElementEncodingType, empty, front, isBidirectionalRange, | |
| isInputRange, isSomeFiniteCharInputRange, put; | |
| import std.traits : isSomeChar, isSomeString, Unqual; | |
| import std.typecons : Flag, No, Yes; | |
| /++ | |
| If flag `KeepTerminator` is set to `KeepTerminator.yes`, then the delimiter | |
| is included in the strings returned. | |
| +/ | |
| alias KeepTerminator = Flag!"keepTerminator"; | |
| version (CRuntime_Microsoft) | |
| { | |
| } | |
| else version (CRuntime_Glibc) | |
| { | |
| } | |
| else version (CRuntime_Bionic) | |
| { | |
| version = GENERIC_IO; | |
| } | |
| else version (CRuntime_Musl) | |
| { | |
| version = GENERIC_IO; | |
| } | |
| else version (CRuntime_UClibc) | |
| { | |
| version = GENERIC_IO; | |
| } | |
| else version (OSX) | |
| { | |
| version = GENERIC_IO; | |
| version = Darwin; | |
| } | |
| else version (iOS) | |
| { | |
| version = GENERIC_IO; | |
| version = Darwin; | |
| } | |
| else version (TVOS) | |
| { | |
| version = GENERIC_IO; | |
| version = Darwin; | |
| } | |
| else version (WatchOS) | |
| { | |
| version = GENERIC_IO; | |
| version = Darwin; | |
| } | |
| else version (FreeBSD) | |
| { | |
| version = GENERIC_IO; | |
| } | |
| else version (NetBSD) | |
| { | |
| version = GENERIC_IO; | |
| } | |
| else version (OpenBSD) | |
| { | |
| version = GENERIC_IO; | |
| } | |
| else version (DragonFlyBSD) | |
| { | |
| version = GENERIC_IO; | |
| } | |
| else version (Solaris) | |
| { | |
| version = GENERIC_IO; | |
| } | |
| else | |
| { | |
| static assert(0, "unsupported operating system"); | |
| } | |
| // Character type used for operating system filesystem APIs | |
| version (Windows) | |
| { | |
| private alias FSChar = wchar; | |
| } | |
| else | |
| { | |
| private alias FSChar = char; | |
| } | |
| private alias fileno_t = int; // file descriptor, fildes, fileno | |
| version (Windows) | |
| { | |
| // core.stdc.stdio.fopen expects file names to be | |
| // encoded in CP_ACP on Windows instead of UTF-8. | |
| /+ Waiting for druntime pull 299 | |
| +/ | |
| extern (C) nothrow @nogc FILE* _wfopen(scope const wchar* filename, scope const wchar* mode); | |
| extern (C) nothrow @nogc FILE* _wfreopen(scope const wchar* filename, scope const wchar* mode, FILE* fp); | |
| import core.sys.windows.basetsd : HANDLE; | |
| } | |
| version (Posix) | |
| { | |
| static import core.sys.posix.stdio; // getdelim, flockfile | |
| } | |
| version (CRuntime_Microsoft) | |
| { | |
| private alias _FPUTC = _fputc_nolock; | |
| private alias _FPUTWC = _fputwc_nolock; | |
| private alias _FGETC = _fgetc_nolock; | |
| private alias _FGETWC = _fgetwc_nolock; | |
| private alias _FLOCK = _lock_file; | |
| private alias _FUNLOCK = _unlock_file; | |
| } | |
| else version (CRuntime_Glibc) | |
| { | |
| private alias _FPUTC = fputc_unlocked; | |
| private alias _FPUTWC = fputwc_unlocked; | |
| private alias _FGETC = fgetc_unlocked; | |
| private alias _FGETWC = fgetwc_unlocked; | |
| private alias _FLOCK = core.sys.posix.stdio.flockfile; | |
| private alias _FUNLOCK = core.sys.posix.stdio.funlockfile; | |
| } | |
| else version (GENERIC_IO) | |
| { | |
| nothrow: | |
| @nogc: | |
| extern (C) private | |
| { | |
| static import core.stdc.wchar_; | |
| pragma(mangle, fputc.mangleof) int _FPUTC(int c, _iobuf* fp); | |
| pragma(mangle, core.stdc.wchar_.fputwc.mangleof) int _FPUTWC(wchar_t c, _iobuf* fp); | |
| pragma(mangle, fgetc.mangleof) int _FGETC(_iobuf* fp); | |
| pragma(mangle, core.stdc.wchar_.fgetwc.mangleof) int _FGETWC(_iobuf* fp); | |
| } | |
| version (Posix) | |
| { | |
| private alias _FLOCK = core.sys.posix.stdio.flockfile; | |
| private alias _FUNLOCK = core.sys.posix.stdio.funlockfile; | |
| } | |
| else | |
| { | |
| static assert(0, "don't know how to lock files on GENERIC_IO"); | |
| } | |
| } | |
| else | |
| { | |
| static assert(0, "unsupported C I/O system"); | |
| } | |
| private extern (C) @nogc nothrow | |
| { | |
| pragma(mangle, _FPUTC.mangleof) int trustedFPUTC(int ch, _iobuf* h) @trusted; | |
| pragma(mangle, _FPUTWC.mangleof) int trustedFPUTWC(wchar_t ch, _iobuf* h) @trusted; | |
| } | |
| //------------------------------------------------------------------------------ | |
| private struct ByRecordImpl(Fields...) | |
| { | |
| private: | |
| import std.typecons : Tuple; | |
| File file; | |
| char[] line; | |
| Tuple!(Fields) current; | |
| string format; | |
| public: | |
| this(File f, string format) | |
| { | |
| assert(f.isOpen); | |
| file = f; | |
| this.format = format; | |
| popFront(); // prime the range | |
| } | |
| /// Range primitive implementations. | |
| @property bool empty() | |
| { | |
| return !file.isOpen; | |
| } | |
| /// Ditto | |
| @property ref Tuple!(Fields) front() | |
| { | |
| return current; | |
| } | |
| /// Ditto | |
| void popFront() | |
| { | |
| import std.conv : text; | |
| import std.exception : enforce; | |
| import std.format.read : formattedRead; | |
| import std.string : chomp; | |
| enforce(file.isOpen, "ByRecord: File must be open"); | |
| file.readln(line); | |
| if (!line.length) | |
| { | |
| file.detach(); | |
| } | |
| else | |
| { | |
| line = chomp(line); | |
| formattedRead(line, format, ¤t); | |
| enforce(line.empty, text("Leftover characters in record: `", | |
| line, "'")); | |
| } | |
| } | |
| } | |
| template byRecord(Fields...) | |
| { | |
| auto byRecord(File f, string format) | |
| { | |
| return typeof(return)(f, format); | |
| } | |
| } | |
| /** | |
| Encapsulates a `FILE*`. Generally D does not attempt to provide | |
| thin wrappers over equivalent functions in the C standard library, but | |
| manipulating `FILE*` values directly is unsafe and error-prone in | |
| many ways. The `File` type ensures safe manipulation, automatic | |
| file closing, and a lot of convenience. | |
| The underlying `FILE*` handle is maintained in a reference-counted | |
| manner, such that as soon as the last `File` variable bound to a | |
| given `FILE*` goes out of scope, the underlying `FILE*` is | |
| automatically closed. | |
| Example: | |
| ---- | |
| // test.d | |
| import std.stdio; | |
| void main(string[] args) | |
| { | |
| auto f = File("test.txt", "w"); // open for writing | |
| f.write("Hello"); | |
| if (args.length > 1) | |
| { | |
| auto g = f; // now g and f write to the same file | |
| // internal reference count is 2 | |
| g.write(", ", args[1]); | |
| // g exits scope, reference count decreases to 1 | |
| } | |
| f.writeln("!"); | |
| // f exits scope, reference count falls to zero, | |
| // underlying `FILE*` is closed. | |
| } | |
| ---- | |
| $(CONSOLE | |
| % rdmd test.d Jimmy | |
| % cat test.txt | |
| Hello, Jimmy! | |
| % __ | |
| ) | |
| */ | |
| struct File | |
| { | |
| import core.atomic : atomicOp, atomicStore, atomicLoad; | |
| import std.range.primitives : ElementEncodingType; | |
| import std.traits : isScalarType, isArray; | |
| enum Orientation { unknown, narrow, wide } | |
| private struct Impl | |
| { | |
| FILE * handle = null; // Is null iff this Impl is closed by another File | |
| shared uint refs = uint.max / 2; | |
| bool isPopened; // true iff the stream has been created by popen() | |
| Orientation orientation; | |
| } | |
| private Impl* _p; | |
| private string _name; | |
| package this(FILE* handle, string name, uint refs = 1, bool isPopened = false) @trusted @nogc nothrow | |
| { | |
| import core.stdc.stdlib : malloc; | |
| assert(!_p); | |
| _p = cast(Impl*) malloc(Impl.sizeof); | |
| if (!_p) | |
| { | |
| import core.exception : onOutOfMemoryError; | |
| onOutOfMemoryError(); | |
| } | |
| initImpl(handle, name, refs, isPopened); | |
| } | |
| private void initImpl(FILE* handle, string name, uint refs = 1, bool isPopened = false) @nogc nothrow pure @safe | |
| { | |
| assert(_p); | |
| _p.handle = handle; | |
| atomicStore(_p.refs, refs); | |
| _p.isPopened = isPopened; | |
| _p.orientation = Orientation.unknown; | |
| _name = name; | |
| } | |
| /** | |
| Constructor taking the name of the file to open and the open mode. | |
| Copying one `File` object to another results in the two `File` | |
| objects referring to the same underlying file. | |
| The destructor automatically closes the file as soon as no `File` | |
| object refers to it anymore. | |
| Params: | |
| name = range or string representing the file _name | |
| stdioOpenmode = range or string represting the open mode | |
| (with the same semantics as in the C standard library | |
| $(CSTDIO fopen) function) | |
| Throws: `ErrnoException` if the file could not be opened. | |
| */ | |
| this(string name, scope const(char)[] stdioOpenmode = "rb") @safe | |
| { | |
| import std.conv : text; | |
| import std.exception : errnoEnforce; | |
| this(errnoEnforce(_fopen(name, stdioOpenmode), | |
| text("Cannot open file `", name, "' in mode `", | |
| stdioOpenmode, "'")), | |
| name); | |
| // MSVCRT workaround (https://issues.dlang.org/show_bug.cgi?id=14422) | |
| version (CRuntime_Microsoft) | |
| { | |
| setAppendWin(stdioOpenmode); | |
| } | |
| } | |
| /// ditto | |
| this(R1, R2)(R1 name) | |
| if (isSomeFiniteCharInputRange!R1) | |
| { | |
| import std.conv : to; | |
| this(name.to!string, "rb"); | |
| } | |
| /// ditto | |
| this(R1, R2)(R1 name, R2 mode) | |
| if (isSomeFiniteCharInputRange!R1 && | |
| isSomeFiniteCharInputRange!R2) | |
| { | |
| import std.conv : to; | |
| this(name.to!string, mode.to!string); | |
| } | |
| @safe unittest | |
| { | |
| static import std.file; | |
| import std.utf : byChar; | |
| auto deleteme = testFilename(); | |
| auto f = File(deleteme.byChar, "w".byChar); | |
| f.close(); | |
| std.file.remove(deleteme); | |
| } | |
| ~this() @safe | |
| { | |
| detach(); | |
| } | |
| this(this) @safe pure nothrow @nogc | |
| { | |
| if (!_p) return; | |
| assert(atomicLoad(_p.refs)); | |
| atomicOp!"+="(_p.refs, 1); | |
| } | |
| /** | |
| Assigns a file to another. The target of the assignment gets detached | |
| from whatever file it was attached to, and attaches itself to the new | |
| file. | |
| */ | |
| ref File opAssign(File rhs) @safe return | |
| { | |
| import std.algorithm.mutation : swap; | |
| swap(this, rhs); | |
| return this; | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=20129 | |
| @safe unittest | |
| { | |
| File[int] aa; | |
| aa.require(0, File.init); | |
| } | |
| /** | |
| Detaches from the current file (throwing on failure), and then attempts to | |
| _open file `name` with mode `stdioOpenmode`. The mode has the | |
| same semantics as in the C standard library $(CSTDIO fopen) function. | |
| Throws: `ErrnoException` in case of error. | |
| */ | |
| void open(string name, scope const(char)[] stdioOpenmode = "rb") @trusted | |
| { | |
| resetFile(name, stdioOpenmode, false); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=20585 | |
| @system unittest | |
| { | |
| File f; | |
| try | |
| f.open("doesn't exist"); | |
| catch (Exception _e) | |
| { | |
| } | |
| assert(!f.isOpen); | |
| f.close(); // to check not crash here | |
| } | |
| private void resetFile(string name, scope const(char)[] stdioOpenmode, bool isPopened) @trusted | |
| { | |
| import core.stdc.stdlib : malloc; | |
| import std.exception : enforce; | |
| import std.conv : text; | |
| import std.exception : errnoEnforce; | |
| if (_p !is null) | |
| { | |
| detach(); | |
| } | |
| FILE* handle; | |
| version (Posix) | |
| { | |
| if (isPopened) | |
| { | |
| errnoEnforce(handle = _popen(name, stdioOpenmode), | |
| "Cannot run command `"~name~"'"); | |
| } | |
| else | |
| { | |
| errnoEnforce(handle = _fopen(name, stdioOpenmode), | |
| text("Cannot open file `", name, "' in mode `", | |
| stdioOpenmode, "'")); | |
| } | |
| } | |
| else | |
| { | |
| assert(isPopened == false); | |
| errnoEnforce(handle = _fopen(name, stdioOpenmode), | |
| text("Cannot open file `", name, "' in mode `", | |
| stdioOpenmode, "'")); | |
| } | |
| _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); | |
| initImpl(handle, name, 1, isPopened); | |
| version (CRuntime_Microsoft) | |
| { | |
| setAppendWin(stdioOpenmode); | |
| } | |
| } | |
| private void closeHandles() @trusted | |
| { | |
| assert(_p); | |
| import std.exception : errnoEnforce; | |
| version (Posix) | |
| { | |
| import core.sys.posix.stdio : pclose; | |
| import std.format : format; | |
| if (_p.isPopened) | |
| { | |
| auto res = pclose(_p.handle); | |
| errnoEnforce(res != -1, | |
| "Could not close pipe `"~_name~"'"); | |
| _p.handle = null; | |
| return; | |
| } | |
| } | |
| if (_p.handle) | |
| { | |
| auto handle = _p.handle; | |
| _p.handle = null; | |
| // fclose disassociates the FILE* even in case of error (https://issues.dlang.org/show_bug.cgi?id=19751) | |
| errnoEnforce(.fclose(handle) == 0, | |
| "Could not close file `"~_name~"'"); | |
| } | |
| } | |
| version (CRuntime_Microsoft) | |
| { | |
| private void setAppendWin(scope const(char)[] stdioOpenmode) @safe | |
| { | |
| bool append, update; | |
| foreach (c; stdioOpenmode) | |
| if (c == 'a') | |
| append = true; | |
| else | |
| if (c == '+') | |
| update = true; | |
| if (append && !update) | |
| seek(size); | |
| } | |
| } | |
| /** | |
| Reuses the `File` object to either open a different file, or change | |
| the file mode. If `name` is `null`, the mode of the currently open | |
| file is changed; otherwise, a new file is opened, reusing the C | |
| `FILE*`. The function has the same semantics as in the C standard | |
| library $(CSTDIO freopen) function. | |
| Note: Calling `reopen` with a `null` `name` is not implemented | |
| in all C runtimes. | |
| Throws: `ErrnoException` in case of error. | |
| */ | |
| void reopen(string name, scope const(char)[] stdioOpenmode = "rb") @trusted | |
| { | |
| import std.conv : text; | |
| import std.exception : enforce, errnoEnforce; | |
| import std.internal.cstring : tempCString; | |
| enforce(isOpen, "Attempting to reopen() an unopened file"); | |
| auto namez = (name == null ? _name : name).tempCString!FSChar(); | |
| auto modez = stdioOpenmode.tempCString!FSChar(); | |
| FILE* fd = _p.handle; | |
| version (Windows) | |
| fd = _wfreopen(namez, modez, fd); | |
| else | |
| fd = freopen(namez, modez, fd); | |
| errnoEnforce(fd, name | |
| ? text("Cannot reopen file `", name, "' in mode `", stdioOpenmode, "'") | |
| : text("Cannot reopen file in mode `", stdioOpenmode, "'")); | |
| if (name !is null) | |
| _name = name; | |
| } | |
| @safe unittest // Test changing filename | |
| { | |
| import std.exception : assertThrown, assertNotThrown; | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "foo"); | |
| scope(exit) std.file.remove(deleteme); | |
| auto f = File(deleteme); | |
| assert(f.readln() == "foo"); | |
| auto deleteme2 = testFilename(); | |
| std.file.write(deleteme2, "bar"); | |
| scope(exit) std.file.remove(deleteme2); | |
| f.reopen(deleteme2); | |
| assert(f.name == deleteme2); | |
| assert(f.readln() == "bar"); | |
| f.close(); | |
| } | |
| version (CRuntime_Microsoft) {} else // Not implemented | |
| @safe unittest // Test changing mode | |
| { | |
| import std.exception : assertThrown, assertNotThrown; | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "foo"); | |
| scope(exit) std.file.remove(deleteme); | |
| auto f = File(deleteme, "r+"); | |
| assert(f.readln() == "foo"); | |
| f.reopen(null, "w"); | |
| f.write("bar"); | |
| f.seek(0); | |
| f.reopen(null, "a"); | |
| f.write("baz"); | |
| assert(f.name == deleteme); | |
| f.close(); | |
| assert(std.file.readText(deleteme) == "barbaz"); | |
| } | |
| /** | |
| Detaches from the current file (throwing on failure), and then runs a command | |
| by calling the C standard library function $(HTTP | |
| pubs.opengroup.org/onlinepubs/7908799/xsh/popen.html, popen). | |
| Throws: `ErrnoException` in case of error. | |
| */ | |
| version (Posix) void popen(string command, scope const(char)[] stdioOpenmode = "r") @safe | |
| { | |
| resetFile(command, stdioOpenmode ,true); | |
| } | |
| /** | |
| First calls `detach` (throwing on failure), then attempts to | |
| associate the given file descriptor with the `File`, and sets the file's name to `null`. | |
| The mode must be compatible with the mode of the file descriptor. | |
| Throws: `ErrnoException` in case of error. | |
| Params: | |
| fd = File descriptor to associate with this `File`. | |
| stdioOpenmode = Mode to associate with this File. The mode has the same | |
| semantics as in the POSIX library function $(HTTP | |
| pubs.opengroup.org/onlinepubs/7908799/xsh/fdopen.html, fdopen) | |
| and must be compatible with `fd`. | |
| */ | |
| void fdopen(int fd, scope const(char)[] stdioOpenmode = "rb") @safe | |
| { | |
| fdopen(fd, stdioOpenmode, null); | |
| } | |
| package void fdopen(int fd, scope const(char)[] stdioOpenmode, string name) @trusted | |
| { | |
| import std.exception : errnoEnforce; | |
| import std.internal.cstring : tempCString; | |
| auto modez = stdioOpenmode.tempCString(); | |
| detach(); | |
| version (CRuntime_Microsoft) | |
| { | |
| auto fp = _fdopen(fd, modez); | |
| errnoEnforce(fp); | |
| } | |
| else version (Posix) | |
| { | |
| import core.sys.posix.stdio : fdopen; | |
| auto fp = fdopen(fd, modez); | |
| errnoEnforce(fp); | |
| } | |
| else | |
| static assert(0, "no fdopen() available"); | |
| this = File(fp, name); | |
| } | |
| // Declare a dummy HANDLE to allow generating documentation | |
| // for Windows-only methods. | |
| version (StdDdoc) { version (Windows) {} else alias HANDLE = int; } | |
| /** | |
| First calls `detach` (throwing on failure), and then attempts to | |
| associate the given Windows `HANDLE` with the `File`. The mode must | |
| be compatible with the access attributes of the handle. Windows only. | |
| Throws: `ErrnoException` in case of error. | |
| */ | |
| version (StdDdoc) | |
| void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode); | |
| version (Windows) | |
| void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode) | |
| { | |
| import core.stdc.stdint : intptr_t; | |
| import std.exception : errnoEnforce; | |
| import std.format : format; | |
| // Create file descriptors from the handles | |
| int mode; | |
| modeLoop: | |
| foreach (c; stdioOpenmode) | |
| switch (c) | |
| { | |
| case 'r': mode |= _O_RDONLY; break; | |
| case '+': mode &=~_O_RDONLY; break; | |
| case 'a': mode |= _O_APPEND; break; | |
| case 'b': mode |= _O_BINARY; break; | |
| case 't': mode |= _O_TEXT; break; | |
| case ',': break modeLoop; | |
| default: break; | |
| } | |
| auto fd = _open_osfhandle(cast(intptr_t) handle, mode); | |
| errnoEnforce(fd >= 0, "Cannot open Windows HANDLE"); | |
| fdopen(fd, stdioOpenmode, "HANDLE(%s)".format(handle)); | |
| } | |
| /** Returns `true` if the file is opened. */ | |
| @property bool isOpen() const @safe pure nothrow | |
| { | |
| return _p !is null && _p.handle; | |
| } | |
| /** | |
| Returns `true` if the file is at end (see $(CSTDIO feof)). | |
| Throws: `Exception` if the file is not opened. | |
| */ | |
| @property bool eof() const @trusted pure | |
| { | |
| import std.exception : enforce; | |
| enforce(_p && _p.handle, "Calling eof() against an unopened file."); | |
| return .feof(cast(FILE*) _p.handle) != 0; | |
| } | |
| /** | |
| Returns the name last used to initialize this `File`, if any. | |
| Some functions that create or initialize the `File` set the name field to `null`. | |
| Examples include $(LREF tmpfile), $(LREF wrapFile), and $(LREF fdopen). See the | |
| documentation of those functions for details. | |
| Returns: The name last used to initialize this this file, or `null` otherwise. | |
| */ | |
| @property string name() const @safe pure nothrow return | |
| { | |
| return _name; | |
| } | |
| /** | |
| If the file is closed or not yet opened, returns `true`. Otherwise, returns | |
| $(CSTDIO ferror) for the file handle. | |
| */ | |
| @property bool error() const @trusted pure nothrow | |
| { | |
| return !isOpen || .ferror(cast(FILE*) _p.handle); | |
| } | |
| @safe unittest | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=12349 | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| auto f = File(deleteme, "w"); | |
| scope(exit) std.file.remove(deleteme); | |
| f.close(); | |
| assert(f.error); | |
| } | |
| /** | |
| Detaches from the underlying file. If the sole owner, calls `close`. | |
| Throws: `ErrnoException` on failure if closing the file. | |
| */ | |
| void detach() @trusted | |
| { | |
| import core.stdc.stdlib : free; | |
| if (!_p) return; | |
| scope(exit) _p = null; | |
| if (atomicOp!"-="(_p.refs, 1) == 0) | |
| { | |
| scope(exit) free(_p); | |
| closeHandles(); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| scope(exit) std.file.remove(deleteme); | |
| auto f = File(deleteme, "w"); | |
| { | |
| auto f2 = f; | |
| f2.detach(); | |
| } | |
| assert(f._p.refs == 1); | |
| f.close(); | |
| } | |
| /** | |
| If the file was closed or not yet opened, succeeds vacuously. Otherwise | |
| closes the file (by calling $(CSTDIO fclose)), | |
| throwing on error. Even if an exception is thrown, afterwards the $(D | |
| File) object is empty. This is different from `detach` in that it | |
| always closes the file; consequently, all other `File` objects | |
| referring to the same handle will see a closed file henceforth. | |
| Throws: `ErrnoException` on error. | |
| */ | |
| void close() @trusted | |
| { | |
| import core.stdc.stdlib : free; | |
| import std.exception : errnoEnforce; | |
| if (!_p) return; // succeed vacuously | |
| scope(exit) | |
| { | |
| if (atomicOp!"-="(_p.refs, 1) == 0) | |
| free(_p); | |
| _p = null; // start a new life | |
| } | |
| if (!_p.handle) return; // Impl is closed by another File | |
| scope(exit) _p.handle = null; // nullify the handle anyway | |
| closeHandles(); | |
| } | |
| /** | |
| If the file is closed or not yet opened, succeeds vacuously. Otherwise, returns | |
| $(CSTDIO clearerr) for the file handle. | |
| */ | |
| void clearerr() @safe pure nothrow | |
| { | |
| _p is null || _p.handle is null || | |
| .clearerr(_p.handle); | |
| } | |
| /** | |
| Flushes the C `FILE` buffers. | |
| Calls $(CSTDIO fflush) for the file handle. | |
| Throws: `Exception` if the file is not opened or if the call to `fflush` fails. | |
| */ | |
| void flush() @trusted | |
| { | |
| import std.exception : enforce, errnoEnforce; | |
| enforce(isOpen, "Attempting to flush() in an unopened file"); | |
| errnoEnforce(.fflush(_p.handle) == 0); | |
| } | |
| @safe unittest | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=12349 | |
| import std.exception : assertThrown; | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| auto f = File(deleteme, "w"); | |
| scope(exit) std.file.remove(deleteme); | |
| f.close(); | |
| assertThrown(f.flush()); | |
| } | |
| /** | |
| Forces any data buffered by the OS to be written to disk. | |
| Call $(LREF flush) before calling this function to flush the C `FILE` buffers first. | |
| This function calls | |
| $(HTTP msdn.microsoft.com/en-us/library/windows/desktop/aa364439%28v=vs.85%29.aspx, | |
| `FlushFileBuffers`) on Windows, | |
| $(HTTP developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html, | |
| `F_FULLFSYNC fcntl`) on Darwin and | |
| $(HTTP pubs.opengroup.org/onlinepubs/7908799/xsh/fsync.html, | |
| `fsync`) on POSIX for the file handle. | |
| Throws: `Exception` if the file is not opened or if the OS call fails. | |
| */ | |
| void sync() @trusted | |
| { | |
| import std.exception : enforce; | |
| enforce(isOpen, "Attempting to sync() an unopened file"); | |
| version (Windows) | |
| { | |
| import core.sys.windows.winbase : FlushFileBuffers; | |
| wenforce(FlushFileBuffers(windowsHandle), "FlushFileBuffers failed"); | |
| } | |
| else version (Darwin) | |
| { | |
| import core.sys.darwin.fcntl : fcntl, F_FULLFSYNC; | |
| import std.exception : errnoEnforce; | |
| errnoEnforce(fcntl(fileno, F_FULLFSYNC, 0) != -1, "fcntl failed"); | |
| } | |
| else | |
| { | |
| import core.sys.posix.unistd : fsync; | |
| import std.exception : errnoEnforce; | |
| errnoEnforce(fsync(fileno) == 0, "fsync failed"); | |
| } | |
| } | |
| /** | |
| Calls $(CSTDIO fread) for the | |
| file handle. The number of items to read and the size of | |
| each item is inferred from the size and type of the input array, respectively. | |
| Returns: The slice of `buffer` containing the data that was actually read. | |
| This will be shorter than `buffer` if EOF was reached before the buffer | |
| could be filled. If the buffer is empty, it will be returned. | |
| Throws: `ErrnoException` if the file is not opened or the call to `fread` fails. | |
| `rawRead` always reads in binary mode on Windows. | |
| */ | |
| T[] rawRead(T)(T[] buffer) | |
| { | |
| import std.exception : enforce, errnoEnforce; | |
| if (!buffer.length) | |
| return buffer; | |
| enforce(isOpen, "Attempting to read from an unopened file"); | |
| version (Windows) | |
| { | |
| immutable fileno_t fd = .fileno(_p.handle); | |
| immutable mode = ._setmode(fd, _O_BINARY); | |
| scope(exit) ._setmode(fd, mode); | |
| } | |
| immutable freadResult = trustedFread(_p.handle, buffer); | |
| assert(freadResult <= buffer.length); // fread return guarantee | |
| if (freadResult != buffer.length) // error or eof | |
| { | |
| errnoEnforce(!error); | |
| return buffer[0 .. freadResult]; | |
| } | |
| return buffer; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| static import std.file; | |
| auto testFile = std.file.deleteme(); | |
| std.file.write(testFile, "\r\n\n\r\n"); | |
| scope(exit) std.file.remove(testFile); | |
| auto f = File(testFile, "r"); | |
| auto buf = f.rawRead(new char[5]); | |
| f.close(); | |
| assert(buf == "\r\n\n\r\n"); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=24685 | |
| static assert(!__traits(compiles, (File f) @safe { int*[1] bar; f.rawRead(bar[]); })); | |
| // https://issues.dlang.org/show_bug.cgi?id=21729 | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| File f; | |
| ubyte[1] u; | |
| assertThrown(f.rawRead(u)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=21728 | |
| @system unittest | |
| { | |
| static if (__traits(compiles, { import std.process : pipe; })) // not available for iOS | |
| { | |
| import std.process : pipe; | |
| import std.exception : assertThrown; | |
| auto p = pipe(); | |
| p.readEnd.close; | |
| ubyte[1] u; | |
| assertThrown(p.readEnd.rawRead(u)); | |
| } | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=13893 | |
| @system unittest | |
| { | |
| import std.exception : assertNotThrown; | |
| File f; | |
| ubyte[0] u; | |
| assertNotThrown(f.rawRead(u)); | |
| } | |
| /** | |
| Calls $(CSTDIO fwrite) for the file | |
| handle. The number of items to write and the size of each | |
| item is inferred from the size and type of the input array, respectively. An | |
| error is thrown if the buffer could not be written in its entirety. | |
| `rawWrite` always writes in binary mode on Windows. | |
| Throws: `ErrnoException` if the file is not opened or if the call to `fwrite` fails. | |
| */ | |
| void rawWrite(T)(in T[] buffer) | |
| { | |
| import std.conv : text; | |
| import std.exception : errnoEnforce; | |
| version (Windows) | |
| { | |
| immutable fileno_t fd = .fileno(_p.handle); | |
| immutable oldMode = ._setmode(fd, _O_BINARY); | |
| if (oldMode != _O_BINARY) | |
| { | |
| // need to flush the data that was written with the original mode | |
| ._setmode(fd, oldMode); | |
| flush(); // before changing translation mode ._setmode(fd, _O_BINARY); | |
| ._setmode(fd, _O_BINARY); | |
| } | |
| scope (exit) | |
| { | |
| if (oldMode != _O_BINARY) | |
| { | |
| flush(); | |
| ._setmode(fd, oldMode); | |
| } | |
| } | |
| } | |
| auto result = trustedFwrite(_p.handle, buffer); | |
| if (result == result.max) result = 0; | |
| errnoEnforce(result == buffer.length, | |
| text("Wrote ", result, " instead of ", buffer.length, | |
| " objects of type ", T.stringof, " to file `", | |
| _name, "'")); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| static import std.file; | |
| auto testFile = std.file.deleteme(); | |
| auto f = File(testFile, "w"); | |
| scope(exit) std.file.remove(testFile); | |
| f.rawWrite("\r\n\n\r\n"); | |
| f.close(); | |
| assert(std.file.read(testFile) == "\r\n\n\r\n"); | |
| } | |
| /** | |
| Calls $(CSTDIO fseek) | |
| for the file handle to move its position indicator. | |
| Params: | |
| offset = Binary files: Number of bytes to offset from origin.$(BR) | |
| Text files: Either zero, or a value returned by $(LREF tell). | |
| origin = Binary files: Position used as reference for the offset, must be | |
| one of $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio), | |
| $(REF_ALTTEXT SEEK_CUR, SEEK_CUR, core,stdc,stdio) or | |
| $(REF_ALTTEXT SEEK_END, SEEK_END, core,stdc,stdio).$(BR) | |
| Text files: Shall necessarily be | |
| $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio). | |
| Throws: `Exception` if the file is not opened. | |
| `ErrnoException` if the call to `fseek` fails. | |
| */ | |
| void seek(long offset, int origin = SEEK_SET) @trusted | |
| { | |
| import std.conv : to, text; | |
| import std.exception : enforce, errnoEnforce; | |
| // Some libc sanitize the whence input (e.g. glibc), but some don't, | |
| // e.g. Microsoft runtime crashes on an invalid origin, | |
| // and Musl additionally accept SEEK_DATA & SEEK_HOLE (Linux extension). | |
| // To provide a consistent behavior cross platform, we use the glibc check | |
| // See also https://issues.dlang.org/show_bug.cgi?id=19797 | |
| enforce(origin == SEEK_SET || origin == SEEK_CUR || origin == SEEK_END, | |
| "Invalid `origin` argument passed to `seek`, must be one of: SEEK_SET, SEEK_CUR, SEEK_END"); | |
| enforce(isOpen, "Attempting to seek() in an unopened file"); | |
| version (Windows) | |
| { | |
| version (CRuntime_Microsoft) | |
| { | |
| alias fseekFun = _fseeki64; | |
| alias off_t = long; | |
| } | |
| else | |
| { | |
| alias fseekFun = fseek; | |
| alias off_t = int; | |
| } | |
| } | |
| else version (Posix) | |
| { | |
| import core.sys.posix.stdio : fseeko, off_t; | |
| alias fseekFun = fseeko; | |
| } | |
| errnoEnforce(fseekFun(_p.handle, to!off_t(offset), origin) == 0, | |
| "Could not seek in file `"~_name~"'"); | |
| } | |
| @system unittest | |
| { | |
| import std.conv : text; | |
| static import std.file; | |
| import std.exception; | |
| auto deleteme = testFilename(); | |
| auto f = File(deleteme, "w+"); | |
| scope(exit) { f.close(); std.file.remove(deleteme); } | |
| f.rawWrite("abcdefghijklmnopqrstuvwxyz"); | |
| f.seek(7); | |
| assert(f.readln() == "hijklmnopqrstuvwxyz"); | |
| version (CRuntime_Bionic) | |
| auto bigOffset = int.max - 100; | |
| else | |
| auto bigOffset = cast(ulong) int.max + 100; | |
| f.seek(bigOffset); | |
| assert(f.tell == bigOffset, text(f.tell)); | |
| // Uncomment the tests below only if you want to wait for | |
| // a long time | |
| // f.rawWrite("abcdefghijklmnopqrstuvwxyz"); | |
| // f.seek(-3, SEEK_END); | |
| // assert(f.readln() == "xyz"); | |
| assertThrown(f.seek(0, ushort.max)); | |
| } | |
| /** | |
| Calls $(CSTDIO ftell) | |
| for the managed file handle, which returns the current value of | |
| the position indicator of the file handle. | |
| Throws: `Exception` if the file is not opened. | |
| `ErrnoException` if the call to `ftell` fails. | |
| */ | |
| @property ulong tell() const @trusted | |
| { | |
| import std.exception : enforce, errnoEnforce; | |
| enforce(isOpen, "Attempting to tell() in an unopened file"); | |
| version (Windows) | |
| { | |
| version (CRuntime_Microsoft) | |
| immutable result = _ftelli64(cast(FILE*) _p.handle); | |
| else | |
| immutable result = ftell(cast(FILE*) _p.handle); | |
| } | |
| else version (Posix) | |
| { | |
| import core.sys.posix.stdio : ftello; | |
| immutable result = ftello(cast(FILE*) _p.handle); | |
| } | |
| errnoEnforce(result != -1, | |
| "Query ftell() failed for file `"~_name~"'"); | |
| return result; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.conv : text; | |
| static import std.file; | |
| auto testFile = std.file.deleteme(); | |
| std.file.write(testFile, "abcdefghijklmnopqrstuvwqxyz"); | |
| scope(exit) { std.file.remove(testFile); } | |
| auto f = File(testFile); | |
| auto a = new ubyte[4]; | |
| f.rawRead(a); | |
| assert(f.tell == 4, text(f.tell)); | |
| } | |
| /** | |
| Calls $(CSTDIO rewind) for the file handle. | |
| Throws: `Exception` if the file is not opened. | |
| */ | |
| void rewind() @safe | |
| { | |
| import std.exception : enforce; | |
| enforce(isOpen, "Attempting to rewind() an unopened file"); | |
| .rewind(_p.handle); | |
| } | |
| /** | |
| Calls $(CSTDIO setvbuf) for the file handle. | |
| Throws: `Exception` if the file is not opened. | |
| `ErrnoException` if the call to `setvbuf` fails. | |
| */ | |
| void setvbuf(size_t size, int mode = _IOFBF) @trusted | |
| { | |
| import std.exception : enforce, errnoEnforce; | |
| enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); | |
| errnoEnforce(.setvbuf(_p.handle, null, mode, size) == 0, | |
| "Could not set buffering for file `"~_name~"'"); | |
| } | |
| /** | |
| Calls $(CSTDIO setvbuf) for the file handle. | |
| Throws: `Exception` if the file is not opened. | |
| `ErrnoException` if the call to `setvbuf` fails. | |
| */ | |
| void setvbuf(void[] buf, int mode = _IOFBF) @trusted | |
| { | |
| import std.exception : enforce, errnoEnforce; | |
| enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); | |
| errnoEnforce(.setvbuf(_p.handle, | |
| cast(char*) buf.ptr, mode, buf.length) == 0, | |
| "Could not set buffering for file `"~_name~"'"); | |
| } | |
| version (Windows) | |
| { | |
| import core.sys.windows.winbase : OVERLAPPED; | |
| import core.sys.windows.winnt : BOOL, ULARGE_INTEGER; | |
| import std.windows.syserror : wenforce; | |
| private BOOL lockImpl(alias F, Flags...)(ulong start, ulong length, | |
| Flags flags) | |
| { | |
| if (!start && !length) | |
| length = ulong.max; | |
| ULARGE_INTEGER liStart = void, liLength = void; | |
| liStart.QuadPart = start; | |
| liLength.QuadPart = length; | |
| OVERLAPPED overlapped; | |
| overlapped.Offset = liStart.LowPart; | |
| overlapped.OffsetHigh = liStart.HighPart; | |
| overlapped.hEvent = null; | |
| return F(windowsHandle, flags, 0, liLength.LowPart, | |
| liLength.HighPart, &overlapped); | |
| } | |
| } | |
| version (Posix) | |
| { | |
| private int lockImpl(int operation, short l_type, | |
| ulong start, ulong length) | |
| { | |
| import core.sys.posix.fcntl : fcntl, flock, off_t; | |
| import core.sys.posix.unistd : getpid; | |
| import std.conv : to; | |
| flock fl = void; | |
| fl.l_type = l_type; | |
| fl.l_whence = SEEK_SET; | |
| fl.l_start = to!off_t(start); | |
| fl.l_len = to!off_t(length); | |
| fl.l_pid = getpid(); | |
| return fcntl(fileno, operation, &fl); | |
| } | |
| } | |
| /** | |
| Locks the specified file segment. If the file segment is already locked | |
| by another process, waits until the existing lock is released. | |
| If both `start` and `length` are zero, the entire file is locked. | |
| Locks created using `lock` and `tryLock` have the following properties: | |
| $(UL | |
| $(LI All locks are automatically released when the process terminates.) | |
| $(LI Locks are not inherited by child processes.) | |
| $(LI Closing a file will release all locks associated with the file. On POSIX, | |
| even locks acquired via a different `File` will be released as well.) | |
| $(LI Not all NFS implementations correctly implement file locking.) | |
| ) | |
| */ | |
| void lock(LockType lockType = LockType.readWrite, | |
| ulong start = 0, ulong length = 0) | |
| { | |
| import std.exception : enforce; | |
| enforce(isOpen, "Attempting to call lock() on an unopened file"); | |
| version (Posix) | |
| { | |
| import core.sys.posix.fcntl : F_RDLCK, F_SETLKW, F_WRLCK; | |
| import std.exception : errnoEnforce; | |
| immutable short type = lockType == LockType.readWrite | |
| ? F_WRLCK : F_RDLCK; | |
| errnoEnforce(lockImpl(F_SETLKW, type, start, length) != -1, | |
| "Could not set lock for file `"~_name~"'"); | |
| } | |
| else | |
| version (Windows) | |
| { | |
| import core.sys.windows.winbase : LockFileEx, LOCKFILE_EXCLUSIVE_LOCK; | |
| immutable type = lockType == LockType.readWrite ? | |
| LOCKFILE_EXCLUSIVE_LOCK : 0; | |
| wenforce(lockImpl!LockFileEx(start, length, type), | |
| "Could not set lock for file `"~_name~"'"); | |
| } | |
| else | |
| static assert(false); | |
| } | |
| /** | |
| Attempts to lock the specified file segment. | |
| If both `start` and `length` are zero, the entire file is locked. | |
| Returns: `true` if the lock was successful, and `false` if the | |
| specified file segment was already locked. | |
| */ | |
| bool tryLock(LockType lockType = LockType.readWrite, | |
| ulong start = 0, ulong length = 0) | |
| { | |
| import std.exception : enforce; | |
| enforce(isOpen, "Attempting to call tryLock() on an unopened file"); | |
| version (Posix) | |
| { | |
| import core.stdc.errno : EACCES, EAGAIN, errno; | |
| import core.sys.posix.fcntl : F_RDLCK, F_SETLK, F_WRLCK; | |
| import std.exception : errnoEnforce; | |
| immutable short type = lockType == LockType.readWrite | |
| ? F_WRLCK : F_RDLCK; | |
| immutable res = lockImpl(F_SETLK, type, start, length); | |
| if (res == -1 && (errno == EACCES || errno == EAGAIN)) | |
| return false; | |
| errnoEnforce(res != -1, "Could not set lock for file `"~_name~"'"); | |
| return true; | |
| } | |
| else | |
| version (Windows) | |
| { | |
| import core.sys.windows.winbase : GetLastError, LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, | |
| LOCKFILE_FAIL_IMMEDIATELY; | |
| import core.sys.windows.winerror : ERROR_IO_PENDING, ERROR_LOCK_VIOLATION; | |
| immutable type = lockType == LockType.readWrite | |
| ? LOCKFILE_EXCLUSIVE_LOCK : 0; | |
| immutable res = lockImpl!LockFileEx(start, length, | |
| type | LOCKFILE_FAIL_IMMEDIATELY); | |
| if (!res && (GetLastError() == ERROR_IO_PENDING | |
| || GetLastError() == ERROR_LOCK_VIOLATION)) | |
| return false; | |
| wenforce(res, "Could not set lock for file `"~_name~"'"); | |
| return true; | |
| } | |
| else | |
| static assert(false); | |
| } | |
| /** | |
| Removes the lock over the specified file segment. | |
| */ | |
| void unlock(ulong start = 0, ulong length = 0) | |
| { | |
| import std.exception : enforce; | |
| enforce(isOpen, "Attempting to call unlock() on an unopened file"); | |
| version (Posix) | |
| { | |
| import core.sys.posix.fcntl : F_SETLK, F_UNLCK; | |
| import std.exception : errnoEnforce; | |
| errnoEnforce(lockImpl(F_SETLK, F_UNLCK, start, length) != -1, | |
| "Could not remove lock for file `"~_name~"'"); | |
| } | |
| else | |
| version (Windows) | |
| { | |
| import core.sys.windows.winbase : UnlockFileEx; | |
| wenforce(lockImpl!UnlockFileEx(start, length), | |
| "Could not remove lock for file `"~_name~"'"); | |
| } | |
| else | |
| static assert(false); | |
| } | |
| version (Windows) | |
| @system unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| scope(exit) std.file.remove(deleteme); | |
| auto f = File(deleteme, "wb"); | |
| assert(f.tryLock()); | |
| auto g = File(deleteme, "wb"); | |
| assert(!g.tryLock()); | |
| assert(!g.tryLock(LockType.read)); | |
| f.unlock(); | |
| f.lock(LockType.read); | |
| assert(!g.tryLock()); | |
| assert(g.tryLock(LockType.read)); | |
| f.unlock(); | |
| g.unlock(); | |
| } | |
| version (Posix) | |
| @system unittest | |
| { | |
| static if (__traits(compiles, { import std.process : spawnProcess; })) | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| scope(exit) std.file.remove(deleteme); | |
| // Since locks are per-process, we cannot test lock failures within | |
| // the same process. fork() is used to create a second process. | |
| static void runForked(void delegate() code) | |
| { | |
| import core.sys.posix.sys.wait : waitpid; | |
| import core.sys.posix.unistd : fork, _exit; | |
| int child, status; | |
| if ((child = fork()) == 0) | |
| { | |
| code(); | |
| _exit(0); | |
| } | |
| else | |
| { | |
| assert(waitpid(child, &status, 0) != -1); | |
| assert(status == 0, "Fork crashed"); | |
| } | |
| } | |
| auto f = File(deleteme, "w+b"); | |
| runForked | |
| ({ | |
| auto g = File(deleteme, "a+b"); | |
| assert(g.tryLock()); | |
| g.unlock(); | |
| assert(g.tryLock(LockType.read)); | |
| }); | |
| assert(f.tryLock()); | |
| runForked | |
| ({ | |
| auto g = File(deleteme, "a+b"); | |
| assert(!g.tryLock()); | |
| assert(!g.tryLock(LockType.read)); | |
| }); | |
| f.unlock(); | |
| f.lock(LockType.read); | |
| runForked | |
| ({ | |
| auto g = File(deleteme, "a+b"); | |
| assert(!g.tryLock()); | |
| assert(g.tryLock(LockType.read)); | |
| g.unlock(); | |
| }); | |
| f.unlock(); | |
| } // static if | |
| } // unittest | |
| /** | |
| Writes its arguments in text format to the file. | |
| Throws: `Exception` if the file is not opened. | |
| `ErrnoException` on an error writing to the file. | |
| */ | |
| void write(S...)(S args) | |
| { | |
| import std.traits : isBoolean, isIntegral, isAggregateType; | |
| import std.utf : UTFException; | |
| auto w = lockingTextWriter(); | |
| foreach (arg; args) | |
| { | |
| try | |
| { | |
| alias A = typeof(arg); | |
| static if (isAggregateType!A || is(A == enum)) | |
| { | |
| import std.format.write : formattedWrite; | |
| formattedWrite(w, "%s", arg); | |
| } | |
| else static if (isSomeString!A) | |
| { | |
| put(w, arg); | |
| } | |
| else static if (isIntegral!A) | |
| { | |
| import std.conv : toTextRange; | |
| toTextRange(arg, w); | |
| } | |
| else static if (isBoolean!A) | |
| { | |
| put(w, arg ? "true" : "false"); | |
| } | |
| else static if (isSomeChar!A) | |
| { | |
| put(w, arg); | |
| } | |
| else | |
| { | |
| import std.format.write : formattedWrite; | |
| // Most general case | |
| formattedWrite(w, "%s", arg); | |
| } | |
| } | |
| catch (UTFException e) | |
| { | |
| /* Reset the writer so that it doesn't throw another | |
| UTFException on destruction. */ | |
| w.highSurrogate = '\0'; | |
| throw e; | |
| } | |
| } | |
| } | |
| /** | |
| Writes its arguments in text format to the file, followed by a newline. | |
| Throws: `Exception` if the file is not opened. | |
| `ErrnoException` on an error writing to the file. | |
| */ | |
| void writeln(S...)(S args) | |
| { | |
| write(args, '\n'); | |
| } | |
| /** | |
| Writes its arguments in text format to the file, according to the | |
| format string fmt. | |
| Params: | |
| fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). | |
| When passed as a compile-time argument, the string will be statically checked | |
| against the argument types passed. | |
| args = Items to write. | |
| Throws: `Exception` if the file is not opened. | |
| `ErrnoException` on an error writing to the file. | |
| */ | |
| void writef(alias fmt, A...)(A args) | |
| if (isSomeString!(typeof(fmt))) | |
| { | |
| import std.format : checkFormatException; | |
| alias e = checkFormatException!(fmt, A); | |
| static assert(!e, e); | |
| return this.writef(fmt, args); | |
| } | |
| /// ditto | |
| void writef(Char, A...)(in Char[] fmt, A args) | |
| { | |
| import std.format.write : formattedWrite; | |
| formattedWrite(lockingTextWriter(), fmt, args); | |
| } | |
| /// Equivalent to `file.writef(fmt, args, '\n')`. | |
| void writefln(alias fmt, A...)(A args) | |
| if (isSomeString!(typeof(fmt))) | |
| { | |
| import std.format : checkFormatException; | |
| alias e = checkFormatException!(fmt, A); | |
| static assert(!e, e); | |
| return this.writefln(fmt, args); | |
| } | |
| /// ditto | |
| void writefln(Char, A...)(in Char[] fmt, A args) | |
| { | |
| import std.format.write : formattedWrite; | |
| auto w = lockingTextWriter(); | |
| formattedWrite(w, fmt, args); | |
| w.put('\n'); | |
| } | |
| /** | |
| Read line from the file handle and return it as a specified type. | |
| This version manages its own read buffer, which means one memory allocation per call. If you are not | |
| retaining a reference to the read data, consider the `File.readln(buf)` version, which may offer | |
| better performance as it can reuse its read buffer. | |
| Params: | |
| S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. | |
| terminator = Line terminator (by default, `'\n'`). | |
| Note: | |
| String terminators are not supported due to ambiguity with readln(buf) below. | |
| Returns: | |
| The line that was read, including the line terminator character. | |
| Throws: | |
| `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. | |
| Example: | |
| --- | |
| // Reads `stdin` and writes it to `stdout`. | |
| import std.stdio; | |
| void main() | |
| { | |
| string line; | |
| while ((line = stdin.readln()) !is null) | |
| write(line); | |
| } | |
| --- | |
| */ | |
| S readln(S = string)(dchar terminator = '\n') @safe | |
| if (isSomeString!S) | |
| { | |
| Unqual!(ElementEncodingType!S)[] buf; | |
| readln(buf, terminator); | |
| return (() @trusted => cast(S) buf)(); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| static import std.file; | |
| import std.meta : AliasSeq; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "hello\nworld\n"); | |
| scope(exit) std.file.remove(deleteme); | |
| static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) | |
| {{ | |
| auto witness = [ "hello\n", "world\n" ]; | |
| auto f = File(deleteme); | |
| uint i = 0; | |
| String buf; | |
| while ((buf = f.readln!String()).length) | |
| { | |
| assert(i < witness.length); | |
| assert(equal(buf, witness[i++])); | |
| } | |
| assert(i == witness.length); | |
| }} | |
| } | |
| @safe unittest | |
| { | |
| static import std.file; | |
| import std.typecons : Tuple; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "cześć \U0002000D"); | |
| scope(exit) std.file.remove(deleteme); | |
| uint[] lengths = [12,8,7]; | |
| static foreach (uint i, C; Tuple!(char, wchar, dchar).Types) | |
| {{ | |
| immutable(C)[] witness = "cześć \U0002000D"; | |
| auto buf = File(deleteme).readln!(immutable(C)[])(); | |
| assert(buf.length == lengths[i]); | |
| assert(buf == witness); | |
| }} | |
| } | |
| /** | |
| Read line from the file handle and write it to `buf[]`, including | |
| terminating character. | |
| This can be faster than $(D line = File.readln()) because you can reuse | |
| the buffer for each call. Note that reusing the buffer means that you | |
| must copy the previous contents if you wish to retain them. | |
| Params: | |
| buf = Buffer used to store the resulting line data. buf is | |
| enlarged if necessary, then set to the slice exactly containing the line. | |
| terminator = Line terminator (by default, `'\n'`). Use | |
| $(REF newline, std,ascii) for portability (unless the file was opened in | |
| text mode). | |
| Returns: | |
| 0 for end of file, otherwise number of characters read. | |
| The return value will always be equal to `buf.length`. | |
| Throws: `StdioException` on I/O error, or `UnicodeException` on Unicode | |
| conversion error. | |
| Example: | |
| --- | |
| // Read lines from `stdin` into a string | |
| // Ignore lines starting with '#' | |
| // Write the string to `stdout` | |
| import std.stdio; | |
| void main() | |
| { | |
| string output; | |
| char[] buf; | |
| while (stdin.readln(buf)) | |
| { | |
| if (buf[0] == '#') | |
| continue; | |
| output ~= buf; | |
| } | |
| write(output); | |
| } | |
| --- | |
| This method can be more efficient than the one in the previous example | |
| because `stdin.readln(buf)` reuses (if possible) memory allocated | |
| for `buf`, whereas $(D line = stdin.readln()) makes a new memory allocation | |
| for every line. | |
| For even better performance you can help `readln` by passing in a | |
| large buffer to avoid memory reallocations. This can be done by reusing the | |
| largest buffer returned by `readln`: | |
| Example: | |
| --- | |
| // Read lines from `stdin` and count words | |
| import std.array, std.stdio; | |
| void main() | |
| { | |
| char[] buf; | |
| size_t words = 0; | |
| while (!stdin.eof) | |
| { | |
| char[] line = buf; | |
| stdin.readln(line); | |
| if (line.length > buf.length) | |
| buf = line; | |
| words += line.split.length; | |
| } | |
| writeln(words); | |
| } | |
| --- | |
| This is actually what $(LREF byLine) does internally, so its usage | |
| is recommended if you want to process a complete file. | |
| */ | |
| size_t readln(C)(ref C[] buf, dchar terminator = '\n') @safe | |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) | |
| { | |
| import std.exception : enforce; | |
| static if (is(C == char)) | |
| { | |
| enforce(_p && _p.handle, "Attempt to read from an unopened file."); | |
| if (_p.orientation == Orientation.unknown) | |
| { | |
| import core.stdc.wchar_ : fwide; | |
| auto w = fwide(_p.handle, 0); | |
| if (w < 0) _p.orientation = Orientation.narrow; | |
| else if (w > 0) _p.orientation = Orientation.wide; | |
| } | |
| return readlnImpl(_p.handle, buf, terminator, _p.orientation); | |
| } | |
| else | |
| { | |
| string s = readln(terminator); | |
| if (!s.length) | |
| { | |
| buf = buf[0 .. 0]; | |
| return 0; | |
| } | |
| import std.utf : codeLength; | |
| buf.length = codeLength!C(s); | |
| size_t idx; | |
| foreach (C c; s) | |
| buf[idx++] = c; | |
| return buf.length; | |
| } | |
| } | |
| @safe unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "123\n456789"); | |
| scope(exit) std.file.remove(deleteme); | |
| auto file = File(deleteme); | |
| char[] buffer = new char[10]; | |
| char[] line = buffer; | |
| file.readln(line); | |
| auto beyond = line.length; | |
| buffer[beyond] = 'a'; | |
| file.readln(line); // should not write buffer beyond line | |
| assert(buffer[beyond] == 'a'); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=15293 | |
| @safe unittest | |
| { | |
| // @system due to readln | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "a\n\naa"); | |
| scope(exit) std.file.remove(deleteme); | |
| auto file = File(deleteme); | |
| char[] buffer; | |
| char[] line; | |
| file.readln(buffer, '\n'); | |
| line = buffer; | |
| file.readln(line, '\n'); | |
| line = buffer; | |
| file.readln(line, '\n'); | |
| assert(line[0 .. 1].capacity == 0); | |
| } | |
| /** ditto */ | |
| size_t readln(C, R)(ref C[] buf, R terminator) @safe | |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && | |
| isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) | |
| { | |
| import std.algorithm.mutation : swap; | |
| import std.algorithm.searching : endsWith; | |
| import std.range.primitives : back; | |
| auto last = terminator.back; | |
| C[] buf2; | |
| swap(buf, buf2); | |
| for (;;) | |
| { | |
| if (!readln(buf2, last) || endsWith(buf2, terminator)) | |
| { | |
| if (buf.empty) | |
| { | |
| buf = buf2; | |
| } | |
| else | |
| { | |
| buf ~= buf2; | |
| } | |
| break; | |
| } | |
| buf ~= buf2; | |
| } | |
| return buf.length; | |
| } | |
| @safe unittest | |
| { | |
| static import std.file; | |
| import std.typecons : Tuple; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "hello\n\rworld\nhow\n\rare ya"); | |
| scope(exit) std.file.remove(deleteme); | |
| foreach (C; Tuple!(char, wchar, dchar).Types) | |
| { | |
| immutable(C)[][] witness = [ "hello\n\r", "world\nhow\n\r", "are ya" ]; | |
| auto f = File(deleteme); | |
| uint i = 0; | |
| C[] buf; | |
| while (f.readln(buf, "\n\r")) | |
| { | |
| assert(i < witness.length); | |
| assert(buf == witness[i++]); | |
| } | |
| assert(buf.length == 0); | |
| } | |
| } | |
| /** | |
| * Reads formatted _data from the file using $(REF formattedRead, std,_format). | |
| * Params: | |
| * format = The $(REF_ALTTEXT format string, formattedWrite, std, _format). | |
| * When passed as a compile-time argument, the string will be statically checked | |
| * against the argument types passed. | |
| * data = Items to be read. | |
| * Returns: | |
| * Same as `formattedRead`: The number of variables filled. If the input range `r` ends early, | |
| * this number will be less than the number of variables provided. | |
| * Example: | |
| ---- | |
| // test.d | |
| void main() | |
| { | |
| import std.stdio; | |
| auto f = File("input"); | |
| foreach (_; 0 .. 3) | |
| { | |
| int a; | |
| f.readf!" %d"(a); | |
| writeln(++a); | |
| } | |
| } | |
| ---- | |
| $(CONSOLE | |
| % echo "1 2 3" > input | |
| % rdmd test.d | |
| 2 | |
| 3 | |
| 4 | |
| ) | |
| */ | |
| uint readf(alias format, Data...)(auto ref Data data) | |
| if (isSomeString!(typeof(format))) | |
| { | |
| import std.format : checkFormatException; | |
| alias e = checkFormatException!(format, Data); | |
| static assert(!e, e); | |
| return this.readf(format, data); | |
| } | |
| /// ditto | |
| uint readf(Data...)(scope const(char)[] format, auto ref Data data) | |
| { | |
| import std.format.read : formattedRead; | |
| assert(isOpen); | |
| auto input = LockingTextReader(this); | |
| return formattedRead(input, format, data); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| static import std.file; | |
| auto deleteme = std.file.deleteme(); | |
| std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); | |
| scope(exit) std.file.remove(deleteme); | |
| string s; | |
| auto f = File(deleteme); | |
| f.readf!"%s\n"(s); | |
| assert(s == "hello", "["~s~"]"); | |
| f.readf("%s\n", s); | |
| assert(s == "world", "["~s~"]"); | |
| bool b1, b2; | |
| f.readf("%s\n%s\n", b1, b2); | |
| assert(b1 == true && b2 == false); | |
| } | |
| // backwards compatibility with pointers | |
| @system unittest | |
| { | |
| // @system due to readf | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); | |
| scope(exit) std.file.remove(deleteme); | |
| string s; | |
| auto f = File(deleteme); | |
| f.readf("%s\n", &s); | |
| assert(s == "hello", "["~s~"]"); | |
| f.readf("%s\n", &s); | |
| assert(s == "world", "["~s~"]"); | |
| // https://issues.dlang.org/show_bug.cgi?id=11698 | |
| bool b1, b2; | |
| f.readf("%s\n%s\n", &b1, &b2); | |
| assert(b1 == true && b2 == false); | |
| } | |
| // backwards compatibility (mixed) | |
| @system unittest | |
| { | |
| // @system due to readf | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); | |
| scope(exit) std.file.remove(deleteme); | |
| string s1, s2; | |
| auto f = File(deleteme); | |
| f.readf("%s\n%s\n", s1, &s2); | |
| assert(s1 == "hello"); | |
| assert(s2 == "world"); | |
| // https://issues.dlang.org/show_bug.cgi?id=11698 | |
| bool b1, b2; | |
| f.readf("%s\n%s\n", &b1, b2); | |
| assert(b1 == true && b2 == false); | |
| } | |
| // Nice error of std.stdio.readf with newlines | |
| // https://issues.dlang.org/show_bug.cgi?id=12260 | |
| @system unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "1\n2"); | |
| scope(exit) std.file.remove(deleteme); | |
| int input; | |
| auto f = File(deleteme); | |
| f.readf("%s", &input); | |
| import std.conv : ConvException; | |
| import std.exception : collectException; | |
| assert(collectException!ConvException(f.readf("%s", &input)).msg == | |
| "Unexpected '\\n' when converting from type LockingTextReader to type int"); | |
| } | |
| /** | |
| Reads a line from the file and parses it using $(REF formattedRead, std,format,read). | |
| Params: | |
| format = The $(MREF_ALTTEXT format string, std,format). When passed as a | |
| compile-time argument, the string will be statically checked against the | |
| argument types passed. | |
| data = Items to be read. | |
| Returns: Same as `formattedRead`: the number of variables filled. If the | |
| input ends early, this number will be less that the number of variables | |
| provided. | |
| Example: | |
| --- | |
| // sum_rows.d | |
| void main() | |
| { | |
| import std.stdio; | |
| auto f = File("input"); | |
| int a, b, c; | |
| while (f.readfln("%d %d %d", a, b, c) == 3) | |
| { | |
| writeln(a + b + c); | |
| } | |
| } | |
| --- | |
| $(CONSOLE | |
| % cat << EOF > input | |
| 1 2 3 | |
| 4 5 6 | |
| 7 8 9 | |
| EOF | |
| % rdmd sum_rows.d | |
| 6 | |
| 15 | |
| 24 | |
| ) | |
| */ | |
| uint readfln(alias format, Data...)(auto ref Data data) | |
| if (isSomeString!(typeof(format))) | |
| { | |
| import std.format : checkFormatException; | |
| alias e = checkFormatException!(format, Data); | |
| static assert(!e, e); | |
| return this.readfln(format, data); | |
| } | |
| /// ditto | |
| uint readfln(Data...)(scope const(char)[] format, auto ref Data data) | |
| { | |
| import std.format.read : formattedRead; | |
| import std.string : stripRight; | |
| string line = this.readln.stripRight("\r\n"); | |
| return formattedRead(line, format, data); | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); | |
| scope(exit) std.file.remove(deleteme); | |
| string s; | |
| auto f = File(deleteme); | |
| f.readfln!"%s"(s); | |
| assert(s == "hello", "["~s~"]"); | |
| f.readfln("%s", s); | |
| assert(s == "world", "["~s~"]"); | |
| bool b1, b2; | |
| f.readfln("%s", b1); | |
| f.readfln("%s", b2); | |
| assert(b1 == true && b2 == false); | |
| } | |
| /** | |
| Returns a temporary file by calling $(CSTDIO tmpfile). | |
| Note that the created file has no $(LREF name).*/ | |
| static File tmpfile() @safe | |
| { | |
| import std.exception : errnoEnforce; | |
| return File(errnoEnforce(.tmpfile(), | |
| "Could not create temporary file with tmpfile()"), | |
| null); | |
| } | |
| /** | |
| Unsafe function that wraps an existing `FILE*`. The resulting $(D | |
| File) never takes the initiative in closing the file. | |
| Note that the created file has no $(LREF name)*/ | |
| /*private*/ static File wrapFile(FILE* f) @safe | |
| { | |
| import std.exception : enforce; | |
| return File(enforce(f, "Could not wrap null FILE*"), | |
| null, /*uint.max / 2*/ 9999); | |
| } | |
| /** | |
| Returns the `FILE*` corresponding to this object. | |
| */ | |
| FILE* getFP() @safe pure | |
| { | |
| import std.exception : enforce; | |
| enforce(_p && _p.handle, | |
| "Attempting to call getFP() on an unopened file"); | |
| return _p.handle; | |
| } | |
| @system unittest | |
| { | |
| static import core.stdc.stdio; | |
| assert(stdout.getFP() == core.stdc.stdio.stdout); | |
| } | |
| /** | |
| Returns the file number corresponding to this object. | |
| */ | |
| @property fileno_t fileno() const @trusted | |
| { | |
| import std.exception : enforce; | |
| enforce(isOpen, "Attempting to call fileno() on an unopened file"); | |
| return .fileno(cast(FILE*) _p.handle); | |
| } | |
| /** | |
| Returns the underlying operating system `HANDLE` (Windows only). | |
| */ | |
| version (StdDdoc) | |
| @property HANDLE windowsHandle(); | |
| version (Windows) | |
| @property HANDLE windowsHandle() | |
| { | |
| return cast(HANDLE)_get_osfhandle(fileno); | |
| } | |
| // Note: This was documented until 2013/08 | |
| /* | |
| Range that reads one line at a time. Returned by $(LREF byLine). | |
| Allows to directly use range operations on lines of a file. | |
| */ | |
| private struct ByLineImpl(Char, Terminator) | |
| { | |
| private: | |
| import std.typecons : borrow, RefCountedAutoInitialize, SafeRefCounted; | |
| /* Ref-counting stops the source range's Impl | |
| * from getting out of sync after the range is copied, e.g. | |
| * when accessing range.front, then using std.range.take, | |
| * then accessing range.front again. */ | |
| alias PImpl = SafeRefCounted!(Impl, RefCountedAutoInitialize.no); | |
| PImpl impl; | |
| static if (isScalarType!Terminator) | |
| enum defTerm = '\n'; | |
| else | |
| enum defTerm = cast(Terminator)"\n"; | |
| public: | |
| this(File f, KeepTerminator kt = No.keepTerminator, | |
| Terminator terminator = defTerm) | |
| { | |
| impl = PImpl(f, kt, terminator); | |
| } | |
| /* Verifiably `@safe` when built with -preview=DIP1000. */ | |
| @property bool empty() @trusted | |
| { | |
| // Using `ref` is actually necessary here. | |
| return impl.borrow!((ref i) => i.empty); | |
| } | |
| /* Verifiably `@safe` when built with -preview=DIP1000. */ | |
| @property Char[] front() @trusted | |
| { | |
| // Using `ref` is likely optional here. | |
| return impl.borrow!((ref i) => i.front); | |
| } | |
| /* Verifiably `@safe` when built with -preview=DIP1000. */ | |
| void popFront() @trusted | |
| { | |
| return impl.borrow!((ref i) => i.popFront()); | |
| } | |
| private: | |
| struct Impl | |
| { | |
| private: | |
| File file; | |
| Char[] line; | |
| Char[] buffer; | |
| Terminator terminator; | |
| KeepTerminator keepTerminator; | |
| bool haveLine; | |
| @safe: | |
| public: | |
| this(File f, KeepTerminator kt, Terminator terminator) | |
| { | |
| file = f; | |
| this.terminator = terminator; | |
| keepTerminator = kt; | |
| } | |
| // Range primitive implementations. | |
| @property bool empty() | |
| { | |
| needLine(); | |
| return line is null; | |
| } | |
| @property Char[] front() | |
| { | |
| needLine(); | |
| return line; | |
| } | |
| void popFront() | |
| { | |
| needLine(); | |
| haveLine = false; | |
| } | |
| private: | |
| void needLine() | |
| { | |
| if (haveLine) | |
| return; | |
| import std.algorithm.searching : endsWith; | |
| assert(file.isOpen); | |
| line = buffer; | |
| file.readln(line, terminator); | |
| if (line.length > buffer.length) | |
| { | |
| buffer = line; | |
| } | |
| if (line.empty) | |
| { | |
| file.detach(); | |
| line = null; | |
| } | |
| else if (keepTerminator == No.keepTerminator | |
| && endsWith(line, terminator)) | |
| { | |
| static if (isScalarType!Terminator) | |
| enum tlen = 1; | |
| else static if (isArray!Terminator) | |
| { | |
| static assert( | |
| is(immutable ElementEncodingType!Terminator == immutable Char)); | |
| const tlen = terminator.length; | |
| } | |
| else | |
| static assert(false); | |
| line = line[0 .. line.length - tlen]; | |
| } | |
| haveLine = true; | |
| } | |
| } | |
| } | |
| /** | |
| Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| set up to read from the file handle one line at a time. | |
| The element type for the range will be `Char[]`. Range primitives | |
| may throw `StdioException` on I/O error. | |
| Note: | |
| Each `front` will not persist after $(D | |
| popFront) is called, so the caller must copy its contents (e.g. by | |
| calling `to!string`) when retention is needed. If the caller needs | |
| to retain a copy of every line, use the $(LREF byLineCopy) function | |
| instead. | |
| Params: | |
| Char = Character type for each line, defaulting to `char`. | |
| keepTerminator = Use `Yes.keepTerminator` to include the | |
| terminator at the end of each line. | |
| terminator = Line separator (`'\n'` by default). Use | |
| $(REF newline, std,ascii) for portability (unless the file was opened in | |
| text mode). | |
| Example: | |
| ---- | |
| import std.algorithm, std.stdio, std.string; | |
| // Count words in a file using ranges. | |
| void main() | |
| { | |
| auto file = File("file.txt"); // Open for reading | |
| const wordCount = file.byLine() // Read lines | |
| .map!split // Split into words | |
| .map!(a => a.length) // Count words per line | |
| .sum(); // Total word count | |
| writeln(wordCount); | |
| } | |
| ---- | |
| Example: | |
| ---- | |
| import std.range, std.stdio; | |
| // Read lines using foreach. | |
| void main() | |
| { | |
| auto file = File("file.txt"); // Open for reading | |
| auto range = file.byLine(); | |
| // Print first three lines | |
| foreach (line; range.take(3)) | |
| writeln(line); | |
| // Print remaining lines beginning with '#' | |
| foreach (line; range) | |
| { | |
| if (!line.empty && line[0] == '#') | |
| writeln(line); | |
| } | |
| } | |
| ---- | |
| Notice that neither example accesses the line data returned by | |
| `front` after the corresponding `popFront` call is made (because | |
| the contents may well have changed). | |
| ---- | |
| Windows specific Example: | |
| ---- | |
| import std.stdio; | |
| version (Windows) | |
| void main() | |
| { | |
| foreach (line; File("file.txt").byLine(No.keepTerminator, "\r\n")) | |
| { | |
| writeln("|"~line~"|"); | |
| if (line == "HelloWorld") | |
| writeln("^This Line is here."); | |
| } | |
| } | |
| */ | |
| auto byLine(Terminator = char, Char = char) | |
| (KeepTerminator keepTerminator = No.keepTerminator, | |
| Terminator terminator = '\n') | |
| if (isScalarType!Terminator) | |
| { | |
| return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); | |
| } | |
| /// ditto | |
| auto byLine(Terminator, Char = char) | |
| (KeepTerminator keepTerminator, Terminator terminator) | |
| if (is(immutable ElementEncodingType!Terminator == immutable Char)) | |
| { | |
| return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); | |
| } | |
| @safe unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "hi"); | |
| scope(success) std.file.remove(deleteme); | |
| import std.meta : AliasSeq; | |
| static foreach (T; AliasSeq!(char, wchar, dchar)) | |
| {{ | |
| auto blc = File(deleteme).byLine!(T, T); | |
| assert(blc.front == "hi"); | |
| // check front is cached | |
| assert(blc.front is blc.front); | |
| }} | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=19980 | |
| @safe unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "Line 1\nLine 2\nLine 3\n"); | |
| scope(success) std.file.remove(deleteme); | |
| auto f = File(deleteme); | |
| f.byLine(); | |
| f.byLine(); | |
| assert(f.byLine().front == "Line 1"); | |
| } | |
| private struct ByLineCopy(Char, Terminator) | |
| { | |
| private: | |
| import std.typecons : borrow, RefCountedAutoInitialize, SafeRefCounted; | |
| /* Ref-counting stops the source range's ByLineCopyImpl | |
| * from getting out of sync after the range is copied, e.g. | |
| * when accessing range.front, then using std.range.take, | |
| * then accessing range.front again. */ | |
| alias Impl = SafeRefCounted!(ByLineCopyImpl!(Char, Terminator), | |
| RefCountedAutoInitialize.no); | |
| Impl impl; | |
| public: | |
| this(File f, KeepTerminator kt, Terminator terminator) | |
| { | |
| impl = Impl(f, kt, terminator); | |
| } | |
| /* Verifiably `@safe` when built with -preview=DIP1000. */ | |
| @property bool empty() @trusted | |
| { | |
| // Using `ref` is actually necessary here. | |
| return impl.borrow!((ref i) => i.empty); | |
| } | |
| /* Verifiably `@safe` when built with -preview=DIP1000. */ | |
| @property Char[] front() @trusted | |
| { | |
| // Using `ref` is likely optional here. | |
| return impl.borrow!((ref i) => i.front); | |
| } | |
| /* Verifiably `@safe` when built with -preview=DIP1000. */ | |
| void popFront() @trusted | |
| { | |
| impl.borrow!((ref i) => i.popFront()); | |
| } | |
| } | |
| private struct ByLineCopyImpl(Char, Terminator) | |
| { | |
| ByLineImpl!(Unqual!Char, Terminator).Impl impl; | |
| bool gotFront; | |
| Char[] line; | |
| public: | |
| this(File f, KeepTerminator kt, Terminator terminator) | |
| { | |
| impl = ByLineImpl!(Unqual!Char, Terminator).Impl(f, kt, terminator); | |
| } | |
| @property bool empty() | |
| { | |
| return impl.empty; | |
| } | |
| @property front() | |
| { | |
| if (!gotFront) | |
| { | |
| line = impl.front.dup; | |
| gotFront = true; | |
| } | |
| return line; | |
| } | |
| void popFront() | |
| { | |
| impl.popFront(); | |
| gotFront = false; | |
| } | |
| } | |
| /** | |
| Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| set up to read from the file handle one line | |
| at a time. Each line will be newly allocated. `front` will cache | |
| its value to allow repeated calls without unnecessary allocations. | |
| Note: Due to caching byLineCopy can be more memory-efficient than | |
| `File.byLine.map!idup`. | |
| The element type for the range will be `Char[]`. Range | |
| primitives may throw `StdioException` on I/O error. | |
| Params: | |
| Char = Character type for each line, defaulting to $(D immutable char). | |
| keepTerminator = Use `Yes.keepTerminator` to include the | |
| terminator at the end of each line. | |
| terminator = Line separator (`'\n'` by default). Use | |
| $(REF newline, std,ascii) for portability (unless the file was opened in | |
| text mode). | |
| Example: | |
| ---- | |
| import std.algorithm, std.array, std.stdio; | |
| // Print sorted lines of a file. | |
| void main() | |
| { | |
| auto sortedLines = File("file.txt") // Open for reading | |
| .byLineCopy() // Read persistent lines | |
| .array() // into an array | |
| .sort(); // then sort them | |
| foreach (line; sortedLines) | |
| writeln(line); | |
| } | |
| ---- | |
| See_Also: | |
| $(REF readText, std,file) | |
| */ | |
| auto byLineCopy(Terminator = char, Char = immutable char) | |
| (KeepTerminator keepTerminator = No.keepTerminator, | |
| Terminator terminator = '\n') | |
| if (isScalarType!Terminator) | |
| { | |
| return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); | |
| } | |
| /// ditto | |
| auto byLineCopy(Terminator, Char = immutable char) | |
| (KeepTerminator keepTerminator, Terminator terminator) | |
| if (is(immutable ElementEncodingType!Terminator == immutable Char)) | |
| { | |
| return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); | |
| } | |
| @safe unittest | |
| { | |
| static assert(is(typeof(File("").byLine.front) == char[])); | |
| static assert(is(typeof(File("").byLineCopy.front) == string)); | |
| static assert( | |
| is(typeof(File("").byLineCopy!(char, char).front) == char[])); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, ""); | |
| scope(success) std.file.remove(deleteme); | |
| // Test empty file | |
| auto f = File(deleteme); | |
| foreach (line; f.byLine()) | |
| { | |
| assert(false); | |
| } | |
| f.detach(); | |
| assert(!f.isOpen); | |
| void test(Terminator)(string txt, in string[] witness, | |
| KeepTerminator kt, Terminator term, bool popFirstLine = false) | |
| { | |
| import std.algorithm.sorting : sort; | |
| import std.array : array; | |
| import std.conv : text; | |
| import std.range.primitives : walkLength; | |
| uint i; | |
| std.file.write(deleteme, txt); | |
| auto f = File(deleteme); | |
| scope(exit) | |
| { | |
| f.close(); | |
| assert(!f.isOpen); | |
| } | |
| auto lines = f.byLine(kt, term); | |
| if (popFirstLine) | |
| { | |
| lines.popFront(); | |
| i = 1; | |
| } | |
| assert(lines.empty || lines.front is lines.front); | |
| foreach (line; lines) | |
| { | |
| assert(line == witness[i++]); | |
| } | |
| assert(i == witness.length, text(i, " != ", witness.length)); | |
| // https://issues.dlang.org/show_bug.cgi?id=11830 | |
| auto walkedLength = File(deleteme).byLine(kt, term).walkLength; | |
| assert(walkedLength == witness.length, text(walkedLength, " != ", witness.length)); | |
| // test persistent lines | |
| assert(File(deleteme).byLineCopy(kt, term).array.sort() == witness.dup.sort()); | |
| } | |
| KeepTerminator kt = No.keepTerminator; | |
| test("", null, kt, '\n'); | |
| test("\n", [ "" ], kt, '\n'); | |
| test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n'); | |
| test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n', true); | |
| test("asd\ndef\nasdf\n", [ "asd", "def", "asdf" ], kt, '\n'); | |
| test("foo", [ "foo" ], kt, '\n', true); | |
| test("bob\r\nmarge\r\nsteve\r\n", ["bob", "marge", "steve"], | |
| kt, "\r\n"); | |
| test("sue\r", ["sue"], kt, '\r'); | |
| kt = Yes.keepTerminator; | |
| test("", null, kt, '\n'); | |
| test("\n", [ "\n" ], kt, '\n'); | |
| test("asd\ndef\nasdf", [ "asd\n", "def\n", "asdf" ], kt, '\n'); | |
| test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n'); | |
| test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n', true); | |
| test("foo", [ "foo" ], kt, '\n'); | |
| test("bob\r\nmarge\r\nsteve\r\n", ["bob\r\n", "marge\r\n", "steve\r\n"], | |
| kt, "\r\n"); | |
| test("sue\r", ["sue\r"], kt, '\r'); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.range : drop, take; | |
| version (Win64) | |
| { | |
| static import std.file; | |
| /* the C function tmpfile doesn't seem to work, even when called from C */ | |
| auto deleteme = testFilename(); | |
| auto file = File(deleteme, "w+"); | |
| scope(success) std.file.remove(deleteme); | |
| } | |
| else version (CRuntime_Bionic) | |
| { | |
| static import std.file; | |
| /* the C function tmpfile doesn't work when called from a shared | |
| library apk: | |
| https://code.google.com/p/android/issues/detail?id=66815 */ | |
| auto deleteme = testFilename(); | |
| auto file = File(deleteme, "w+"); | |
| scope(success) std.file.remove(deleteme); | |
| } | |
| else | |
| auto file = File.tmpfile(); | |
| file.write("1\n2\n3\n"); | |
| // https://issues.dlang.org/show_bug.cgi?id=9599 | |
| file.rewind(); | |
| File.ByLineImpl!(char, char) fbl = file.byLine(); | |
| auto fbl2 = fbl; | |
| assert(fbl.front == "1"); | |
| assert(fbl.front is fbl2.front); | |
| assert(fbl.take(1).equal(["1"])); | |
| assert(fbl.equal(["2", "3"])); | |
| assert(fbl.empty); | |
| assert(file.isOpen); // we still have a valid reference | |
| file.rewind(); | |
| fbl = file.byLine(); | |
| assert(!fbl.drop(2).empty); | |
| assert(fbl.equal(["3"])); | |
| assert(fbl.empty); | |
| assert(file.isOpen); | |
| file.detach(); | |
| assert(!file.isOpen); | |
| } | |
| @safe unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "hi"); | |
| scope(success) std.file.remove(deleteme); | |
| auto blc = File(deleteme).byLineCopy; | |
| assert(!blc.empty); | |
| // check front is cached | |
| assert(blc.front is blc.front); | |
| } | |
| /** | |
| Creates an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| set up to parse one line at a time from the file into a tuple. | |
| Range primitives may throw `StdioException` on I/O error. | |
| Params: | |
| format = tuple record $(REF_ALTTEXT _format, formattedRead, std, _format) | |
| Returns: | |
| The input range set up to parse one line at a time into a record tuple. | |
| See_Also: | |
| It is similar to $(LREF byLine) and uses | |
| $(REF_ALTTEXT _format, formattedRead, std, _format) under the hood. | |
| */ | |
| template byRecord(Fields...) | |
| { | |
| auto byRecord(string format) | |
| { | |
| return ByRecordImpl!(Fields)(this, format); | |
| } | |
| } | |
| /// | |
| @system unittest | |
| { | |
| static import std.file; | |
| import std.typecons : tuple; | |
| // prepare test file | |
| auto testFile = std.file.deleteme(); | |
| scope(failure) printf("Failed test at line %d\n", __LINE__); | |
| std.file.write(testFile, "1 2\n4 1\n5 100"); | |
| scope(exit) std.file.remove(testFile); | |
| File f = File(testFile); | |
| scope(exit) f.close(); | |
| auto expected = [tuple(1, 2), tuple(4, 1), tuple(5, 100)]; | |
| uint i; | |
| foreach (e; f.byRecord!(int, int)("%s %s")) | |
| { | |
| assert(e == expected[i++]); | |
| } | |
| } | |
| // Note: This was documented until 2013/08 | |
| /* | |
| * Range that reads a chunk at a time. | |
| */ | |
| private struct ByChunkImpl | |
| { | |
| private: | |
| File file_; | |
| ubyte[] chunk_; | |
| void prime() | |
| { | |
| chunk_ = file_.rawRead(chunk_); | |
| if (chunk_.length == 0) | |
| file_.detach(); | |
| } | |
| public: | |
| this(File file, size_t size) | |
| { | |
| this(file, new ubyte[](size)); | |
| } | |
| this(File file, ubyte[] buffer) | |
| { | |
| import std.exception : enforce; | |
| enforce(buffer.length, "size must be larger than 0"); | |
| file_ = file; | |
| chunk_ = buffer; | |
| prime(); | |
| } | |
| // `ByChunk`'s input range primitive operations. | |
| @property nothrow | |
| bool empty() const | |
| { | |
| return !file_.isOpen; | |
| } | |
| /// Ditto | |
| @property nothrow | |
| ubyte[] front() | |
| { | |
| version (assert) | |
| { | |
| import core.exception : RangeError; | |
| if (empty) | |
| throw new RangeError(); | |
| } | |
| return chunk_; | |
| } | |
| /// Ditto | |
| void popFront() | |
| { | |
| version (assert) | |
| { | |
| import core.exception : RangeError; | |
| if (empty) | |
| throw new RangeError(); | |
| } | |
| prime(); | |
| } | |
| } | |
| /** | |
| Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| set up to read from the file handle a chunk at a time. | |
| The element type for the range will be `ubyte[]`. Range primitives | |
| may throw `StdioException` on I/O error. | |
| Example: | |
| --------- | |
| void main() | |
| { | |
| // Read standard input 4KB at a time | |
| foreach (ubyte[] buffer; stdin.byChunk(4096)) | |
| { | |
| ... use buffer ... | |
| } | |
| } | |
| --------- | |
| The parameter may be a number (as shown in the example above) dictating the | |
| size of each chunk. Alternatively, `byChunk` accepts a | |
| user-provided buffer that it uses directly. | |
| Example: | |
| --------- | |
| void main() | |
| { | |
| // Read standard input 4KB at a time | |
| foreach (ubyte[] buffer; stdin.byChunk(new ubyte[4096])) | |
| { | |
| ... use buffer ... | |
| } | |
| } | |
| --------- | |
| In either case, the content of the buffer is reused across calls. That means | |
| `front` will not persist after `popFront` is called, so if retention is | |
| needed, the caller must copy its contents (e.g. by calling `buffer.dup`). | |
| In the example above, `buffer.length` is 4096 for all iterations, except | |
| for the last one, in which case `buffer.length` may be less than 4096 (but | |
| always greater than zero). | |
| With the mentioned limitations, `byChunk` works with any algorithm | |
| compatible with input ranges. | |
| Example: | |
| --- | |
| // Efficient file copy, 1MB at a time. | |
| import std.algorithm, std.stdio; | |
| void main() | |
| { | |
| stdin.byChunk(1024 * 1024).copy(stdout.lockingTextWriter()); | |
| } | |
| --- | |
| $(REF joiner, std,algorithm,iteration) can be used to join chunks together into | |
| a single range lazily. | |
| Example: | |
| --- | |
| import std.algorithm, std.stdio; | |
| void main() | |
| { | |
| //Range of ranges | |
| static assert(is(typeof(stdin.byChunk(4096).front) == ubyte[])); | |
| //Range of elements | |
| static assert(is(typeof(stdin.byChunk(4096).joiner.front) == ubyte)); | |
| } | |
| --- | |
| Returns: A call to `byChunk` returns a range initialized with the `File` | |
| object and the appropriate buffer. | |
| Throws: If the user-provided size is zero or the user-provided buffer | |
| is empty, throws an `Exception`. In case of an I/O error throws | |
| `StdioException`. | |
| */ | |
| auto byChunk(size_t chunkSize) | |
| { | |
| return ByChunkImpl(this, chunkSize); | |
| } | |
| /// Ditto | |
| auto byChunk(ubyte[] buffer) | |
| { | |
| return ByChunkImpl(this, buffer); | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| scope(failure) printf("Failed test at line %d\n", __LINE__); | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "asd\ndef\nasdf"); | |
| auto witness = ["asd\n", "def\n", "asdf" ]; | |
| auto f = File(deleteme); | |
| scope(exit) | |
| { | |
| f.close(); | |
| assert(!f.isOpen); | |
| std.file.remove(deleteme); | |
| } | |
| uint i; | |
| foreach (chunk; f.byChunk(4)) | |
| assert(chunk == cast(ubyte[]) witness[i++]); | |
| assert(i == witness.length); | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| scope(failure) printf("Failed test at line %d\n", __LINE__); | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "asd\ndef\nasdf"); | |
| auto witness = ["asd\n", "def\n", "asdf" ]; | |
| auto f = File(deleteme); | |
| scope(exit) | |
| { | |
| f.close(); | |
| assert(!f.isOpen); | |
| std.file.remove(deleteme); | |
| } | |
| uint i; | |
| foreach (chunk; f.byChunk(new ubyte[4])) | |
| assert(chunk == cast(ubyte[]) witness[i++]); | |
| assert(i == witness.length); | |
| } | |
| // Note: This was documented until 2013/08 | |
| /* | |
| `Range` that locks the file and allows fast writing to it. | |
| */ | |
| struct LockingTextWriter | |
| { | |
| private: | |
| import std.range.primitives : ElementType, isInfinite, isInputRange; | |
| // Access the FILE* handle through the 'file_' member | |
| // to keep the object alive through refcounting | |
| File file_; | |
| // the unshared version of FILE* handle, extracted from the File object | |
| @property _iobuf* handle_() @trusted { return cast(_iobuf*) file_._p.handle; } | |
| // the file's orientation (byte- or wide-oriented) | |
| int orientation_; | |
| // Buffers for when we need to transcode. | |
| wchar highSurrogate = '\0'; // '\0' indicates empty | |
| void highSurrogateShouldBeEmpty() @safe | |
| { | |
| import std.utf : UTFException; | |
| if (highSurrogate != '\0') | |
| throw new UTFException("unpaired surrogate UTF-16 value"); | |
| } | |
| char[4] rbuf8; | |
| size_t rbuf8Filled = 0; | |
| public: | |
| this(ref File f) @trusted | |
| { | |
| import std.exception : enforce; | |
| enforce(f._p && f._p.handle, "Attempting to write to closed File"); | |
| file_ = f; | |
| FILE* fps = f._p.handle; | |
| version (CRuntime_Microsoft) | |
| { | |
| // Microsoft doesn't implement fwide. Instead, there's the | |
| // concept of ANSI/UNICODE mode. fputc doesn't work in UNICODE | |
| // mode; fputwc has to be used. So that essentially means | |
| // "wide-oriented" for us. | |
| immutable int mode = _setmode(f.fileno, _O_TEXT); | |
| // Set some arbitrary mode to obtain the previous one. | |
| if (mode != -1) // _setmode() succeeded | |
| { | |
| _setmode(f.fileno, mode); // Restore previous mode. | |
| if (mode & (_O_WTEXT | _O_U16TEXT | _O_U8TEXT)) | |
| { | |
| orientation_ = 1; // wide | |
| } | |
| } | |
| } | |
| else | |
| { | |
| import core.stdc.wchar_ : fwide; | |
| orientation_ = fwide(fps, 0); | |
| } | |
| _FLOCK(fps); | |
| } | |
| ~this() @trusted | |
| { | |
| if (auto p = file_._p) | |
| { | |
| if (p.handle) _FUNLOCK(p.handle); | |
| } | |
| file_ = File.init; | |
| /* Destroy file_ before possibly throwing. Else it wouldn't be | |
| destroyed, and its reference count would be wrong. */ | |
| highSurrogateShouldBeEmpty(); | |
| } | |
| this(this) @trusted | |
| { | |
| if (auto p = file_._p) | |
| { | |
| if (p.handle) _FLOCK(p.handle); | |
| } | |
| } | |
| /// Range primitive implementations. | |
| void put(A)(scope A writeme) | |
| if ((isSomeChar!(ElementType!A) || | |
| is(ElementType!A : const(ubyte))) && | |
| isInputRange!A && | |
| !isInfinite!A) | |
| { | |
| import std.exception : errnoEnforce; | |
| alias C = ElementEncodingType!A; | |
| static assert(!is(C == void)); | |
| static if (isSomeString!A && C.sizeof == 1 || is(A : const(ubyte)[])) | |
| { | |
| if (orientation_ <= 0) | |
| { | |
| //file.write(writeme); causes infinite recursion!!! | |
| //file.rawWrite(writeme); | |
| auto result = trustedFwrite(file_._p.handle, writeme); | |
| if (result != writeme.length) errnoEnforce(0); | |
| return; | |
| } | |
| } | |
| // put each element in turn. | |
| foreach (c; writeme) | |
| { | |
| put(c); | |
| } | |
| } | |
| /// ditto | |
| void put(C)(scope C c) @safe | |
| if (isSomeChar!C || is(C : const(ubyte))) | |
| { | |
| import std.utf : decodeFront, encode, stride; | |
| static if (c.sizeof == 1) | |
| { | |
| highSurrogateShouldBeEmpty(); | |
| if (orientation_ <= 0) trustedFPUTC(c, handle_); | |
| else if (c <= 0x7F) trustedFPUTWC(c, handle_); | |
| else if (c >= 0b1100_0000) // start byte of multibyte sequence | |
| { | |
| rbuf8[0] = c; | |
| rbuf8Filled = 1; | |
| } | |
| else // continuation byte of multibyte sequence | |
| { | |
| rbuf8[rbuf8Filled] = c; | |
| ++rbuf8Filled; | |
| if (stride(rbuf8[]) == rbuf8Filled) // sequence is complete | |
| { | |
| char[] str = rbuf8[0 .. rbuf8Filled]; | |
| immutable dchar d = decodeFront(str); | |
| wchar_t[4 / wchar_t.sizeof] wbuf; | |
| immutable size = encode(wbuf, d); | |
| foreach (i; 0 .. size) | |
| trustedFPUTWC(wbuf[i], handle_); | |
| rbuf8Filled = 0; | |
| } | |
| } | |
| } | |
| else static if (c.sizeof == 2) | |
| { | |
| import std.utf : decode; | |
| if (c <= 0x7F) | |
| { | |
| highSurrogateShouldBeEmpty(); | |
| if (orientation_ <= 0) trustedFPUTC(c, handle_); | |
| else trustedFPUTWC(c, handle_); | |
| } | |
| else if (0xD800 <= c && c <= 0xDBFF) // high surrogate | |
| { | |
| highSurrogateShouldBeEmpty(); | |
| highSurrogate = c; | |
| } | |
| else // standalone or low surrogate | |
| { | |
| dchar d = c; | |
| if (highSurrogate != '\0') | |
| { | |
| immutable wchar[2] rbuf = [highSurrogate, c]; | |
| size_t index = 0; | |
| d = decode(rbuf[], index); | |
| highSurrogate = 0; | |
| } | |
| if (orientation_ <= 0) | |
| { | |
| char[4] wbuf; | |
| immutable size = encode(wbuf, d); | |
| foreach (i; 0 .. size) | |
| trustedFPUTC(wbuf[i], handle_); | |
| } | |
| else | |
| { | |
| wchar_t[4 / wchar_t.sizeof] wbuf; | |
| immutable size = encode(wbuf, d); | |
| foreach (i; 0 .. size) | |
| trustedFPUTWC(wbuf[i], handle_); | |
| } | |
| rbuf8Filled = 0; | |
| } | |
| } | |
| else // 32-bit characters | |
| { | |
| import std.utf : encode; | |
| highSurrogateShouldBeEmpty(); | |
| if (orientation_ <= 0) | |
| { | |
| if (c <= 0x7F) | |
| { | |
| trustedFPUTC(c, handle_); | |
| } | |
| else | |
| { | |
| char[4] buf = void; | |
| immutable len = encode(buf, c); | |
| foreach (i ; 0 .. len) | |
| trustedFPUTC(buf[i], handle_); | |
| } | |
| } | |
| else | |
| { | |
| version (Windows) | |
| { | |
| import std.utf : isValidDchar; | |
| assert(isValidDchar(c)); | |
| if (c <= 0xFFFF) | |
| { | |
| trustedFPUTWC(cast(wchar_t) c, handle_); | |
| } | |
| else | |
| { | |
| trustedFPUTWC(cast(wchar_t) | |
| ((((c - 0x10000) >> 10) & 0x3FF) | |
| + 0xD800), handle_); | |
| trustedFPUTWC(cast(wchar_t) | |
| (((c - 0x10000) & 0x3FF) + 0xDC00), | |
| handle_); | |
| } | |
| } | |
| else version (Posix) | |
| { | |
| trustedFPUTWC(cast(wchar_t) c, handle_); | |
| } | |
| else | |
| { | |
| static assert(0); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Output range which locks the file when created, and unlocks the file when it goes | |
| * out of scope. | |
| * | |
| * Returns: An $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) | |
| * which accepts string types, `ubyte[]`, individual character types, and | |
| * individual `ubyte`s. | |
| * | |
| * Note: Writing either arrays of `char`s or `ubyte`s is faster than | |
| * writing each character individually from a range. For large amounts of data, | |
| * writing the contents in chunks using an intermediary array can result | |
| * in a speed increase. | |
| * | |
| * Throws: $(REF UTFException, std, utf) if the data given is a `char` range | |
| * and it contains malformed UTF data. | |
| * | |
| * See_Also: $(LREF byChunk) for an example. | |
| */ | |
| auto lockingTextWriter() @safe | |
| { | |
| return LockingTextWriter(this); | |
| } | |
| // An output range which optionally locks the file and puts it into | |
| // binary mode (similar to rawWrite). Because it needs to restore | |
| // the file mode on destruction, it is RefCounted on Windows. | |
| struct BinaryWriterImpl(bool locking) | |
| { | |
| import std.traits : hasIndirections; | |
| private: | |
| // Access the FILE* handle through the 'file_' member | |
| // to keep the object alive through refcounting | |
| File file_; | |
| string name; | |
| version (Windows) | |
| { | |
| fileno_t fd; | |
| int oldMode; | |
| } | |
| public: | |
| // Don't use this, but `File.lockingBinaryWriter()` instead. | |
| // Must be public for RefCounted and emplace() in druntime. | |
| this(scope ref File f) | |
| { | |
| import std.exception : enforce; | |
| file_ = f; | |
| enforce(f._p && f._p.handle); | |
| name = f._name; | |
| FILE* fps = f._p.handle; | |
| static if (locking) | |
| _FLOCK(fps); | |
| version (Windows) | |
| { | |
| .fflush(fps); // before changing translation mode | |
| fd = .fileno(fps); | |
| oldMode = ._setmode(fd, _O_BINARY); | |
| } | |
| } | |
| ~this() | |
| { | |
| if (!file_._p || !file_._p.handle) | |
| return; | |
| FILE* fps = file_._p.handle; | |
| version (Windows) | |
| { | |
| .fflush(fps); // before restoring translation mode | |
| ._setmode(fd, oldMode); | |
| } | |
| _FUNLOCK(fps); | |
| } | |
| void rawWrite(T)(in T[] buffer) | |
| { | |
| import std.conv : text; | |
| import std.exception : errnoEnforce; | |
| auto result = trustedFwrite(file_._p.handle, buffer); | |
| if (result == result.max) result = 0; | |
| errnoEnforce(result == buffer.length, | |
| text("Wrote ", result, " instead of ", buffer.length, | |
| " objects of type ", T.stringof, " to file `", | |
| name, "'")); | |
| } | |
| version (Windows) | |
| { | |
| @disable this(this); | |
| } | |
| else | |
| { | |
| this(this) | |
| { | |
| if (auto p = file_._p) | |
| { | |
| if (p.handle) _FLOCK(p.handle); | |
| } | |
| } | |
| } | |
| void put(T)(auto ref scope const T value) | |
| if (!hasIndirections!T && | |
| !isInputRange!T) | |
| { | |
| rawWrite((&value)[0 .. 1]); | |
| } | |
| void put(T)(scope const(T)[] array) | |
| if (!hasIndirections!T && | |
| !isInputRange!T) | |
| { | |
| rawWrite(array); | |
| } | |
| } | |
| /** Returns an output range that locks the file and allows fast writing to it. | |
| Example: | |
| Produce a grayscale image of the $(LINK2 https://en.wikipedia.org/wiki/Mandelbrot_set, Mandelbrot set) | |
| in binary $(LINK2 https://en.wikipedia.org/wiki/Netpbm_format, Netpbm format) to standard output. | |
| --- | |
| import std.algorithm, std.complex, std.range, std.stdio; | |
| void main() | |
| { | |
| enum size = 500; | |
| writef("P5\n%d %d %d\n", size, size, ubyte.max); | |
| iota(-1, 3, 2.0/size).map!(y => | |
| iota(-1.5, 0.5, 2.0/size).map!(x => | |
| cast(ubyte)(1+ | |
| recurrence!((a, n) => x + y * complex(0, 1) + a[n-1]^^2)(complex(0)) | |
| .take(ubyte.max) | |
| .countUntil!(z => z.re^^2 + z.im^^2 > 4)) | |
| ) | |
| ) | |
| .copy(stdout.lockingBinaryWriter); | |
| } | |
| --- | |
| */ | |
| auto lockingBinaryWriter() | |
| { | |
| alias LockingBinaryWriterImpl = BinaryWriterImpl!true; | |
| version (Windows) | |
| { | |
| import std.typecons : RefCounted; | |
| alias LockingBinaryWriter = RefCounted!LockingBinaryWriterImpl; | |
| } | |
| else | |
| alias LockingBinaryWriter = LockingBinaryWriterImpl; | |
| return LockingBinaryWriter(this); | |
| } | |
| @system unittest | |
| { | |
| import std.algorithm.mutation : reverse; | |
| import std.exception : collectException; | |
| static import std.file; | |
| import std.range : only, retro; | |
| import std.string : format; | |
| auto deleteme = testFilename(); | |
| scope(exit) collectException(std.file.remove(deleteme)); | |
| { | |
| auto writer = File(deleteme, "wb").lockingBinaryWriter(); | |
| auto input = File(deleteme, "rb"); | |
| ubyte[1] byteIn = [42]; | |
| writer.rawWrite(byteIn); | |
| destroy(writer); | |
| ubyte[1] byteOut = input.rawRead(new ubyte[1]); | |
| assert(byteIn[0] == byteOut[0]); | |
| } | |
| auto output = File(deleteme, "wb"); | |
| auto writer = output.lockingBinaryWriter(); | |
| auto input = File(deleteme, "rb"); | |
| T[] readExact(T)(T[] buf) | |
| { | |
| auto result = input.rawRead(buf); | |
| assert(result.length == buf.length, | |
| "Read %d out of %d bytes" | |
| .format(result.length, buf.length)); | |
| return result; | |
| } | |
| // test raw values | |
| ubyte byteIn = 42; | |
| byteIn.only.copy(writer); output.flush(); | |
| ubyte byteOut = readExact(new ubyte[1])[0]; | |
| assert(byteIn == byteOut); | |
| // test arrays | |
| ubyte[] bytesIn = [1, 2, 3, 4, 5]; | |
| bytesIn.copy(writer); output.flush(); | |
| ubyte[] bytesOut = readExact(new ubyte[bytesIn.length]); | |
| scope(failure) .writeln(bytesOut); | |
| assert(bytesIn == bytesOut); | |
| // test ranges of values | |
| bytesIn.retro.copy(writer); output.flush(); | |
| bytesOut = readExact(bytesOut); | |
| bytesOut.reverse(); | |
| assert(bytesIn == bytesOut); | |
| // test string | |
| "foobar".copy(writer); output.flush(); | |
| char[] charsOut = readExact(new char[6]); | |
| assert(charsOut == "foobar"); | |
| // test ranges of arrays | |
| only("foo", "bar").copy(writer); output.flush(); | |
| charsOut = readExact(charsOut); | |
| assert(charsOut == "foobar"); | |
| // test that we are writing arrays as is, | |
| // without UTF-8 transcoding | |
| "foo"d.copy(writer); output.flush(); | |
| dchar[] dcharsOut = readExact(new dchar[3]); | |
| assert(dcharsOut == "foo"); | |
| } | |
| /** Returns the size of the file in bytes, ulong.max if file is not searchable or throws if the operation fails. | |
| Example: | |
| --- | |
| import std.stdio, std.file; | |
| void main() | |
| { | |
| string deleteme = "delete.me"; | |
| auto file_handle = File(deleteme, "w"); | |
| file_handle.write("abc"); //create temporary file | |
| scope(exit) deleteme.remove; //remove temporary file at scope exit | |
| assert(file_handle.size() == 3); //check if file size is 3 bytes | |
| } | |
| --- | |
| */ | |
| @property ulong size() @safe | |
| { | |
| import std.exception : collectException; | |
| ulong pos = void; | |
| if (collectException(pos = tell)) return ulong.max; | |
| scope(exit) seek(pos); | |
| seek(0, SEEK_END); | |
| return tell; | |
| } | |
| } | |
| @system unittest | |
| { | |
| @system struct SystemToString | |
| { | |
| string toString() | |
| { | |
| return "system"; | |
| } | |
| } | |
| @trusted struct TrustedToString | |
| { | |
| string toString() | |
| { | |
| return "trusted"; | |
| } | |
| } | |
| @safe struct SafeToString | |
| { | |
| string toString() | |
| { | |
| return "safe"; | |
| } | |
| } | |
| @system void systemTests() | |
| { | |
| //system code can write to files/stdout with anything! | |
| if (false) | |
| { | |
| auto f = File(); | |
| f.write("just a string"); | |
| f.write("string with arg: ", 47); | |
| f.write(SystemToString()); | |
| f.write(TrustedToString()); | |
| f.write(SafeToString()); | |
| write("just a string"); | |
| write("string with arg: ", 47); | |
| write(SystemToString()); | |
| write(TrustedToString()); | |
| write(SafeToString()); | |
| f.writeln("just a string"); | |
| f.writeln("string with arg: ", 47); | |
| f.writeln(SystemToString()); | |
| f.writeln(TrustedToString()); | |
| f.writeln(SafeToString()); | |
| writeln("just a string"); | |
| writeln("string with arg: ", 47); | |
| writeln(SystemToString()); | |
| writeln(TrustedToString()); | |
| writeln(SafeToString()); | |
| f.writef("string with arg: %s", 47); | |
| f.writef("%s", SystemToString()); | |
| f.writef("%s", TrustedToString()); | |
| f.writef("%s", SafeToString()); | |
| writef("string with arg: %s", 47); | |
| writef("%s", SystemToString()); | |
| writef("%s", TrustedToString()); | |
| writef("%s", SafeToString()); | |
| f.writefln("string with arg: %s", 47); | |
| f.writefln("%s", SystemToString()); | |
| f.writefln("%s", TrustedToString()); | |
| f.writefln("%s", SafeToString()); | |
| writefln("string with arg: %s", 47); | |
| writefln("%s", SystemToString()); | |
| writefln("%s", TrustedToString()); | |
| writefln("%s", SafeToString()); | |
| } | |
| } | |
| @safe void safeTests() | |
| { | |
| auto f = File(); | |
| //safe code can write to files only with @safe and @trusted code... | |
| if (false) | |
| { | |
| f.write("just a string"); | |
| f.write("string with arg: ", 47); | |
| f.write(TrustedToString()); | |
| f.write(SafeToString()); | |
| write("just a string"); | |
| write("string with arg: ", 47); | |
| write(TrustedToString()); | |
| write(SafeToString()); | |
| f.writeln("just a string"); | |
| f.writeln("string with arg: ", 47); | |
| f.writeln(TrustedToString()); | |
| f.writeln(SafeToString()); | |
| writeln("just a string"); | |
| writeln("string with arg: ", 47); | |
| writeln(TrustedToString()); | |
| writeln(SafeToString()); | |
| f.writef("string with arg: %s", 47); | |
| f.writef("%s", TrustedToString()); | |
| f.writef("%s", SafeToString()); | |
| writef("string with arg: %s", 47); | |
| writef("%s", TrustedToString()); | |
| writef("%s", SafeToString()); | |
| f.writefln("string with arg: %s", 47); | |
| f.writefln("%s", TrustedToString()); | |
| f.writefln("%s", SafeToString()); | |
| writefln("string with arg: %s", 47); | |
| writefln("%s", TrustedToString()); | |
| writefln("%s", SafeToString()); | |
| } | |
| static assert(!__traits(compiles, f.write(SystemToString().toString()))); | |
| static assert(!__traits(compiles, f.writeln(SystemToString()))); | |
| static assert(!__traits(compiles, f.writef("%s", SystemToString()))); | |
| static assert(!__traits(compiles, f.writefln("%s", SystemToString()))); | |
| static assert(!__traits(compiles, write(SystemToString().toString()))); | |
| static assert(!__traits(compiles, writeln(SystemToString()))); | |
| static assert(!__traits(compiles, writef("%s", SystemToString()))); | |
| static assert(!__traits(compiles, writefln("%s", SystemToString()))); | |
| } | |
| systemTests(); | |
| safeTests(); | |
| } | |
| @safe unittest | |
| { | |
| import std.exception : collectException; | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| scope(exit) collectException(std.file.remove(deleteme)); | |
| std.file.write(deleteme, "1 2 3"); | |
| auto f = File(deleteme); | |
| assert(f.size == 5); | |
| assert(f.tell == 0); | |
| } | |
| @safe unittest | |
| { | |
| static import std.file; | |
| import std.range : chain, only, repeat; | |
| import std.range.primitives : isOutputRange; | |
| auto deleteme = testFilename(); | |
| scope(exit) std.file.remove(deleteme); | |
| { | |
| auto writer = File(deleteme, "w").lockingTextWriter(); | |
| static assert(isOutputRange!(typeof(writer), dchar)); | |
| writer.put("日本語"); | |
| writer.put("日本語"w); | |
| writer.put("日本語"d); | |
| writer.put('日'); | |
| writer.put(chain(only('本'), only('語'))); | |
| // https://issues.dlang.org/show_bug.cgi?id=11945 | |
| writer.put(repeat('#', 12)); | |
| // https://issues.dlang.org/show_bug.cgi?id=17229 | |
| writer.put(cast(immutable(ubyte)[])"日本語"); | |
| } | |
| assert(File(deleteme).readln() == "日本語日本語日本語日本語############日本語"); | |
| } | |
| @safe unittest // wchar -> char | |
| { | |
| static import std.file; | |
| import std.exception : assertThrown; | |
| import std.utf : UTFException; | |
| auto deleteme = testFilename(); | |
| scope(exit) std.file.remove(deleteme); | |
| { | |
| auto writer = File(deleteme, "w").lockingTextWriter(); | |
| writer.put("\U0001F608"w); | |
| } | |
| assert(std.file.readText!string(deleteme) == "\U0001F608"); | |
| // Test invalid input: unpaired high surrogate | |
| { | |
| immutable wchar surr = "\U0001F608"w[0]; | |
| auto f = File(deleteme, "w"); | |
| assertThrown!UTFException(() { | |
| auto writer = f.lockingTextWriter(); | |
| writer.put('x'); | |
| writer.put(surr); | |
| assertThrown!UTFException(writer.put(char('y'))); | |
| assertThrown!UTFException(writer.put(wchar('y'))); | |
| assertThrown!UTFException(writer.put(dchar('y'))); | |
| assertThrown!UTFException(writer.put(surr)); | |
| // First `surr` is still unpaired at this point. `writer` gets | |
| // destroyed now, and the destructor throws a UTFException for | |
| // the unpaired surrogate. | |
| } ()); | |
| } | |
| assert(std.file.readText!string(deleteme) == "x"); | |
| // Test invalid input: unpaired low surrogate | |
| { | |
| immutable wchar surr = "\U0001F608"w[1]; | |
| auto writer = File(deleteme, "w").lockingTextWriter(); | |
| assertThrown!UTFException(writer.put(surr)); | |
| writer.put('y'); | |
| assertThrown!UTFException(writer.put(surr)); | |
| } | |
| assert(std.file.readText!string(deleteme) == "y"); | |
| } | |
| @safe unittest // https://issues.dlang.org/show_bug.cgi?id=18801 | |
| { | |
| static import std.file; | |
| import std.string : stripLeft; | |
| auto deleteme = testFilename(); | |
| scope(exit) std.file.remove(deleteme); | |
| { | |
| auto writer = File(deleteme, "w,ccs=UTF-8").lockingTextWriter(); | |
| writer.put("foo"); | |
| } | |
| assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foo"); | |
| { | |
| auto writer = File(deleteme, "a,ccs=UTF-8").lockingTextWriter(); | |
| writer.put("bar"); | |
| } | |
| assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foobar"); | |
| } | |
| @safe unittest // char/wchar -> wchar_t | |
| { | |
| import core.stdc.locale : LC_CTYPE, setlocale; | |
| import core.stdc.wchar_ : fwide; | |
| import core.stdc.string : strlen; | |
| import std.algorithm.searching : any, endsWith; | |
| import std.conv : text; | |
| import std.meta : AliasSeq; | |
| import std.string : fromStringz, stripLeft; | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| scope(exit) std.file.remove(deleteme); | |
| const char* oldCt = () @trusted { | |
| const(char)* p = setlocale(LC_CTYPE, null); | |
| // Subsequent calls to `setlocale` might invalidate this return value, | |
| // so duplicate it. | |
| // See: https://github.com/dlang/phobos/pull/7660 | |
| return p ? p[0 .. strlen(p) + 1].idup.ptr : null; | |
| }(); | |
| const utf8 = ["en_US.UTF-8", "C.UTF-8", ".65001"].any!((loc) @trusted { | |
| return setlocale(LC_CTYPE, loc.ptr).fromStringz.endsWith(loc); | |
| }); | |
| scope(exit) () @trusted { setlocale(LC_CTYPE, oldCt); } (); | |
| alias strs = AliasSeq!("xä\U0001F607", "yö\U0001F608"w); | |
| { | |
| auto f = File(deleteme, "w"); | |
| version (CRuntime_Microsoft) | |
| { | |
| () @trusted { _setmode(fileno(f.getFP()), _O_U8TEXT); } (); | |
| } | |
| else | |
| { | |
| assert(fwide(f.getFP(), 1) == 1); | |
| } | |
| auto writer = f.lockingTextWriter(); | |
| assert(writer.orientation_ == 1); | |
| static foreach (s; strs) writer.put(s); | |
| } | |
| assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == | |
| text(strs)); | |
| } | |
| @safe unittest // https://issues.dlang.org/show_bug.cgi?id=18789 | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| scope(exit) std.file.remove(deleteme); | |
| // converting to char | |
| { | |
| auto f = File(deleteme, "w"); | |
| f.writeln("\U0001F608"w); // UTFException | |
| } | |
| // converting to wchar_t | |
| { | |
| auto f = File(deleteme, "w,ccs=UTF-16LE"); | |
| // from char | |
| f.writeln("ö"); // writes garbage | |
| f.writeln("\U0001F608"); // ditto | |
| // from wchar | |
| f.writeln("\U0001F608"w); // leads to ErrnoException | |
| } | |
| } | |
| @safe unittest | |
| { | |
| import std.exception : collectException; | |
| auto e = collectException({ File f; f.writeln("Hello!"); }()); | |
| assert(e && e.msg == "Attempting to write to closed File"); | |
| } | |
| @safe unittest // https://issues.dlang.org/show_bug.cgi?id=21592 | |
| { | |
| import std.exception : collectException; | |
| import std.utf : UTFException; | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| scope(exit) std.file.remove(deleteme); | |
| auto f = File(deleteme, "w"); | |
| auto e = collectException!UTFException(f.writeln(wchar(0xD801))); | |
| assert(e.next is null); | |
| } | |
| version (StdStressTest) | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=15768 | |
| @system unittest | |
| { | |
| import std.parallelism : parallel; | |
| import std.range : iota; | |
| auto deleteme = testFilename(); | |
| stderr = File(deleteme, "w"); | |
| foreach (t; 1_000_000.iota.parallel) | |
| { | |
| stderr.write("aaa"); | |
| } | |
| } | |
| } | |
| /// Used to specify the lock type for `File.lock` and `File.tryLock`. | |
| enum LockType | |
| { | |
| /** | |
| * Specifies a _read (shared) lock. A _read lock denies all processes | |
| * write access to the specified region of the file, including the | |
| * process that first locks the region. All processes can _read the | |
| * locked region. Multiple simultaneous _read locks are allowed, as | |
| * long as there are no exclusive locks. | |
| */ | |
| read, | |
| /** | |
| * Specifies a read/write (exclusive) lock. A read/write lock denies all | |
| * other processes both read and write access to the locked file region. | |
| * If a segment has an exclusive lock, it may not have any shared locks | |
| * or other exclusive locks. | |
| */ | |
| readWrite | |
| } | |
| struct LockingTextReader | |
| { | |
| private File _f; | |
| private char _front; | |
| private bool _hasChar; | |
| this(File f) | |
| { | |
| import std.exception : enforce; | |
| enforce(f.isOpen, "LockingTextReader: File must be open"); | |
| _f = f; | |
| _FLOCK(_f._p.handle); | |
| } | |
| this(this) | |
| { | |
| _FLOCK(_f._p.handle); | |
| } | |
| ~this() | |
| { | |
| if (_hasChar) | |
| ungetc(_front, cast(FILE*)_f._p.handle); | |
| // File locking has its own reference count | |
| if (_f.isOpen) _FUNLOCK(_f._p.handle); | |
| } | |
| void opAssign(LockingTextReader r) | |
| { | |
| import std.algorithm.mutation : swap; | |
| swap(this, r); | |
| } | |
| @property bool empty() | |
| { | |
| if (!_hasChar) | |
| { | |
| if (!_f.isOpen || _f.eof) | |
| return true; | |
| immutable int c = _FGETC(cast(_iobuf*) _f._p.handle); | |
| if (c == EOF) | |
| { | |
| .destroy(_f); | |
| return true; | |
| } | |
| _front = cast(char) c; | |
| _hasChar = true; | |
| } | |
| return false; | |
| } | |
| @property char front() | |
| { | |
| if (!_hasChar) | |
| { | |
| version (assert) | |
| { | |
| import core.exception : RangeError; | |
| if (empty) | |
| throw new RangeError(); | |
| } | |
| else | |
| { | |
| empty; | |
| } | |
| } | |
| return _front; | |
| } | |
| void popFront() | |
| { | |
| if (!_hasChar) | |
| empty; | |
| _hasChar = false; | |
| } | |
| } | |
| @system unittest | |
| { | |
| // @system due to readf | |
| static import std.file; | |
| import std.range.primitives : isInputRange; | |
| static assert(isInputRange!LockingTextReader); | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "1 2 3"); | |
| scope(exit) std.file.remove(deleteme); | |
| int x; | |
| auto f = File(deleteme); | |
| f.readf("%s ", &x); | |
| assert(x == 1); | |
| f.readf("%d ", &x); | |
| assert(x == 2); | |
| f.readf("%d ", &x); | |
| assert(x == 3); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=13686 | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| static import std.file; | |
| import std.utf : byDchar; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "Тест"); | |
| scope(exit) std.file.remove(deleteme); | |
| string s; | |
| File(deleteme).readf("%s", &s); | |
| assert(s == "Тест"); | |
| auto ltr = LockingTextReader(File(deleteme)).byDchar; | |
| assert(equal(ltr, "Тест".byDchar)); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=12320 | |
| @system unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "ab"); | |
| scope(exit) std.file.remove(deleteme); | |
| auto ltr = LockingTextReader(File(deleteme)); | |
| assert(ltr.front == 'a'); | |
| ltr.popFront(); | |
| assert(ltr.front == 'b'); | |
| ltr.popFront(); | |
| assert(ltr.empty); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=14861 | |
| @system unittest | |
| { | |
| // @system due to readf | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| File fw = File(deleteme, "w"); | |
| for (int i; i != 5000; i++) | |
| fw.writeln(i, ";", "Иванов;Пётр;Петрович"); | |
| fw.close(); | |
| scope(exit) std.file.remove(deleteme); | |
| // Test read | |
| File fr = File(deleteme, "r"); | |
| scope (exit) fr.close(); | |
| int nom; string fam, nam, ot; | |
| // Error format read | |
| while (!fr.eof) | |
| fr.readf("%s;%s;%s;%s\n", &nom, &fam, &nam, &ot); | |
| } | |
| /** | |
| * Indicates whether `T` is a file handle, i.e. the type | |
| * is implicitly convertable to $(LREF File) or a pointer to a | |
| * $(REF FILE, core,stdc,stdio). | |
| * | |
| * Returns: | |
| * `true` if `T` is a file handle, `false` otherwise. | |
| */ | |
| template isFileHandle(T) | |
| { | |
| enum isFileHandle = is(T : FILE*) || | |
| is(T : File); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| static assert(isFileHandle!(FILE*)); | |
| static assert(isFileHandle!(File)); | |
| } | |
| /** | |
| * Property used by writeln/etc. so it can infer @safe since stdout is __gshared | |
| */ | |
| private @property File trustedStdout() @trusted | |
| { | |
| return stdout; | |
| } | |
| /*********************************** | |
| Writes its arguments in text format to standard output (without a trailing newline). | |
| Params: | |
| args = the items to write to `stdout` | |
| Throws: In case of an I/O error, throws an `StdioException`. | |
| Example: | |
| Reads `stdin` and writes it to `stdout` with an argument | |
| counter. | |
| --- | |
| import std.stdio; | |
| void main() | |
| { | |
| string line; | |
| for (size_t count = 0; (line = readln) !is null; count++) | |
| { | |
| write("Input ", count, ": ", line, "\n"); | |
| } | |
| } | |
| --- | |
| */ | |
| void write(T...)(T args) | |
| if (!is(T[0] : File)) | |
| { | |
| trustedStdout.write(args); | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| scope(failure) printf("Failed test at line %d\n", __LINE__); | |
| void[] buf; | |
| if (false) write(buf); | |
| // test write | |
| auto deleteme = testFilename(); | |
| auto f = File(deleteme, "w"); | |
| f.write("Hello, ", "world number ", 42, "!"); | |
| f.close(); | |
| scope(exit) { std.file.remove(deleteme); } | |
| assert(cast(char[]) std.file.read(deleteme) == "Hello, world number 42!"); | |
| } | |
| /*********************************** | |
| * Equivalent to `write(args, '\n')`. Calling `writeln` without | |
| * arguments is valid and just prints a newline to the standard | |
| * output. | |
| * | |
| * Params: | |
| * args = the items to write to `stdout` | |
| * | |
| * Throws: | |
| * In case of an I/O error, throws an $(LREF StdioException). | |
| * Example: | |
| * Reads `stdin` and writes it to `stdout` with an argument | |
| * counter. | |
| --- | |
| import std.stdio; | |
| void main() | |
| { | |
| string line; | |
| for (size_t count = 0; (line = readln) !is null; count++) | |
| { | |
| writeln("Input ", count, ": ", line); | |
| } | |
| } | |
| --- | |
| */ | |
| void writeln(T...)(T args) | |
| { | |
| static if (T.length == 0) | |
| { | |
| import std.exception : enforce; | |
| enforce(fputc('\n', .trustedStdout._p.handle) != EOF, "fputc failed"); | |
| } | |
| else static if (T.length == 1 && | |
| is(T[0] : const(char)[]) && | |
| (is(T[0] == U[], U) || __traits(isStaticArray, T[0]))) | |
| { | |
| // Specialization for strings - a very frequent case | |
| auto w = .trustedStdout.lockingTextWriter(); | |
| static if (__traits(isStaticArray, T[0])) | |
| { | |
| w.put(args[0][]); | |
| } | |
| else | |
| { | |
| w.put(args[0]); | |
| } | |
| w.put('\n'); | |
| } | |
| else | |
| { | |
| // Most general instance | |
| trustedStdout.write(args, '\n'); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| // Just make sure the call compiles | |
| if (false) writeln(); | |
| if (false) writeln("wyda"); | |
| // https://issues.dlang.org/show_bug.cgi?id=8040 | |
| if (false) writeln(null); | |
| if (false) writeln(">", null, "<"); | |
| // https://issues.dlang.org/show_bug.cgi?id=14041 | |
| if (false) | |
| { | |
| char[8] a; | |
| writeln(a); | |
| immutable b = a; | |
| b.writeln; | |
| const c = a[]; | |
| c.writeln; | |
| } | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| scope(failure) printf("Failed test at line %d\n", __LINE__); | |
| // test writeln | |
| auto deleteme = testFilename(); | |
| auto f = File(deleteme, "w"); | |
| scope(exit) { std.file.remove(deleteme); } | |
| f.writeln("Hello, ", "world number ", 42, "!"); | |
| f.close(); | |
| version (Windows) | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "Hello, world number 42!\r\n"); | |
| else | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "Hello, world number 42!\n"); | |
| // test writeln on stdout | |
| auto saveStdout = stdout; | |
| scope(exit) stdout = saveStdout; | |
| stdout.open(deleteme, "w"); | |
| writeln("Hello, ", "world number ", 42, "!"); | |
| stdout.close(); | |
| version (Windows) | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "Hello, world number 42!\r\n"); | |
| else | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "Hello, world number 42!\n"); | |
| stdout.open(deleteme, "w"); | |
| writeln("Hello!"c); | |
| writeln("Hello!"w); // https://issues.dlang.org/show_bug.cgi?id=8386 | |
| writeln("Hello!"d); // https://issues.dlang.org/show_bug.cgi?id=8386 | |
| writeln("embedded\0null"c); // https://issues.dlang.org/show_bug.cgi?id=8730 | |
| stdout.close(); | |
| version (Windows) | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "Hello!\r\nHello!\r\nHello!\r\nembedded\0null\r\n"); | |
| else | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "Hello!\nHello!\nHello!\nembedded\0null\n"); | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| auto f = File(deleteme, "w"); | |
| scope(exit) { std.file.remove(deleteme); } | |
| enum EI : int { A, B } | |
| enum ED : double { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle | |
| enum EC : char { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle | |
| enum ES : string { A = "aaa", B = "bbb" } | |
| f.writeln(EI.A); // false, but A on 2.058 | |
| f.writeln(EI.B); // true, but B on 2.058 | |
| f.writeln(ED.A); // A | |
| f.writeln(ED.B); // B | |
| f.writeln(EC.A); // A | |
| f.writeln(EC.B); // B | |
| f.writeln(ES.A); // A | |
| f.writeln(ES.B); // B | |
| f.close(); | |
| version (Windows) | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "A\r\nB\r\nA\r\nB\r\nA\r\nB\r\nA\r\nB\r\n"); | |
| else | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "A\nB\nA\nB\nA\nB\nA\nB\n"); | |
| } | |
| @system unittest | |
| { | |
| static auto useInit(T)(T ltw) | |
| { | |
| T val; | |
| val = ltw; | |
| val = T.init; | |
| return val; | |
| } | |
| useInit(stdout.lockingTextWriter()); | |
| } | |
| @system unittest | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=21920 | |
| void function(string) printer = &writeln!string; | |
| if (false) printer("Hello"); | |
| } | |
| /*********************************** | |
| Writes formatted data to standard output (without a trailing newline). | |
| Params: | |
| fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). | |
| When passed as a compile-time argument, the string will be statically checked | |
| against the argument types passed. | |
| args = Items to write. | |
| Note: In older versions of Phobos, it used to be possible to write: | |
| ------ | |
| writef(stderr, "%s", "message"); | |
| ------ | |
| to print a message to `stderr`. This syntax is no longer supported, and has | |
| been superceded by: | |
| ------ | |
| stderr.writef("%s", "message"); | |
| ------ | |
| */ | |
| void writef(alias fmt, A...)(A args) | |
| if (isSomeString!(typeof(fmt))) | |
| { | |
| import std.format : checkFormatException; | |
| alias e = checkFormatException!(fmt, A); | |
| static assert(!e, e); | |
| return .writef(fmt, args); | |
| } | |
| /// ditto | |
| void writef(Char, A...)(in Char[] fmt, A args) | |
| { | |
| trustedStdout.writef(fmt, args); | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| scope(failure) printf("Failed test at line %d\n", __LINE__); | |
| // test writef | |
| auto deleteme = testFilename(); | |
| auto f = File(deleteme, "w"); | |
| scope(exit) { std.file.remove(deleteme); } | |
| f.writef!"Hello, %s world number %s!"("nice", 42); | |
| f.close(); | |
| assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); | |
| // test write on stdout | |
| auto saveStdout = stdout; | |
| scope(exit) stdout = saveStdout; | |
| stdout.open(deleteme, "w"); | |
| writef!"Hello, %s world number %s!"("nice", 42); | |
| stdout.close(); | |
| assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); | |
| } | |
| /*********************************** | |
| * Equivalent to $(D writef(fmt, args, '\n')). | |
| */ | |
| void writefln(alias fmt, A...)(A args) | |
| if (isSomeString!(typeof(fmt))) | |
| { | |
| import std.format : checkFormatException; | |
| alias e = checkFormatException!(fmt, A); | |
| static assert(!e, e); | |
| return .writefln(fmt, args); | |
| } | |
| /// ditto | |
| void writefln(Char, A...)(in Char[] fmt, A args) | |
| { | |
| trustedStdout.writefln(fmt, args); | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| scope(failure) printf("Failed test at line %d\n", __LINE__); | |
| // test File.writefln | |
| auto deleteme = testFilename(); | |
| auto f = File(deleteme, "w"); | |
| scope(exit) { std.file.remove(deleteme); } | |
| f.writefln!"Hello, %s world number %s!"("nice", 42); | |
| f.close(); | |
| version (Windows) | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "Hello, nice world number 42!\r\n"); | |
| else | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "Hello, nice world number 42!\n", | |
| cast(char[]) std.file.read(deleteme)); | |
| // test writefln | |
| auto saveStdout = stdout; | |
| scope(exit) stdout = saveStdout; | |
| stdout.open(deleteme, "w"); | |
| writefln!"Hello, %s world number %s!"("nice", 42); | |
| stdout.close(); | |
| version (Windows) | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "Hello, nice world number 42!\r\n"); | |
| else | |
| assert(cast(char[]) std.file.read(deleteme) == | |
| "Hello, nice world number 42!\n"); | |
| } | |
| /** | |
| * Reads formatted data from `stdin` using $(REF formattedRead, std,_format). | |
| * Params: | |
| * format = The $(REF_ALTTEXT format string, formattedWrite, std, _format). | |
| * When passed as a compile-time argument, the string will be statically checked | |
| * against the argument types passed. | |
| * args = Items to be read. | |
| * Returns: | |
| * Same as `formattedRead`: The number of variables filled. If the input range `r` ends early, | |
| * this number will be less than the number of variables provided. | |
| * Example: | |
| ---- | |
| // test.d | |
| void main() | |
| { | |
| import std.stdio; | |
| foreach (_; 0 .. 3) | |
| { | |
| int a; | |
| readf!" %d"(a); | |
| writeln(++a); | |
| } | |
| } | |
| ---- | |
| $(CONSOLE | |
| % echo "1 2 3" | rdmd test.d | |
| 2 | |
| 3 | |
| 4 | |
| ) | |
| */ | |
| uint readf(alias format, A...)(auto ref A args) | |
| if (isSomeString!(typeof(format))) | |
| { | |
| import std.format : checkFormatException; | |
| alias e = checkFormatException!(format, A); | |
| static assert(!e, e); | |
| return .readf(format, args); | |
| } | |
| /// ditto | |
| uint readf(A...)(scope const(char)[] format, auto ref A args) | |
| { | |
| return stdin.readf(format, args); | |
| } | |
| @system unittest | |
| { | |
| float f; | |
| if (false) readf("%s", &f); | |
| char a; | |
| wchar b; | |
| dchar c; | |
| if (false) readf("%s %s %s", a, b, c); | |
| // backwards compatibility with pointers | |
| if (false) readf("%s %s %s", a, &b, c); | |
| if (false) readf("%s %s %s", &a, &b, &c); | |
| } | |
| /********************************** | |
| * Read line from `stdin`. | |
| * | |
| * This version manages its own read buffer, which means one memory allocation per call. If you are not | |
| * retaining a reference to the read data, consider the `readln(buf)` version, which may offer | |
| * better performance as it can reuse its read buffer. | |
| * | |
| * Returns: | |
| * The line that was read, including the line terminator character. | |
| * Params: | |
| * S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. | |
| * terminator = Line terminator (by default, `'\n'`). | |
| * Note: | |
| * String terminators are not supported due to ambiguity with readln(buf) below. | |
| * Throws: | |
| * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. | |
| * Example: | |
| * Reads `stdin` and writes it to `stdout`. | |
| --- | |
| import std.stdio; | |
| void main() | |
| { | |
| string line; | |
| while ((line = readln()) !is null) | |
| write(line); | |
| } | |
| --- | |
| */ | |
| S readln(S = string)(dchar terminator = '\n') | |
| if (isSomeString!S) | |
| { | |
| return stdin.readln!S(terminator); | |
| } | |
| /********************************** | |
| * Read line from `stdin` and write it to buf[], including terminating character. | |
| * | |
| * This can be faster than $(D line = readln()) because you can reuse | |
| * the buffer for each call. Note that reusing the buffer means that you | |
| * must copy the previous contents if you wish to retain them. | |
| * | |
| * Returns: | |
| * `size_t` 0 for end of file, otherwise number of characters read | |
| * Params: | |
| * buf = Buffer used to store the resulting line data. buf is resized as necessary. | |
| * terminator = Line terminator (by default, `'\n'`). Use $(REF newline, std,ascii) | |
| * for portability (unless the file was opened in text mode). | |
| * Throws: | |
| * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. | |
| * Example: | |
| * Reads `stdin` and writes it to `stdout`. | |
| --- | |
| import std.stdio; | |
| void main() | |
| { | |
| char[] buf; | |
| while (readln(buf)) | |
| write(buf); | |
| } | |
| --- | |
| */ | |
| size_t readln(C)(ref C[] buf, dchar terminator = '\n') | |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) | |
| { | |
| return stdin.readln(buf, terminator); | |
| } | |
| /** ditto */ | |
| size_t readln(C, R)(ref C[] buf, R terminator) | |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && | |
| isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) | |
| { | |
| return stdin.readln(buf, terminator); | |
| } | |
| @safe unittest | |
| { | |
| import std.meta : AliasSeq; | |
| //we can't actually test readln, so at the very least, | |
| //we test compilability | |
| void foo() | |
| { | |
| readln(); | |
| readln('\t'); | |
| static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) | |
| { | |
| readln!String(); | |
| readln!String('\t'); | |
| } | |
| static foreach (String; AliasSeq!(char[], wchar[], dchar[])) | |
| {{ | |
| String buf; | |
| readln(buf); | |
| readln(buf, '\t'); | |
| readln(buf, "<br />"); | |
| }} | |
| } | |
| } | |
| /** | |
| Reads a line from `stdin` and parses it using $(REF formattedRead, std,format,read). | |
| Params: | |
| format = The $(MREF_ALTTEXT format string, std,format). When passed as a | |
| compile-time argument, the string will be statically checked against the | |
| argument types passed. | |
| data = Items to be read. | |
| Returns: Same as `formattedRead`: the number of variables filled. If the | |
| input ends early, this number will be less that the number of variables | |
| provided. | |
| Example: | |
| --- | |
| // sum_rows.d | |
| void main() | |
| { | |
| import std.stdio; | |
| int a, b, c; | |
| while (readfln("%d %d %d", a, b, c) == 3) | |
| { | |
| writeln(a + b + c); | |
| } | |
| } | |
| --- | |
| $(CONSOLE | |
| % cat << EOF > input | |
| 1 2 3 | |
| 4 5 6 | |
| 7 8 9 | |
| EOF | |
| % rdmd sum_rows.d < input | |
| 6 | |
| 15 | |
| 24 | |
| ) | |
| */ | |
| uint readfln(alias format, Data...)(auto ref Data data) | |
| { | |
| import std.format : checkFormatException; | |
| alias e = checkFormatException!(format, Data); | |
| static assert(!e, e); | |
| return .readfln(format, data); | |
| } | |
| /// ditto | |
| uint readfln(Data...)(scope const(char)[] format, auto ref Data data) | |
| { | |
| return stdin.readfln(format, data); | |
| } | |
| @system unittest | |
| { | |
| float f; | |
| string s; | |
| char c; | |
| int n; | |
| if (false) readfln("%f %s %c %d", f, s, c, n); | |
| if (false) readfln!"%f %s %c %d"(f, s, c, n); | |
| } | |
| /* | |
| * Convenience function that forwards to `core.sys.posix.stdio.fopen` | |
| * (to `_wfopen` on Windows) | |
| * with appropriately-constructed C-style strings. | |
| */ | |
| private FILE* _fopen(R1, R2)(R1 name, R2 mode = "r") | |
| if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) && | |
| (isSomeFiniteCharInputRange!R2 || isSomeString!R2)) | |
| { | |
| import std.internal.cstring : tempCString; | |
| auto namez = name.tempCString!FSChar(); | |
| auto modez = mode.tempCString!FSChar(); | |
| static _fopenImpl(scope const(FSChar)* namez, scope const(FSChar)* modez) @trusted nothrow @nogc | |
| { | |
| version (Windows) | |
| { | |
| return _wfopen(namez, modez); | |
| } | |
| else version (Posix) | |
| { | |
| /* | |
| * The new opengroup large file support API is transparently | |
| * included in the normal C bindings. https://www.opengroup.org/platform/lfs.html#1.0 | |
| * if _FILE_OFFSET_BITS in druntime is 64, off_t is 64 bit and | |
| * the normal functions work fine. If not, then large file support | |
| * probably isn't available. Do not use the old transitional API | |
| * (the native extern(C) fopen64, https://unix.org/version2/whatsnew/lfs20mar.html#3.0) | |
| */ | |
| import core.sys.posix.stdio : fopen; | |
| return fopen(namez, modez); | |
| } | |
| else | |
| { | |
| return fopen(namez, modez); | |
| } | |
| } | |
| return _fopenImpl(namez, modez); | |
| } | |
| version (Posix) | |
| { | |
| /*********************************** | |
| * Convenience function that forwards to `core.sys.posix.stdio.popen` | |
| * with appropriately-constructed C-style strings. | |
| */ | |
| FILE* _popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc | |
| if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) && | |
| (isSomeFiniteCharInputRange!R2 || isSomeString!R2)) | |
| { | |
| import std.internal.cstring : tempCString; | |
| auto namez = name.tempCString!FSChar(); | |
| auto modez = mode.tempCString!FSChar(); | |
| static popenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc | |
| { | |
| import core.sys.posix.stdio : popen; | |
| return popen(namez, modez); | |
| } | |
| return popenImpl(namez, modez); | |
| } | |
| } | |
| /* | |
| * Convenience function that forwards to `core.stdc.stdio.fwrite` | |
| */ | |
| private auto trustedFwrite(T)(FILE* f, const T[] obj) @trusted | |
| { | |
| return fwrite(obj.ptr, T.sizeof, obj.length, f); | |
| } | |
| /* | |
| * Convenience function that forwards to `core.stdc.stdio.fread` | |
| */ | |
| private auto trustedFread(T)(FILE* f, T[] obj) @trusted | |
| if (!imported!"std.traits".hasIndirections!T) | |
| { | |
| return fread(obj.ptr, T.sizeof, obj.length, f); | |
| } | |
| private auto trustedFread(T)(FILE* f, T[] obj) @system | |
| if (imported!"std.traits".hasIndirections!T) | |
| { | |
| return fread(obj.ptr, T.sizeof, obj.length, f); | |
| } | |
| /** | |
| * Iterates through the lines of a file by using `foreach`. | |
| * | |
| * Example: | |
| * | |
| --------- | |
| void main() | |
| { | |
| foreach (string line; lines(stdin)) | |
| { | |
| ... use line ... | |
| } | |
| } | |
| --------- | |
| The line terminator (`'\n'` by default) is part of the string read (it | |
| could be missing in the last line of the file). Several types are | |
| supported for `line`, and the behavior of `lines` | |
| changes accordingly: | |
| $(OL $(LI If `line` has type `string`, $(D | |
| wstring), or `dstring`, a new string of the respective type | |
| is allocated every read.) $(LI If `line` has type $(D | |
| char[]), `wchar[]`, `dchar[]`, the line's content | |
| will be reused (overwritten) across reads.) $(LI If `line` | |
| has type `immutable(ubyte)[]`, the behavior is similar to | |
| case (1), except that no UTF checking is attempted upon input.) $(LI | |
| If `line` has type `ubyte[]`, the behavior is | |
| similar to case (2), except that no UTF checking is attempted upon | |
| input.)) | |
| In all cases, a two-symbols versions is also accepted, in which case | |
| the first symbol (of integral type, e.g. `ulong` or $(D | |
| uint)) tracks the zero-based number of the current line. | |
| Example: | |
| ---- | |
| foreach (ulong i, string line; lines(stdin)) | |
| { | |
| ... use line ... | |
| } | |
| ---- | |
| In case of an I/O error, an `StdioException` is thrown. | |
| See_Also: | |
| $(LREF byLine) | |
| */ | |
| struct lines | |
| { | |
| private File f; | |
| private dchar terminator = '\n'; | |
| /** | |
| Constructor. | |
| Params: | |
| f = File to read lines from. | |
| terminator = Line separator (`'\n'` by default). | |
| */ | |
| this(File f, dchar terminator = '\n') @safe | |
| { | |
| this.f = f; | |
| this.terminator = terminator; | |
| } | |
| int opApply(D)(scope D dg) | |
| { | |
| import std.traits : Parameters; | |
| alias Parms = Parameters!(dg); | |
| static if (isSomeString!(Parms[$ - 1])) | |
| { | |
| int result = 0; | |
| static if (is(Parms[$ - 1] : const(char)[])) | |
| alias C = char; | |
| else static if (is(Parms[$ - 1] : const(wchar)[])) | |
| alias C = wchar; | |
| else static if (is(Parms[$ - 1] : const(dchar)[])) | |
| alias C = dchar; | |
| C[] line; | |
| static if (Parms.length == 2) | |
| Parms[0] i = 0; | |
| for (;;) | |
| { | |
| import std.conv : to; | |
| if (!f.readln(line, terminator)) break; | |
| auto copy = to!(Parms[$ - 1])(line); | |
| static if (Parms.length == 2) | |
| { | |
| result = dg(i, copy); | |
| ++i; | |
| } | |
| else | |
| { | |
| result = dg(copy); | |
| } | |
| if (result != 0) break; | |
| } | |
| return result; | |
| } | |
| else | |
| { | |
| // raw read | |
| return opApplyRaw(dg); | |
| } | |
| } | |
| // no UTF checking | |
| int opApplyRaw(D)(scope D dg) | |
| { | |
| import std.conv : to; | |
| import std.exception : assumeUnique; | |
| import std.traits : Parameters; | |
| alias Parms = Parameters!(dg); | |
| enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]); | |
| int result = 1; | |
| int c = void; | |
| _FLOCK(f._p.handle); | |
| scope(exit) _FUNLOCK(f._p.handle); | |
| ubyte[] buffer; | |
| static if (Parms.length == 2) | |
| Parms[0] line = 0; | |
| while ((c = _FGETC(cast(_iobuf*) f._p.handle)) != -1) | |
| { | |
| buffer ~= to!(ubyte)(c); | |
| if (c == terminator) | |
| { | |
| static if (duplicate) | |
| auto arg = assumeUnique(buffer); | |
| else | |
| alias arg = buffer; | |
| // unlock the file while calling the delegate | |
| _FUNLOCK(f._p.handle); | |
| scope(exit) _FLOCK(f._p.handle); | |
| static if (Parms.length == 1) | |
| { | |
| result = dg(arg); | |
| } | |
| else | |
| { | |
| result = dg(line, arg); | |
| ++line; | |
| } | |
| if (result) break; | |
| static if (!duplicate) | |
| buffer.length = 0; | |
| } | |
| } | |
| // can only reach when _FGETC returned -1 | |
| if (!f.eof) throw new StdioException("Error in reading file"); // error occured | |
| return result; | |
| } | |
| } | |
| @safe unittest | |
| { | |
| /* | |
| As pointed out in <https://github.com/dlang/phobos/issues/10605>, | |
| it's a pity that `byLine()` & co. aren't @safe to use yet. | |
| This is a first attempt at working towards that goal. | |
| For now, this test doesn't do much; as there isn't much to do safely yet. | |
| */ | |
| auto deleteMe = testFilename(); | |
| scope(exit) { imported!"std.file".remove(deleteMe); } | |
| // Setup | |
| { | |
| auto f = File(deleteMe, "w"); | |
| scope(exit) { f.close(); } | |
| foreach (i; 1 .. 11) | |
| f.writeln(i); | |
| } | |
| // Actual tests | |
| { | |
| auto f = File(deleteMe, "r"); | |
| scope(exit) { f.close(); } | |
| auto myLines = lines(f); | |
| foreach (string line; myLines) | |
| continue; | |
| } | |
| { | |
| auto f = File(deleteMe, "r"); | |
| scope(exit) { f.close(); } | |
| auto myByLineCopy = f.byLineCopy; | |
| foreach (line; myByLineCopy) | |
| continue; | |
| } | |
| { | |
| auto f = File(deleteMe, "r"); | |
| scope(exit) { f.close(); } | |
| auto myByLine = f.byLine; | |
| foreach (line; myByLine) | |
| continue; | |
| } | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| import std.meta : AliasSeq; | |
| scope(failure) printf("Failed test at line %d\n", __LINE__); | |
| auto deleteme = testFilename(); | |
| scope(exit) { std.file.remove(deleteme); } | |
| alias TestedWith = | |
| AliasSeq!(string, wstring, dstring, | |
| char[], wchar[], dchar[]); | |
| foreach (T; TestedWith) | |
| { | |
| // test looping with an empty file | |
| std.file.write(deleteme, ""); | |
| auto f = File(deleteme, "r"); | |
| foreach (T line; lines(f)) | |
| { | |
| assert(false); | |
| } | |
| f.close(); | |
| // test looping with a file with three lines | |
| std.file.write(deleteme, "Line one\nline two\nline three\n"); | |
| f.open(deleteme, "r"); | |
| uint i = 0; | |
| foreach (T line; lines(f)) | |
| { | |
| if (i == 0) assert(line == "Line one\n"); | |
| else if (i == 1) assert(line == "line two\n"); | |
| else if (i == 2) assert(line == "line three\n"); | |
| else assert(false); | |
| ++i; | |
| } | |
| f.close(); | |
| // test looping with a file with three lines, last without a newline | |
| std.file.write(deleteme, "Line one\nline two\nline three"); | |
| f.open(deleteme, "r"); | |
| i = 0; | |
| foreach (T line; lines(f)) | |
| { | |
| if (i == 0) assert(line == "Line one\n"); | |
| else if (i == 1) assert(line == "line two\n"); | |
| else if (i == 2) assert(line == "line three"); | |
| else assert(false); | |
| ++i; | |
| } | |
| f.close(); | |
| } | |
| // test with ubyte[] inputs | |
| alias TestedWith2 = AliasSeq!(immutable(ubyte)[], ubyte[]); | |
| foreach (T; TestedWith2) | |
| { | |
| // test looping with an empty file | |
| std.file.write(deleteme, ""); | |
| auto f = File(deleteme, "r"); | |
| foreach (T line; lines(f)) | |
| { | |
| assert(false); | |
| } | |
| f.close(); | |
| // test looping with a file with three lines | |
| std.file.write(deleteme, "Line one\nline two\nline three\n"); | |
| f.open(deleteme, "r"); | |
| uint i = 0; | |
| foreach (T line; lines(f)) | |
| { | |
| if (i == 0) assert(cast(char[]) line == "Line one\n"); | |
| else if (i == 1) assert(cast(char[]) line == "line two\n", | |
| T.stringof ~ " " ~ cast(char[]) line); | |
| else if (i == 2) assert(cast(char[]) line == "line three\n"); | |
| else assert(false); | |
| ++i; | |
| } | |
| f.close(); | |
| // test looping with a file with three lines, last without a newline | |
| std.file.write(deleteme, "Line one\nline two\nline three"); | |
| f.open(deleteme, "r"); | |
| i = 0; | |
| foreach (T line; lines(f)) | |
| { | |
| if (i == 0) assert(cast(char[]) line == "Line one\n"); | |
| else if (i == 1) assert(cast(char[]) line == "line two\n"); | |
| else if (i == 2) assert(cast(char[]) line == "line three"); | |
| else assert(false); | |
| ++i; | |
| } | |
| f.close(); | |
| } | |
| static foreach (T; AliasSeq!(ubyte[])) | |
| { | |
| // test looping with a file with three lines, last without a newline | |
| // using a counter too this time | |
| std.file.write(deleteme, "Line one\nline two\nline three"); | |
| auto f = File(deleteme, "r"); | |
| uint i = 0; | |
| foreach (ulong j, T line; lines(f)) | |
| { | |
| if (i == 0) assert(cast(char[]) line == "Line one\n"); | |
| else if (i == 1) assert(cast(char[]) line == "line two\n"); | |
| else if (i == 2) assert(cast(char[]) line == "line three"); | |
| else assert(false); | |
| ++i; | |
| } | |
| f.close(); | |
| } | |
| } | |
| /** | |
| Iterates through a file a chunk at a time by using `foreach`. | |
| Example: | |
| --------- | |
| void main() | |
| { | |
| foreach (ubyte[] buffer; chunks(stdin, 4096)) | |
| { | |
| ... use buffer ... | |
| } | |
| } | |
| --------- | |
| The content of `buffer` is reused across calls. In the | |
| example above, `buffer.length` is 4096 for all iterations, | |
| except for the last one, in which case `buffer.length` may | |
| be less than 4096 (but always greater than zero). | |
| In case of an I/O error, an `StdioException` is thrown. | |
| */ | |
| auto chunks(File f, size_t size) | |
| { | |
| return ChunksImpl(f, size); | |
| } | |
| private struct ChunksImpl | |
| { | |
| private File f; | |
| private size_t size; | |
| // private string fileName; // Currently, no use | |
| this(File f, size_t size) | |
| in | |
| { | |
| assert(size, "size must be larger than 0"); | |
| } | |
| do | |
| { | |
| this.f = f; | |
| this.size = size; | |
| } | |
| int opApply(D)(scope D dg) | |
| { | |
| import core.stdc.stdlib : alloca; | |
| import std.exception : enforce; | |
| enforce(f.isOpen, "Attempting to read from an unopened file"); | |
| enum maxStackSize = 1024 * 16; | |
| ubyte[] buffer = void; | |
| if (size < maxStackSize) | |
| buffer = (cast(ubyte*) alloca(size))[0 .. size]; | |
| else | |
| buffer = new ubyte[size]; | |
| size_t r = void; | |
| int result = 1; | |
| uint tally = 0; | |
| while ((r = trustedFread(f._p.handle, buffer)) > 0) | |
| { | |
| assert(r <= size); | |
| if (r != size) | |
| { | |
| // error occured | |
| if (!f.eof) throw new StdioException(null); | |
| buffer.length = r; | |
| } | |
| static if (is(typeof(dg(tally, buffer)))) | |
| { | |
| if ((result = dg(tally, buffer)) != 0) break; | |
| } | |
| else | |
| { | |
| if ((result = dg(buffer)) != 0) break; | |
| } | |
| ++tally; | |
| } | |
| return result; | |
| } | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| scope(failure) printf("Failed test at line %d\n", __LINE__); | |
| auto deleteme = testFilename(); | |
| scope(exit) { std.file.remove(deleteme); } | |
| // test looping with an empty file | |
| std.file.write(deleteme, ""); | |
| auto f = File(deleteme, "r"); | |
| foreach (ubyte[] line; chunks(f, 4)) | |
| { | |
| assert(false); | |
| } | |
| f.close(); | |
| // test looping with a file with three lines | |
| std.file.write(deleteme, "Line one\nline two\nline three\n"); | |
| f = File(deleteme, "r"); | |
| uint i = 0; | |
| foreach (ubyte[] line; chunks(f, 3)) | |
| { | |
| if (i == 0) assert(cast(char[]) line == "Lin"); | |
| else if (i == 1) assert(cast(char[]) line == "e o"); | |
| else if (i == 2) assert(cast(char[]) line == "ne\n"); | |
| else break; | |
| ++i; | |
| } | |
| f.close(); | |
| } | |
| // Issue 21730 - null ptr dereferenced in ChunksImpl.opApply (SIGSEGV) | |
| @system unittest | |
| { | |
| import std.exception : assertThrown; | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| scope(exit) { if (std.file.exists(deleteme)) std.file.remove(deleteme); } | |
| auto err1 = File(deleteme, "w+x"); | |
| err1.close; | |
| std.file.remove(deleteme); | |
| assertThrown(() {foreach (ubyte[] buf; chunks(err1, 4096)) {}}()); | |
| } | |
| /** | |
| Writes an array or range to a file. | |
| Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)). | |
| Similar to $(REF write, std,file), strings are written as-is, | |
| rather than encoded according to the `File`'s $(HTTP | |
| en.cppreference.com/w/c/io#Narrow_and_wide_orientation, | |
| orientation). | |
| */ | |
| void toFile(T)(T data, string fileName) | |
| if (is(typeof(copy(data, stdout.lockingBinaryWriter)))) | |
| { | |
| copy(data, File(fileName, "wb").lockingBinaryWriter); | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| scope(exit) { std.file.remove(deleteme); } | |
| "Test".toFile(deleteme); | |
| assert(std.file.readText(deleteme) == "Test"); | |
| } | |
| /********************* | |
| * Thrown if I/O errors happen. | |
| */ | |
| class StdioException : Exception | |
| { | |
| static import core.stdc.errno; | |
| /// Operating system error code. | |
| uint errno; | |
| /** | |
| Initialize with a message and an error code. | |
| */ | |
| this(string message, uint e = core.stdc.errno.errno) @trusted | |
| { | |
| import std.exception : errnoString; | |
| errno = e; | |
| auto sysmsg = errnoString(errno); | |
| // If e is 0, we don't use the system error message. (The message | |
| // is "Success", which is rather pointless for an exception.) | |
| super(e == 0 ? message | |
| : (message ? message ~ " (" ~ sysmsg ~ ")" : sysmsg)); | |
| } | |
| /** Convenience functions that throw an `StdioException`. */ | |
| static void opCall(string msg) @safe | |
| { | |
| throw new StdioException(msg); | |
| } | |
| /// ditto | |
| static void opCall() @safe | |
| { | |
| throw new StdioException(null, core.stdc.errno.errno); | |
| } | |
| } | |
| enum StdFileHandle: string | |
| { | |
| stdin = "core.stdc.stdio.stdin", | |
| stdout = "core.stdc.stdio.stdout", | |
| stderr = "core.stdc.stdio.stderr", | |
| } | |
| // Undocumented but public because the std* handles are aliasing it. | |
| @property ref File makeGlobal(StdFileHandle _iob)() | |
| { | |
| __gshared File.Impl impl; | |
| __gshared File result; | |
| // Use an inline spinlock to make sure the initializer is only run once. | |
| // We assume there will be at most uint.max / 2 threads trying to initialize | |
| // `handle` at once and steal the high bit to indicate that the globals have | |
| // been initialized. | |
| static shared uint spinlock; | |
| import core.atomic : atomicLoad, atomicOp, MemoryOrder; | |
| if (atomicLoad!(MemoryOrder.acq)(spinlock) <= uint.max / 2) | |
| { | |
| for (;;) | |
| { | |
| if (atomicLoad!(MemoryOrder.acq)(spinlock) > uint.max / 2) | |
| break; | |
| if (atomicOp!"+="(spinlock, 1) == 1) | |
| { | |
| with (StdFileHandle) | |
| assert(_iob == stdin || _iob == stdout || _iob == stderr); | |
| impl.handle = cast() mixin(_iob); | |
| result._p = &impl; | |
| atomicOp!"+="(spinlock, uint.max / 2); | |
| break; | |
| } | |
| atomicOp!"-="(spinlock, 1); | |
| } | |
| } | |
| return result; | |
| } | |
| /** The standard input stream. | |
| Returns: | |
| stdin as a $(LREF File). | |
| Note: | |
| The returned $(LREF File) wraps $(REF stdin,core,stdc,stdio), and | |
| is therefore thread global. Reassigning `stdin` to a different | |
| `File` must be done in a single-threaded or locked context in | |
| order to avoid race conditions. | |
| All reading from `stdin` automatically locks the file globally, | |
| and will cause all other threads calling `read` to wait until | |
| the lock is released. | |
| */ | |
| alias stdin = makeGlobal!(StdFileHandle.stdin); | |
| /// | |
| @safe unittest | |
| { | |
| // Read stdin, sort lines, write to stdout | |
| import std.algorithm.mutation : copy; | |
| import std.algorithm.sorting : sort; | |
| import std.array : array; | |
| import std.typecons : Yes; | |
| void main() | |
| { | |
| stdin // read from stdin | |
| .byLineCopy(Yes.keepTerminator) // copying each line | |
| .array() // convert to array of lines | |
| .sort() // sort the lines | |
| .copy( // copy output of .sort to an OutputRange | |
| stdout.lockingTextWriter()); // the OutputRange | |
| } | |
| } | |
| /** | |
| The standard output stream. | |
| Returns: | |
| stdout as a $(LREF File). | |
| Note: | |
| The returned $(LREF File) wraps $(REF stdout,core,stdc,stdio), and | |
| is therefore thread global. Reassigning `stdout` to a different | |
| `File` must be done in a single-threaded or locked context in | |
| order to avoid race conditions. | |
| All writing to `stdout` automatically locks the file globally, | |
| and will cause all other threads calling `write` to wait until | |
| the lock is released. | |
| */ | |
| alias stdout = makeGlobal!(StdFileHandle.stdout); | |
| /// | |
| @safe unittest | |
| { | |
| void main() | |
| { | |
| stdout.writeln("Write a message to stdout."); | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| void main() | |
| { | |
| import std.algorithm.iteration : filter, map, sum; | |
| import std.format : format; | |
| import std.range : iota, tee; | |
| int len; | |
| const r = 6.iota | |
| .filter!(a => a % 2) // 1 3 5 | |
| .map!(a => a * 2) // 2 6 10 | |
| .tee!(_ => stdout.writefln("len: %d", len++)) | |
| .sum; | |
| assert(r == 18); | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| void main() | |
| { | |
| import std.algorithm.mutation : copy; | |
| import std.algorithm.iteration : map; | |
| import std.format : format; | |
| import std.range : iota; | |
| 10.iota | |
| .map!(e => "N: %d".format(e)) | |
| .copy(stdout.lockingTextWriter()); // the OutputRange | |
| } | |
| } | |
| /** | |
| The standard error stream. | |
| Returns: | |
| stderr as a $(LREF File). | |
| Note: | |
| The returned $(LREF File) wraps $(REF stderr,core,stdc,stdio), and | |
| is therefore thread global. Reassigning `stderr` to a different | |
| `File` must be done in a single-threaded or locked context in | |
| order to avoid race conditions. | |
| All writing to `stderr` automatically locks the file globally, | |
| and will cause all other threads calling `write` to wait until | |
| the lock is released. | |
| */ | |
| alias stderr = makeGlobal!(StdFileHandle.stderr); | |
| /// | |
| @safe unittest | |
| { | |
| void main() | |
| { | |
| stderr.writeln("Write a message to stderr."); | |
| } | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| import std.typecons : tuple; | |
| scope(failure) printf("Failed test at line %d\n", __LINE__); | |
| auto deleteme = testFilename(); | |
| std.file.write(deleteme, "1 2\n4 1\n5 100"); | |
| scope(exit) std.file.remove(deleteme); | |
| { | |
| File f = File(deleteme); | |
| scope(exit) f.close(); | |
| auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ]; | |
| uint i; | |
| foreach (e; f.byRecord!(int, int)("%s %s")) | |
| { | |
| //writeln(e); | |
| assert(e == t[i++]); | |
| } | |
| assert(i == 3); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| // Retain backwards compatibility | |
| // https://issues.dlang.org/show_bug.cgi?id=17472 | |
| static assert(is(typeof(stdin) == File)); | |
| static assert(is(typeof(stdout) == File)); | |
| static assert(is(typeof(stderr) == File)); | |
| } | |
| // roll our own appender, but with "safe" arrays | |
| private struct ReadlnAppender | |
| { | |
| char[] buf; | |
| size_t pos; | |
| bool safeAppend = false; | |
| void initialize(char[] b) @safe | |
| { | |
| buf = b; | |
| pos = 0; | |
| } | |
| @property char[] data() @trusted | |
| { | |
| if (safeAppend) | |
| assumeSafeAppend(buf.ptr[0 .. pos]); | |
| return buf.ptr[0 .. pos]; | |
| } | |
| bool reserveWithoutAllocating(size_t n) | |
| { | |
| if (buf.length >= pos + n) // buf is already large enough | |
| return true; | |
| immutable curCap = buf.capacity; | |
| if (curCap >= pos + n) | |
| { | |
| buf.length = curCap; | |
| /* Any extra capacity we end up not using can safely be claimed | |
| by someone else. */ | |
| safeAppend = true; | |
| return true; | |
| } | |
| return false; | |
| } | |
| void reserve(size_t n) @trusted | |
| { | |
| import core.stdc.string : memcpy; | |
| if (!reserveWithoutAllocating(n)) | |
| { | |
| size_t ncap = buf.length * 2 + 128 + n; | |
| char[] nbuf = new char[ncap]; | |
| memcpy(nbuf.ptr, buf.ptr, pos); | |
| buf = nbuf; | |
| // Allocated a new buffer. No one else knows about it. | |
| safeAppend = true; | |
| } | |
| } | |
| void putchar(char c) @trusted | |
| { | |
| reserve(1); | |
| buf.ptr[pos++] = c; | |
| } | |
| void putdchar(dchar dc) @trusted | |
| { | |
| import std.utf : encode, UseReplacementDchar; | |
| char[4] ubuf; | |
| immutable size = encode!(UseReplacementDchar.yes)(ubuf, dc); | |
| reserve(size); | |
| foreach (c; ubuf) | |
| buf.ptr[pos++] = c; | |
| } | |
| void putonly(const char[] b) @trusted | |
| { | |
| import core.stdc.string : memcpy; | |
| assert(pos == 0); // assume this is the only put call | |
| if (reserveWithoutAllocating(b.length)) | |
| memcpy(buf.ptr + pos, b.ptr, b.length); | |
| else | |
| buf = b.dup; | |
| pos = b.length; | |
| } | |
| } | |
| private struct LockedFile | |
| { | |
| private @system _iobuf* fp; | |
| this(FILE* fps) @trusted | |
| { | |
| _FLOCK(fps); | |
| // Since fps is now locked, we can cast away shared | |
| fp = cast(_iobuf*) fps; | |
| } | |
| @disable this(); | |
| @disable this(this); | |
| @disable void opAssign(LockedFile); | |
| // these use unlocked fgetc calls | |
| @trusted fgetc() { return _FGETC(fp); } | |
| @trusted fgetwc() { return _FGETWC(fp); } | |
| ~this() @trusted | |
| { | |
| _FUNLOCK(cast(FILE*) fp); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| void f() @safe | |
| { | |
| FILE* fps; | |
| auto lf = LockedFile(fps); | |
| static assert(!__traits(compiles, lf = LockedFile(fps))); | |
| version (ShouldFail) | |
| { | |
| lf.fps = null; // error with -preview=systemVariables | |
| } | |
| } | |
| } | |
| // Private implementation of readln | |
| private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation) @safe | |
| { | |
| version (CRuntime_Microsoft) | |
| { | |
| auto lf = LockedFile(fps); | |
| ReadlnAppender app; | |
| app.initialize(buf); | |
| int c; | |
| while ((c = lf.fgetc()) != -1) | |
| { | |
| app.putchar(cast(char) c); | |
| if (c == terminator) | |
| { | |
| buf = app.data; | |
| return buf.length; | |
| } | |
| } | |
| if (ferror(fps)) | |
| StdioException(); | |
| buf = app.data; | |
| return buf.length; | |
| } | |
| else static if (__traits(compiles, core.sys.posix.stdio.getdelim)) | |
| { | |
| if (orientation == File.Orientation.wide) | |
| { | |
| import core.stdc.wchar_ : fwide; | |
| auto lf = LockedFile(fps); | |
| /* Stream is in wide characters. | |
| * Read them and convert to chars. | |
| */ | |
| version (Windows) | |
| { | |
| buf.length = 0; | |
| for (int c = void; (c = lf.fgetwc()) != -1; ) | |
| { | |
| if ((c & ~0x7F) == 0) | |
| { buf ~= c; | |
| if (c == terminator) | |
| break; | |
| } | |
| else | |
| { | |
| if (c >= 0xD800 && c <= 0xDBFF) | |
| { | |
| int c2 = void; | |
| if ((c2 = lf.fgetwc()) != -1 || | |
| c2 < 0xDC00 && c2 > 0xDFFF) | |
| { | |
| StdioException("unpaired UTF-16 surrogate"); | |
| } | |
| c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); | |
| } | |
| import std.utf : encode; | |
| encode(buf, c); | |
| } | |
| } | |
| if (ferror(fps)) | |
| StdioException(); | |
| return buf.length; | |
| } | |
| else version (Posix) | |
| { | |
| buf.length = 0; | |
| for (int c; (c = lf.fgetwc()) != -1; ) | |
| { | |
| import std.utf : encode; | |
| if ((c & ~0x7F) == 0) | |
| buf ~= cast(char) c; | |
| else | |
| encode(buf, cast(dchar) c); | |
| if (c == terminator) | |
| break; | |
| } | |
| if (ferror(fps)) | |
| StdioException(); | |
| return buf.length; | |
| } | |
| else | |
| { | |
| static assert(0); | |
| } | |
| } | |
| return () @trusted { | |
| import core.stdc.stdlib : free; | |
| static char *lineptr = null; | |
| static size_t n = 0; | |
| scope(exit) | |
| { | |
| if (n > 128 * 1024) | |
| { | |
| // Bound memory used by readln | |
| free(lineptr); | |
| lineptr = null; | |
| n = 0; | |
| } | |
| } | |
| const s = core.sys.posix.stdio.getdelim(&lineptr, &n, terminator, fps); | |
| if (s < 0) | |
| { | |
| if (ferror(fps)) | |
| StdioException(); | |
| buf.length = 0; // end of file | |
| return 0; | |
| } | |
| const line = lineptr[0 .. s]; | |
| if (s <= buf.length) | |
| { | |
| buf = buf[0 .. s]; | |
| buf[] = line; | |
| } | |
| else | |
| { | |
| buf = line.dup; | |
| } | |
| return s; | |
| }(); | |
| } | |
| else // version (NO_GETDELIM) | |
| { | |
| import core.stdc.wchar_ : fwide; | |
| auto lf = LockedFile(fps); | |
| if (orientation == File.Orientation.wide) | |
| { | |
| /* Stream is in wide characters. | |
| * Read them and convert to chars. | |
| */ | |
| version (Windows) | |
| { | |
| buf.length = 0; | |
| for (int c; (c = lf.fgetwc()) != -1; ) | |
| { | |
| if ((c & ~0x7F) == 0) | |
| { buf ~= c; | |
| if (c == terminator) | |
| break; | |
| } | |
| else | |
| { | |
| if (c >= 0xD800 && c <= 0xDBFF) | |
| { | |
| int c2 = void; | |
| if ((c2 = lf.fgetwc()) != -1 || | |
| c2 < 0xDC00 && c2 > 0xDFFF) | |
| { | |
| StdioException("unpaired UTF-16 surrogate"); | |
| } | |
| c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); | |
| } | |
| import std.utf : encode; | |
| encode(buf, c); | |
| } | |
| } | |
| if (ferror(fps)) | |
| StdioException(); | |
| return buf.length; | |
| } | |
| else version (Posix) | |
| { | |
| import std.utf : encode; | |
| buf.length = 0; | |
| for (int c; (c = lf.fgetwc()) != -1; ) | |
| { | |
| if ((c & ~0x7F) == 0) | |
| buf ~= cast(char) c; | |
| else | |
| encode(buf, cast(dchar) c); | |
| if (c == terminator) | |
| break; | |
| } | |
| if (ferror(fps)) | |
| StdioException(); | |
| return buf.length; | |
| } | |
| else | |
| { | |
| static assert(0); | |
| } | |
| } | |
| // Narrow stream | |
| // First, fill the existing buffer | |
| for (size_t bufPos = 0; bufPos < buf.length; ) | |
| { | |
| immutable c = lf.fgetc(); | |
| if (c == -1) | |
| { | |
| buf.length = bufPos; | |
| goto endGame; | |
| } | |
| buf[bufPos++] = cast(char) c; | |
| if (c == terminator) | |
| { | |
| // No need to test for errors in file | |
| buf.length = bufPos; | |
| return bufPos; | |
| } | |
| } | |
| // Then, append to it | |
| for (int c; (c = lf.fgetc()) != -1; ) | |
| { | |
| buf ~= cast(char) c; | |
| if (c == terminator) | |
| { | |
| // No need to test for errors in file | |
| return buf.length; | |
| } | |
| } | |
| endGame: | |
| if (ferror(fps)) | |
| StdioException(); | |
| return buf.length; | |
| } | |
| } | |
| @system unittest | |
| { | |
| static import std.file; | |
| auto deleteme = testFilename(); | |
| scope(exit) std.file.remove(deleteme); | |
| std.file.write(deleteme, "abcd\n0123456789abcde\n1234\n"); | |
| File f = File(deleteme, "rb"); | |
| char[] ln = new char[2]; | |
| f.readln(ln); | |
| assert(ln == "abcd\n"); | |
| char[] t = ln[0 .. 2]; | |
| t ~= 't'; | |
| assert(t == "abt"); | |
| // https://issues.dlang.org/show_bug.cgi?id=13856: ln stomped to "abtd" | |
| assert(ln == "abcd\n"); | |
| // it can also stomp the array length | |
| ln = new char[4]; | |
| f.readln(ln); | |
| assert(ln == "0123456789abcde\n"); | |
| char[100] buf; | |
| ln = buf[]; | |
| f.readln(ln); | |
| assert(ln == "1234\n"); | |
| assert(ln.ptr == buf.ptr); // avoid allocation, buffer is good enough | |
| } | |
| /** Experimental network access via the File interface | |
| Opens a TCP connection to the given host and port, then returns | |
| a File struct with read and write access through the same interface | |
| as any other file (meaning writef and the byLine ranges work!). | |
| Authors: | |
| Adam D. Ruppe | |
| Bugs: | |
| Only works on Linux | |
| */ | |
| version (linux) | |
| { | |
| File openNetwork(string host, ushort port) | |
| { | |
| import core.stdc.string : memcpy; | |
| import core.sys.posix.arpa.inet : htons; | |
| import core.sys.posix.netdb : gethostbyname; | |
| import core.sys.posix.netinet.in_ : sockaddr_in; | |
| static import core.sys.posix.unistd; | |
| static import sock = core.sys.posix.sys.socket; | |
| import std.conv : to; | |
| import std.exception : enforce; | |
| import std.internal.cstring : tempCString; | |
| auto h = enforce( gethostbyname(host.tempCString()), | |
| new StdioException("gethostbyname")); | |
| int s = sock.socket(sock.AF_INET, sock.SOCK_STREAM, 0); | |
| enforce(s != -1, new StdioException("socket")); | |
| scope(failure) | |
| { | |
| // want to make sure it doesn't dangle if something throws. Upon | |
| // normal exit, the File struct's reference counting takes care of | |
| // closing, so we don't need to worry about success | |
| core.sys.posix.unistd.close(s); | |
| } | |
| sockaddr_in addr; | |
| addr.sin_family = sock.AF_INET; | |
| addr.sin_port = htons(port); | |
| memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length); | |
| enforce(sock.connect(s, cast(sock.sockaddr*) &addr, addr.sizeof) != -1, | |
| new StdioException("Connect failed")); | |
| File f; | |
| f.fdopen(s, "w+", host ~ ":" ~ to!string(port)); | |
| return f; | |
| } | |
| } | |
| version (StdUnittest) private string testFilename(string file = __FILE__, size_t line = __LINE__) @safe | |
| { | |
| import std.conv : text; | |
| import std.file : deleteme; | |
| import std.path : baseName; | |
| // filename intentionally contains non-ASCII (Russian) characters for | |
| // https://issues.dlang.org/show_bug.cgi?id=7648 | |
| return text(deleteme, "-детка.", baseName(file), ".", line); | |
| } |