mirror of
https://github.com/dlang/phobos.git
synced 2025-04-27 13:40:20 +03:00

combines the very common constraint `isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R)` and adds a bunch of documentation with examples for users to understand it better. This should lower the neccessary needed technical insight to read basic docs, especially std.path and std.file docs.
5906 lines
169 KiB
D
5906 lines
169 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
$(SCRIPT inhibitQuickIndex = 1;)
|
|
$(DIVC quickindex,
|
|
$(BOOKTABLE,
|
|
$(TR $(TH Category) $(TH Symbols))
|
|
$(TR $(TD File handles) $(TD
|
|
$(MYREF __popen)
|
|
$(MYREF File)
|
|
$(MYREF isFileHandle)
|
|
$(MYREF openNetwork)
|
|
$(MYREF stderr)
|
|
$(MYREF stdin)
|
|
$(MYREF stdout)
|
|
))
|
|
$(TR $(TD Reading) $(TD
|
|
$(MYREF chunks)
|
|
$(MYREF lines)
|
|
$(MYREF readf)
|
|
$(MYREF readln)
|
|
))
|
|
$(TR $(TD Writing) $(TD
|
|
$(MYREF toFile)
|
|
$(MYREF write)
|
|
$(MYREF writef)
|
|
$(MYREF writefln)
|
|
$(MYREF writeln)
|
|
))
|
|
$(TR $(TD Misc) $(TD
|
|
$(MYREF KeepTerminator)
|
|
$(MYREF LockType)
|
|
$(MYREF StdioException)
|
|
))
|
|
))
|
|
|
|
Standard I/O functions that extend $(B core.stdc.stdio). $(B core.stdc.stdio)
|
|
is $(D_PARAM public)ally imported when importing $(B std.stdio).
|
|
|
|
Source: $(PHOBOSSRC std/stdio.d)
|
|
Copyright: Copyright The D Language Foundation 2007-.
|
|
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
Authors: $(HTTP digitalmars.com, Walter Bright),
|
|
$(HTTP erdani.org, Andrei Alexandrescu),
|
|
Alex Rønne Petersen
|
|
*/
|
|
module std.stdio;
|
|
|
|
import core.stdc.stddef : wchar_t;
|
|
public import core.stdc.stdio;
|
|
import std.algorithm.mutation : copy;
|
|
import std.meta : allSatisfy;
|
|
import std.range : ElementEncodingType, empty, front, isBidirectionalRange,
|
|
isInputRange, isSomeFiniteCharInputRange, put;
|
|
import std.traits : isSomeChar, isSomeString, Unqual, isPointer;
|
|
import std.typecons : Flag, No, Yes;
|
|
|
|
/++
|
|
If flag `KeepTerminator` is set to `KeepTerminator.yes`, then the delimiter
|
|
is included in the strings returned.
|
|
+/
|
|
alias KeepTerminator = Flag!"keepTerminator";
|
|
|
|
version (CRuntime_Microsoft)
|
|
{
|
|
version = MICROSOFT_STDIO;
|
|
}
|
|
else version (CRuntime_DigitalMars)
|
|
{
|
|
// Specific to the way Digital Mars C does stdio
|
|
version = DIGITAL_MARS_STDIO;
|
|
}
|
|
else version (CRuntime_Glibc)
|
|
{
|
|
// Specific to the way Gnu C does stdio
|
|
version = GCC_IO;
|
|
}
|
|
else version (CRuntime_Bionic)
|
|
{
|
|
version = GENERIC_IO;
|
|
}
|
|
else version (CRuntime_Musl)
|
|
{
|
|
version = GENERIC_IO;
|
|
}
|
|
else version (CRuntime_UClibc)
|
|
{
|
|
// uClibc supports GCC IO
|
|
version = GCC_IO;
|
|
}
|
|
else version (OSX)
|
|
{
|
|
version = GENERIC_IO;
|
|
version = Darwin;
|
|
}
|
|
else version (iOS)
|
|
{
|
|
version = GENERIC_IO;
|
|
version = Darwin;
|
|
}
|
|
else version (TVOS)
|
|
{
|
|
version = GENERIC_IO;
|
|
version = Darwin;
|
|
}
|
|
else version (WatchOS)
|
|
{
|
|
version = GENERIC_IO;
|
|
version = Darwin;
|
|
}
|
|
else version (FreeBSD)
|
|
{
|
|
version = GENERIC_IO;
|
|
}
|
|
else version (NetBSD)
|
|
{
|
|
version = GENERIC_IO;
|
|
}
|
|
else version (OpenBSD)
|
|
{
|
|
version = GENERIC_IO;
|
|
}
|
|
else version (DragonFlyBSD)
|
|
{
|
|
version = GENERIC_IO;
|
|
}
|
|
else version (Solaris)
|
|
{
|
|
version = GENERIC_IO;
|
|
}
|
|
|
|
// Character type used for operating system filesystem APIs
|
|
version (Windows)
|
|
{
|
|
private alias FSChar = wchar;
|
|
}
|
|
else
|
|
{
|
|
private alias FSChar = char;
|
|
}
|
|
|
|
|
|
version (Windows)
|
|
{
|
|
// core.stdc.stdio.fopen expects file names to be
|
|
// encoded in CP_ACP on Windows instead of UTF-8.
|
|
/+ Waiting for druntime pull 299
|
|
+/
|
|
extern (C) nothrow @nogc FILE* _wfopen(in wchar* filename, in wchar* mode);
|
|
extern (C) nothrow @nogc FILE* _wfreopen(in wchar* filename, in wchar* mode, FILE* fp);
|
|
|
|
import core.sys.windows.basetsd : HANDLE;
|
|
}
|
|
|
|
version (Posix)
|
|
{
|
|
static import core.sys.posix.stdio; // getdelim, flockfile
|
|
}
|
|
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
private alias _FPUTC = _fputc_nlock;
|
|
private alias _FPUTWC = _fputwc_nlock;
|
|
private alias _FGETC = _fgetc_nlock;
|
|
private alias _FGETWC = _fgetwc_nlock;
|
|
private alias _FLOCK = __fp_lock;
|
|
private alias _FUNLOCK = __fp_unlock;
|
|
|
|
// Alias for MICROSOFT_STDIO compatibility.
|
|
// @@@DEPRECATED_2.107@@@
|
|
// Rename this back to _setmode once the deprecation phase has ended.
|
|
private alias __setmode = setmode;
|
|
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FPUTC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FPUTC = _fputc_nlock;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FPUTWC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FPUTWC = _fputwc_nlock;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FGETC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FGETC = _fgetc_nlock;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FGETWC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FGETWC = _fgetwc_nlock;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FLOCK was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FLOCK = __fp_lock;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FUNLOCK was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FUNLOCK = __fp_unlock;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias _setmode was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias _setmode = setmode;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal function _fileno was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
int _fileno(FILE* f) { return f._file; }
|
|
}
|
|
else version (MICROSOFT_STDIO)
|
|
{
|
|
private alias _FPUTC = _fputc_nolock;
|
|
private alias _FPUTWC = _fputwc_nolock;
|
|
private alias _FGETC = _fgetc_nolock;
|
|
private alias _FGETWC = _fgetwc_nolock;
|
|
private alias _FLOCK = _lock_file;
|
|
private alias _FUNLOCK = _unlock_file;
|
|
|
|
// @@@DEPRECATED_2.107@@@
|
|
// Remove this once the deprecation phase for DIGITAL_MARS_STDIO has ended.
|
|
private alias __setmode = _setmode;
|
|
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FPUTC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FPUTC = _fputc_nolock;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FPUTWC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FPUTWC = _fputwc_nolock;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FGETC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FGETC = _fgetc_nolock;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FGETWC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FGETWC = _fgetwc_nolock;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FLOCK was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FLOCK = _lock_file;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FUNLOCK was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FUNLOCK = _unlock_file;
|
|
}
|
|
else version (GCC_IO)
|
|
{
|
|
private alias _FPUTC = fputc_unlocked;
|
|
private alias _FPUTWC = fputwc_unlocked;
|
|
private alias _FGETC = fgetc_unlocked;
|
|
private alias _FGETWC = fgetwc_unlocked;
|
|
private alias _FLOCK = core.sys.posix.stdio.flockfile;
|
|
private alias _FUNLOCK = core.sys.posix.stdio.funlockfile;
|
|
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FPUTC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FPUTC = fputc_unlocked;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FPUTWC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FPUTWC = fputwc_unlocked;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FGETC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FGETC = fgetc_unlocked;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FGETWC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FGETWC = fgetwc_unlocked;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FLOCK was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FLOCK = core.sys.posix.stdio.flockfile;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FUNLOCK was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FUNLOCK = core.sys.posix.stdio.funlockfile;
|
|
}
|
|
else version (GENERIC_IO)
|
|
{
|
|
nothrow:
|
|
@nogc:
|
|
|
|
extern (C) private
|
|
{
|
|
static import core.stdc.wchar_;
|
|
|
|
pragma(mangle, fputc.mangleof) int _FPUTC(int c, _iobuf* fp);
|
|
pragma(mangle, core.stdc.wchar_.fputwc.mangleof) int _FPUTWC(wchar_t c, _iobuf* fp);
|
|
pragma(mangle, fgetc.mangleof) int _FGETC(_iobuf* fp);
|
|
pragma(mangle, core.stdc.wchar_.fgetwc.mangleof) int _FGETWC(_iobuf* fp);
|
|
}
|
|
|
|
version (Posix)
|
|
{
|
|
private alias _FLOCK = core.sys.posix.stdio.flockfile;
|
|
private alias _FUNLOCK = core.sys.posix.stdio.funlockfile;
|
|
}
|
|
else
|
|
{
|
|
static assert(0, "don't know how to lock files on GENERIC_IO");
|
|
}
|
|
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal function fputc_unlocked was unintentionally available "
|
|
~ "from std.stdio and will be removed afer 2.107")
|
|
extern (C) pragma(mangle, fputc.mangleof) int fputc_unlocked(int c, _iobuf* fp);
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal function fputwc_unlocked was unintentionally available "
|
|
~ "from std.stdio and will be removed afer 2.107")
|
|
extern (C) pragma(mangle, core.stdc.wchar_.fputwc.mangleof) int fputwc_unlocked(wchar_t c, _iobuf* fp);
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal function fgetc_unlocked was unintentionally available "
|
|
~ "from std.stdio and will be removed afer 2.107")
|
|
extern (C) pragma(mangle, fgetc.mangleof) int fgetc_unlocked(_iobuf* fp);
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal function fgetwc_unlocked was unintentionally available "
|
|
~ "from std.stdio and will be removed afer 2.107")
|
|
extern (C) pragma(mangle, core.stdc.wchar_.fgetwc.mangleof) int fgetwc_unlocked(_iobuf* fp);
|
|
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FPUTC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FPUTC = fputc_unlocked;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FPUTWC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FPUTWC = fputwc_unlocked;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FGETC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FGETC = fgetc_unlocked;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FGETWC was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FGETWC = fgetwc_unlocked;
|
|
|
|
version (Posix)
|
|
{
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FLOCK was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FLOCK = core.sys.posix.stdio.flockfile;
|
|
// @@@DEPRECATED_2.107@@@
|
|
deprecated("internal alias FUNLOCK was unintentionally available from "
|
|
~ "std.stdio and will be removed afer 2.107")
|
|
alias FUNLOCK = core.sys.posix.stdio.funlockfile;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static assert(0, "unsupported C I/O system");
|
|
}
|
|
|
|
private extern (C) @nogc nothrow
|
|
{
|
|
pragma(mangle, _FPUTC.mangleof) int trustedFPUTC(int ch, _iobuf* h) @trusted;
|
|
|
|
version (DIGITAL_MARS_STDIO)
|
|
pragma(mangle, _FPUTWC.mangleof) int trustedFPUTWC(int ch, _iobuf* h) @trusted;
|
|
else
|
|
pragma(mangle, _FPUTWC.mangleof) int trustedFPUTWC(wchar_t ch, _iobuf* h) @trusted;
|
|
}
|
|
|
|
static if (__traits(compiles, core.sys.posix.stdio.getdelim))
|
|
{
|
|
extern(C) nothrow @nogc
|
|
{
|
|
// @@@DEPRECATED_2.104@@@
|
|
deprecated("To be removed after 2.104. Use core.sys.posix.stdio.getdelim instead.")
|
|
ptrdiff_t getdelim(char**, size_t*, int, FILE*);
|
|
|
|
// @@@DEPRECATED_2.104@@@
|
|
// getline() always comes together with getdelim()
|
|
deprecated("To be removed after 2.104. Use core.sys.posix.stdio.getline instead.")
|
|
ptrdiff_t getline(char**, size_t*, FILE*);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
private struct ByRecordImpl(Fields...)
|
|
{
|
|
private:
|
|
import std.typecons : Tuple;
|
|
|
|
File file;
|
|
char[] line;
|
|
Tuple!(Fields) current;
|
|
string format;
|
|
|
|
public:
|
|
this(File f, string format)
|
|
{
|
|
assert(f.isOpen);
|
|
file = f;
|
|
this.format = format;
|
|
popFront(); // prime the range
|
|
}
|
|
|
|
/// Range primitive implementations.
|
|
@property bool empty()
|
|
{
|
|
return !file.isOpen;
|
|
}
|
|
|
|
/// Ditto
|
|
@property ref Tuple!(Fields) front()
|
|
{
|
|
return current;
|
|
}
|
|
|
|
/// Ditto
|
|
void popFront()
|
|
{
|
|
import std.conv : text;
|
|
import std.exception : enforce;
|
|
import std.format.read : formattedRead;
|
|
import std.string : chomp;
|
|
|
|
enforce(file.isOpen, "ByRecord: File must be open");
|
|
file.readln(line);
|
|
if (!line.length)
|
|
{
|
|
file.detach();
|
|
}
|
|
else
|
|
{
|
|
line = chomp(line);
|
|
formattedRead(line, format, ¤t);
|
|
enforce(line.empty, text("Leftover characters in record: `",
|
|
line, "'"));
|
|
}
|
|
}
|
|
}
|
|
|
|
template byRecord(Fields...)
|
|
{
|
|
auto byRecord(File f, string format)
|
|
{
|
|
return typeof(return)(f, format);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Encapsulates a `FILE*`. Generally D does not attempt to provide
|
|
thin wrappers over equivalent functions in the C standard library, but
|
|
manipulating `FILE*` values directly is unsafe and error-prone in
|
|
many ways. The `File` type ensures safe manipulation, automatic
|
|
file closing, and a lot of convenience.
|
|
|
|
The underlying `FILE*` handle is maintained in a reference-counted
|
|
manner, such that as soon as the last `File` variable bound to a
|
|
given `FILE*` goes out of scope, the underlying `FILE*` is
|
|
automatically closed.
|
|
|
|
Example:
|
|
----
|
|
// test.d
|
|
import std.stdio;
|
|
|
|
void main(string[] args)
|
|
{
|
|
auto f = File("test.txt", "w"); // open for writing
|
|
f.write("Hello");
|
|
if (args.length > 1)
|
|
{
|
|
auto g = f; // now g and f write to the same file
|
|
// internal reference count is 2
|
|
g.write(", ", args[1]);
|
|
// g exits scope, reference count decreases to 1
|
|
}
|
|
f.writeln("!");
|
|
// f exits scope, reference count falls to zero,
|
|
// underlying `FILE*` is closed.
|
|
}
|
|
----
|
|
$(CONSOLE
|
|
% rdmd test.d Jimmy
|
|
% cat test.txt
|
|
Hello, Jimmy!
|
|
% __
|
|
)
|
|
*/
|
|
struct File
|
|
{
|
|
import core.atomic : atomicOp, atomicStore, atomicLoad;
|
|
import std.range.primitives : ElementEncodingType;
|
|
import std.traits : isScalarType, isArray;
|
|
enum Orientation { unknown, narrow, wide }
|
|
|
|
private struct Impl
|
|
{
|
|
FILE * handle = null; // Is null iff this Impl is closed by another File
|
|
shared uint refs = uint.max / 2;
|
|
bool isPopened; // true iff the stream has been created by popen()
|
|
Orientation orientation;
|
|
}
|
|
private Impl* _p;
|
|
private string _name;
|
|
|
|
package this(FILE* handle, string name, uint refs = 1, bool isPopened = false) @trusted
|
|
{
|
|
import core.stdc.stdlib : malloc;
|
|
import std.exception : enforce;
|
|
|
|
assert(!_p);
|
|
_p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory");
|
|
initImpl(handle, name, refs, isPopened);
|
|
}
|
|
|
|
private void initImpl(FILE* handle, string name, uint refs = 1, bool isPopened = false)
|
|
{
|
|
assert(_p);
|
|
_p.handle = handle;
|
|
atomicStore(_p.refs, refs);
|
|
_p.isPopened = isPopened;
|
|
_p.orientation = Orientation.unknown;
|
|
_name = name;
|
|
}
|
|
|
|
/**
|
|
Constructor taking the name of the file to open and the open mode.
|
|
|
|
Copying one `File` object to another results in the two `File`
|
|
objects referring to the same underlying file.
|
|
|
|
The destructor automatically closes the file as soon as no `File`
|
|
object refers to it anymore.
|
|
|
|
Params:
|
|
name = range or string representing the file _name
|
|
stdioOpenmode = range or string represting the open mode
|
|
(with the same semantics as in the C standard library
|
|
$(HTTP cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen)
|
|
function)
|
|
|
|
Throws: `ErrnoException` if the file could not be opened.
|
|
*/
|
|
this(string name, scope const(char)[] stdioOpenmode = "rb") @safe
|
|
{
|
|
import std.conv : text;
|
|
import std.exception : errnoEnforce;
|
|
|
|
this(errnoEnforce(_fopen(name, stdioOpenmode),
|
|
text("Cannot open file `", name, "' in mode `",
|
|
stdioOpenmode, "'")),
|
|
name);
|
|
|
|
// MSVCRT workaround (issue 14422)
|
|
version (MICROSOFT_STDIO)
|
|
{
|
|
setAppendWin(stdioOpenmode);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
this(R1, R2)(R1 name)
|
|
if (isSomeFiniteCharInputRange!R1)
|
|
{
|
|
import std.conv : to;
|
|
this(name.to!string, "rb");
|
|
}
|
|
|
|
/// ditto
|
|
this(R1, R2)(R1 name, R2 mode)
|
|
if (isSomeFiniteCharInputRange!R1 &&
|
|
isSomeFiniteCharInputRange!R2)
|
|
{
|
|
import std.conv : to;
|
|
this(name.to!string, mode.to!string);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static import std.file;
|
|
import std.utf : byChar;
|
|
auto deleteme = testFilename();
|
|
auto f = File(deleteme.byChar, "w".byChar);
|
|
f.close();
|
|
std.file.remove(deleteme);
|
|
}
|
|
|
|
~this() @safe
|
|
{
|
|
detach();
|
|
}
|
|
|
|
this(this) @safe nothrow
|
|
{
|
|
if (!_p) return;
|
|
assert(atomicLoad(_p.refs));
|
|
atomicOp!"+="(_p.refs, 1);
|
|
}
|
|
|
|
/**
|
|
Assigns a file to another. The target of the assignment gets detached
|
|
from whatever file it was attached to, and attaches itself to the new
|
|
file.
|
|
*/
|
|
ref File opAssign(File rhs) @safe return
|
|
{
|
|
import std.algorithm.mutation : swap;
|
|
|
|
swap(this, rhs);
|
|
return this;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=20129
|
|
@safe unittest
|
|
{
|
|
File[int] aa;
|
|
aa.require(0, File.init);
|
|
}
|
|
|
|
/**
|
|
Detaches from the current file (throwing on failure), and then attempts to
|
|
_open file `name` with mode `stdioOpenmode`. The mode has the
|
|
same semantics as in the C standard library $(HTTP
|
|
cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) function.
|
|
|
|
Throws: `ErrnoException` in case of error.
|
|
*/
|
|
void open(string name, scope const(char)[] stdioOpenmode = "rb") @trusted
|
|
{
|
|
resetFile(name, stdioOpenmode, false);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=20585
|
|
@system unittest
|
|
{
|
|
File f;
|
|
try
|
|
f.open("doesn't exist");
|
|
catch (Exception _e)
|
|
{
|
|
}
|
|
|
|
assert(!f.isOpen);
|
|
|
|
f.close(); // to check not crash here
|
|
}
|
|
|
|
private void resetFile(string name, scope const(char)[] stdioOpenmode, bool isPopened) @trusted
|
|
{
|
|
import core.stdc.stdlib : malloc;
|
|
import std.exception : enforce;
|
|
import std.conv : text;
|
|
import std.exception : errnoEnforce;
|
|
|
|
if (_p !is null)
|
|
{
|
|
detach();
|
|
}
|
|
|
|
FILE* handle;
|
|
version (Posix)
|
|
{
|
|
if (isPopened)
|
|
{
|
|
errnoEnforce(handle = _popen(name, stdioOpenmode),
|
|
"Cannot run command `"~name~"'");
|
|
}
|
|
else
|
|
{
|
|
errnoEnforce(handle = _fopen(name, stdioOpenmode),
|
|
text("Cannot open file `", name, "' in mode `",
|
|
stdioOpenmode, "'"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(isPopened == false);
|
|
errnoEnforce(handle = _fopen(name, stdioOpenmode),
|
|
text("Cannot open file `", name, "' in mode `",
|
|
stdioOpenmode, "'"));
|
|
}
|
|
_p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory");
|
|
initImpl(handle, name, 1, isPopened);
|
|
version (MICROSOFT_STDIO)
|
|
{
|
|
setAppendWin(stdioOpenmode);
|
|
}
|
|
}
|
|
|
|
private void closeHandles() @trusted
|
|
{
|
|
assert(_p);
|
|
import std.exception : errnoEnforce;
|
|
|
|
version (Posix)
|
|
{
|
|
import core.sys.posix.stdio : pclose;
|
|
import std.format : format;
|
|
|
|
if (_p.isPopened)
|
|
{
|
|
auto res = pclose(_p.handle);
|
|
errnoEnforce(res != -1,
|
|
"Could not close pipe `"~_name~"'");
|
|
_p.handle = null;
|
|
return;
|
|
}
|
|
}
|
|
if (_p.handle)
|
|
{
|
|
auto handle = _p.handle;
|
|
_p.handle = null;
|
|
// fclose disassociates the FILE* even in case of error (issue 19751)
|
|
errnoEnforce(.fclose(handle) == 0,
|
|
"Could not close file `"~_name~"'");
|
|
}
|
|
}
|
|
|
|
version (MICROSOFT_STDIO)
|
|
{
|
|
private void setAppendWin(scope const(char)[] stdioOpenmode) @safe
|
|
{
|
|
bool append, update;
|
|
foreach (c; stdioOpenmode)
|
|
if (c == 'a')
|
|
append = true;
|
|
else
|
|
if (c == '+')
|
|
update = true;
|
|
if (append && !update)
|
|
seek(size);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Reuses the `File` object to either open a different file, or change
|
|
the file mode. If `name` is `null`, the mode of the currently open
|
|
file is changed; otherwise, a new file is opened, reusing the C
|
|
`FILE*`. The function has the same semantics as in the C standard
|
|
library $(HTTP cplusplus.com/reference/cstdio/freopen/, freopen)
|
|
function.
|
|
|
|
Note: Calling `reopen` with a `null` `name` is not implemented
|
|
in all C runtimes.
|
|
|
|
Throws: `ErrnoException` in case of error.
|
|
*/
|
|
void reopen(string name, scope const(char)[] stdioOpenmode = "rb") @trusted
|
|
{
|
|
import std.conv : text;
|
|
import std.exception : enforce, errnoEnforce;
|
|
import std.internal.cstring : tempCString;
|
|
|
|
enforce(isOpen, "Attempting to reopen() an unopened file");
|
|
|
|
auto namez = (name == null ? _name : name).tempCString!FSChar();
|
|
auto modez = stdioOpenmode.tempCString!FSChar();
|
|
|
|
FILE* fd = _p.handle;
|
|
version (Windows)
|
|
fd = _wfreopen(namez, modez, fd);
|
|
else
|
|
fd = freopen(namez, modez, fd);
|
|
|
|
errnoEnforce(fd, name
|
|
? text("Cannot reopen file `", name, "' in mode `", stdioOpenmode, "'")
|
|
: text("Cannot reopen file in mode `", stdioOpenmode, "'"));
|
|
|
|
if (name !is null)
|
|
_name = name;
|
|
}
|
|
|
|
@system unittest // Test changing filename
|
|
{
|
|
import std.exception : assertThrown, assertNotThrown;
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "foo");
|
|
scope(exit) std.file.remove(deleteme);
|
|
auto f = File(deleteme);
|
|
assert(f.readln() == "foo");
|
|
|
|
auto deleteme2 = testFilename();
|
|
std.file.write(deleteme2, "bar");
|
|
scope(exit) std.file.remove(deleteme2);
|
|
f.reopen(deleteme2);
|
|
assert(f.name == deleteme2);
|
|
assert(f.readln() == "bar");
|
|
f.close();
|
|
}
|
|
|
|
version (CRuntime_DigitalMars) {} else // Not implemented
|
|
version (CRuntime_Microsoft) {} else // Not implemented
|
|
@system unittest // Test changing mode
|
|
{
|
|
import std.exception : assertThrown, assertNotThrown;
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "foo");
|
|
scope(exit) std.file.remove(deleteme);
|
|
auto f = File(deleteme, "r+");
|
|
assert(f.readln() == "foo");
|
|
f.reopen(null, "w");
|
|
f.write("bar");
|
|
f.seek(0);
|
|
f.reopen(null, "a");
|
|
f.write("baz");
|
|
assert(f.name == deleteme);
|
|
f.close();
|
|
assert(std.file.readText(deleteme) == "barbaz");
|
|
}
|
|
|
|
/**
|
|
Detaches from the current file (throwing on failure), and then runs a command
|
|
by calling the C standard library function $(HTTP
|
|
opengroup.org/onlinepubs/007908799/xsh/_popen.html, _popen).
|
|
|
|
Throws: `ErrnoException` in case of error.
|
|
*/
|
|
version (Posix) void popen(string command, scope const(char)[] stdioOpenmode = "r") @safe
|
|
{
|
|
resetFile(command, stdioOpenmode ,true);
|
|
}
|
|
|
|
/**
|
|
First calls `detach` (throwing on failure), then attempts to
|
|
associate the given file descriptor with the `File`, and sets the file's name to `null`.
|
|
|
|
The mode must be compatible with the mode of the file descriptor.
|
|
|
|
Throws: `ErrnoException` in case of error.
|
|
Params:
|
|
fd = File descriptor to associate with this `File`.
|
|
stdioOpenmode = Mode to associate with this File. The mode has the same semantics
|
|
semantics as in the C standard library
|
|
$(HTTP cplusplus.com/reference/cstdio/fopen/, fdopen) function, and must be compatible with `fd`.
|
|
*/
|
|
void fdopen(int fd, scope const(char)[] stdioOpenmode = "rb") @safe
|
|
{
|
|
fdopen(fd, stdioOpenmode, null);
|
|
}
|
|
|
|
package void fdopen(int fd, scope const(char)[] stdioOpenmode, string name) @trusted
|
|
{
|
|
import std.exception : errnoEnforce;
|
|
import std.internal.cstring : tempCString;
|
|
|
|
auto modez = stdioOpenmode.tempCString();
|
|
detach();
|
|
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
// This is a re-implementation of DMC's fdopen, but without the
|
|
// mucking with the file descriptor. POSIX standard requires the
|
|
// new fdopen'd file to retain the given file descriptor's
|
|
// position.
|
|
auto fp = fopen("NUL", modez);
|
|
errnoEnforce(fp, "Cannot open placeholder NUL stream");
|
|
_FLOCK(fp);
|
|
auto iob = cast(_iobuf*) fp;
|
|
.close(iob._file);
|
|
iob._file = fd;
|
|
iob._flag &= ~_IOTRAN;
|
|
_FUNLOCK(fp);
|
|
}
|
|
else
|
|
{
|
|
version (Windows) // MSVCRT
|
|
auto fp = _fdopen(fd, modez);
|
|
else version (Posix)
|
|
{
|
|
import core.sys.posix.stdio : fdopen;
|
|
auto fp = fdopen(fd, modez);
|
|
}
|
|
errnoEnforce(fp);
|
|
}
|
|
this = File(fp, name);
|
|
}
|
|
|
|
// Declare a dummy HANDLE to allow generating documentation
|
|
// for Windows-only methods.
|
|
version (StdDdoc) { version (Windows) {} else alias HANDLE = int; }
|
|
|
|
/**
|
|
First calls `detach` (throwing on failure), and then attempts to
|
|
associate the given Windows `HANDLE` with the `File`. The mode must
|
|
be compatible with the access attributes of the handle. Windows only.
|
|
|
|
Throws: `ErrnoException` in case of error.
|
|
*/
|
|
version (StdDdoc)
|
|
void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode);
|
|
|
|
version (Windows)
|
|
void windowsHandleOpen(HANDLE handle, scope const(char)[] stdioOpenmode)
|
|
{
|
|
import core.stdc.stdint : intptr_t;
|
|
import std.exception : errnoEnforce;
|
|
import std.format : format;
|
|
|
|
// Create file descriptors from the handles
|
|
version (DIGITAL_MARS_STDIO)
|
|
auto fd = _handleToFD(handle, FHND_DEVICE);
|
|
else // MSVCRT
|
|
{
|
|
int mode;
|
|
modeLoop:
|
|
foreach (c; stdioOpenmode)
|
|
switch (c)
|
|
{
|
|
case 'r': mode |= _O_RDONLY; break;
|
|
case '+': mode &=~_O_RDONLY; break;
|
|
case 'a': mode |= _O_APPEND; break;
|
|
case 'b': mode |= _O_BINARY; break;
|
|
case 't': mode |= _O_TEXT; break;
|
|
case ',': break modeLoop;
|
|
default: break;
|
|
}
|
|
|
|
auto fd = _open_osfhandle(cast(intptr_t) handle, mode);
|
|
}
|
|
|
|
errnoEnforce(fd >= 0, "Cannot open Windows HANDLE");
|
|
fdopen(fd, stdioOpenmode, "HANDLE(%s)".format(handle));
|
|
}
|
|
|
|
|
|
/** Returns `true` if the file is opened. */
|
|
@property bool isOpen() const @safe pure nothrow
|
|
{
|
|
return _p !is null && _p.handle;
|
|
}
|
|
|
|
/**
|
|
Returns `true` if the file is at end (see $(HTTP
|
|
cplusplus.com/reference/clibrary/cstdio/feof.html, feof)).
|
|
|
|
Throws: `Exception` if the file is not opened.
|
|
*/
|
|
@property bool eof() const @trusted pure
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(_p && _p.handle, "Calling eof() against an unopened file.");
|
|
return .feof(cast(FILE*) _p.handle) != 0;
|
|
}
|
|
|
|
/**
|
|
Returns the name last used to initialize this `File`, if any.
|
|
|
|
Some functions that create or initialize the `File` set the name field to `null`.
|
|
Examples include $(LREF tmpfile), $(LREF wrapFile), and $(LREF fdopen). See the
|
|
documentation of those functions for details.
|
|
|
|
Returns: The name last used to initialize this this file, or `null` otherwise.
|
|
*/
|
|
@property string name() const @safe pure nothrow return
|
|
{
|
|
return _name;
|
|
}
|
|
|
|
/**
|
|
If the file is closed or not yet opened, returns `true`. Otherwise, returns
|
|
$(HTTP cplusplus.com/reference/clibrary/cstdio/ferror.html, ferror) for
|
|
the file handle.
|
|
*/
|
|
@property bool error() const @trusted pure nothrow
|
|
{
|
|
return !isOpen || .ferror(cast(FILE*) _p.handle);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=12349
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
auto f = File(deleteme, "w");
|
|
scope(exit) std.file.remove(deleteme);
|
|
|
|
f.close();
|
|
assert(f.error);
|
|
}
|
|
|
|
/**
|
|
Detaches from the underlying file. If the sole owner, calls `close`.
|
|
|
|
Throws: `ErrnoException` on failure if closing the file.
|
|
*/
|
|
void detach() @trusted
|
|
{
|
|
import core.stdc.stdlib : free;
|
|
|
|
if (!_p) return;
|
|
scope(exit) _p = null;
|
|
|
|
if (atomicOp!"-="(_p.refs, 1) == 0)
|
|
{
|
|
scope(exit) free(_p);
|
|
closeHandles();
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
scope(exit) std.file.remove(deleteme);
|
|
auto f = File(deleteme, "w");
|
|
{
|
|
auto f2 = f;
|
|
f2.detach();
|
|
}
|
|
assert(f._p.refs == 1);
|
|
f.close();
|
|
}
|
|
|
|
/**
|
|
If the file was closed or not yet opened, succeeds vacuously. Otherwise
|
|
closes the file (by calling $(HTTP
|
|
cplusplus.com/reference/clibrary/cstdio/fclose.html, fclose)),
|
|
throwing on error. Even if an exception is thrown, afterwards the $(D
|
|
File) object is empty. This is different from `detach` in that it
|
|
always closes the file; consequently, all other `File` objects
|
|
referring to the same handle will see a closed file henceforth.
|
|
|
|
Throws: `ErrnoException` on error.
|
|
*/
|
|
void close() @trusted
|
|
{
|
|
import core.stdc.stdlib : free;
|
|
import std.exception : errnoEnforce;
|
|
|
|
if (!_p) return; // succeed vacuously
|
|
scope(exit)
|
|
{
|
|
if (atomicOp!"-="(_p.refs, 1) == 0)
|
|
free(_p);
|
|
_p = null; // start a new life
|
|
}
|
|
if (!_p.handle) return; // Impl is closed by another File
|
|
|
|
scope(exit) _p.handle = null; // nullify the handle anyway
|
|
closeHandles();
|
|
}
|
|
|
|
/**
|
|
If the file is closed or not yet opened, succeeds vacuously. Otherwise, returns
|
|
$(HTTP cplusplus.com/reference/clibrary/cstdio/_clearerr.html,
|
|
_clearerr) for the file handle.
|
|
*/
|
|
void clearerr() @safe pure nothrow
|
|
{
|
|
_p is null || _p.handle is null ||
|
|
.clearerr(_p.handle);
|
|
}
|
|
|
|
/**
|
|
Flushes the C `FILE` buffers.
|
|
|
|
Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_fflush.html, _fflush)
|
|
for the file handle.
|
|
|
|
Throws: `Exception` if the file is not opened or if the call to `fflush` fails.
|
|
*/
|
|
void flush() @trusted
|
|
{
|
|
import std.exception : enforce, errnoEnforce;
|
|
|
|
enforce(isOpen, "Attempting to flush() in an unopened file");
|
|
errnoEnforce(.fflush(_p.handle) == 0);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=12349
|
|
import std.exception : assertThrown;
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
auto f = File(deleteme, "w");
|
|
scope(exit) std.file.remove(deleteme);
|
|
|
|
f.close();
|
|
assertThrown(f.flush());
|
|
}
|
|
|
|
/**
|
|
Forces any data buffered by the OS to be written to disk.
|
|
Call $(LREF flush) before calling this function to flush the C `FILE` buffers first.
|
|
|
|
This function calls
|
|
$(HTTP msdn.microsoft.com/en-us/library/windows/desktop/aa364439%28v=vs.85%29.aspx,
|
|
`FlushFileBuffers`) on Windows,
|
|
$(HTTP developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html,
|
|
`F_FULLFSYNC fcntl`) on Darwin and
|
|
$(HTTP pubs.opengroup.org/onlinepubs/7908799/xsh/fsync.html,
|
|
`fsync`) on POSIX for the file handle.
|
|
|
|
Throws: `Exception` if the file is not opened or if the OS call fails.
|
|
*/
|
|
void sync() @trusted
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(isOpen, "Attempting to sync() an unopened file");
|
|
|
|
version (Windows)
|
|
{
|
|
import core.sys.windows.winbase : FlushFileBuffers;
|
|
wenforce(FlushFileBuffers(windowsHandle), "FlushFileBuffers failed");
|
|
}
|
|
else version (Darwin)
|
|
{
|
|
import core.sys.darwin.fcntl : fcntl, F_FULLFSYNC;
|
|
import std.exception : errnoEnforce;
|
|
errnoEnforce(fcntl(fileno, F_FULLFSYNC, 0) != -1, "fcntl failed");
|
|
}
|
|
else
|
|
{
|
|
import core.sys.posix.unistd : fsync;
|
|
import std.exception : errnoEnforce;
|
|
errnoEnforce(fsync(fileno) == 0, "fsync failed");
|
|
}
|
|
}
|
|
|
|
/**
|
|
Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fread.html, fread) for the
|
|
file handle. The number of items to read and the size of
|
|
each item is inferred from the size and type of the input array, respectively.
|
|
|
|
Returns: The slice of `buffer` containing the data that was actually read.
|
|
This will be shorter than `buffer` if EOF was reached before the buffer
|
|
could be filled.
|
|
|
|
Throws: `Exception` if `buffer` is empty.
|
|
`ErrnoException` if the file is not opened or the call to `fread` fails.
|
|
|
|
`rawRead` always reads in binary mode on Windows.
|
|
*/
|
|
T[] rawRead(T)(T[] buffer)
|
|
{
|
|
import std.exception : enforce, errnoEnforce;
|
|
|
|
if (!buffer.length)
|
|
throw new Exception("rawRead must take a non-empty buffer");
|
|
enforce(isOpen, "Attempting to read from an unopened file");
|
|
version (Windows)
|
|
{
|
|
immutable fd = .fileno(_p.handle);
|
|
immutable mode = .__setmode(fd, _O_BINARY);
|
|
scope(exit) .__setmode(fd, mode);
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
import core.atomic : atomicOp;
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=4243
|
|
immutable info = __fhnd_info[fd];
|
|
atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT);
|
|
scope(exit) __fhnd_info[fd] = info;
|
|
}
|
|
}
|
|
immutable freadResult = trustedFread(_p.handle, buffer);
|
|
assert(freadResult <= buffer.length); // fread return guarantee
|
|
if (freadResult != buffer.length) // error or eof
|
|
{
|
|
errnoEnforce(!error);
|
|
return buffer[0 .. freadResult];
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
auto testFile = std.file.deleteme();
|
|
std.file.write(testFile, "\r\n\n\r\n");
|
|
scope(exit) std.file.remove(testFile);
|
|
|
|
auto f = File(testFile, "r");
|
|
auto buf = f.rawRead(new char[5]);
|
|
f.close();
|
|
assert(buf == "\r\n\n\r\n");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=21729
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
File f;
|
|
ubyte[1] u;
|
|
assertThrown(f.rawRead(u));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=21728
|
|
@system unittest
|
|
{
|
|
static if (__traits(compiles, { import std.process : pipe; })) // not available for iOS
|
|
{
|
|
import std.process : pipe;
|
|
import std.exception : assertThrown;
|
|
|
|
auto p = pipe();
|
|
p.readEnd.close;
|
|
ubyte[1] u;
|
|
assertThrown(p.readEnd.rawRead(u));
|
|
}
|
|
}
|
|
|
|
/**
|
|
Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fwrite.html, fwrite) for the file
|
|
handle. The number of items to write and the size of each
|
|
item is inferred from the size and type of the input array, respectively. An
|
|
error is thrown if the buffer could not be written in its entirety.
|
|
|
|
`rawWrite` always writes in binary mode on Windows.
|
|
|
|
Throws: `ErrnoException` if the file is not opened or if the call to `fwrite` fails.
|
|
*/
|
|
void rawWrite(T)(in T[] buffer)
|
|
{
|
|
import std.conv : text;
|
|
import std.exception : errnoEnforce;
|
|
|
|
version (Windows)
|
|
{
|
|
immutable fd = .fileno(_p.handle);
|
|
immutable oldMode = .__setmode(fd, _O_BINARY);
|
|
|
|
if (oldMode != _O_BINARY)
|
|
{
|
|
// need to flush the data that was written with the original mode
|
|
.__setmode(fd, oldMode);
|
|
flush(); // before changing translation mode .__setmode(fd, _O_BINARY);
|
|
.__setmode(fd, _O_BINARY);
|
|
}
|
|
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
import core.atomic : atomicOp;
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=4243
|
|
immutable info = __fhnd_info[fd];
|
|
atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT);
|
|
scope (exit) __fhnd_info[fd] = info;
|
|
}
|
|
|
|
scope (exit)
|
|
{
|
|
if (oldMode != _O_BINARY)
|
|
{
|
|
flush();
|
|
.__setmode(fd, oldMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto result = trustedFwrite(_p.handle, buffer);
|
|
if (result == result.max) result = 0;
|
|
errnoEnforce(result == buffer.length,
|
|
text("Wrote ", result, " instead of ", buffer.length,
|
|
" objects of type ", T.stringof, " to file `",
|
|
_name, "'"));
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
auto testFile = std.file.deleteme();
|
|
auto f = File(testFile, "w");
|
|
scope(exit) std.file.remove(testFile);
|
|
|
|
f.rawWrite("\r\n\n\r\n");
|
|
f.close();
|
|
assert(std.file.read(testFile) == "\r\n\n\r\n");
|
|
}
|
|
|
|
/**
|
|
Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fseek.html, fseek)
|
|
for the file handle to move its position indicator.
|
|
|
|
Params:
|
|
offset = Binary files: Number of bytes to offset from origin.$(BR)
|
|
Text files: Either zero, or a value returned by $(LREF tell).
|
|
origin = Binary files: Position used as reference for the offset, must be
|
|
one of $(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio),
|
|
$(REF_ALTTEXT SEEK_CUR, SEEK_CUR, core,stdc,stdio) or
|
|
$(REF_ALTTEXT SEEK_END, SEEK_END, core,stdc,stdio).$(BR)
|
|
Text files: Shall necessarily be
|
|
$(REF_ALTTEXT SEEK_SET, SEEK_SET, core,stdc,stdio).
|
|
|
|
Throws: `Exception` if the file is not opened.
|
|
`ErrnoException` if the call to `fseek` fails.
|
|
*/
|
|
void seek(long offset, int origin = SEEK_SET) @trusted
|
|
{
|
|
import std.conv : to, text;
|
|
import std.exception : enforce, errnoEnforce;
|
|
|
|
// Some libc sanitize the whence input (e.g. glibc), but some don't,
|
|
// e.g. Microsoft runtime crashes on an invalid origin,
|
|
// and Musl additionally accept SEEK_DATA & SEEK_HOLE (Linux extension).
|
|
// To provide a consistent behavior cross platform, we use the glibc check
|
|
// See also https://issues.dlang.org/show_bug.cgi?id=19797
|
|
enforce(origin == SEEK_SET || origin == SEEK_CUR || origin == SEEK_END,
|
|
"Invalid `origin` argument passed to `seek`, must be one of: SEEK_SET, SEEK_CUR, SEEK_END");
|
|
|
|
enforce(isOpen, "Attempting to seek() in an unopened file");
|
|
version (Windows)
|
|
{
|
|
version (CRuntime_Microsoft)
|
|
{
|
|
alias fseekFun = _fseeki64;
|
|
alias off_t = long;
|
|
}
|
|
else
|
|
{
|
|
alias fseekFun = fseek;
|
|
alias off_t = int;
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
import core.sys.posix.stdio : fseeko, off_t;
|
|
alias fseekFun = fseeko;
|
|
}
|
|
errnoEnforce(fseekFun(_p.handle, to!off_t(offset), origin) == 0,
|
|
"Could not seek in file `"~_name~"'");
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.conv : text;
|
|
static import std.file;
|
|
import std.exception;
|
|
|
|
auto deleteme = testFilename();
|
|
auto f = File(deleteme, "w+");
|
|
scope(exit) { f.close(); std.file.remove(deleteme); }
|
|
f.rawWrite("abcdefghijklmnopqrstuvwxyz");
|
|
f.seek(7);
|
|
assert(f.readln() == "hijklmnopqrstuvwxyz");
|
|
|
|
version (CRuntime_DigitalMars)
|
|
auto bigOffset = int.max - 100;
|
|
else
|
|
version (CRuntime_Bionic)
|
|
auto bigOffset = int.max - 100;
|
|
else
|
|
auto bigOffset = cast(ulong) int.max + 100;
|
|
f.seek(bigOffset);
|
|
assert(f.tell == bigOffset, text(f.tell));
|
|
// Uncomment the tests below only if you want to wait for
|
|
// a long time
|
|
// f.rawWrite("abcdefghijklmnopqrstuvwxyz");
|
|
// f.seek(-3, SEEK_END);
|
|
// assert(f.readln() == "xyz");
|
|
|
|
assertThrown(f.seek(0, ushort.max));
|
|
}
|
|
|
|
/**
|
|
Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/ftell.html, ftell) for the
|
|
managed file handle.
|
|
|
|
Throws: `Exception` if the file is not opened.
|
|
`ErrnoException` if the call to `ftell` fails.
|
|
*/
|
|
@property ulong tell() const @trusted
|
|
{
|
|
import std.exception : enforce, errnoEnforce;
|
|
|
|
enforce(isOpen, "Attempting to tell() in an unopened file");
|
|
version (Windows)
|
|
{
|
|
version (CRuntime_Microsoft)
|
|
immutable result = _ftelli64(cast(FILE*) _p.handle);
|
|
else
|
|
immutable result = ftell(cast(FILE*) _p.handle);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
import core.sys.posix.stdio : ftello;
|
|
immutable result = ftello(cast(FILE*) _p.handle);
|
|
}
|
|
errnoEnforce(result != -1,
|
|
"Query ftell() failed for file `"~_name~"'");
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.conv : text;
|
|
static import std.file;
|
|
|
|
auto testFile = std.file.deleteme();
|
|
std.file.write(testFile, "abcdefghijklmnopqrstuvwqxyz");
|
|
scope(exit) { std.file.remove(testFile); }
|
|
|
|
auto f = File(testFile);
|
|
auto a = new ubyte[4];
|
|
f.rawRead(a);
|
|
assert(f.tell == 4, text(f.tell));
|
|
}
|
|
|
|
/**
|
|
Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_rewind.html, _rewind)
|
|
for the file handle.
|
|
|
|
Throws: `Exception` if the file is not opened.
|
|
*/
|
|
void rewind() @safe
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(isOpen, "Attempting to rewind() an unopened file");
|
|
.rewind(_p.handle);
|
|
}
|
|
|
|
/**
|
|
Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, _setvbuf) for
|
|
the file handle.
|
|
|
|
Throws: `Exception` if the file is not opened.
|
|
`ErrnoException` if the call to `setvbuf` fails.
|
|
*/
|
|
void setvbuf(size_t size, int mode = _IOFBF) @trusted
|
|
{
|
|
import std.exception : enforce, errnoEnforce;
|
|
|
|
enforce(isOpen, "Attempting to call setvbuf() on an unopened file");
|
|
errnoEnforce(.setvbuf(_p.handle, null, mode, size) == 0,
|
|
"Could not set buffering for file `"~_name~"'");
|
|
}
|
|
|
|
/**
|
|
Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html,
|
|
_setvbuf) for the file handle.
|
|
|
|
Throws: `Exception` if the file is not opened.
|
|
`ErrnoException` if the call to `setvbuf` fails.
|
|
*/
|
|
void setvbuf(void[] buf, int mode = _IOFBF) @trusted
|
|
{
|
|
import std.exception : enforce, errnoEnforce;
|
|
|
|
enforce(isOpen, "Attempting to call setvbuf() on an unopened file");
|
|
errnoEnforce(.setvbuf(_p.handle,
|
|
cast(char*) buf.ptr, mode, buf.length) == 0,
|
|
"Could not set buffering for file `"~_name~"'");
|
|
}
|
|
|
|
|
|
version (Windows)
|
|
{
|
|
import core.sys.windows.winbase : OVERLAPPED;
|
|
import core.sys.windows.winnt : BOOL, ULARGE_INTEGER;
|
|
|
|
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, lazy string str)
|
|
{
|
|
import core.sys.windows.winbase : 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 `start` and `length` are zero, the entire file is locked.
|
|
|
|
Locks created using `lock` and `tryLock` have the following properties:
|
|
$(UL
|
|
$(LI All locks are automatically released when the process terminates.)
|
|
$(LI Locks are not inherited by child processes.)
|
|
$(LI Closing a file will release all locks associated with the file. On POSIX,
|
|
even locks acquired via a different `File` will be released as well.)
|
|
$(LI Not all NFS implementations correctly implement file locking.)
|
|
)
|
|
*/
|
|
void lock(LockType lockType = LockType.readWrite,
|
|
ulong start = 0, ulong length = 0)
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(isOpen, "Attempting to call lock() on an unopened file");
|
|
version (Posix)
|
|
{
|
|
import core.sys.posix.fcntl : F_RDLCK, F_SETLKW, F_WRLCK;
|
|
import std.exception : errnoEnforce;
|
|
immutable short type = lockType == LockType.readWrite
|
|
? F_WRLCK : F_RDLCK;
|
|
errnoEnforce(lockImpl(F_SETLKW, type, start, length) != -1,
|
|
"Could not set lock for file `"~_name~"'");
|
|
}
|
|
else
|
|
version (Windows)
|
|
{
|
|
import core.sys.windows.winbase : LockFileEx, LOCKFILE_EXCLUSIVE_LOCK;
|
|
immutable type = lockType == LockType.readWrite ?
|
|
LOCKFILE_EXCLUSIVE_LOCK : 0;
|
|
wenforce(lockImpl!LockFileEx(start, length, type),
|
|
"Could not set lock for file `"~_name~"'");
|
|
}
|
|
else
|
|
static assert(false);
|
|
}
|
|
|
|
/**
|
|
Attempts to lock the specified file segment.
|
|
If both `start` and `length` are zero, the entire file is locked.
|
|
Returns: `true` if the lock was successful, and `false` if the
|
|
specified file segment was already locked.
|
|
*/
|
|
bool tryLock(LockType lockType = LockType.readWrite,
|
|
ulong start = 0, ulong length = 0)
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(isOpen, "Attempting to call tryLock() on an unopened file");
|
|
version (Posix)
|
|
{
|
|
import core.stdc.errno : EACCES, EAGAIN, errno;
|
|
import core.sys.posix.fcntl : F_RDLCK, F_SETLK, F_WRLCK;
|
|
import std.exception : errnoEnforce;
|
|
immutable short type = lockType == LockType.readWrite
|
|
? F_WRLCK : F_RDLCK;
|
|
immutable res = lockImpl(F_SETLK, type, start, length);
|
|
if (res == -1 && (errno == EACCES || errno == EAGAIN))
|
|
return false;
|
|
errnoEnforce(res != -1, "Could not set lock for file `"~_name~"'");
|
|
return true;
|
|
}
|
|
else
|
|
version (Windows)
|
|
{
|
|
import core.sys.windows.winbase : GetLastError, LockFileEx, LOCKFILE_EXCLUSIVE_LOCK,
|
|
LOCKFILE_FAIL_IMMEDIATELY;
|
|
import core.sys.windows.winerror : ERROR_IO_PENDING, ERROR_LOCK_VIOLATION;
|
|
immutable type = lockType == LockType.readWrite
|
|
? LOCKFILE_EXCLUSIVE_LOCK : 0;
|
|
immutable res = lockImpl!LockFileEx(start, length,
|
|
type | LOCKFILE_FAIL_IMMEDIATELY);
|
|
if (!res && (GetLastError() == ERROR_IO_PENDING
|
|
|| GetLastError() == ERROR_LOCK_VIOLATION))
|
|
return false;
|
|
wenforce(res, "Could not set lock for file `"~_name~"'");
|
|
return true;
|
|
}
|
|
else
|
|
static assert(false);
|
|
}
|
|
|
|
/**
|
|
Removes the lock over the specified file segment.
|
|
*/
|
|
void unlock(ulong start = 0, ulong length = 0)
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(isOpen, "Attempting to call unlock() on an unopened file");
|
|
version (Posix)
|
|
{
|
|
import core.sys.posix.fcntl : F_SETLK, F_UNLCK;
|
|
import std.exception : errnoEnforce;
|
|
errnoEnforce(lockImpl(F_SETLK, F_UNLCK, start, length) != -1,
|
|
"Could not remove lock for file `"~_name~"'");
|
|
}
|
|
else
|
|
version (Windows)
|
|
{
|
|
import core.sys.windows.winbase : UnlockFileEx;
|
|
wenforce(lockImpl!UnlockFileEx(start, length),
|
|
"Could not remove lock for file `"~_name~"'");
|
|
}
|
|
else
|
|
static assert(false);
|
|
}
|
|
|
|
version (Windows)
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
scope(exit) std.file.remove(deleteme);
|
|
auto f = File(deleteme, "wb");
|
|
assert(f.tryLock());
|
|
auto g = File(deleteme, "wb");
|
|
assert(!g.tryLock());
|
|
assert(!g.tryLock(LockType.read));
|
|
f.unlock();
|
|
f.lock(LockType.read);
|
|
assert(!g.tryLock());
|
|
assert(g.tryLock(LockType.read));
|
|
f.unlock();
|
|
g.unlock();
|
|
}
|
|
|
|
version (Posix)
|
|
@system unittest
|
|
{
|
|
static if (__traits(compiles, { import std.process : spawnProcess; }))
|
|
{
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
scope(exit) std.file.remove(deleteme);
|
|
|
|
// Since locks are per-process, we cannot test lock failures within
|
|
// the same process. fork() is used to create a second process.
|
|
static void runForked(void delegate() code)
|
|
{
|
|
import core.sys.posix.sys.wait : waitpid;
|
|
import core.sys.posix.unistd : fork, _exit;
|
|
int child, status;
|
|
if ((child = fork()) == 0)
|
|
{
|
|
code();
|
|
_exit(0);
|
|
}
|
|
else
|
|
{
|
|
assert(waitpid(child, &status, 0) != -1);
|
|
assert(status == 0, "Fork crashed");
|
|
}
|
|
}
|
|
|
|
auto f = File(deleteme, "w+b");
|
|
|
|
runForked
|
|
({
|
|
auto g = File(deleteme, "a+b");
|
|
assert(g.tryLock());
|
|
g.unlock();
|
|
assert(g.tryLock(LockType.read));
|
|
});
|
|
|
|
assert(f.tryLock());
|
|
runForked
|
|
({
|
|
auto g = File(deleteme, "a+b");
|
|
assert(!g.tryLock());
|
|
assert(!g.tryLock(LockType.read));
|
|
});
|
|
f.unlock();
|
|
|
|
f.lock(LockType.read);
|
|
runForked
|
|
({
|
|
auto g = File(deleteme, "a+b");
|
|
assert(!g.tryLock());
|
|
assert(g.tryLock(LockType.read));
|
|
g.unlock();
|
|
});
|
|
f.unlock();
|
|
} // static if
|
|
} // unittest
|
|
|
|
|
|
/**
|
|
Writes its arguments in text format to the file.
|
|
|
|
Throws: `Exception` if the file is not opened.
|
|
`ErrnoException` on an error writing to the file.
|
|
*/
|
|
void write(S...)(S args)
|
|
{
|
|
import std.traits : isBoolean, isIntegral, isAggregateType;
|
|
import std.utf : UTFException;
|
|
auto w = lockingTextWriter();
|
|
foreach (arg; args)
|
|
{
|
|
try
|
|
{
|
|
alias A = typeof(arg);
|
|
static if (isAggregateType!A || is(A == enum))
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
formattedWrite(w, "%s", arg);
|
|
}
|
|
else static if (isSomeString!A)
|
|
{
|
|
put(w, arg);
|
|
}
|
|
else static if (isIntegral!A)
|
|
{
|
|
import std.conv : toTextRange;
|
|
|
|
toTextRange(arg, w);
|
|
}
|
|
else static if (isBoolean!A)
|
|
{
|
|
put(w, arg ? "true" : "false");
|
|
}
|
|
else static if (isSomeChar!A)
|
|
{
|
|
put(w, arg);
|
|
}
|
|
else
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
// Most general case
|
|
formattedWrite(w, "%s", arg);
|
|
}
|
|
}
|
|
catch (UTFException e)
|
|
{
|
|
/* Reset the writer so that it doesn't throw another
|
|
UTFException on destruction. */
|
|
w.highSurrogate = '\0';
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Writes its arguments in text format to the file, followed by a newline.
|
|
|
|
Throws: `Exception` if the file is not opened.
|
|
`ErrnoException` on an error writing to the file.
|
|
*/
|
|
void writeln(S...)(S args)
|
|
{
|
|
write(args, '\n');
|
|
}
|
|
|
|
/**
|
|
Writes its arguments in text format to the file, according to the
|
|
format string fmt.
|
|
|
|
Params:
|
|
fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format).
|
|
When passed as a compile-time argument, the string will be statically checked
|
|
against the argument types passed.
|
|
args = Items to write.
|
|
|
|
Throws: `Exception` if the file is not opened.
|
|
`ErrnoException` on an error writing to the file.
|
|
*/
|
|
void writef(alias fmt, A...)(A args)
|
|
if (isSomeString!(typeof(fmt)))
|
|
{
|
|
import std.format : checkFormatException;
|
|
|
|
alias e = checkFormatException!(fmt, A);
|
|
static assert(!e, e.msg);
|
|
return this.writef(fmt, args);
|
|
}
|
|
|
|
/// ditto
|
|
void writef(Char, A...)(in Char[] fmt, A args)
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
formattedWrite(lockingTextWriter(), fmt, args);
|
|
}
|
|
|
|
/// Equivalent to `file.writef(fmt, args, '\n')`.
|
|
void writefln(alias fmt, A...)(A args)
|
|
if (isSomeString!(typeof(fmt)))
|
|
{
|
|
import std.format : checkFormatException;
|
|
|
|
alias e = checkFormatException!(fmt, A);
|
|
static assert(!e, e.msg);
|
|
return this.writefln(fmt, args);
|
|
}
|
|
|
|
/// ditto
|
|
void writefln(Char, A...)(in Char[] fmt, A args)
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
auto w = lockingTextWriter();
|
|
formattedWrite(w, fmt, args);
|
|
w.put('\n');
|
|
}
|
|
|
|
/**
|
|
Read line from the file handle and return it as a specified type.
|
|
|
|
This version manages its own read buffer, which means one memory allocation per call. If you are not
|
|
retaining a reference to the read data, consider the `File.readln(buf)` version, which may offer
|
|
better performance as it can reuse its read buffer.
|
|
|
|
Params:
|
|
S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`.
|
|
terminator = Line terminator (by default, `'\n'`).
|
|
|
|
Note:
|
|
String terminators are not supported due to ambiguity with readln(buf) below.
|
|
|
|
Returns:
|
|
The line that was read, including the line terminator character.
|
|
|
|
Throws:
|
|
`StdioException` on I/O error, or `UnicodeException` on Unicode conversion error.
|
|
|
|
Example:
|
|
---
|
|
// Reads `stdin` and writes it to `stdout`.
|
|
import std.stdio;
|
|
|
|
void main()
|
|
{
|
|
string line;
|
|
while ((line = stdin.readln()) !is null)
|
|
write(line);
|
|
}
|
|
---
|
|
*/
|
|
S readln(S = string)(dchar terminator = '\n')
|
|
if (isSomeString!S)
|
|
{
|
|
Unqual!(ElementEncodingType!S)[] buf;
|
|
readln(buf, terminator);
|
|
return cast(S) buf;
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
static import std.file;
|
|
import std.meta : AliasSeq;
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "hello\nworld\n");
|
|
scope(exit) std.file.remove(deleteme);
|
|
static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[]))
|
|
{{
|
|
auto witness = [ "hello\n", "world\n" ];
|
|
auto f = File(deleteme);
|
|
uint i = 0;
|
|
String buf;
|
|
while ((buf = f.readln!String()).length)
|
|
{
|
|
assert(i < witness.length);
|
|
assert(equal(buf, witness[i++]));
|
|
}
|
|
assert(i == witness.length);
|
|
}}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
import std.typecons : Tuple;
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "cześć \U0002000D");
|
|
scope(exit) std.file.remove(deleteme);
|
|
uint[] lengths = [12,8,7];
|
|
static foreach (uint i, C; Tuple!(char, wchar, dchar).Types)
|
|
{{
|
|
immutable(C)[] witness = "cześć \U0002000D";
|
|
auto buf = File(deleteme).readln!(immutable(C)[])();
|
|
assert(buf.length == lengths[i]);
|
|
assert(buf == witness);
|
|
}}
|
|
}
|
|
|
|
/**
|
|
Read line from the file handle and write it to `buf[]`, including
|
|
terminating character.
|
|
|
|
This can be faster than $(D line = File.readln()) because you can reuse
|
|
the buffer for each call. Note that reusing the buffer means that you
|
|
must copy the previous contents if you wish to retain them.
|
|
|
|
Params:
|
|
buf = Buffer used to store the resulting line data. buf is
|
|
enlarged if necessary, then set to the slice exactly containing the line.
|
|
terminator = Line terminator (by default, `'\n'`). Use
|
|
$(REF newline, std,ascii) for portability (unless the file was opened in
|
|
text mode).
|
|
|
|
Returns:
|
|
0 for end of file, otherwise number of characters read.
|
|
The return value will always be equal to `buf.length`.
|
|
|
|
Throws: `StdioException` on I/O error, or `UnicodeException` on Unicode
|
|
conversion error.
|
|
|
|
Example:
|
|
---
|
|
// Read lines from `stdin` into a string
|
|
// Ignore lines starting with '#'
|
|
// Write the string to `stdout`
|
|
import std.stdio;
|
|
|
|
void main()
|
|
{
|
|
string output;
|
|
char[] buf;
|
|
|
|
while (stdin.readln(buf))
|
|
{
|
|
if (buf[0] == '#')
|
|
continue;
|
|
|
|
output ~= buf;
|
|
}
|
|
|
|
write(output);
|
|
}
|
|
---
|
|
|
|
This method can be more efficient than the one in the previous example
|
|
because `stdin.readln(buf)` reuses (if possible) memory allocated
|
|
for `buf`, whereas $(D line = stdin.readln()) makes a new memory allocation
|
|
for every line.
|
|
|
|
For even better performance you can help `readln` by passing in a
|
|
large buffer to avoid memory reallocations. This can be done by reusing the
|
|
largest buffer returned by `readln`:
|
|
|
|
Example:
|
|
---
|
|
// Read lines from `stdin` and count words
|
|
import std.array, std.stdio;
|
|
|
|
void main()
|
|
{
|
|
char[] buf;
|
|
size_t words = 0;
|
|
|
|
while (!stdin.eof)
|
|
{
|
|
char[] line = buf;
|
|
stdin.readln(line);
|
|
if (line.length > buf.length)
|
|
buf = line;
|
|
|
|
words += line.split.length;
|
|
}
|
|
|
|
writeln(words);
|
|
}
|
|
---
|
|
This is actually what $(LREF byLine) does internally, so its usage
|
|
is recommended if you want to process a complete file.
|
|
*/
|
|
size_t readln(C)(ref C[] buf, dchar terminator = '\n')
|
|
if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum))
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
static if (is(C == char))
|
|
{
|
|
enforce(_p && _p.handle, "Attempt to read from an unopened file.");
|
|
if (_p.orientation == Orientation.unknown)
|
|
{
|
|
import core.stdc.wchar_ : fwide;
|
|
auto w = fwide(_p.handle, 0);
|
|
if (w < 0) _p.orientation = Orientation.narrow;
|
|
else if (w > 0) _p.orientation = Orientation.wide;
|
|
}
|
|
return readlnImpl(_p.handle, buf, terminator, _p.orientation);
|
|
}
|
|
else
|
|
{
|
|
string s = readln(terminator);
|
|
if (!s.length)
|
|
{
|
|
buf = buf[0 .. 0];
|
|
return 0;
|
|
}
|
|
|
|
import std.utf : codeLength;
|
|
buf.length = codeLength!C(s);
|
|
size_t idx;
|
|
foreach (C c; s)
|
|
buf[idx++] = c;
|
|
|
|
return buf.length;
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
// @system due to readln
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "123\n456789");
|
|
scope(exit) std.file.remove(deleteme);
|
|
|
|
auto file = File(deleteme);
|
|
char[] buffer = new char[10];
|
|
char[] line = buffer;
|
|
file.readln(line);
|
|
auto beyond = line.length;
|
|
buffer[beyond] = 'a';
|
|
file.readln(line); // should not write buffer beyond line
|
|
assert(buffer[beyond] == 'a');
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15293
|
|
@system unittest
|
|
{
|
|
// @system due to readln
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "a\n\naa");
|
|
scope(exit) std.file.remove(deleteme);
|
|
|
|
auto file = File(deleteme);
|
|
char[] buffer;
|
|
char[] line;
|
|
|
|
file.readln(buffer, '\n');
|
|
|
|
line = buffer;
|
|
file.readln(line, '\n');
|
|
|
|
line = buffer;
|
|
file.readln(line, '\n');
|
|
|
|
assert(line[0 .. 1].capacity == 0);
|
|
}
|
|
|
|
/** ditto */
|
|
size_t readln(C, R)(ref C[] buf, R terminator)
|
|
if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) &&
|
|
isBidirectionalRange!R && is(typeof(terminator.front == dchar.init)))
|
|
{
|
|
import std.algorithm.mutation : swap;
|
|
import std.algorithm.searching : endsWith;
|
|
import std.range.primitives : back;
|
|
|
|
auto last = terminator.back;
|
|
C[] buf2;
|
|
swap(buf, buf2);
|
|
for (;;)
|
|
{
|
|
if (!readln(buf2, last) || endsWith(buf2, terminator))
|
|
{
|
|
if (buf.empty)
|
|
{
|
|
buf = buf2;
|
|
}
|
|
else
|
|
{
|
|
buf ~= buf2;
|
|
}
|
|
break;
|
|
}
|
|
buf ~= buf2;
|
|
}
|
|
return buf.length;
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
import std.typecons : Tuple;
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "hello\n\rworld\nhow\n\rare ya");
|
|
scope(exit) std.file.remove(deleteme);
|
|
foreach (C; Tuple!(char, wchar, dchar).Types)
|
|
{
|
|
immutable(C)[][] witness = [ "hello\n\r", "world\nhow\n\r", "are ya" ];
|
|
auto f = File(deleteme);
|
|
uint i = 0;
|
|
C[] buf;
|
|
while (f.readln(buf, "\n\r"))
|
|
{
|
|
assert(i < witness.length);
|
|
assert(buf == witness[i++]);
|
|
}
|
|
assert(buf.length == 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads formatted _data from the file using $(REF formattedRead, std,_format).
|
|
* Params:
|
|
* format = The $(REF_ALTTEXT format string, formattedWrite, std, _format).
|
|
* When passed as a compile-time argument, the string will be statically checked
|
|
* against the argument types passed.
|
|
* data = Items to be read.
|
|
* Returns:
|
|
* Same as `formattedRead`: The number of variables filled. If the input range `r` ends early,
|
|
* this number will be less than the number of variables provided.
|
|
* Example:
|
|
----
|
|
// test.d
|
|
void main()
|
|
{
|
|
import std.stdio;
|
|
auto f = File("input");
|
|
foreach (_; 0 .. 3)
|
|
{
|
|
int a;
|
|
f.readf!" %d"(a);
|
|
writeln(++a);
|
|
}
|
|
}
|
|
----
|
|
$(CONSOLE
|
|
% echo "1 2 3" > input
|
|
% rdmd test.d
|
|
2
|
|
3
|
|
4
|
|
)
|
|
*/
|
|
uint readf(alias format, Data...)(auto ref Data data)
|
|
if (isSomeString!(typeof(format)))
|
|
{
|
|
import std.format : checkFormatException;
|
|
|
|
alias e = checkFormatException!(format, Data);
|
|
static assert(!e, e.msg);
|
|
return this.readf(format, data);
|
|
}
|
|
|
|
/// ditto
|
|
uint readf(Data...)(scope const(char)[] format, auto ref Data data)
|
|
{
|
|
import std.format.read : formattedRead;
|
|
|
|
assert(isOpen);
|
|
auto input = LockingTextReader(this);
|
|
return formattedRead(input, format, data);
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
auto deleteme = std.file.deleteme();
|
|
std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n");
|
|
scope(exit) std.file.remove(deleteme);
|
|
string s;
|
|
auto f = File(deleteme);
|
|
f.readf!"%s\n"(s);
|
|
assert(s == "hello", "["~s~"]");
|
|
f.readf("%s\n", s);
|
|
assert(s == "world", "["~s~"]");
|
|
|
|
bool b1, b2;
|
|
f.readf("%s\n%s\n", b1, b2);
|
|
assert(b1 == true && b2 == false);
|
|
}
|
|
|
|
// backwards compatibility with pointers
|
|
@system unittest
|
|
{
|
|
// @system due to readf
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n");
|
|
scope(exit) std.file.remove(deleteme);
|
|
string s;
|
|
auto f = File(deleteme);
|
|
f.readf("%s\n", &s);
|
|
assert(s == "hello", "["~s~"]");
|
|
f.readf("%s\n", &s);
|
|
assert(s == "world", "["~s~"]");
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=11698
|
|
bool b1, b2;
|
|
f.readf("%s\n%s\n", &b1, &b2);
|
|
assert(b1 == true && b2 == false);
|
|
}
|
|
|
|
// backwards compatibility (mixed)
|
|
@system unittest
|
|
{
|
|
// @system due to readf
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n");
|
|
scope(exit) std.file.remove(deleteme);
|
|
string s1, s2;
|
|
auto f = File(deleteme);
|
|
f.readf("%s\n%s\n", s1, &s2);
|
|
assert(s1 == "hello");
|
|
assert(s2 == "world");
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=11698
|
|
bool b1, b2;
|
|
f.readf("%s\n%s\n", &b1, b2);
|
|
assert(b1 == true && b2 == false);
|
|
}
|
|
|
|
// Nice error of std.stdio.readf with newlines
|
|
// https://issues.dlang.org/show_bug.cgi?id=12260
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "1\n2");
|
|
scope(exit) std.file.remove(deleteme);
|
|
int input;
|
|
auto f = File(deleteme);
|
|
f.readf("%s", &input);
|
|
|
|
import std.conv : ConvException;
|
|
import std.exception : collectException;
|
|
assert(collectException!ConvException(f.readf("%s", &input)).msg ==
|
|
"Unexpected '\\n' when converting from type LockingTextReader to type int");
|
|
}
|
|
|
|
/**
|
|
Returns a temporary file by calling
|
|
$(HTTP cplusplus.com/reference/clibrary/cstdio/_tmpfile.html, _tmpfile).
|
|
Note that the created file has no $(LREF name).*/
|
|
static File tmpfile() @safe
|
|
{
|
|
import std.exception : errnoEnforce;
|
|
|
|
return File(errnoEnforce(.tmpfile(),
|
|
"Could not create temporary file with tmpfile()"),
|
|
null);
|
|
}
|
|
|
|
/**
|
|
Unsafe function that wraps an existing `FILE*`. The resulting $(D
|
|
File) never takes the initiative in closing the file.
|
|
Note that the created file has no $(LREF name)*/
|
|
/*private*/ static File wrapFile(FILE* f) @safe
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
return File(enforce(f, "Could not wrap null FILE*"),
|
|
null, /*uint.max / 2*/ 9999);
|
|
}
|
|
|
|
/**
|
|
Returns the `FILE*` corresponding to this object.
|
|
*/
|
|
FILE* getFP() @safe pure
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(_p && _p.handle,
|
|
"Attempting to call getFP() on an unopened file");
|
|
return _p.handle;
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import core.stdc.stdio;
|
|
assert(stdout.getFP() == core.stdc.stdio.stdout);
|
|
}
|
|
|
|
/**
|
|
Returns the file number corresponding to this object.
|
|
*/
|
|
@property int fileno() const @trusted
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(isOpen, "Attempting to call fileno() on an unopened file");
|
|
return .fileno(cast(FILE*) _p.handle);
|
|
}
|
|
|
|
/**
|
|
Returns the underlying operating system `HANDLE` (Windows only).
|
|
*/
|
|
version (StdDdoc)
|
|
@property HANDLE windowsHandle();
|
|
|
|
version (Windows)
|
|
@property HANDLE windowsHandle()
|
|
{
|
|
version (DIGITAL_MARS_STDIO)
|
|
return _fdToHandle(fileno);
|
|
else
|
|
return cast(HANDLE)_get_osfhandle(fileno);
|
|
}
|
|
|
|
|
|
// Note: This was documented until 2013/08
|
|
/*
|
|
Range that reads one line at a time. Returned by $(LREF byLine).
|
|
|
|
Allows to directly use range operations on lines of a file.
|
|
*/
|
|
private struct ByLineImpl(Char, Terminator)
|
|
{
|
|
private:
|
|
import std.typecons : RefCounted, RefCountedAutoInitialize;
|
|
|
|
/* Ref-counting stops the source range's Impl
|
|
* from getting out of sync after the range is copied, e.g.
|
|
* when accessing range.front, then using std.range.take,
|
|
* then accessing range.front again. */
|
|
alias PImpl = RefCounted!(Impl, RefCountedAutoInitialize.no);
|
|
PImpl impl;
|
|
|
|
static if (isScalarType!Terminator)
|
|
enum defTerm = '\n';
|
|
else
|
|
enum defTerm = cast(Terminator)"\n";
|
|
|
|
public:
|
|
this(File f, KeepTerminator kt = No.keepTerminator,
|
|
Terminator terminator = defTerm)
|
|
{
|
|
impl = PImpl(f, kt, terminator);
|
|
}
|
|
|
|
@property bool empty()
|
|
{
|
|
return impl.refCountedPayload.empty;
|
|
}
|
|
|
|
@property Char[] front()
|
|
{
|
|
return impl.refCountedPayload.front;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
impl.refCountedPayload.popFront();
|
|
}
|
|
|
|
private:
|
|
struct Impl
|
|
{
|
|
private:
|
|
File file;
|
|
Char[] line;
|
|
Char[] buffer;
|
|
Terminator terminator;
|
|
KeepTerminator keepTerminator;
|
|
bool haveLine;
|
|
|
|
public:
|
|
this(File f, KeepTerminator kt, Terminator terminator)
|
|
{
|
|
file = f;
|
|
this.terminator = terminator;
|
|
keepTerminator = kt;
|
|
}
|
|
|
|
// Range primitive implementations.
|
|
@property bool empty()
|
|
{
|
|
needLine();
|
|
return line is null;
|
|
}
|
|
|
|
@property Char[] front()
|
|
{
|
|
needLine();
|
|
return line;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
needLine();
|
|
haveLine = false;
|
|
}
|
|
|
|
private:
|
|
void needLine()
|
|
{
|
|
if (haveLine)
|
|
return;
|
|
import std.algorithm.searching : endsWith;
|
|
assert(file.isOpen);
|
|
line = buffer;
|
|
file.readln(line, terminator);
|
|
if (line.length > buffer.length)
|
|
{
|
|
buffer = line;
|
|
}
|
|
if (line.empty)
|
|
{
|
|
file.detach();
|
|
line = null;
|
|
}
|
|
else if (keepTerminator == No.keepTerminator
|
|
&& endsWith(line, terminator))
|
|
{
|
|
static if (isScalarType!Terminator)
|
|
enum tlen = 1;
|
|
else static if (isArray!Terminator)
|
|
{
|
|
static assert(
|
|
is(immutable ElementEncodingType!Terminator == immutable Char));
|
|
const tlen = terminator.length;
|
|
}
|
|
else
|
|
static assert(false);
|
|
line = line[0 .. line.length - tlen];
|
|
}
|
|
haveLine = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
|
|
set up to read from the file handle one line at a time.
|
|
|
|
The element type for the range will be `Char[]`. Range primitives
|
|
may throw `StdioException` on I/O error.
|
|
|
|
Note:
|
|
Each `front` will not persist after $(D
|
|
popFront) is called, so the caller must copy its contents (e.g. by
|
|
calling `to!string`) when retention is needed. If the caller needs
|
|
to retain a copy of every line, use the $(LREF byLineCopy) function
|
|
instead.
|
|
|
|
Params:
|
|
Char = Character type for each line, defaulting to `char`.
|
|
keepTerminator = Use `Yes.keepTerminator` to include the
|
|
terminator at the end of each line.
|
|
terminator = Line separator (`'\n'` by default). Use
|
|
$(REF newline, std,ascii) for portability (unless the file was opened in
|
|
text mode).
|
|
|
|
Example:
|
|
----
|
|
import std.algorithm, std.stdio, std.string;
|
|
// Count words in a file using ranges.
|
|
void main()
|
|
{
|
|
auto file = File("file.txt"); // Open for reading
|
|
const wordCount = file.byLine() // Read lines
|
|
.map!split // Split into words
|
|
.map!(a => a.length) // Count words per line
|
|
.sum(); // Total word count
|
|
writeln(wordCount);
|
|
}
|
|
----
|
|
|
|
Example:
|
|
----
|
|
import std.range, std.stdio;
|
|
// Read lines using foreach.
|
|
void main()
|
|
{
|
|
auto file = File("file.txt"); // Open for reading
|
|
auto range = file.byLine();
|
|
// Print first three lines
|
|
foreach (line; range.take(3))
|
|
writeln(line);
|
|
// Print remaining lines beginning with '#'
|
|
foreach (line; range)
|
|
{
|
|
if (!line.empty && line[0] == '#')
|
|
writeln(line);
|
|
}
|
|
}
|
|
----
|
|
Notice that neither example accesses the line data returned by
|
|
`front` after the corresponding `popFront` call is made (because
|
|
the contents may well have changed).
|
|
*/
|
|
auto byLine(Terminator = char, Char = char)
|
|
(KeepTerminator keepTerminator = No.keepTerminator,
|
|
Terminator terminator = '\n')
|
|
if (isScalarType!Terminator)
|
|
{
|
|
return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator);
|
|
}
|
|
|
|
/// ditto
|
|
auto byLine(Terminator, Char = char)
|
|
(KeepTerminator keepTerminator, Terminator terminator)
|
|
if (is(immutable ElementEncodingType!Terminator == immutable Char))
|
|
{
|
|
return ByLineImpl!(Char, Terminator)(this, keepTerminator, terminator);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "hi");
|
|
scope(success) std.file.remove(deleteme);
|
|
|
|
import std.meta : AliasSeq;
|
|
static foreach (T; AliasSeq!(char, wchar, dchar))
|
|
{{
|
|
auto blc = File(deleteme).byLine!(T, T);
|
|
assert(blc.front == "hi");
|
|
// check front is cached
|
|
assert(blc.front is blc.front);
|
|
}}
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=19980
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "Line 1\nLine 2\nLine 3\n");
|
|
scope(success) std.file.remove(deleteme);
|
|
|
|
auto f = File(deleteme);
|
|
f.byLine();
|
|
f.byLine();
|
|
assert(f.byLine().front == "Line 1");
|
|
}
|
|
|
|
private struct ByLineCopy(Char, Terminator)
|
|
{
|
|
private:
|
|
import std.typecons : RefCounted, RefCountedAutoInitialize;
|
|
|
|
/* Ref-counting stops the source range's ByLineCopyImpl
|
|
* from getting out of sync after the range is copied, e.g.
|
|
* when accessing range.front, then using std.range.take,
|
|
* then accessing range.front again. */
|
|
alias Impl = RefCounted!(ByLineCopyImpl!(Char, Terminator),
|
|
RefCountedAutoInitialize.no);
|
|
Impl impl;
|
|
|
|
public:
|
|
this(File f, KeepTerminator kt, Terminator terminator)
|
|
{
|
|
impl = Impl(f, kt, terminator);
|
|
}
|
|
|
|
@property bool empty()
|
|
{
|
|
return impl.refCountedPayload.empty;
|
|
}
|
|
|
|
@property Char[] front()
|
|
{
|
|
return impl.refCountedPayload.front;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
impl.refCountedPayload.popFront();
|
|
}
|
|
}
|
|
|
|
private struct ByLineCopyImpl(Char, Terminator)
|
|
{
|
|
ByLineImpl!(Unqual!Char, Terminator).Impl impl;
|
|
bool gotFront;
|
|
Char[] line;
|
|
|
|
public:
|
|
this(File f, KeepTerminator kt, Terminator terminator)
|
|
{
|
|
impl = ByLineImpl!(Unqual!Char, Terminator).Impl(f, kt, terminator);
|
|
}
|
|
|
|
@property bool empty()
|
|
{
|
|
return impl.empty;
|
|
}
|
|
|
|
@property front()
|
|
{
|
|
if (!gotFront)
|
|
{
|
|
line = impl.front.dup;
|
|
gotFront = true;
|
|
}
|
|
return line;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
impl.popFront();
|
|
gotFront = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
|
|
set up to read from the file handle one line
|
|
at a time. Each line will be newly allocated. `front` will cache
|
|
its value to allow repeated calls without unnecessary allocations.
|
|
|
|
Note: Due to caching byLineCopy can be more memory-efficient than
|
|
`File.byLine.map!idup`.
|
|
|
|
The element type for the range will be `Char[]`. Range
|
|
primitives may throw `StdioException` on I/O error.
|
|
|
|
Params:
|
|
Char = Character type for each line, defaulting to $(D immutable char).
|
|
keepTerminator = Use `Yes.keepTerminator` to include the
|
|
terminator at the end of each line.
|
|
terminator = Line separator (`'\n'` by default). Use
|
|
$(REF newline, std,ascii) for portability (unless the file was opened in
|
|
text mode).
|
|
|
|
Example:
|
|
----
|
|
import std.algorithm, std.array, std.stdio;
|
|
// Print sorted lines of a file.
|
|
void main()
|
|
{
|
|
auto sortedLines = File("file.txt") // Open for reading
|
|
.byLineCopy() // Read persistent lines
|
|
.array() // into an array
|
|
.sort(); // then sort them
|
|
foreach (line; sortedLines)
|
|
writeln(line);
|
|
}
|
|
----
|
|
See_Also:
|
|
$(REF readText, std,file)
|
|
*/
|
|
auto byLineCopy(Terminator = char, Char = immutable char)
|
|
(KeepTerminator keepTerminator = No.keepTerminator,
|
|
Terminator terminator = '\n')
|
|
if (isScalarType!Terminator)
|
|
{
|
|
return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator);
|
|
}
|
|
|
|
/// ditto
|
|
auto byLineCopy(Terminator, Char = immutable char)
|
|
(KeepTerminator keepTerminator, Terminator terminator)
|
|
if (is(immutable ElementEncodingType!Terminator == immutable Char))
|
|
{
|
|
return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static assert(is(typeof(File("").byLine.front) == char[]));
|
|
static assert(is(typeof(File("").byLineCopy.front) == string));
|
|
static assert(
|
|
is(typeof(File("").byLineCopy!(char, char).front) == char[]));
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
static import std.file;
|
|
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "");
|
|
scope(success) std.file.remove(deleteme);
|
|
|
|
// Test empty file
|
|
auto f = File(deleteme);
|
|
foreach (line; f.byLine())
|
|
{
|
|
assert(false);
|
|
}
|
|
f.detach();
|
|
assert(!f.isOpen);
|
|
|
|
void test(Terminator)(string txt, in string[] witness,
|
|
KeepTerminator kt, Terminator term, bool popFirstLine = false)
|
|
{
|
|
import std.algorithm.sorting : sort;
|
|
import std.array : array;
|
|
import std.conv : text;
|
|
import std.range.primitives : walkLength;
|
|
|
|
uint i;
|
|
std.file.write(deleteme, txt);
|
|
auto f = File(deleteme);
|
|
scope(exit)
|
|
{
|
|
f.close();
|
|
assert(!f.isOpen);
|
|
}
|
|
auto lines = f.byLine(kt, term);
|
|
if (popFirstLine)
|
|
{
|
|
lines.popFront();
|
|
i = 1;
|
|
}
|
|
assert(lines.empty || lines.front is lines.front);
|
|
foreach (line; lines)
|
|
{
|
|
assert(line == witness[i++]);
|
|
}
|
|
assert(i == witness.length, text(i, " != ", witness.length));
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=11830
|
|
auto walkedLength = File(deleteme).byLine(kt, term).walkLength;
|
|
assert(walkedLength == witness.length, text(walkedLength, " != ", witness.length));
|
|
|
|
// test persistent lines
|
|
assert(File(deleteme).byLineCopy(kt, term).array.sort() == witness.dup.sort());
|
|
}
|
|
|
|
KeepTerminator kt = No.keepTerminator;
|
|
test("", null, kt, '\n');
|
|
test("\n", [ "" ], kt, '\n');
|
|
test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n');
|
|
test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n', true);
|
|
test("asd\ndef\nasdf\n", [ "asd", "def", "asdf" ], kt, '\n');
|
|
test("foo", [ "foo" ], kt, '\n', true);
|
|
test("bob\r\nmarge\r\nsteve\r\n", ["bob", "marge", "steve"],
|
|
kt, "\r\n");
|
|
test("sue\r", ["sue"], kt, '\r');
|
|
|
|
kt = Yes.keepTerminator;
|
|
test("", null, kt, '\n');
|
|
test("\n", [ "\n" ], kt, '\n');
|
|
test("asd\ndef\nasdf", [ "asd\n", "def\n", "asdf" ], kt, '\n');
|
|
test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n');
|
|
test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n', true);
|
|
test("foo", [ "foo" ], kt, '\n');
|
|
test("bob\r\nmarge\r\nsteve\r\n", ["bob\r\n", "marge\r\n", "steve\r\n"],
|
|
kt, "\r\n");
|
|
test("sue\r", ["sue\r"], kt, '\r');
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : drop, take;
|
|
|
|
version (Win64)
|
|
{
|
|
static import std.file;
|
|
|
|
/* the C function tmpfile doesn't seem to work, even when called from C */
|
|
auto deleteme = testFilename();
|
|
auto file = File(deleteme, "w+");
|
|
scope(success) std.file.remove(deleteme);
|
|
}
|
|
else version (CRuntime_Bionic)
|
|
{
|
|
static import std.file;
|
|
|
|
/* the C function tmpfile doesn't work when called from a shared
|
|
library apk:
|
|
https://code.google.com/p/android/issues/detail?id=66815 */
|
|
auto deleteme = testFilename();
|
|
auto file = File(deleteme, "w+");
|
|
scope(success) std.file.remove(deleteme);
|
|
}
|
|
else
|
|
auto file = File.tmpfile();
|
|
file.write("1\n2\n3\n");
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=9599
|
|
file.rewind();
|
|
File.ByLineImpl!(char, char) fbl = file.byLine();
|
|
auto fbl2 = fbl;
|
|
assert(fbl.front == "1");
|
|
assert(fbl.front is fbl2.front);
|
|
assert(fbl.take(1).equal(["1"]));
|
|
assert(fbl.equal(["2", "3"]));
|
|
assert(fbl.empty);
|
|
assert(file.isOpen); // we still have a valid reference
|
|
|
|
file.rewind();
|
|
fbl = file.byLine();
|
|
assert(!fbl.drop(2).empty);
|
|
assert(fbl.equal(["3"]));
|
|
assert(fbl.empty);
|
|
assert(file.isOpen);
|
|
|
|
file.detach();
|
|
assert(!file.isOpen);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "hi");
|
|
scope(success) std.file.remove(deleteme);
|
|
|
|
auto blc = File(deleteme).byLineCopy;
|
|
assert(!blc.empty);
|
|
// check front is cached
|
|
assert(blc.front is blc.front);
|
|
}
|
|
|
|
/**
|
|
Creates an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
|
|
set up to parse one line at a time from the file into a tuple.
|
|
|
|
Range primitives may throw `StdioException` on I/O error.
|
|
|
|
Params:
|
|
format = tuple record $(REF_ALTTEXT _format, formattedRead, std, _format)
|
|
|
|
Returns:
|
|
The input range set up to parse one line at a time into a record tuple.
|
|
|
|
See_Also:
|
|
|
|
It is similar to $(LREF byLine) and uses
|
|
$(REF_ALTTEXT _format, formattedRead, std, _format) under the hood.
|
|
*/
|
|
template byRecord(Fields...)
|
|
{
|
|
auto byRecord(string format)
|
|
{
|
|
return ByRecordImpl!(Fields)(this, format);
|
|
}
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
import std.typecons : tuple;
|
|
|
|
// prepare test file
|
|
auto testFile = std.file.deleteme();
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
std.file.write(testFile, "1 2\n4 1\n5 100");
|
|
scope(exit) std.file.remove(testFile);
|
|
|
|
File f = File(testFile);
|
|
scope(exit) f.close();
|
|
|
|
auto expected = [tuple(1, 2), tuple(4, 1), tuple(5, 100)];
|
|
uint i;
|
|
foreach (e; f.byRecord!(int, int)("%s %s"))
|
|
{
|
|
assert(e == expected[i++]);
|
|
}
|
|
}
|
|
|
|
// Note: This was documented until 2013/08
|
|
/*
|
|
* Range that reads a chunk at a time.
|
|
*/
|
|
private struct ByChunkImpl
|
|
{
|
|
private:
|
|
File file_;
|
|
ubyte[] chunk_;
|
|
|
|
void prime()
|
|
{
|
|
chunk_ = file_.rawRead(chunk_);
|
|
if (chunk_.length == 0)
|
|
file_.detach();
|
|
}
|
|
|
|
public:
|
|
this(File file, size_t size)
|
|
{
|
|
this(file, new ubyte[](size));
|
|
}
|
|
|
|
this(File file, ubyte[] buffer)
|
|
{
|
|
import std.exception : enforce;
|
|
enforce(buffer.length, "size must be larger than 0");
|
|
file_ = file;
|
|
chunk_ = buffer;
|
|
prime();
|
|
}
|
|
|
|
// `ByChunk`'s input range primitive operations.
|
|
@property nothrow
|
|
bool empty() const
|
|
{
|
|
return !file_.isOpen;
|
|
}
|
|
|
|
/// Ditto
|
|
@property nothrow
|
|
ubyte[] front()
|
|
{
|
|
version (assert)
|
|
{
|
|
import core.exception : RangeError;
|
|
if (empty)
|
|
throw new RangeError();
|
|
}
|
|
return chunk_;
|
|
}
|
|
|
|
/// Ditto
|
|
void popFront()
|
|
{
|
|
version (assert)
|
|
{
|
|
import core.exception : RangeError;
|
|
if (empty)
|
|
throw new RangeError();
|
|
}
|
|
prime();
|
|
}
|
|
}
|
|
|
|
/**
|
|
Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
|
|
set up to read from the file handle a chunk at a time.
|
|
|
|
The element type for the range will be `ubyte[]`. Range primitives
|
|
may throw `StdioException` on I/O error.
|
|
|
|
Example:
|
|
---------
|
|
void main()
|
|
{
|
|
// Read standard input 4KB at a time
|
|
foreach (ubyte[] buffer; stdin.byChunk(4096))
|
|
{
|
|
... use buffer ...
|
|
}
|
|
}
|
|
---------
|
|
|
|
The parameter may be a number (as shown in the example above) dictating the
|
|
size of each chunk. Alternatively, `byChunk` accepts a
|
|
user-provided buffer that it uses directly.
|
|
|
|
Example:
|
|
---------
|
|
void main()
|
|
{
|
|
// Read standard input 4KB at a time
|
|
foreach (ubyte[] buffer; stdin.byChunk(new ubyte[4096]))
|
|
{
|
|
... use buffer ...
|
|
}
|
|
}
|
|
---------
|
|
|
|
In either case, the content of the buffer is reused across calls. That means
|
|
`front` will not persist after `popFront` is called, so if retention is
|
|
needed, the caller must copy its contents (e.g. by calling `buffer.dup`).
|
|
|
|
In the example above, `buffer.length` is 4096 for all iterations, except
|
|
for the last one, in which case `buffer.length` may be less than 4096 (but
|
|
always greater than zero).
|
|
|
|
With the mentioned limitations, `byChunk` works with any algorithm
|
|
compatible with input ranges.
|
|
|
|
Example:
|
|
---
|
|
// Efficient file copy, 1MB at a time.
|
|
import std.algorithm, std.stdio;
|
|
void main()
|
|
{
|
|
stdin.byChunk(1024 * 1024).copy(stdout.lockingTextWriter());
|
|
}
|
|
---
|
|
|
|
$(REF joiner, std,algorithm,iteration) can be used to join chunks together into
|
|
a single range lazily.
|
|
Example:
|
|
---
|
|
import std.algorithm, std.stdio;
|
|
void main()
|
|
{
|
|
//Range of ranges
|
|
static assert(is(typeof(stdin.byChunk(4096).front) == ubyte[]));
|
|
//Range of elements
|
|
static assert(is(typeof(stdin.byChunk(4096).joiner.front) == ubyte));
|
|
}
|
|
---
|
|
|
|
Returns: A call to `byChunk` returns a range initialized with the `File`
|
|
object and the appropriate buffer.
|
|
|
|
Throws: If the user-provided size is zero or the user-provided buffer
|
|
is empty, throws an `Exception`. In case of an I/O error throws
|
|
`StdioException`.
|
|
*/
|
|
auto byChunk(size_t chunkSize)
|
|
{
|
|
return ByChunkImpl(this, chunkSize);
|
|
}
|
|
/// Ditto
|
|
auto byChunk(ubyte[] buffer)
|
|
{
|
|
return ByChunkImpl(this, buffer);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "asd\ndef\nasdf");
|
|
|
|
auto witness = ["asd\n", "def\n", "asdf" ];
|
|
auto f = File(deleteme);
|
|
scope(exit)
|
|
{
|
|
f.close();
|
|
assert(!f.isOpen);
|
|
std.file.remove(deleteme);
|
|
}
|
|
|
|
uint i;
|
|
foreach (chunk; f.byChunk(4))
|
|
assert(chunk == cast(ubyte[]) witness[i++]);
|
|
|
|
assert(i == witness.length);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "asd\ndef\nasdf");
|
|
|
|
auto witness = ["asd\n", "def\n", "asdf" ];
|
|
auto f = File(deleteme);
|
|
scope(exit)
|
|
{
|
|
f.close();
|
|
assert(!f.isOpen);
|
|
std.file.remove(deleteme);
|
|
}
|
|
|
|
uint i;
|
|
foreach (chunk; f.byChunk(new ubyte[4]))
|
|
assert(chunk == cast(ubyte[]) witness[i++]);
|
|
|
|
assert(i == witness.length);
|
|
}
|
|
|
|
// Note: This was documented until 2013/08
|
|
/*
|
|
`Range` that locks the file and allows fast writing to it.
|
|
*/
|
|
struct LockingTextWriter
|
|
{
|
|
private:
|
|
import std.range.primitives : ElementType, isInfinite, isInputRange;
|
|
// Access the FILE* handle through the 'file_' member
|
|
// to keep the object alive through refcounting
|
|
File file_;
|
|
|
|
// the unshared version of FILE* handle, extracted from the File object
|
|
@property _iobuf* handle_() @trusted { return cast(_iobuf*) file_._p.handle; }
|
|
|
|
// the file's orientation (byte- or wide-oriented)
|
|
int orientation_;
|
|
|
|
// Buffers for when we need to transcode.
|
|
wchar highSurrogate = '\0'; // '\0' indicates empty
|
|
void highSurrogateShouldBeEmpty() @safe
|
|
{
|
|
import std.utf : UTFException;
|
|
if (highSurrogate != '\0')
|
|
throw new UTFException("unpaired surrogate UTF-16 value");
|
|
}
|
|
char[4] rbuf8;
|
|
size_t rbuf8Filled = 0;
|
|
public:
|
|
|
|
this(ref File f) @trusted
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(f._p && f._p.handle, "Attempting to write to closed File");
|
|
file_ = f;
|
|
FILE* fps = f._p.handle;
|
|
|
|
version (MICROSOFT_STDIO)
|
|
{
|
|
// Microsoft doesn't implement fwide. Instead, there's the
|
|
// concept of ANSI/UNICODE mode. fputc doesn't work in UNICODE
|
|
// mode; fputwc has to be used. So that essentially means
|
|
// "wide-oriented" for us.
|
|
immutable int mode = __setmode(f.fileno, _O_TEXT);
|
|
// Set some arbitrary mode to obtain the previous one.
|
|
__setmode(f.fileno, mode); // Restore previous mode.
|
|
if (mode & (_O_WTEXT | _O_U16TEXT | _O_U8TEXT))
|
|
{
|
|
orientation_ = 1; // wide
|
|
}
|
|
}
|
|
else
|
|
{
|
|
import core.stdc.wchar_ : fwide;
|
|
orientation_ = fwide(fps, 0);
|
|
}
|
|
|
|
_FLOCK(fps);
|
|
}
|
|
|
|
~this() @trusted
|
|
{
|
|
if (auto p = file_._p)
|
|
{
|
|
if (p.handle) _FUNLOCK(p.handle);
|
|
}
|
|
file_ = File.init;
|
|
/* Destroy file_ before possibly throwing. Else it wouldn't be
|
|
destroyed, and its reference count would be wrong. */
|
|
highSurrogateShouldBeEmpty();
|
|
}
|
|
|
|
this(this) @trusted
|
|
{
|
|
if (auto p = file_._p)
|
|
{
|
|
if (p.handle) _FLOCK(p.handle);
|
|
}
|
|
}
|
|
|
|
/// Range primitive implementations.
|
|
void put(A)(scope A writeme)
|
|
if ((isSomeChar!(ElementType!A) ||
|
|
is(ElementType!A : const(ubyte))) &&
|
|
isInputRange!A &&
|
|
!isInfinite!A)
|
|
{
|
|
import std.exception : errnoEnforce;
|
|
|
|
alias C = ElementEncodingType!A;
|
|
static assert(!is(C == void));
|
|
static if (isSomeString!A && C.sizeof == 1 || is(A : const(ubyte)[]))
|
|
{
|
|
if (orientation_ <= 0)
|
|
{
|
|
//file.write(writeme); causes infinite recursion!!!
|
|
//file.rawWrite(writeme);
|
|
auto result = trustedFwrite(file_._p.handle, writeme);
|
|
if (result != writeme.length) errnoEnforce(0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// put each element in turn.
|
|
foreach (c; writeme)
|
|
{
|
|
put(c);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
void put(C)(scope C c) @safe if (isSomeChar!C || is(C : const(ubyte)))
|
|
{
|
|
import std.utf : decodeFront, encode, stride;
|
|
|
|
static if (c.sizeof == 1)
|
|
{
|
|
highSurrogateShouldBeEmpty();
|
|
if (orientation_ <= 0) trustedFPUTC(c, handle_);
|
|
else if (c <= 0x7F) trustedFPUTWC(c, handle_);
|
|
else if (c >= 0b1100_0000) // start byte of multibyte sequence
|
|
{
|
|
rbuf8[0] = c;
|
|
rbuf8Filled = 1;
|
|
}
|
|
else // continuation byte of multibyte sequence
|
|
{
|
|
rbuf8[rbuf8Filled] = c;
|
|
++rbuf8Filled;
|
|
if (stride(rbuf8[]) == rbuf8Filled) // sequence is complete
|
|
{
|
|
char[] str = rbuf8[0 .. rbuf8Filled];
|
|
immutable dchar d = decodeFront(str);
|
|
wchar_t[4 / wchar_t.sizeof] wbuf;
|
|
immutable size = encode(wbuf, d);
|
|
foreach (i; 0 .. size)
|
|
trustedFPUTWC(wbuf[i], handle_);
|
|
rbuf8Filled = 0;
|
|
}
|
|
}
|
|
}
|
|
else static if (c.sizeof == 2)
|
|
{
|
|
import std.utf : decode;
|
|
|
|
if (c <= 0x7F)
|
|
{
|
|
highSurrogateShouldBeEmpty();
|
|
if (orientation_ <= 0) trustedFPUTC(c, handle_);
|
|
else trustedFPUTWC(c, handle_);
|
|
}
|
|
else if (0xD800 <= c && c <= 0xDBFF) // high surrogate
|
|
{
|
|
highSurrogateShouldBeEmpty();
|
|
highSurrogate = c;
|
|
}
|
|
else // standalone or low surrogate
|
|
{
|
|
dchar d = c;
|
|
if (highSurrogate != '\0')
|
|
{
|
|
immutable wchar[2] rbuf = [highSurrogate, c];
|
|
size_t index = 0;
|
|
d = decode(rbuf[], index);
|
|
highSurrogate = 0;
|
|
}
|
|
if (orientation_ <= 0)
|
|
{
|
|
char[4] wbuf;
|
|
immutable size = encode(wbuf, d);
|
|
foreach (i; 0 .. size)
|
|
trustedFPUTC(wbuf[i], handle_);
|
|
}
|
|
else
|
|
{
|
|
wchar_t[4 / wchar_t.sizeof] wbuf;
|
|
immutable size = encode(wbuf, d);
|
|
foreach (i; 0 .. size)
|
|
trustedFPUTWC(wbuf[i], handle_);
|
|
}
|
|
rbuf8Filled = 0;
|
|
}
|
|
}
|
|
else // 32-bit characters
|
|
{
|
|
import std.utf : encode;
|
|
|
|
highSurrogateShouldBeEmpty();
|
|
if (orientation_ <= 0)
|
|
{
|
|
if (c <= 0x7F)
|
|
{
|
|
trustedFPUTC(c, handle_);
|
|
}
|
|
else
|
|
{
|
|
char[4] buf = void;
|
|
immutable len = encode(buf, c);
|
|
foreach (i ; 0 .. len)
|
|
trustedFPUTC(buf[i], handle_);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
version (Windows)
|
|
{
|
|
import std.utf : isValidDchar;
|
|
|
|
assert(isValidDchar(c));
|
|
if (c <= 0xFFFF)
|
|
{
|
|
trustedFPUTWC(cast(wchar_t) c, handle_);
|
|
}
|
|
else
|
|
{
|
|
trustedFPUTWC(cast(wchar_t)
|
|
((((c - 0x10000) >> 10) & 0x3FF)
|
|
+ 0xD800), handle_);
|
|
trustedFPUTWC(cast(wchar_t)
|
|
(((c - 0x10000) & 0x3FF) + 0xDC00),
|
|
handle_);
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
trustedFPUTWC(cast(wchar_t) c, handle_);
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Output range which locks the file when created, and unlocks the file when it goes
|
|
* out of scope.
|
|
*
|
|
* Returns: An $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
|
|
* which accepts string types, `ubyte[]`, individual character types, and
|
|
* individual `ubyte`s.
|
|
*
|
|
* Note: Writing either arrays of `char`s or `ubyte`s is faster than
|
|
* writing each character individually from a range. For large amounts of data,
|
|
* writing the contents in chunks using an intermediary array can result
|
|
* in a speed increase.
|
|
*
|
|
* Throws: $(REF UTFException, std, utf) if the data given is a `char` range
|
|
* and it contains malformed UTF data.
|
|
*
|
|
* See_Also: $(LREF byChunk) for an example.
|
|
*/
|
|
auto lockingTextWriter() @safe
|
|
{
|
|
return LockingTextWriter(this);
|
|
}
|
|
|
|
// An output range which optionally locks the file and puts it into
|
|
// binary mode (similar to rawWrite). Because it needs to restore
|
|
// the file mode on destruction, it is RefCounted on Windows.
|
|
struct BinaryWriterImpl(bool locking)
|
|
{
|
|
import std.traits : hasIndirections;
|
|
private:
|
|
// Access the FILE* handle through the 'file_' member
|
|
// to keep the object alive through refcounting
|
|
File file_;
|
|
string name;
|
|
|
|
version (Windows)
|
|
{
|
|
int fd, oldMode;
|
|
version (DIGITAL_MARS_STDIO)
|
|
ubyte oldInfo;
|
|
}
|
|
|
|
public:
|
|
// Don't use this, but `File.lockingBinaryWriter()` instead.
|
|
// Must be public for RefCounted and emplace() in druntime.
|
|
this(scope ref File f)
|
|
{
|
|
import std.exception : enforce;
|
|
file_ = f;
|
|
enforce(f._p && f._p.handle);
|
|
name = f._name;
|
|
FILE* fps = f._p.handle;
|
|
static if (locking)
|
|
_FLOCK(fps);
|
|
|
|
version (Windows)
|
|
{
|
|
.fflush(fps); // before changing translation mode
|
|
fd = .fileno(fps);
|
|
oldMode = .__setmode(fd, _O_BINARY);
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
import core.atomic : atomicOp;
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=4243
|
|
oldInfo = __fhnd_info[fd];
|
|
atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT);
|
|
}
|
|
}
|
|
}
|
|
|
|
~this()
|
|
{
|
|
if (!file_._p || !file_._p.handle)
|
|
return;
|
|
|
|
FILE* fps = file_._p.handle;
|
|
|
|
version (Windows)
|
|
{
|
|
.fflush(fps); // before restoring translation mode
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=4243
|
|
__fhnd_info[fd] = oldInfo;
|
|
}
|
|
.__setmode(fd, oldMode);
|
|
}
|
|
|
|
_FUNLOCK(fps);
|
|
}
|
|
|
|
void rawWrite(T)(in T[] buffer)
|
|
{
|
|
import std.conv : text;
|
|
import std.exception : errnoEnforce;
|
|
|
|
auto result = trustedFwrite(file_._p.handle, buffer);
|
|
if (result == result.max) result = 0;
|
|
errnoEnforce(result == buffer.length,
|
|
text("Wrote ", result, " instead of ", buffer.length,
|
|
" objects of type ", T.stringof, " to file `",
|
|
name, "'"));
|
|
}
|
|
|
|
version (Windows)
|
|
{
|
|
@disable this(this);
|
|
}
|
|
else
|
|
{
|
|
this(this)
|
|
{
|
|
if (auto p = file_._p)
|
|
{
|
|
if (p.handle) _FLOCK(p.handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
void put(T)(auto ref scope const T value)
|
|
if (!hasIndirections!T &&
|
|
!isInputRange!T)
|
|
{
|
|
rawWrite((&value)[0 .. 1]);
|
|
}
|
|
|
|
void put(T)(scope const(T)[] array)
|
|
if (!hasIndirections!T &&
|
|
!isInputRange!T)
|
|
{
|
|
rawWrite(array);
|
|
}
|
|
}
|
|
|
|
/** Returns an output range that locks the file and allows fast writing to it.
|
|
|
|
Example:
|
|
Produce a grayscale image of the $(LINK2 https://en.wikipedia.org/wiki/Mandelbrot_set, Mandelbrot set)
|
|
in binary $(LINK2 https://en.wikipedia.org/wiki/Netpbm_format, Netpbm format) to standard output.
|
|
---
|
|
import std.algorithm, std.complex, std.range, std.stdio;
|
|
|
|
void main()
|
|
{
|
|
enum size = 500;
|
|
writef("P5\n%d %d %d\n", size, size, ubyte.max);
|
|
|
|
iota(-1, 3, 2.0/size).map!(y =>
|
|
iota(-1.5, 0.5, 2.0/size).map!(x =>
|
|
cast(ubyte)(1+
|
|
recurrence!((a, n) => x + y * complex(0, 1) + a[n-1]^^2)(complex(0))
|
|
.take(ubyte.max)
|
|
.countUntil!(z => z.re^^2 + z.im^^2 > 4))
|
|
)
|
|
)
|
|
.copy(stdout.lockingBinaryWriter);
|
|
}
|
|
---
|
|
*/
|
|
auto lockingBinaryWriter()
|
|
{
|
|
alias LockingBinaryWriterImpl = BinaryWriterImpl!true;
|
|
|
|
version (Windows)
|
|
{
|
|
import std.typecons : RefCounted;
|
|
alias LockingBinaryWriter = RefCounted!LockingBinaryWriterImpl;
|
|
}
|
|
else
|
|
alias LockingBinaryWriter = LockingBinaryWriterImpl;
|
|
|
|
return LockingBinaryWriter(this);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.algorithm.mutation : reverse;
|
|
import std.exception : collectException;
|
|
static import std.file;
|
|
import std.range : only, retro;
|
|
import std.string : format;
|
|
|
|
auto deleteme = testFilename();
|
|
scope(exit) collectException(std.file.remove(deleteme));
|
|
|
|
{
|
|
auto writer = File(deleteme, "wb").lockingBinaryWriter();
|
|
auto input = File(deleteme, "rb");
|
|
|
|
ubyte[1] byteIn = [42];
|
|
writer.rawWrite(byteIn);
|
|
destroy(writer);
|
|
|
|
ubyte[1] byteOut = input.rawRead(new ubyte[1]);
|
|
assert(byteIn[0] == byteOut[0]);
|
|
}
|
|
|
|
auto output = File(deleteme, "wb");
|
|
auto writer = output.lockingBinaryWriter();
|
|
auto input = File(deleteme, "rb");
|
|
|
|
T[] readExact(T)(T[] buf)
|
|
{
|
|
auto result = input.rawRead(buf);
|
|
assert(result.length == buf.length,
|
|
"Read %d out of %d bytes"
|
|
.format(result.length, buf.length));
|
|
return result;
|
|
}
|
|
|
|
// test raw values
|
|
ubyte byteIn = 42;
|
|
byteIn.only.copy(writer); output.flush();
|
|
ubyte byteOut = readExact(new ubyte[1])[0];
|
|
assert(byteIn == byteOut);
|
|
|
|
// test arrays
|
|
ubyte[] bytesIn = [1, 2, 3, 4, 5];
|
|
bytesIn.copy(writer); output.flush();
|
|
ubyte[] bytesOut = readExact(new ubyte[bytesIn.length]);
|
|
scope(failure) .writeln(bytesOut);
|
|
assert(bytesIn == bytesOut);
|
|
|
|
// test ranges of values
|
|
bytesIn.retro.copy(writer); output.flush();
|
|
bytesOut = readExact(bytesOut);
|
|
bytesOut.reverse();
|
|
assert(bytesIn == bytesOut);
|
|
|
|
// test string
|
|
"foobar".copy(writer); output.flush();
|
|
char[] charsOut = readExact(new char[6]);
|
|
assert(charsOut == "foobar");
|
|
|
|
// test ranges of arrays
|
|
only("foo", "bar").copy(writer); output.flush();
|
|
charsOut = readExact(charsOut);
|
|
assert(charsOut == "foobar");
|
|
|
|
// test that we are writing arrays as is,
|
|
// without UTF-8 transcoding
|
|
"foo"d.copy(writer); output.flush();
|
|
dchar[] dcharsOut = readExact(new dchar[3]);
|
|
assert(dcharsOut == "foo");
|
|
}
|
|
|
|
/** Returns the size of the file in bytes, ulong.max if file is not searchable or throws if the operation fails.
|
|
Example:
|
|
---
|
|
import std.stdio, std.file;
|
|
|
|
void main()
|
|
{
|
|
string deleteme = "delete.me";
|
|
auto file_handle = File(deleteme, "w");
|
|
file_handle.write("abc"); //create temporary file
|
|
scope(exit) deleteme.remove; //remove temporary file at scope exit
|
|
|
|
assert(file_handle.size() == 3); //check if file size is 3 bytes
|
|
}
|
|
---
|
|
*/
|
|
@property ulong size() @safe
|
|
{
|
|
import std.exception : collectException;
|
|
|
|
ulong pos = void;
|
|
if (collectException(pos = tell)) return ulong.max;
|
|
scope(exit) seek(pos);
|
|
seek(0, SEEK_END);
|
|
return tell;
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
@system struct SystemToString
|
|
{
|
|
string toString()
|
|
{
|
|
return "system";
|
|
}
|
|
}
|
|
|
|
@trusted struct TrustedToString
|
|
{
|
|
string toString()
|
|
{
|
|
return "trusted";
|
|
}
|
|
}
|
|
|
|
@safe struct SafeToString
|
|
{
|
|
string toString()
|
|
{
|
|
return "safe";
|
|
}
|
|
}
|
|
|
|
@system void systemTests()
|
|
{
|
|
//system code can write to files/stdout with anything!
|
|
if (false)
|
|
{
|
|
auto f = File();
|
|
|
|
f.write("just a string");
|
|
f.write("string with arg: ", 47);
|
|
f.write(SystemToString());
|
|
f.write(TrustedToString());
|
|
f.write(SafeToString());
|
|
|
|
write("just a string");
|
|
write("string with arg: ", 47);
|
|
write(SystemToString());
|
|
write(TrustedToString());
|
|
write(SafeToString());
|
|
|
|
f.writeln("just a string");
|
|
f.writeln("string with arg: ", 47);
|
|
f.writeln(SystemToString());
|
|
f.writeln(TrustedToString());
|
|
f.writeln(SafeToString());
|
|
|
|
writeln("just a string");
|
|
writeln("string with arg: ", 47);
|
|
writeln(SystemToString());
|
|
writeln(TrustedToString());
|
|
writeln(SafeToString());
|
|
|
|
f.writef("string with arg: %s", 47);
|
|
f.writef("%s", SystemToString());
|
|
f.writef("%s", TrustedToString());
|
|
f.writef("%s", SafeToString());
|
|
|
|
writef("string with arg: %s", 47);
|
|
writef("%s", SystemToString());
|
|
writef("%s", TrustedToString());
|
|
writef("%s", SafeToString());
|
|
|
|
f.writefln("string with arg: %s", 47);
|
|
f.writefln("%s", SystemToString());
|
|
f.writefln("%s", TrustedToString());
|
|
f.writefln("%s", SafeToString());
|
|
|
|
writefln("string with arg: %s", 47);
|
|
writefln("%s", SystemToString());
|
|
writefln("%s", TrustedToString());
|
|
writefln("%s", SafeToString());
|
|
}
|
|
}
|
|
|
|
@safe void safeTests()
|
|
{
|
|
auto f = File();
|
|
|
|
//safe code can write to files only with @safe and @trusted code...
|
|
if (false)
|
|
{
|
|
f.write("just a string");
|
|
f.write("string with arg: ", 47);
|
|
f.write(TrustedToString());
|
|
f.write(SafeToString());
|
|
|
|
write("just a string");
|
|
write("string with arg: ", 47);
|
|
write(TrustedToString());
|
|
write(SafeToString());
|
|
|
|
f.writeln("just a string");
|
|
f.writeln("string with arg: ", 47);
|
|
f.writeln(TrustedToString());
|
|
f.writeln(SafeToString());
|
|
|
|
writeln("just a string");
|
|
writeln("string with arg: ", 47);
|
|
writeln(TrustedToString());
|
|
writeln(SafeToString());
|
|
|
|
f.writef("string with arg: %s", 47);
|
|
f.writef("%s", TrustedToString());
|
|
f.writef("%s", SafeToString());
|
|
|
|
writef("string with arg: %s", 47);
|
|
writef("%s", TrustedToString());
|
|
writef("%s", SafeToString());
|
|
|
|
f.writefln("string with arg: %s", 47);
|
|
f.writefln("%s", TrustedToString());
|
|
f.writefln("%s", SafeToString());
|
|
|
|
writefln("string with arg: %s", 47);
|
|
writefln("%s", TrustedToString());
|
|
writefln("%s", SafeToString());
|
|
}
|
|
|
|
static assert(!__traits(compiles, f.write(SystemToString().toString())));
|
|
static assert(!__traits(compiles, f.writeln(SystemToString())));
|
|
static assert(!__traits(compiles, f.writef("%s", SystemToString())));
|
|
static assert(!__traits(compiles, f.writefln("%s", SystemToString())));
|
|
|
|
static assert(!__traits(compiles, write(SystemToString().toString())));
|
|
static assert(!__traits(compiles, writeln(SystemToString())));
|
|
static assert(!__traits(compiles, writef("%s", SystemToString())));
|
|
static assert(!__traits(compiles, writefln("%s", SystemToString())));
|
|
}
|
|
|
|
systemTests();
|
|
safeTests();
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.exception : collectException;
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
scope(exit) collectException(std.file.remove(deleteme));
|
|
std.file.write(deleteme, "1 2 3");
|
|
auto f = File(deleteme);
|
|
assert(f.size == 5);
|
|
assert(f.tell == 0);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
// @system due to readln
|
|
static import std.file;
|
|
import std.range : chain, only, repeat;
|
|
import std.range.primitives : isOutputRange;
|
|
|
|
auto deleteme = testFilename();
|
|
scope(exit) std.file.remove(deleteme);
|
|
|
|
{
|
|
auto writer = File(deleteme, "w").lockingTextWriter();
|
|
static assert(isOutputRange!(typeof(writer), dchar));
|
|
writer.put("日本語");
|
|
writer.put("日本語"w);
|
|
writer.put("日本語"d);
|
|
writer.put('日');
|
|
writer.put(chain(only('本'), only('語')));
|
|
// https://issues.dlang.org/show_bug.cgi?id=11945
|
|
writer.put(repeat('#', 12));
|
|
// https://issues.dlang.org/show_bug.cgi?id=17229
|
|
writer.put(cast(immutable(ubyte)[])"日本語");
|
|
}
|
|
assert(File(deleteme).readln() == "日本語日本語日本語日本語############日本語");
|
|
}
|
|
|
|
@safe unittest // wchar -> char
|
|
{
|
|
static import std.file;
|
|
import std.exception : assertThrown;
|
|
import std.utf : UTFException;
|
|
|
|
auto deleteme = testFilename();
|
|
scope(exit) std.file.remove(deleteme);
|
|
|
|
{
|
|
auto writer = File(deleteme, "w").lockingTextWriter();
|
|
writer.put("\U0001F608"w);
|
|
}
|
|
assert(std.file.readText!string(deleteme) == "\U0001F608");
|
|
|
|
// Test invalid input: unpaired high surrogate
|
|
{
|
|
immutable wchar surr = "\U0001F608"w[0];
|
|
auto f = File(deleteme, "w");
|
|
assertThrown!UTFException(() {
|
|
auto writer = f.lockingTextWriter();
|
|
writer.put('x');
|
|
writer.put(surr);
|
|
assertThrown!UTFException(writer.put(char('y')));
|
|
assertThrown!UTFException(writer.put(wchar('y')));
|
|
assertThrown!UTFException(writer.put(dchar('y')));
|
|
assertThrown!UTFException(writer.put(surr));
|
|
// First `surr` is still unpaired at this point. `writer` gets
|
|
// destroyed now, and the destructor throws a UTFException for
|
|
// the unpaired surrogate.
|
|
} ());
|
|
}
|
|
assert(std.file.readText!string(deleteme) == "x");
|
|
|
|
// Test invalid input: unpaired low surrogate
|
|
{
|
|
immutable wchar surr = "\U0001F608"w[1];
|
|
auto writer = File(deleteme, "w").lockingTextWriter();
|
|
assertThrown!UTFException(writer.put(surr));
|
|
writer.put('y');
|
|
assertThrown!UTFException(writer.put(surr));
|
|
}
|
|
assert(std.file.readText!string(deleteme) == "y");
|
|
}
|
|
|
|
@safe unittest // issue 18801
|
|
{
|
|
static import std.file;
|
|
import std.string : stripLeft;
|
|
|
|
auto deleteme = testFilename();
|
|
scope(exit) std.file.remove(deleteme);
|
|
|
|
{
|
|
auto writer = File(deleteme, "w,ccs=UTF-8").lockingTextWriter();
|
|
writer.put("foo");
|
|
}
|
|
assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foo");
|
|
|
|
{
|
|
auto writer = File(deleteme, "a,ccs=UTF-8").lockingTextWriter();
|
|
writer.put("bar");
|
|
}
|
|
assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") == "foobar");
|
|
}
|
|
@safe unittest // char/wchar -> wchar_t
|
|
{
|
|
import core.stdc.locale : LC_CTYPE, setlocale;
|
|
import core.stdc.wchar_ : fwide;
|
|
import core.stdc.string : strlen;
|
|
import std.algorithm.searching : any, endsWith;
|
|
import std.conv : text;
|
|
import std.meta : AliasSeq;
|
|
import std.string : fromStringz, stripLeft;
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
scope(exit) std.file.remove(deleteme);
|
|
const char* oldCt = () @trusted {
|
|
const(char)* p = setlocale(LC_CTYPE, null);
|
|
// Subsequent calls to `setlocale` might invalidate this return value,
|
|
// so duplicate it.
|
|
// See: https://github.com/dlang/phobos/pull/7660
|
|
return p ? p[0 .. strlen(p) + 1].idup.ptr : null;
|
|
}();
|
|
const utf8 = ["en_US.UTF-8", "C.UTF-8", ".65001"].any!((loc) @trusted {
|
|
return setlocale(LC_CTYPE, loc.ptr).fromStringz.endsWith(loc);
|
|
});
|
|
scope(exit) () @trusted { setlocale(LC_CTYPE, oldCt); } ();
|
|
version (DIGITAL_MARS_STDIO) // DM can't handle Unicode above U+07FF.
|
|
{
|
|
alias strs = AliasSeq!("xä\u07FE", "yö\u07FF"w);
|
|
}
|
|
else
|
|
{
|
|
alias strs = AliasSeq!("xä\U0001F607", "yö\U0001F608"w);
|
|
}
|
|
{
|
|
auto f = File(deleteme, "w");
|
|
version (MICROSOFT_STDIO)
|
|
{
|
|
() @trusted { __setmode(fileno(f.getFP()), _O_U8TEXT); } ();
|
|
}
|
|
else
|
|
{
|
|
assert(fwide(f.getFP(), 1) == 1);
|
|
}
|
|
auto writer = f.lockingTextWriter();
|
|
assert(writer.orientation_ == 1);
|
|
static foreach (s; strs) writer.put(s);
|
|
}
|
|
assert(std.file.readText!string(deleteme).stripLeft("\uFEFF") ==
|
|
text(strs));
|
|
}
|
|
@safe unittest // https://issues.dlang.org/show_bug.cgi?id=18789
|
|
{
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
scope(exit) std.file.remove(deleteme);
|
|
// converting to char
|
|
{
|
|
auto f = File(deleteme, "w");
|
|
f.writeln("\U0001F608"w); // UTFException
|
|
}
|
|
// converting to wchar_t
|
|
{
|
|
auto f = File(deleteme, "w,ccs=UTF-16LE");
|
|
// from char
|
|
f.writeln("ö"); // writes garbage
|
|
f.writeln("\U0001F608"); // ditto
|
|
// from wchar
|
|
f.writeln("\U0001F608"w); // leads to ErrnoException
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.exception : collectException;
|
|
auto e = collectException({ File f; f.writeln("Hello!"); }());
|
|
assert(e && e.msg == "Attempting to write to closed File");
|
|
}
|
|
|
|
@safe unittest // https://issues.dlang.org/show_bug.cgi?id=21592
|
|
{
|
|
import std.exception : collectException;
|
|
import std.utf : UTFException;
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
scope(exit) std.file.remove(deleteme);
|
|
auto f = File(deleteme, "w");
|
|
auto e = collectException!UTFException(f.writeln(wchar(0xD801)));
|
|
assert(e.next is null);
|
|
}
|
|
|
|
version (StdStressTest)
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=15768
|
|
@system unittest
|
|
{
|
|
import std.parallelism : parallel;
|
|
import std.range : iota;
|
|
|
|
auto deleteme = testFilename();
|
|
stderr = File(deleteme, "w");
|
|
|
|
foreach (t; 1_000_000.iota.parallel)
|
|
{
|
|
stderr.write("aaa");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Used to specify the lock type for `File.lock` and `File.tryLock`.
|
|
enum LockType
|
|
{
|
|
/**
|
|
* Specifies a _read (shared) lock. A _read lock denies all processes
|
|
* write access to the specified region of the file, including the
|
|
* process that first locks the region. All processes can _read the
|
|
* locked region. Multiple simultaneous _read locks are allowed, as
|
|
* long as there are no exclusive locks.
|
|
*/
|
|
read,
|
|
|
|
/**
|
|
* Specifies a read/write (exclusive) lock. A read/write lock denies all
|
|
* other processes both read and write access to the locked file region.
|
|
* If a segment has an exclusive lock, it may not have any shared locks
|
|
* or other exclusive locks.
|
|
*/
|
|
readWrite
|
|
}
|
|
|
|
struct LockingTextReader
|
|
{
|
|
private File _f;
|
|
private char _front;
|
|
private bool _hasChar;
|
|
|
|
this(File f)
|
|
{
|
|
import std.exception : enforce;
|
|
enforce(f.isOpen, "LockingTextReader: File must be open");
|
|
_f = f;
|
|
_FLOCK(_f._p.handle);
|
|
}
|
|
|
|
this(this)
|
|
{
|
|
_FLOCK(_f._p.handle);
|
|
}
|
|
|
|
~this()
|
|
{
|
|
if (_hasChar)
|
|
ungetc(_front, cast(FILE*)_f._p.handle);
|
|
|
|
// File locking has its own reference count
|
|
if (_f.isOpen) _FUNLOCK(_f._p.handle);
|
|
}
|
|
|
|
void opAssign(LockingTextReader r)
|
|
{
|
|
import std.algorithm.mutation : swap;
|
|
swap(this, r);
|
|
}
|
|
|
|
@property bool empty()
|
|
{
|
|
if (!_hasChar)
|
|
{
|
|
if (!_f.isOpen || _f.eof)
|
|
return true;
|
|
immutable int c = _FGETC(cast(_iobuf*) _f._p.handle);
|
|
if (c == EOF)
|
|
{
|
|
.destroy(_f);
|
|
return true;
|
|
}
|
|
_front = cast(char) c;
|
|
_hasChar = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@property char front()
|
|
{
|
|
if (!_hasChar)
|
|
{
|
|
version (assert)
|
|
{
|
|
import core.exception : RangeError;
|
|
if (empty)
|
|
throw new RangeError();
|
|
}
|
|
else
|
|
{
|
|
empty;
|
|
}
|
|
}
|
|
return _front;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
if (!_hasChar)
|
|
empty;
|
|
_hasChar = false;
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
// @system due to readf
|
|
static import std.file;
|
|
import std.range.primitives : isInputRange;
|
|
|
|
static assert(isInputRange!LockingTextReader);
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "1 2 3");
|
|
scope(exit) std.file.remove(deleteme);
|
|
int x;
|
|
auto f = File(deleteme);
|
|
f.readf("%s ", &x);
|
|
assert(x == 1);
|
|
f.readf("%d ", &x);
|
|
assert(x == 2);
|
|
f.readf("%d ", &x);
|
|
assert(x == 3);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=13686
|
|
@system unittest
|
|
{
|
|
import std.algorithm.comparison : equal;
|
|
static import std.file;
|
|
import std.utf : byDchar;
|
|
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "Тест");
|
|
scope(exit) std.file.remove(deleteme);
|
|
|
|
string s;
|
|
File(deleteme).readf("%s", &s);
|
|
assert(s == "Тест");
|
|
|
|
auto ltr = LockingTextReader(File(deleteme)).byDchar;
|
|
assert(equal(ltr, "Тест".byDchar));
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=12320
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
std.file.write(deleteme, "ab");
|
|
scope(exit) std.file.remove(deleteme);
|
|
auto ltr = LockingTextReader(File(deleteme));
|
|
assert(ltr.front == 'a');
|
|
ltr.popFront();
|
|
assert(ltr.front == 'b');
|
|
ltr.popFront();
|
|
assert(ltr.empty);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14861
|
|
@system unittest
|
|
{
|
|
// @system due to readf
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
File fw = File(deleteme, "w");
|
|
for (int i; i != 5000; i++)
|
|
fw.writeln(i, ";", "Иванов;Пётр;Петрович");
|
|
fw.close();
|
|
scope(exit) std.file.remove(deleteme);
|
|
// Test read
|
|
File fr = File(deleteme, "r");
|
|
scope (exit) fr.close();
|
|
int nom; string fam, nam, ot;
|
|
// Error format read
|
|
while (!fr.eof)
|
|
fr.readf("%s;%s;%s;%s\n", &nom, &fam, &nam, &ot);
|
|
}
|
|
|
|
/**
|
|
* Indicates whether `T` is a file handle, i.e. the type
|
|
* is implicitly convertable to $(LREF File) or a pointer to a
|
|
* $(REF FILE, core,stdc,stdio).
|
|
*
|
|
* Returns:
|
|
* `true` if `T` is a file handle, `false` otherwise.
|
|
*/
|
|
template isFileHandle(T)
|
|
{
|
|
enum isFileHandle = is(T : FILE*) ||
|
|
is(T : File);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
static assert(isFileHandle!(FILE*));
|
|
static assert(isFileHandle!(File));
|
|
}
|
|
|
|
/**
|
|
* Property used by writeln/etc. so it can infer @safe since stdout is __gshared
|
|
*/
|
|
private @property File trustedStdout() @trusted
|
|
{
|
|
return stdout;
|
|
}
|
|
|
|
/***********************************
|
|
Writes its arguments in text format to standard output (without a trailing newline).
|
|
|
|
Params:
|
|
args = the items to write to `stdout`
|
|
|
|
Throws: In case of an I/O error, throws an `StdioException`.
|
|
|
|
Example:
|
|
Reads `stdin` and writes it to `stdout` with an argument
|
|
counter.
|
|
---
|
|
import std.stdio;
|
|
|
|
void main()
|
|
{
|
|
string line;
|
|
|
|
for (size_t count = 0; (line = readln) !is null; count++)
|
|
{
|
|
write("Input ", count, ": ", line, "\n");
|
|
}
|
|
}
|
|
---
|
|
*/
|
|
void write(T...)(T args)
|
|
if (!is(T[0] : File))
|
|
{
|
|
trustedStdout.write(args);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
void[] buf;
|
|
if (false) write(buf);
|
|
// test write
|
|
auto deleteme = testFilename();
|
|
auto f = File(deleteme, "w");
|
|
f.write("Hello, ", "world number ", 42, "!");
|
|
f.close();
|
|
scope(exit) { std.file.remove(deleteme); }
|
|
assert(cast(char[]) std.file.read(deleteme) == "Hello, world number 42!");
|
|
}
|
|
|
|
/***********************************
|
|
* Equivalent to `write(args, '\n')`. Calling `writeln` without
|
|
* arguments is valid and just prints a newline to the standard
|
|
* output.
|
|
*
|
|
* Params:
|
|
* args = the items to write to `stdout`
|
|
*
|
|
* Throws:
|
|
* In case of an I/O error, throws an $(LREF StdioException).
|
|
* Example:
|
|
* Reads `stdin` and writes it to `stdout` with an argument
|
|
* counter.
|
|
---
|
|
import std.stdio;
|
|
|
|
void main()
|
|
{
|
|
string line;
|
|
|
|
for (size_t count = 0; (line = readln) !is null; count++)
|
|
{
|
|
writeln("Input ", count, ": ", line);
|
|
}
|
|
}
|
|
---
|
|
*/
|
|
void writeln(T...)(T args)
|
|
{
|
|
static if (T.length == 0)
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(fputc('\n', .trustedStdout._p.handle) != EOF, "fputc failed");
|
|
}
|
|
else static if (T.length == 1 &&
|
|
is(T[0] : const(char)[]) &&
|
|
(is(T[0] == U[], U) || __traits(isStaticArray, T[0])))
|
|
{
|
|
// Specialization for strings - a very frequent case
|
|
auto w = .trustedStdout.lockingTextWriter();
|
|
|
|
static if (__traits(isStaticArray, T[0]))
|
|
{
|
|
w.put(args[0][]);
|
|
}
|
|
else
|
|
{
|
|
w.put(args[0]);
|
|
}
|
|
w.put('\n');
|
|
}
|
|
else
|
|
{
|
|
// Most general instance
|
|
trustedStdout.write(args, '\n');
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Just make sure the call compiles
|
|
if (false) writeln();
|
|
|
|
if (false) writeln("wyda");
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=8040
|
|
if (false) writeln(null);
|
|
if (false) writeln(">", null, "<");
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14041
|
|
if (false)
|
|
{
|
|
char[8] a;
|
|
writeln(a);
|
|
immutable b = a;
|
|
b.writeln;
|
|
const c = a[];
|
|
c.writeln;
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
|
|
// test writeln
|
|
auto deleteme = testFilename();
|
|
auto f = File(deleteme, "w");
|
|
scope(exit) { std.file.remove(deleteme); }
|
|
f.writeln("Hello, ", "world number ", 42, "!");
|
|
f.close();
|
|
version (Windows)
|
|
assert(cast(char[]) std.file.read(deleteme) ==
|
|
"Hello, world number 42!\r\n");
|
|
else
|
|
assert(cast(char[]) std.file.read(deleteme) ==
|
|
"Hello, world number 42!\n");
|
|
|
|
// test writeln on stdout
|
|
auto saveStdout = stdout;
|
|
scope(exit) stdout = saveStdout;
|
|
stdout.open(deleteme, "w");
|
|
writeln("Hello, ", "world number ", 42, "!");
|
|
stdout.close();
|
|
version (Windows)
|
|
assert(cast(char[]) std.file.read(deleteme) ==
|
|
"Hello, world number 42!\r\n");
|
|
else
|
|
assert(cast(char[]) std.file.read(deleteme) ==
|
|
"Hello, world number 42!\n");
|
|
|
|
stdout.open(deleteme, "w");
|
|
writeln("Hello!"c);
|
|
writeln("Hello!"w); // https://issues.dlang.org/show_bug.cgi?id=8386
|
|
writeln("Hello!"d); // https://issues.dlang.org/show_bug.cgi?id=8386
|
|
writeln("embedded\0null"c); // https://issues.dlang.org/show_bug.cgi?id=8730
|
|
stdout.close();
|
|
version (Windows)
|
|
assert(cast(char[]) std.file.read(deleteme) ==
|
|
"Hello!\r\nHello!\r\nHello!\r\nembedded\0null\r\n");
|
|
else
|
|
assert(cast(char[]) std.file.read(deleteme) ==
|
|
"Hello!\nHello!\nHello!\nembedded\0null\n");
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
auto f = File(deleteme, "w");
|
|
scope(exit) { std.file.remove(deleteme); }
|
|
|
|
enum EI : int { A, B }
|
|
enum ED : double { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle
|
|
enum EC : char { A = 0, B } // NOTE: explicit initialization to 0 required during Enum init deprecation cycle
|
|
enum ES : string { A = "aaa", B = "bbb" }
|
|
|
|
f.writeln(EI.A); // false, but A on 2.058
|
|
f.writeln(EI.B); // true, but B on 2.058
|
|
|
|
f.writeln(ED.A); // A
|
|
f.writeln(ED.B); // B
|
|
|
|
f.writeln(EC.A); // A
|
|
f.writeln(EC.B); // B
|
|
|
|
f.writeln(ES.A); // A
|
|
f.writeln(ES.B); // B
|
|
|
|
f.close();
|
|
version (Windows)
|
|
assert(cast(char[]) std.file.read(deleteme) ==
|
|
"A\r\nB\r\nA\r\nB\r\nA\r\nB\r\nA\r\nB\r\n");
|
|
else
|
|
assert(cast(char[]) std.file.read(deleteme) ==
|
|
"A\nB\nA\nB\nA\nB\nA\nB\n");
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static auto useInit(T)(T ltw)
|
|
{
|
|
T val;
|
|
val = ltw;
|
|
val = T.init;
|
|
return val;
|
|
}
|
|
useInit(stdout.lockingTextWriter());
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=21920
|
|
void function(string) printer = &writeln!string;
|
|
if (false) printer("Hello");
|
|
}
|
|
|
|
|
|
/***********************************
|
|
Writes formatted data to standard output (without a trailing newline).
|
|
|
|
Params:
|
|
fmt = The $(REF_ALTTEXT format string, formattedWrite, std, _format).
|
|
When passed as a compile-time argument, the string will be statically checked
|
|
against the argument types passed.
|
|
args = Items to write.
|
|
|
|
Note: In older versions of Phobos, it used to be possible to write:
|
|
|
|
------
|
|
writef(stderr, "%s", "message");
|
|
------
|
|
|
|
to print a message to `stderr`. This syntax is no longer supported, and has
|
|
been superceded by:
|
|
|
|
------
|
|
stderr.writef("%s", "message");
|
|
------
|
|
|
|
*/
|
|
void writef(alias fmt, A...)(A args)
|
|
if (isSomeString!(typeof(fmt)))
|
|
{
|
|
import std.format : checkFormatException;
|
|
|
|
alias e = checkFormatException!(fmt, A);
|
|
static assert(!e, e.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 `stdin` using $(REF formattedRead, std,_format).
|
|
* Params:
|
|
* format = The $(REF_ALTTEXT format string, formattedWrite, std, _format).
|
|
* When passed as a compile-time argument, the string will be statically checked
|
|
* against the argument types passed.
|
|
* args = Items to be read.
|
|
* Returns:
|
|
* Same as `formattedRead`: The number of variables filled. If the input range `r` ends early,
|
|
* this number will be less than the number of variables provided.
|
|
* Example:
|
|
----
|
|
// test.d
|
|
void main()
|
|
{
|
|
import std.stdio;
|
|
foreach (_; 0 .. 3)
|
|
{
|
|
int a;
|
|
readf!" %d"(a);
|
|
writeln(++a);
|
|
}
|
|
}
|
|
----
|
|
$(CONSOLE
|
|
% echo "1 2 3" | rdmd test.d
|
|
2
|
|
3
|
|
4
|
|
)
|
|
*/
|
|
uint readf(alias format, A...)(auto ref A args)
|
|
if (isSomeString!(typeof(format)))
|
|
{
|
|
import std.format : checkFormatException;
|
|
|
|
alias e = checkFormatException!(format, A);
|
|
static assert(!e, e.msg);
|
|
return .readf(format, args);
|
|
}
|
|
|
|
/// ditto
|
|
uint readf(A...)(scope const(char)[] format, auto ref A args)
|
|
{
|
|
return stdin.readf(format, args);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
float f;
|
|
if (false) readf("%s", &f);
|
|
|
|
char a;
|
|
wchar b;
|
|
dchar c;
|
|
if (false) readf("%s %s %s", a, b, c);
|
|
// backwards compatibility with pointers
|
|
if (false) readf("%s %s %s", a, &b, c);
|
|
if (false) readf("%s %s %s", &a, &b, &c);
|
|
}
|
|
|
|
/**********************************
|
|
* Read line from `stdin`.
|
|
*
|
|
* This version manages its own read buffer, which means one memory allocation per call. If you are not
|
|
* retaining a reference to the read data, consider the `readln(buf)` version, which may offer
|
|
* better performance as it can reuse its read buffer.
|
|
*
|
|
* Returns:
|
|
* The line that was read, including the line terminator character.
|
|
* Params:
|
|
* S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to `string`.
|
|
* terminator = Line terminator (by default, `'\n'`).
|
|
* Note:
|
|
* String terminators are not supported due to ambiguity with readln(buf) below.
|
|
* Throws:
|
|
* `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error.
|
|
* Example:
|
|
* Reads `stdin` and writes it to `stdout`.
|
|
---
|
|
import std.stdio;
|
|
|
|
void main()
|
|
{
|
|
string line;
|
|
while ((line = readln()) !is null)
|
|
write(line);
|
|
}
|
|
---
|
|
*/
|
|
S readln(S = string)(dchar terminator = '\n')
|
|
if (isSomeString!S)
|
|
{
|
|
return stdin.readln!S(terminator);
|
|
}
|
|
|
|
/**********************************
|
|
* Read line from `stdin` and write it to buf[], including terminating character.
|
|
*
|
|
* This can be faster than $(D line = readln()) because you can reuse
|
|
* the buffer for each call. Note that reusing the buffer means that you
|
|
* must copy the previous contents if you wish to retain them.
|
|
*
|
|
* Returns:
|
|
* `size_t` 0 for end of file, otherwise number of characters read
|
|
* Params:
|
|
* buf = Buffer used to store the resulting line data. buf is resized as necessary.
|
|
* terminator = Line terminator (by default, `'\n'`). Use $(REF newline, std,ascii)
|
|
* for portability (unless the file was opened in text mode).
|
|
* Throws:
|
|
* `StdioException` on I/O error, or `UnicodeException` on Unicode conversion error.
|
|
* Example:
|
|
* Reads `stdin` and writes it to `stdout`.
|
|
---
|
|
import std.stdio;
|
|
|
|
void main()
|
|
{
|
|
char[] buf;
|
|
while (readln(buf))
|
|
write(buf);
|
|
}
|
|
---
|
|
*/
|
|
size_t readln(C)(ref C[] buf, dchar terminator = '\n')
|
|
if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum))
|
|
{
|
|
return stdin.readln(buf, terminator);
|
|
}
|
|
|
|
/** ditto */
|
|
size_t readln(C, R)(ref C[] buf, R terminator)
|
|
if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) &&
|
|
isBidirectionalRange!R && is(typeof(terminator.front == dchar.init)))
|
|
{
|
|
return stdin.readln(buf, terminator);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.meta : AliasSeq;
|
|
|
|
//we can't actually test readln, so at the very least,
|
|
//we test compilability
|
|
void foo()
|
|
{
|
|
readln();
|
|
readln('\t');
|
|
static foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[]))
|
|
{
|
|
readln!String();
|
|
readln!String('\t');
|
|
}
|
|
static foreach (String; AliasSeq!(char[], wchar[], dchar[]))
|
|
{{
|
|
String buf;
|
|
readln(buf);
|
|
readln(buf, '\t');
|
|
readln(buf, "<br />");
|
|
}}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convenience function that forwards to `core.sys.posix.stdio.fopen`
|
|
* (to `_wfopen` on Windows)
|
|
* with appropriately-constructed C-style strings.
|
|
*/
|
|
private FILE* _fopen(R1, R2)(R1 name, R2 mode = "r")
|
|
if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) &&
|
|
(isSomeFiniteCharInputRange!R2 || isSomeString!R2))
|
|
{
|
|
import std.internal.cstring : tempCString;
|
|
|
|
auto namez = name.tempCString!FSChar();
|
|
auto modez = mode.tempCString!FSChar();
|
|
|
|
static _fopenImpl(scope const(FSChar)* namez, scope const(FSChar)* modez) @trusted nothrow @nogc
|
|
{
|
|
version (Windows)
|
|
{
|
|
return _wfopen(namez, modez);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
/*
|
|
* The new opengroup large file support API is transparently
|
|
* included in the normal C bindings. http://opengroup.org/platform/lfs.html#1.0
|
|
* if _FILE_OFFSET_BITS in druntime is 64, off_t is 64 bit and
|
|
* the normal functions work fine. If not, then large file support
|
|
* probably isn't available. Do not use the old transitional API
|
|
* (the native extern(C) fopen64, http://www.unix.org/version2/whatsnew/lfs20mar.html#3.0)
|
|
*/
|
|
import core.sys.posix.stdio : fopen;
|
|
return fopen(namez, modez);
|
|
}
|
|
else
|
|
{
|
|
return fopen(namez, modez);
|
|
}
|
|
}
|
|
return _fopenImpl(namez, modez);
|
|
}
|
|
|
|
version (Posix)
|
|
{
|
|
/***********************************
|
|
* Convenience function that forwards to `core.sys.posix.stdio.popen`
|
|
* with appropriately-constructed C-style strings.
|
|
*/
|
|
FILE* _popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc
|
|
if ((isSomeFiniteCharInputRange!R1 || isSomeString!R1) &&
|
|
(isSomeFiniteCharInputRange!R2 || isSomeString!R2))
|
|
{
|
|
import std.internal.cstring : tempCString;
|
|
|
|
auto namez = name.tempCString!FSChar();
|
|
auto modez = mode.tempCString!FSChar();
|
|
|
|
static popenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc
|
|
{
|
|
import core.sys.posix.stdio : popen;
|
|
return popen(namez, modez);
|
|
}
|
|
return popenImpl(namez, modez);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convenience function that forwards to `core.stdc.stdio.fwrite`
|
|
*/
|
|
private auto trustedFwrite(T)(FILE* f, const T[] obj) @trusted
|
|
{
|
|
return fwrite(obj.ptr, T.sizeof, obj.length, f);
|
|
}
|
|
|
|
/*
|
|
* Convenience function that forwards to `core.stdc.stdio.fread`
|
|
*/
|
|
private auto trustedFread(T)(FILE* f, T[] obj) @trusted
|
|
{
|
|
return fread(obj.ptr, T.sizeof, obj.length, f);
|
|
}
|
|
|
|
/**
|
|
* Iterates through the lines of a file by using `foreach`.
|
|
*
|
|
* Example:
|
|
*
|
|
---------
|
|
void main()
|
|
{
|
|
foreach (string line; lines(stdin))
|
|
{
|
|
... use line ...
|
|
}
|
|
}
|
|
---------
|
|
The line terminator (`'\n'` by default) is part of the string read (it
|
|
could be missing in the last line of the file). Several types are
|
|
supported for `line`, and the behavior of `lines`
|
|
changes accordingly:
|
|
|
|
$(OL $(LI If `line` has type `string`, $(D
|
|
wstring), or `dstring`, a new string of the respective type
|
|
is allocated every read.) $(LI If `line` has type $(D
|
|
char[]), `wchar[]`, `dchar[]`, the line's content
|
|
will be reused (overwritten) across reads.) $(LI If `line`
|
|
has type `immutable(ubyte)[]`, the behavior is similar to
|
|
case (1), except that no UTF checking is attempted upon input.) $(LI
|
|
If `line` has type `ubyte[]`, the behavior is
|
|
similar to case (2), except that no UTF checking is attempted upon
|
|
input.))
|
|
|
|
In all cases, a two-symbols versions is also accepted, in which case
|
|
the first symbol (of integral type, e.g. `ulong` or $(D
|
|
uint)) tracks the zero-based number of the current line.
|
|
|
|
Example:
|
|
----
|
|
foreach (ulong i, string line; lines(stdin))
|
|
{
|
|
... use line ...
|
|
}
|
|
----
|
|
|
|
In case of an I/O error, an `StdioException` is thrown.
|
|
|
|
See_Also:
|
|
$(LREF byLine)
|
|
*/
|
|
|
|
struct lines
|
|
{
|
|
private File f;
|
|
private dchar terminator = '\n';
|
|
|
|
/**
|
|
Constructor.
|
|
Params:
|
|
f = File to read lines from.
|
|
terminator = Line separator (`'\n'` by default).
|
|
*/
|
|
this(File f, dchar terminator = '\n')
|
|
{
|
|
this.f = f;
|
|
this.terminator = terminator;
|
|
}
|
|
|
|
int opApply(D)(scope D dg)
|
|
{
|
|
import std.traits : Parameters;
|
|
alias Parms = Parameters!(dg);
|
|
static if (isSomeString!(Parms[$ - 1]))
|
|
{
|
|
int result = 0;
|
|
static if (is(Parms[$ - 1] : const(char)[]))
|
|
alias C = char;
|
|
else static if (is(Parms[$ - 1] : const(wchar)[]))
|
|
alias C = wchar;
|
|
else static if (is(Parms[$ - 1] : const(dchar)[]))
|
|
alias C = dchar;
|
|
C[] line;
|
|
static if (Parms.length == 2)
|
|
Parms[0] i = 0;
|
|
for (;;)
|
|
{
|
|
import std.conv : to;
|
|
|
|
if (!f.readln(line, terminator)) break;
|
|
auto copy = to!(Parms[$ - 1])(line);
|
|
static if (Parms.length == 2)
|
|
{
|
|
result = dg(i, copy);
|
|
++i;
|
|
}
|
|
else
|
|
{
|
|
result = dg(copy);
|
|
}
|
|
if (result != 0) break;
|
|
}
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
// raw read
|
|
return opApplyRaw(dg);
|
|
}
|
|
}
|
|
// no UTF checking
|
|
int opApplyRaw(D)(scope D dg)
|
|
{
|
|
import std.conv : to;
|
|
import std.exception : assumeUnique;
|
|
import std.traits : Parameters;
|
|
|
|
alias Parms = Parameters!(dg);
|
|
enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]);
|
|
int result = 1;
|
|
int c = void;
|
|
_FLOCK(f._p.handle);
|
|
scope(exit) _FUNLOCK(f._p.handle);
|
|
ubyte[] buffer;
|
|
static if (Parms.length == 2)
|
|
Parms[0] line = 0;
|
|
while ((c = _FGETC(cast(_iobuf*) f._p.handle)) != -1)
|
|
{
|
|
buffer ~= to!(ubyte)(c);
|
|
if (c == terminator)
|
|
{
|
|
static if (duplicate)
|
|
auto arg = assumeUnique(buffer);
|
|
else
|
|
alias arg = buffer;
|
|
// unlock the file while calling the delegate
|
|
_FUNLOCK(f._p.handle);
|
|
scope(exit) _FLOCK(f._p.handle);
|
|
static if (Parms.length == 1)
|
|
{
|
|
result = dg(arg);
|
|
}
|
|
else
|
|
{
|
|
result = dg(line, arg);
|
|
++line;
|
|
}
|
|
if (result) break;
|
|
static if (!duplicate)
|
|
buffer.length = 0;
|
|
}
|
|
}
|
|
// can only reach when _FGETC returned -1
|
|
if (!f.eof) throw new StdioException("Error in reading file"); // error occured
|
|
return result;
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
import std.meta : AliasSeq;
|
|
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
|
|
auto deleteme = testFilename();
|
|
scope(exit) { std.file.remove(deleteme); }
|
|
|
|
alias TestedWith =
|
|
AliasSeq!(string, wstring, dstring,
|
|
char[], wchar[], dchar[]);
|
|
foreach (T; TestedWith)
|
|
{
|
|
// test looping with an empty file
|
|
std.file.write(deleteme, "");
|
|
auto f = File(deleteme, "r");
|
|
foreach (T line; lines(f))
|
|
{
|
|
assert(false);
|
|
}
|
|
f.close();
|
|
|
|
// test looping with a file with three lines
|
|
std.file.write(deleteme, "Line one\nline two\nline three\n");
|
|
f.open(deleteme, "r");
|
|
uint i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(line == "Line one\n");
|
|
else if (i == 1) assert(line == "line two\n");
|
|
else if (i == 2) assert(line == "line three\n");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
f.close();
|
|
|
|
// test looping with a file with three lines, last without a newline
|
|
std.file.write(deleteme, "Line one\nline two\nline three");
|
|
f.open(deleteme, "r");
|
|
i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(line == "Line one\n");
|
|
else if (i == 1) assert(line == "line two\n");
|
|
else if (i == 2) assert(line == "line three");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
f.close();
|
|
}
|
|
|
|
// test with ubyte[] inputs
|
|
alias TestedWith2 = AliasSeq!(immutable(ubyte)[], ubyte[]);
|
|
foreach (T; TestedWith2)
|
|
{
|
|
// test looping with an empty file
|
|
std.file.write(deleteme, "");
|
|
auto f = File(deleteme, "r");
|
|
foreach (T line; lines(f))
|
|
{
|
|
assert(false);
|
|
}
|
|
f.close();
|
|
|
|
// test looping with a file with three lines
|
|
std.file.write(deleteme, "Line one\nline two\nline three\n");
|
|
f.open(deleteme, "r");
|
|
uint i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Line one\n");
|
|
else if (i == 1) assert(cast(char[]) line == "line two\n",
|
|
T.stringof ~ " " ~ cast(char[]) line);
|
|
else if (i == 2) assert(cast(char[]) line == "line three\n");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
f.close();
|
|
|
|
// test looping with a file with three lines, last without a newline
|
|
std.file.write(deleteme, "Line one\nline two\nline three");
|
|
f.open(deleteme, "r");
|
|
i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Line one\n");
|
|
else if (i == 1) assert(cast(char[]) line == "line two\n");
|
|
else if (i == 2) assert(cast(char[]) line == "line three");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
f.close();
|
|
|
|
}
|
|
|
|
static foreach (T; AliasSeq!(ubyte[]))
|
|
{
|
|
// test looping with a file with three lines, last without a newline
|
|
// using a counter too this time
|
|
std.file.write(deleteme, "Line one\nline two\nline three");
|
|
auto f = File(deleteme, "r");
|
|
uint i = 0;
|
|
foreach (ulong j, T line; lines(f))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Line one\n");
|
|
else if (i == 1) assert(cast(char[]) line == "line two\n");
|
|
else if (i == 2) assert(cast(char[]) line == "line three");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
f.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
Iterates through a file a chunk at a time by using `foreach`.
|
|
|
|
Example:
|
|
|
|
---------
|
|
void main()
|
|
{
|
|
foreach (ubyte[] buffer; chunks(stdin, 4096))
|
|
{
|
|
... use buffer ...
|
|
}
|
|
}
|
|
---------
|
|
|
|
The content of `buffer` is reused across calls. In the
|
|
example above, `buffer.length` is 4096 for all iterations,
|
|
except for the last one, in which case `buffer.length` may
|
|
be less than 4096 (but always greater than zero).
|
|
|
|
In case of an I/O error, an `StdioException` is thrown.
|
|
*/
|
|
auto chunks(File f, size_t size)
|
|
{
|
|
return ChunksImpl(f, size);
|
|
}
|
|
private struct ChunksImpl
|
|
{
|
|
private File f;
|
|
private size_t size;
|
|
// private string fileName; // Currently, no use
|
|
|
|
this(File f, size_t size)
|
|
in
|
|
{
|
|
assert(size, "size must be larger than 0");
|
|
}
|
|
do
|
|
{
|
|
this.f = f;
|
|
this.size = size;
|
|
}
|
|
|
|
int opApply(D)(scope D dg)
|
|
{
|
|
import core.stdc.stdlib : alloca;
|
|
import std.exception : enforce;
|
|
|
|
enforce(f.isOpen, "Attempting to read from an unopened file");
|
|
enum maxStackSize = 1024 * 16;
|
|
ubyte[] buffer = void;
|
|
if (size < maxStackSize)
|
|
buffer = (cast(ubyte*) alloca(size))[0 .. size];
|
|
else
|
|
buffer = new ubyte[size];
|
|
size_t r = void;
|
|
int result = 1;
|
|
uint tally = 0;
|
|
while ((r = trustedFread(f._p.handle, buffer)) > 0)
|
|
{
|
|
assert(r <= size);
|
|
if (r != size)
|
|
{
|
|
// error occured
|
|
if (!f.eof) throw new StdioException(null);
|
|
buffer.length = r;
|
|
}
|
|
static if (is(typeof(dg(tally, buffer))))
|
|
{
|
|
if ((result = dg(tally, buffer)) != 0) break;
|
|
}
|
|
else
|
|
{
|
|
if ((result = dg(buffer)) != 0) break;
|
|
}
|
|
++tally;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
|
|
auto deleteme = testFilename();
|
|
scope(exit) { std.file.remove(deleteme); }
|
|
|
|
// test looping with an empty file
|
|
std.file.write(deleteme, "");
|
|
auto f = File(deleteme, "r");
|
|
foreach (ubyte[] line; chunks(f, 4))
|
|
{
|
|
assert(false);
|
|
}
|
|
f.close();
|
|
|
|
// test looping with a file with three lines
|
|
std.file.write(deleteme, "Line one\nline two\nline three\n");
|
|
f = File(deleteme, "r");
|
|
uint i = 0;
|
|
foreach (ubyte[] line; chunks(f, 3))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Lin");
|
|
else if (i == 1) assert(cast(char[]) line == "e o");
|
|
else if (i == 2) assert(cast(char[]) line == "ne\n");
|
|
else break;
|
|
++i;
|
|
}
|
|
f.close();
|
|
}
|
|
|
|
// Issue 21730 - null ptr dereferenced in ChunksImpl.opApply (SIGSEGV)
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
scope(exit) { if (std.file.exists(deleteme)) std.file.remove(deleteme); }
|
|
|
|
auto err1 = File(deleteme, "w+x");
|
|
err1.close;
|
|
std.file.remove(deleteme);
|
|
assertThrown(() {foreach (ubyte[] buf; chunks(err1, 4096)) {}}());
|
|
}
|
|
|
|
/**
|
|
Writes an array or range to a file.
|
|
Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)).
|
|
Similar to $(REF write, std,file), strings are written as-is,
|
|
rather than encoded according to the `File`'s $(HTTP
|
|
en.cppreference.com/w/c/io#Narrow_and_wide_orientation,
|
|
orientation).
|
|
*/
|
|
void toFile(T)(T data, string fileName)
|
|
if (is(typeof(copy(data, stdout.lockingBinaryWriter))))
|
|
{
|
|
copy(data, File(fileName, "wb").lockingBinaryWriter);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
|
|
auto deleteme = testFilename();
|
|
scope(exit) { std.file.remove(deleteme); }
|
|
|
|
"Test".toFile(deleteme);
|
|
assert(std.file.readText(deleteme) == "Test");
|
|
}
|
|
|
|
/*********************
|
|
* Thrown if I/O errors happen.
|
|
*/
|
|
class StdioException : Exception
|
|
{
|
|
static import core.stdc.errno;
|
|
/// Operating system error code.
|
|
uint errno;
|
|
|
|
/**
|
|
Initialize with a message and an error code.
|
|
*/
|
|
this(string message, uint e = core.stdc.errno.errno) @trusted
|
|
{
|
|
import std.exception : errnoString;
|
|
errno = e;
|
|
auto sysmsg = errnoString(errno);
|
|
// If e is 0, we don't use the system error message. (The message
|
|
// is "Success", which is rather pointless for an exception.)
|
|
super(e == 0 ? message
|
|
: (message ? message ~ " (" ~ sysmsg ~ ")" : sysmsg));
|
|
}
|
|
|
|
/** Convenience functions that throw an `StdioException`. */
|
|
static void opCall(string msg)
|
|
{
|
|
throw new StdioException(msg);
|
|
}
|
|
|
|
/// ditto
|
|
static void opCall()
|
|
{
|
|
throw new StdioException(null, core.stdc.errno.errno);
|
|
}
|
|
}
|
|
|
|
enum StdFileHandle: string
|
|
{
|
|
stdin = "core.stdc.stdio.stdin",
|
|
stdout = "core.stdc.stdio.stdout",
|
|
stderr = "core.stdc.stdio.stderr",
|
|
}
|
|
|
|
// Undocumented but public because the std* handles are aliasing it.
|
|
@property ref File makeGlobal(StdFileHandle _iob)()
|
|
{
|
|
__gshared File.Impl impl;
|
|
__gshared File result;
|
|
|
|
// Use an inline spinlock to make sure the initializer is only run once.
|
|
// We assume there will be at most uint.max / 2 threads trying to initialize
|
|
// `handle` at once and steal the high bit to indicate that the globals have
|
|
// been initialized.
|
|
static shared uint spinlock;
|
|
import core.atomic : atomicLoad, atomicOp, MemoryOrder;
|
|
if (atomicLoad!(MemoryOrder.acq)(spinlock) <= uint.max / 2)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (atomicLoad!(MemoryOrder.acq)(spinlock) > uint.max / 2)
|
|
break;
|
|
if (atomicOp!"+="(spinlock, 1) == 1)
|
|
{
|
|
with (StdFileHandle)
|
|
assert(_iob == stdin || _iob == stdout || _iob == stderr);
|
|
impl.handle = mixin(_iob);
|
|
result._p = &impl;
|
|
atomicOp!"+="(spinlock, uint.max / 2);
|
|
break;
|
|
}
|
|
atomicOp!"-="(spinlock, 1);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** The standard input stream.
|
|
|
|
Returns:
|
|
stdin as a $(LREF File).
|
|
|
|
Note:
|
|
The returned $(LREF File) wraps $(REF stdin,core,stdc,stdio), and
|
|
is therefore thread global. Reassigning `stdin` to a different
|
|
`File` must be done in a single-threaded or locked context in
|
|
order to avoid race conditions.
|
|
|
|
All reading from `stdin` automatically locks the file globally,
|
|
and will cause all other threads calling `read` to wait until
|
|
the lock is released.
|
|
*/
|
|
alias stdin = makeGlobal!(StdFileHandle.stdin);
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
// Read stdin, sort lines, write to stdout
|
|
import std.algorithm.mutation : copy;
|
|
import std.algorithm.sorting : sort;
|
|
import std.array : array;
|
|
import std.typecons : Yes;
|
|
|
|
void main()
|
|
{
|
|
stdin // read from stdin
|
|
.byLineCopy(Yes.keepTerminator) // copying each line
|
|
.array() // convert to array of lines
|
|
.sort() // sort the lines
|
|
.copy( // copy output of .sort to an OutputRange
|
|
stdout.lockingTextWriter()); // the OutputRange
|
|
}
|
|
}
|
|
|
|
/**
|
|
The standard output stream.
|
|
|
|
Returns:
|
|
stdout as a $(LREF File).
|
|
|
|
Note:
|
|
The returned $(LREF File) wraps $(REF stdout,core,stdc,stdio), and
|
|
is therefore thread global. Reassigning `stdout` to a different
|
|
`File` must be done in a single-threaded or locked context in
|
|
order to avoid race conditions.
|
|
|
|
All writing to `stdout` automatically locks the file globally,
|
|
and will cause all other threads calling `write` to wait until
|
|
the lock is released.
|
|
*/
|
|
alias stdout = makeGlobal!(StdFileHandle.stdout);
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
void main()
|
|
{
|
|
stdout.writeln("Write a message to stdout.");
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
void main()
|
|
{
|
|
import std.algorithm.iteration : filter, map, sum;
|
|
import std.format : format;
|
|
import std.range : iota, tee;
|
|
|
|
int len;
|
|
const r = 6.iota
|
|
.filter!(a => a % 2) // 1 3 5
|
|
.map!(a => a * 2) // 2 6 10
|
|
.tee!(_ => stdout.writefln("len: %d", len++))
|
|
.sum;
|
|
|
|
assert(r == 18);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
void main()
|
|
{
|
|
import std.algorithm.mutation : copy;
|
|
import std.algorithm.iteration : map;
|
|
import std.format : format;
|
|
import std.range : iota;
|
|
|
|
10.iota
|
|
.map!(e => "N: %d".format(e))
|
|
.copy(stdout.lockingTextWriter()); // the OutputRange
|
|
}
|
|
}
|
|
|
|
/**
|
|
The standard error stream.
|
|
|
|
Returns:
|
|
stderr as a $(LREF File).
|
|
|
|
Note:
|
|
The returned $(LREF File) wraps $(REF stderr,core,stdc,stdio), and
|
|
is therefore thread global. Reassigning `stderr` to a different
|
|
`File` must be done in a single-threaded or locked context in
|
|
order to avoid race conditions.
|
|
|
|
All writing to `stderr` automatically locks the file globally,
|
|
and will cause all other threads calling `write` to wait until
|
|
the lock is released.
|
|
*/
|
|
alias stderr = makeGlobal!(StdFileHandle.stderr);
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
void main()
|
|
{
|
|
stderr.writeln("Write a message to stderr.");
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
import std.typecons : tuple;
|
|
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
auto deleteme = testFilename();
|
|
|
|
std.file.write(deleteme, "1 2\n4 1\n5 100");
|
|
scope(exit) std.file.remove(deleteme);
|
|
{
|
|
File f = File(deleteme);
|
|
scope(exit) f.close();
|
|
auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ];
|
|
uint i;
|
|
foreach (e; f.byRecord!(int, int)("%s %s"))
|
|
{
|
|
//writeln(e);
|
|
assert(e == t[i++]);
|
|
}
|
|
assert(i == 3);
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Retain backwards compatibility
|
|
// https://issues.dlang.org/show_bug.cgi?id=17472
|
|
static assert(is(typeof(stdin) == File));
|
|
static assert(is(typeof(stdout) == File));
|
|
static assert(is(typeof(stderr) == File));
|
|
}
|
|
|
|
// roll our own appender, but with "safe" arrays
|
|
private struct ReadlnAppender
|
|
{
|
|
char[] buf;
|
|
size_t pos;
|
|
bool safeAppend = false;
|
|
|
|
void initialize(char[] b)
|
|
{
|
|
buf = b;
|
|
pos = 0;
|
|
}
|
|
@property char[] data() @trusted
|
|
{
|
|
if (safeAppend)
|
|
assumeSafeAppend(buf.ptr[0 .. pos]);
|
|
return buf.ptr[0 .. pos];
|
|
}
|
|
|
|
bool reserveWithoutAllocating(size_t n)
|
|
{
|
|
if (buf.length >= pos + n) // buf is already large enough
|
|
return true;
|
|
|
|
immutable curCap = buf.capacity;
|
|
if (curCap >= pos + n)
|
|
{
|
|
buf.length = curCap;
|
|
/* Any extra capacity we end up not using can safely be claimed
|
|
by someone else. */
|
|
safeAppend = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
void reserve(size_t n) @trusted
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
if (!reserveWithoutAllocating(n))
|
|
{
|
|
size_t ncap = buf.length * 2 + 128 + n;
|
|
char[] nbuf = new char[ncap];
|
|
memcpy(nbuf.ptr, buf.ptr, pos);
|
|
buf = nbuf;
|
|
// Allocated a new buffer. No one else knows about it.
|
|
safeAppend = true;
|
|
}
|
|
}
|
|
void putchar(char c) @trusted
|
|
{
|
|
reserve(1);
|
|
buf.ptr[pos++] = c;
|
|
}
|
|
void putdchar(dchar dc) @trusted
|
|
{
|
|
import std.utf : encode, UseReplacementDchar;
|
|
|
|
char[4] ubuf;
|
|
immutable size = encode!(UseReplacementDchar.yes)(ubuf, dc);
|
|
reserve(size);
|
|
foreach (c; ubuf)
|
|
buf.ptr[pos++] = c;
|
|
}
|
|
void putonly(char[] b) @trusted
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
assert(pos == 0); // assume this is the only put call
|
|
if (reserveWithoutAllocating(b.length))
|
|
memcpy(buf.ptr + pos, b.ptr, b.length);
|
|
else
|
|
buf = b.dup;
|
|
pos = b.length;
|
|
}
|
|
}
|
|
|
|
// Private implementation of readln
|
|
private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation)
|
|
{
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
_FLOCK(fps);
|
|
scope(exit) _FUNLOCK(fps);
|
|
|
|
/* Since fps is now locked, we can create an "unshared" version
|
|
* of fp.
|
|
*/
|
|
auto fp = cast(_iobuf*) fps;
|
|
|
|
ReadlnAppender app;
|
|
app.initialize(buf);
|
|
|
|
if (__fhnd_info[fp._file] & FHND_WCHAR)
|
|
{ /* Stream is in wide characters.
|
|
* Read them and convert to chars.
|
|
*/
|
|
static assert(wchar_t.sizeof == 2);
|
|
for (int c = void; (c = _FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
{
|
|
app.putchar(cast(char) c);
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (c >= 0xD800 && c <= 0xDBFF)
|
|
{
|
|
int c2 = void;
|
|
if ((c2 = _FGETWC(fp)) != -1 ||
|
|
c2 < 0xDC00 && c2 > 0xDFFF)
|
|
{
|
|
StdioException("unpaired UTF-16 surrogate");
|
|
}
|
|
c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
|
|
}
|
|
app.putdchar(cast(dchar) c);
|
|
}
|
|
}
|
|
if (ferror(fps))
|
|
StdioException();
|
|
}
|
|
|
|
else if (fp._flag & _IONBF)
|
|
{
|
|
/* Use this for unbuffered I/O, when running
|
|
* across buffer boundaries, or for any but the common
|
|
* cases.
|
|
*/
|
|
L1:
|
|
int c;
|
|
while ((c = _FGETC(fp)) != -1)
|
|
{
|
|
app.putchar(cast(char) c);
|
|
if (c == terminator)
|
|
{
|
|
buf = app.data;
|
|
return buf.length;
|
|
}
|
|
|
|
}
|
|
|
|
if (ferror(fps))
|
|
StdioException();
|
|
}
|
|
else
|
|
{
|
|
int u = fp._cnt;
|
|
char* p = fp._ptr;
|
|
int i;
|
|
if (fp._flag & _IOTRAN)
|
|
{ /* Translated mode ignores \r and treats ^Z as end-of-file
|
|
*/
|
|
char c;
|
|
while (1)
|
|
{
|
|
if (i == u) // if end of buffer
|
|
goto L1; // give up
|
|
c = p[i];
|
|
i++;
|
|
if (c != '\r')
|
|
{
|
|
if (c == terminator)
|
|
break;
|
|
if (c != 0x1A)
|
|
continue;
|
|
goto L1;
|
|
}
|
|
else
|
|
{ if (i != u && p[i] == terminator)
|
|
break;
|
|
goto L1;
|
|
}
|
|
}
|
|
app.putonly(p[0 .. i]);
|
|
app.buf[i - 1] = cast(char) terminator;
|
|
if (terminator == '\n' && c == '\r')
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
while (1)
|
|
{
|
|
if (i == u) // if end of buffer
|
|
goto L1; // give up
|
|
auto c = p[i];
|
|
i++;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
app.putonly(p[0 .. i]);
|
|
}
|
|
fp._cnt -= i;
|
|
fp._ptr += i;
|
|
}
|
|
|
|
buf = app.data;
|
|
return buf.length;
|
|
}
|
|
else version (MICROSOFT_STDIO)
|
|
{
|
|
_FLOCK(fps);
|
|
scope(exit) _FUNLOCK(fps);
|
|
|
|
/* Since fps is now locked, we can create an "unshared" version
|
|
* of fp.
|
|
*/
|
|
auto fp = cast(_iobuf*) fps;
|
|
|
|
ReadlnAppender app;
|
|
app.initialize(buf);
|
|
|
|
int c;
|
|
while ((c = _FGETC(fp)) != -1)
|
|
{
|
|
app.putchar(cast(char) c);
|
|
if (c == terminator)
|
|
{
|
|
buf = app.data;
|
|
return buf.length;
|
|
}
|
|
|
|
}
|
|
|
|
if (ferror(fps))
|
|
StdioException();
|
|
buf = app.data;
|
|
return buf.length;
|
|
}
|
|
else static if (__traits(compiles, core.sys.posix.stdio.getdelim))
|
|
{
|
|
import core.stdc.stdlib : free;
|
|
import core.stdc.wchar_ : fwide;
|
|
|
|
if (orientation == File.Orientation.wide)
|
|
{
|
|
/* Stream is in wide characters.
|
|
* Read them and convert to chars.
|
|
*/
|
|
_FLOCK(fps);
|
|
scope(exit) _FUNLOCK(fps);
|
|
auto fp = cast(_iobuf*) fps;
|
|
version (Windows)
|
|
{
|
|
buf.length = 0;
|
|
for (int c = void; (c = _FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
{ buf ~= c;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (c >= 0xD800 && c <= 0xDBFF)
|
|
{
|
|
int c2 = void;
|
|
if ((c2 = _FGETWC(fp)) != -1 ||
|
|
c2 < 0xDC00 && c2 > 0xDFFF)
|
|
{
|
|
StdioException("unpaired UTF-16 surrogate");
|
|
}
|
|
c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
|
|
}
|
|
import std.utf : encode;
|
|
encode(buf, c);
|
|
}
|
|
}
|
|
if (ferror(fp))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
buf.length = 0;
|
|
for (int c; (c = _FGETWC(fp)) != -1; )
|
|
{
|
|
import std.utf : encode;
|
|
|
|
if ((c & ~0x7F) == 0)
|
|
buf ~= cast(char) c;
|
|
else
|
|
encode(buf, cast(dchar) c);
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
if (ferror(fps))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
}
|
|
|
|
static char *lineptr = null;
|
|
static size_t n = 0;
|
|
scope(exit)
|
|
{
|
|
if (n > 128 * 1024)
|
|
{
|
|
// Bound memory used by readln
|
|
free(lineptr);
|
|
lineptr = null;
|
|
n = 0;
|
|
}
|
|
}
|
|
|
|
auto s = core.sys.posix.stdio.getdelim(&lineptr, &n, terminator, fps);
|
|
if (s < 0)
|
|
{
|
|
if (ferror(fps))
|
|
StdioException();
|
|
buf.length = 0; // end of file
|
|
return 0;
|
|
}
|
|
|
|
if (s <= buf.length)
|
|
{
|
|
buf = buf[0 .. s];
|
|
buf[] = lineptr[0 .. s];
|
|
}
|
|
else
|
|
{
|
|
buf = lineptr[0 .. s].dup;
|
|
}
|
|
return s;
|
|
}
|
|
else // version (NO_GETDELIM)
|
|
{
|
|
import core.stdc.wchar_ : fwide;
|
|
|
|
_FLOCK(fps);
|
|
scope(exit) _FUNLOCK(fps);
|
|
auto fp = cast(_iobuf*) fps;
|
|
if (orientation == File.Orientation.wide)
|
|
{
|
|
/* Stream is in wide characters.
|
|
* Read them and convert to chars.
|
|
*/
|
|
version (Windows)
|
|
{
|
|
buf.length = 0;
|
|
for (int c; (c = _FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
{ buf ~= c;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (c >= 0xD800 && c <= 0xDBFF)
|
|
{
|
|
int c2 = void;
|
|
if ((c2 = _FGETWC(fp)) != -1 ||
|
|
c2 < 0xDC00 && c2 > 0xDFFF)
|
|
{
|
|
StdioException("unpaired UTF-16 surrogate");
|
|
}
|
|
c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
|
|
}
|
|
import std.utf : encode;
|
|
encode(buf, c);
|
|
}
|
|
}
|
|
if (ferror(fp))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
import std.utf : encode;
|
|
buf.length = 0;
|
|
for (int c; (c = _FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
buf ~= cast(char) c;
|
|
else
|
|
encode(buf, cast(dchar) c);
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
if (ferror(fps))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
}
|
|
|
|
// Narrow stream
|
|
// First, fill the existing buffer
|
|
for (size_t bufPos = 0; bufPos < buf.length; )
|
|
{
|
|
immutable c = _FGETC(fp);
|
|
if (c == -1)
|
|
{
|
|
buf.length = bufPos;
|
|
goto endGame;
|
|
}
|
|
buf[bufPos++] = cast(char) c;
|
|
if (c == terminator)
|
|
{
|
|
// No need to test for errors in file
|
|
buf.length = bufPos;
|
|
return bufPos;
|
|
}
|
|
}
|
|
// Then, append to it
|
|
for (int c; (c = _FGETC(fp)) != -1; )
|
|
{
|
|
buf ~= cast(char) c;
|
|
if (c == terminator)
|
|
{
|
|
// No need to test for errors in file
|
|
return buf.length;
|
|
}
|
|
}
|
|
|
|
endGame:
|
|
if (ferror(fps))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
static import std.file;
|
|
auto deleteme = testFilename();
|
|
scope(exit) std.file.remove(deleteme);
|
|
|
|
std.file.write(deleteme, "abcd\n0123456789abcde\n1234\n");
|
|
File f = File(deleteme, "rb");
|
|
|
|
char[] ln = new char[2];
|
|
f.readln(ln);
|
|
|
|
assert(ln == "abcd\n");
|
|
char[] t = ln[0 .. 2];
|
|
t ~= 't';
|
|
assert(t == "abt");
|
|
// https://issues.dlang.org/show_bug.cgi?id=13856: ln stomped to "abtd"
|
|
assert(ln == "abcd\n");
|
|
|
|
// it can also stomp the array length
|
|
ln = new char[4];
|
|
f.readln(ln);
|
|
assert(ln == "0123456789abcde\n");
|
|
|
|
char[100] buf;
|
|
ln = buf[];
|
|
f.readln(ln);
|
|
assert(ln == "1234\n");
|
|
assert(ln.ptr == buf.ptr); // avoid allocation, buffer is good enough
|
|
}
|
|
|
|
/** Experimental network access via the File interface
|
|
|
|
Opens a TCP connection to the given host and port, then returns
|
|
a File struct with read and write access through the same interface
|
|
as any other file (meaning writef and the byLine ranges work!).
|
|
|
|
Authors:
|
|
Adam D. Ruppe
|
|
|
|
Bugs:
|
|
Only works on Linux
|
|
*/
|
|
version (linux)
|
|
{
|
|
File openNetwork(string host, ushort port)
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
import core.sys.posix.arpa.inet : htons;
|
|
import core.sys.posix.netdb : gethostbyname;
|
|
import core.sys.posix.netinet.in_ : sockaddr_in;
|
|
static import core.sys.posix.unistd;
|
|
static import sock = core.sys.posix.sys.socket;
|
|
import std.conv : to;
|
|
import std.exception : enforce;
|
|
import std.internal.cstring : tempCString;
|
|
|
|
auto h = enforce( gethostbyname(host.tempCString()),
|
|
new StdioException("gethostbyname"));
|
|
|
|
int s = sock.socket(sock.AF_INET, sock.SOCK_STREAM, 0);
|
|
enforce(s != -1, new StdioException("socket"));
|
|
|
|
scope(failure)
|
|
{
|
|
// want to make sure it doesn't dangle if something throws. Upon
|
|
// normal exit, the File struct's reference counting takes care of
|
|
// closing, so we don't need to worry about success
|
|
core.sys.posix.unistd.close(s);
|
|
}
|
|
|
|
sockaddr_in addr;
|
|
|
|
addr.sin_family = sock.AF_INET;
|
|
addr.sin_port = htons(port);
|
|
memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length);
|
|
|
|
enforce(sock.connect(s, cast(sock.sockaddr*) &addr, addr.sizeof) != -1,
|
|
new StdioException("Connect failed"));
|
|
|
|
File f;
|
|
f.fdopen(s, "w+", host ~ ":" ~ to!string(port));
|
|
return f;
|
|
}
|
|
}
|
|
|
|
version (StdUnittest) private string testFilename(string file = __FILE__, size_t line = __LINE__) @safe
|
|
{
|
|
import std.conv : text;
|
|
import std.file : deleteme;
|
|
import std.path : baseName;
|
|
|
|
// filename intentionally contains non-ASCII (Russian) characters for
|
|
// https://issues.dlang.org/show_bug.cgi?id=7648
|
|
return text(deleteme, "-детка.", baseName(file), ".", line);
|
|
}
|