| // Written in the D programming language. |
| |
| /** |
| $(SCRIPT inhibitQuickIndex = 1;) |
| $(DIVC quickindex, |
| $(BOOKTABLE, |
| $(TR $(TH Category) $(TH Symbols)) |
| $(TR $(TD File handles) $(TD |
| $(MYREF __popen) |
| $(MYREF File) |
| $(MYREF isFileHandle) |
| $(MYREF openNetwork) |
| $(MYREF stderr) |
| $(MYREF stdin) |
| $(MYREF stdout) |
| )) |
| $(TR $(TD Reading) $(TD |
| $(MYREF chunks) |
| $(MYREF lines) |
| $(MYREF readf) |
| $(MYREF readln) |
| )) |
| $(TR $(TD Writing) $(TD |
| $(MYREF toFile) |
| $(MYREF write) |
| $(MYREF writef) |
| $(MYREF writefln) |
| $(MYREF writeln) |
| )) |
| $(TR $(TD Misc) $(TD |
| $(MYREF KeepTerminator) |
| $(MYREF LockType) |
| $(MYREF StdioException) |
| )) |
| )) |
| |
| Standard I/O functions that extend $(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 The D Language Foundation 2007-. |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| Authors: $(HTTP digitalmars.com, Walter Bright), |
| $(HTTP erdani.org, Andrei Alexandrescu), |
| Alex Rønne Petersen |
| */ |
| 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 : ElementEncodingType, empty, front, isBidirectionalRange, |
| isInputRange, isSomeFiniteCharInputRange, put; |
| import std.traits : isSomeChar, isSomeString, Unqual, isPointer; |
| import std.typecons : Flag, No, Yes; |
| |
| /++ |
| If flag `KeepTerminator` is set to `KeepTerminator.yes`, then the delimiter |
| is included in the strings returned. |
| +/ |
| alias KeepTerminator = Flag!"keepTerminator"; |
| |
| version (CRuntime_Microsoft) |
| { |
| 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; |
| version = Darwin; |
| } |
| else version (iOS) |
| { |
| version = GENERIC_IO; |
| version = Darwin; |
| } |
| else version (TVOS) |
| { |
| version = GENERIC_IO; |
| version = Darwin; |
| } |
| else version (WatchOS) |
| { |
| version = GENERIC_IO; |
| version = Darwin; |
| } |
| else version (FreeBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (NetBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (OpenBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (DragonFlyBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (Solaris) |
| { |
| version = GENERIC_IO; |
| } |
| |
| // 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.basetsd : HANDLE; |
| } |
| |
| version (Posix) |
| { |
| static import core.sys.posix.stdio; // getdelim, flockfile |
| } |
| |
| version (DIGITAL_MARS_STDIO) |
| { |
| private alias _FPUTC = _fputc_nlock; |
| private alias _FPUTWC = _fputwc_nlock; |
| private alias _FGETC = _fgetc_nlock; |
| private alias _FGETWC = _fgetwc_nlock; |
| private alias _FLOCK = __fp_lock; |
| private alias _FUNLOCK = __fp_unlock; |
| |
| // Alias for MICROSOFT_STDIO compatibility. |
| // @@@DEPRECATED_2.107@@@ |
| // Rename this back to _setmode once the deprecation phase has ended. |
| private alias __setmode = setmode; |
| |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FPUTC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FPUTC = _fputc_nlock; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FPUTWC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FPUTWC = _fputwc_nlock; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FGETC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FGETC = _fgetc_nlock; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FGETWC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FGETWC = _fgetwc_nlock; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FLOCK was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FLOCK = __fp_lock; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FUNLOCK was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FUNLOCK = __fp_unlock; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias _setmode was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias _setmode = setmode; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal function _fileno was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| int _fileno(FILE* f) { return f._file; } |
| } |
| else version (MICROSOFT_STDIO) |
| { |
| private alias _FPUTC = _fputc_nolock; |
| private alias _FPUTWC = _fputwc_nolock; |
| private alias _FGETC = _fgetc_nolock; |
| private alias _FGETWC = _fgetwc_nolock; |
| private alias _FLOCK = _lock_file; |
| private alias _FUNLOCK = _unlock_file; |
| |
| // @@@DEPRECATED_2.107@@@ |
| // Remove this once the deprecation phase for DIGITAL_MARS_STDIO has ended. |
| private alias __setmode = _setmode; |
| |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FPUTC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FPUTC = _fputc_nolock; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FPUTWC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FPUTWC = _fputwc_nolock; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FGETC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FGETC = _fgetc_nolock; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FGETWC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FGETWC = _fgetwc_nolock; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FLOCK was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FLOCK = _lock_file; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FUNLOCK was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FUNLOCK = _unlock_file; |
| } |
| else version (GCC_IO) |
| { |
| private alias _FPUTC = fputc_unlocked; |
| private alias _FPUTWC = fputwc_unlocked; |
| private alias _FGETC = fgetc_unlocked; |
| private alias _FGETWC = fgetwc_unlocked; |
| private alias _FLOCK = core.sys.posix.stdio.flockfile; |
| private alias _FUNLOCK = core.sys.posix.stdio.funlockfile; |
| |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FPUTC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FPUTC = fputc_unlocked; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FPUTWC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FPUTWC = fputwc_unlocked; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FGETC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FGETC = fgetc_unlocked; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FGETWC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FGETWC = fgetwc_unlocked; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FLOCK was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FLOCK = core.sys.posix.stdio.flockfile; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FUNLOCK was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FUNLOCK = core.sys.posix.stdio.funlockfile; |
| } |
| else version (GENERIC_IO) |
| { |
| nothrow: |
| @nogc: |
| |
| extern (C) private |
| { |
| static import core.stdc.wchar_; |
| |
| pragma(mangle, fputc.mangleof) int _FPUTC(int c, _iobuf* fp); |
| pragma(mangle, core.stdc.wchar_.fputwc.mangleof) int _FPUTWC(wchar_t c, _iobuf* fp); |
| pragma(mangle, fgetc.mangleof) int _FGETC(_iobuf* fp); |
| pragma(mangle, core.stdc.wchar_.fgetwc.mangleof) int _FGETWC(_iobuf* fp); |
| } |
| |
| version (Posix) |
| { |
| private alias _FLOCK = core.sys.posix.stdio.flockfile; |
| private alias _FUNLOCK = core.sys.posix.stdio.funlockfile; |
| } |
| else |
| { |
| static assert(0, "don't know how to lock files on GENERIC_IO"); |
| } |
| |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal function fputc_unlocked was unintentionally available " |
| ~ "from std.stdio and will be removed afer 2.107") |
| extern (C) pragma(mangle, fputc.mangleof) int fputc_unlocked(int c, _iobuf* fp); |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal function fputwc_unlocked was unintentionally available " |
| ~ "from std.stdio and will be removed afer 2.107") |
| extern (C) pragma(mangle, core.stdc.wchar_.fputwc.mangleof) int fputwc_unlocked(wchar_t c, _iobuf* fp); |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal function fgetc_unlocked was unintentionally available " |
| ~ "from std.stdio and will be removed afer 2.107") |
| extern (C) pragma(mangle, fgetc.mangleof) int fgetc_unlocked(_iobuf* fp); |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal function fgetwc_unlocked was unintentionally available " |
| ~ "from std.stdio and will be removed afer 2.107") |
| extern (C) pragma(mangle, core.stdc.wchar_.fgetwc.mangleof) int fgetwc_unlocked(_iobuf* fp); |
| |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FPUTC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FPUTC = fputc_unlocked; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FPUTWC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FPUTWC = fputwc_unlocked; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FGETC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FGETC = fgetc_unlocked; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FGETWC was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FGETWC = fgetwc_unlocked; |
| |
| version (Posix) |
| { |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FLOCK was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FLOCK = core.sys.posix.stdio.flockfile; |
| // @@@DEPRECATED_2.107@@@ |
| deprecated("internal alias FUNLOCK was unintentionally available from " |
| ~ "std.stdio and will be removed afer 2.107") |
| alias FUNLOCK = core.sys.posix.stdio.funlockfile; |
| } |
| } |
| else |
| { |
| static assert(0, "unsupported C I/O system"); |
| } |
| |
| private extern (C) @nogc nothrow |
| { |
| pragma(mangle, _FPUTC.mangleof) int trustedFPUTC(int ch, _iobuf* h) @trusted; |
| |
| version (DIGITAL_MARS_STDIO) |
| pragma(mangle, _FPUTWC.mangleof) int trustedFPUTWC(int ch, _iobuf* h) @trusted; |
| else |
| pragma(mangle, _FPUTWC.mangleof) int trustedFPUTWC(wchar_t ch, _iobuf* h) @trusted; |
| } |
| |
| 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*); |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| private struct ByRecordImpl(Fields...) |
| { |
| private: |
| import std.typecons : Tuple; |
| |
| File file; |
| char[] line; |
| Tuple!(Fields) current; |
| string format; |
| |
| public: |
| this(File f, string format) |
| { |
| assert(f.isOpen); |
| file = f; |
| this.format = format; |
| popFront(); // prime the range |
| } |
| |
| /// Range primitive implementations. |
| @property bool empty() |
| { |
| return !file.isOpen; |
| } |
| |
| /// Ditto |
| @property ref Tuple!(Fields) front() |
| { |
| return current; |
| } |
| |
| /// Ditto |
| void popFront() |
| { |
| import std.conv : text; |
| import std.exception : enforce; |
| import std.format.read : formattedRead; |
| import std.string : chomp; |
| |
| enforce(file.isOpen, "ByRecord: File must be open"); |
| file.readln(line); |
| if (!line.length) |
| { |
| file.detach(); |
| } |
| else |
| { |
| line = chomp(line); |
| formattedRead(line, format, ¤t); |
| enforce(line.empty, text("Leftover characters in record: `", |
| line, "'")); |
| } |
| } |
| } |
| |
| template byRecord(Fields...) |
| { |
| auto byRecord(File f, string format) |
| { |
| return typeof(return)(f, format); |
| } |
| } |
| |
| /** |
| Encapsulates a `FILE*`. Generally D does not attempt to provide |
| thin wrappers over equivalent functions in the C standard library, but |
| manipulating `FILE*` values directly is unsafe and error-prone in |
| many ways. The `File` type ensures safe manipulation, automatic |
| file closing, and a lot of convenience. |
| |
| The underlying `FILE*` handle is maintained in a reference-counted |
| manner, such that as soon as the last `File` variable bound to a |
| given `FILE*` goes out of scope, the underlying `FILE*` is |
| automatically closed. |
| |
| Example: |
| ---- |
| // test.d |
| import std.stdio; |
| |
| void main(string[] args) |
| { |
| auto f = File("test.txt", "w"); // open for writing |
| f.write("Hello"); |
| if (args.length > 1) |
| { |
| auto g = f; // now g and f write to the same file |
| // internal reference count is 2 |
| g.write(", ", args[1]); |
| // g exits scope, reference count decreases to 1 |
| } |
| f.writeln("!"); |
| // f exits scope, reference count falls to zero, |
| // underlying `FILE*` is closed. |
| } |
| ---- |
| $(CONSOLE |
| % rdmd test.d Jimmy |
| % cat test.txt |
| Hello, Jimmy! |
| % __ |
| ) |
| */ |
| struct File |
| { |
| import core.atomic : atomicOp, atomicStore, atomicLoad; |
| import std.range.primitives : ElementEncodingType; |
| import std.traits : isScalarType, isArray; |
| enum Orientation { unknown, narrow, wide } |
| |
| private struct Impl |
| { |
| FILE * handle = null; // Is null iff this Impl is closed by another File |
| shared uint refs = uint.max / 2; |
| bool isPopened; // true iff the stream has been created by popen() |
| Orientation orientation; |
| } |
| private Impl* _p; |
| private string _name; |
| |
| package this(FILE* handle, string name, uint refs = 1, bool isPopened = false) @trusted |
| { |
| import core.stdc.stdlib : malloc; |
| import std.exception : enforce; |
| |
| assert(!_p); |
| _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); |
| initImpl(handle, name, refs, isPopened); |
| } |
| |
| private void initImpl(FILE* handle, string name, uint refs = 1, bool isPopened = false) |
| { |
| assert(_p); |
| _p.handle = handle; |
| atomicStore(_p.refs, refs); |
| _p.isPopened = isPopened; |
| _p.orientation = Orientation.unknown; |
| _name = name; |
| } |
| |
| /** |
| Constructor taking the name of the file to open and the open mode. |
| |
| Copying one `File` object to another results in the two `File` |
| objects referring to the same underlying file. |
| |
| The destructor automatically closes the file as soon as no `File` |
| object refers to it anymore. |
| |
| Params: |
| name = range or string representing the file _name |
| stdioOpenmode = range or string represting the open mode |
| (with the same semantics as in the C standard library |
| $(HTTP cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) |
| function) |
| |
| Throws: `ErrnoException` if the file could not be opened. |
| */ |
| this(string name, scope const(char)[] stdioOpenmode = "rb") @safe |
| { |
| import std.conv : text; |
| import std.exception : errnoEnforce; |
| |
| this(errnoEnforce(_fopen(name, stdioOpenmode), |
| text("Cannot open file `", name, "' in mode `", |
| stdioOpenmode, "'")), |
| name); |
| |
| // MSVCRT workaround (issue 14422) |
| version (MICROSOFT_STDIO) |
| { |
| setAppendWin(stdioOpenmode); |
| } |
| } |
| |
| /// ditto |
| this(R1, R2)(R1 name) |
| if (isSomeFiniteCharInputRange!R1) |
| { |
| import std.conv : to; |
| this(name.to!string, "rb"); |
| } |
| |
| /// ditto |
| this(R1, R2)(R1 name, R2 mode) |
| if (isSomeFiniteCharInputRange!R1 && |
| isSomeFiniteCharInputRange!R2) |
| { |
| import std.conv : to; |
| this(name.to!string, mode.to!string); |
| } |
| |
| @safe unittest |
| { |
| static import std.file; |
| import std.utf : byChar; |
| auto deleteme = testFilename(); |
| auto f = File(deleteme.byChar, "w".byChar); |
| f.close(); |
| std.file.remove(deleteme); |
| } |
| |
| ~this() @safe |
| { |
| detach(); |
| } |
| |
| this(this) @safe nothrow |
| { |
| if (!_p) return; |
| assert(atomicLoad(_p.refs)); |
| atomicOp!"+="(_p.refs, 1); |
| } |
| |
| /** |
| Assigns a file to another. The target of the assignment gets detached |
| from whatever file it was attached to, and attaches itself to the new |
| file. |
| */ |
| ref File opAssign(File rhs) @safe return |
| { |
| import std.algorithm.mutation : swap; |
| |
| swap(this, rhs); |
| return this; |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=20129 |
| @safe unittest |
| { |
| File[int] aa; |
| aa.require(0, File.init); |
| } |
| |
| /** |
| Detaches from the current file (throwing on failure), and then attempts to |
| _open file `name` with mode `stdioOpenmode`. The mode has the |
| same semantics as in the C standard library $(HTTP |
| cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) function. |
| |
| Throws: `ErrnoException` in case of error. |
| */ |
| void open(string name, scope const(char)[] stdioOpenmode = "rb") @trusted |
| { |
| resetFile(name, stdioOpenmode, false); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=20585 |
| @system unittest |
| { |
| File f; |
| try |
| f.open("doesn't exist"); |
| catch (Exception _e) |
| { |
| } |
| |
| assert(!f.isOpen); |
| |
| f.close(); // to check not crash here |
| } |
| |
| private void resetFile(string name, scope const(char)[] stdioOpenmode, bool isPopened) @trusted |
| { |
| import core.stdc.stdlib : malloc; |
| import std.exception : enforce; |
| import std.conv : text; |
| import std.exception : errnoEnforce; |
| |
| if (_p !is null) |
| { |
| detach(); |
| } |
| |
| FILE* handle; |
| version (Posix) |
| { |
| if (isPopened) |
| { |
| errnoEnforce(handle = _popen(name, stdioOpenmode), |
| "Cannot run command `"~name~"'"); |
| } |
| else |
| { |
| errnoEnforce(handle = _fopen(name, stdioOpenmode), |
| text("Cannot open file `", name, "' in mode `", |
| stdioOpenmode, "'")); |
| } |
| } |
| else |
| { |
| assert(isPopened == false); |
| errnoEnforce(handle = _fopen(name, stdioOpenmode), |
| text("Cannot open file `", name, "' in mode `", |
| stdioOpenmode, "'")); |
| } |
| _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); |
| initImpl(handle, name, 1, isPopened); |
| version (MICROSOFT_STDIO) |
| { |
| setAppendWin(stdioOpenmode); |
| } |
| } |
| |
| private void closeHandles() @trusted |
| { |
| assert(_p); |
| import std.exception : errnoEnforce; |
| |
| version (Posix) |
| { |
| import core.sys.posix.stdio : pclose; |
| import std.format : format; |
| |
| if (_p.isPopened) |
| { |
| auto res = pclose(_p.handle); |
| errnoEnforce(res != -1, |
| "Could not close pipe `"~_name~"'"); |
| _p.handle = null; |
| return; |
| } |
| } |
| if (_p.handle) |
| { |
| auto handle = _p.handle; |
| _p.handle = null; |
| // fclose disassociates the FILE* even in case of error (issue 19751) |
| errnoEnforce(.fclose(handle) == 0, |
| "Could not close file `"~_name~"'"); |
| } |
| } |
| |
| version (MICROSOFT_STDIO) |
| { |
| private void setAppendWin(scope const(char)[] stdioOpenmode) @safe |
| { |
| bool append, update; |
| foreach (c; stdioOpenmode) |
| if (c == 'a') |
| append = true; |
| else |
| if (c == '+') |
| update = true; |
| if (append && !update) |
| seek(size); |
| } |
| } |
| |
| /** |
| Reuses the `File` object to either open a different file, or change |
| the file mode. If `name` is `null`, the mode of the currently open |
| file is changed; otherwise, a new file is opened, reusing the C |
| `FILE*`. The function has the same semantics as in the C standard |
| library $(HTTP cplusplus.com/reference/cstdio/freopen/, freopen) |
| function. |
| |
| Note: Calling `reopen` with a `null` `name` is not implemented |
| in all C runtimes. |
| |
| Throws: `ErrnoException` in case of error. |
| */ |
| void reopen(string name, scope const(char)[] stdioOpenmode = "rb") @trusted |
| { |
| import std.conv : text; |
| import std.exception : enforce, errnoEnforce; |
| import std.internal.cstring : tempCString; |
| |
| enforce(isOpen, "Attempting to reopen() an unopened file"); |
| |
| auto namez = (name == null ? _name : name).tempCString!FSChar(); |
| auto modez = stdioOpenmode.tempCString!FSChar(); |
| |
| FILE* fd = _p.handle; |
| version (Windows) |
| fd = _wfreopen(namez, modez, fd); |
| else |
| fd = freopen(namez, modez, fd); |
| |
| errnoEnforce(fd, name |
| ? text("Cannot reopen file `", name, "' in mode `", stdioOpenmode, "'") |
| : text("Cannot reopen file in mode `", stdioOpenmode, "'")); |
| |
| if (name !is null) |
| _name = name; |
| } |
| |
| @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"); |
| } |
| |
| /** |
| Detaches from the current file (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: `ErrnoException` in case of error. |
| */ |
| version (Posix) void popen(string command, scope const(char)[] stdioOpenmode = "r") @safe |
| { |
| resetFile(command, stdioOpenmode ,true); |
| } |
| |
| /** |
| First calls `detach` (throwing on failure), then attempts to |
| associate the given file descriptor with the `File`, and sets the file's name to `null`. |
| |
| The mode must be compatible with the mode of the file descriptor. |
| |
| Throws: `ErrnoException` in case of error. |
| Params: |
| fd = File descriptor to associate with this `File`. |
| stdioOpenmode = Mode to associate with this File. The mode has the same semantics |
| semantics as in the C standard library |
| $(HTTP cplusplus.com/reference/cstdio/fopen/, fdopen) function, and must be compatible with `fd`. |
| */ |
| void fdopen(int fd, scope const(char)[] stdioOpenmode = "rb") @safe |
| { |
| fdopen(fd, stdioOpenmode, null); |
| } |
| |
| package void fdopen(int fd, scope const(char)[] stdioOpenmode, string name) @trusted |
| { |
| import std.exception : errnoEnforce; |
| import std.internal.cstring : tempCString; |
| |
| auto modez = stdioOpenmode.tempCString(); |
| detach(); |
| |
| version (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. |
| 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 `detach` (throwing on failure), and then attempts to |
| associate the given Windows `HANDLE` with the `File`. The mode must |
| be compatible with the access attributes of the handle. Windows only. |
| |
| Throws: `ErrnoException` in case of error. |
| */ |
| version (StdDdoc) |
| void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode); |
| |
| version (Windows) |
| void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode) |
| { |
| import core.stdc.stdint : intptr_t; |
| import std.exception : errnoEnforce; |
| import std.format : format; |
| |
| // Create file descriptors from the handles |
| 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 `true` if the file is opened. */ |
| @property bool isOpen() const @safe pure nothrow |
| { |
| return _p !is null && _p.handle; |
| } |
| |
| /** |
| Returns `true` if the file is at end (see $(HTTP |
| cplusplus.com/reference/clibrary/cstdio/feof.html, feof)). |
| |
| Throws: `Exception` if the file is not opened. |
| */ |
| @property bool eof() const @trusted pure |
| { |
| import std.exception : enforce; |
| |
| enforce(_p && _p.handle, "Calling eof() against an unopened file."); |
| return .feof(cast(FILE*) _p.handle) != 0; |
| } |
| |
| /** |
| Returns the name last used to initialize this `File`, if any. |
| |
| Some functions that create or initialize the `File` set the name field to `null`. |
| Examples include $(LREF tmpfile), $(LREF wrapFile), and $(LREF fdopen). See the |
| documentation of those functions for details. |
| |
| Returns: The name last used to initialize this this file, or `null` otherwise. |
| */ |
| @property string name() const @safe pure nothrow return |
| { |
| return _name; |
| } |
| |
| /** |
| If the file is closed or not yet opened, returns `true`. Otherwise, returns |
| $(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 |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=12349 |
| static import std.file; |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) std.file.remove(deleteme); |
| |
| f.close(); |
| assert(f.error); |
| } |
| |
| /** |
| Detaches from the underlying file. If the sole owner, calls `close`. |
| |
| Throws: `ErrnoException` on failure if closing the file. |
| */ |
| void detach() @trusted |
| { |
| import core.stdc.stdlib : free; |
| |
| if (!_p) return; |
| scope(exit) _p = null; |
| |
| if (atomicOp!"-="(_p.refs, 1) == 0) |
| { |
| scope(exit) free(_p); |
| closeHandles(); |
| } |
| } |
| |
| @safe unittest |
| { |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| auto f = File(deleteme, "w"); |
| { |
| auto f2 = f; |
| f2.detach(); |
| } |
| assert(f._p.refs == 1); |
| f.close(); |
| } |
| |
| /** |
| If the file was closed or not yet opened, succeeds vacuously. Otherwise |
| closes the file (by calling $(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 `detach` in that it |
| always closes the file; consequently, all other `File` objects |
| referring to the same handle will see a closed file henceforth. |
| |
| Throws: `ErrnoException` on error. |
| */ |
| void close() @trusted |
| { |
| import core.stdc.stdlib : free; |
| import std.exception : errnoEnforce; |
| |
| if (!_p) return; // succeed vacuously |
| scope(exit) |
| { |
| if (atomicOp!"-="(_p.refs, 1) == 0) |
| free(_p); |
| _p = null; // start a new life |
| } |
| if (!_p.handle) return; // Impl is closed by another File |
| |
| scope(exit) _p.handle = null; // nullify the handle anyway |
| closeHandles(); |
| } |
| |
| /** |
| If the file is closed or not yet opened, succeeds vacuously. Otherwise, returns |
| $(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 `FILE` buffers. |
| |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_fflush.html, _fflush) |
| for the file handle. |
| |
| Throws: `Exception` if the file is not opened or if the call to `fflush` fails. |
| */ |
| void flush() @trusted |
| { |
| import std.exception : enforce, errnoEnforce; |
| |
| enforce(isOpen, "Attempting to flush() in an unopened file"); |
| errnoEnforce(.fflush(_p.handle) == 0); |
| } |
| |
| @safe unittest |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=12349 |
| import std.exception : assertThrown; |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) std.file.remove(deleteme); |
| |
| f.close(); |
| assertThrown(f.flush()); |
| } |
| |
| /** |
| Forces any data buffered by the OS to be written to disk. |
| Call $(LREF flush) before calling this function to flush the C `FILE` buffers first. |
| |
| This function calls |
| $(HTTP msdn.microsoft.com/en-us/library/windows/desktop/aa364439%28v=vs.85%29.aspx, |
| `FlushFileBuffers`) on Windows, |
| $(HTTP developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html, |
| `F_FULLFSYNC fcntl`) on Darwin and |
| $(HTTP pubs.opengroup.org/onlinepubs/7908799/xsh/fsync.html, |
| `fsync`) on POSIX for the file handle. |
| |
| Throws: `Exception` if the file is not opened or if the OS call fails. |
| */ |
| void sync() @trusted |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to sync() an unopened file"); |
| |
| version (Windows) |
| { |
| import core.sys.windows.winbase : FlushFileBuffers; |
| wenforce(FlushFileBuffers(windowsHandle), "FlushFileBuffers failed"); |
| } |
| else version (Darwin) |
| { |
| import core.sys.darwin.fcntl : fcntl, F_FULLFSYNC; |
| import std.exception : errnoEnforce; |
| errnoEnforce(fcntl(fileno, F_FULLFSYNC, 0) != -1, "fcntl failed"); |
| } |
| else |
| { |
| import core.sys.posix.unistd : fsync; |
| import std.exception : errnoEnforce; |
| errnoEnforce(fsync(fileno) == 0, "fsync failed"); |
| } |
| } |
| |
| /** |
| Calls $(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 `buffer` containing the data that was actually read. |
| This will be shorter than `buffer` if EOF was reached before the buffer |
| could be filled. |
| |
| Throws: `Exception` if `buffer` is empty. |
| `ErrnoException` if the file is not opened or the call to `fread` fails. |
| |
| `rawRead` always reads in binary mode on Windows. |
| */ |
| T[] rawRead(T)(T[] buffer) |
| { |
| import std.exception : enforce, errnoEnforce; |
| |
| if (!buffer.length) |
| throw new Exception("rawRead must take a non-empty buffer"); |
| enforce(isOpen, "Attempting to read from an unopened file"); |
| 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; |
| |
| // https://issues.dlang.org/show_bug.cgi?id=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 = std.file.deleteme(); |
| std.file.write(testFile, "\r\n\n\r\n"); |
| scope(exit) std.file.remove(testFile); |
| |
| auto f = File(testFile, "r"); |
| auto buf = f.rawRead(new char[5]); |
| f.close(); |
| assert(buf == "\r\n\n\r\n"); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=21729 |
| @system unittest |
| { |
| import std.exception : assertThrown; |
| |
| File f; |
| ubyte[1] u; |
| assertThrown(f.rawRead(u)); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=21728 |
| @system unittest |
| { |
| static if (__traits(compiles, { import std.process : pipe; })) // not available for iOS |
| { |
| import std.process : pipe; |
| import std.exception : assertThrown; |
| |
| auto p = pipe(); |
| p.readEnd.close; |
| ubyte[1] u; |
| assertThrown(p.readEnd.rawRead(u)); |
| } |
| } |
| |
| /** |
| 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. |
| |
| `rawWrite` always writes in binary mode on Windows. |
| |
| Throws: `ErrnoException` if the file is not opened or if the call to `fwrite` fails. |
| */ |
| void rawWrite(T)(in T[] buffer) |
| { |
| import std.conv : text; |
| import std.exception : errnoEnforce; |
| |
| version (Windows) |
| { |
| immutable fd = .fileno(_p.handle); |
| immutable oldMode = .__setmode(fd, _O_BINARY); |
| |
| if (oldMode != _O_BINARY) |
| { |
| // need to flush the data that was written with the original mode |
| .__setmode(fd, oldMode); |
| flush(); // before changing translation mode .__setmode(fd, _O_BINARY); |
| .__setmode(fd, _O_BINARY); |
| } |
| |
| version (DIGITAL_MARS_STDIO) |
| { |
| import core.atomic : atomicOp; |
| |
| // https://issues.dlang.org/show_bug.cgi?id=4243 |
| immutable info = __fhnd_info[fd]; |
| atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); |
| scope (exit) __fhnd_info[fd] = info; |
| } |
| |
| scope (exit) |
| { |
| if (oldMode != _O_BINARY) |
| { |
| flush(); |
| .__setmode(fd, oldMode); |
| } |
| } |
| } |
| |
| auto result = trustedFwrite(_p.handle, buffer); |
| if (result == result.max) result = 0; |
| errnoEnforce(result == buffer.length, |
| text("Wrote ", result, " instead of ", buffer.length, |
| " objects of type ", T.stringof, " to file `", |
| _name, "'")); |
| } |
| |
| /// |
| @system unittest |
| { |
| static import std.file; |
| |
| auto testFile = std.file.deleteme(); |
| auto f = File(testFile, "w"); |
| scope(exit) std.file.remove(testFile); |
| |
| f.rawWrite("\r\n\n\r\n"); |
| f.close(); |
| assert(std.file.read(testFile) == "\r\n\n\r\n"); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fseek.html, fseek) |
| for the file handle to move its position indicator. |
| |
| Params: |
| offset = Binary files: Number of bytes to offset from origin.$(BR) |
| Text files: Either zero, or a value returned by $(LREF tell). |
| origin = Binary files: Position used as reference for the offset, must be |
| one of $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio), |
| $(REF_ALTTEXT SEEK_CUR, SEEK_CUR, core,stdc,stdio) or |
| $(REF_ALTTEXT SEEK_END, SEEK_END, core,stdc,stdio).$(BR) |
| Text files: Shall necessarily be |
| $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio). |
| |
| Throws: `Exception` if the file is not opened. |
| `ErrnoException` if the call to `fseek` fails. |
| */ |
| void seek(long offset, int origin = SEEK_SET) @trusted |
| { |
| import std.conv : to, text; |
| import std.exception : enforce, errnoEnforce; |
| |
| // Some libc sanitize the whence input (e.g. glibc), but some don't, |
| // e.g. Microsoft runtime crashes on an invalid origin, |
| // and Musl additionally accept SEEK_DATA & SEEK_HOLE (Linux extension). |
| // To provide a consistent behavior cross platform, we use the glibc check |
| // See also https://issues.dlang.org/show_bug.cgi?id=19797 |
| enforce(origin == SEEK_SET || origin == SEEK_CUR || origin == SEEK_END, |
| "Invalid `origin` argument passed to `seek`, must be one of: SEEK_SET, SEEK_CUR, SEEK_END"); |
| |
| enforce(isOpen, "Attempting to seek() in an unopened file"); |
| version (Windows) |
| { |
| version (CRuntime_Microsoft) |
| { |
| alias fseekFun = _fseeki64; |
| alias off_t = long; |
| } |
| else |
| { |
| alias fseekFun = fseek; |
| alias off_t = int; |
| } |
| } |
| else version (Posix) |
| { |
| import core.sys.posix.stdio : fseeko, off_t; |
| alias fseekFun = fseeko; |
| } |
| errnoEnforce(fseekFun(_p.handle, to!off_t(offset), origin) == 0, |
| "Could not seek in file `"~_name~"'"); |
| } |
| |
| @system unittest |
| { |
| import std.conv : text; |
| static import std.file; |
| import std.exception; |
| |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w+"); |
| scope(exit) { f.close(); std.file.remove(deleteme); } |
| f.rawWrite("abcdefghijklmnopqrstuvwxyz"); |
| f.seek(7); |
| assert(f.readln() == "hijklmnopqrstuvwxyz"); |
| |
| version (CRuntime_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"); |
| |
| assertThrown(f.seek(0, ushort.max)); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/ftell.html, ftell) for the |
| managed file handle. |
| |
| Throws: `Exception` if the file is not opened. |
| `ErrnoException` if the call to `ftell` fails. |
| */ |
| @property ulong tell() const @trusted |
| { |
| import std.exception : enforce, errnoEnforce; |
| |
| enforce(isOpen, "Attempting to tell() in an unopened file"); |
| version (Windows) |
| { |
| version (CRuntime_Microsoft) |
| immutable result = _ftelli64(cast(FILE*) _p.handle); |
| else |
| immutable result = ftell(cast(FILE*) _p.handle); |
| } |
| else version (Posix) |
| { |
| import core.sys.posix.stdio : ftello; |
| immutable result = ftello(cast(FILE*) _p.handle); |
| } |
| errnoEnforce(result != -1, |
| "Query ftell() failed for file `"~_name~"'"); |
| return result; |
| } |
| |
| /// |
| @system unittest |
| { |
| import std.conv : text; |
| static import std.file; |
| |
| auto testFile = std.file.deleteme(); |
| std.file.write(testFile, "abcdefghijklmnopqrstuvwqxyz"); |
| scope(exit) { std.file.remove(testFile); } |
| |
| auto f = File(testFile); |
| auto a = new ubyte[4]; |
| f.rawRead(a); |
| assert(f.tell == 4, text(f.tell)); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_rewind.html, _rewind) |
| for the file handle. |
| |
| Throws: `Exception` if the file is not opened. |
| */ |
| void rewind() @safe |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to rewind() an unopened file"); |
| .rewind(_p.handle); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, _setvbuf) for |
| the file handle. |
| |
| Throws: `Exception` if the file is not opened. |
| `ErrnoException` if the call to `setvbuf` fails. |
| */ |
| void setvbuf(size_t size, int mode = _IOFBF) @trusted |
| { |
| import std.exception : enforce, errnoEnforce; |
| |
| enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); |
| errnoEnforce(.setvbuf(_p.handle, null, mode, size) == 0, |
| "Could not set buffering for file `"~_name~"'"); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, |
| _setvbuf) for the file handle. |
| |
| Throws: `Exception` if the file is not opened. |
| `ErrnoException` if the call to `setvbuf` fails. |
| */ |
| void setvbuf(void[] buf, int mode = _IOFBF) @trusted |
| { |
| import std.exception : enforce, errnoEnforce; |
| |
| enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); |
| errnoEnforce(.setvbuf(_p.handle, |
| cast(char*) buf.ptr, mode, buf.length) == 0, |
| "Could not set buffering for file `"~_name~"'"); |
| } |
| |
| |
| version (Windows) |
| { |
| import core.sys.windows.winbase : OVERLAPPED; |
| import core.sys.windows.winnt : BOOL, ULARGE_INTEGER; |
| import std.windows.syserror : wenforce; |
| |
| private BOOL lockImpl(alias F, Flags...)(ulong start, ulong length, |
| Flags flags) |
| { |
| if (!start && !length) |
| length = ulong.max; |
| ULARGE_INTEGER liStart = void, liLength = void; |
| liStart.QuadPart = start; |
| liLength.QuadPart = length; |
| OVERLAPPED overlapped; |
| overlapped.Offset = liStart.LowPart; |
| overlapped.OffsetHigh = liStart.HighPart; |
| overlapped.hEvent = null; |
| return F(windowsHandle, flags, 0, liLength.LowPart, |
| liLength.HighPart, &overlapped); |
| } |
| } |
| version (Posix) |
| { |
| private int lockImpl(int operation, short l_type, |
| ulong start, ulong length) |
| { |
| import core.sys.posix.fcntl : fcntl, flock, off_t; |
| import core.sys.posix.unistd : getpid; |
| import std.conv : to; |
| |
| flock fl = void; |
| fl.l_type = l_type; |
| fl.l_whence = SEEK_SET; |
| fl.l_start = to!off_t(start); |
| fl.l_len = to!off_t(length); |
| fl.l_pid = getpid(); |
| return fcntl(fileno, operation, &fl); |
| } |
| } |
| |
| /** |
| Locks the specified file segment. If the file segment is already locked |
| by another process, waits until the existing lock is released. |
| If both `start` and `length` are zero, the entire file is locked. |
| |
| Locks created using `lock` and `tryLock` have the following properties: |
| $(UL |
| $(LI All locks are automatically released when the process terminates.) |
| $(LI Locks are not inherited by child processes.) |
| $(LI Closing a file will release all locks associated with the file. On POSIX, |
| even locks acquired via a different `File` will be released as well.) |
| $(LI Not all NFS implementations correctly implement file locking.) |
| ) |
| */ |
| void lock(LockType lockType = LockType.readWrite, |
| ulong start = 0, ulong length = 0) |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to call lock() on an unopened file"); |
| version (Posix) |
| { |
| import core.sys.posix.fcntl : F_RDLCK, F_SETLKW, F_WRLCK; |
| import std.exception : errnoEnforce; |
| immutable short type = lockType == LockType.readWrite |
| ? F_WRLCK : F_RDLCK; |
| errnoEnforce(lockImpl(F_SETLKW, type, start, length) != -1, |
| "Could not set lock for file `"~_name~"'"); |
| } |
| else |
| version (Windows) |
| { |
| import core.sys.windows.winbase : LockFileEx, LOCKFILE_EXCLUSIVE_LOCK; |
| immutable type = lockType == LockType.readWrite ? |
| LOCKFILE_EXCLUSIVE_LOCK : 0; |
| wenforce(lockImpl!LockFileEx(start, length, type), |
| "Could not set lock for file `"~_name~"'"); |
| } |
| else |
| static assert(false); |
| } |
| |
| /** |
| Attempts to lock the specified file segment. |
| If both `start` and `length` are zero, the entire file is locked. |
| Returns: `true` if the lock was successful, and `false` if the |
| specified file segment was already locked. |
| */ |
| bool tryLock(LockType lockType = LockType.readWrite, |
| ulong start = 0, ulong length = 0) |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to call tryLock() on an unopened file"); |
| version (Posix) |
| { |
| import core.stdc.errno : EACCES, EAGAIN, errno; |
| import core.sys.posix.fcntl : F_RDLCK, F_SETLK, F_WRLCK; |
| import std.exception : errnoEnforce; |
| immutable short type = lockType == LockType.readWrite |
| ? F_WRLCK : F_RDLCK; |
| immutable res = lockImpl(F_SETLK, type, start, length); |
| if (res == -1 && (errno == EACCES || errno == EAGAIN)) |
| return false; |
| errnoEnforce(res != -1, "Could not set lock for file `"~_name~"'"); |
| return true; |
| } |
| else |
| version (Windows) |
| { |
| import core.sys.windows.winbase : GetLastError, LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, |
| LOCKFILE_FAIL_IMMEDIATELY; |
| import core.sys.windows.winerror : ERROR_IO_PENDING, ERROR_LOCK_VIOLATION; |
| immutable type = lockType == LockType.readWrite |
| ? LOCKFILE_EXCLUSIVE_LOCK : 0; |
| immutable res = lockImpl!LockFileEx(start, length, |
| type | LOCKFILE_FAIL_IMMEDIATELY); |
| if (!res && (GetLastError() == ERROR_IO_PENDING |
| || GetLastError() == ERROR_LOCK_VIOLATION)) |
| return false; |
| wenforce(res, "Could not set lock for file `"~_name~"'"); |
| return true; |
| } |
| else |
| static assert(false); |
| } |
| |
| /** |
| Removes the lock over the specified file segment. |
| */ |
| void unlock(ulong start = 0, ulong length = 0) |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to call unlock() on an unopened file"); |
| version (Posix) |
| { |
| import core.sys.posix.fcntl : F_SETLK, F_UNLCK; |
| import std.exception : errnoEnforce; |
| errnoEnforce(lockImpl(F_SETLK, F_UNLCK, start, length) != -1, |
| "Could not remove lock for file `"~_name~"'"); |
| } |
| else |
| version (Windows) |
| { |
| import core.sys.windows.winbase : UnlockFileEx; |
| wenforce(lockImpl!UnlockFileEx(start, length), |
| "Could not remove lock for file `"~_name~"'"); |
| } |
| else |
| static assert(false); |
| } |
| |
| version (Windows) |
| @system unittest |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| auto f = File(deleteme, "wb"); |
| assert(f.tryLock()); |
| auto g = File(deleteme, "wb"); |
| assert(!g.tryLock()); |
| assert(!g.tryLock(LockType.read)); |
| f.unlock(); |
| f.lock(LockType.read); |
| assert(!g.tryLock()); |
| assert(g.tryLock(LockType.read)); |
| f.unlock(); |
| g.unlock(); |
| } |
| |
| version (Posix) |
| @system unittest |
| { |
| static if (__traits(compiles, { import std.process : spawnProcess; })) |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| |
| // Since locks are per-process, we cannot test lock failures within |
| // the same process. fork() is used to create a second process. |
| static void runForked(void delegate() code) |
| { |
| import core.sys.posix.sys.wait : waitpid; |
| import core.sys.posix.unistd : fork, _exit; |
| int child, status; |
| if ((child = fork()) == 0) |
| { |
| code(); |
| _exit(0); |
| } |
| else |
| { |
| assert(waitpid(child, &status, 0) != -1); |
| assert(status == 0, "Fork crashed"); |
| } |
| } |
| |
| auto f = File(deleteme, "w+b"); |
| |
| runForked |
| ({ |
| auto g = File(deleteme, "a+b"); |
| assert(g.tryLock()); |
| g.unlock(); |
| assert(g.tryLock(LockType.read)); |
| }); |
| |
| assert(f.tryLock()); |
| runForked |
| ({ |
| auto g = File(deleteme, "a+b"); |
| assert(!g.tryLock()); |
| assert(!g.tryLock(LockType.read)); |
| }); |
| f.unlock(); |
| |
| f.lock(LockType.read); |
| runForked |
| ({ |
| auto g = File(deleteme, "a+b"); |
| assert(!g.tryLock()); |
| assert(g.tryLock(LockType.read)); |
| g.unlock(); |
| }); |
| f.unlock(); |
| } // static if |
| } // unittest |
| |
| |
| /** |
| Writes its arguments in text format to the file. |
| |
| Throws: `Exception` if the file is not opened. |
| `ErrnoException` on an error writing to the file. |
| */ |
| void write(S...)(S args) |
| { |
| import std.traits : isBoolean, isIntegral, isAggregateType; |
| import std.utf : UTFException; |
| auto w = lockingTextWriter(); |
| foreach (arg; args) |
| { |
| try |
| { |
| alias A = typeof(arg); |
| static if (isAggregateType!A || is(A == enum)) |
| { |
| import std.format.write : formattedWrite; |
| |
| formattedWrite(w, "%s", arg); |
| } |
| else static if (isSomeString!A) |
| { |
| put(w, arg); |
| } |
| else static if (isIntegral!A) |
| { |
| import std.conv : toTextRange; |
| |
| toTextRange(arg, w); |
| } |
| else static if (isBoolean!A) |
| { |
| put(w, arg ? "true" : "false"); |
| } |
| else static if (isSomeChar!A) |
| { |
| put(w, arg); |
| } |
| else |
| { |
| import std.format.write : formattedWrite; |
| |
| // Most general case |
| formattedWrite(w, "%s", arg); |
| } |
| } |
| catch (UTFException e) |
| { |
| /* Reset the writer so that it doesn't throw another |
| UTFException on destruction. */ |
| w.highSurrogate = '\0'; |
| throw e; |
| } |
| } |
| } |
| |
| /** |
| Writes its arguments in text format to the file, followed by a newline. |
| |
| Throws: `Exception` if the file is not opened. |
| `ErrnoException` on an error writing to the file. |
| */ |
| void writeln(S...)(S args) |
| { |
| write(args, '\n'); |
| } |
| |
| /** |
| Writes its arguments in text format to the file, according to the |
| format string fmt. |
| |
| Params: |
| fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). |
| When passed as a compile-time argument, the string will be statically checked |
| against the argument types passed. |
| args = Items to write. |
| |
| Throws: `Exception` if the file is not opened. |
| `ErrnoException` on an error writing to the file. |
| */ |
| void writef(alias fmt, A...)(A args) |
| if (isSomeString!(typeof(fmt))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(fmt, A); |
| static assert(!e, e); |
| return this.writef(fmt, args); |
| } |
| |
| /// ditto |
| void writef(Char, A...)(in Char[] fmt, A args) |
| { |
| import std.format.write : formattedWrite; |
| |
| formattedWrite(lockingTextWriter(), fmt, args); |
| } |
| |
| /// Equivalent to `file.writef(fmt, args, '\n')`. |
| void writefln(alias fmt, A...)(A args) |
| if (isSomeString!(typeof(fmt))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(fmt, A); |
| static assert(!e, e); |
| return this.writefln(fmt, args); |
| } |
| |
| /// ditto |
| void writefln(Char, A...)(in Char[] fmt, A args) |
| { |
| import std.format.write : formattedWrite; |
| |
| auto w = lockingTextWriter(); |
| formattedWrite(w, fmt, args); |
| w.put('\n'); |
| } |
| |
| /** |
| Read line from the file handle and return it as a specified type. |
| |
| This version manages its own read buffer, which means one memory allocation per call. If you are not |
| retaining a reference to the read data, consider the `File.readln(buf)` version, which may offer |
| better performance as it can reuse its read buffer. |
| |
| Params: |
| S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. |
| terminator = Line terminator (by default, `'\n'`). |
| |
| Note: |
| String terminators are not supported due to ambiguity with readln(buf) below. |
| |
| Returns: |
| The line that was read, including the line terminator character. |
| |
| Throws: |
| `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. |
| |
| Example: |
| --- |
| // Reads `stdin` and writes it to `stdout`. |
| import std.stdio; |
| |
| void main() |
| { |
| string line; |
| while ((line = stdin.readln()) !is null) |
| write(line); |
| } |
| --- |
| */ |
| S readln(S = string)(dchar terminator = '\n') |
| 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); |
| static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) |
| {{ |
| auto witness = [ "hello\n", "world\n" ]; |
| auto f = File(deleteme); |
| uint i = 0; |
| String buf; |
| while ((buf = f.readln!String()).length) |
| { |
| assert(i < witness.length); |
| assert(equal(buf, witness[i++])); |
| } |
| assert(i == witness.length); |
| }} |
| } |
| |
| @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]; |
| static foreach (uint i, C; Tuple!(char, wchar, dchar).Types) |
| {{ |
| immutable(C)[] witness = "cześć \U0002000D"; |
| auto buf = File(deleteme).readln!(immutable(C)[])(); |
| assert(buf.length == lengths[i]); |
| assert(buf == witness); |
| }} |
| } |
| |
| /** |
| Read line from the file handle and write it to `buf[]`, including |
| terminating character. |
| |
| This can be faster than $(D line = File.readln()) because you can reuse |
| the buffer for each call. Note that reusing the buffer means that you |
| must copy the previous contents if you wish to retain them. |
| |
| Params: |
| buf = Buffer used to store the resulting line data. buf is |
| enlarged if necessary, then set to the slice exactly containing the line. |
| terminator = Line terminator (by default, `'\n'`). Use |
| $(REF newline, std,ascii) for portability (unless the file was opened in |
| text mode). |
| |
| Returns: |
| 0 for end of file, otherwise number of characters read. |
| The return value will always be equal to `buf.length`. |
| |
| Throws: `StdioException` on I/O error, or `UnicodeException` on Unicode |
| conversion error. |
| |
| Example: |
| --- |
| // Read lines from `stdin` into a string |
| // Ignore lines starting with '#' |
| // Write the string to `stdout` |
| import std.stdio; |
| |
| void main() |
| { |
| string output; |
| char[] buf; |
| |
| while (stdin.readln(buf)) |
| { |
| if (buf[0] == '#') |
| continue; |
| |
| output ~= buf; |
| } |
| |
| write(output); |
| } |
| --- |
| |
| This method can be more efficient than the one in the previous example |
| because `stdin.readln(buf)` reuses (if possible) memory allocated |
| for `buf`, whereas $(D line = stdin.readln()) makes a new memory allocation |
| for every line. |
| |
| For even better performance you can help `readln` by passing in a |
| large buffer to avoid memory reallocations. This can be done by reusing the |
| largest buffer returned by `readln`: |
| |
| Example: |
| --- |
| // Read lines from `stdin` and count words |
| import std.array, std.stdio; |
| |
| void main() |
| { |
| char[] buf; |
| size_t words = 0; |
| |
| while (!stdin.eof) |
| { |
| char[] line = buf; |
| stdin.readln(line); |
| if (line.length > buf.length) |
| buf = line; |
| |
| words += line.split.length; |
| } |
| |
| writeln(words); |
| } |
| --- |
| This is actually what $(LREF byLine) does internally, so its usage |
| is recommended if you want to process a complete file. |
| */ |
| size_t readln(C)(ref C[] buf, dchar terminator = '\n') |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) |
| { |
| import std.exception : enforce; |
| |
| static if (is(C == char)) |
| { |
| enforce(_p && _p.handle, "Attempt to read from an unopened file."); |
| if (_p.orientation == Orientation.unknown) |
| { |
| import core.stdc.wchar_ : fwide; |
| auto w = fwide(_p.handle, 0); |
| if (w < 0) _p.orientation = Orientation.narrow; |
| else if (w > 0) _p.orientation = Orientation.wide; |
| } |
| return readlnImpl(_p.handle, buf, terminator, _p.orientation); |
| } |
| else |
| { |
| string s = readln(terminator); |
| if (!s.length) |
| { |
| buf = buf[0 .. 0]; |
| return 0; |
| } |
| |
| import std.utf : codeLength; |
| buf.length = codeLength!C(s); |
| size_t idx; |
| foreach (C c; s) |
| buf[idx++] = c; |
| |
| return buf.length; |
| } |
| } |
| |
| @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'); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=15293 |
| @system unittest |
| { |
| // @system due to readln |
| static import std.file; |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "a\n\naa"); |
| scope(exit) std.file.remove(deleteme); |
| |
| auto file = File(deleteme); |
| char[] buffer; |
| char[] line; |
| |
| file.readln(buffer, '\n'); |
| |
| line = buffer; |
| file.readln(line, '\n'); |
| |
| line = buffer; |
| file.readln(line, '\n'); |
| |
| assert(line[0 .. 1].capacity == 0); |
| } |
| |
| /** ditto */ |
| size_t readln(C, R)(ref C[] buf, R terminator) |
| 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 $(REF_ALTTEXT format string, formattedWrite, std, _format). |
| * When passed as a compile-time argument, the string will be statically checked |
| * against the argument types passed. |
| * data = Items to be read. |
| * Returns: |
| * Same as `formattedRead`: The number of variables filled. If the input range `r` ends early, |
| * this number will be less than the number of variables provided. |
| * Example: |
| ---- |
| // test.d |
| void main() |
| { |
| import std.stdio; |
| auto f = File("input"); |
| foreach (_; 0 .. 3) |
| { |
| int a; |
| f.readf!" %d"(a); |
| writeln(++a); |
| } |
| } |
| ---- |
| $(CONSOLE |
| % echo "1 2 3" > input |
| % rdmd test.d |
| 2 |
| 3 |
| 4 |
| ) |
| */ |
| uint readf(alias format, Data...)(auto ref Data data) |
| if (isSomeString!(typeof(format))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(format, Data); |
| static assert(!e, e); |
| return this.readf(format, data); |
| } |
| |
| /// ditto |
| uint readf(Data...)(scope const(char)[] format, auto ref Data data) |
| { |
| import std.format.read : formattedRead; |
| |
| assert(isOpen); |
| auto input = LockingTextReader(this); |
| return formattedRead(input, format, data); |
| } |
| |
| /// |
| @system unittest |
| { |
| static import std.file; |
| |
| auto deleteme = std.file.deleteme(); |
| std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); |
| scope(exit) std.file.remove(deleteme); |
| string s; |
| auto f = File(deleteme); |
| f.readf!"%s\n"(s); |
| assert(s == "hello", "["~s~"]"); |
| f.readf("%s\n", s); |
| assert(s == "world", "["~s~"]"); |
| |
| bool b1, b2; |
| f.readf("%s\n%s\n", b1, b2); |
| assert(b1 == true && b2 == false); |
| } |
| |
| // backwards compatibility with pointers |
| @system unittest |
| { |
| // @system due to readf |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); |
| scope(exit) std.file.remove(deleteme); |
| string s; |
| auto f = File(deleteme); |
| f.readf("%s\n", &s); |
| assert(s == "hello", "["~s~"]"); |
| f.readf("%s\n", &s); |
| assert(s == "world", "["~s~"]"); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=11698 |
| bool b1, b2; |
| f.readf("%s\n%s\n", &b1, &b2); |
| assert(b1 == true && b2 == false); |
| } |
| |
| // backwards compatibility (mixed) |
| @system unittest |
| { |
| // @system due to readf |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); |
| scope(exit) std.file.remove(deleteme); |
| string s1, s2; |
| auto f = File(deleteme); |
| f.readf("%s\n%s\n", s1, &s2); |
| assert(s1 == "hello"); |
| assert(s2 == "world"); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=11698 |
| bool b1, b2; |
| f.readf("%s\n%s\n", &b1, b2); |
| assert(b1 == true && b2 == false); |
| } |
| |
| // Nice error of std.stdio.readf with newlines |
| // https://issues.dlang.org/show_bug.cgi?id=12260 |
| @system unittest |
| { |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "1\n2"); |
| scope(exit) std.file.remove(deleteme); |
| int input; |
| auto f = File(deleteme); |
| f.readf("%s", &input); |
| |
| import std.conv : ConvException; |
| import std.exception : collectException; |
| assert(collectException!ConvException(f.readf("%s", &input)).msg == |
| "Unexpected '\\n' when converting from type LockingTextReader to type int"); |
| } |
| |
| /** |
| 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 `FILE*`. The resulting $(D |
| File) never takes the initiative in closing the file. |
| Note that the created file has no $(LREF name)*/ |
| /*private*/ static File wrapFile(FILE* f) @safe |
| { |
| import std.exception : enforce; |
| |
| return File(enforce(f, "Could not wrap null FILE*"), |
| null, /*uint.max / 2*/ 9999); |
| } |
| |
| /** |
| Returns the `FILE*` corresponding to this object. |
| */ |
| FILE* getFP() @safe pure |
| { |
| import std.exception : enforce; |
| |
| enforce(_p && _p.handle, |
| "Attempting to call getFP() on an unopened file"); |
| return _p.handle; |
| } |
| |
| @system unittest |
| { |
| static import core.stdc.stdio; |
| assert(stdout.getFP() == core.stdc.stdio.stdout); |
| } |
| |
| /** |
| Returns the file number corresponding to this object. |
| */ |
| @property 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 `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. |
| */ |
| private struct ByLineImpl(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; |
| bool haveLine; |
| |
| public: |
| this(File f, KeepTerminator kt, Terminator terminator) |
| { |
| file = f; |
| this.terminator = terminator; |
| keepTerminator = kt; |
| } |
| |
| // Range primitive implementations. |
| @property bool empty() |
| { |
| needLine(); |
| return line is null; |
| } |
| |
| @property Char[] front() |
| { |
| needLine(); |
| return line; |
| } |
| |
| void popFront() |
| { |
| needLine(); |
| haveLine = false; |
| } |
| |
| private: |
| void needLine() |
| { |
| if (haveLine) |
| return; |
| import std.algorithm.searching : endsWith; |
| assert(file.isOpen); |
| line = buffer; |
| file.readln(line, terminator); |
| if (line.length > buffer.length) |
| { |
| buffer = line; |
| } |
| if (line.empty) |
| { |
| file.detach(); |
| line = null; |
| } |
| else if (keepTerminator == No.keepTerminator |
| && endsWith(line, terminator)) |
| { |
| static if (isScalarType!Terminator) |
| enum tlen = 1; |
| else static if (isArray!Terminator) |
| { |
| static assert( |
| is(immutable ElementEncodingType!Terminator == immutable Char)); |
| const tlen = terminator.length; |
| } |
| else |
| static assert(false); |
| line = line[0 .. line.length - tlen]; |
| } |
| haveLine = true; |
| } |
| } |
| } |
| |
| /** |
| Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) |
| set up to read from the file handle one line at a time. |
| |
| The element type for the range will be `Char[]`. Range primitives |
| may throw `StdioException` on I/O error. |
| |
| Note: |
| Each `front` will not persist after $(D |
| popFront) is called, so the caller must copy its contents (e.g. by |
| calling `to!string`) when retention is needed. If the caller needs |
| to retain a copy of every line, use the $(LREF byLineCopy) function |
| instead. |
| |
| Params: |
| Char = Character type for each line, defaulting to `char`. |
| keepTerminator = Use `Yes.keepTerminator` to include the |
| terminator at the end of each line. |
| terminator = Line separator (`'\n'` by default). Use |
| $(REF newline, std,ascii) for portability (unless the file was opened in |
| text mode). |
| |
| Example: |
| ---- |
| import std.algorithm, std.stdio, std.string; |
| // Count words in a file using ranges. |
| void main() |
| { |
| auto file = File("file.txt"); // Open for reading |
| const wordCount = file.byLine() // Read lines |
| .map!split // Split into words |
| .map!(a => a.length) // Count words per line |
| .sum(); // Total word count |
| writeln(wordCount); |
| } |
| ---- |
| |
| Example: |
| ---- |
| import std.range, std.stdio; |
| // Read lines using foreach. |
| void main() |
| { |
| auto file = File("file.txt"); // Open for reading |
| auto range = file.byLine(); |
| // Print first three lines |
| foreach (line; range.take(3)) |
| writeln(line); |
| // Print remaining lines beginning with '#' |
| foreach (line; range) |
| { |
| if (!line.empty && line[0] == '#') |
| writeln(line); |
| } |
| } |
| ---- |
| Notice that neither example accesses the line data returned by |
| `front` after the corresponding `popFront` call is made (because |
| the contents may well have changed). |
| */ |
| auto byLine(Terminator = char, Char = char) |
| (KeepTerminator keepTerminator = No.keepTerminator, |
| Terminator terminator = '\n') |
| if (isScalarType!Terminator) |
| { |
| return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); |
| } |
| |
| /// ditto |
| auto byLine(Terminator, Char = char) |
| (KeepTerminator keepTerminator, Terminator terminator) |
| if (is(immutable ElementEncodingType!Terminator == immutable Char)) |
| { |
| return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "hi"); |
| scope(success) std.file.remove(deleteme); |
| |
| import std.meta : AliasSeq; |
| static foreach (T; AliasSeq!(char, wchar, dchar)) |
| {{ |
| auto blc = File(deleteme).byLine!(T, T); |
| assert(blc.front == "hi"); |
| // check front is cached |
| assert(blc.front is blc.front); |
| }} |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=19980 |
| @system unittest |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "Line 1\nLine 2\nLine 3\n"); |
| scope(success) std.file.remove(deleteme); |
| |
| auto f = File(deleteme); |
| f.byLine(); |
| f.byLine(); |
| assert(f.byLine().front == "Line 1"); |
| } |
| |
| private struct ByLineCopy(Char, Terminator) |
| { |
| private: |
| import std.typecons : 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) |
| { |
| ByLineImpl!(Unqual!Char, Terminator).Impl impl; |
| bool gotFront; |
| Char[] line; |
| |
| public: |
| this(File f, KeepTerminator kt, Terminator terminator) |
| { |
| impl = ByLineImpl!(Unqual!Char, Terminator).Impl(f, kt, terminator); |
| } |
| |
| @property bool empty() |
| { |
| return impl.empty; |
| } |
| |
| @property front() |
| { |
| if (!gotFront) |
| { |
| line = impl.front.dup; |
| gotFront = true; |
| } |
| return line; |
| } |
| |
| void popFront() |
| { |
| impl.popFront(); |
| gotFront = false; |
| } |
| } |
| |
| /** |
| Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) |
| set up to read from the file handle one line |
| at a time. Each line will be newly allocated. `front` will cache |
| its value to allow repeated calls without unnecessary allocations. |
| |
| Note: Due to caching byLineCopy can be more memory-efficient than |
| `File.byLine.map!idup`. |
| |
| The element type for the range will be `Char[]`. Range |
| primitives may throw `StdioException` on I/O error. |
| |
| Params: |
| Char = Character type for each line, defaulting to $(D immutable char). |
| keepTerminator = Use `Yes.keepTerminator` to include the |
| terminator at the end of each line. |
| terminator = Line separator (`'\n'` by default). Use |
| $(REF newline, std,ascii) for portability (unless the file was opened in |
| text mode). |
| |
| Example: |
| ---- |
| import std.algorithm, std.array, std.stdio; |
| // Print sorted lines of a file. |
| void main() |
| { |
| auto sortedLines = File("file.txt") // Open for reading |
| .byLineCopy() // Read persistent lines |
| .array() // into an array |
| .sort(); // then sort them |
| foreach (line; sortedLines) |
| writeln(line); |
| } |
| ---- |
| See_Also: |
| $(REF readText, std,file) |
| */ |
| auto byLineCopy(Terminator = char, Char = immutable char) |
| (KeepTerminator keepTerminator = No.keepTerminator, |
| Terminator terminator = '\n') |
| if (isScalarType!Terminator) |
| { |
| return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); |
| } |
| |
| /// ditto |
| auto byLineCopy(Terminator, Char = immutable char) |
| (KeepTerminator keepTerminator, Terminator terminator) |
| if (is(immutable ElementEncodingType!Terminator == immutable Char)) |
| { |
| return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); |
| } |
| |
| @safe unittest |
| { |
| static assert(is(typeof(File("").byLine.front) == char[])); |
| static assert(is(typeof(File("").byLineCopy.front) == string)); |
| static assert( |
| is(typeof(File("").byLineCopy!(char, char).front) == char[])); |
| } |
| |
| @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)); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=11830 |
| auto walkedLength = File(deleteme).byLine(kt, term).walkLength; |
| assert(walkedLength == witness.length, text(walkedLength, " != ", witness.length)); |
| |
| // test persistent lines |
| assert(File(deleteme).byLineCopy(kt, term).array.sort() == witness.dup.sort()); |
| } |
| |
| KeepTerminator kt = No.keepTerminator; |
| test("", null, kt, '\n'); |
| test("\n", [ "" ], kt, '\n'); |
| test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n'); |
| test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n', true); |
| test("asd\ndef\nasdf\n", [ "asd", "def", "asdf" ], kt, '\n'); |
| test("foo", [ "foo" ], kt, '\n', true); |
| test("bob\r\nmarge\r\nsteve\r\n", ["bob", "marge", "steve"], |
| kt, "\r\n"); |
| test("sue\r", ["sue"], kt, '\r'); |
| |
| kt = Yes.keepTerminator; |
| test("", null, kt, '\n'); |
| test("\n", [ "\n" ], kt, '\n'); |
| test("asd\ndef\nasdf", [ "asd\n", "def\n", "asdf" ], kt, '\n'); |
| test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n'); |
| test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n', true); |
| test("foo", [ "foo" ], kt, '\n'); |
| test("bob\r\nmarge\r\nsteve\r\n", ["bob\r\n", "marge\r\n", "steve\r\n"], |
| kt, "\r\n"); |
| test("sue\r", ["sue\r"], kt, '\r'); |
| } |
| |
| @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"); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=9599 |
| file.rewind(); |
| File.ByLineImpl!(char, char) fbl = file.byLine(); |
| auto fbl2 = fbl; |
| assert(fbl.front == "1"); |
| assert(fbl.front is fbl2.front); |
| assert(fbl.take(1).equal(["1"])); |
| assert(fbl.equal(["2", "3"])); |
| assert(fbl.empty); |
| assert(file.isOpen); // we still have a valid reference |
| |
| file.rewind(); |
| fbl = file.byLine(); |
| assert(!fbl.drop(2).empty); |
| assert(fbl.equal(["3"])); |
| assert(fbl.empty); |
| assert(file.isOpen); |
| |
| file.detach(); |
| assert(!file.isOpen); |
| } |
| |
| @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 $(REF_ALTTEXT input range, isInputRange, std,range,primitives) |
| set up to parse one line at a time from the file into a tuple. |
| |
| Range primitives may throw `StdioException` on I/O error. |
| |
| Params: |
| format = tuple record $(REF_ALTTEXT _format, formattedRead, std, _format) |
| |
| Returns: |
| The input range set up to parse one line at a time into a record tuple. |
| |
| See_Also: |
| |
| It is similar to $(LREF byLine) and uses |
| $(REF_ALTTEXT _format, formattedRead, std, _format) under the hood. |
| */ |
| template byRecord(Fields...) |
| { |
| auto byRecord(string format) |
| { |
| return ByRecordImpl!(Fields)(this, format); |
| } |
| } |
| |
| /// |
| @system unittest |
| { |
| static import std.file; |
| import std.typecons : tuple; |
| |
| // prepare test file |
| auto testFile = std.file.deleteme(); |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| std.file.write(testFile, "1 2\n4 1\n5 100"); |
| scope(exit) std.file.remove(testFile); |
| |
| File f = File(testFile); |
| scope(exit) f.close(); |
| |
| auto expected = [tuple(1, 2), tuple(4, 1), tuple(5, 100)]; |
| uint i; |
| foreach (e; f.byRecord!(int, int)("%s %s")) |
| { |
| assert(e == expected[i++]); |
| } |
| } |
| |
| // Note: This was documented until 2013/08 |
| /* |
| * Range that reads a chunk at a time. |
| */ |
| private struct ByChunkImpl |
| { |
| private: |
| File file_; |
| ubyte[] chunk_; |
| |
| void prime() |
| { |
| chunk_ = file_.rawRead(chunk_); |
| if (chunk_.length == 0) |
| file_.detach(); |
| } |
| |
| public: |
| this(File file, size_t size) |
| { |
| this(file, new ubyte[](size)); |
| } |
| |
| this(File file, ubyte[] buffer) |
| { |
| import std.exception : enforce; |
| enforce(buffer.length, "size must be larger than 0"); |
| file_ = file; |
| chunk_ = buffer; |
| prime(); |
| } |
| |
| // `ByChunk`'s input range primitive operations. |
| @property nothrow |
| bool empty() const |
| { |
| return !file_.isOpen; |
| } |
| |
| /// Ditto |
| @property nothrow |
| ubyte[] front() |
| { |
| version (assert) |
| { |
| import core.exception : RangeError; |
| if (empty) |
| throw new RangeError(); |
| } |
| return chunk_; |
| } |
| |
| /// Ditto |
| void popFront() |
| { |
| version (assert) |
| { |
| import core.exception : RangeError; |
| if (empty) |
| throw new RangeError(); |
| } |
| prime(); |
| } |
| } |
| |
| /** |
| Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) |
| set up to read from the file handle a chunk at a time. |
| |
| The element type for the range will be `ubyte[]`. Range primitives |
| may throw `StdioException` on I/O error. |
| |
| Example: |
| --------- |
| void main() |
| { |
| // Read standard input 4KB at a time |
| foreach (ubyte[] buffer; stdin.byChunk(4096)) |
| { |
| ... use buffer ... |
| } |
| } |
| --------- |
| |
| The parameter may be a number (as shown in the example above) dictating the |
| size of each chunk. Alternatively, `byChunk` accepts a |
| user-provided buffer that it uses directly. |
| |
| Example: |
| --------- |
| void main() |
| { |
| // Read standard input 4KB at a time |
| foreach (ubyte[] buffer; stdin.byChunk(new ubyte[4096])) |
| { |
| ... use buffer ... |
| } |
| } |
| --------- |
| |
| In either case, the content of the buffer is reused across calls. That means |
| `front` will not persist after `popFront` is called, so if retention is |
| needed, the caller must copy its contents (e.g. by calling `buffer.dup`). |
| |
| In the example above, `buffer.length` is 4096 for all iterations, except |
| for the last one, in which case `buffer.length` may be less than 4096 (but |
| always greater than zero). |
| |
| With the mentioned limitations, `byChunk` works with any algorithm |
| compatible with input ranges. |
| |
| Example: |
| --- |
| // Efficient file copy, 1MB at a time. |
| import std.algorithm, std.stdio; |
| void main() |
| { |
| stdin.byChunk(1024 * 1024).copy(stdout.lockingTextWriter()); |
| } |
| --- |
| |
| $(REF joiner, std,algorithm,iteration) can be used to join chunks together into |
| a single range lazily. |
| Example: |
| --- |
| import std.algorithm, std.stdio; |
| void main() |
| { |
| //Range of ranges |
| static assert(is(typeof(stdin.byChunk(4096).front) == ubyte[])); |
| //Range of elements |
| static assert(is(typeof(stdin.byChunk(4096).joiner.front) == ubyte)); |
| } |
| --- |
| |
| Returns: A call to `byChunk` returns a range initialized with the `File` |
| object and the appropriate buffer. |
| |
| Throws: If the user-provided size is zero or the user-provided buffer |
| is empty, throws an `Exception`. In case of an I/O error throws |
| `StdioException`. |
| */ |
| auto byChunk(size_t chunkSize) |
| { |
| return ByChunkImpl(this, chunkSize); |
| } |
| /// Ditto |
| auto byChunk(ubyte[] buffer) |
| { |
| return ByChunkImpl(this, buffer); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "asd\ndef\nasdf"); |
| |
| auto witness = ["asd\n", "def\n", "asdf" ]; |
| auto f = File(deleteme); |
| scope(exit) |
| { |
| f.close(); |
| assert(!f.isOpen); |
| std.file.remove(deleteme); |
| } |
| |
| uint i; |
| foreach (chunk; f.byChunk(4)) |
| assert(chunk == cast(ubyte[]) witness[i++]); |
| |
| assert(i == witness.length); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "asd\ndef\nasdf"); |
| |
| auto witness = ["asd\n", "def\n", "asdf" ]; |
| auto f = File(deleteme); |
| scope(exit) |
| { |
| f.close(); |
| assert(!f.isOpen); |
| std.file.remove(deleteme); |
| } |
| |
| uint i; |
| foreach (chunk; f.byChunk(new ubyte[4])) |
| assert(chunk == cast(ubyte[]) witness[i++]); |
| |
| assert(i == witness.length); |
| } |
| |
| // Note: This was documented until 2013/08 |
| /* |
| `Range` that locks the file and allows fast writing to it. |
| */ |
| struct LockingTextWriter |
| { |
| private: |
| import std.range.primitives : ElementType, isInfinite, isInputRange; |
| // Access the FILE* handle through the 'file_' member |
| // to keep the object alive through refcounting |
| File file_; |
| |
| // the unshared version of FILE* handle, extracted from the File object |
| @property _iobuf* handle_() @trusted { return cast(_iobuf*) file_._p.handle; } |
| |
| // the file's orientation (byte- or wide-oriented) |
| int orientation_; |
| |
| // Buffers for when we need to transcode. |
| wchar highSurrogate = '\0'; // '\0' indicates empty |
| void highSurrogateShouldBeEmpty() @safe |
| { |
| import std.utf : UTFException; |
| if (highSurrogate != '\0') |
| throw new UTFException("unpaired surrogate UTF-16 value"); |
| } |
| char[4] rbuf8; |
| size_t rbuf8Filled = 0; |
| public: |
| |
| this(ref File f) @trusted |
| { |
| import std.exception : enforce; |
| |
| enforce(f._p && f._p.handle, "Attempting to write to closed File"); |
| file_ = f; |
| FILE* fps = f._p.handle; |
| |
| version (MICROSOFT_STDIO) |
| { |
| // Microsoft doesn't implement fwide. Instead, there's the |
| // concept of ANSI/UNICODE mode. fputc doesn't work in UNICODE |
| // mode; fputwc has to be used. So that essentially means |
| // "wide-oriented" for us. |
| immutable int mode = __setmode(f.fileno, _O_TEXT); |
| // Set some arbitrary mode to obtain the previous one. |
| __setmode(f.fileno, mode); // Restore previous mode. |
| if (mode & (_O_WTEXT | _O_U16TEXT | _O_U8TEXT)) |
| { |
| orientation_ = 1; // wide |
| } |
| } |
| else |
| { |
| import core.stdc.wchar_ : fwide; |
| orientation_ = fwide(fps, 0); |
| } |
| |
| _FLOCK(fps); |
| } |
| |
| ~this() @trusted |
| { |
| if (auto p = file_._p) |
| { |
| if (p.handle) _FUNLOCK(p.handle); |
| } |
| file_ = File.init; |
| /* Destroy file_ before possibly throwing. Else it wouldn't be |
| destroyed, and its reference count would be wrong. */ |
| highSurrogateShouldBeEmpty(); |
| } |
| |
| this(this) @trusted |
| { |
| if (auto p = file_._p) |
| { |
| if (p.handle) _FLOCK(p.handle); |
| } |
| } |
| |
| /// Range primitive implementations. |
| void put(A)(scope A writeme) |
| if ((isSomeChar!(ElementType!A) || |
| is(ElementType!A : const(ubyte))) && |
| isInputRange!A && |
| !isInfinite!A) |
| { |
| import std.exception : errnoEnforce; |
| |
| alias C = ElementEncodingType!A; |
| static assert(!is(C == void)); |
| static if (isSomeString!A && C.sizeof == 1 || is(A : const(ubyte)[])) |
|