| // 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)[])) |
| { |
| if (orientation_ <= 0) |
| { |
| //file.write(writeme); causes infinite recursion!!! |
| //file.rawWrite(writeme); |
| auto result = trustedFwrite(file_._p.handle, writeme); |
| if (result != writeme.length) errnoEnforce(0); |
| return; |
| } |
| } |
| |
| // put each element in turn. |
| foreach (c; writeme) |
| { |
| put(c); |
| } |
| } |
| |
| /// ditto |
| void put(C)(scope C c) @safe if (isSomeChar!C || is(C : const(ubyte))) |
| { |
| import std.utf : decodeFront, encode, stride; |
| |
| static if (c.sizeof == 1) |
| { |
| highSurrogateShouldBeEmpty(); |
| if (orientation_ <= 0) trustedFPUTC(c, handle_); |
| else if (c <= 0x7F) trustedFPUTWC(c, handle_); |
| else if (c >= 0b1100_0000) // start byte of multibyte sequence |
| { |
| rbuf8[0] = c; |
| rbuf8Filled = 1; |
| } |
| else // continuation byte of multibyte sequence |
| { |
| rbuf8[rbuf8Filled] = c; |
| ++rbuf8Filled; |
| if (stride(rbuf8[]) == rbuf8Filled) // sequence is complete |
| { |
| char[] str = rbuf8[0 .. rbuf8Filled]; |
| immutable dchar d = decodeFront(str); |
| wchar_t[4 / wchar_t.sizeof] wbuf; |
| immutable size = encode(wbuf, d); |
| foreach (i; 0 .. size) |
| trustedFPUTWC(wbuf[i], handle_); |
| rbuf8Filled = 0; |
| } |
| } |
| } |
| else static if (c.sizeof == 2) |
| { |
| import std.utf : decode; |
| |
| if (c <= 0x7F) |
| { |
| highSurrogateShouldBeEmpty(); |
| if (orientation_ <= 0) trustedFPUTC(c, handle_); |
| else trustedFPUTWC(c, handle_); |
| } |
| else if (0xD800 <= c && c <= 0xDBFF) // high surrogate |
| { |
| highSurrogateShouldBeEmpty(); |
| highSurrogate = c; |
| } |
| else // standalone or low surrogate |
| { |
| dchar d = c; |
| if (highSurrogate != '\0') |
| { |
| immutable wchar[2] rbuf = [highSurrogate, c]; |
| size_t index = 0; |
| d = decode(rbuf[], index); |
| highSurrogate = 0; |
| } |
| if (orientation_ <= 0) |
| { |
| char[4] wbuf; |
| immutable size = encode(wbuf, d); |
| foreach (i; 0 .. size) |
| trustedFPUTC(wbuf[i], handle_); |
| } |
| else |
| { |
| wchar_t[4 / wchar_t.sizeof] wbuf; |
| immutable size = encode(wbuf, d); |
| foreach (i; 0 .. size) |
| trustedFPUTWC(wbuf[i], handle_); |
| } |
| rbuf8Filled = 0; |
| } |
| } |
| else // 32-bit characters |
| { |
| import std.utf : encode; |
| |
| highSurrogateShouldBeEmpty(); |
| if (orientation_ <= 0) |
| { |
| if (c <= 0x7F) |
| { |
| trustedFPUTC(c, handle_); |
| } |
| else |
| { |
| char[4] buf = void; |
| immutable len = encode(buf, c); |
| foreach (i ; 0 .. len) |
| trustedFPUTC(buf[i], handle_); |
| } |
| } |
| else |
| { |
| version (Windows) |
| { |
| import std.utf : isValidDchar; |
| |
| assert(isValidDchar(c)); |
| if (c <= 0xFFFF) |
| { |
| trustedFPUTWC(cast(wchar_t) c, handle_); |
| } |
| else |
| { |
| trustedFPUTWC(cast(wchar_t) |
| ((((c - 0x10000) >> 10) & 0x3FF) |
| + 0xD800), handle_); |
| trustedFPUTWC(cast(wchar_t) |
| (((c - 0x10000) & 0x3FF) + 0xDC00), |
| handle_); |
| } |
| } |
| else version (Posix) |
| { |
| trustedFPUTWC(cast(wchar_t) c, handle_); |
| } |
| else |
| { |
| static assert(0); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Output range which locks the file when created, and unlocks the file when it goes |
| * out of scope. |
| * |
| * Returns: An $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) |
| * which accepts string types, `ubyte[]`, individual character types, and |
| * individual `ubyte`s. |
| * |
| * Note: Writing either arrays of `char`s or `ubyte`s is faster than |
| * writing each character individually from a range. For large amounts of data, |
| * writing the contents in chunks using an intermediary array can result |
| * in a speed increase. |
| * |
| * Throws: $(REF UTFException, std, utf) if the data given is a `char` range |
| * and it contains malformed UTF data. |
| * |
| * See_Also: $(LREF byChunk) for an example. |
| */ |
| auto lockingTextWriter() @safe |
| { |
| return LockingTextWriter(this); |
| } |
| |
| // An output range which optionally locks the file and puts it into |
| // binary mode (similar to rawWrite). Because it needs to restore |
| // the file mode on destruction, it is RefCounted on Windows. |
| struct BinaryWriterImpl(bool locking) |
| { |
| import std.traits : hasIndirections; |
| private: |
| // Access the FILE* handle through the 'file_' member |
| // to keep the object alive through refcounting |
| File file_; |
| string name; |
| |
| version (Windows) |
| { |
| int fd, oldMode; |
| version (DIGITAL_MARS_STDIO) |
| ubyte oldInfo; |
| } |
| |
| public: |
| // Don't use this, but `File.lockingBinaryWriter()` instead. |
| // Must be public for RefCounted and emplace() in druntime. |
| this(scope ref File f) |
| { |
| import std.exception : enforce; |
| file_ = f; |
| enforce(f._p && f._p.handle); |
| name = f._name; |
| FILE* fps = f._p.handle; |
| static if (locking) |
| _FLOCK(fps); |
| |
| version (Windows) |
| { |
| .fflush(fps); // before changing translation mode |
| fd = .fileno(fps); |
| oldMode = .__setmode(fd, _O_BINARY); |
| version (DIGITAL_MARS_STDIO) |
| { |
| import core.atomic : atomicOp; |
| |
| // https://issues.dlang.org/show_bug.cgi?id=4243 |
| oldInfo = __fhnd_info[fd]; |
| atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); |
| } |
| } |
| } |
| |
| ~this() |
| { |
| if (!file_._p || !file_._p.handle) |
| return; |
| |
| FILE* fps = file_._p.handle; |
| |
| version (Windows) |
| { |
| .fflush(fps); // before restoring translation mode |
| version (DIGITAL_MARS_STDIO) |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=4243 |
| __fhnd_info[fd] = oldInfo; |
| } |
| .__setmode(fd, oldMode); |
| } |
| |
| _FUNLOCK(fps); |
| } |
| |
| void rawWrite(T)(in T[] buffer) |
| { |
| import std.conv : text; |
| import std.exception : errnoEnforce; |
| |
| auto result = trustedFwrite(file_._p.handle, buffer); |
| if (result == result.max) result = 0; |
| errnoEnforce(result == buffer.length, |
| text("Wrote ", result, " instead of ", buffer.length, |
| " objects of type ", T.stringof, " to file `", |
| name, "'")); |
| } |
| |
| version (Windows) |
| { |
| @disable this(this); |
| } |
| else |
| { |
| this(this) |
| { |
| if (auto p = file_._p) |
| { |
| if (p.handle) _FLOCK(p.handle); |
| } |
| } |
| } |
| |
| void put(T)(auto ref scope const T value) |
| if (!hasIndirections!T && |
| !isInputRange!T) |
| { |
| rawWrite((&value)[0 .. 1]); |
| } |
| |
| void put(T)(scope const(T)[] array) |
| if (!hasIndirections!T && |
| !isInputRange!T) |
| { |
| rawWrite(array); |
| } |
| } |
| |
| /** Returns an output range that locks the file and allows fast writing to it. |
| |
| Example: |
| Produce a grayscale image of the $(LINK2 https://en.wikipedia.org/wiki/Mandelbrot_set, Mandelbrot set) |
| in binary $(LINK2 https://en.wikipedia.org/wiki/Netpbm_format, Netpbm format) to standard output. |
| --- |
| import std.algorithm, std.complex, std.range, std.stdio; |
| |
| void main() |
| { |
| enum size = 500; |
| writef("P5\n%d %d %d\n", size, size, ubyte.max); |
| |
| iota(-1, 3, 2.0/size).map!(y => |
| iota(-1.5, 0.5, 2.0/size).map!(x => |
| cast(ubyte)(1+ |
| recurrence!((a, n) => x + y * complex(0, 1) + a[n-1]^^2)(complex(0)) |
| .take(ubyte.max) |
| .countUntil!(z => z.re^^2 + z.im^^2 > 4)) |
| ) |
| ) |
| .copy(stdout.lockingBinaryWriter); |
| } |
| --- |
| */ |
| auto lockingBinaryWriter() |
| { |
| alias LockingBinaryWriterImpl = BinaryWriterImpl!true; |
| |
| version (Windows) |
| { |
| import std.typecons : RefCounted; |
| alias LockingBinaryWriter = RefCounted!LockingBinaryWriterImpl; |
| } |
| else |
| alias LockingBinaryWriter = LockingBinaryWriterImpl; |
| |
| return LockingBinaryWriter(this); |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.mutation : reverse; |
| import std.exception : collectException; |
| static import std.file; |
| import std.range : only, retro; |
| import std.string : format; |
| |
| auto deleteme = testFilename(); |
| scope(exit) collectException(std.file.remove(deleteme)); |
| |
| { |
| auto writer = File(deleteme, "wb").lockingBinaryWriter(); |
| auto input = File(deleteme, "rb"); |
| |
| ubyte[1] byteIn = [42]; |
| writer.rawWrite(byteIn); |
| destroy(writer); |
| |
| ubyte[1] byteOut = input.rawRead(new ubyte[1]); |
| assert(byteIn[0] == byteOut[0]); |
| } |
| |
| auto output = File(deleteme, "wb"); |
| auto writer = output.lockingBinaryWriter(); |
| auto input = File(deleteme, "rb"); |
| |
| T[] readExact(T)(T[] buf) |
| { |
| auto result = input.rawRead(buf); |
| assert(result.length == buf.length, |
| "Read %d out of %d bytes" |
| .format(result.length, buf.length)); |
| return result; |
| } |
| |
| // test raw values |
| ubyte byteIn = 42; |
| byteIn.only.copy(writer); output.flush(); |
| ubyte byteOut = readExact(new ubyte[1])[0]; |
| assert(byteIn == byteOut); |
| |
| // test arrays |
| ubyte[] bytesIn = [1, 2, 3, 4, 5]; |
| bytesIn.copy(writer); output.flush(); |
| ubyte[] bytesOut = readExact(new ubyte[bytesIn.length]); |
| scope(failure) .writeln(bytesOut); |
| assert(bytesIn == bytesOut); |
| |
| // test ranges of values |
| bytesIn.retro.copy(writer); output.flush(); |
| bytesOut = readExact(bytesOut); |
| bytesOut.reverse(); |
| assert(bytesIn == bytesOut); |
| |
| // test string |
| "foobar".copy(writer); output.flush(); |
| char[] charsOut = readExact(new char[6]); |
| assert(charsOut == "foobar"); |
| |
| // test ranges of arrays |
| only("foo", "bar").copy(writer); output.flush(); |
| charsOut = readExact(charsOut); |
| assert(charsOut == "foobar"); |
| |
| // test that we are writing arrays as is, |
| // without UTF-8 transcoding |
| "foo"d.copy(writer); output.flush(); |
| dchar[] dcharsOut = readExact(new dchar[3]); |
| assert(dcharsOut == "foo"); |
| } |
| |
| /** Returns the size of the file in bytes, ulong.max if file is not searchable or throws if the operation fails. |
| Example: |
| --- |
| import std.stdio, std.file; |
| |
| void main() |
| { |
| string deleteme = "delete.me"; |
| auto file_handle = File(deleteme, "w"); |
| file_handle.write("abc"); //create temporary file |
| scope(exit) deleteme.remove; //remove temporary file at scope exit |
| |
| assert(file_handle.size() == 3); //check if file size is 3 bytes |
| } |
| --- |
| */ |
| @property ulong size() @safe |
| { |
| import std.exception : collectException; |
| |
| ulong pos = void; |
| if (collectException(pos = tell)) return ulong.max; |
| scope(exit) seek(pos); |
| seek(0, SEEK_END); |
| return tell; |
| } |
| } |
| |
| @system unittest |
| { |
| @system struct SystemToString |
| { |
| string toString() |
| { |
| return "system"; |
| } |
| } |
| |
| @trusted struct TrustedToString |
| { |
| string toString() |
| { |
| return "trusted"; |
| } |
| } |
| |
| @safe struct SafeToString |
| { |
| string toString() |
| { |
| return "safe"; |
| } |
| } |
| |
| @system void systemTests() |
| { |
| //system code can write to files/stdout with anything! |
| if (false) |
| { |
| auto f = File(); |
| |
| f.write("just a string"); |
| f.write("string with arg: ", 47); |
| f.write(SystemToString()); |
| f.write(TrustedToString()); |
| f.write(SafeToString()); |
| |
| write("just a string"); |
| write("string with arg: ", 47); |
| write(SystemToString()); |
| write(TrustedToString()); |
| write(SafeToString()); |
| |
| f.writeln("just a string"); |
| f.writeln("string with arg: ", 47); |
| f.writeln(SystemToString()); |
| f.writeln(TrustedToString()); |
| f.writeln(SafeToString()); |
| |
| writeln("just a string"); |
| writeln("string with arg: ", 47); |
| writeln(SystemToString()); |
| writeln(TrustedToString()); |
| writeln(SafeToString()); |
| |
| f.writef("string with arg: %s", 47); |
| f.writef("%s", SystemToString()); |
| f.writef("%s", TrustedToString()); |
| f.writef("%s", SafeToString()); |
| |
| writef("string with arg: %s", 47); |
| writef("%s", SystemToString()); |
| writef("%s", TrustedToString()); |
| writef("%s", SafeToString()); |
| |
| f.writefln("string with arg: %s", 47); |
| f.writefln("%s", SystemToString()); |
| f.writefln("%s", TrustedToString()); |
| f.writefln("%s", SafeToString()); |
| |
| writefln("string with arg: %s", 47); |
| writefln("%s", SystemToString()); |
| writefln("%s", TrustedToString()); |
| writefln("%s", SafeToString()); |
| } |
| } |
| |
| @safe void safeTests() |
| { |
| auto f = File(); |
| |
| //safe code can write to files only with @safe and @trusted code... |
| if (false) |
| { |
| f.write("just a string"); |
| f.write("string with arg: ", 47); |
| f.write(TrustedToString()); |
| f.write(SafeToString()); |
| |
| write("just a string"); |
| write("string with arg: ", 47); |
| write(TrustedToString()); |
| write(SafeToString()); |
| |
| f.writeln("just a string"); |
| f.writeln("string with arg: ", 47); |
| f.writeln(TrustedToString()); |
| f.writeln(SafeToString()); |
| |
| writeln("just a string"); |
| writeln("string with arg: ", 47); |
| writeln(TrustedToString()); |
| writeln(SafeToString()); |
| |
| f.writef("string with arg: %s", 47); |
| f.writef("%s", TrustedToString()); |
| f.writef("%s", SafeToString()); |
| |
| writef("string with arg: %s", 47); |
| writef("%s", TrustedToString()); |
| writef("%s", SafeToString()); |
| |
| f.writefln("string with arg: %s", 47); |
| f.writefln("%s", TrustedToString()); |
| f.writefln("%s", SafeToString()); |
| |
| writefln("string with arg: %s", 47); |
| writefln("%s", TrustedToString()); |
| writefln("%s", SafeToString()); |
| } |
| |
| static assert(!__traits(compiles, f.write(SystemToString().toString()))); |
| static assert(!__traits(compiles, f.writeln(SystemToString()))); |
| static assert(!__traits(compiles, f.writef("%s", SystemToString()))); |
| static assert(!__traits(compiles, f.writefln("%s", SystemToString()))); |
| |
| static assert(!__traits(compiles, write(SystemToString().toString()))); |
| static assert(!__traits(compiles, writeln(SystemToString()))); |
| static assert(!__traits(compiles, writef("%s", SystemToString()))); |
| static assert(!__traits(compiles, writefln("%s", SystemToString()))); |
| } |
| |
| systemTests(); |
| safeTests(); |
| } |
| |
| @safe unittest |
| { |
| import std.exception : collectException; |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| scope(exit) collectException(std.file.remove(deleteme)); |
| std.file.write(deleteme, "1 2 3"); |
| auto f = File(deleteme); |
| assert(f.size == 5); |
| assert(f.tell == 0); |
| } |
| |
| @system unittest |
| { |
| // @system due to readln |
| static import std.file; |
| import std.range : chain, only, repeat; |
| import std.range.primitives : isOutputRange; |
| |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| |
| { |
| auto writer = File(deleteme, "w").lockingTextWriter(); |
| static assert(isOutputRange!(typeof(writer), dchar)); |
| writer.put("日本語"); |
| writer.put("日本語"w); |
| writer.put("日本語"d); |
| writer.put('日'); |
| writer.put(chain(only('本'), only('語'))); |
| // https://issues.dlang.org/show_bug.cgi?id=11945 |
| writer.put(repeat('#', 12)); |
| // https://issues.dlang.org/show_bug.cgi?id=17229 |
| writer.put(cast(immutable(ubyte)[])"日本語"); |
| } |
| assert(File(deleteme).readln() == "日本語日本語日本語日本語############日本語"); |
| } |
| |
| @safe unittest // wchar -> char |
| { |
| static import std.file; |
| import std.exception : assertThrown; |
| import std.utf : UTFException; |
| |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| |
| { |
| auto writer = File(deleteme, "w").lockingTextWriter(); |
| writer.put("\U0001F608"w); |
| } |
| assert(std.file.readText!string(deleteme) == "\U0001F608"); |
| |
| // Test invalid input: unpaired high surrogate |
| { |
| immutable wchar surr = "\U0001F608"w[0]; |
| auto f = File(deleteme, "w"); |
| assertThrown!UTFException(() { |
| auto writer = f.lockingTextWriter(); |
| writer.put('x'); |
| writer.put(surr); |
| assertThrown!UTFException(writer.put(char('y'))); |
| assertThrown!UTFException(writer.put(wchar('y'))); |
| assertThrown!UTFException(writer.put(dchar('y'))); |
| assertThrown!UTFException(writer.put(surr)); |
| // First `surr` is still unpaired at this point. `writer` gets |
| // destroyed now, and the destructor throws a UTFException for |
| // the unpaired surrogate. |
| } ()); |
| } |
| assert(std.file.readText!string(deleteme) == "x"); |
| |
| // Test invalid input: unpaired low surrogate |
| { |
| immutable wchar surr = "\U0001F608"w[1]; |
| auto writer = File(deleteme, "w").lockingTextWriter(); |
| assertThrown!UTFException(writer.put(surr)); |
| writer.put('y'); |
| assertThrown!UTFException(writer.put(surr)); |
| } |
| assert(std.file.readText!string(deleteme) == "y"); |
| } |
| |
| @safe unittest // issue 18801 |
| { |
| static import std.file; |
| import std.string : stripLeft; |
| |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| |
| { |
| auto writer = File(deleteme, "w,ccs=UTF-8").lockingTextWriter(); |
| writer.put("foo"); |
| } |
| assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foo"); |
| |
| { |
| auto writer = File(deleteme, "a,ccs=UTF-8").lockingTextWriter(); |
| writer.put("bar"); |
| } |
| assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foobar"); |
| } |
| @safe unittest // char/wchar -> wchar_t |
| { |
| import core.stdc.locale : LC_CTYPE, setlocale; |
| import core.stdc.wchar_ : fwide; |
| import core.stdc.string : strlen; |
| import std.algorithm.searching : any, endsWith; |
| import std.conv : text; |
| import std.meta : AliasSeq; |
| import std.string : fromStringz, stripLeft; |
| static import std.file; |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| const char* oldCt = () @trusted { |
| const(char)* p = setlocale(LC_CTYPE, null); |
| // Subsequent calls to `setlocale` might invalidate this return value, |
| // so duplicate it. |
| // See: https://github.com/dlang/phobos/pull/7660 |
| return p ? p[0 .. strlen(p) + 1].idup.ptr : null; |
| }(); |
| const utf8 = ["en_US.UTF-8", "C.UTF-8", ".65001"].any!((loc) @trusted { |
| return setlocale(LC_CTYPE, loc.ptr).fromStringz.endsWith(loc); |
| }); |
| scope(exit) () @trusted { setlocale(LC_CTYPE, oldCt); } (); |
| version (DIGITAL_MARS_STDIO) // DM can't handle Unicode above U+07FF. |
| { |
| alias strs = AliasSeq!("xä\u07FE", "yö\u07FF"w); |
| } |
| else |
| { |
| alias strs = AliasSeq!("xä\U0001F607", "yö\U0001F608"w); |
| } |
| { |
| auto f = File(deleteme, "w"); |
| version (MICROSOFT_STDIO) |
| { |
| () @trusted { __setmode(fileno(f.getFP()), _O_U8TEXT); } (); |
| } |
| else |
| { |
| assert(fwide(f.getFP(), 1) == 1); |
| } |
| auto writer = f.lockingTextWriter(); |
| assert(writer.orientation_ == 1); |
| static foreach (s; strs) writer.put(s); |
| } |
| assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == |
| text(strs)); |
| } |
| @safe unittest // https://issues.dlang.org/show_bug.cgi?id=18789 |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| // converting to char |
| { |
| auto f = File(deleteme, "w"); |
| f.writeln("\U0001F608"w); // UTFException |
| } |
| // converting to wchar_t |
| { |
| auto f = File(deleteme, "w,ccs=UTF-16LE"); |
| // from char |
| f.writeln("ö"); // writes garbage |
| f.writeln("\U0001F608"); // ditto |
| // from wchar |
| f.writeln("\U0001F608"w); // leads to ErrnoException |
| } |
| } |
| |
| @safe unittest |
| { |
| import std.exception : collectException; |
| auto e = collectException({ File f; f.writeln("Hello!"); }()); |
| assert(e && e.msg == "Attempting to write to closed File"); |
| } |
| |
| @safe unittest // https://issues.dlang.org/show_bug.cgi?id=21592 |
| { |
| import std.exception : collectException; |
| import std.utf : UTFException; |
| static import std.file; |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| auto f = File(deleteme, "w"); |
| auto e = collectException!UTFException(f.writeln(wchar(0xD801))); |
| assert(e.next is null); |
| } |
| |
| version (StdStressTest) |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=15768 |
| @system unittest |
| { |
| import std.parallelism : parallel; |
| import std.range : iota; |
| |
| auto deleteme = testFilename(); |
| stderr = File(deleteme, "w"); |
| |
| foreach (t; 1_000_000.iota.parallel) |
| { |
| stderr.write("aaa"); |
| } |
| } |
| } |
| |
| /// Used to specify the lock type for `File.lock` and `File.tryLock`. |
| enum LockType |
| { |
| /** |
| * Specifies a _read (shared) lock. A _read lock denies all processes |
| * write access to the specified region of the file, including the |
| * process that first locks the region. All processes can _read the |
| * locked region. Multiple simultaneous _read locks are allowed, as |
| * long as there are no exclusive locks. |
| */ |
| read, |
| |
| /** |
| * Specifies a read/write (exclusive) lock. A read/write lock denies all |
| * other processes both read and write access to the locked file region. |
| * If a segment has an exclusive lock, it may not have any shared locks |
| * or other exclusive locks. |
| */ |
| readWrite |
| } |
| |
| struct LockingTextReader |
| { |
| private File _f; |
| private char _front; |
| private bool _hasChar; |
| |
| this(File f) |
| { |
| import std.exception : enforce; |
| enforce(f.isOpen, "LockingTextReader: File must be open"); |
| _f = f; |
| _FLOCK(_f._p.handle); |
| } |
| |
| this(this) |
| { |
| _FLOCK(_f._p.handle); |
| } |
| |
| ~this() |
| { |
| if (_hasChar) |
| ungetc(_front, cast(FILE*)_f._p.handle); |
| |
| // File locking has its own reference count |
| if (_f.isOpen) _FUNLOCK(_f._p.handle); |
| } |
| |
| void opAssign(LockingTextReader r) |
| { |
| import std.algorithm.mutation : swap; |
| swap(this, r); |
| } |
| |
| @property bool empty() |
| { |
| if (!_hasChar) |
| { |
| if (!_f.isOpen || _f.eof) |
| return true; |
| immutable int c = _FGETC(cast(_iobuf*) _f._p.handle); |
| if (c == EOF) |
| { |
| .destroy(_f); |
| return true; |
| } |
| _front = cast(char) c; |
| _hasChar = true; |
| } |
| return false; |
| } |
| |
| @property char front() |
| { |
| if (!_hasChar) |
| { |
| version (assert) |
| { |
| import core.exception : RangeError; |
| if (empty) |
| throw new RangeError(); |
| } |
| else |
| { |
| empty; |
| } |
| } |
| return _front; |
| } |
| |
| void popFront() |
| { |
| if (!_hasChar) |
| empty; |
| _hasChar = false; |
| } |
| } |
| |
| @system unittest |
| { |
| // @system due to readf |
| static import std.file; |
| import std.range.primitives : isInputRange; |
| |
| static assert(isInputRange!LockingTextReader); |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "1 2 3"); |
| scope(exit) std.file.remove(deleteme); |
| int x; |
| auto f = File(deleteme); |
| f.readf("%s ", &x); |
| assert(x == 1); |
| f.readf("%d ", &x); |
| assert(x == 2); |
| f.readf("%d ", &x); |
| assert(x == 3); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=13686 |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| static import std.file; |
| import std.utf : byDchar; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "Тест"); |
| scope(exit) std.file.remove(deleteme); |
| |
| string s; |
| File(deleteme).readf("%s", &s); |
| assert(s == "Тест"); |
| |
| auto ltr = LockingTextReader(File(deleteme)).byDchar; |
| assert(equal(ltr, "Тест".byDchar)); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=12320 |
| @system unittest |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "ab"); |
| scope(exit) std.file.remove(deleteme); |
| auto ltr = LockingTextReader(File(deleteme)); |
| assert(ltr.front == 'a'); |
| ltr.popFront(); |
| assert(ltr.front == 'b'); |
| ltr.popFront(); |
| assert(ltr.empty); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=14861 |
| @system unittest |
| { |
| // @system due to readf |
| static import std.file; |
| auto deleteme = testFilename(); |
| File fw = File(deleteme, "w"); |
| for (int i; i != 5000; i++) |
| fw.writeln(i, ";", "Иванов;Пётр;Петрович"); |
| fw.close(); |
| scope(exit) std.file.remove(deleteme); |
| // Test read |
| File fr = File(deleteme, "r"); |
| scope (exit) fr.close(); |
| int nom; string fam, nam, ot; |
| // Error format read |
| while (!fr.eof) |
| fr.readf("%s;%s;%s;%s\n", &nom, &fam, &nam, &ot); |
| } |
| |
| /** |
| * Indicates whether `T` is a file handle, i.e. the type |
| * is implicitly convertable to $(LREF File) or a pointer to a |
| * $(REF FILE, core,stdc,stdio). |
| * |
| * Returns: |
| * `true` if `T` is a file handle, `false` otherwise. |
| */ |
| template isFileHandle(T) |
| { |
| enum isFileHandle = is(T : FILE*) || |
| is(T : File); |
| } |
| |
| /// |
| @safe unittest |
| { |
| static assert(isFileHandle!(FILE*)); |
| static assert(isFileHandle!(File)); |
| } |
| |
| /** |
| * Property used by writeln/etc. so it can infer @safe since stdout is __gshared |
| */ |
| private @property File trustedStdout() @trusted |
| { |
| return stdout; |
| } |
| |
| /*********************************** |
| Writes its arguments in text format to standard output (without a trailing newline). |
| |
| Params: |
| args = the items to write to `stdout` |
| |
| Throws: In case of an I/O error, throws an `StdioException`. |
| |
| Example: |
| Reads `stdin` and writes it to `stdout` with an argument |
| counter. |
| --- |
| import std.stdio; |
| |
| void main() |
| { |
| string line; |
| |
| for (size_t count = 0; (line = readln) !is null; count++) |
| { |
| write("Input ", count, ": ", line, "\n"); |
| } |
| } |
| --- |
| */ |
| void write(T...)(T args) |
| if (!is(T[0] : File)) |
| { |
| trustedStdout.write(args); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| void[] buf; |
| if (false) write(buf); |
| // test write |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| f.write("Hello, ", "world number ", 42, "!"); |
| f.close(); |
| scope(exit) { std.file.remove(deleteme); } |
| assert(cast(char[]) std.file.read(deleteme) == "Hello, world number 42!"); |
| } |
| |
| /*********************************** |
| * Equivalent to `write(args, '\n')`. Calling `writeln` without |
| * arguments is valid and just prints a newline to the standard |
| * output. |
| * |
| * Params: |
| * args = the items to write to `stdout` |
| * |
| * Throws: |
| * In case of an I/O error, throws an $(LREF StdioException). |
| * Example: |
| * Reads `stdin` and writes it to `stdout` with an argument |
| * counter. |
| --- |
| import std.stdio; |
| |
| void main() |
| { |
| string line; |
| |
| for (size_t count = 0; (line = readln) !is null; count++) |
| { |
| writeln("Input ", count, ": ", line); |
| } |
| } |
| --- |
| */ |
| void writeln(T...)(T args) |
| { |
| static if (T.length == 0) |
| { |
| import std.exception : enforce; |
| |
| enforce(fputc('\n', .trustedStdout._p.handle) != EOF, "fputc failed"); |
| } |
| else static if (T.length == 1 && |
| is(T[0] : const(char)[]) && |
| (is(T[0] == U[], U) || __traits(isStaticArray, T[0]))) |
| { |
| // Specialization for strings - a very frequent case |
| auto w = .trustedStdout.lockingTextWriter(); |
| |
| static if (__traits(isStaticArray, T[0])) |
| { |
| w.put(args[0][]); |
| } |
| else |
| { |
| w.put(args[0]); |
| } |
| w.put('\n'); |
| } |
| else |
| { |
| // Most general instance |
| trustedStdout.write(args, '\n'); |
| } |
| } |
| |
| @safe unittest |
| { |
| // Just make sure the call compiles |
| if (false) writeln(); |
| |
| if (false) writeln("wyda"); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=8040 |
| if (false) writeln(null); |
| if (false) writeln(">", null, "<"); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=14041 |
| if (false) |
| { |
| char[8] a; |
| writeln(a); |
| immutable b = a; |
| b.writeln; |
| const c = a[]; |
| c.writeln; |
| } |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| // test writeln |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) { std.file.remove(deleteme); } |
| f.writeln("Hello, ", "world number ", 42, "!"); |
| f.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, world number 42!\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, world number 42!\n"); |
| |
| // test writeln on stdout |
| auto saveStdout = stdout; |
| scope(exit) stdout = saveStdout; |
| stdout.open(deleteme, "w"); |
| writeln("Hello, ", "world number ", 42, "!"); |
| stdout.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, world number 42!\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, world number 42!\n"); |
| |
| stdout.open(deleteme, "w"); |
| writeln("Hello!"c); |
| writeln("Hello!"w); // https://issues.dlang.org/show_bug.cgi?id=8386 |
| writeln("Hello!"d); // https://issues.dlang.org/show_bug.cgi?id=8386 |
| writeln("embedded\0null"c); // https://issues.dlang.org/show_bug.cgi?id=8730 |
| stdout.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello!\r\nHello!\r\nHello!\r\nembedded\0null\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello!\nHello!\nHello!\nembedded\0null\n"); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) { std.file.remove(deleteme); } |
| |
| enum EI : int { A, B } |
| enum ED : double { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle |
| enum EC : char { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle |
| enum ES : string { A = "aaa", B = "bbb" } |
| |
| f.writeln(EI.A); // false, but A on 2.058 |
| f.writeln(EI.B); // true, but B on 2.058 |
| |
| f.writeln(ED.A); // A |
| f.writeln(ED.B); // B |
| |
| f.writeln(EC.A); // A |
| f.writeln(EC.B); // B |
| |
| f.writeln(ES.A); // A |
| f.writeln(ES.B); // B |
| |
| f.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "A\r\nB\r\nA\r\nB\r\nA\r\nB\r\nA\r\nB\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "A\nB\nA\nB\nA\nB\nA\nB\n"); |
| } |
| |
| @system unittest |
| { |
| static auto useInit(T)(T ltw) |
| { |
| T val; |
| val = ltw; |
| val = T.init; |
| return val; |
| } |
| useInit(stdout.lockingTextWriter()); |
| } |
| |
| @system unittest |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=21920 |
| void function(string) printer = &writeln!string; |
| if (false) printer("Hello"); |
| } |
| |
| |
| /*********************************** |
| Writes formatted data to standard output (without a trailing newline). |
| |
| Params: |
| fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format). |
| When passed as a compile-time argument, the string will be statically checked |
| against the argument types passed. |
| args = Items to write. |
| |
| Note: In older versions of Phobos, it used to be possible to write: |
| |
| ------ |
| writef(stderr, "%s", "message"); |
| ------ |
| |
| to print a message to `stderr`. This syntax is no longer supported, and has |
| been superceded by: |
| |
| ------ |
| stderr.writef("%s", "message"); |
| ------ |
| |
| */ |
| void writef(alias fmt, A...)(A args) |
| if (isSomeString!(typeof(fmt))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(fmt, A); |
| static assert(!e, e); |
| return .writef(fmt, args); |
| } |
| |
| /// ditto |
| void writef(Char, A...)(in Char[] fmt, A args) |
| { |
| trustedStdout.writef(fmt, args); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| // test writef |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) { std.file.remove(deleteme); } |
| f.writef!"Hello, %s world number %s!"("nice", 42); |
| f.close(); |
| assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); |
| // test write on stdout |
| auto saveStdout = stdout; |
| scope(exit) stdout = saveStdout; |
| stdout.open(deleteme, "w"); |
| writef!"Hello, %s world number %s!"("nice", 42); |
| stdout.close(); |
| assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); |
| } |
| |
| /*********************************** |
| * Equivalent to $(D writef(fmt, args, '\n')). |
| */ |
| void writefln(alias fmt, A...)(A args) |
| if (isSomeString!(typeof(fmt))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(fmt, A); |
| static assert(!e, e); |
| return .writefln(fmt, args); |
| } |
| |
| /// ditto |
| void writefln(Char, A...)(in Char[] fmt, A args) |
| { |
| trustedStdout.writefln(fmt, args); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| // test File.writefln |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) { std.file.remove(deleteme); } |
| f.writefln!"Hello, %s world number %s!"("nice", 42); |
| f.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, nice world number 42!\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, nice world number 42!\n", |
| cast(char[]) std.file.read(deleteme)); |
| |
| // test writefln |
| auto saveStdout = stdout; |
| scope(exit) stdout = saveStdout; |
| stdout.open(deleteme, "w"); |
| writefln!"Hello, %s world number %s!"("nice", 42); |
| stdout.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, nice world number 42!\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, nice world number 42!\n"); |
| } |
| |
| /** |
| * Reads formatted data from `stdin` using $(REF formattedRead, std,_format). |
| * Params: |
| * format = The $(REF_ALTTEXT format string, formattedWrite, std, _format). |
| * When passed as a compile-time argument, the string will be statically checked |
| * against the argument types passed. |
| * args = Items to be read. |
| * Returns: |
| * Same as `formattedRead`: The number of variables filled. If the input range `r` ends early, |
| * this number will be less than the number of variables provided. |
| * Example: |
| ---- |
| // test.d |
| void main() |
| { |
| import std.stdio; |
| foreach (_; 0 .. 3) |
| { |
| int a; |
| readf!" %d"(a); |
| writeln(++a); |
| } |
| } |
| ---- |
| $(CONSOLE |
| % echo "1 2 3" | rdmd test.d |
| 2 |
| 3 |
| 4 |
| ) |
| */ |
| uint readf(alias format, A...)(auto ref A args) |
| if (isSomeString!(typeof(format))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(format, A); |
| static assert(!e, e); |
| return .readf(format, args); |
| } |
| |
| /// ditto |
| uint readf(A...)(scope const(char)[] format, auto ref A args) |
| { |
| return stdin.readf(format, args); |
| } |
| |
| @system unittest |
| { |
| float f; |
| if (false) readf("%s", &f); |
| |
| char a; |
| wchar b; |
| dchar c; |
| if (false) readf("%s %s %s", a, b, c); |
| // backwards compatibility with pointers |
| if (false) readf("%s %s %s", a, &b, c); |
| if (false) readf("%s %s %s", &a, &b, &c); |
| } |
| |
| /********************************** |
| * Read line from `stdin`. |
| * |
| * This version manages its own read buffer, which means one memory allocation per call. If you are not |
| * retaining a reference to the read data, consider the `readln(buf)` version, which may offer |
| * better performance as it can reuse its read buffer. |
| * |
| * Returns: |
| * The line that was read, including the line terminator character. |
| * Params: |
| * S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`. |
| * terminator = Line terminator (by default, `'\n'`). |
| * Note: |
| * String terminators are not supported due to ambiguity with readln(buf) below. |
| * Throws: |
| * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. |
| * Example: |
| * Reads `stdin` and writes it to `stdout`. |
| --- |
| import std.stdio; |
| |
| void main() |
| { |
| string line; |
| while ((line = readln()) !is null) |
| write(line); |
| } |
| --- |
| */ |
| S readln(S = string)(dchar terminator = '\n') |
| if (isSomeString!S) |
| { |
| return stdin.readln!S(terminator); |
| } |
| |
| /********************************** |
| * Read line from `stdin` and write it to buf[], including terminating character. |
| * |
| * This can be faster than $(D line = readln()) because you can reuse |
| * the buffer for each call. Note that reusing the buffer means that you |
| * must copy the previous contents if you wish to retain them. |
| * |
| * Returns: |
| * `size_t` 0 for end of file, otherwise number of characters read |
| * Params: |
| * buf = Buffer used to store the resulting line data. buf is resized as necessary. |
| * terminator = Line terminator (by default, `'\n'`). Use $(REF newline, std,ascii) |
| * for portability (unless the file was opened in text mode). |
| * Throws: |
| * `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error. |
| * Example: |
| * Reads `stdin` and writes it to `stdout`. |
| --- |
| import std.stdio; |
| |
| void main() |
| { |
| char[] buf; |
| while (readln(buf)) |
| write(buf); |
| } |
| --- |
| */ |
| size_t readln(C)(ref C[] buf, dchar terminator = '\n') |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) |
| { |
| return stdin.readln(buf, terminator); |
| } |
| |
| /** ditto */ |
| size_t readln(C, R)(ref C[] buf, R terminator) |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && |
| isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) |
| { |
| return stdin.readln(buf, terminator); |
| } |
| |
| @safe unittest |
| { |
| import std.meta : AliasSeq; |
| |
| //we can't actually test readln, so at the very least, |
| //we test compilability |
| void foo() |
| { |
| readln(); |
| readln('\t'); |
| static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) |
| { |
| readln!String(); |
| readln!String('\t'); |
| } |
| static foreach (String; AliasSeq!(char[], wchar[], dchar[])) |
| {{ |
| String buf; |
| readln(buf); |
| readln(buf, '\t'); |
| readln(buf, "<br />"); |
| }} |
| } |
| } |
| |
| /* |
| * Convenience function that forwards to `core.sys.posix.stdio.fopen` |
| * (to `_wfopen` on Windows) |
| * with appropriately-constructed C-style strings. |
| */ |
| private FILE* _fopen(R1, R2)(R1 name, R2 mode = "r") |
| if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) && |
| (isSomeFiniteCharInputRange!R2 || isSomeString!R2)) |
| { |
| import std.internal.cstring : tempCString; |
| |
| auto namez = name.tempCString!FSChar(); |
| auto modez = mode.tempCString!FSChar(); |
| |
| static _fopenImpl(scope const(FSChar)* namez, scope const(FSChar)* modez) @trusted nothrow @nogc |
| { |
| version (Windows) |
| { |
| return _wfopen(namez, modez); |
| } |
| else version (Posix) |
| { |
| /* |
| * The new opengroup large file support API is transparently |
| * included in the normal C bindings. http://opengroup.org/platform/lfs.html#1.0 |
| * if _FILE_OFFSET_BITS in druntime is 64, off_t is 64 bit and |
| * the normal functions work fine. If not, then large file support |
| * probably isn't available. Do not use the old transitional API |
| * (the native extern(C) fopen64, http://www.unix.org/version2/whatsnew/lfs20mar.html#3.0) |
| */ |
| import core.sys.posix.stdio : fopen; |
| return fopen(namez, modez); |
| } |
| else |
| { |
| return fopen(namez, modez); |
| } |
| } |
| return _fopenImpl(namez, modez); |
| } |
| |
| version (Posix) |
| { |
| /*********************************** |
| * Convenience function that forwards to `core.sys.posix.stdio.popen` |
| * with appropriately-constructed C-style strings. |
| */ |
| FILE* _popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc |
| if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) && |
| (isSomeFiniteCharInputRange!R2 || isSomeString!R2)) |
| { |
| import std.internal.cstring : tempCString; |
| |
| auto namez = name.tempCString!FSChar(); |
| auto modez = mode.tempCString!FSChar(); |
| |
| static popenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc |
| { |
| import core.sys.posix.stdio : popen; |
| return popen(namez, modez); |
| } |
| return popenImpl(namez, modez); |
| } |
| } |
| |
| /* |
| * Convenience function that forwards to `core.stdc.stdio.fwrite` |
| */ |
| private auto trustedFwrite(T)(FILE* f, const T[] obj) @trusted |
| { |
| return fwrite(obj.ptr, T.sizeof, obj.length, f); |
| } |
| |
| /* |
| * Convenience function that forwards to `core.stdc.stdio.fread` |
| */ |
| private auto trustedFread(T)(FILE* f, T[] obj) @trusted |
| { |
| return fread(obj.ptr, T.sizeof, obj.length, f); |
| } |
| |
| /** |
| * Iterates through the lines of a file by using `foreach`. |
| * |
| * Example: |
| * |
| --------- |
| void main() |
| { |
| foreach (string line; lines(stdin)) |
| { |
| ... use line ... |
| } |
| } |
| --------- |
| The line terminator (`'\n'` by default) is part of the string read (it |
| could be missing in the last line of the file). Several types are |
| supported for `line`, and the behavior of `lines` |
| changes accordingly: |
| |
| $(OL $(LI If `line` has type `string`, $(D |
| wstring), or `dstring`, a new string of the respective type |
| is allocated every read.) $(LI If `line` has type $(D |
| char[]), `wchar[]`, `dchar[]`, the line's content |
| will be reused (overwritten) across reads.) $(LI If `line` |
| has type `immutable(ubyte)[]`, the behavior is similar to |
| case (1), except that no UTF checking is attempted upon input.) $(LI |
| If `line` has type `ubyte[]`, the behavior is |
| similar to case (2), except that no UTF checking is attempted upon |
| input.)) |
| |
| In all cases, a two-symbols versions is also accepted, in which case |
| the first symbol (of integral type, e.g. `ulong` or $(D |
| uint)) tracks the zero-based number of the current line. |
| |
| Example: |
| ---- |
| foreach (ulong i, string line; lines(stdin)) |
| { |
| ... use line ... |
| } |
| ---- |
| |
| In case of an I/O error, an `StdioException` is thrown. |
| |
| See_Also: |
| $(LREF byLine) |
| */ |
| |
| struct lines |
| { |
| private File f; |
| private dchar terminator = '\n'; |
| |
| /** |
| Constructor. |
| Params: |
| f = File to read lines from. |
| terminator = Line separator (`'\n'` by default). |
| */ |
| this(File f, dchar terminator = '\n') |
| { |
| this.f = f; |
| this.terminator = terminator; |
| } |
| |
| int opApply(D)(scope D dg) |
| { |
| import std.traits : Parameters; |
| alias Parms = Parameters!(dg); |
| static if (isSomeString!(Parms[$ - 1])) |
| { |
| int result = 0; |
| static if (is(Parms[$ - 1] : const(char)[])) |
| alias C = char; |
| else static if (is(Parms[$ - 1] : const(wchar)[])) |
| alias C = wchar; |
| else static if (is(Parms[$ - 1] : const(dchar)[])) |
| alias C = dchar; |
| C[] line; |
| static if (Parms.length == 2) |
| Parms[0] i = 0; |
| for (;;) |
| { |
| import std.conv : to; |
| |
| if (!f.readln(line, terminator)) break; |
| auto copy = to!(Parms[$ - 1])(line); |
| static if (Parms.length == 2) |
| { |
| result = dg(i, copy); |
| ++i; |
| } |
| else |
| { |
| result = dg(copy); |
| } |
| if (result != 0) break; |
| } |
| return result; |
| } |
| else |
| { |
| // raw read |
| return opApplyRaw(dg); |
| } |
| } |
| // no UTF checking |
| int opApplyRaw(D)(scope D dg) |
| { |
| import std.conv : to; |
| import std.exception : assumeUnique; |
| import std.traits : Parameters; |
| |
| alias Parms = Parameters!(dg); |
| enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]); |
| int result = 1; |
| int c = void; |
| _FLOCK(f._p.handle); |
| scope(exit) _FUNLOCK(f._p.handle); |
| ubyte[] buffer; |
| static if (Parms.length == 2) |
| Parms[0] line = 0; |
| while ((c = _FGETC(cast(_iobuf*) f._p.handle)) != -1) |
| { |
| buffer ~= to!(ubyte)(c); |
| if (c == terminator) |
| { |
| static if (duplicate) |
| auto arg = assumeUnique(buffer); |
| else |
| alias arg = buffer; |
| // unlock the file while calling the delegate |
| _FUNLOCK(f._p.handle); |
| scope(exit) _FLOCK(f._p.handle); |
| static if (Parms.length == 1) |
| { |
| result = dg(arg); |
| } |
| else |
| { |
| result = dg(line, arg); |
| ++line; |
| } |
| if (result) break; |
| static if (!duplicate) |
| buffer.length = 0; |
| } |
| } |
| // can only reach when _FGETC returned -1 |
| if (!f.eof) throw new StdioException("Error in reading file"); // error occured |
| return result; |
| } |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| import std.meta : AliasSeq; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| auto deleteme = testFilename(); |
| scope(exit) { std.file.remove(deleteme); } |
| |
| alias TestedWith = |
| AliasSeq!(string, wstring, dstring, |
| char[], wchar[], dchar[]); |
| foreach (T; TestedWith) |
| { |
| // test looping with an empty file |
| std.file.write(deleteme, ""); |
| auto f = File(deleteme, "r"); |
| foreach (T line; lines(f)) |
| { |
| assert(false); |
| } |
| f.close(); |
| |
| // test looping with a file with three lines |
| std.file.write(deleteme, "Line one\nline two\nline three\n"); |
| f.open(deleteme, "r"); |
| uint i = 0; |
| foreach (T line; lines(f)) |
| { |
| if (i == 0) assert(line == "Line one\n"); |
| else if (i == 1) assert(line == "line two\n"); |
| else if (i == 2) assert(line == "line three\n"); |
| else assert(false); |
| ++i; |
| } |
| f.close(); |
| |
| // test looping with a file with three lines, last without a newline |
| std.file.write(deleteme, "Line one\nline two\nline three"); |
| f.open(deleteme, "r"); |
| i = 0; |
| foreach (T line; lines(f)) |
| { |
| if (i == 0) assert(line == "Line one\n"); |
| else if (i == 1) assert(line == "line two\n"); |
| else if (i == 2) assert(line == "line three"); |
| else assert(false); |
| ++i; |
| } |
| f.close(); |
| } |
| |
| // test with ubyte[] inputs |
| alias TestedWith2 = AliasSeq!(immutable(ubyte)[], ubyte[]); |
| foreach (T; TestedWith2) |
| { |
| // test looping with an empty file |
| std.file.write(deleteme, ""); |
| auto f = File(deleteme, "r"); |
| foreach (T line; lines(f)) |
| { |
| assert(false); |
| } |
| f.close(); |
| |
| // test looping with a file with three lines |
| std.file.write(deleteme, "Line one\nline two\nline three\n"); |
| f.open(deleteme, "r"); |
| uint i = 0; |
| foreach (T line; lines(f)) |
| { |
| if (i == 0) assert(cast(char[]) line == "Line one\n"); |
| else if (i == 1) assert(cast(char[]) line == "line two\n", |
| T.stringof ~ " " ~ cast(char[]) line); |
| else if (i == 2) assert(cast(char[]) line == "line three\n"); |
| else assert(false); |
| ++i; |
| } |
| f.close(); |
| |
| // test looping with a file with three lines, last without a newline |
| std.file.write(deleteme, "Line one\nline two\nline three"); |
| f.open(deleteme, "r"); |
| i = 0; |
| foreach (T line; lines(f)) |
| { |
| if (i == 0) assert(cast(char[]) line == "Line one\n"); |
| else if (i == 1) assert(cast(char[]) line == "line two\n"); |
| else if (i == 2) assert(cast(char[]) line == "line three"); |
| else assert(false); |
| ++i; |
| } |
| f.close(); |
| |
| } |
| |
| static foreach (T; AliasSeq!(ubyte[])) |
| { |
| // test looping with a file with three lines, last without a newline |
| // using a counter too this time |
| std.file.write(deleteme, "Line one\nline two\nline three"); |
| auto f = File(deleteme, "r"); |
| uint i = 0; |
| foreach (ulong j, T line; lines(f)) |
| { |
| if (i == 0) assert(cast(char[]) line == "Line one\n"); |
| else if (i == 1) assert(cast(char[]) line == "line two\n"); |
| else if (i == 2) assert(cast(char[]) line == "line three"); |
| else assert(false); |
| ++i; |
| } |
| f.close(); |
| } |
| } |
| |
| /** |
| Iterates through a file a chunk at a time by using `foreach`. |
| |
| Example: |
| |
| --------- |
| void main() |
| { |
| foreach (ubyte[] buffer; chunks(stdin, 4096)) |
| { |
| ... use buffer ... |
| } |
| } |
| --------- |
| |
| The content of `buffer` is reused across calls. In the |
| example above, `buffer.length` is 4096 for all iterations, |
| except for the last one, in which case `buffer.length` may |
| be less than 4096 (but always greater than zero). |
| |
| In case of an I/O error, an `StdioException` is thrown. |
| */ |
| auto chunks(File f, size_t size) |
| { |
| return ChunksImpl(f, size); |
| } |
| private struct ChunksImpl |
| { |
| private File f; |
| private size_t size; |
| // private string fileName; // Currently, no use |
| |
| this(File f, size_t size) |
| in |
| { |
| assert(size, "size must be larger than 0"); |
| } |
| do |
| { |
| this.f = f; |
| this.size = size; |
| } |
| |
| int opApply(D)(scope D dg) |
| { |
| import core.stdc.stdlib : alloca; |
| import std.exception : enforce; |
| |
| enforce(f.isOpen, "Attempting to read from an unopened file"); |
| enum maxStackSize = 1024 * 16; |
| ubyte[] buffer = void; |
| if (size < maxStackSize) |
| buffer = (cast(ubyte*) alloca(size))[0 .. size]; |
| else |
| buffer = new ubyte[size]; |
| size_t r = void; |
| int result = 1; |
| uint tally = 0; |
| while ((r = trustedFread(f._p.handle, buffer)) > 0) |
| { |
| assert(r <= size); |
| if (r != size) |
| { |
| // error occured |
| if (!f.eof) throw new StdioException(null); |
| buffer.length = r; |
| } |
| static if (is(typeof(dg(tally, buffer)))) |
| { |
| if ((result = dg(tally, buffer)) != 0) break; |
| } |
| else |
| { |
| if ((result = dg(buffer)) != 0) break; |
| } |
| ++tally; |
| } |
| return result; |
| } |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| auto deleteme = testFilename(); |
| scope(exit) { std.file.remove(deleteme); } |
| |
| // test looping with an empty file |
| std.file.write(deleteme, ""); |
| auto f = File(deleteme, "r"); |
| foreach (ubyte[] line; chunks(f, 4)) |
| { |
| assert(false); |
| } |
| f.close(); |
| |
| // test looping with a file with three lines |
| std.file.write(deleteme, "Line one\nline two\nline three\n"); |
| f = File(deleteme, "r"); |
| uint i = 0; |
| foreach (ubyte[] line; chunks(f, 3)) |
| { |
| if (i == 0) assert(cast(char[]) line == "Lin"); |
| else if (i == 1) assert(cast(char[]) line == "e o"); |
| else if (i == 2) assert(cast(char[]) line == "ne\n"); |
| else break; |
| ++i; |
| } |
| f.close(); |
| } |
| |
| // Issue 21730 - null ptr dereferenced in ChunksImpl.opApply (SIGSEGV) |
| @system unittest |
| { |
| import std.exception : assertThrown; |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| scope(exit) { if (std.file.exists(deleteme)) std.file.remove(deleteme); } |
| |
| auto err1 = File(deleteme, "w+x"); |
| err1.close; |
| std.file.remove(deleteme); |
| assertThrown(() {foreach (ubyte[] buf; chunks(err1, 4096)) {}}()); |
| } |
| |
| /** |
| Writes an array or range to a file. |
| Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)). |
| Similar to $(REF write, std,file), strings are written as-is, |
| rather than encoded according to the `File`'s $(HTTP |
| en.cppreference.com/w/c/io#Narrow_and_wide_orientation, |
| orientation). |
| */ |
| void toFile(T)(T data, string fileName) |
| if (is(typeof(copy(data, stdout.lockingBinaryWriter)))) |
| { |
| copy(data, File(fileName, "wb").lockingBinaryWriter); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| scope(exit) { std.file.remove(deleteme); } |
| |
| "Test".toFile(deleteme); |
| assert(std.file.readText(deleteme) == "Test"); |
| } |
| |
| /********************* |
| * Thrown if I/O errors happen. |
| */ |
| class StdioException : Exception |
| { |
| static import core.stdc.errno; |
| /// Operating system error code. |
| uint errno; |
| |
| /** |
| Initialize with a message and an error code. |
| */ |
| this(string message, uint e = core.stdc.errno.errno) @trusted |
| { |
| import std.exception : errnoString; |
| errno = e; |
| auto sysmsg = errnoString(errno); |
| // If e is 0, we don't use the system error message. (The message |
| // is "Success", which is rather pointless for an exception.) |
| super(e == 0 ? message |
| : (message ? message ~ " (" ~ sysmsg ~ ")" : sysmsg)); |
| } |
| |
| /** Convenience functions that throw an `StdioException`. */ |
| static void opCall(string msg) |
| { |
| throw new StdioException(msg); |
| } |
| |
| /// ditto |
| static void opCall() |
| { |
| throw new StdioException(null, core.stdc.errno.errno); |
| } |
| } |
| |
| enum StdFileHandle: string |
| { |
| stdin = "core.stdc.stdio.stdin", |
| stdout = "core.stdc.stdio.stdout", |
| stderr = "core.stdc.stdio.stderr", |
| } |
| |
| // Undocumented but public because the std* handles are aliasing it. |
| @property ref File makeGlobal(StdFileHandle _iob)() |
| { |
| __gshared File.Impl impl; |
| __gshared File result; |
| |
| // Use an inline spinlock to make sure the initializer is only run once. |
| // We assume there will be at most uint.max / 2 threads trying to initialize |
| // `handle` at once and steal the high bit to indicate that the globals have |
| // been initialized. |
| static shared uint spinlock; |
| import core.atomic : atomicLoad, atomicOp, MemoryOrder; |
| if (atomicLoad!(MemoryOrder.acq)(spinlock) <= uint.max / 2) |
| { |
| for (;;) |
| { |
| if (atomicLoad!(MemoryOrder.acq)(spinlock) > uint.max / 2) |
| break; |
| if (atomicOp!"+="(spinlock, 1) == 1) |
| { |
| with (StdFileHandle) |
| assert(_iob == stdin || _iob == stdout || _iob == stderr); |
| impl.handle = mixin(_iob); |
| result._p = &impl; |
| atomicOp!"+="(spinlock, uint.max / 2); |
| break; |
| } |
| atomicOp!"-="(spinlock, 1); |
| } |
| } |
| return result; |
| } |
| |
| /** The standard input stream. |
| |
| Returns: |
| stdin as a $(LREF File). |
| |
| Note: |
| The returned $(LREF File) wraps $(REF stdin,core,stdc,stdio), and |
| is therefore thread global. Reassigning `stdin` to a different |
| `File` must be done in a single-threaded or locked context in |
| order to avoid race conditions. |
| |
| All reading from `stdin` automatically locks the file globally, |
| and will cause all other threads calling `read` to wait until |
| the lock is released. |
| */ |
| alias stdin = makeGlobal!(StdFileHandle.stdin); |
| |
| /// |
| @safe unittest |
| { |
| // Read stdin, sort lines, write to stdout |
| import std.algorithm.mutation : copy; |
| import std.algorithm.sorting : sort; |
| import std.array : array; |
| import std.typecons : Yes; |
| |
| void main() |
| { |
| stdin // read from stdin |
| .byLineCopy(Yes.keepTerminator) // copying each line |
| .array() // convert to array of lines |
| .sort() // sort the lines |
| .copy( // copy output of .sort to an OutputRange |
| stdout.lockingTextWriter()); // the OutputRange |
| } |
| } |
| |
| /** |
| The standard output stream. |
| |
| Returns: |
| stdout as a $(LREF File). |
| |
| Note: |
| The returned $(LREF File) wraps $(REF stdout,core,stdc,stdio), and |
| is therefore thread global. Reassigning `stdout` to a different |
| `File` must be done in a single-threaded or locked context in |
| order to avoid race conditions. |
| |
| All writing to `stdout` automatically locks the file globally, |
| and will cause all other threads calling `write` to wait until |
| the lock is released. |
| */ |
| alias stdout = makeGlobal!(StdFileHandle.stdout); |
| |
| /// |
| @safe unittest |
| { |
| void main() |
| { |
| stdout.writeln("Write a message to stdout."); |
| } |
| } |
| |
| /// |
| @safe unittest |
| { |
| void main() |
| { |
| import std.algorithm.iteration : filter, map, sum; |
| import std.format : format; |
| import std.range : iota, tee; |
| |
| int len; |
| const r = 6.iota |
| .filter!(a => a % 2) // 1 3 5 |
| .map!(a => a * 2) // 2 6 10 |
| .tee!(_ => stdout.writefln("len: %d", len++)) |
| .sum; |
| |
| assert(r == 18); |
| } |
| } |
| |
| /// |
| @safe unittest |
| { |
| void main() |
| { |
| import std.algorithm.mutation : copy; |
| import std.algorithm.iteration : map; |
| import std.format : format; |
| import std.range : iota; |
| |
| 10.iota |
| .map!(e => "N: %d".format(e)) |
| .copy(stdout.lockingTextWriter()); // the OutputRange |
| } |
| } |
| |
| /** |
| The standard error stream. |
| |
| Returns: |
| stderr as a $(LREF File). |
| |
| Note: |
| The returned $(LREF File) wraps $(REF stderr,core,stdc,stdio), and |
| is therefore thread global. Reassigning `stderr` to a different |
| `File` must be done in a single-threaded or locked context in |
| order to avoid race conditions. |
| |
| All writing to `stderr` automatically locks the file globally, |
| and will cause all other threads calling `write` to wait until |
| the lock is released. |
| */ |
| alias stderr = makeGlobal!(StdFileHandle.stderr); |
| |
| /// |
| @safe unittest |
| { |
| void main() |
| { |
| stderr.writeln("Write a message to stderr."); |
| } |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| import std.typecons : tuple; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| auto deleteme = testFilename(); |
| |
| std.file.write(deleteme, "1 2\n4 1\n5 100"); |
| scope(exit) std.file.remove(deleteme); |
| { |
| File f = File(deleteme); |
| scope(exit) f.close(); |
| auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ]; |
| uint i; |
| foreach (e; f.byRecord!(int, int)("%s %s")) |
| { |
| //writeln(e); |
| assert(e == t[i++]); |
| } |
| assert(i == 3); |
| } |
| } |
| |
| @safe unittest |
| { |
| // Retain backwards compatibility |
| // https://issues.dlang.org/show_bug.cgi?id=17472 |
| static assert(is(typeof(stdin) == File)); |
| static assert(is(typeof(stdout) == File)); |
| static assert(is(typeof(stderr) == File)); |
| } |
| |
| // roll our own appender, but with "safe" arrays |
| private struct ReadlnAppender |
| { |
| char[] buf; |
| size_t pos; |
| bool safeAppend = false; |
| |
| void initialize(char[] b) |
| { |
| buf = b; |
| pos = 0; |
| } |
| @property char[] data() @trusted |
| { |
| if (safeAppend) |
| assumeSafeAppend(buf.ptr[0 .. pos]); |
| return buf.ptr[0 .. pos]; |
| } |
| |
| bool reserveWithoutAllocating(size_t n) |
| { |
| if (buf.length >= pos + n) // buf is already large enough |
| return true; |
| |
| immutable curCap = buf.capacity; |
| if (curCap >= pos + n) |
| { |
| buf.length = curCap; |
| /* Any extra capacity we end up not using can safely be claimed |
| by someone else. */ |
| safeAppend = true; |
| return true; |
| } |
| |
| return false; |
| } |
| void reserve(size_t n) @trusted |
| { |
| import core.stdc.string : memcpy; |
| if (!reserveWithoutAllocating(n)) |
| { |
| size_t ncap = buf.length * 2 + 128 + n; |
| char[] nbuf = new char[ncap]; |
| memcpy(nbuf.ptr, buf.ptr, pos); |
| buf = nbuf; |
| // Allocated a new buffer. No one else knows about it. |
| safeAppend = true; |
| } |
| } |
| void putchar(char c) @trusted |
| { |
| reserve(1); |
| buf.ptr[pos++] = c; |
| } |
| void putdchar(dchar dc) @trusted |
| { |
| import std.utf : encode, UseReplacementDchar; |
| |
| char[4] ubuf; |
| immutable size = encode!(UseReplacementDchar.yes)(ubuf, dc); |
| reserve(size); |
| foreach (c; ubuf) |
| buf.ptr[pos++] = c; |
| } |
| void putonly(char[] b) @trusted |
| { |
| import core.stdc.string : memcpy; |
| assert(pos == 0); // assume this is the only put call |
| if (reserveWithoutAllocating(b.length)) |
| memcpy(buf.ptr + pos, b.ptr, b.length); |
| else |
| buf = b.dup; |
| pos = b.length; |
| } |
| } |
| |
| // Private implementation of readln |
| private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation) |
| { |
| version (DIGITAL_MARS_STDIO) |
| { |
| _FLOCK(fps); |
| scope(exit) _FUNLOCK(fps); |
| |
| /* Since fps is now locked, we can create an "unshared" version |
| * of fp. |
| */ |
| auto fp = cast(_iobuf*) fps; |
| |
| ReadlnAppender app; |
| app.initialize(buf); |
| |
| if (__fhnd_info[fp._file] & FHND_WCHAR) |
| { /* Stream is in wide characters. |
| * Read them and convert to chars. |
| */ |
| static assert(wchar_t.sizeof == 2); |
| for (int c = void; (c = _FGETWC(fp)) != -1; ) |
| { |
| if ((c & ~0x7F) == 0) |
| { |
| app.putchar(cast(char) c); |
| if (c == terminator) |
| break; |
| } |
| else |
| { |
| if (c >= 0xD800 && c <= 0xDBFF) |
| { |
| int c2 = void; |
| if ((c2 = _FGETWC(fp)) != -1 || |
| c2 < 0xDC00 && c2 > 0xDFFF) |
| { |
| StdioException("unpaired UTF-16 surrogate"); |
| } |
| c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); |
| } |
| app.putdchar(cast(dchar) c); |
| } |
| } |
| if (ferror(fps)) |
| StdioException(); |
| } |
| |
| else if (fp._flag & _IONBF) |
| { |
| /* Use this for unbuffered I/O, when running |
| * across buffer boundaries, or for any but the common |
| * cases. |
| */ |
| L1: |
| int c; |
| while ((c = _FGETC(fp)) != -1) |
| { |
| app.putchar(cast(char) c); |
| if (c == terminator) |
| { |
| buf = app.data; |
| return buf.length; |
| } |
| |
| } |
| |
| if (ferror(fps)) |
| StdioException(); |
| } |
| else |
| { |
| int u = fp._cnt; |
| char* p = fp._ptr; |
| int i; |
| if (fp._flag & _IOTRAN) |
| { /* Translated mode ignores \r and treats ^Z as end-of-file |
| */ |
| char c; |
| while (1) |
| { |
| if (i == u) // if end of buffer |
| goto L1; // give up |
| c = p[i]; |
| i++; |
| if (c != '\r') |
| { |
| if (c == terminator) |
| break; |
| if (c != 0x1A) |
| continue; |
| goto L1; |
| } |
| else |
| { if (i != u && p[i] == terminator) |
| break; |
| goto L1; |
| } |
| } |
| app.putonly(p[0 .. i]); |
| app.buf[i - 1] = cast(char) terminator; |
| if (terminator == '\n' && c == '\r') |
| i++; |
| } |
| else |
| { |
| while (1) |
| { |
| if (i == u) // if end of buffer |
| goto L1; // give up |
| auto c = p[i]; |
| i++; |
| if (c == terminator) |
| break; |
| } |
| app.putonly(p[0 .. i]); |
| } |
| fp._cnt -= i; |
| fp._ptr += i; |
| } |
| |
| buf = app.data; |
| return buf.length; |
| } |
| else version (MICROSOFT_STDIO) |
| { |
| _FLOCK(fps); |
| scope(exit) _FUNLOCK(fps); |
| |
| /* Since fps is now locked, we can create an "unshared" version |
| * of fp. |
| */ |
| auto fp = cast(_iobuf*) fps; |
| |
| ReadlnAppender app; |
| app.initialize(buf); |
| |
| int c; |
| while ((c = _FGETC(fp)) != -1) |
| { |
| app.putchar(cast(char) c); |
| if (c == terminator) |
| { |
| buf = app.data; |
| return buf.length; |
| } |
| |
| } |
| |
| if (ferror(fps)) |
| StdioException(); |
| buf = app.data; |
| return buf.length; |
| } |
| else static if (__traits(compiles, core.sys.posix.stdio.getdelim)) |
| { |
| import core.stdc.stdlib : free; |
| import core.stdc.wchar_ : fwide; |
| |
| if (orientation == File.Orientation.wide) |
| { |
| /* Stream is in wide characters. |
| * Read them and convert to chars. |
| */ |
| _FLOCK(fps); |
| scope(exit) _FUNLOCK(fps); |
| auto fp = cast(_iobuf*) fps; |
| version (Windows) |
| { |
| buf.length = 0; |
| for (int c = void; (c = _FGETWC(fp)) != -1; ) |
| { |
| if ((c & ~0x7F) == 0) |
| { buf ~= c; |
| if (c == terminator) |
| break; |
| } |
| else |
| { |
| if (c >= 0xD800 && c <= 0xDBFF) |
| { |
| int c2 = void; |
| if ((c2 = _FGETWC(fp)) != -1 || |
| c2 < 0xDC00 && c2 > 0xDFFF) |
| { |
| StdioException("unpaired UTF-16 surrogate"); |
| } |
| c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); |
| } |
| import std.utf : encode; |
| encode(buf, c); |
| } |
| } |
| if (ferror(fp)) |
| StdioException(); |
| return buf.length; |
| } |
| else version (Posix) |
| { |
| buf.length = 0; |
| for (int c; (c = _FGETWC(fp)) != -1; ) |
| { |
| import std.utf : encode; |
| |
| if ((c & ~0x7F) == 0) |
| buf ~= cast(char) c; |
| else |
| encode(buf, cast(dchar) c); |
| if (c == terminator) |
| break; |
| } |
| if (ferror(fps)) |
| StdioException(); |
| return buf.length; |
| } |
| else |
| { |
| static assert(0); |
| } |
| } |
| |
| static char *lineptr = null; |
| static size_t n = 0; |
| scope(exit) |
| { |
| if (n > 128 * 1024) |
| { |
| // Bound memory used by readln |
| free(lineptr); |
| lineptr = null; |
| n = 0; |
| } |
| } |
| |
| auto s = core.sys.posix.stdio.getdelim(&lineptr, &n, terminator, fps); |
| if (s < 0) |
| { |
| if (ferror(fps)) |
| StdioException(); |
| buf.length = 0; // end of file |
| return 0; |
| } |
| |
| if (s <= buf.length) |
| { |
| buf = buf[0 .. s]; |
| buf[] = lineptr[0 .. s]; |
| } |
| else |
| { |
| buf = lineptr[0 .. s].dup; |
| } |
| return s; |
| } |
| else // version (NO_GETDELIM) |
| { |
| import core.stdc.wchar_ : fwide; |
| |
| _FLOCK(fps); |
| scope(exit) _FUNLOCK(fps); |
| auto fp = cast(_iobuf*) fps; |
| if (orientation == File.Orientation.wide) |
| { |
| /* Stream is in wide characters. |
| * Read them and convert to chars. |
| */ |
| version (Windows) |
| { |
| buf.length = 0; |
| for (int c; (c = _FGETWC(fp)) != -1; ) |
| { |
| if ((c & ~0x7F) == 0) |
| { buf ~= c; |
| if (c == terminator) |
| break; |
| } |
| else |
| { |
| if (c >= 0xD800 && c <= 0xDBFF) |
| { |
| int c2 = void; |
| if ((c2 = _FGETWC(fp)) != -1 || |
| c2 < 0xDC00 && c2 > 0xDFFF) |
| { |
| StdioException("unpaired UTF-16 surrogate"); |
| } |
| c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); |
| } |
| import std.utf : encode; |
| encode(buf, c); |
| } |
| } |
| if (ferror(fp)) |
| StdioException(); |
| return buf.length; |
| } |
| else version (Posix) |
| { |
| import std.utf : encode; |
| buf.length = 0; |
| for (int c; (c = _FGETWC(fp)) != -1; ) |
| { |
| if ((c & ~0x7F) == 0) |
| buf ~= cast(char) c; |
| else |
| encode(buf, cast(dchar) c); |
| if (c == terminator) |
| break; |
| } |
| if (ferror(fps)) |
| StdioException(); |
| return buf.length; |
| } |
| else |
| { |
| static assert(0); |
| } |
| } |
| |
| // Narrow stream |
| // First, fill the existing buffer |
| for (size_t bufPos = 0; bufPos < buf.length; ) |
| { |
| immutable c = _FGETC(fp); |
| if (c == -1) |
| { |
| buf.length = bufPos; |
| goto endGame; |
| } |
| buf[bufPos++] = cast(char) c; |
| if (c == terminator) |
| { |
| // No need to test for errors in file |
| buf.length = bufPos; |
| return bufPos; |
| } |
| } |
| // Then, append to it |
| for (int c; (c = _FGETC(fp)) != -1; ) |
| { |
| buf ~= cast(char) c; |
| if (c == terminator) |
| { |
| // No need to test for errors in file |
| return buf.length; |
| } |
| } |
| |
| endGame: |
| if (ferror(fps)) |
| StdioException(); |
| return buf.length; |
| } |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| |
| std.file.write(deleteme, "abcd\n0123456789abcde\n1234\n"); |
| File f = File(deleteme, "rb"); |
| |
| char[] ln = new char[2]; |
| f.readln(ln); |
| |
| assert(ln == "abcd\n"); |
| char[] t = ln[0 .. 2]; |
| t ~= 't'; |
| assert(t == "abt"); |
| // https://issues.dlang.org/show_bug.cgi?id=13856: ln stomped to "abtd" |
| assert(ln == "abcd\n"); |
| |
| // it can also stomp the array length |
| ln = new char[4]; |
| f.readln(ln); |
| assert(ln == "0123456789abcde\n"); |
| |
| char[100] buf; |
| ln = buf[]; |
| f.readln(ln); |
| assert(ln == "1234\n"); |
| assert(ln.ptr == buf.ptr); // avoid allocation, buffer is good enough |
| } |
| |
| /** Experimental network access via the File interface |
| |
| Opens a TCP connection to the given host and port, then returns |
| a File struct with read and write access through the same interface |
| as any other file (meaning writef and the byLine ranges work!). |
| |
| Authors: |
| Adam D. Ruppe |
| |
| Bugs: |
| Only works on Linux |
| */ |
| version (linux) |
| { |
| File openNetwork(string host, ushort port) |
| { |
| import core.stdc.string : memcpy; |
| import core.sys.posix.arpa.inet : htons; |
| import core.sys.posix.netdb : gethostbyname; |
| import core.sys.posix.netinet.in_ : sockaddr_in; |
| static import core.sys.posix.unistd; |
| static import sock = core.sys.posix.sys.socket; |
| import std.conv : to; |
| import std.exception : enforce; |
| import std.internal.cstring : tempCString; |
| |
| auto h = enforce( gethostbyname(host.tempCString()), |
| new StdioException("gethostbyname")); |
| |
| int s = sock.socket(sock.AF_INET, sock.SOCK_STREAM, 0); |
| enforce(s != -1, new StdioException("socket")); |
| |
| scope(failure) |
| { |
| // want to make sure it doesn't dangle if something throws. Upon |
| // normal exit, the File struct's reference counting takes care of |
| // closing, so we don't need to worry about success |
| core.sys.posix.unistd.close(s); |
| } |
| |
| sockaddr_in addr; |
| |
| addr.sin_family = sock.AF_INET; |
| addr.sin_port = htons(port); |
| memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length); |
| |
| enforce(sock.connect(s, cast(sock.sockaddr*) &addr, addr.sizeof) != -1, |
| new StdioException("Connect failed")); |
| |
| File f; |
| f.fdopen(s, "w+", host ~ ":" ~ to!string(port)); |
| return f; |
| } |
| } |
| |
| version (StdUnittest) private string testFilename(string file = __FILE__, size_t line = __LINE__) @safe |
| { |
| import std.conv : text; |
| import std.file : deleteme; |
| import std.path : baseName; |
| |
| // filename intentionally contains non-ASCII (Russian) characters for |
| // https://issues.dlang.org/show_bug.cgi?id=7648 |
| return text(deleteme, "-детка.", baseName(file), ".", line); |
| } |