| // Written in the D programming language. |
| |
| /** |
| Standard I/O functions that extend $(B core.stdc.stdio). $(B core.stdc.stdio) |
| is $(D_PARAM public)ally imported when importing $(B std.stdio). |
| |
| Source: $(PHOBOSSRC std/_stdio.d) |
| Copyright: Copyright Digital Mars 2007-. |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| Authors: $(HTTP digitalmars.com, Walter Bright), |
| $(HTTP erdani.org, Andrei Alexandrescu), |
| Alex Rønne Petersen |
| */ |
| module std.stdio; |
| |
| import core.stdc.stddef; // wchar_t |
| public import core.stdc.stdio; |
| import std.algorithm.mutation; // copy |
| import std.meta; // allSatisfy |
| import std.range.primitives; // ElementEncodingType, empty, front, |
| // isBidirectionalRange, isInputRange, put |
| import std.traits; // isSomeChar, isSomeString, Unqual, isPointer |
| import std.typecons; // Flag |
| |
| /++ |
| If flag $(D KeepTerminator) is set to $(D KeepTerminator.yes), then the delimiter |
| is included in the strings returned. |
| +/ |
| alias KeepTerminator = Flag!"keepTerminator"; |
| |
| version (CRuntime_Microsoft) |
| { |
| version = MICROSOFT_STDIO; |
| } |
| else version (CRuntime_DigitalMars) |
| { |
| // Specific to the way Digital Mars C does stdio |
| version = DIGITAL_MARS_STDIO; |
| } |
| else version (CRuntime_Glibc) |
| { |
| // Specific to the way Gnu C does stdio |
| version = GCC_IO; |
| } |
| else version (CRuntime_Bionic) |
| { |
| version = GENERIC_IO; |
| } |
| else version (CRuntime_Musl) |
| { |
| version = GENERIC_IO; |
| } |
| else version (CRuntime_UClibc) |
| { |
| // uClibc supports GCC IO |
| version = GCC_IO; |
| } |
| else version (OSX) |
| { |
| version = GENERIC_IO; |
| } |
| else version (iOS) |
| { |
| version = GENERIC_IO; |
| } |
| else version (TVOS) |
| { |
| version = GENERIC_IO; |
| } |
| else version (WatchOS) |
| { |
| version = GENERIC_IO; |
| } |
| else version (FreeBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (NetBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (OpenBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (DragonFlyBSD) |
| { |
| version = GENERIC_IO; |
| } |
| else version (Solaris) |
| { |
| version = GENERIC_IO; |
| } |
| |
| // Character type used for operating system filesystem APIs |
| version (Windows) |
| { |
| private alias FSChar = wchar; |
| } |
| else |
| { |
| private alias FSChar = char; |
| } |
| |
| |
| version (Windows) |
| { |
| // core.stdc.stdio.fopen expects file names to be |
| // encoded in CP_ACP on Windows instead of UTF-8. |
| /+ Waiting for druntime pull 299 |
| +/ |
| extern (C) nothrow @nogc FILE* _wfopen(in wchar* filename, in wchar* mode); |
| extern (C) nothrow @nogc FILE* _wfreopen(in wchar* filename, in wchar* mode, FILE* fp); |
| |
| import core.sys.windows.windows : HANDLE; |
| } |
| |
| version (Posix) |
| { |
| static import core.sys.posix.stdio; // getdelim |
| } |
| |
| version (DIGITAL_MARS_STDIO) |
| { |
| extern (C) |
| { |
| /* ** |
| * Digital Mars under-the-hood C I/O functions. |
| * Use _iobuf* for the unshared version of FILE*, |
| * usable when the FILE is locked. |
| */ |
| nothrow: |
| @nogc: |
| int _fputc_nlock(int, _iobuf*); |
| int _fputwc_nlock(int, _iobuf*); |
| int _fgetc_nlock(_iobuf*); |
| int _fgetwc_nlock(_iobuf*); |
| int __fp_lock(FILE*); |
| void __fp_unlock(FILE*); |
| |
| int setmode(int, int); |
| } |
| alias FPUTC = _fputc_nlock; |
| alias FPUTWC = _fputwc_nlock; |
| alias FGETC = _fgetc_nlock; |
| alias FGETWC = _fgetwc_nlock; |
| |
| alias FLOCK = __fp_lock; |
| alias FUNLOCK = __fp_unlock; |
| |
| alias _setmode = setmode; |
| enum _O_BINARY = 0x8000; |
| int _fileno(FILE* f) { return f._file; } |
| alias fileno = _fileno; |
| } |
| else version (MICROSOFT_STDIO) |
| { |
| extern (C) |
| { |
| /* ** |
| * Microsoft under-the-hood C I/O functions |
| */ |
| nothrow: |
| @nogc: |
| int _fputc_nolock(int, _iobuf*); |
| int _fputwc_nolock(int, _iobuf*); |
| int _fgetc_nolock(_iobuf*); |
| int _fgetwc_nolock(_iobuf*); |
| void _lock_file(FILE*); |
| void _unlock_file(FILE*); |
| int _setmode(int, int); |
| int _fileno(FILE*); |
| FILE* _fdopen(int, const (char)*); |
| int _fseeki64(FILE*, long, int); |
| long _ftelli64(FILE*); |
| } |
| alias FPUTC = _fputc_nolock; |
| alias FPUTWC = _fputwc_nolock; |
| alias FGETC = _fgetc_nolock; |
| alias FGETWC = _fgetwc_nolock; |
| |
| alias FLOCK = _lock_file; |
| alias FUNLOCK = _unlock_file; |
| |
| alias setmode = _setmode; |
| alias fileno = _fileno; |
| |
| enum |
| { |
| _O_RDONLY = 0x0000, |
| _O_APPEND = 0x0004, |
| _O_TEXT = 0x4000, |
| _O_BINARY = 0x8000, |
| } |
| } |
| else version (GCC_IO) |
| { |
| /* ** |
| * Gnu under-the-hood C I/O functions; see |
| * http://gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html |
| */ |
| extern (C) |
| { |
| nothrow: |
| @nogc: |
| int fputc_unlocked(int, _iobuf*); |
| int fputwc_unlocked(wchar_t, _iobuf*); |
| int fgetc_unlocked(_iobuf*); |
| int fgetwc_unlocked(_iobuf*); |
| void flockfile(FILE*); |
| void funlockfile(FILE*); |
| |
| private size_t fwrite_unlocked(const(void)* ptr, |
| size_t size, size_t n, _iobuf *stream); |
| } |
| |
| alias FPUTC = fputc_unlocked; |
| alias FPUTWC = fputwc_unlocked; |
| alias FGETC = fgetc_unlocked; |
| alias FGETWC = fgetwc_unlocked; |
| |
| alias FLOCK = flockfile; |
| alias FUNLOCK = funlockfile; |
| } |
| else version (GENERIC_IO) |
| { |
| nothrow: |
| @nogc: |
| |
| extern (C) |
| { |
| void flockfile(FILE*); |
| void funlockfile(FILE*); |
| } |
| |
| int fputc_unlocked(int c, _iobuf* fp) { return fputc(c, cast(shared) fp); } |
| int fputwc_unlocked(wchar_t c, _iobuf* fp) |
| { |
| import core.stdc.wchar_ : fputwc; |
| return fputwc(c, cast(shared) fp); |
| } |
| int fgetc_unlocked(_iobuf* fp) { return fgetc(cast(shared) fp); } |
| int fgetwc_unlocked(_iobuf* fp) |
| { |
| import core.stdc.wchar_ : fgetwc; |
| return fgetwc(cast(shared) fp); |
| } |
| |
| alias FPUTC = fputc_unlocked; |
| alias FPUTWC = fputwc_unlocked; |
| alias FGETC = fgetc_unlocked; |
| alias FGETWC = fgetwc_unlocked; |
| |
| alias FLOCK = flockfile; |
| alias FUNLOCK = funlockfile; |
| } |
| else |
| { |
| static assert(0, "unsupported C I/O system"); |
| } |
| |
| static if (__traits(compiles, core.sys.posix.stdio.getdelim)) |
| { |
| extern(C) nothrow @nogc |
| { |
| // @@@DEPRECATED_2.104@@@ |
| deprecated("To be removed after 2.104. Use core.sys.posix.stdio.getdelim instead.") |
| ptrdiff_t getdelim(char**, size_t*, int, FILE*); |
| |
| // @@@DEPRECATED_2.104@@@ |
| // getline() always comes together with getdelim() |
| deprecated("To be removed after 2.104. Use core.sys.posix.stdio.getline instead.") |
| ptrdiff_t getline(char**, size_t*, FILE*); |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| struct ByRecord(Fields...) |
| { |
| private: |
| import std.typecons : Tuple; |
| |
| File file; |
| char[] line; |
| Tuple!(Fields) current; |
| string format; |
| |
| public: |
| this(File f, string format) |
| { |
| assert(f.isOpen); |
| file = f; |
| this.format = format; |
| popFront(); // prime the range |
| } |
| |
| /// Range primitive implementations. |
| @property bool empty() |
| { |
| return !file.isOpen; |
| } |
| |
| /// Ditto |
| @property ref Tuple!(Fields) front() |
| { |
| return current; |
| } |
| |
| /// Ditto |
| void popFront() |
| { |
| import std.conv : text; |
| import std.exception : enforce; |
| import std.format : formattedRead; |
| import std.string : chomp; |
| |
| enforce(file.isOpen, "ByRecord: File must be open"); |
| file.readln(line); |
| if (!line.length) |
| { |
| file.detach(); |
| } |
| else |
| { |
| line = chomp(line); |
| formattedRead(line, format, ¤t); |
| enforce(line.empty, text("Leftover characters in record: `", |
| line, "'")); |
| } |
| } |
| } |
| |
| template byRecord(Fields...) |
| { |
| ByRecord!(Fields) byRecord(File f, string format) |
| { |
| return typeof(return)(f, format); |
| } |
| } |
| |
| /** |
| Encapsulates a $(D FILE*). Generally D does not attempt to provide |
| thin wrappers over equivalent functions in the C standard library, but |
| manipulating $(D FILE*) values directly is unsafe and error-prone in |
| many ways. The $(D File) type ensures safe manipulation, automatic |
| file closing, and a lot of convenience. |
| |
| The underlying $(D FILE*) handle is maintained in a reference-counted |
| manner, such that as soon as the last $(D File) variable bound to a |
| given $(D FILE*) goes out of scope, the underlying $(D FILE*) is |
| automatically closed. |
| |
| Example: |
| ---- |
| // test.d |
| void main(string[] args) |
| { |
| auto f = File("test.txt", "w"); // open for writing |
| f.write("Hello"); |
| if (args.length > 1) |
| { |
| auto g = f; // now g and f write to the same file |
| // internal reference count is 2 |
| g.write(", ", args[1]); |
| // g exits scope, reference count decreases to 1 |
| } |
| f.writeln("!"); |
| // f exits scope, reference count falls to zero, |
| // underlying `FILE*` is closed. |
| } |
| ---- |
| $(CONSOLE |
| % rdmd test.d Jimmy |
| % cat test.txt |
| Hello, Jimmy! |
| % __ |
| ) |
| */ |
| struct File |
| { |
| import std.range.primitives : ElementEncodingType; |
| import std.traits : isScalarType, isArray; |
| enum Orientation { unknown, narrow, wide } |
| |
| private struct Impl |
| { |
| FILE * handle = null; // Is null iff this Impl is closed by another File |
| uint refs = uint.max / 2; |
| bool isPopened; // true iff the stream has been created by popen() |
| Orientation orientation; |
| } |
| private Impl* _p; |
| private string _name; |
| |
| package this(FILE* handle, string name, uint refs = 1, bool isPopened = false) @trusted |
| { |
| import core.stdc.stdlib : malloc; |
| import std.exception : enforce; |
| |
| assert(!_p); |
| _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); |
| _p.handle = handle; |
| _p.refs = refs; |
| _p.isPopened = isPopened; |
| _p.orientation = Orientation.unknown; |
| _name = name; |
| } |
| |
| /** |
| Constructor taking the name of the file to open and the open mode. |
| |
| Copying one $(D File) object to another results in the two $(D File) |
| objects referring to the same underlying file. |
| |
| The destructor automatically closes the file as soon as no $(D File) |
| object refers to it anymore. |
| |
| Params: |
| name = range or string representing the file _name |
| stdioOpenmode = range or string represting the open mode |
| (with the same semantics as in the C standard library |
| $(HTTP cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) |
| function) |
| |
| Throws: $(D ErrnoException) if the file could not be opened. |
| */ |
| this(string name, in char[] stdioOpenmode = "rb") @safe |
| { |
| import std.conv : text; |
| import std.exception : errnoEnforce; |
| |
| this(errnoEnforce(.fopen(name, stdioOpenmode), |
| text("Cannot open file `", name, "' in mode `", |
| stdioOpenmode, "'")), |
| name); |
| |
| // MSVCRT workaround (issue 14422) |
| version (MICROSOFT_STDIO) |
| { |
| bool append, update; |
| foreach (c; stdioOpenmode) |
| if (c == 'a') |
| append = true; |
| else |
| if (c == '+') |
| update = true; |
| if (append && !update) |
| seek(size); |
| } |
| } |
| |
| /// ditto |
| this(R1, R2)(R1 name) |
| if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1)) |
| { |
| import std.conv : to; |
| this(name.to!string, "rb"); |
| } |
| |
| /// ditto |
| this(R1, R2)(R1 name, R2 mode) |
| if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) && |
| isInputRange!R2 && isSomeChar!(ElementEncodingType!R2)) |
| { |
| import std.conv : to; |
| this(name.to!string, mode.to!string); |
| } |
| |
| @safe unittest |
| { |
| static import std.file; |
| import std.utf : byChar; |
| auto deleteme = testFilename(); |
| auto f = File(deleteme.byChar, "w".byChar); |
| f.close(); |
| std.file.remove(deleteme); |
| } |
| |
| ~this() @safe |
| { |
| detach(); |
| } |
| |
| this(this) @safe nothrow |
| { |
| if (!_p) return; |
| assert(_p.refs); |
| ++_p.refs; |
| } |
| |
| /** |
| Assigns a file to another. The target of the assignment gets detached |
| from whatever file it was attached to, and attaches itself to the new |
| file. |
| */ |
| void opAssign(File rhs) @safe |
| { |
| import std.algorithm.mutation : swap; |
| |
| swap(this, rhs); |
| } |
| |
| /** |
| First calls $(D detach) (throwing on failure), and then attempts to |
| _open file $(D name) with mode $(D stdioOpenmode). The mode has the |
| same semantics as in the C standard library $(HTTP |
| cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) function. |
| |
| Throws: $(D ErrnoException) in case of error. |
| */ |
| void open(string name, in char[] stdioOpenmode = "rb") @safe |
| { |
| detach(); |
| this = File(name, stdioOpenmode); |
| } |
| |
| /** |
| Reuses the `File` object to either open a different file, or change |
| the file mode. If `name` is `null`, the mode of the currently open |
| file is changed; otherwise, a new file is opened, reusing the C |
| `FILE*`. The function has the same semantics as in the C standard |
| library $(HTTP cplusplus.com/reference/cstdio/freopen/, freopen) |
| function. |
| |
| Note: Calling `reopen` with a `null` `name` is not implemented |
| in all C runtimes. |
| |
| Throws: $(D ErrnoException) in case of error. |
| */ |
| void reopen(string name, in char[] stdioOpenmode = "rb") @trusted |
| { |
| import std.conv : text; |
| import std.exception : enforce, errnoEnforce; |
| import std.internal.cstring : tempCString; |
| |
| enforce(isOpen, "Attempting to reopen() an unopened file"); |
| |
| auto namez = (name == null ? _name : name).tempCString!FSChar(); |
| auto modez = stdioOpenmode.tempCString!FSChar(); |
| |
| FILE* fd = _p.handle; |
| version (Windows) |
| fd = _wfreopen(namez, modez, fd); |
| else |
| fd = freopen(namez, modez, fd); |
| |
| errnoEnforce(fd, name |
| ? text("Cannot reopen file `", name, "' in mode `", stdioOpenmode, "'") |
| : text("Cannot reopen file in mode `", stdioOpenmode, "'")); |
| |
| if (name !is null) |
| _name = name; |
| } |
| |
| @system unittest // Test changing filename |
| { |
| import std.exception : assertThrown, assertNotThrown; |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "foo"); |
| scope(exit) std.file.remove(deleteme); |
| auto f = File(deleteme); |
| assert(f.readln() == "foo"); |
| |
| auto deleteme2 = testFilename(); |
| std.file.write(deleteme2, "bar"); |
| scope(exit) std.file.remove(deleteme2); |
| f.reopen(deleteme2); |
| assert(f.name == deleteme2); |
| assert(f.readln() == "bar"); |
| f.close(); |
| } |
| |
| version (CRuntime_DigitalMars) {} else // Not implemented |
| version (CRuntime_Microsoft) {} else // Not implemented |
| @system unittest // Test changing mode |
| { |
| import std.exception : assertThrown, assertNotThrown; |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "foo"); |
| scope(exit) std.file.remove(deleteme); |
| auto f = File(deleteme, "r+"); |
| assert(f.readln() == "foo"); |
| f.reopen(null, "w"); |
| f.write("bar"); |
| f.seek(0); |
| f.reopen(null, "a"); |
| f.write("baz"); |
| assert(f.name == deleteme); |
| f.close(); |
| assert(std.file.readText(deleteme) == "barbaz"); |
| } |
| |
| /** |
| First calls $(D detach) (throwing on failure), and then runs a command |
| by calling the C standard library function $(HTTP |
| opengroup.org/onlinepubs/007908799/xsh/_popen.html, _popen). |
| |
| Throws: $(D ErrnoException) in case of error. |
| */ |
| version (Posix) void popen(string command, in char[] stdioOpenmode = "r") @safe |
| { |
| import std.exception : errnoEnforce; |
| |
| detach(); |
| this = File(errnoEnforce(.popen(command, stdioOpenmode), |
| "Cannot run command `"~command~"'"), |
| command, 1, true); |
| } |
| |
| /** |
| First calls $(D detach) (throwing on failure), and then attempts to |
| associate the given file descriptor with the $(D File). The mode must |
| be compatible with the mode of the file descriptor. |
| |
| Throws: $(D ErrnoException) in case of error. |
| */ |
| void fdopen(int fd, in char[] stdioOpenmode = "rb") @safe |
| { |
| fdopen(fd, stdioOpenmode, null); |
| } |
| |
| package void fdopen(int fd, in char[] stdioOpenmode, string name) @trusted |
| { |
| import std.exception : errnoEnforce; |
| import std.internal.cstring : tempCString; |
| |
| auto modez = stdioOpenmode.tempCString(); |
| detach(); |
| |
| version (DIGITAL_MARS_STDIO) |
| { |
| // This is a re-implementation of DMC's fdopen, but without the |
| // mucking with the file descriptor. POSIX standard requires the |
| // new fdopen'd file to retain the given file descriptor's |
| // position. |
| import core.stdc.stdio : fopen; |
| auto fp = fopen("NUL", modez); |
| errnoEnforce(fp, "Cannot open placeholder NUL stream"); |
| FLOCK(fp); |
| auto iob = cast(_iobuf*) fp; |
| .close(iob._file); |
| iob._file = fd; |
| iob._flag &= ~_IOTRAN; |
| FUNLOCK(fp); |
| } |
| else |
| { |
| version (Windows) // MSVCRT |
| auto fp = _fdopen(fd, modez); |
| else version (Posix) |
| { |
| import core.sys.posix.stdio : fdopen; |
| auto fp = fdopen(fd, modez); |
| } |
| errnoEnforce(fp); |
| } |
| this = File(fp, name); |
| } |
| |
| // Declare a dummy HANDLE to allow generating documentation |
| // for Windows-only methods. |
| version (StdDdoc) { version (Windows) {} else alias HANDLE = int; } |
| |
| /** |
| First calls $(D detach) (throwing on failure), and then attempts to |
| associate the given Windows $(D HANDLE) with the $(D File). The mode must |
| be compatible with the access attributes of the handle. Windows only. |
| |
| Throws: $(D ErrnoException) in case of error. |
| */ |
| version (StdDdoc) |
| void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode); |
| |
| version (Windows) |
| void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode) |
| { |
| import core.stdc.stdint : intptr_t; |
| import std.exception : errnoEnforce; |
| import std.format : format; |
| |
| // Create file descriptors from the handles |
| version (DIGITAL_MARS_STDIO) |
| auto fd = _handleToFD(handle, FHND_DEVICE); |
| else // MSVCRT |
| { |
| int mode; |
| modeLoop: |
| foreach (c; stdioOpenmode) |
| switch (c) |
| { |
| case 'r': mode |= _O_RDONLY; break; |
| case '+': mode &=~_O_RDONLY; break; |
| case 'a': mode |= _O_APPEND; break; |
| case 'b': mode |= _O_BINARY; break; |
| case 't': mode |= _O_TEXT; break; |
| case ',': break modeLoop; |
| default: break; |
| } |
| |
| auto fd = _open_osfhandle(cast(intptr_t) handle, mode); |
| } |
| |
| errnoEnforce(fd >= 0, "Cannot open Windows HANDLE"); |
| fdopen(fd, stdioOpenmode, "HANDLE(%s)".format(handle)); |
| } |
| |
| |
| /** Returns $(D true) if the file is opened. */ |
| @property bool isOpen() const @safe pure nothrow |
| { |
| return _p !is null && _p.handle; |
| } |
| |
| /** |
| Returns $(D true) if the file is at end (see $(HTTP |
| cplusplus.com/reference/clibrary/cstdio/feof.html, feof)). |
| |
| Throws: $(D Exception) if the file is not opened. |
| */ |
| @property bool eof() const @trusted pure |
| { |
| import std.exception : enforce; |
| |
| enforce(_p && _p.handle, "Calling eof() against an unopened file."); |
| return .feof(cast(FILE*) _p.handle) != 0; |
| } |
| |
| /** Returns the name of the last opened file, if any. |
| If a $(D File) was created with $(LREF tmpfile) and $(LREF wrapFile) |
| it has no name.*/ |
| @property string name() const @safe pure nothrow |
| { |
| return _name; |
| } |
| |
| /** |
| If the file is not opened, returns $(D true). Otherwise, returns |
| $(HTTP cplusplus.com/reference/clibrary/cstdio/ferror.html, ferror) for |
| the file handle. |
| */ |
| @property bool error() const @trusted pure nothrow |
| { |
| return !isOpen || .ferror(cast(FILE*) _p.handle); |
| } |
| |
| @safe unittest |
| { |
| // Issue 12349 |
| static import std.file; |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) std.file.remove(deleteme); |
| |
| f.close(); |
| assert(f.error); |
| } |
| |
| /** |
| Detaches from the underlying file. If the sole owner, calls $(D close). |
| |
| Throws: $(D ErrnoException) on failure if closing the file. |
| */ |
| void detach() @safe |
| { |
| if (!_p) return; |
| if (_p.refs == 1) |
| close(); |
| else |
| { |
| assert(_p.refs); |
| --_p.refs; |
| _p = null; |
| } |
| } |
| |
| @safe unittest |
| { |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| auto f = File(deleteme, "w"); |
| { |
| auto f2 = f; |
| f2.detach(); |
| } |
| assert(f._p.refs == 1); |
| f.close(); |
| } |
| |
| /** |
| If the file was unopened, succeeds vacuously. Otherwise closes the |
| file (by calling $(HTTP |
| cplusplus.com/reference/clibrary/cstdio/fclose.html, fclose)), |
| throwing on error. Even if an exception is thrown, afterwards the $(D |
| File) object is empty. This is different from $(D detach) in that it |
| always closes the file; consequently, all other $(D File) objects |
| referring to the same handle will see a closed file henceforth. |
| |
| Throws: $(D ErrnoException) on error. |
| */ |
| void close() @trusted |
| { |
| import core.stdc.stdlib : free; |
| import std.exception : errnoEnforce; |
| |
| if (!_p) return; // succeed vacuously |
| scope(exit) |
| { |
| assert(_p.refs); |
| if (!--_p.refs) |
| free(_p); |
| _p = null; // start a new life |
| } |
| if (!_p.handle) return; // Impl is closed by another File |
| |
| scope(exit) _p.handle = null; // nullify the handle anyway |
| version (Posix) |
| { |
| import core.sys.posix.stdio : pclose; |
| import std.format : format; |
| |
| if (_p.isPopened) |
| { |
| auto res = pclose(_p.handle); |
| errnoEnforce(res != -1, |
| "Could not close pipe `"~_name~"'"); |
| errnoEnforce(res == 0, format("Command returned %d", res)); |
| return; |
| } |
| } |
| errnoEnforce(.fclose(_p.handle) == 0, |
| "Could not close file `"~_name~"'"); |
| } |
| |
| /** |
| If the file is not opened, succeeds vacuously. Otherwise, returns |
| $(HTTP cplusplus.com/reference/clibrary/cstdio/_clearerr.html, |
| _clearerr) for the file handle. |
| */ |
| void clearerr() @safe pure nothrow |
| { |
| _p is null || _p.handle is null || |
| .clearerr(_p.handle); |
| } |
| |
| /** |
| Flushes the C $(D FILE) buffers. |
| |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_fflush.html, _fflush) |
| for the file handle. |
| |
| Throws: $(D Exception) if the file is not opened or if the call to $(D fflush) fails. |
| */ |
| void flush() @trusted |
| { |
| import std.exception : enforce, errnoEnforce; |
| |
| enforce(isOpen, "Attempting to flush() in an unopened file"); |
| errnoEnforce(.fflush(_p.handle) == 0); |
| } |
| |
| @safe unittest |
| { |
| // Issue 12349 |
| import std.exception : assertThrown; |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) std.file.remove(deleteme); |
| |
| f.close(); |
| assertThrown(f.flush()); |
| } |
| |
| /** |
| Forces any data buffered by the OS to be written to disk. |
| Call $(LREF flush) before calling this function to flush the C $(D FILE) buffers first. |
| |
| This function calls |
| $(HTTP msdn.microsoft.com/en-us/library/windows/desktop/aa364439%28v=vs.85%29.aspx, |
| $(D FlushFileBuffers)) on Windows and |
| $(HTTP pubs.opengroup.org/onlinepubs/7908799/xsh/fsync.html, |
| $(D fsync)) on POSIX for the file handle. |
| |
| Throws: $(D Exception) if the file is not opened or if the OS call fails. |
| */ |
| void sync() @trusted |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to sync() an unopened file"); |
| |
| version (Windows) |
| { |
| import core.sys.windows.windows : FlushFileBuffers; |
| wenforce(FlushFileBuffers(windowsHandle), "FlushFileBuffers failed"); |
| } |
| else |
| { |
| import core.sys.posix.unistd : fsync; |
| import std.exception : errnoEnforce; |
| errnoEnforce(fsync(fileno) == 0, "fsync failed"); |
| } |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fread.html, fread) for the |
| file handle. The number of items to read and the size of |
| each item is inferred from the size and type of the input array, respectively. |
| |
| Returns: The slice of $(D buffer) containing the data that was actually read. |
| This will be shorter than $(D buffer) if EOF was reached before the buffer |
| could be filled. |
| |
| Throws: $(D Exception) if $(D buffer) is empty. |
| $(D ErrnoException) if the file is not opened or the call to $(D fread) fails. |
| |
| $(D rawRead) always reads in binary mode on Windows. |
| */ |
| T[] rawRead(T)(T[] buffer) |
| { |
| import std.exception : errnoEnforce; |
| |
| if (!buffer.length) |
| throw new Exception("rawRead must take a non-empty buffer"); |
| version (Windows) |
| { |
| immutable fd = ._fileno(_p.handle); |
| immutable mode = ._setmode(fd, _O_BINARY); |
| scope(exit) ._setmode(fd, mode); |
| version (DIGITAL_MARS_STDIO) |
| { |
| import core.atomic : atomicOp; |
| |
| // @@@BUG@@@ 4243 |
| immutable info = __fhnd_info[fd]; |
| atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); |
| scope(exit) __fhnd_info[fd] = info; |
| } |
| } |
| immutable freadResult = trustedFread(_p.handle, buffer); |
| assert(freadResult <= buffer.length); // fread return guarantee |
| if (freadResult != buffer.length) // error or eof |
| { |
| errnoEnforce(!error); |
| return buffer[0 .. freadResult]; |
| } |
| return buffer; |
| } |
| |
| /// |
| @system unittest |
| { |
| static import std.file; |
| |
| auto testFile = testFilename(); |
| std.file.write(testFile, "\r\n\n\r\n"); |
| scope(exit) std.file.remove(testFile); |
| |
| auto f = File(testFile, "r"); |
| auto buf = f.rawRead(new char[5]); |
| f.close(); |
| assert(buf == "\r\n\n\r\n"); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fwrite.html, fwrite) for the file |
| handle. The number of items to write and the size of each |
| item is inferred from the size and type of the input array, respectively. An |
| error is thrown if the buffer could not be written in its entirety. |
| |
| $(D rawWrite) always writes in binary mode on Windows. |
| |
| Throws: $(D ErrnoException) if the file is not opened or if the call to $(D fwrite) fails. |
| */ |
| void rawWrite(T)(in T[] buffer) |
| { |
| import std.conv : text; |
| import std.exception : errnoEnforce; |
| |
| version (Windows) |
| { |
| flush(); // before changing translation mode |
| immutable fd = ._fileno(_p.handle); |
| immutable mode = ._setmode(fd, _O_BINARY); |
| scope(exit) ._setmode(fd, mode); |
| version (DIGITAL_MARS_STDIO) |
| { |
| import core.atomic : atomicOp; |
| |
| // @@@BUG@@@ 4243 |
| immutable info = __fhnd_info[fd]; |
| atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); |
| scope(exit) __fhnd_info[fd] = info; |
| } |
| scope(exit) flush(); // before restoring translation mode |
| } |
| auto result = trustedFwrite(_p.handle, buffer); |
| if (result == result.max) result = 0; |
| errnoEnforce(result == buffer.length, |
| text("Wrote ", result, " instead of ", buffer.length, |
| " objects of type ", T.stringof, " to file `", |
| _name, "'")); |
| } |
| |
| /// |
| @system unittest |
| { |
| static import std.file; |
| |
| auto testFile = testFilename(); |
| auto f = File(testFile, "w"); |
| scope(exit) std.file.remove(testFile); |
| |
| f.rawWrite("\r\n\n\r\n"); |
| f.close(); |
| assert(std.file.read(testFile) == "\r\n\n\r\n"); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fseek.html, fseek) |
| for the file handle. |
| |
| Throws: $(D Exception) if the file is not opened. |
| $(D ErrnoException) if the call to $(D fseek) fails. |
| */ |
| void seek(long offset, int origin = SEEK_SET) @trusted |
| { |
| import std.conv : to, text; |
| import std.exception : enforce, errnoEnforce; |
| |
| enforce(isOpen, "Attempting to seek() in an unopened file"); |
| version (Windows) |
| { |
| version (CRuntime_Microsoft) |
| { |
| alias fseekFun = _fseeki64; |
| alias off_t = long; |
| } |
| else |
| { |
| alias fseekFun = fseek; |
| alias off_t = int; |
| } |
| } |
| else version (Posix) |
| { |
| import core.sys.posix.stdio : fseeko, off_t; |
| alias fseekFun = fseeko; |
| } |
| errnoEnforce(fseekFun(_p.handle, to!off_t(offset), origin) == 0, |
| "Could not seek in file `"~_name~"'"); |
| } |
| |
| @system unittest |
| { |
| import std.conv : text; |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w+"); |
| scope(exit) { f.close(); std.file.remove(deleteme); } |
| f.rawWrite("abcdefghijklmnopqrstuvwxyz"); |
| f.seek(7); |
| assert(f.readln() == "hijklmnopqrstuvwxyz"); |
| |
| version (CRuntime_DigitalMars) |
| auto bigOffset = int.max - 100; |
| else |
| version (CRuntime_Bionic) |
| auto bigOffset = int.max - 100; |
| else |
| auto bigOffset = cast(ulong) int.max + 100; |
| f.seek(bigOffset); |
| assert(f.tell == bigOffset, text(f.tell)); |
| // Uncomment the tests below only if you want to wait for |
| // a long time |
| // f.rawWrite("abcdefghijklmnopqrstuvwxyz"); |
| // f.seek(-3, SEEK_END); |
| // assert(f.readln() == "xyz"); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/ftell.html, ftell) for the |
| managed file handle. |
| |
| Throws: $(D Exception) if the file is not opened. |
| $(D ErrnoException) if the call to $(D ftell) fails. |
| */ |
| @property ulong tell() const @trusted |
| { |
| import std.exception : enforce, errnoEnforce; |
| |
| enforce(isOpen, "Attempting to tell() in an unopened file"); |
| version (Windows) |
| { |
| version (CRuntime_Microsoft) |
| immutable result = _ftelli64(cast(FILE*) _p.handle); |
| else |
| immutable result = ftell(cast(FILE*) _p.handle); |
| } |
| else version (Posix) |
| { |
| import core.sys.posix.stdio : ftello; |
| immutable result = ftello(cast(FILE*) _p.handle); |
| } |
| errnoEnforce(result != -1, |
| "Query ftell() failed for file `"~_name~"'"); |
| return result; |
| } |
| |
| /// |
| @system unittest |
| { |
| import std.conv : text; |
| static import std.file; |
| |
| auto testFile = testFilename(); |
| std.file.write(testFile, "abcdefghijklmnopqrstuvwqxyz"); |
| scope(exit) { std.file.remove(testFile); } |
| |
| auto f = File(testFile); |
| auto a = new ubyte[4]; |
| f.rawRead(a); |
| assert(f.tell == 4, text(f.tell)); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_rewind.html, _rewind) |
| for the file handle. |
| |
| Throws: $(D Exception) if the file is not opened. |
| */ |
| void rewind() @safe |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to rewind() an unopened file"); |
| .rewind(_p.handle); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, _setvbuf) for |
| the file handle. |
| |
| Throws: $(D Exception) if the file is not opened. |
| $(D ErrnoException) if the call to $(D setvbuf) fails. |
| */ |
| void setvbuf(size_t size, int mode = _IOFBF) @trusted |
| { |
| import std.exception : enforce, errnoEnforce; |
| |
| enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); |
| errnoEnforce(.setvbuf(_p.handle, null, mode, size) == 0, |
| "Could not set buffering for file `"~_name~"'"); |
| } |
| |
| /** |
| Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, |
| _setvbuf) for the file handle. |
| |
| Throws: $(D Exception) if the file is not opened. |
| $(D ErrnoException) if the call to $(D setvbuf) fails. |
| */ |
| void setvbuf(void[] buf, int mode = _IOFBF) @trusted |
| { |
| import std.exception : enforce, errnoEnforce; |
| |
| enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); |
| errnoEnforce(.setvbuf(_p.handle, |
| cast(char*) buf.ptr, mode, buf.length) == 0, |
| "Could not set buffering for file `"~_name~"'"); |
| } |
| |
| |
| version (Windows) |
| { |
| import core.sys.windows.windows : ULARGE_INTEGER, OVERLAPPED, BOOL; |
| |
| private BOOL lockImpl(alias F, Flags...)(ulong start, ulong length, |
| Flags flags) |
| { |
| if (!start && !length) |
| length = ulong.max; |
| ULARGE_INTEGER liStart = void, liLength = void; |
| liStart.QuadPart = start; |
| liLength.QuadPart = length; |
| OVERLAPPED overlapped; |
| overlapped.Offset = liStart.LowPart; |
| overlapped.OffsetHigh = liStart.HighPart; |
| overlapped.hEvent = null; |
| return F(windowsHandle, flags, 0, liLength.LowPart, |
| liLength.HighPart, &overlapped); |
| } |
| |
| private static T wenforce(T)(T cond, string str) |
| { |
| import core.sys.windows.windows : GetLastError; |
| import std.windows.syserror : sysErrorString; |
| |
| if (cond) return cond; |
| throw new Exception(str ~ ": " ~ sysErrorString(GetLastError())); |
| } |
| } |
| version (Posix) |
| { |
| private int lockImpl(int operation, short l_type, |
| ulong start, ulong length) |
| { |
| import core.sys.posix.fcntl : fcntl, flock, off_t; |
| import core.sys.posix.unistd : getpid; |
| import std.conv : to; |
| |
| flock fl = void; |
| fl.l_type = l_type; |
| fl.l_whence = SEEK_SET; |
| fl.l_start = to!off_t(start); |
| fl.l_len = to!off_t(length); |
| fl.l_pid = getpid(); |
| return fcntl(fileno, operation, &fl); |
| } |
| } |
| |
| /** |
| Locks the specified file segment. If the file segment is already locked |
| by another process, waits until the existing lock is released. |
| If both $(D start) and $(D length) are zero, the entire file is locked. |
| |
| Locks created using $(D lock) and $(D tryLock) have the following properties: |
| $(UL |
| $(LI All locks are automatically released when the process terminates.) |
| $(LI Locks are not inherited by child processes.) |
| $(LI Closing a file will release all locks associated with the file. On POSIX, |
| even locks acquired via a different $(D File) will be released as well.) |
| $(LI Not all NFS implementations correctly implement file locking.) |
| ) |
| */ |
| void lock(LockType lockType = LockType.readWrite, |
| ulong start = 0, ulong length = 0) |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to call lock() on an unopened file"); |
| version (Posix) |
| { |
| import core.sys.posix.fcntl : F_RDLCK, F_SETLKW, F_WRLCK; |
| import std.exception : errnoEnforce; |
| immutable short type = lockType == LockType.readWrite |
| ? F_WRLCK : F_RDLCK; |
| errnoEnforce(lockImpl(F_SETLKW, type, start, length) != -1, |
| "Could not set lock for file `"~_name~"'"); |
| } |
| else |
| version (Windows) |
| { |
| import core.sys.windows.windows : LockFileEx, LOCKFILE_EXCLUSIVE_LOCK; |
| immutable type = lockType == LockType.readWrite ? |
| LOCKFILE_EXCLUSIVE_LOCK : 0; |
| wenforce(lockImpl!LockFileEx(start, length, type), |
| "Could not set lock for file `"~_name~"'"); |
| } |
| else |
| static assert(false); |
| } |
| |
| /** |
| Attempts to lock the specified file segment. |
| If both $(D start) and $(D length) are zero, the entire file is locked. |
| Returns: $(D true) if the lock was successful, and $(D false) if the |
| specified file segment was already locked. |
| */ |
| bool tryLock(LockType lockType = LockType.readWrite, |
| ulong start = 0, ulong length = 0) |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to call tryLock() on an unopened file"); |
| version (Posix) |
| { |
| import core.stdc.errno : EACCES, EAGAIN, errno; |
| import core.sys.posix.fcntl : F_RDLCK, F_SETLK, F_WRLCK; |
| import std.exception : errnoEnforce; |
| immutable short type = lockType == LockType.readWrite |
| ? F_WRLCK : F_RDLCK; |
| immutable res = lockImpl(F_SETLK, type, start, length); |
| if (res == -1 && (errno == EACCES || errno == EAGAIN)) |
| return false; |
| errnoEnforce(res != -1, "Could not set lock for file `"~_name~"'"); |
| return true; |
| } |
| else |
| version (Windows) |
| { |
| import core.sys.windows.windows : GetLastError, LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, |
| ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, LOCKFILE_FAIL_IMMEDIATELY; |
| immutable type = lockType == LockType.readWrite |
| ? LOCKFILE_EXCLUSIVE_LOCK : 0; |
| immutable res = lockImpl!LockFileEx(start, length, |
| type | LOCKFILE_FAIL_IMMEDIATELY); |
| if (!res && (GetLastError() == ERROR_IO_PENDING |
| || GetLastError() == ERROR_LOCK_VIOLATION)) |
| return false; |
| wenforce(res, "Could not set lock for file `"~_name~"'"); |
| return true; |
| } |
| else |
| static assert(false); |
| } |
| |
| /** |
| Removes the lock over the specified file segment. |
| */ |
| void unlock(ulong start = 0, ulong length = 0) |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to call unlock() on an unopened file"); |
| version (Posix) |
| { |
| import core.sys.posix.fcntl : F_SETLK, F_UNLCK; |
| import std.exception : errnoEnforce; |
| errnoEnforce(lockImpl(F_SETLK, F_UNLCK, start, length) != -1, |
| "Could not remove lock for file `"~_name~"'"); |
| } |
| else |
| version (Windows) |
| { |
| import core.sys.windows.windows : UnlockFileEx; |
| wenforce(lockImpl!UnlockFileEx(start, length), |
| "Could not remove lock for file `"~_name~"'"); |
| } |
| else |
| static assert(false); |
| } |
| |
| version (Windows) |
| @system unittest |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| auto f = File(deleteme, "wb"); |
| assert(f.tryLock()); |
| auto g = File(deleteme, "wb"); |
| assert(!g.tryLock()); |
| assert(!g.tryLock(LockType.read)); |
| f.unlock(); |
| f.lock(LockType.read); |
| assert(!g.tryLock()); |
| assert(g.tryLock(LockType.read)); |
| f.unlock(); |
| g.unlock(); |
| } |
| |
| version (Posix) |
| @system unittest |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| |
| // Since locks are per-process, we cannot test lock failures within |
| // the same process. fork() is used to create a second process. |
| static void runForked(void delegate() code) |
| { |
| import core.stdc.stdlib : exit; |
| import core.sys.posix.sys.wait : wait; |
| import core.sys.posix.unistd : fork; |
| int child, status; |
| if ((child = fork()) == 0) |
| { |
| code(); |
| exit(0); |
| } |
| else |
| { |
| assert(wait(&status) != -1); |
| assert(status == 0, "Fork crashed"); |
| } |
| } |
| |
| auto f = File(deleteme, "w+b"); |
| |
| runForked |
| ({ |
| auto g = File(deleteme, "a+b"); |
| assert(g.tryLock()); |
| g.unlock(); |
| assert(g.tryLock(LockType.read)); |
| }); |
| |
| assert(f.tryLock()); |
| runForked |
| ({ |
| auto g = File(deleteme, "a+b"); |
| assert(!g.tryLock()); |
| assert(!g.tryLock(LockType.read)); |
| }); |
| f.unlock(); |
| |
| f.lock(LockType.read); |
| runForked |
| ({ |
| auto g = File(deleteme, "a+b"); |
| assert(!g.tryLock()); |
| assert(g.tryLock(LockType.read)); |
| g.unlock(); |
| }); |
| f.unlock(); |
| } |
| |
| |
| /** |
| Writes its arguments in text format to the file. |
| |
| Throws: $(D Exception) if the file is not opened. |
| $(D ErrnoException) on an error writing to the file. |
| */ |
| void write(S...)(S args) |
| { |
| import std.traits : isBoolean, isIntegral, isAggregateType; |
| auto w = lockingTextWriter(); |
| foreach (arg; args) |
| { |
| alias A = typeof(arg); |
| static if (isAggregateType!A || is(A == enum)) |
| { |
| import std.format : formattedWrite; |
| |
| formattedWrite(w, "%s", arg); |
| } |
| else static if (isSomeString!A) |
| { |
| put(w, arg); |
| } |
| else static if (isIntegral!A) |
| { |
| import std.conv : toTextRange; |
| |
| toTextRange(arg, w); |
| } |
| else static if (isBoolean!A) |
| { |
| put(w, arg ? "true" : "false"); |
| } |
| else static if (isSomeChar!A) |
| { |
| put(w, arg); |
| } |
| else |
| { |
| import std.format : formattedWrite; |
| |
| // Most general case |
| formattedWrite(w, "%s", arg); |
| } |
| } |
| } |
| |
| /** |
| Writes its arguments in text format to the file, followed by a newline. |
| |
| Throws: $(D Exception) if the file is not opened. |
| $(D ErrnoException) on an error writing to the file. |
| */ |
| void writeln(S...)(S args) |
| { |
| write(args, '\n'); |
| } |
| |
| /** |
| Writes its arguments in text format to the file, according to the |
| format string fmt. |
| |
| Params: |
| fmt = The $(LINK2 std_format.html#format-string, format string). |
| When passed as a compile-time argument, the string will be statically checked |
| against the argument types passed. |
| args = Items to write. |
| |
| Throws: $(D Exception) if the file is not opened. |
| $(D ErrnoException) on an error writing to the file. |
| */ |
| void writef(alias fmt, A...)(A args) |
| if (isSomeString!(typeof(fmt))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(fmt, A); |
| static assert(!e, e.msg); |
| return this.writef(fmt, args); |
| } |
| |
| /// ditto |
| void writef(Char, A...)(in Char[] fmt, A args) |
| { |
| import std.format : formattedWrite; |
| |
| formattedWrite(lockingTextWriter(), fmt, args); |
| } |
| |
| /// Equivalent to `file.writef(fmt, args, '\n')`. |
| void writefln(alias fmt, A...)(A args) |
| if (isSomeString!(typeof(fmt))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(fmt, A); |
| static assert(!e, e.msg); |
| return this.writefln(fmt, args); |
| } |
| |
| /// ditto |
| void writefln(Char, A...)(in Char[] fmt, A args) |
| { |
| import std.format : formattedWrite; |
| |
| auto w = lockingTextWriter(); |
| formattedWrite(w, fmt, args); |
| w.put('\n'); |
| } |
| |
| /** |
| Read line from the file handle and return it as a specified type. |
| |
| This version manages its own read buffer, which means one memory allocation per call. If you are not |
| retaining a reference to the read data, consider the $(D File.readln(buf)) version, which may offer |
| better performance as it can reuse its read buffer. |
| |
| Params: |
| S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to $(D string). |
| terminator = Line terminator (by default, $(D '\n')). |
| |
| Note: |
| String terminators are not supported due to ambiguity with readln(buf) below. |
| |
| Returns: |
| The line that was read, including the line terminator character. |
| |
| Throws: |
| $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error. |
| |
| Example: |
| --- |
| // Reads `stdin` and writes it to `stdout`. |
| import std.stdio; |
| |
| void main() |
| { |
| string line; |
| while ((line = stdin.readln()) !is null) |
| write(line); |
| } |
| --- |
| */ |
| S readln(S = string)(dchar terminator = '\n') |
| if (isSomeString!S) |
| { |
| Unqual!(ElementEncodingType!S)[] buf; |
| readln(buf, terminator); |
| return cast(S) buf; |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| static import std.file; |
| import std.meta : AliasSeq; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "hello\nworld\n"); |
| scope(exit) std.file.remove(deleteme); |
| foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) |
| { |
| auto witness = [ "hello\n", "world\n" ]; |
| auto f = File(deleteme); |
| uint i = 0; |
| String buf; |
| while ((buf = f.readln!String()).length) |
| { |
| assert(i < witness.length); |
| assert(equal(buf, witness[i++])); |
| } |
| assert(i == witness.length); |
| } |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| import std.typecons : Tuple; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "cześć \U0002000D"); |
| scope(exit) std.file.remove(deleteme); |
| uint[] lengths = [12,8,7]; |
| foreach (uint i, C; Tuple!(char, wchar, dchar).Types) |
| { |
| immutable(C)[] witness = "cześć \U0002000D"; |
| auto buf = File(deleteme).readln!(immutable(C)[])(); |
| assert(buf.length == lengths[i]); |
| assert(buf == witness); |
| } |
| } |
| |
| /** |
| Read line from the file handle and write it to $(D buf[]), including |
| terminating character. |
| |
| This can be faster than $(D line = File.readln()) because you can reuse |
| the buffer for each call. Note that reusing the buffer means that you |
| must copy the previous contents if you wish to retain them. |
| |
| Params: |
| buf = Buffer used to store the resulting line data. buf is |
| resized as necessary. |
| terminator = Line terminator (by default, $(D '\n')). Use |
| $(REF newline, std,ascii) for portability (unless the file was opened in |
| text mode). |
| |
| Returns: |
| 0 for end of file, otherwise number of characters read |
| |
| Throws: $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode |
| conversion error. |
| |
| Example: |
| --- |
| // Read lines from `stdin` into a string |
| // Ignore lines starting with '#' |
| // Write the string to `stdout` |
| |
| void main() |
| { |
| string output; |
| char[] buf; |
| |
| while (stdin.readln(buf)) |
| { |
| if (buf[0] == '#') |
| continue; |
| |
| output ~= buf; |
| } |
| |
| write(output); |
| } |
| --- |
| |
| This method can be more efficient than the one in the previous example |
| because $(D stdin.readln(buf)) reuses (if possible) memory allocated |
| for $(D buf), whereas $(D line = stdin.readln()) makes a new memory allocation |
| for every line. |
| |
| For even better performance you can help $(D readln) by passing in a |
| large buffer to avoid memory reallocations. This can be done by reusing the |
| largest buffer returned by $(D readln): |
| |
| Example: |
| --- |
| // Read lines from `stdin` and count words |
| |
| void main() |
| { |
| char[] buf; |
| size_t words = 0; |
| |
| while (!stdin.eof) |
| { |
| char[] line = buf; |
| stdin.readln(line); |
| if (line.length > buf.length) |
| buf = line; |
| |
| words += line.split.length; |
| } |
| |
| writeln(words); |
| } |
| --- |
| This is actually what $(LREF byLine) does internally, so its usage |
| is recommended if you want to process a complete file. |
| */ |
| size_t readln(C)(ref C[] buf, dchar terminator = '\n') |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) |
| { |
| import std.exception : enforce; |
| |
| static if (is(C == char)) |
| { |
| enforce(_p && _p.handle, "Attempt to read from an unopened file."); |
| if (_p.orientation == Orientation.unknown) |
| { |
| import core.stdc.wchar_ : fwide; |
| auto w = fwide(_p.handle, 0); |
| if (w < 0) _p.orientation = Orientation.narrow; |
| else if (w > 0) _p.orientation = Orientation.wide; |
| } |
| return readlnImpl(_p.handle, buf, terminator, _p.orientation); |
| } |
| else |
| { |
| // TODO: optimize this |
| string s = readln(terminator); |
| buf.length = 0; |
| if (!s.length) return 0; |
| foreach (C c; s) |
| { |
| buf ~= c; |
| } |
| return buf.length; |
| } |
| } |
| |
| @system unittest |
| { |
| // @system due to readln |
| static import std.file; |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "123\n456789"); |
| scope(exit) std.file.remove(deleteme); |
| |
| auto file = File(deleteme); |
| char[] buffer = new char[10]; |
| char[] line = buffer; |
| file.readln(line); |
| auto beyond = line.length; |
| buffer[beyond] = 'a'; |
| file.readln(line); // should not write buffer beyond line |
| assert(buffer[beyond] == 'a'); |
| } |
| |
| @system unittest // bugzilla 15293 |
| { |
| // @system due to readln |
| static import std.file; |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "a\n\naa"); |
| scope(exit) std.file.remove(deleteme); |
| |
| auto file = File(deleteme); |
| char[] buffer; |
| char[] line; |
| |
| file.readln(buffer, '\n'); |
| |
| line = buffer; |
| file.readln(line, '\n'); |
| |
| line = buffer; |
| file.readln(line, '\n'); |
| |
| assert(line[0 .. 1].capacity == 0); |
| } |
| |
| /** ditto */ |
| size_t readln(C, R)(ref C[] buf, R terminator) |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && |
| isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) |
| { |
| import std.algorithm.mutation : swap; |
| import std.algorithm.searching : endsWith; |
| import std.range.primitives : back; |
| |
| auto last = terminator.back; |
| C[] buf2; |
| swap(buf, buf2); |
| for (;;) |
| { |
| if (!readln(buf2, last) || endsWith(buf2, terminator)) |
| { |
| if (buf.empty) |
| { |
| buf = buf2; |
| } |
| else |
| { |
| buf ~= buf2; |
| } |
| break; |
| } |
| buf ~= buf2; |
| } |
| return buf.length; |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| import std.typecons : Tuple; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "hello\n\rworld\nhow\n\rare ya"); |
| scope(exit) std.file.remove(deleteme); |
| foreach (C; Tuple!(char, wchar, dchar).Types) |
| { |
| immutable(C)[][] witness = [ "hello\n\r", "world\nhow\n\r", "are ya" ]; |
| auto f = File(deleteme); |
| uint i = 0; |
| C[] buf; |
| while (f.readln(buf, "\n\r")) |
| { |
| assert(i < witness.length); |
| assert(buf == witness[i++]); |
| } |
| assert(buf.length == 0); |
| } |
| } |
| |
| /** |
| * Reads formatted _data from the file using $(REF formattedRead, std,_format). |
| * Params: |
| * format = The $(LINK2 std_format.html#_format-string, _format string). |
| * When passed as a compile-time argument, the string will be statically checked |
| * against the argument types passed. |
| * data = Items to be read. |
| * Example: |
| ---- |
| // test.d |
| void main() |
| { |
| import std.stdio; |
| auto f = File("input"); |
| foreach (_; 0 .. 3) |
| { |
| int a; |
| f.readf!" %d"(a); |
| writeln(++a); |
| } |
| } |
| ---- |
| $(CONSOLE |
| % echo "1 2 3" > input |
| % rdmd test.d |
| 2 |
| 3 |
| 4 |
| ) |
| */ |
| uint readf(alias format, Data...)(auto ref Data data) |
| if (isSomeString!(typeof(format))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(format, Data); |
| static assert(!e, e.msg); |
| return this.readf(format, data); |
| } |
| |
| /// ditto |
| uint readf(Data...)(in char[] format, auto ref Data data) |
| { |
| import std.format : formattedRead; |
| |
| assert(isOpen); |
| auto input = LockingTextReader(this); |
| return formattedRead(input, format, data); |
| } |
| |
| /// |
| @system unittest |
| { |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); |
| scope(exit) std.file.remove(deleteme); |
| string s; |
| auto f = File(deleteme); |
| f.readf!"%s\n"(s); |
| assert(s == "hello", "["~s~"]"); |
| f.readf("%s\n", s); |
| assert(s == "world", "["~s~"]"); |
| |
| bool b1, b2; |
| f.readf("%s\n%s\n", b1, b2); |
| assert(b1 == true && b2 == false); |
| } |
| |
| // backwards compatibility with pointers |
| @system unittest |
| { |
| // @system due to readf |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); |
| scope(exit) std.file.remove(deleteme); |
| string s; |
| auto f = File(deleteme); |
| f.readf("%s\n", &s); |
| assert(s == "hello", "["~s~"]"); |
| f.readf("%s\n", &s); |
| assert(s == "world", "["~s~"]"); |
| |
| // Issue 11698 |
| bool b1, b2; |
| f.readf("%s\n%s\n", &b1, &b2); |
| assert(b1 == true && b2 == false); |
| } |
| |
| // backwards compatibility (mixed) |
| @system unittest |
| { |
| // @system due to readf |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); |
| scope(exit) std.file.remove(deleteme); |
| string s1, s2; |
| auto f = File(deleteme); |
| f.readf("%s\n%s\n", s1, &s2); |
| assert(s1 == "hello"); |
| assert(s2 == "world"); |
| |
| // Issue 11698 |
| bool b1, b2; |
| f.readf("%s\n%s\n", &b1, b2); |
| assert(b1 == true && b2 == false); |
| } |
| |
| // Issue 12260 - Nice error of std.stdio.readf with newlines |
| @system unittest |
| { |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "1\n2"); |
| scope(exit) std.file.remove(deleteme); |
| int input; |
| auto f = File(deleteme); |
| f.readf("%s", &input); |
| |
| import std.conv : ConvException; |
| import std.exception : collectException; |
| assert(collectException!ConvException(f.readf("%s", &input)).msg == |
| "Unexpected '\\n' when converting from type LockingTextReader to type int"); |
| } |
| |
| /** |
| Returns a temporary file by calling |
| $(HTTP cplusplus.com/reference/clibrary/cstdio/_tmpfile.html, _tmpfile). |
| Note that the created file has no $(LREF name).*/ |
| static File tmpfile() @safe |
| { |
| import std.exception : errnoEnforce; |
| |
| return File(errnoEnforce(.tmpfile(), |
| "Could not create temporary file with tmpfile()"), |
| null); |
| } |
| |
| /** |
| Unsafe function that wraps an existing $(D FILE*). The resulting $(D |
| File) never takes the initiative in closing the file. |
| Note that the created file has no $(LREF name)*/ |
| /*private*/ static File wrapFile(FILE* f) @safe |
| { |
| import std.exception : enforce; |
| |
| return File(enforce(f, "Could not wrap null FILE*"), |
| null, /*uint.max / 2*/ 9999); |
| } |
| |
| /** |
| Returns the $(D FILE*) corresponding to this object. |
| */ |
| FILE* getFP() @safe pure |
| { |
| import std.exception : enforce; |
| |
| enforce(_p && _p.handle, |
| "Attempting to call getFP() on an unopened file"); |
| return _p.handle; |
| } |
| |
| @system unittest |
| { |
| static import core.stdc.stdio; |
| assert(stdout.getFP() == core.stdc.stdio.stdout); |
| } |
| |
| /** |
| Returns the file number corresponding to this object. |
| */ |
| @property int fileno() const @trusted |
| { |
| import std.exception : enforce; |
| |
| enforce(isOpen, "Attempting to call fileno() on an unopened file"); |
| return .fileno(cast(FILE*) _p.handle); |
| } |
| |
| /** |
| Returns the underlying operating system $(D HANDLE) (Windows only). |
| */ |
| version (StdDdoc) |
| @property HANDLE windowsHandle(); |
| |
| version (Windows) |
| @property HANDLE windowsHandle() |
| { |
| version (DIGITAL_MARS_STDIO) |
| return _fdToHandle(fileno); |
| else |
| return cast(HANDLE)_get_osfhandle(fileno); |
| } |
| |
| |
| // Note: This was documented until 2013/08 |
| /* |
| Range that reads one line at a time. Returned by $(LREF byLine). |
| |
| Allows to directly use range operations on lines of a file. |
| */ |
| struct ByLine(Char, Terminator) |
| { |
| private: |
| import std.typecons : RefCounted, RefCountedAutoInitialize; |
| |
| /* Ref-counting stops the source range's Impl |
| * from getting out of sync after the range is copied, e.g. |
| * when accessing range.front, then using std.range.take, |
| * then accessing range.front again. */ |
| alias PImpl = RefCounted!(Impl, RefCountedAutoInitialize.no); |
| PImpl impl; |
| |
| static if (isScalarType!Terminator) |
| enum defTerm = '\n'; |
| else |
| enum defTerm = cast(Terminator)"\n"; |
| |
| public: |
| this(File f, KeepTerminator kt = No.keepTerminator, |
| Terminator terminator = defTerm) |
| { |
| impl = PImpl(f, kt, terminator); |
| } |
| |
| @property bool empty() |
| { |
| return impl.refCountedPayload.empty; |
| } |
| |
| @property Char[] front() |
| { |
| return impl.refCountedPayload.front; |
| } |
| |
| void popFront() |
| { |
| impl.refCountedPayload.popFront(); |
| } |
| |
| private: |
| struct Impl |
| { |
| private: |
| File file; |
| Char[] line; |
| Char[] buffer; |
| Terminator terminator; |
| KeepTerminator keepTerminator; |
| |
| public: |
| this(File f, KeepTerminator kt, Terminator terminator) |
| { |
| file = f; |
| this.terminator = terminator; |
| keepTerminator = kt; |
| popFront(); |
| } |
| |
| // Range primitive implementations. |
| @property bool empty() |
| { |
| return line is null; |
| } |
| |
| @property Char[] front() |
| { |
| return line; |
| } |
| |
| void popFront() |
| { |
| import std.algorithm.searching : endsWith; |
| assert(file.isOpen); |
| line = buffer; |
| file.readln(line, terminator); |
| if (line.length > buffer.length) |
| { |
| buffer = line; |
| } |
| if (line.empty) |
| { |
| file.detach(); |
| line = null; |
| } |
| else if (keepTerminator == No.keepTerminator |
| && endsWith(line, terminator)) |
| { |
| static if (isScalarType!Terminator) |
| enum tlen = 1; |
| else static if (isArray!Terminator) |
| { |
| static assert( |
| is(Unqual!(ElementEncodingType!Terminator) == Char)); |
| const tlen = terminator.length; |
| } |
| else |
| static assert(false); |
| line = line[0 .. line.length - tlen]; |
| } |
| } |
| } |
| } |
| |
| /** |
| Returns an input range set up to read from the file handle one line |
| at a time. |
| |
| The element type for the range will be $(D Char[]). Range primitives |
| may throw $(D StdioException) on I/O error. |
| |
| Note: |
| Each $(D front) will not persist after $(D |
| popFront) is called, so the caller must copy its contents (e.g. by |
| calling $(D to!string)) when retention is needed. If the caller needs |
| to retain a copy of every line, use the $(LREF byLineCopy) function |
| instead. |
| |
| Params: |
| Char = Character type for each line, defaulting to $(D char). |
| keepTerminator = Use $(D Yes.keepTerminator) to include the |
| terminator at the end of each line. |
| terminator = Line separator ($(D '\n') by default). Use |
| $(REF newline, std,ascii) for portability (unless the file was opened in |
| text mode). |
| |
| Example: |
| ---- |
| import std.algorithm, std.stdio, std.string; |
| // Count words in a file using ranges. |
| void main() |
| { |
| auto file = File("file.txt"); // Open for reading |
| const wordCount = file.byLine() // Read lines |
| .map!split // Split into words |
| .map!(a => a.length) // Count words per line |
| .sum(); // Total word count |
| writeln(wordCount); |
| } |
| ---- |
| |
| Example: |
| ---- |
| import std.range, std.stdio; |
| // Read lines using foreach. |
| void main() |
| { |
| auto file = File("file.txt"); // Open for reading |
| auto range = file.byLine(); |
| // Print first three lines |
| foreach (line; range.take(3)) |
| writeln(line); |
| // Print remaining lines beginning with '#' |
| foreach (line; range) |
| { |
| if (!line.empty && line[0] == '#') |
| writeln(line); |
| } |
| } |
| ---- |
| Notice that neither example accesses the line data returned by |
| $(D front) after the corresponding $(D popFront) call is made (because |
| the contents may well have changed). |
| */ |
| auto byLine(Terminator = char, Char = char) |
| (KeepTerminator keepTerminator = No.keepTerminator, |
| Terminator terminator = '\n') |
| if (isScalarType!Terminator) |
| { |
| return ByLine!(Char, Terminator)(this, keepTerminator, terminator); |
| } |
| |
| /// ditto |
| auto byLine(Terminator, Char = char) |
| (KeepTerminator keepTerminator, Terminator terminator) |
| if (is(Unqual!(ElementEncodingType!Terminator) == Char)) |
| { |
| return ByLine!(Char, Terminator)(this, keepTerminator, terminator); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "hi"); |
| scope(success) std.file.remove(deleteme); |
| |
| import std.meta : AliasSeq; |
| foreach (T; AliasSeq!(char, wchar, dchar)) |
| { |
| auto blc = File(deleteme).byLine!(T, T); |
| assert(blc.front == "hi"); |
| // check front is cached |
| assert(blc.front is blc.front); |
| } |
| } |
| |
| private struct ByLineCopy(Char, Terminator) |
| { |
| private: |
| import std.typecons : RefCounted, RefCountedAutoInitialize; |
| |
| /* Ref-counting stops the source range's ByLineCopyImpl |
| * from getting out of sync after the range is copied, e.g. |
| * when accessing range.front, then using std.range.take, |
| * then accessing range.front again. */ |
| alias Impl = RefCounted!(ByLineCopyImpl!(Char, Terminator), |
| RefCountedAutoInitialize.no); |
| Impl impl; |
| |
| public: |
| this(File f, KeepTerminator kt, Terminator terminator) |
| { |
| impl = Impl(f, kt, terminator); |
| } |
| |
| @property bool empty() |
| { |
| return impl.refCountedPayload.empty; |
| } |
| |
| @property Char[] front() |
| { |
| return impl.refCountedPayload.front; |
| } |
| |
| void popFront() |
| { |
| impl.refCountedPayload.popFront(); |
| } |
| } |
| |
| private struct ByLineCopyImpl(Char, Terminator) |
| { |
| ByLine!(Unqual!Char, Terminator).Impl impl; |
| bool gotFront; |
| Char[] line; |
| |
| public: |
| this(File f, KeepTerminator kt, Terminator terminator) |
| { |
| impl = ByLine!(Unqual!Char, Terminator).Impl(f, kt, terminator); |
| } |
| |
| @property bool empty() |
| { |
| return impl.empty; |
| } |
| |
| @property front() |
| { |
| if (!gotFront) |
| { |
| line = impl.front.dup; |
| gotFront = true; |
| } |
| return line; |
| } |
| |
| void popFront() |
| { |
| impl.popFront(); |
| gotFront = false; |
| } |
| } |
| |
| /** |
| Returns an input range set up to read from the file handle one line |
| at a time. Each line will be newly allocated. $(D front) will cache |
| its value to allow repeated calls without unnecessary allocations. |
| |
| Note: Due to caching byLineCopy can be more memory-efficient than |
| $(D File.byLine.map!idup). |
| |
| The element type for the range will be $(D Char[]). Range |
| primitives may throw $(D StdioException) on I/O error. |
| |
| Params: |
| Char = Character type for each line, defaulting to $(D immutable char). |
| keepTerminator = Use $(D Yes.keepTerminator) to include the |
| terminator at the end of each line. |
| terminator = Line separator ($(D '\n') by default). Use |
| $(REF newline, std,ascii) for portability (unless the file was opened in |
| text mode). |
| |
| Example: |
| ---- |
| import std.algorithm, std.array, std.stdio; |
| // Print sorted lines of a file. |
| void main() |
| { |
| auto sortedLines = File("file.txt") // Open for reading |
| .byLineCopy() // Read persistent lines |
| .array() // into an array |
| .sort(); // then sort them |
| foreach (line; sortedLines) |
| writeln(line); |
| } |
| ---- |
| See_Also: |
| $(REF readText, std,file) |
| */ |
| auto byLineCopy(Terminator = char, Char = immutable char) |
| (KeepTerminator keepTerminator = No.keepTerminator, |
| Terminator terminator = '\n') |
| if (isScalarType!Terminator) |
| { |
| return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); |
| } |
| |
| /// ditto |
| auto byLineCopy(Terminator, Char = immutable char) |
| (KeepTerminator keepTerminator, Terminator terminator) |
| if (is(Unqual!(ElementEncodingType!Terminator) == Unqual!Char)) |
| { |
| return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); |
| } |
| |
| @safe unittest |
| { |
| static assert(is(typeof(File("").byLine.front) == char[])); |
| static assert(is(typeof(File("").byLineCopy.front) == string)); |
| static assert( |
| is(typeof(File("").byLineCopy!(char, char).front) == char[])); |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, ""); |
| scope(success) std.file.remove(deleteme); |
| |
| // Test empty file |
| auto f = File(deleteme); |
| foreach (line; f.byLine()) |
| { |
| assert(false); |
| } |
| f.detach(); |
| assert(!f.isOpen); |
| |
| void test(Terminator)(string txt, in string[] witness, |
| KeepTerminator kt, Terminator term, bool popFirstLine = false) |
| { |
| import std.algorithm.sorting : sort; |
| import std.array : array; |
| import std.conv : text; |
| import std.range.primitives : walkLength; |
| |
| uint i; |
| std.file.write(deleteme, txt); |
| auto f = File(deleteme); |
| scope(exit) |
| { |
| f.close(); |
| assert(!f.isOpen); |
| } |
| auto lines = f.byLine(kt, term); |
| if (popFirstLine) |
| { |
| lines.popFront(); |
| i = 1; |
| } |
| assert(lines.empty || lines.front is lines.front); |
| foreach (line; lines) |
| { |
| assert(line == witness[i++]); |
| } |
| assert(i == witness.length, text(i, " != ", witness.length)); |
| |
| // Issue 11830 |
| auto walkedLength = File(deleteme).byLine(kt, term).walkLength; |
| assert(walkedLength == witness.length, text(walkedLength, " != ", witness.length)); |
| |
| // test persistent lines |
| assert(File(deleteme).byLineCopy(kt, term).array.sort() == witness.dup.sort()); |
| } |
| |
| KeepTerminator kt = No.keepTerminator; |
| test("", null, kt, '\n'); |
| test("\n", [ "" ], kt, '\n'); |
| test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n'); |
| test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n', true); |
| test("asd\ndef\nasdf\n", [ "asd", "def", "asdf" ], kt, '\n'); |
| test("foo", [ "foo" ], kt, '\n', true); |
| test("bob\r\nmarge\r\nsteve\r\n", ["bob", "marge", "steve"], |
| kt, "\r\n"); |
| test("sue\r", ["sue"], kt, '\r'); |
| |
| kt = Yes.keepTerminator; |
| test("", null, kt, '\n'); |
| test("\n", [ "\n" ], kt, '\n'); |
| test("asd\ndef\nasdf", [ "asd\n", "def\n", "asdf" ], kt, '\n'); |
| test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n'); |
| test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n', true); |
| test("foo", [ "foo" ], kt, '\n'); |
| test("bob\r\nmarge\r\nsteve\r\n", ["bob\r\n", "marge\r\n", "steve\r\n"], |
| kt, "\r\n"); |
| test("sue\r", ["sue\r"], kt, '\r'); |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.range : drop, take; |
| |
| version (Win64) |
| { |
| static import std.file; |
| |
| /* the C function tmpfile doesn't seem to work, even when called from C */ |
| auto deleteme = testFilename(); |
| auto file = File(deleteme, "w+"); |
| scope(success) std.file.remove(deleteme); |
| } |
| else version (CRuntime_Bionic) |
| { |
| static import std.file; |
| |
| /* the C function tmpfile doesn't work when called from a shared |
| library apk: |
| https://code.google.com/p/android/issues/detail?id=66815 */ |
| auto deleteme = testFilename(); |
| auto file = File(deleteme, "w+"); |
| scope(success) std.file.remove(deleteme); |
| } |
| else |
| auto file = File.tmpfile(); |
| file.write("1\n2\n3\n"); |
| |
| // bug 9599 |
| file.rewind(); |
| File.ByLine!(char, char) fbl = file.byLine(); |
| auto fbl2 = fbl; |
| assert(fbl.front == "1"); |
| assert(fbl.front is fbl2.front); |
| assert(fbl.take(1).equal(["1"])); |
| assert(fbl.equal(["2", "3"])); |
| assert(fbl.empty); |
| assert(file.isOpen); // we still have a valid reference |
| |
| file.rewind(); |
| fbl = file.byLine(); |
| assert(!fbl.drop(2).empty); |
| assert(fbl.equal(["3"])); |
| assert(fbl.empty); |
| assert(file.isOpen); |
| |
| file.detach(); |
| assert(!file.isOpen); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "hi"); |
| scope(success) std.file.remove(deleteme); |
| |
| auto blc = File(deleteme).byLineCopy; |
| assert(!blc.empty); |
| // check front is cached |
| assert(blc.front is blc.front); |
| } |
| |
| /** |
| Creates an input range set up to parse one line at a time from the file |
| into a tuple. |
| |
| Range primitives may throw $(D StdioException) on I/O error. |
| |
| Params: |
| format = tuple record $(REF_ALTTEXT _format, formattedRead, std, _format) |
| |
| Returns: |
| The input range set up to parse one line at a time into a record tuple. |
| |
| See_Also: |
| |
| It is similar to $(LREF byLine) and uses |
| $(REF_ALTTEXT _format, formattedRead, std, _format) under the hood. |
| */ |
| template byRecord(Fields...) |
| { |
| ByRecord!(Fields) byRecord(string format) |
| { |
| return typeof(return)(this, format); |
| } |
| } |
| |
| /// |
| @system unittest |
| { |
| static import std.file; |
| import std.typecons : tuple; |
| |
| // prepare test file |
| auto testFile = testFilename(); |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| std.file.write(testFile, "1 2\n4 1\n5 100"); |
| scope(exit) std.file.remove(testFile); |
| |
| File f = File(testFile); |
| scope(exit) f.close(); |
| |
| auto expected = [tuple(1, 2), tuple(4, 1), tuple(5, 100)]; |
| uint i; |
| foreach (e; f.byRecord!(int, int)("%s %s")) |
| { |
| assert(e == expected[i++]); |
| } |
| } |
| |
| // Note: This was documented until 2013/08 |
| /* |
| * Range that reads a chunk at a time. |
| */ |
| struct ByChunk |
| { |
| private: |
| File file_; |
| ubyte[] chunk_; |
| |
| void prime() |
| { |
| chunk_ = file_.rawRead(chunk_); |
| if (chunk_.length == 0) |
| file_.detach(); |
| } |
| |
| public: |
| this(File file, size_t size) |
| { |
| this(file, new ubyte[](size)); |
| } |
| |
| this(File file, ubyte[] buffer) |
| { |
| import std.exception : enforce; |
| enforce(buffer.length, "size must be larger than 0"); |
| file_ = file; |
| chunk_ = buffer; |
| prime(); |
| } |
| |
| // $(D ByChunk)'s input range primitive operations. |
| @property nothrow |
| bool empty() const |
| { |
| return !file_.isOpen; |
| } |
| |
| /// Ditto |
| @property nothrow |
| ubyte[] front() |
| { |
| version (assert) |
| { |
| import core.exception : RangeError; |
| if (empty) |
| throw new RangeError(); |
| } |
| return chunk_; |
| } |
| |
| /// Ditto |
| void popFront() |
| { |
| version (assert) |
| { |
| import core.exception : RangeError; |
| if (empty) |
| throw new RangeError(); |
| } |
| prime(); |
| } |
| } |
| |
| /** |
| Returns an input range set up to read from the file handle a chunk at a |
| time. |
| |
| The element type for the range will be $(D ubyte[]). Range primitives |
| may throw $(D StdioException) on I/O error. |
| |
| Example: |
| --------- |
| void main() |
| { |
| // Read standard input 4KB at a time |
| foreach (ubyte[] buffer; stdin.byChunk(4096)) |
| { |
| ... use buffer ... |
| } |
| } |
| --------- |
| |
| The parameter may be a number (as shown in the example above) dictating the |
| size of each chunk. Alternatively, $(D byChunk) accepts a |
| user-provided buffer that it uses directly. |
| |
| Example: |
| --------- |
| void main() |
| { |
| // Read standard input 4KB at a time |
| foreach (ubyte[] buffer; stdin.byChunk(new ubyte[4096])) |
| { |
| ... use buffer ... |
| } |
| } |
| --------- |
| |
| In either case, the content of the buffer is reused across calls. That means |
| $(D front) will not persist after $(D popFront) is called, so if retention is |
| needed, the caller must copy its contents (e.g. by calling $(D buffer.dup)). |
| |
| In the example above, $(D buffer.length) is 4096 for all iterations, except |
| for the last one, in which case $(D buffer.length) may be less than 4096 (but |
| always greater than zero). |
| |
| With the mentioned limitations, $(D byChunk) works with any algorithm |
| compatible with input ranges. |
| |
| Example: |
| --- |
| // Efficient file copy, 1MB at a time. |
| import std.algorithm, std.stdio; |
| void main() |
| { |
| stdin.byChunk(1024 * 1024).copy(stdout.lockingTextWriter()); |
| } |
| --- |
| |
| $(REF joiner, std,algorithm,iteration) can be used to join chunks together into |
| a single range lazily. |
| Example: |
| --- |
| import std.algorithm, std.stdio; |
| void main() |
| { |
| //Range of ranges |
| static assert(is(typeof(stdin.byChunk(4096).front) == ubyte[])); |
| //Range of elements |
| static assert(is(typeof(stdin.byChunk(4096).joiner.front) == ubyte)); |
| } |
| --- |
| |
| Returns: A call to $(D byChunk) returns a range initialized with the $(D File) |
| object and the appropriate buffer. |
| |
| Throws: If the user-provided size is zero or the user-provided buffer |
| is empty, throws an $(D Exception). In case of an I/O error throws |
| $(D StdioException). |
| */ |
| auto byChunk(size_t chunkSize) |
| { |
| return ByChunk(this, chunkSize); |
| } |
| /// Ditto |
| ByChunk byChunk(ubyte[] buffer) |
| { |
| return ByChunk(this, buffer); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "asd\ndef\nasdf"); |
| |
| auto witness = ["asd\n", "def\n", "asdf" ]; |
| auto f = File(deleteme); |
| scope(exit) |
| { |
| f.close(); |
| assert(!f.isOpen); |
| std.file.remove(deleteme); |
| } |
| |
| uint i; |
| foreach (chunk; f.byChunk(4)) |
| assert(chunk == cast(ubyte[]) witness[i++]); |
| |
| assert(i == witness.length); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "asd\ndef\nasdf"); |
| |
| auto witness = ["asd\n", "def\n", "asdf" ]; |
| auto f = File(deleteme); |
| scope(exit) |
| { |
| f.close(); |
| assert(!f.isOpen); |
| std.file.remove(deleteme); |
| } |
| |
| uint i; |
| foreach (chunk; f.byChunk(new ubyte[4])) |
| assert(chunk == cast(ubyte[]) witness[i++]); |
| |
| assert(i == witness.length); |
| } |
| |
| // Note: This was documented until 2013/08 |
| /* |
| $(D Range) that locks the file and allows fast writing to it. |
| */ |
| struct LockingTextWriter |
| { |
| private: |
| import std.range.primitives : ElementType, isInfinite, isInputRange; |
| // the shared file handle |
| FILE* fps_; |
| |
| // the unshared version of fps |
| @property _iobuf* handle_() @trusted { return cast(_iobuf*) fps_; } |
| |
| // the file's orientation (byte- or wide-oriented) |
| int orientation_; |
| public: |
| |
| this(ref File f) @trusted |
| { |
| import core.stdc.wchar_ : fwide; |
| import std.exception : enforce; |
| |
| enforce(f._p && f._p.handle, "Attempting to write to closed File"); |
| fps_ = f._p.handle; |
| orientation_ = fwide(fps_, 0); |
| FLOCK(fps_); |
| } |
| |
| ~this() @trusted |
| { |
| if (fps_) |
| { |
| FUNLOCK(fps_); |
| fps_ = null; |
| } |
| } |
| |
| this(this) @trusted |
| { |
| if (fps_) |
| { |
| FLOCK(fps_); |
| } |
| } |
| |
| /// Range primitive implementations. |
| void put(A)(A writeme) |
| if ((isSomeChar!(Unqual!(ElementType!A)) || |
| is(ElementType!A : const(ubyte))) && |
| isInputRange!A && |
| !isInfinite!A) |
| { |
| import std.exception : errnoEnforce; |
| |
| alias C = ElementEncodingType!A; |
| static assert(!is(C == void)); |
| static if (isSomeString!A && C.sizeof == 1 || is(A : const(ubyte)[])) |
| { |
| if (orientation_ <= 0) |
| { |
| //file.write(writeme); causes infinite recursion!!! |
| //file.rawWrite(writeme); |
| auto result = trustedFwrite(fps_, writeme); |
| if (result != writeme.length) errnoEnforce(0); |
| return; |
| } |
| } |
| |
| // put each element in turn. |
| alias Elem = Unqual!(ElementType!A); |
| foreach (Elem c; writeme) |
| { |
| put(c); |
| } |
| } |
| |
| /// ditto |
| void put(C)(C c) @safe if (isSomeChar!C || is(C : const(ubyte))) |
| { |
| import std.traits : Parameters; |
| static auto trustedFPUTC(int ch, _iobuf* h) @trusted |
| { |
| return FPUTC(ch, h); |
| } |
| static auto trustedFPUTWC(Parameters!FPUTWC[0] ch, _iobuf* h) @trusted |
| { |
| return FPUTWC(ch, h); |
| } |
| |
| static if (c.sizeof == 1) |
| { |
| // simple char |
| if (orientation_ <= 0) trustedFPUTC(c, handle_); |
| else trustedFPUTWC(c, handle_); |
| } |
| else static if (c.sizeof == 2) |
| { |
| import std.utf : encode, UseReplacementDchar; |
| |
| if (orientation_ <= 0) |
| { |
| if (c <= 0x7F) |
| { |
| trustedFPUTC(c, handle_); |
| } |
| else |
| { |
| char[4] buf; |
| immutable size = encode!(UseReplacementDchar.yes)(buf, c); |
| foreach (i ; 0 .. size) |
| trustedFPUTC(buf[i], handle_); |
| } |
| } |
| else |
| { |
| trustedFPUTWC(c, handle_); |
| } |
| } |
| else // 32-bit characters |
| { |
| import std.utf : encode; |
| |
| if (orientation_ <= 0) |
| { |
| if (c <= 0x7F) |
| { |
| trustedFPUTC(c, handle_); |
| } |
| else |
| { |
| char[4] buf = void; |
| immutable len = encode(buf, c); |
| foreach (i ; 0 .. len) |
| trustedFPUTC(buf[i], handle_); |
| } |
| } |
| else |
| { |
| version (Windows) |
| { |
| import std.utf : isValidDchar; |
| |
| assert(isValidDchar(c)); |
| if (c <= 0xFFFF) |
| { |
| trustedFPUTWC(c, handle_); |
| } |
| else |
| { |
| trustedFPUTWC(cast(wchar) |
| ((((c - 0x10000) >> 10) & 0x3FF) |
| + 0xD800), handle_); |
| trustedFPUTWC(cast(wchar) |
| (((c - 0x10000) & 0x3FF) + 0xDC00), |
| handle_); |
| } |
| } |
| else version (Posix) |
| { |
| trustedFPUTWC(c, handle_); |
| } |
| else |
| { |
| static assert(0); |
| } |
| } |
| } |
| } |
| } |
| |
| /** Returns an output range that locks the file and allows fast writing to it. |
| |
| See $(LREF byChunk) for an example. |
| */ |
| auto lockingTextWriter() @safe |
| { |
| return LockingTextWriter(this); |
| } |
| |
| // An output range which optionally locks the file and puts it into |
| // binary mode (similar to rawWrite). Because it needs to restore |
| // the file mode on destruction, it is RefCounted on Windows. |
| struct BinaryWriterImpl(bool locking) |
| { |
| import std.traits : hasIndirections; |
| private: |
| FILE* fps; |
| string name; |
| |
| version (Windows) |
| { |
| int fd, oldMode; |
| version (DIGITAL_MARS_STDIO) |
| ubyte oldInfo; |
| } |
| |
| package: |
| this(ref File f) |
| { |
| import std.exception : enforce; |
| |
| enforce(f._p && f._p.handle); |
| name = f._name; |
| fps = f._p.handle; |
| static if (locking) |
| FLOCK(fps); |
| |
| version (Windows) |
| { |
| .fflush(fps); // before changing translation mode |
| fd = ._fileno(fps); |
| oldMode = ._setmode(fd, _O_BINARY); |
| version (DIGITAL_MARS_STDIO) |
| { |
| import core.atomic : atomicOp; |
| |
| // @@@BUG@@@ 4243 |
| oldInfo = __fhnd_info[fd]; |
| atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); |
| } |
| } |
| } |
| |
| public: |
| ~this() |
| { |
| if (!fps) |
| return; |
| |
| version (Windows) |
| { |
| .fflush(fps); // before restoring translation mode |
| version (DIGITAL_MARS_STDIO) |
| { |
| // @@@BUG@@@ 4243 |
| __fhnd_info[fd] = oldInfo; |
| } |
| ._setmode(fd, oldMode); |
| } |
| |
| FUNLOCK(fps); |
| fps = null; |
| } |
| |
| void rawWrite(T)(in T[] buffer) |
| { |
| import std.conv : text; |
| import std.exception : errnoEnforce; |
| |
| auto result = trustedFwrite(fps, buffer); |
| if (result == result.max) result = 0; |
| errnoEnforce(result == buffer.length, |
| text("Wrote ", result, " instead of ", buffer.length, |
| " objects of type ", T.stringof, " to file `", |
| name, "'")); |
| } |
| |
| version (Windows) |
| { |
| @disable this(this); |
| } |
| else |
| { |
| this(this) |
| { |
| if (fps) |
| { |
| FLOCK(fps); |
| } |
| } |
| } |
| |
| void put(T)(auto ref in T value) |
| if (!hasIndirections!T && |
| !isInputRange!T) |
| { |
| rawWrite((&value)[0 .. 1]); |
| } |
| |
| void put(T)(in T[] array) |
| if (!hasIndirections!T && |
| !isInputRange!T) |
| { |
| rawWrite(array); |
| } |
| } |
| |
| /** Returns an output range that locks the file and allows fast writing to it. |
| |
| Example: |
| Produce a grayscale image of the $(LINK2 https://en.wikipedia.org/wiki/Mandelbrot_set, Mandelbrot set) |
| in binary $(LINK2 https://en.wikipedia.org/wiki/Netpbm_format, Netpbm format) to standard output. |
| --- |
| import std.algorithm, std.range, std.stdio; |
| |
| void main() |
| { |
| enum size = 500; |
| writef("P5\n%d %d %d\n", size, size, ubyte.max); |
| |
| iota(-1, 3, 2.0/size).map!(y => |
| iota(-1.5, 0.5, 2.0/size).map!(x => |
| cast(ubyte)(1+ |
| recurrence!((a, n) => x + y*1i + a[n-1]^^2)(0+0i) |
| .take(ubyte.max) |
| .countUntil!(z => z.re^^2 + z.im^^2 > 4)) |
| ) |
| ) |
| .copy(stdout.lockingBinaryWriter); |
| } |
| --- |
| */ |
| auto lockingBinaryWriter() |
| { |
| alias LockingBinaryWriterImpl = BinaryWriterImpl!true; |
| |
| version (Windows) |
| { |
| import std.typecons : RefCounted; |
| alias LockingBinaryWriter = RefCounted!LockingBinaryWriterImpl; |
| } |
| else |
| alias LockingBinaryWriter = LockingBinaryWriterImpl; |
| |
| return LockingBinaryWriter(this); |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.mutation : reverse; |
| import std.exception : collectException; |
| static import std.file; |
| import std.range : only, retro; |
| import std.string : format; |
| |
| auto deleteme = testFilename(); |
| scope(exit) collectException(std.file.remove(deleteme)); |
| auto output = File(deleteme, "wb"); |
| auto writer = output.lockingBinaryWriter(); |
| auto input = File(deleteme, "rb"); |
| |
| T[] readExact(T)(T[] buf) |
| { |
| auto result = input.rawRead(buf); |
| assert(result.length == buf.length, |
| "Read %d out of %d bytes" |
| .format(result.length, buf.length)); |
| return result; |
| } |
| |
| // test raw values |
| ubyte byteIn = 42; |
| byteIn.only.copy(writer); output.flush(); |
| ubyte byteOut = readExact(new ubyte[1])[0]; |
| assert(byteIn == byteOut); |
| |
| // test arrays |
| ubyte[] bytesIn = [1, 2, 3, 4, 5]; |
| bytesIn.copy(writer); output.flush(); |
| ubyte[] bytesOut = readExact(new ubyte[bytesIn.length]); |
| scope(failure) .writeln(bytesOut); |
| assert(bytesIn == bytesOut); |
| |
| // test ranges of values |
| bytesIn.retro.copy(writer); output.flush(); |
| bytesOut = readExact(bytesOut); |
| bytesOut.reverse(); |
| assert(bytesIn == bytesOut); |
| |
| // test string |
| "foobar".copy(writer); output.flush(); |
| char[] charsOut = readExact(new char[6]); |
| assert(charsOut == "foobar"); |
| |
| // test ranges of arrays |
| only("foo", "bar").copy(writer); output.flush(); |
| charsOut = readExact(charsOut); |
| assert(charsOut == "foobar"); |
| |
| // test that we are writing arrays as is, |
| // without UTF-8 transcoding |
| "foo"d.copy(writer); output.flush(); |
| dchar[] dcharsOut = readExact(new dchar[3]); |
| assert(dcharsOut == "foo"); |
| } |
| |
| /// Get the size of the file, ulong.max if file is not searchable, but still throws if an actual error occurs. |
| @property ulong size() @safe |
| { |
| import std.exception : collectException; |
| |
| ulong pos = void; |
| if (collectException(pos = tell)) return ulong.max; |
| scope(exit) seek(pos); |
| seek(0, SEEK_END); |
| return tell; |
| } |
| } |
| |
| @system unittest |
| { |
| @system struct SystemToString |
| { |
| string toString() |
| { |
| return "system"; |
| } |
| } |
| |
| @trusted struct TrustedToString |
| { |
| string toString() |
| { |
| return "trusted"; |
| } |
| } |
| |
| @safe struct SafeToString |
| { |
| string toString() |
| { |
| return "safe"; |
| } |
| } |
| |
| @system void systemTests() |
| { |
| //system code can write to files/stdout with anything! |
| if (false) |
| { |
| auto f = File(); |
| |
| f.write("just a string"); |
| f.write("string with arg: ", 47); |
| f.write(SystemToString()); |
| f.write(TrustedToString()); |
| f.write(SafeToString()); |
| |
| write("just a string"); |
| write("string with arg: ", 47); |
| write(SystemToString()); |
| write(TrustedToString()); |
| write(SafeToString()); |
| |
| f.writeln("just a string"); |
| f.writeln("string with arg: ", 47); |
| f.writeln(SystemToString()); |
| f.writeln(TrustedToString()); |
| f.writeln(SafeToString()); |
| |
| writeln("just a string"); |
| writeln("string with arg: ", 47); |
| writeln(SystemToString()); |
| writeln(TrustedToString()); |
| writeln(SafeToString()); |
| |
| f.writef("string with arg: %s", 47); |
| f.writef("%s", SystemToString()); |
| f.writef("%s", TrustedToString()); |
| f.writef("%s", SafeToString()); |
| |
| writef("string with arg: %s", 47); |
| writef("%s", SystemToString()); |
| writef("%s", TrustedToString()); |
| writef("%s", SafeToString()); |
| |
| f.writefln("string with arg: %s", 47); |
| f.writefln("%s", SystemToString()); |
| f.writefln("%s", TrustedToString()); |
| f.writefln("%s", SafeToString()); |
| |
| writefln("string with arg: %s", 47); |
| writefln("%s", SystemToString()); |
| writefln("%s", TrustedToString()); |
| writefln("%s", SafeToString()); |
| } |
| } |
| |
| @safe void safeTests() |
| { |
| auto f = File(); |
| |
| //safe code can write to files only with @safe and @trusted code... |
| if (false) |
| { |
| f.write("just a string"); |
| f.write("string with arg: ", 47); |
| f.write(TrustedToString()); |
| f.write(SafeToString()); |
| |
| write("just a string"); |
| write("string with arg: ", 47); |
| write(TrustedToString()); |
| write(SafeToString()); |
| |
| f.writeln("just a string"); |
| f.writeln("string with arg: ", 47); |
| f.writeln(TrustedToString()); |
| f.writeln(SafeToString()); |
| |
| writeln("just a string"); |
| writeln("string with arg: ", 47); |
| writeln(TrustedToString()); |
| writeln(SafeToString()); |
| |
| f.writef("string with arg: %s", 47); |
| f.writef("%s", TrustedToString()); |
| f.writef("%s", SafeToString()); |
| |
| writef("string with arg: %s", 47); |
| writef("%s", TrustedToString()); |
| writef("%s", SafeToString()); |
| |
| f.writefln("string with arg: %s", 47); |
| f.writefln("%s", TrustedToString()); |
| f.writefln("%s", SafeToString()); |
| |
| writefln("string with arg: %s", 47); |
| writefln("%s", TrustedToString()); |
| writefln("%s", SafeToString()); |
| } |
| |
| static assert(!__traits(compiles, f.write(SystemToString().toString()))); |
| static assert(!__traits(compiles, f.writeln(SystemToString()))); |
| static assert(!__traits(compiles, f.writef("%s", SystemToString()))); |
| static assert(!__traits(compiles, f.writefln("%s", SystemToString()))); |
| |
| static assert(!__traits(compiles, write(SystemToString().toString()))); |
| static assert(!__traits(compiles, writeln(SystemToString()))); |
| static assert(!__traits(compiles, writef("%s", SystemToString()))); |
| static assert(!__traits(compiles, writefln("%s", SystemToString()))); |
| } |
| |
| systemTests(); |
| safeTests(); |
| } |
| |
| @safe unittest |
| { |
| import std.exception : collectException; |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| scope(exit) collectException(std.file.remove(deleteme)); |
| std.file.write(deleteme, "1 2 3"); |
| auto f = File(deleteme); |
| assert(f.size == 5); |
| assert(f.tell == 0); |
| } |
| |
| @system unittest |
| { |
| // @system due to readln |
| static import std.file; |
| import std.range : chain, only, repeat; |
| import std.range.primitives : isOutputRange; |
| |
| auto deleteme = testFilename(); |
| scope(exit) std.file.remove(deleteme); |
| |
| { |
| File f = File(deleteme, "w"); |
| auto writer = f.lockingTextWriter(); |
| static assert(isOutputRange!(typeof(writer), dchar)); |
| writer.put("日本語"); |
| writer.put("日本語"w); |
| writer.put("日本語"d); |
| writer.put('日'); |
| writer.put(chain(only('本'), only('語'))); |
| writer.put(repeat('#', 12)); // BUG 11945 |
| writer.put(cast(immutable(ubyte)[])"日本語"); // Bug 17229 |
| } |
| assert(File(deleteme).readln() == "日本語日本語日本語日本語############日本語"); |
| } |
| |
| @safe unittest |
| { |
| import std.exception : collectException; |
| auto e = collectException({ File f; f.writeln("Hello!"); }()); |
| assert(e && e.msg == "Attempting to write to closed File"); |
| } |
| |
| /// Used to specify the lock type for $(D File.lock) and $(D File.tryLock). |
| enum LockType |
| { |
| /** |
| * Specifies a _read (shared) lock. A _read lock denies all processes |
| * write access to the specified region of the file, including the |
| * process that first locks the region. All processes can _read the |
| * locked region. Multiple simultaneous _read locks are allowed, as |
| * long as there are no exclusive locks. |
| */ |
| read, |
| |
| /** |
| * Specifies a read/write (exclusive) lock. A read/write lock denies all |
| * other processes both read and write access to the locked file region. |
| * If a segment has an exclusive lock, it may not have any shared locks |
| * or other exclusive locks. |
| */ |
| readWrite |
| } |
| |
| struct LockingTextReader |
| { |
| private File _f; |
| private char _front; |
| private bool _hasChar; |
| |
| this(File f) |
| { |
| import std.exception : enforce; |
| enforce(f.isOpen, "LockingTextReader: File must be open"); |
| _f = f; |
| FLOCK(_f._p.handle); |
| } |
| |
| this(this) |
| { |
| FLOCK(_f._p.handle); |
| } |
| |
| ~this() |
| { |
| if (_hasChar) |
| ungetc(_front, cast(FILE*)_f._p.handle); |
| |
| // File locking has its own reference count |
| if (_f.isOpen) FUNLOCK(_f._p.handle); |
| } |
| |
| void opAssign(LockingTextReader r) |
| { |
| import std.algorithm.mutation : swap; |
| swap(this, r); |
| } |
| |
| @property bool empty() |
| { |
| if (!_hasChar) |
| { |
| if (!_f.isOpen || _f.eof) |
| return true; |
| immutable int c = FGETC(cast(_iobuf*) _f._p.handle); |
| if (c == EOF) |
| { |
| .destroy(_f); |
| return true; |
| } |
| _front = cast(char) c; |
| _hasChar = true; |
| } |
| return false; |
| } |
| |
| @property char front() |
| { |
| if (!_hasChar) |
| { |
| version (assert) |
| { |
| import core.exception : RangeError; |
| if (empty) |
| throw new RangeError(); |
| } |
| else |
| { |
| empty; |
| } |
| } |
| return _front; |
| } |
| |
| void popFront() |
| { |
| if (!_hasChar) |
| empty; |
| _hasChar = false; |
| } |
| } |
| |
| @system unittest |
| { |
| // @system due to readf |
| static import std.file; |
| import std.range.primitives : isInputRange; |
| |
| static assert(isInputRange!LockingTextReader); |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "1 2 3"); |
| scope(exit) std.file.remove(deleteme); |
| int x, y; |
| auto f = File(deleteme); |
| f.readf("%s ", &x); |
| assert(x == 1); |
| f.readf("%d ", &x); |
| assert(x == 2); |
| f.readf("%d ", &x); |
| assert(x == 3); |
| } |
| |
| @system unittest // bugzilla 13686 |
| { |
| import std.algorithm.comparison : equal; |
| static import std.file; |
| import std.utf : byDchar; |
| |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "Тест"); |
| scope(exit) std.file.remove(deleteme); |
| |
| string s; |
| File(deleteme).readf("%s", &s); |
| assert(s == "Тест"); |
| |
| auto ltr = LockingTextReader(File(deleteme)).byDchar; |
| assert(equal(ltr, "Тест".byDchar)); |
| } |
| |
| @system unittest // bugzilla 12320 |
| { |
| static import std.file; |
| auto deleteme = testFilename(); |
| std.file.write(deleteme, "ab"); |
| scope(exit) std.file.remove(deleteme); |
| auto ltr = LockingTextReader(File(deleteme)); |
| assert(ltr.front == 'a'); |
| ltr.popFront(); |
| assert(ltr.front == 'b'); |
| ltr.popFront(); |
| assert(ltr.empty); |
| } |
| |
| @system unittest // bugzilla 14861 |
| { |
| // @system due to readf |
| static import std.file; |
| auto deleteme = testFilename(); |
| File fw = File(deleteme, "w"); |
| for (int i; i != 5000; i++) |
| fw.writeln(i, ";", "Иванов;Пётр;Петрович"); |
| fw.close(); |
| scope(exit) std.file.remove(deleteme); |
| // Test read |
| File fr = File(deleteme, "r"); |
| scope (exit) fr.close(); |
| int nom; string fam, nam, ot; |
| // Error format read |
| while (!fr.eof) |
| fr.readf("%s;%s;%s;%s\n", &nom, &fam, &nam, &ot); |
| } |
| |
| /** |
| * Indicates whether $(D T) is a file handle, i.e. the type |
| * is implicitly convertable to $(LREF File) or a pointer to a |
| * $(REF FILE, core,stdc,stdio). |
| * |
| * Returns: |
| * `true` if `T` is a file handle, `false` otherwise. |
| */ |
| template isFileHandle(T) |
| { |
| enum isFileHandle = is(T : FILE*) || |
| is(T : File); |
| } |
| |
| /// |
| @safe unittest |
| { |
| static assert(isFileHandle!(FILE*)); |
| static assert(isFileHandle!(File)); |
| } |
| |
| /** |
| * Property used by writeln/etc. so it can infer @safe since stdout is __gshared |
| */ |
| private @property File trustedStdout() @trusted |
| { |
| return stdout; |
| } |
| |
| /*********************************** |
| For each argument $(D arg) in $(D args), format the argument (using |
| $(REF to, std,conv)) and write the resulting |
| string to $(D args[0]). A call without any arguments will fail to |
| compile. |
| |
| Params: |
| args = the items to write to `stdout` |
| |
| Throws: In case of an I/O error, throws an $(D StdioException). |
| |
| Example: |
| Reads `stdin` and writes it to `stdout` with an argument |
| counter. |
| --- |
| import std.stdio; |
| |
| void main() |
| { |
| string line; |
| |
| for (size_t count = 0; (line = readln) !is null; count++) |
| { |
| write("Input ", count, ": ", line, "\n"); |
| } |
| } |
| --- |
| */ |
| void write(T...)(T args) |
| if (!is(T[0] : File)) |
| { |
| trustedStdout.write(args); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| void[] buf; |
| if (false) write(buf); |
| // test write |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| f.write("Hello, ", "world number ", 42, "!"); |
| f.close(); |
| scope(exit) { std.file.remove(deleteme); } |
| assert(cast(char[]) std.file.read(deleteme) == "Hello, world number 42!"); |
| } |
| |
| /*********************************** |
| * Equivalent to `write(args, '\n')`. Calling `writeln` without |
| * arguments is valid and just prints a newline to the standard |
| * output. |
| * |
| * Params: |
| * args = the items to write to `stdout` |
| * |
| * Throws: |
| * In case of an I/O error, throws an $(LREF StdioException). |
| * Example: |
| * Reads $(D stdin) and writes it to $(D stdout) with a argument |
| * counter. |
| --- |
| import std.stdio; |
| |
| void main() |
| { |
| string line; |
| |
| for (size_t count = 0; (line = readln) !is null; count++) |
| { |
| writeln("Input ", count, ": ", line); |
| } |
| } |
| --- |
| */ |
| void writeln(T...)(T args) |
| { |
| import std.traits : isAggregateType; |
| static if (T.length == 0) |
| { |
| import std.exception : enforce; |
| |
| enforce(fputc('\n', .trustedStdout._p.handle) != EOF, "fputc failed"); |
| } |
| else static if (T.length == 1 && |
| is(typeof(args[0]) : const(char)[]) && |
| !is(typeof(args[0]) == enum) && |
| !is(Unqual!(typeof(args[0])) == typeof(null)) && |
| !isAggregateType!(typeof(args[0]))) |
| { |
| import std.traits : isStaticArray; |
| |
| // Specialization for strings - a very frequent case |
| auto w = .trustedStdout.lockingTextWriter(); |
| |
| static if (isStaticArray!(typeof(args[0]))) |
| { |
| w.put(args[0][]); |
| } |
| else |
| { |
| w.put(args[0]); |
| } |
| w.put('\n'); |
| } |
| else |
| { |
| // Most general instance |
| trustedStdout.write(args, '\n'); |
| } |
| } |
| |
| @safe unittest |
| { |
| // Just make sure the call compiles |
| if (false) writeln(); |
| |
| if (false) writeln("wyda"); |
| |
| // bug 8040 |
| if (false) writeln(null); |
| if (false) writeln(">", null, "<"); |
| |
| // Bugzilla 14041 |
| if (false) |
| { |
| char[8] a; |
| writeln(a); |
| } |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| // test writeln |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) { std.file.remove(deleteme); } |
| f.writeln("Hello, ", "world number ", 42, "!"); |
| f.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, world number 42!\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, world number 42!\n"); |
| |
| // test writeln on stdout |
| auto saveStdout = stdout; |
| scope(exit) stdout = saveStdout; |
| stdout.open(deleteme, "w"); |
| writeln("Hello, ", "world number ", 42, "!"); |
| stdout.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, world number 42!\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, world number 42!\n"); |
| |
| stdout.open(deleteme, "w"); |
| writeln("Hello!"c); |
| writeln("Hello!"w); // bug 8386 |
| writeln("Hello!"d); // bug 8386 |
| writeln("embedded\0null"c); // bug 8730 |
| stdout.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello!\r\nHello!\r\nHello!\r\nembedded\0null\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello!\nHello!\nHello!\nembedded\0null\n"); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) { std.file.remove(deleteme); } |
| |
| enum EI : int { A, B } |
| enum ED : double { A, B } |
| enum EC : char { A, B } |
| enum ES : string { A = "aaa", B = "bbb" } |
| |
| f.writeln(EI.A); // false, but A on 2.058 |
| f.writeln(EI.B); // true, but B on 2.058 |
| |
| f.writeln(ED.A); // A |
| f.writeln(ED.B); // B |
| |
| f.writeln(EC.A); // A |
| f.writeln(EC.B); // B |
| |
| f.writeln(ES.A); // A |
| f.writeln(ES.B); // B |
| |
| f.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "A\r\nB\r\nA\r\nB\r\nA\r\nB\r\nA\r\nB\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "A\nB\nA\nB\nA\nB\nA\nB\n"); |
| } |
| |
| @system unittest |
| { |
| static auto useInit(T)(T ltw) |
| { |
| T val; |
| val = ltw; |
| val = T.init; |
| return val; |
| } |
| useInit(stdout.lockingTextWriter()); |
| } |
| |
| |
| /*********************************** |
| Writes formatted data to standard output (without a trailing newline). |
| |
| Params: |
| fmt = The $(LINK2 std_format.html#format-string, format string). |
| When passed as a compile-time argument, the string will be statically checked |
| against the argument types passed. |
| args = Items to write. |
| |
| Note: In older versions of Phobos, it used to be possible to write: |
| |
| ------ |
| writef(stderr, "%s", "message"); |
| ------ |
| |
| to print a message to $(D stderr). This syntax is no longer supported, and has |
| been superceded by: |
| |
| ------ |
| stderr.writef("%s", "message"); |
| ------ |
| |
| */ |
| void writef(alias fmt, A...)(A args) |
| if (isSomeString!(typeof(fmt))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(fmt, A); |
| static assert(!e, e.msg); |
| return .writef(fmt, args); |
| } |
| |
| /// ditto |
| void writef(Char, A...)(in Char[] fmt, A args) |
| { |
| trustedStdout.writef(fmt, args); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| // test writef |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) { std.file.remove(deleteme); } |
| f.writef!"Hello, %s world number %s!"("nice", 42); |
| f.close(); |
| assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); |
| // test write on stdout |
| auto saveStdout = stdout; |
| scope(exit) stdout = saveStdout; |
| stdout.open(deleteme, "w"); |
| writef!"Hello, %s world number %s!"("nice", 42); |
| stdout.close(); |
| assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); |
| } |
| |
| /*********************************** |
| * Equivalent to $(D writef(fmt, args, '\n')). |
| */ |
| void writefln(alias fmt, A...)(A args) |
| if (isSomeString!(typeof(fmt))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(fmt, A); |
| static assert(!e, e.msg); |
| return .writefln(fmt, args); |
| } |
| |
| /// ditto |
| void writefln(Char, A...)(in Char[] fmt, A args) |
| { |
| trustedStdout.writefln(fmt, args); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| // test File.writefln |
| auto deleteme = testFilename(); |
| auto f = File(deleteme, "w"); |
| scope(exit) { std.file.remove(deleteme); } |
| f.writefln!"Hello, %s world number %s!"("nice", 42); |
| f.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, nice world number 42!\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, nice world number 42!\n", |
| cast(char[]) std.file.read(deleteme)); |
| |
| // test writefln |
| auto saveStdout = stdout; |
| scope(exit) stdout = saveStdout; |
| stdout.open(deleteme, "w"); |
| writefln!"Hello, %s world number %s!"("nice", 42); |
| stdout.close(); |
| version (Windows) |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, nice world number 42!\r\n"); |
| else |
| assert(cast(char[]) std.file.read(deleteme) == |
| "Hello, nice world number 42!\n"); |
| } |
| |
| /** |
| * Reads formatted data from $(D stdin) using $(REF formattedRead, std,_format). |
| * Params: |
| * format = The $(LINK2 std_format.html#_format-string, _format string). |
| * When passed as a compile-time argument, the string will be statically checked |
| * against the argument types passed. |
| * args = Items to be read. |
| * Example: |
| ---- |
| // test.d |
| void main() |
| { |
| import std.stdio; |
| foreach (_; 0 .. 3) |
| { |
| int a; |
| readf!" %d"(a); |
| writeln(++a); |
| } |
| } |
| ---- |
| $(CONSOLE |
| % echo "1 2 3" | rdmd test.d |
| 2 |
| 3 |
| 4 |
| ) |
| */ |
| uint readf(alias format, A...)(auto ref A args) |
| if (isSomeString!(typeof(format))) |
| { |
| import std.format : checkFormatException; |
| |
| alias e = checkFormatException!(format, A); |
| static assert(!e, e.msg); |
| return .readf(format, args); |
| } |
| |
| /// ditto |
| uint readf(A...)(in char[] format, auto ref A args) |
| { |
| return stdin.readf(format, args); |
| } |
| |
| @system unittest |
| { |
| float f; |
| if (false) uint x = readf("%s", &f); |
| |
| char a; |
| wchar b; |
| dchar c; |
| if (false) readf("%s %s %s", a, b, c); |
| // backwards compatibility with pointers |
| if (false) readf("%s %s %s", a, &b, c); |
| if (false) readf("%s %s %s", &a, &b, &c); |
| } |
| |
| /********************************** |
| * Read line from $(D stdin). |
| * |
| * This version manages its own read buffer, which means one memory allocation per call. If you are not |
| * retaining a reference to the read data, consider the $(D readln(buf)) version, which may offer |
| * better performance as it can reuse its read buffer. |
| * |
| * Returns: |
| * The line that was read, including the line terminator character. |
| * Params: |
| * S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to $(D string). |
| * terminator = Line terminator (by default, $(D '\n')). |
| * Note: |
| * String terminators are not supported due to ambiguity with readln(buf) below. |
| * Throws: |
| * $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error. |
| * Example: |
| * Reads $(D stdin) and writes it to $(D stdout). |
| --- |
| import std.stdio; |
| |
| void main() |
| { |
| string line; |
| while ((line = readln()) !is null) |
| write(line); |
| } |
| --- |
| */ |
| S readln(S = string)(dchar terminator = '\n') |
| if (isSomeString!S) |
| { |
| return stdin.readln!S(terminator); |
| } |
| |
| /********************************** |
| * Read line from $(D stdin) and write it to buf[], including terminating character. |
| * |
| * This can be faster than $(D line = readln()) because you can reuse |
| * the buffer for each call. Note that reusing the buffer means that you |
| * must copy the previous contents if you wish to retain them. |
| * |
| * Returns: |
| * $(D size_t) 0 for end of file, otherwise number of characters read |
| * Params: |
| * buf = Buffer used to store the resulting line data. buf is resized as necessary. |
| * terminator = Line terminator (by default, $(D '\n')). Use $(REF newline, std,ascii) |
| * for portability (unless the file was opened in text mode). |
| * Throws: |
| * $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error. |
| * Example: |
| * Reads $(D stdin) and writes it to $(D stdout). |
| --- |
| import std.stdio; |
| |
| void main() |
| { |
| char[] buf; |
| while (readln(buf)) |
| write(buf); |
| } |
| --- |
| */ |
| size_t readln(C)(ref C[] buf, dchar terminator = '\n') |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) |
| { |
| return stdin.readln(buf, terminator); |
| } |
| |
| /** ditto */ |
| size_t readln(C, R)(ref C[] buf, R terminator) |
| if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && |
| isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) |
| { |
| return stdin.readln(buf, terminator); |
| } |
| |
| @safe unittest |
| { |
| import std.meta : AliasSeq; |
| |
| //we can't actually test readln, so at the very least, |
| //we test compilability |
| void foo() |
| { |
| readln(); |
| readln('\t'); |
| foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) |
| { |
| readln!String(); |
| readln!String('\t'); |
| } |
| foreach (String; AliasSeq!(char[], wchar[], dchar[])) |
| { |
| String buf; |
| readln(buf); |
| readln(buf, '\t'); |
| readln(buf, "<br />"); |
| } |
| } |
| } |
| |
| /* |
| * Convenience function that forwards to $(D core.sys.posix.stdio.fopen) |
| * (to $(D _wfopen) on Windows) |
| * with appropriately-constructed C-style strings. |
| */ |
| private FILE* fopen(R1, R2)(R1 name, R2 mode = "r") |
| if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) && |
| (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2)) |
| { |
| import std.internal.cstring : tempCString; |
| |
| auto namez = name.tempCString!FSChar(); |
| auto modez = mode.tempCString!FSChar(); |
| |
| static fopenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc |
| { |
| version (Windows) |
| { |
| return _wfopen(namez, modez); |
| } |
| else version (Posix) |
| { |
| /* |
| * The new opengroup large file support API is transparently |
| * included in the normal C bindings. http://opengroup.org/platform/lfs.html#1.0 |
| * if _FILE_OFFSET_BITS in druntime is 64, off_t is 64 bit and |
| * the normal functions work fine. If not, then large file support |
| * probably isn't available. Do not use the old transitional API |
| * (the native extern(C) fopen64, http://www.unix.org/version2/whatsnew/lfs20mar.html#3.0) |
| */ |
| import core.sys.posix.stdio : fopen; |
| return fopen(namez, modez); |
| } |
| else |
| { |
| return .fopen(namez, modez); |
| } |
| } |
| return fopenImpl(namez, modez); |
| } |
| |
| version (Posix) |
| { |
| /*********************************** |
| * Convenience function that forwards to $(D core.sys.posix.stdio.popen) |
| * with appropriately-constructed C-style strings. |
| */ |
| FILE* popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc |
| if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) && |
| (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2)) |
| { |
| import std.internal.cstring : tempCString; |
| |
| auto namez = name.tempCString!FSChar(); |
| auto modez = mode.tempCString!FSChar(); |
| |
| static popenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc |
| { |
| import core.sys.posix.stdio : popen; |
| return popen(namez, modez); |
| } |
| return popenImpl(namez, modez); |
| } |
| } |
| |
| /* |
| * Convenience function that forwards to $(D core.stdc.stdio.fwrite) |
| */ |
| private auto trustedFwrite(T)(FILE* f, const T[] obj) @trusted |
| { |
| return fwrite(obj.ptr, T.sizeof, obj.length, f); |
| } |
| |
| /* |
| * Convenience function that forwards to $(D core.stdc.stdio.fread) |
| */ |
| private auto trustedFread(T)(FILE* f, T[] obj) @trusted |
| { |
| return fread(obj.ptr, T.sizeof, obj.length, f); |
| } |
| |
| /** |
| * Iterates through the lines of a file by using $(D foreach). |
| * |
| * Example: |
| * |
| --------- |
| void main() |
| { |
| foreach (string line; lines(stdin)) |
| { |
| ... use line ... |
| } |
| } |
| --------- |
| The line terminator ($(D '\n') by default) is part of the string read (it |
| could be missing in the last line of the file). Several types are |
| supported for $(D line), and the behavior of $(D lines) |
| changes accordingly: |
| |
| $(OL $(LI If $(D line) has type $(D string), $(D |
| wstring), or $(D dstring), a new string of the respective type |
| is allocated every read.) $(LI If $(D line) has type $(D |
| char[]), $(D wchar[]), $(D dchar[]), the line's content |
| will be reused (overwritten) across reads.) $(LI If $(D line) |
| has type $(D immutable(ubyte)[]), the behavior is similar to |
| case (1), except that no UTF checking is attempted upon input.) $(LI |
| If $(D line) has type $(D ubyte[]), the behavior is |
| similar to case (2), except that no UTF checking is attempted upon |
| input.)) |
| |
| In all cases, a two-symbols versions is also accepted, in which case |
| the first symbol (of integral type, e.g. $(D ulong) or $(D |
| uint)) tracks the zero-based number of the current line. |
| |
| Example: |
| ---- |
| foreach (ulong i, string line; lines(stdin)) |
| { |
| ... use line ... |
| } |
| ---- |
| |
| In case of an I/O error, an $(D StdioException) is thrown. |
| |
| See_Also: |
| $(LREF byLine) |
| */ |
| |
| struct lines |
| { |
| private File f; |
| private dchar terminator = '\n'; |
| |
| /** |
| Constructor. |
| Params: |
| f = File to read lines from. |
| terminator = Line separator ($(D '\n') by default). |
| */ |
| this(File f, dchar terminator = '\n') |
| { |
| this.f = f; |
| this.terminator = terminator; |
| } |
| |
| int opApply(D)(scope D dg) |
| { |
| import std.traits : Parameters; |
| alias Parms = Parameters!(dg); |
| static if (isSomeString!(Parms[$ - 1])) |
| { |
| enum bool duplicate = is(Parms[$ - 1] == string) |
| || is(Parms[$ - 1] == wstring) || is(Parms[$ - 1] == dstring); |
| int result = 0; |
| static if (is(Parms[$ - 1] : const(char)[])) |
| alias C = char; |
| else static if (is(Parms[$ - 1] : const(wchar)[])) |
| alias C = wchar; |
| else static if (is(Parms[$ - 1] : const(dchar)[])) |
| alias C = dchar; |
| C[] line; |
| static if (Parms.length == 2) |
| Parms[0] i = 0; |
| for (;;) |
| { |
| import std.conv : to; |
| |
| if (!f.readln(line, terminator)) break; |
| auto copy = to!(Parms[$ - 1])(line); |
| static if (Parms.length == 2) |
| { |
| result = dg(i, copy); |
| ++i; |
| } |
| else |
| { |
| result = dg(copy); |
| } |
| if (result != 0) break; |
| } |
| return result; |
| } |
| else |
| { |
| // raw read |
| return opApplyRaw(dg); |
| } |
| } |
| // no UTF checking |
| int opApplyRaw(D)(scope D dg) |
| { |
| import std.conv : to; |
| import std.exception : assumeUnique; |
| import std.traits : Parameters; |
| |
| alias Parms = Parameters!(dg); |
| enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]); |
| int result = 1; |
| int c = void; |
| FLOCK(f._p.handle); |
| scope(exit) FUNLOCK(f._p.handle); |
| ubyte[] buffer; |
| static if (Parms.length == 2) |
| Parms[0] line = 0; |
| while ((c = FGETC(cast(_iobuf*) f._p.handle)) != -1) |
| { |
| buffer ~= to!(ubyte)(c); |
| if (c == terminator) |
| { |
| static if (duplicate) |
| auto arg = assumeUnique(buffer); |
| else |
| alias arg = buffer; |
| // unlock the file while calling the delegate |
| FUNLOCK(f._p.handle); |
| scope(exit) FLOCK(f._p.handle); |
| static if (Parms.length == 1) |
| { |
| result = dg(arg); |
| } |
| else |
| { |
| result = dg(line, arg); |
| ++line; |
| } |
| if (result) break; |
| static if (!duplicate) |
| buffer.length = 0; |
| } |
| } |
| // can only reach when FGETC returned -1 |
| if (!f.eof) throw new StdioException("Error in reading file"); // error occured |
| return result; |
| } |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| import std.meta : AliasSeq; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| auto deleteme = testFilename(); |
| scope(exit) { std.file.remove(deleteme); } |
| |
| alias TestedWith = |
| AliasSeq!(string, wstring, dstring, |
| char[], wchar[], dchar[]); |
| foreach (T; TestedWith) |
| { |
| // test looping with an empty file |
| std.file.write(deleteme, ""); |
| auto f = File(deleteme, "r"); |
| foreach (T line; lines(f)) |
| { |
| assert(false); |
| } |
| f.close(); |
| |
| // test looping with a file with three lines |
| std.file.write(deleteme, "Line one\nline two\nline three\n"); |
| f.open(deleteme, "r"); |
| uint i = 0; |
| foreach (T line; lines(f)) |
| { |
| if (i == 0) assert(line == "Line one\n"); |
| else if (i == 1) assert(line == "line two\n"); |
| else if (i == 2) assert(line == "line three\n"); |
| else assert(false); |
| ++i; |
| } |
| f.close(); |
| |
| // test looping with a file with three lines, last without a newline |
| std.file.write(deleteme, "Line one\nline two\nline three"); |
| f.open(deleteme, "r"); |
| i = 0; |
| foreach (T line; lines(f)) |
| { |
| if (i == 0) assert(line == "Line one\n"); |
| else if (i == 1) assert(line == "line two\n"); |
| else if (i == 2) assert(line == "line three"); |
| else assert(false); |
| ++i; |
| } |
| f.close(); |
| } |
| |
| // test with ubyte[] inputs |
| alias TestedWith2 = AliasSeq!(immutable(ubyte)[], ubyte[]); |
| foreach (T; TestedWith2) |
| { |
| // test looping with an empty file |
| std.file.write(deleteme, ""); |
| auto f = File(deleteme, "r"); |
| foreach (T line; lines(f)) |
| { |
| assert(false); |
| } |
| f.close(); |
| |
| // test looping with a file with three lines |
| std.file.write(deleteme, "Line one\nline two\nline three\n"); |
| f.open(deleteme, "r"); |
| uint i = 0; |
| foreach (T line; lines(f)) |
| { |
| if (i == 0) assert(cast(char[]) line == "Line one\n"); |
| else if (i == 1) assert(cast(char[]) line == "line two\n", |
| T.stringof ~ " " ~ cast(char[]) line); |
| else if (i == 2) assert(cast(char[]) line == "line three\n"); |
| else assert(false); |
| ++i; |
| } |
| f.close(); |
| |
| // test looping with a file with three lines, last without a newline |
| std.file.write(deleteme, "Line one\nline two\nline three"); |
| f.open(deleteme, "r"); |
| i = 0; |
| foreach (T line; lines(f)) |
| { |
| if (i == 0) assert(cast(char[]) line == "Line one\n"); |
| else if (i == 1) assert(cast(char[]) line == "line two\n"); |
| else if (i == 2) assert(cast(char[]) line == "line three"); |
| else assert(false); |
| ++i; |
| } |
| f.close(); |
| |
| } |
| |
| foreach (T; AliasSeq!(ubyte[])) |
| { |
| // test looping with a file with three lines, last without a newline |
| // using a counter too this time |
| std.file.write(deleteme, "Line one\nline two\nline three"); |
| auto f = File(deleteme, "r"); |
| uint i = 0; |
| foreach (ulong j, T line; lines(f)) |
| { |
| if (i == 0) assert(cast(char[]) line == "Line one\n"); |
| else if (i == 1) assert(cast(char[]) line == "line two\n"); |
| else if (i == 2) assert(cast(char[]) line == "line three"); |
| else assert(false); |
| ++i; |
| } |
| f.close(); |
| } |
| } |
| |
| /** |
| Iterates through a file a chunk at a time by using $(D foreach). |
| |
| Example: |
| |
| --------- |
| void main() |
| { |
| foreach (ubyte[] buffer; chunks(stdin, 4096)) |
| { |
| ... use buffer ... |
| } |
| } |
| --------- |
| |
| The content of $(D buffer) is reused across calls. In the |
| example above, $(D buffer.length) is 4096 for all iterations, |
| except for the last one, in which case $(D buffer.length) may |
| be less than 4096 (but always greater than zero). |
| |
| In case of an I/O error, an $(D StdioException) is thrown. |
| */ |
| auto chunks(File f, size_t size) |
| { |
| return ChunksImpl(f, size); |
| } |
| private struct ChunksImpl |
| { |
| private File f; |
| private size_t size; |
| // private string fileName; // Currently, no use |
| |
| this(File f, size_t size) |
| in |
| { |
| assert(size, "size must be larger than 0"); |
| } |
| body |
| { |
| this.f = f; |
| this.size = size; |
| } |
| |
| int opApply(D)(scope D dg) |
| { |
| import core.stdc.stdlib : alloca; |
| enum maxStackSize = 1024 * 16; |
| ubyte[] buffer = void; |
| if (size < maxStackSize) |
| buffer = (cast(ubyte*) alloca(size))[0 .. size]; |
| else |
| buffer = new ubyte[size]; |
| size_t r = void; |
| int result = 1; |
| uint tally = 0; |
| while ((r = trustedFread(f._p.handle, buffer)) > 0) |
| { |
| assert(r <= size); |
| if (r != size) |
| { |
| // error occured |
| if (!f.eof) throw new StdioException(null); |
| buffer.length = r; |
| } |
| static if (is(typeof(dg(tally, buffer)))) |
| { |
| if ((result = dg(tally, buffer)) != 0) break; |
| } |
| else |
| { |
| if ((result = dg(buffer)) != 0) break; |
| } |
| ++tally; |
| } |
| return result; |
| } |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| scope(failure) printf("Failed test at line %d\n", __LINE__); |
| |
| auto deleteme = testFilename(); |
| scope(exit) { std.file.remove(deleteme); } |
| |
| // test looping with an empty file |
| std.file.write(deleteme, ""); |
| auto f = File(deleteme, "r"); |
| foreach (ubyte[] line; chunks(f, 4)) |
| { |
| assert(false); |
| } |
| f.close(); |
| |
| // test looping with a file with three lines |
| std.file.write(deleteme, "Line one\nline two\nline three\n"); |
| f = File(deleteme, "r"); |
| uint i = 0; |
| foreach (ubyte[] line; chunks(f, 3)) |
| { |
| if (i == 0) assert(cast(char[]) line == "Lin"); |
| else if (i == 1) assert(cast(char[]) line == "e o"); |
| else if (i == 2) assert(cast(char[]) line == "ne\n"); |
| else break; |
| ++i; |
| } |
| f.close(); |
| } |
| |
| |
| /** |
| Writes an array or range to a file. |
| Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)). |
| Similar to $(REF write, std,file), strings are written as-is, |
| rather than encoded according to the $(D File)'s $(HTTP |
| en.cppreference.com/w/c/io#Narrow_and_wide_orientation, |
| orientation). |
| */ |
| void toFile(T)(T data, string fileName) |
| if (is(typeof(copy(data, stdout.lockingBinaryWriter)))) |
| { |
| copy(data, File(fileName, "wb").lockingBinaryWriter); |
| } |
| |
| @system unittest |
| { |
| static import std.file; |
| |
| auto deleteme = testFilename(); |
| scope(exit) { std.file.remove(deleteme); } |
| |
| "Test".toFile(deleteme); |
| assert(std.file.readText(deleteme) == "Test"); |
| } |
| |
| /********************* |
| * Thrown if I/O errors happen. |
| */ |
| class StdioException : Exception |
| { |
| static import core.stdc.errno; |
| /// Operating system error code. |
| uint errno; |
| |
| /** |
| Initialize with a message and an error code. |
| */ |
| this(string message, uint e = core.stdc.errno.errno) @trusted |
| { |
| import std.exception : errnoString; |
| errno = e; |
| auto sysmsg = errnoString(errno); |
| // If e is 0, we don't use the system error message. (The message |
| // is "Success", which is rather pointless for an exception.) |
| super(e == 0 ? message |
| : (message ? message ~ " (" ~ sysmsg ~ ")" : sysmsg)); |
| } |
| |
| /** Convenience functions that throw an $(D StdioException). */ |
| static void opCall(string msg) |
| { |
| throw new StdioException(msg); |
| } |
| |
| /// ditto |
| static void opCall() |
| { |
| throw new StdioException(null, core.stdc.errno.errno); |
| } |
| } |
| |
| 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. |
| Bugs: |
| Due to $(LINK2 https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768), |
| it is thread un-safe to reassign `stdin` to a different `File` instance |
| than the default. |
| */ |
| alias stdin = makeGlobal!(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. |
| Bugs: |
| Due to $(LINK2 https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768), |
| it is thread un-safe to reassign `stdout` to a different `File` instance |
| than the default. |
| */ |
| alias stdout = makeGlobal!(StdFileHandle.stdout); |
| |
| /** |
| The standard error stream. |
| Bugs: |
| Due to $(LINK2 https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768), |
| it is thread un-safe to reassign `stderr` to a different `File` instance |
| than the default. |
| */ |
| alias stderr = makeGlobal!(StdFileHandle.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]; |
| char* lnptr = ln.ptr; |
| f.readln(ln); |
| |
| assert(ln == "abcd\n"); |
| char[] t = ln[0 .. 2]; |
| t ~= 't'; |
| assert(t == "abt"); |
| assert(ln == "abcd\n"); // bug 13856: ln stomped to "abtd" |
| |
| // it can also stomp the array length |
| ln = new char[4]; |
| lnptr = ln.ptr; |
| f.readln(ln); |
| assert(ln == "0123456789abcde\n"); |
| |
| char[100] buf; |
| ln = buf[]; |
| f.readln(ln); |
| assert(ln == "1234\n"); |
| assert(ln.ptr == buf.ptr); // avoid allocation, buffer is good enough |
| } |
| |
| /** Experimental network access via the File interface |
| |
| Opens a TCP connection to the given host and port, then returns |
| a File struct with read and write access through the same interface |
| as any other file (meaning writef and the byLine ranges work!). |
| |
| Authors: |
| Adam D. Ruppe |
| |
| Bugs: |
| Only works on Linux |
| */ |
| version (linux) |
| { |
| File openNetwork(string host, ushort port) |
| { |
| import core.stdc.string : memcpy; |
| import core.sys.posix.arpa.inet : htons; |
| import core.sys.posix.netdb : gethostbyname; |
| import core.sys.posix.netinet.in_ : sockaddr_in; |
| static import core.sys.posix.unistd; |
| static import sock = core.sys.posix.sys.socket; |
| import std.conv : to; |
| import std.exception : enforce; |
| import std.internal.cstring : tempCString; |
| |
| auto h = enforce( gethostbyname(host.tempCString()), |
| new StdioException("gethostbyname")); |
| |
| int s = sock.socket(sock.AF_INET, sock.SOCK_STREAM, 0); |
| enforce(s != -1, new StdioException("socket")); |
| |
| scope(failure) |
| { |
| // want to make sure it doesn't dangle if something throws. Upon |
| // normal exit, the File struct's reference counting takes care of |
| // closing, so we don't need to worry about success |
| core.sys.posix.unistd.close(s); |
| } |
| |
| sockaddr_in addr; |
| |
| addr.sin_family = sock.AF_INET; |
| addr.sin_port = htons(port); |
| memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length); |
| |
| enforce(sock.connect(s, cast(sock.sockaddr*) &addr, addr.sizeof) != -1, |
| new StdioException("Connect failed")); |
| |
| File f; |
| f.fdopen(s, "w+", host ~ ":" ~ to!string(port)); |
| return f; |
| } |
| } |
| |
| version (unittest) string testFilename(string file = __FILE__, size_t line = __LINE__) @safe |
| { |
| import std.conv : text; |
| import std.file : deleteme; |
| import std.path : baseName; |
| |
| // filename intentionally contains non-ASCII (Russian) characters for test Issue 7648 |
| return text(deleteme, "-детка.", baseName(file), ".", line); |
| } |