blob: bbf785773d48618c88d30ca13fc851aecb991297 [file] [log] [blame]
// 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, &current);
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