| // 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; |
| } |
| else version (CRuntime_Glibc) |
| { |
| // Specific to the way Gnu C does stdio |
| version = GCC_IO; |
| } |
| else version (CRuntime_Bionic) |
| { |
| version = GENERIC_IO; |
| } |
| else version (CRuntime_Musl) |
| { |
| version = GENERIC_IO; |
| } |
| else version (CRuntime_UClibc) |
| { |
| // uClibc supports GCC IO |
| version = GCC_IO; |
| } |
| else version (OSX) |
| { |
| version = GENERIC_IO; |
| } |
| else version (iOS) |
| { |
| version = GENERIC_IO; |
| } |
| else version (TVOS) |
| { |
| version = GENERIC_IO; |
| } |
| else version (WatchOS) |
| { |
| version = GENERIC_IO; |
| } |
| else version (FreeBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (NetBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (OpenBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (DragonFlyBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (Solaris) |
| { |
| version = GENERIC_IO; |
| } |
| |
| // Character type used for operating system filesystem APIs |
| version (Windows) |
| { |
| private alias FSChar = wchar; |
| } |
| else |
| { |
| private alias FSChar = char; |
| } |
| |
| |
| 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 (Posix) |
| { |
| static import core.sys.posix.stdio; // getdelim |
| } |
| |
| 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"); |
| } |
| |
| static if (__traits(compiles, core.sys.posix.stdio.getdelim)) |
| { |
| extern(C) nothrow @nogc |
| { |
| // @@@DEPRECATED_2.104@@@ |
| deprecated("To be removed after 2.104. Use core.sys.posix.stdio.getdelim instead.") |
| ptrdiff_t getdelim(char**, size_t*, int, FILE*); |
| |
| // @@@DEPRECATED_2.104@@@ |
| // getline() always comes together with getdelim() |
| deprecated("To be removed after 2.104. Use core.sys.posix.stdio.getline instead.") |
| 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 |