diff options
Diffstat (limited to 'libphobos/src/std/stdio.d')
-rw-r--r-- | libphobos/src/std/stdio.d | 5159 |
1 files changed, 5159 insertions, 0 deletions
diff --git a/libphobos/src/std/stdio.d b/libphobos/src/std/stdio.d new file mode 100644 index 0000000..9683c98 --- /dev/null +++ b/libphobos/src/std/stdio.d @@ -0,0 +1,5159 @@ +// Written in the D programming language. + +/** +Standard I/O functions that extend $(B core.stdc.stdio). $(B core.stdc.stdio) +is $(D_PARAM public)ally imported when importing $(B std.stdio). + +Source: $(PHOBOSSRC std/_stdio.d) +Copyright: Copyright Digital Mars 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 + */ +module 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.primitives; // ElementEncodingType, empty, front, + // isBidirectionalRange, isInputRange, put +import std.traits; // isSomeChar, isSomeString, Unqual, isPointer +import std.typecons; // Flag + +/++ +If flag $(D KeepTerminator) is set to $(D KeepTerminator.yes), then the delimiter +is included in the strings returned. ++/ +alias KeepTerminator = Flag!"keepTerminator"; + +version (CRuntime_Microsoft) +{ + version = MICROSOFT_STDIO; +} +else version (CRuntime_DigitalMars) +{ + // Specific to the way Digital Mars C does stdio + version = DIGITAL_MARS_STDIO; +} + +version (CRuntime_Glibc) +{ + // Specific to the way Gnu C does stdio + version = GCC_IO; + version = HAS_GETDELIM; +} + +version (OSX) +{ + version = GENERIC_IO; + version = HAS_GETDELIM; +} + +version (FreeBSD) +{ + version = GENERIC_IO; + version = HAS_GETDELIM; +} + +version (NetBSD) +{ + version = GENERIC_IO; + version = HAS_GETDELIM; +} + +version (Solaris) +{ + version = GENERIC_IO; + version = NO_GETDELIM; +} + +version (CRuntime_Bionic) +{ + version = GENERIC_IO; + version = NO_GETDELIM; +} + +// Character type used for operating system filesystem APIs +version (Windows) +{ + private alias FSChar = wchar; +} +else version (Posix) +{ + private alias FSChar = char; +} +else + static assert(0); + +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(in wchar* filename, in wchar* mode); + extern (C) nothrow @nogc FILE* _wfreopen(in wchar* filename, in wchar* mode, FILE* fp); + + import core.sys.windows.windows : HANDLE; +} + +version (DIGITAL_MARS_STDIO) +{ + extern (C) + { + /* ** + * Digital Mars under-the-hood C I/O functions. + * Use _iobuf* for the unshared version of FILE*, + * usable when the FILE is locked. + */ + nothrow: + @nogc: + int _fputc_nlock(int, _iobuf*); + int _fputwc_nlock(int, _iobuf*); + int _fgetc_nlock(_iobuf*); + int _fgetwc_nlock(_iobuf*); + int __fp_lock(FILE*); + void __fp_unlock(FILE*); + + int setmode(int, int); + } + alias FPUTC = _fputc_nlock; + alias FPUTWC = _fputwc_nlock; + alias FGETC = _fgetc_nlock; + alias FGETWC = _fgetwc_nlock; + + alias FLOCK = __fp_lock; + alias FUNLOCK = __fp_unlock; + + alias _setmode = setmode; + enum _O_BINARY = 0x8000; + int _fileno(FILE* f) { return f._file; } + alias fileno = _fileno; +} +else version (MICROSOFT_STDIO) +{ + extern (C) + { + /* ** + * Microsoft under-the-hood C I/O functions + */ + nothrow: + @nogc: + int _fputc_nolock(int, _iobuf*); + int _fputwc_nolock(int, _iobuf*); + int _fgetc_nolock(_iobuf*); + int _fgetwc_nolock(_iobuf*); + void _lock_file(FILE*); + void _unlock_file(FILE*); + int _setmode(int, int); + int _fileno(FILE*); + FILE* _fdopen(int, const (char)*); + int _fseeki64(FILE*, long, int); + long _ftelli64(FILE*); + } + alias FPUTC = _fputc_nolock; + alias FPUTWC = _fputwc_nolock; + alias FGETC = _fgetc_nolock; + alias FGETWC = _fgetwc_nolock; + + alias FLOCK = _lock_file; + alias FUNLOCK = _unlock_file; + + alias setmode = _setmode; + alias fileno = _fileno; + + enum + { + _O_RDONLY = 0x0000, + _O_APPEND = 0x0004, + _O_TEXT = 0x4000, + _O_BINARY = 0x8000, + } +} +else version (GCC_IO) +{ + /* ** + * Gnu under-the-hood C I/O functions; see + * http://gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html + */ + extern (C) + { + nothrow: + @nogc: + int fputc_unlocked(int, _iobuf*); + int fputwc_unlocked(wchar_t, _iobuf*); + int fgetc_unlocked(_iobuf*); + int fgetwc_unlocked(_iobuf*); + void flockfile(FILE*); + void funlockfile(FILE*); + + private size_t fwrite_unlocked(const(void)* ptr, + size_t size, size_t n, _iobuf *stream); + } + + alias FPUTC = fputc_unlocked; + alias FPUTWC = fputwc_unlocked; + alias FGETC = fgetc_unlocked; + alias FGETWC = fgetwc_unlocked; + + alias FLOCK = flockfile; + alias FUNLOCK = funlockfile; +} +else version (GENERIC_IO) +{ + nothrow: + @nogc: + + extern (C) + { + void flockfile(FILE*); + void funlockfile(FILE*); + } + + int fputc_unlocked(int c, _iobuf* fp) { return fputc(c, cast(shared) fp); } + int fputwc_unlocked(wchar_t c, _iobuf* fp) + { + import core.stdc.wchar_ : fputwc; + return fputwc(c, cast(shared) fp); + } + int fgetc_unlocked(_iobuf* fp) { return fgetc(cast(shared) fp); } + int fgetwc_unlocked(_iobuf* fp) + { + import core.stdc.wchar_ : fgetwc; + return fgetwc(cast(shared) fp); + } + + alias FPUTC = fputc_unlocked; + alias FPUTWC = fputwc_unlocked; + alias FGETC = fgetc_unlocked; + alias FGETWC = fgetwc_unlocked; + + alias FLOCK = flockfile; + alias FUNLOCK = funlockfile; +} +else +{ + static assert(0, "unsupported C I/O system"); +} + +version (HAS_GETDELIM) extern(C) nothrow @nogc +{ + ptrdiff_t getdelim(char**, size_t*, int, FILE*); + // getline() always comes together with getdelim() + ptrdiff_t getline(char**, size_t*, FILE*); +} + +//------------------------------------------------------------------------------ +struct ByRecord(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 : 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...) +{ + ByRecord!(Fields) byRecord(File f, string format) + { + return typeof(return)(f, format); + } +} + +/** +Encapsulates a $(D FILE*). Generally D does not attempt to provide +thin wrappers over equivalent functions in the C standard library, but +manipulating $(D FILE*) values directly is unsafe and error-prone in +many ways. The $(D File) type ensures safe manipulation, automatic +file closing, and a lot of convenience. + +The underlying $(D FILE*) handle is maintained in a reference-counted +manner, such that as soon as the last $(D File) variable bound to a +given $(D FILE*) goes out of scope, the underlying $(D FILE*) is +automatically closed. + +Example: +---- +// test.d +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 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 + 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 + { + import core.stdc.stdlib : malloc; + import std.exception : enforce; + + assert(!_p); + _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); + _p.handle = handle; + _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 $(D File) object to another results in the two $(D File) +objects referring to the same underlying file. + +The destructor automatically closes the file as soon as no $(D 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 + $(HTTP cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) + function) + +Throws: $(D ErrnoException) if the file could not be opened. + */ + this(string name, in 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 (issue 14422) + version (MICROSOFT_STDIO) + { + bool append, update; + foreach (c; stdioOpenmode) + if (c == 'a') + append = true; + else + if (c == '+') + update = true; + if (append && !update) + seek(size); + } + } + + /// ditto + this(R1, R2)(R1 name) + if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1)) + { + import std.conv : to; + this(name.to!string, "rb"); + } + + /// ditto + this(R1, R2)(R1 name, R2 mode) + if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) && + isInputRange!R2 && isSomeChar!(ElementEncodingType!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 nothrow + { + if (!_p) return; + assert(_p.refs); + ++_p.refs; + } + +/** +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. + */ + void opAssign(File rhs) @safe + { + import std.algorithm.mutation : swap; + + swap(this, rhs); + } + +/** +First calls $(D detach) (throwing on failure), and then attempts to +_open file $(D name) with mode $(D stdioOpenmode). The mode has the +same semantics as in the C standard library $(HTTP +cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) function. + +Throws: $(D ErrnoException) in case of error. + */ + void open(string name, in char[] stdioOpenmode = "rb") @safe + { + detach(); + this = File(name, stdioOpenmode); + } + +/** +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 $(HTTP cplusplus.com/reference/cstdio/freopen/, freopen) +function. + +Note: Calling `reopen` with a `null` `name` is not implemented +in all C runtimes. + +Throws: $(D ErrnoException) in case of error. + */ + void reopen(string name, in 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; + } + + @system 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_DigitalMars) {} else // Not implemented + version (CRuntime_Microsoft) {} else // Not implemented + @system 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"); + } + +/** +First calls $(D detach) (throwing on failure), and then runs a command +by calling the C standard library function $(HTTP +opengroup.org/onlinepubs/007908799/xsh/_popen.html, _popen). + +Throws: $(D ErrnoException) in case of error. + */ + version (Posix) void popen(string command, in char[] stdioOpenmode = "r") @safe + { + import std.exception : errnoEnforce; + + detach(); + this = File(errnoEnforce(.popen(command, stdioOpenmode), + "Cannot run command `"~command~"'"), + command, 1, true); + } + +/** +First calls $(D detach) (throwing on failure), and then attempts to +associate the given file descriptor with the $(D File). The mode must +be compatible with the mode of the file descriptor. + +Throws: $(D ErrnoException) in case of error. + */ + void fdopen(int fd, in char[] stdioOpenmode = "rb") @safe + { + fdopen(fd, stdioOpenmode, null); + } + + package void fdopen(int fd, in char[] stdioOpenmode, string name) @trusted + { + import std.exception : errnoEnforce; + import std.internal.cstring : tempCString; + + auto modez = stdioOpenmode.tempCString(); + detach(); + + version (DIGITAL_MARS_STDIO) + { + // This is a re-implementation of DMC's fdopen, but without the + // mucking with the file descriptor. POSIX standard requires the + // new fdopen'd file to retain the given file descriptor's + // position. + import core.stdc.stdio : fopen; + auto fp = fopen("NUL", modez); + errnoEnforce(fp, "Cannot open placeholder NUL stream"); + FLOCK(fp); + auto iob = cast(_iobuf*) fp; + .close(iob._file); + iob._file = fd; + iob._flag &= ~_IOTRAN; + FUNLOCK(fp); + } + else + { + version (Windows) // MSVCRT + auto fp = _fdopen(fd, modez); + else version (Posix) + { + import core.sys.posix.stdio : fdopen; + auto fp = fdopen(fd, modez); + } + errnoEnforce(fp); + } + 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 $(D detach) (throwing on failure), and then attempts to +associate the given Windows $(D HANDLE) with the $(D File). The mode must +be compatible with the access attributes of the handle. Windows only. + +Throws: $(D ErrnoException) in case of error. +*/ + version (StdDdoc) + void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode); + + version (Windows) + void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode) + { + import core.stdc.stdint : intptr_t; + import std.exception : errnoEnforce; + import std.format : format; + + // Create file descriptors from the handles + version (DIGITAL_MARS_STDIO) + auto fd = _handleToFD(handle, FHND_DEVICE); + else // MSVCRT + { + 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 $(D true) if the file is opened. */ + @property bool isOpen() const @safe pure nothrow + { + return _p !is null && _p.handle; + } + +/** +Returns $(D true) if the file is at end (see $(HTTP +cplusplus.com/reference/clibrary/cstdio/feof.html, feof)). + +Throws: $(D 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 of the last opened file, if any. +If a $(D File) was created with $(LREF tmpfile) and $(LREF wrapFile) +it has no name.*/ + @property string name() const @safe pure nothrow + { + return _name; + } + +/** +If the file is not opened, returns $(D true). Otherwise, returns +$(HTTP cplusplus.com/reference/clibrary/cstdio/ferror.html, ferror) for +the file handle. + */ + @property bool error() const @trusted pure nothrow + { + return !isOpen || .ferror(cast(FILE*) _p.handle); + } + + @safe unittest + { + // Issue 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 $(D close). + +Throws: $(D ErrnoException) on failure if closing the file. + */ + void detach() @safe + { + if (!_p) return; + if (_p.refs == 1) + close(); + else + { + assert(_p.refs); + --_p.refs; + _p = null; + } + } + + @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 unopened, succeeds vacuously. Otherwise closes the +file (by calling $(HTTP +cplusplus.com/reference/clibrary/cstdio/fclose.html, fclose)), +throwing on error. Even if an exception is thrown, afterwards the $(D +File) object is empty. This is different from $(D detach) in that it +always closes the file; consequently, all other $(D File) objects +referring to the same handle will see a closed file henceforth. + +Throws: $(D ErrnoException) on error. + */ + void close() @trusted + { + import core.stdc.stdlib : free; + import std.exception : errnoEnforce; + + if (!_p) return; // succeed vacuously + scope(exit) + { + assert(_p.refs); + if (!--_p.refs) + 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 + 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~"'"); + errnoEnforce(res == 0, format("Command returned %d", res)); + return; + } + } + errnoEnforce(.fclose(_p.handle) == 0, + "Could not close file `"~_name~"'"); + } + +/** +If the file is not opened, succeeds vacuously. Otherwise, returns +$(HTTP cplusplus.com/reference/clibrary/cstdio/_clearerr.html, +_clearerr) for the file handle. + */ + void clearerr() @safe pure nothrow + { + _p is null || _p.handle is null || + .clearerr(_p.handle); + } + +/** +Flushes the C $(D FILE) buffers. + +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_fflush.html, _fflush) +for the file handle. + +Throws: $(D Exception) if the file is not opened or if the call to $(D 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 + { + // Issue 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 $(D FILE) buffers first. + +This function calls +$(HTTP msdn.microsoft.com/en-us/library/windows/desktop/aa364439%28v=vs.85%29.aspx, +$(D FlushFileBuffers)) on Windows and +$(HTTP pubs.opengroup.org/onlinepubs/7908799/xsh/fsync.html, +$(D fsync)) on POSIX for the file handle. + +Throws: $(D 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.windows : FlushFileBuffers; + wenforce(FlushFileBuffers(windowsHandle), "FlushFileBuffers failed"); + } + else + { + import core.sys.posix.unistd : fsync; + import std.exception : errnoEnforce; + errnoEnforce(fsync(fileno) == 0, "fsync failed"); + } + } + +/** +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fread.html, 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 $(D buffer) containing the data that was actually read. +This will be shorter than $(D buffer) if EOF was reached before the buffer +could be filled. + +Throws: $(D Exception) if $(D buffer) is empty. + $(D ErrnoException) if the file is not opened or the call to $(D fread) fails. + +$(D rawRead) always reads in binary mode on Windows. + */ + T[] rawRead(T)(T[] buffer) + { + import std.exception : errnoEnforce; + + if (!buffer.length) + throw new Exception("rawRead must take a non-empty buffer"); + version (Windows) + { + immutable fd = ._fileno(_p.handle); + immutable mode = ._setmode(fd, _O_BINARY); + scope(exit) ._setmode(fd, mode); + version (DIGITAL_MARS_STDIO) + { + import core.atomic : atomicOp; + + // @@@BUG@@@ 4243 + immutable info = __fhnd_info[fd]; + atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); + scope(exit) __fhnd_info[fd] = info; + } + } + 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 = testFilename(); + 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"); + } + +/** +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fwrite.html, 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. + +$(D rawWrite) always writes in binary mode on Windows. + +Throws: $(D ErrnoException) if the file is not opened or if the call to $(D fwrite) fails. + */ + void rawWrite(T)(in T[] buffer) + { + import std.conv : text; + import std.exception : errnoEnforce; + + version (Windows) + { + flush(); // before changing translation mode + immutable fd = ._fileno(_p.handle); + immutable mode = ._setmode(fd, _O_BINARY); + scope(exit) ._setmode(fd, mode); + version (DIGITAL_MARS_STDIO) + { + import core.atomic : atomicOp; + + // @@@BUG@@@ 4243 + immutable info = __fhnd_info[fd]; + atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); + scope(exit) __fhnd_info[fd] = info; + } + scope(exit) flush(); // before restoring translation mode + } + 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 = testFilename(); + 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 $(HTTP cplusplus.com/reference/clibrary/cstdio/fseek.html, fseek) +for the file handle. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) if the call to $(D fseek) fails. + */ + void seek(long offset, int origin = SEEK_SET) @trusted + { + import std.conv : to, text; + import std.exception : enforce, errnoEnforce; + + 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; + + 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_DigitalMars) + auto bigOffset = int.max - 100; + else + 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"); + } + +/** +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/ftell.html, ftell) for the +managed file handle. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) if the call to $(D 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 = testFilename(); + 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 $(HTTP cplusplus.com/reference/clibrary/cstdio/_rewind.html, _rewind) +for the file handle. + +Throws: $(D 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 $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, _setvbuf) for +the file handle. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) if the call to $(D 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 $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, +_setvbuf) for the file handle. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) if the call to $(D 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.windows : ULARGE_INTEGER, OVERLAPPED, BOOL; + + 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); + } + + private static T wenforce(T)(T cond, string str) + { + import core.sys.windows.windows : GetLastError; + import std.windows.syserror : sysErrorString; + + if (cond) return cond; + throw new Exception(str ~ ": " ~ sysErrorString(GetLastError())); + } + } + 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 $(D start) and $(D length) are zero, the entire file is locked. + +Locks created using $(D lock) and $(D 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 $(D 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.windows : 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 $(D start) and $(D length) are zero, the entire file is locked. +Returns: $(D true) if the lock was successful, and $(D 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.windows : GetLastError, LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, + ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, LOCKFILE_FAIL_IMMEDIATELY; + 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.windows : 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 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.stdc.stdlib : exit; + import core.sys.posix.sys.wait : wait; + import core.sys.posix.unistd : fork; + int child, status; + if ((child = fork()) == 0) + { + code(); + exit(0); + } + else + { + assert(wait(&status) != -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(); + } + + +/** +Writes its arguments in text format to the file. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) on an error writing to the file. +*/ + void write(S...)(S args) + { + import std.traits : isBoolean, isIntegral, isAggregateType; + auto w = lockingTextWriter(); + foreach (arg; args) + { + alias A = typeof(arg); + static if (isAggregateType!A || is(A == enum)) + { + import std.format : 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 : formattedWrite; + + // Most general case + formattedWrite(w, "%s", arg); + } + } + } + +/** +Writes its arguments in text format to the file, followed by a newline. + +Throws: $(D Exception) if the file is not opened. + $(D 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 $(LINK2 std_format.html#format-string, format string). +When passed as a compile-time argument, the string will be statically checked +against the argument types passed. +args = Items to write. + +Throws: $(D Exception) if the file is not opened. + $(D 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.msg); + return this.writef(fmt, args); + } + + /// ditto + void writef(Char, A...)(in Char[] fmt, A args) + { + import std.format : 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.msg); + return this.writefln(fmt, args); + } + + /// ditto + void writefln(Char, A...)(in Char[] fmt, A args) + { + import std.format : 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 $(D 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 $(D string). + terminator = Line terminator (by default, $(D '\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: + $(D StdioException) on I/O error, or $(D 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') + if (isSomeString!S) + { + Unqual!(ElementEncodingType!S)[] buf; + readln(buf, terminator); + return cast(S) buf; + } + + @system 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); + 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); + } + } + + @system 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]; + 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 $(D 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 +resized as necessary. +terminator = Line terminator (by default, $(D '\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 + +Throws: $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode +conversion error. + +Example: +--- +// Read lines from `stdin` into a string +// Ignore lines starting with '#' +// Write the string to `stdout` + +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 $(D stdin.readln(buf)) reuses (if possible) memory allocated +for $(D buf), whereas $(D line = stdin.readln()) makes a new memory allocation +for every line. + +For even better performance you can help $(D readln) by passing in a +large buffer to avoid memory reallocations. This can be done by reusing the +largest buffer returned by $(D readln): + +Example: +--- +// Read lines from `stdin` and count words + +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') + 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 + { + // TODO: optimize this + string s = readln(terminator); + buf.length = 0; + if (!s.length) return 0; + foreach (C c; s) + { + buf ~= c; + } + return buf.length; + } + } + + @system unittest + { + // @system due to readln + 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'); + } + + @system unittest // bugzilla 15293 + { + // @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) + 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; + } + + @system 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 $(LINK2 std_format.html#_format-string, _format string). + * When passed as a compile-time argument, the string will be statically checked + * against the argument types passed. + * data = Items to be read. + * 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.msg); + return this.readf(format, data); + } + + /// ditto + uint readf(Data...)(in char[] format, auto ref Data data) + { + import std.format : formattedRead; + + assert(isOpen); + auto input = LockingTextReader(this); + return formattedRead(input, 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.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~"]"); + + // Issue 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"); + + // Issue 11698 + bool b1, b2; + f.readf("%s\n%s\n", &b1, b2); + assert(b1 == true && b2 == false); + } + + // Issue 12260 - Nice error of std.stdio.readf with newlines + @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"); + } + +/** + Returns a temporary file by calling + $(HTTP cplusplus.com/reference/clibrary/cstdio/_tmpfile.html, _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 $(D 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 $(D 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 int 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 $(D HANDLE) (Windows only). +*/ + version (StdDdoc) + @property HANDLE windowsHandle(); + + version (Windows) + @property HANDLE windowsHandle() + { + version (DIGITAL_MARS_STDIO) + return _fdToHandle(fileno); + else + 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. +*/ + struct ByLine(Char, Terminator) + { + private: + import std.typecons : RefCounted, RefCountedAutoInitialize; + + /* 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 = RefCounted!(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); + } + + @property bool empty() + { + return impl.refCountedPayload.empty; + } + + @property Char[] front() + { + return impl.refCountedPayload.front; + } + + void popFront() + { + impl.refCountedPayload.popFront(); + } + + private: + struct Impl + { + private: + File file; + Char[] line; + Char[] buffer; + Terminator terminator; + KeepTerminator keepTerminator; + + public: + this(File f, KeepTerminator kt, Terminator terminator) + { + file = f; + this.terminator = terminator; + keepTerminator = kt; + popFront(); + } + + // Range primitive implementations. + @property bool empty() + { + return line is null; + } + + @property Char[] front() + { + return line; + } + + void popFront() + { + 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(Unqual!(ElementEncodingType!Terminator) == Char)); + const tlen = terminator.length; + } + else + static assert(false); + line = line[0 .. line.length - tlen]; + } + } + } + } + +/** +Returns an input range set up to read from the file handle one line +at a time. + +The element type for the range will be $(D Char[]). Range primitives +may throw $(D StdioException) on I/O error. + +Note: +Each $(D front) will not persist after $(D +popFront) is called, so the caller must copy its contents (e.g. by +calling $(D 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 $(D char). +keepTerminator = Use $(D Yes.keepTerminator) to include the +terminator at the end of each line. +terminator = Line separator ($(D '\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 +$(D front) after the corresponding $(D popFront) call is made (because +the contents may well have changed). +*/ + auto byLine(Terminator = char, Char = char) + (KeepTerminator keepTerminator = No.keepTerminator, + Terminator terminator = '\n') + if (isScalarType!Terminator) + { + return ByLine!(Char, Terminator)(this, keepTerminator, terminator); + } + +/// ditto + auto byLine(Terminator, Char = char) + (KeepTerminator keepTerminator, Terminator terminator) + if (is(Unqual!(ElementEncodingType!Terminator) == Char)) + { + return ByLine!(Char, Terminator)(this, keepTerminator, terminator); + } + + @system unittest + { + static import std.file; + auto deleteme = testFilename(); + std.file.write(deleteme, "hi"); + scope(success) std.file.remove(deleteme); + + import std.meta : AliasSeq; + 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); + } + } + + private struct ByLineCopy(Char, Terminator) + { + private: + import std.typecons : RefCounted, RefCountedAutoInitialize; + + /* 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 = RefCounted!(ByLineCopyImpl!(Char, Terminator), + RefCountedAutoInitialize.no); + Impl impl; + + public: + this(File f, KeepTerminator kt, Terminator terminator) + { + impl = Impl(f, kt, terminator); + } + + @property bool empty() + { + return impl.refCountedPayload.empty; + } + + @property Char[] front() + { + return impl.refCountedPayload.front; + } + + void popFront() + { + impl.refCountedPayload.popFront(); + } + } + + private struct ByLineCopyImpl(Char, Terminator) + { + ByLine!(Unqual!Char, Terminator).Impl impl; + bool gotFront; + Char[] line; + + public: + this(File f, KeepTerminator kt, Terminator terminator) + { + impl = ByLine!(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 input range set up to read from the file handle one line +at a time. Each line will be newly allocated. $(D front) will cache +its value to allow repeated calls without unnecessary allocations. + +Note: Due to caching byLineCopy can be more memory-efficient than +$(D File.byLine.map!idup). + +The element type for the range will be $(D Char[]). Range +primitives may throw $(D StdioException) on I/O error. + +Params: +Char = Character type for each line, defaulting to $(D immutable char). +keepTerminator = Use $(D Yes.keepTerminator) to include the +terminator at the end of each line. +terminator = Line separator ($(D '\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(Unqual!(ElementEncodingType!Terminator) == Unqual!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[])); + } + + @system unittest + { + import std.algorithm.comparison : equal; + static import std.file; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + 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)); + + // Issue 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'); + } + + @system 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"); + + // bug 9599 + file.rewind(); + File.ByLine!(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); + } + + @system 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 input range set up to parse one line at a time from the file + into a tuple. + + Range primitives may throw $(D 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...) + { + ByRecord!(Fields) byRecord(string format) + { + return typeof(return)(this, format); + } + } + + /// + @system unittest + { + static import std.file; + import std.typecons : tuple; + + // prepare test file + auto testFile = testFilename(); + 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. + */ + struct ByChunk + { + 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(); + } + + // $(D 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 input range set up to read from the file handle a chunk at a +time. + +The element type for the range will be $(D ubyte[]). Range primitives +may throw $(D 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, $(D 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 +$(D front) will not persist after $(D popFront) is called, so if retention is +needed, the caller must copy its contents (e.g. by calling $(D buffer.dup)). + +In the example above, $(D buffer.length) is 4096 for all iterations, except +for the last one, in which case $(D buffer.length) may be less than 4096 (but +always greater than zero). + +With the mentioned limitations, $(D 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 $(D byChunk) returns a range initialized with the $(D File) +object and the appropriate buffer. + +Throws: If the user-provided size is zero or the user-provided buffer +is empty, throws an $(D Exception). In case of an I/O error throws +$(D StdioException). + */ + auto byChunk(size_t chunkSize) + { + return ByChunk(this, chunkSize); + } +/// Ditto + ByChunk byChunk(ubyte[] buffer) + { + return ByChunk(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 +/* +$(D Range) that locks the file and allows fast writing to it. + */ + struct LockingTextWriter + { + private: + import std.range.primitives : ElementType, isInfinite, isInputRange; + // the shared file handle + FILE* fps_; + + // the unshared version of fps + @property _iobuf* handle_() @trusted { return cast(_iobuf*) fps_; } + + // the file's orientation (byte- or wide-oriented) + int orientation_; + public: + + this(ref File f) @trusted + { + import core.stdc.wchar_ : fwide; + import std.exception : enforce; + + enforce(f._p && f._p.handle, "Attempting to write to closed File"); + fps_ = f._p.handle; + orientation_ = fwide(fps_, 0); + FLOCK(fps_); + } + + ~this() @trusted + { + if (fps_) + { + FUNLOCK(fps_); + fps_ = null; + } + } + + this(this) @trusted + { + if (fps_) + { + FLOCK(fps_); + } + } + + /// Range primitive implementations. + void put(A)(A writeme) + if ((isSomeChar!(Unqual!(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(fps_, writeme); + if (result != writeme.length) errnoEnforce(0); + return; + } + } + + // put each element in turn. + alias Elem = Unqual!(ElementType!A); + foreach (Elem c; writeme) + { + put(c); + } + } + + /// ditto + void put(C)(C c) @safe if (isSomeChar!C || is(C : const(ubyte))) + { + import std.traits : Parameters; + static auto trustedFPUTC(int ch, _iobuf* h) @trusted + { + return FPUTC(ch, h); + } + static auto trustedFPUTWC(Parameters!FPUTWC[0] ch, _iobuf* h) @trusted + { + return FPUTWC(ch, h); + } + + static if (c.sizeof == 1) + { + // simple char + if (orientation_ <= 0) trustedFPUTC(c, handle_); + else trustedFPUTWC(c, handle_); + } + else static if (c.sizeof == 2) + { + import std.utf : encode, UseReplacementDchar; + + if (orientation_ <= 0) + { + if (c <= 0x7F) + { + trustedFPUTC(c, handle_); + } + else + { + char[4] buf; + immutable size = encode!(UseReplacementDchar.yes)(buf, c); + foreach (i ; 0 .. size) + trustedFPUTC(buf[i], handle_); + } + } + else + { + trustedFPUTWC(c, handle_); + } + } + else // 32-bit characters + { + import std.utf : encode; + + 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(c, handle_); + } + else + { + trustedFPUTWC(cast(wchar) + ((((c - 0x10000) >> 10) & 0x3FF) + + 0xD800), handle_); + trustedFPUTWC(cast(wchar) + (((c - 0x10000) & 0x3FF) + 0xDC00), + handle_); + } + } + else version (Posix) + { + trustedFPUTWC(c, handle_); + } + else + { + static assert(0); + } + } + } + } + } + +/** Returns an output range that locks the file and allows fast writing to it. + +See $(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: + FILE* fps; + string name; + + version (Windows) + { + int fd, oldMode; + version (DIGITAL_MARS_STDIO) + ubyte oldInfo; + } + + package: + this(ref File f) + { + import std.exception : enforce; + + enforce(f._p && f._p.handle); + name = f._name; + 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); + version (DIGITAL_MARS_STDIO) + { + import core.atomic : atomicOp; + + // @@@BUG@@@ 4243 + oldInfo = __fhnd_info[fd]; + atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); + } + } + } + + public: + ~this() + { + if (!fps) + return; + + version (Windows) + { + .fflush(fps); // before restoring translation mode + version (DIGITAL_MARS_STDIO) + { + // @@@BUG@@@ 4243 + __fhnd_info[fd] = oldInfo; + } + ._setmode(fd, oldMode); + } + + FUNLOCK(fps); + fps = null; + } + + void rawWrite(T)(in T[] buffer) + { + import std.conv : text; + import std.exception : errnoEnforce; + + auto result = trustedFwrite(fps, 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 (fps) + { + FLOCK(fps); + } + } + } + + void put(T)(auto ref in T value) + if (!hasIndirections!T && + !isInputRange!T) + { + rawWrite((&value)[0 .. 1]); + } + + void put(T)(in 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.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*1i + a[n-1]^^2)(0+0i) + .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 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"); + } + +/// Get the size of the file, ulong.max if file is not searchable, but still throws if an actual error occurs. + @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); +} + +@system unittest +{ + // @system due to readln + static import std.file; + import std.range : chain, only, repeat; + import std.range.primitives : isOutputRange; + + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + + { + File f = File(deleteme, "w"); + auto writer = f.lockingTextWriter(); + static assert(isOutputRange!(typeof(writer), dchar)); + writer.put("日本語"); + writer.put("日本語"w); + writer.put("日本語"d); + writer.put('日'); + writer.put(chain(only('本'), only('語'))); + writer.put(repeat('#', 12)); // BUG 11945 + writer.put(cast(immutable(ubyte)[])"日本語"); // Bug 17229 + } + assert(File(deleteme).readln() == "日本語日本語日本語日本語############日本語"); +} + +@safe unittest +{ + import std.exception : collectException; + auto e = collectException({ File f; f.writeln("Hello!"); }()); + assert(e && e.msg == "Attempting to write to closed File"); +} + +/// Used to specify the lock type for $(D File.lock) and $(D 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, y; + 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); +} + +@system unittest // bugzilla 13686 +{ + 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)); +} + +@system unittest // bugzilla 12320 +{ + 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); +} + +@system unittest // bugzilla 14861 +{ + // @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 $(D 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; +} + +/*********************************** +For each argument $(D arg) in $(D args), format the argument (using +$(REF to, std,conv)) and write the resulting +string to $(D args[0]). A call without any arguments will fail to +compile. + +Params: + args = the items to write to `stdout` + +Throws: In case of an I/O error, throws an $(D 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 $(D stdin) and writes it to $(D stdout) with a 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) +{ + import std.traits : isAggregateType; + 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(typeof(args[0]) : const(char)[]) && + !is(typeof(args[0]) == enum) && + !is(Unqual!(typeof(args[0])) == typeof(null)) && + !isAggregateType!(typeof(args[0]))) + { + import std.traits : isStaticArray; + + // Specialization for strings - a very frequent case + auto w = .trustedStdout.lockingTextWriter(); + + static if (isStaticArray!(typeof(args[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"); + + // bug 8040 + if (false) writeln(null); + if (false) writeln(">", null, "<"); + + // Bugzilla 14041 + if (false) + { + char[8] a; + writeln(a); + } +} + +@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); // bug 8386 + writeln("Hello!"d); // bug 8386 + writeln("embedded\0null"c); // bug 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, B } + enum EC : char { A, B } + 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()); +} + + +/*********************************** +Writes formatted data to standard output (without a trailing newline). + +Params: +fmt = The $(LINK2 std_format.html#format-string, format string). +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 $(D 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.msg); + 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.msg); + 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 $(D stdin) using $(REF formattedRead, std,_format). + * Params: + * format = The $(LINK2 std_format.html#_format-string, _format string). + * When passed as a compile-time argument, the string will be statically checked + * against the argument types passed. + * args = Items to be read. + * 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.msg); + return .readf(format, args); +} + +/// ditto +uint readf(A...)(in char[] format, auto ref A args) +{ + return stdin.readf(format, args); +} + +@system unittest +{ + float f; + if (false) uint x = 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 $(D 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 $(D 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 $(D string). + * terminator = Line terminator (by default, $(D '\n')). + * Note: + * String terminators are not supported due to ambiguity with readln(buf) below. + * Throws: + * $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error. + * Example: + * Reads $(D stdin) and writes it to $(D 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 $(D 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: + * $(D 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, $(D '\n')). Use $(REF newline, std,ascii) + * for portability (unless the file was opened in text mode). + * Throws: + * $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error. + * Example: + * Reads $(D stdin) and writes it to $(D 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'); + foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) + { + readln!String(); + readln!String('\t'); + } + foreach (String; AliasSeq!(char[], wchar[], dchar[])) + { + String buf; + readln(buf); + readln(buf, '\t'); + readln(buf, "<br />"); + } + } +} + +/* + * Convenience function that forwards to $(D core.sys.posix.stdio.fopen) + * (to $(D _wfopen) on Windows) + * with appropriately-constructed C-style strings. + */ +private FILE* fopen(R1, R2)(R1 name, R2 mode = "r") +if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) && + (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2)) +{ + import std.internal.cstring : tempCString; + + auto namez = name.tempCString!FSChar(); + auto modez = mode.tempCString!FSChar(); + + static fopenImpl(const(FSChar)* namez, 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. http://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, http://www.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 $(D core.sys.posix.stdio.popen) + * with appropriately-constructed C-style strings. + */ + FILE* popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc + if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) && + (isInputRange!R2 && isSomeChar!(ElementEncodingType!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 $(D 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 $(D core.stdc.stdio.fread) + */ +private auto trustedFread(T)(FILE* f, T[] obj) @trusted +{ + return fread(obj.ptr, T.sizeof, obj.length, f); +} + +/** + * Iterates through the lines of a file by using $(D foreach). + * + * Example: + * +--------- +void main() +{ + foreach (string line; lines(stdin)) + { + ... use line ... + } +} +--------- +The line terminator ($(D '\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 $(D line), and the behavior of $(D lines) +changes accordingly: + +$(OL $(LI If $(D line) has type $(D string), $(D +wstring), or $(D dstring), a new string of the respective type +is allocated every read.) $(LI If $(D line) has type $(D +char[]), $(D wchar[]), $(D dchar[]), the line's content +will be reused (overwritten) across reads.) $(LI If $(D line) +has type $(D immutable(ubyte)[]), the behavior is similar to +case (1), except that no UTF checking is attempted upon input.) $(LI +If $(D line) has type $(D 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. $(D 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 $(D 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 ($(D '\n') by default). + */ + this(File f, dchar terminator = '\n') + { + 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])) + { + enum bool duplicate = is(Parms[$ - 1] == string) + || is(Parms[$ - 1] == wstring) || is(Parms[$ - 1] == dstring); + 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; + } +} + +@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(); + + } + + 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 $(D foreach). + +Example: + +--------- +void main() +{ + foreach (ubyte[] buffer; chunks(stdin, 4096)) + { + ... use buffer ... + } +} +--------- + +The content of $(D buffer) is reused across calls. In the + example above, $(D buffer.length) is 4096 for all iterations, + except for the last one, in which case $(D buffer.length) may + be less than 4096 (but always greater than zero). + + In case of an I/O error, an $(D 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"); + } + body + { + this.f = f; + this.size = size; + } + + int opApply(D)(scope D dg) + { + import core.stdc.stdlib : alloca; + 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(); +} + + +/** +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 $(D 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 $(D StdioException). */ + static void opCall(string msg) + { + throw new StdioException(msg); + } + +/// ditto + static void opCall() + { + throw new StdioException(null, core.stdc.errno.errno); + } +} + +// Undocumented but public because the std* handles are aliasing it. +@property ref File makeGlobal(alias handle)() +{ + __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) + { + impl.handle = handle; + result._p = &impl; + atomicOp!"+="(spinlock, uint.max / 2); + break; + } + atomicOp!"-="(spinlock, 1); + } + } + return result; +} + +/** The standard input stream. + Bugs: + Due to $(LINK2 https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768), + it is thread un-safe to reassign `stdin` to a different `File` instance + than the default. +*/ +alias stdin = makeGlobal!(core.stdc.stdio.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. + Bugs: + Due to $(LINK2 https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768), + it is thread un-safe to reassign `stdout` to a different `File` instance + than the default. +*/ +alias stdout = makeGlobal!(core.stdc.stdio.stdout); + +/** + The standard error stream. + Bugs: + Due to $(LINK2 https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768), + it is thread un-safe to reassign `stderr` to a different `File` instance + than the default. +*/ +alias stderr = makeGlobal!(core.stdc.stdio.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) + { + 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(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 implementation of readln +version (DIGITAL_MARS_STDIO) +private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation /*ignored*/) +{ + FLOCK(fps); + scope(exit) FUNLOCK(fps); + + /* Since fps is now locked, we can create an "unshared" version + * of fp. + */ + auto fp = cast(_iobuf*) fps; + + ReadlnAppender app; + app.initialize(buf); + + if (__fhnd_info[fp._file] & FHND_WCHAR) + { /* Stream is in wide characters. + * Read them and convert to chars. + */ + static assert(wchar_t.sizeof == 2); + for (int c = void; (c = FGETWC(fp)) != -1; ) + { + if ((c & ~0x7F) == 0) + { + app.putchar(cast(char) c); + if (c == terminator) + break; + } + else + { + if (c >= 0xD800 && c <= 0xDBFF) + { + int c2 = void; + if ((c2 = FGETWC(fp)) != -1 || + c2 < 0xDC00 && c2 > 0xDFFF) + { + StdioException("unpaired UTF-16 surrogate"); + } + c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); + } + app.putdchar(cast(dchar) c); + } + } + if (ferror(fps)) + StdioException(); + } + + else if (fp._flag & _IONBF) + { + /* Use this for unbuffered I/O, when running + * across buffer boundaries, or for any but the common + * cases. + */ + L1: + int c; + while ((c = FGETC(fp)) != -1) + { + app.putchar(cast(char) c); + if (c == terminator) + { + buf = app.data; + return buf.length; + } + + } + + if (ferror(fps)) + StdioException(); + } + else + { + int u = fp._cnt; + char* p = fp._ptr; + int i; + if (fp._flag & _IOTRAN) + { /* Translated mode ignores \r and treats ^Z as end-of-file + */ + char c; + while (1) + { + if (i == u) // if end of buffer + goto L1; // give up + c = p[i]; + i++; + if (c != '\r') + { + if (c == terminator) + break; + if (c != 0x1A) + continue; + goto L1; + } + else + { if (i != u && p[i] == terminator) + break; + goto L1; + } + } + app.putonly(p[0 .. i]); + app.buf[i - 1] = cast(char) terminator; + if (terminator == '\n' && c == '\r') + i++; + } + else + { + while (1) + { + if (i == u) // if end of buffer + goto L1; // give up + auto c = p[i]; + i++; + if (c == terminator) + break; + } + app.putonly(p[0 .. i]); + } + fp._cnt -= i; + fp._ptr += i; + } + + buf = app.data; + return buf.length; +} + +version (MICROSOFT_STDIO) +private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation /*ignored*/) +{ + FLOCK(fps); + scope(exit) FUNLOCK(fps); + + /* Since fps is now locked, we can create an "unshared" version + * of fp. + */ + auto fp = cast(_iobuf*) fps; + + ReadlnAppender app; + app.initialize(buf); + + int c; + while ((c = FGETC(fp)) != -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; +} + +version (HAS_GETDELIM) +private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation) +{ + import core.stdc.stdlib : free; + import core.stdc.wchar_ : fwide; + + if (orientation == File.Orientation.wide) + { + /* Stream is in wide characters. + * Read them and convert to chars. + */ + FLOCK(fps); + scope(exit) FUNLOCK(fps); + auto fp = cast(_iobuf*) fps; + version (Windows) + { + buf.length = 0; + for (int c = void; (c = FGETWC(fp)) != -1; ) + { + if ((c & ~0x7F) == 0) + { buf ~= c; + if (c == terminator) + break; + } + else + { + if (c >= 0xD800 && c <= 0xDBFF) + { + int c2 = void; + if ((c2 = FGETWC(fp)) != -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(fp)) + StdioException(); + return buf.length; + } + else version (Posix) + { + buf.length = 0; + for (int c; (c = FGETWC(fp)) != -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); + } + } + + 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; + } + } + + auto s = getdelim(&lineptr, &n, terminator, fps); + if (s < 0) + { + if (ferror(fps)) + StdioException(); + buf.length = 0; // end of file + return 0; + } + + if (s <= buf.length) + { + buf = buf[0 .. s]; + buf[] = lineptr[0 .. s]; + } + else + { + buf = lineptr[0 .. s].dup; + } + return s; +} + +version (NO_GETDELIM) +private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation) +{ + import core.stdc.wchar_ : fwide; + + FLOCK(fps); + scope(exit) FUNLOCK(fps); + auto fp = cast(_iobuf*) 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 = FGETWC(fp)) != -1; ) + { + if ((c & ~0x7F) == 0) + { buf ~= c; + if (c == terminator) + break; + } + else + { + if (c >= 0xD800 && c <= 0xDBFF) + { + int c2 = void; + if ((c2 = FGETWC(fp)) != -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(fp)) + StdioException(); + return buf.length; + } + else version (Posix) + { + import std.utf : encode; + buf.length = 0; + for (int c; (c = FGETWC(fp)) != -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 = FGETC(fp); + 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 = FGETC(fp)) != -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]; + char* lnptr = ln.ptr; + f.readln(ln); + + assert(ln == "abcd\n"); + char[] t = ln[0 .. 2]; + t ~= 't'; + assert(t == "abt"); + assert(ln == "abcd\n"); // bug 13856: ln stomped to "abtd" + + // it can also stomp the array length + ln = new char[4]; + lnptr = ln.ptr; + 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 (unittest) 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 test Issue 7648 + return text(deleteme, "-детка.", baseName(file), ".", line); +} |