aboutsummaryrefslogtreecommitdiff
path: root/libphobos/src/std/file.d
diff options
context:
space:
mode:
Diffstat (limited to 'libphobos/src/std/file.d')
-rw-r--r--libphobos/src/std/file.d2090
1 files changed, 1562 insertions, 528 deletions
diff --git a/libphobos/src/std/file.d b/libphobos/src/std/file.d
index 99530cb..741656d 100644
--- a/libphobos/src/std/file.d
+++ b/libphobos/src/std/file.d
@@ -2,11 +2,12 @@
/**
Utilities for manipulating files and scanning directories. Functions
-in this module handle files as a unit, e.g., read or write one _file
+in this module handle files as a unit, e.g., read or write one file
at a time. For opening files and manipulating them via handles refer
to module $(MREF std, stdio).
$(SCRIPT inhibitQuickIndex = 1;)
+$(DIVC quickindex,
$(BOOKTABLE,
$(TR $(TH Category) $(TH Functions))
$(TR $(TD General) $(TD
@@ -54,17 +55,20 @@ $(TR $(TD Timestamp) $(TD
$(LREF getTimesWin)
$(LREF setTimes)
$(LREF timeLastModified)
+ $(LREF timeLastAccessed)
+ $(LREF timeStatusChanged)
))
$(TR $(TD Other) $(TD
$(LREF DirEntry)
$(LREF FileException)
$(LREF PreserveAttributes)
$(LREF SpanMode)
+ $(LREF getAvailableDiskSpace)
+))
))
-)
-Copyright: Copyright Digital Mars 2007 - 2011.
+Copyright: Copyright The D Language Foundation 2007 - 2011.
See_Also: The $(HTTP ddili.org/ders/d.en/files.html, official tutorial) for an
introduction to working with files in D, module
$(MREF std, stdio) for opening files and manipulating them via handles,
@@ -73,8 +77,8 @@ and module $(MREF std, path) for manipulating path strings.
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
Authors: $(HTTP digitalmars.com, Walter Bright),
$(HTTP erdani.org, Andrei Alexandrescu),
- Jonathan M Davis
-Source: $(PHOBOSSRC std/_file.d)
+ $(HTTP jmdavisprog.com, Jonathan M Davis)
+Source: $(PHOBOSSRC std/file.d)
*/
module std.file;
@@ -89,9 +93,18 @@ import std.range.primitives;
import std.traits;
import std.typecons;
+version (OSX)
+ version = Darwin;
+else version (iOS)
+ version = Darwin;
+else version (TVOS)
+ version = Darwin;
+else version (WatchOS)
+ version = Darwin;
+
version (Windows)
{
- import core.sys.windows.windows, std.windows.syserror;
+ import core.sys.windows.winbase, core.sys.windows.winnt, std.windows.syserror;
}
else version (Posix)
{
@@ -104,7 +117,7 @@ else
// Character type used for operating system filesystem APIs
version (Windows)
{
- private alias FSChar = wchar;
+ private alias FSChar = WCHAR; // WCHAR can be aliased to wchar or wchar_t
}
else version (Posix)
{
@@ -116,25 +129,21 @@ else
// Purposefully not documented. Use at your own risk
@property string deleteme() @safe
{
- import std.conv : to;
+ import std.conv : text;
import std.path : buildPath;
import std.process : thisProcessID;
- static _deleteme = "deleteme.dmd.unittest.pid";
- static _first = true;
+ enum base = "deleteme.dmd.unittest.pid";
+ static string fileName;
- if (_first)
- {
- _deleteme = buildPath(tempDir(), _deleteme) ~ to!string(thisProcessID);
- _first = false;
- }
-
- return _deleteme;
+ if (!fileName)
+ fileName = text(buildPath(tempDir(), base), thisProcessID);
+ return fileName;
}
-version (unittest) private struct TestAliasedString
+version (StdUnittest) private struct TestAliasedString
{
- string get() @safe @nogc pure nothrow { return _s; }
+ string get() @safe @nogc pure nothrow return scope { return _s; }
alias get this;
@disable this(this);
string _s;
@@ -164,7 +173,7 @@ class FileException : Exception
+/
immutable uint errno;
- private this(in char[] name, in char[] msg, string file, size_t line, uint errno) @safe pure
+ private this(scope const(char)[] name, scope const(char)[] msg, string file, size_t line, uint errno) @safe pure
{
if (msg.empty)
super(name.idup, file, line);
@@ -180,34 +189,34 @@ class FileException : Exception
Params:
name = Name of file for which the error occurred.
msg = Message describing the error.
- file = The _file where the error occurred.
+ file = The file where the error occurred.
line = The _line where the error occurred.
+/
- this(in char[] name, in char[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure
+ this(scope const(char)[] name, scope const(char)[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure
{
this(name, msg, file, line, 0);
}
/++
Constructor which takes the error number ($(LUCKY GetLastError)
- in Windows, $(D_PARAM errno) in Posix).
+ in Windows, $(D_PARAM errno) in POSIX).
Params:
name = Name of file for which the error occurred.
errno = The error number.
- file = The _file where the error occurred.
- Defaults to $(D __FILE__).
+ file = The file where the error occurred.
+ Defaults to `__FILE__`.
line = The _line where the error occurred.
- Defaults to $(D __LINE__).
+ Defaults to `__LINE__`.
+/
- version (Windows) this(in char[] name,
+ version (Windows) this(scope const(char)[] name,
uint errno = .GetLastError(),
string file = __FILE__,
size_t line = __LINE__) @safe
{
this(name, sysErrorString(errno), file, line, errno);
}
- else version (Posix) this(in char[] name,
+ else version (Posix) this(scope const(char)[] name,
uint errno = .errno,
string file = __FILE__,
size_t line = __LINE__) @trusted
@@ -217,7 +226,15 @@ class FileException : Exception
}
}
-private T cenforce(T)(T condition, lazy const(char)[] name, string file = __FILE__, size_t line = __LINE__)
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ assertThrown!FileException("non.existing.file.".readText);
+}
+
+private T cenforce(T)(T condition, lazy scope const(char)[] name, string file = __FILE__, size_t line = __LINE__)
{
if (condition)
return condition;
@@ -233,7 +250,7 @@ private T cenforce(T)(T condition, lazy const(char)[] name, string file = __FILE
version (Windows)
@trusted
-private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
+private T cenforce(T)(T condition, scope const(char)[] name, scope const(FSChar)* namez,
string file = __FILE__, size_t line = __LINE__)
{
if (condition)
@@ -251,7 +268,7 @@ private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
version (Posix)
@trusted
-private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
+private T cenforce(T)(T condition, scope const(char)[] name, scope const(FSChar)* namez,
string file = __FILE__, size_t line = __LINE__)
{
if (condition)
@@ -266,9 +283,9 @@ private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
throw new FileException(name, .errno, file, line);
}
+// https://issues.dlang.org/show_bug.cgi?id=17102
@safe unittest
{
- // issue 17102
try
{
cenforce(false, null, null,
@@ -282,8 +299,8 @@ private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
*/
/********************************************
-Read entire contents of file $(D name) and returns it as an untyped
-array. If the file size is larger than $(D upTo), only $(D upTo)
+Read entire contents of file `name` and returns it as an untyped
+array. If the file size is larger than `upTo`, only `upTo`
bytes are _read.
Params:
@@ -299,7 +316,7 @@ void[] read(R)(R name, size_t upTo = size_t.max)
if (isInputRange!R && isSomeChar!(ElementEncodingType!R) && !isInfinite!R &&
!isConvertibleToString!R)
{
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
return readImpl(name, name.tempCString!FSChar(), upTo);
else
return readImpl(null, name.tempCString!FSChar(), upTo);
@@ -315,7 +332,7 @@ if (isInputRange!R && isSomeChar!(ElementEncodingType!R) && !isInfinite!R &&
remove(deleteme);
}
- write(deleteme, "1234"); // deleteme is the name of a temporary file
+ std.file.write(deleteme, "1234"); // deleteme is the name of a temporary file
assert(read(deleteme, 2) == "12");
assert(read(deleteme.byChar) == "1234");
assert((cast(const(ubyte)[])read(deleteme)).length == 4);
@@ -333,12 +350,13 @@ if (isConvertibleToString!R)
static assert(__traits(compiles, read(TestAliasedString(null))));
}
-version (Posix) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @trusted
+version (Posix) private void[] readImpl(scope const(char)[] name, scope const(FSChar)* namez,
+ size_t upTo = size_t.max) @trusted
{
import core.memory : GC;
import std.algorithm.comparison : min;
- import std.array : uninitializedArray;
import std.conv : to;
+ import std.experimental.checkedint : checked;
// A few internal configuration parameters {
enum size_t
@@ -359,48 +377,49 @@ version (Posix) private void[] readImpl(const(char)[] name, const(FSChar)* namez
immutable initialAlloc = min(upTo, to!size_t(statbuf.st_size
? min(statbuf.st_size + 1, maxInitialAlloc)
: minInitialAlloc));
- void[] result = uninitializedArray!(ubyte[])(initialAlloc);
+ void[] result = GC.malloc(initialAlloc, GC.BlkAttr.NO_SCAN)[0 .. initialAlloc];
scope(failure) GC.free(result.ptr);
- size_t size = 0;
+
+ auto size = checked(size_t(0));
for (;;)
{
- immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size,
- min(result.length, upTo) - size);
+ immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size.get,
+ (min(result.length, upTo) - size).get);
cenforce(actual != -1, name, namez);
if (actual == 0) break;
size += actual;
if (size >= upTo) break;
if (size < result.length) continue;
immutable newAlloc = size + sizeIncrement;
- result = GC.realloc(result.ptr, newAlloc, GC.BlkAttr.NO_SCAN)[0 .. newAlloc];
+ result = GC.realloc(result.ptr, newAlloc.get, GC.BlkAttr.NO_SCAN)[0 .. newAlloc.get];
}
return result.length - size >= maxSlackMemoryAllowed
- ? GC.realloc(result.ptr, size, GC.BlkAttr.NO_SCAN)[0 .. size]
- : result[0 .. size];
+ ? GC.realloc(result.ptr, size.get, GC.BlkAttr.NO_SCAN)[0 .. size.get]
+ : result[0 .. size.get];
}
-version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @safe
+version (Windows) private void[] readImpl(scope const(char)[] name, scope const(FSChar)* namez,
+ size_t upTo = size_t.max) @trusted
{
import core.memory : GC;
import std.algorithm.comparison : min;
- import std.array : uninitializedArray;
- static trustedCreateFileW(const(wchar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode,
+ static trustedCreateFileW(scope const(wchar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode,
SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition,
- DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted
+ DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
{
return CreateFileW(namez, dwDesiredAccess, dwShareMode,
lpSecurityAttributes, dwCreationDisposition,
dwFlagsAndAttributes, hTemplateFile);
}
- static trustedCloseHandle(HANDLE hObject) @trusted
+ static trustedCloseHandle(HANDLE hObject)
{
return CloseHandle(hObject);
}
- static trustedGetFileSize(HANDLE hFile, out ulong fileSize) @trusted
+ static trustedGetFileSize(HANDLE hFile, out ulong fileSize)
{
DWORD sizeHigh;
DWORD sizeLow = GetFileSize(hFile, &sizeHigh);
@@ -409,7 +428,7 @@ version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* nam
fileSize = makeUlong(sizeLow, sizeHigh);
return result;
}
- static trustedReadFile(HANDLE hFile, void *lpBuffer, ulong nNumberOfBytesToRead) @trusted
+ static trustedReadFile(HANDLE hFile, void *lpBuffer, ulong nNumberOfBytesToRead)
{
// Read by chunks of size < 4GB (Windows API limit)
ulong totalNumRead = 0;
@@ -437,11 +456,11 @@ version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* nam
ulong fileSize = void;
cenforce(trustedGetFileSize(h, fileSize), name, namez);
size_t size = min(upTo, fileSize);
- auto buf = uninitializedArray!(ubyte[])(size);
+ auto buf = () { return GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size]; } ();
scope(failure)
{
- () @trusted { GC.free(buf.ptr); } ();
+ () { GC.free(buf.ptr); } ();
}
if (size)
@@ -452,7 +471,7 @@ version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* nam
version (linux) @safe unittest
{
// A file with "zero" length that doesn't have 0 length at all
- auto s = std.file.readText("/proc/sys/kernel/osrelease");
+ auto s = std.file.readText("/proc/cpuinfo");
assert(s.length > 0);
//writefln("'%s'", s);
}
@@ -466,49 +485,143 @@ version (linux) @safe unittest
assert(read(deleteme) == "abcd");
}
-/********************************************
-Read and validates (using $(REF validate, std,utf)) a text file. $(D S)
-can be a type of array of characters of any width and constancy. No
-width conversion is performed; if the width of the characters in file
-$(D name) is different from the width of elements of $(D S),
-validation will fail.
-
-Params:
- name = string or range of characters representing the file _name
+/++
+ Reads and validates (using $(REF validate, std, utf)) a text file. S can be
+ an array of any character type. However, no width or endian conversions are
+ performed. So, if the width or endianness of the characters in the given
+ file differ from the width or endianness of the element type of S, then
+ validation will fail.
-Returns: Array of characters read.
+ Params:
+ S = the string type of the file
+ name = string or range of characters representing the file _name
-Throws: $(D FileException) on file error, $(D UTFException) on UTF
-decoding error.
- */
+ Returns: Array of characters read.
-S readText(S = string, R)(R name)
-if (isSomeString!S &&
- (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
- !isConvertibleToString!R)
+ Throws: $(LREF FileException) if there is an error reading the file,
+ $(REF UTFException, std, utf) on UTF decoding error.
++/
+S readText(S = string, R)(auto ref R name)
+if (isSomeString!S && (isInputRange!R && !isInfinite!R && isSomeChar!(ElementType!R) || is(StringTypeOf!R)))
{
- import std.utf : validate;
- static auto trustedCast(void[] buf) @trusted { return cast(S) buf; }
- auto result = trustedCast(read(name));
+ import std.algorithm.searching : startsWith;
+ import std.encoding : getBOM, BOM;
+ import std.exception : enforce;
+ import std.format : format;
+ import std.utf : UTFException, validate;
+
+ static if (is(StringTypeOf!R))
+ StringTypeOf!R filename = name;
+ else
+ auto filename = name;
+
+ static auto trustedCast(T)(void[] buf) @trusted { return cast(T) buf; }
+ auto data = trustedCast!(ubyte[])(read(filename));
+
+ immutable bomSeq = getBOM(data);
+ immutable bom = bomSeq.schema;
+
+ static if (is(immutable ElementEncodingType!S == immutable char))
+ {
+ with(BOM) switch (bom)
+ {
+ case utf16be:
+ case utf16le: throw new UTFException("UTF-8 requested. BOM is for UTF-16");
+ case utf32be:
+ case utf32le: throw new UTFException("UTF-8 requested. BOM is for UTF-32");
+ default: break;
+ }
+ }
+ else static if (is(immutable ElementEncodingType!S == immutable wchar))
+ {
+ with(BOM) switch (bom)
+ {
+ case utf8: throw new UTFException("UTF-16 requested. BOM is for UTF-8");
+ case utf16be:
+ {
+ version (BigEndian)
+ break;
+ else
+ throw new UTFException("BOM is for UTF-16 LE on Big Endian machine");
+ }
+ case utf16le:
+ {
+ version (BigEndian)
+ throw new UTFException("BOM is for UTF-16 BE on Little Endian machine");
+ else
+ break;
+ }
+ case utf32be:
+ case utf32le: throw new UTFException("UTF-8 requested. BOM is for UTF-32");
+ default: break;
+ }
+ }
+ else
+ {
+ with(BOM) switch (bom)
+ {
+ case utf8: throw new UTFException("UTF-16 requested. BOM is for UTF-8");
+ case utf16be:
+ case utf16le: throw new UTFException("UTF-8 requested. BOM is for UTF-16");
+ case utf32be:
+ {
+ version (BigEndian)
+ break;
+ else
+ throw new UTFException("BOM is for UTF-32 LE on Big Endian machine");
+ }
+ case utf32le:
+ {
+ version (BigEndian)
+ throw new UTFException("BOM is for UTF-32 BE on Little Endian machine");
+ else
+ break;
+ }
+ default: break;
+ }
+ }
+
+ if (data.length % ElementEncodingType!S.sizeof != 0)
+ throw new UTFException(format!"The content of %s is not UTF-%s"(filename, ElementEncodingType!S.sizeof * 8));
+
+ auto result = trustedCast!S(data);
validate(result);
return result;
}
-///
+/// Read file with UTF-8 text.
@safe unittest
{
- import std.exception : enforce;
write(deleteme, "abc"); // deleteme is the name of a temporary file
scope(exit) remove(deleteme);
string content = readText(deleteme);
- enforce(content == "abc");
+ assert(content == "abc");
}
-/// ditto
-S readText(S = string, R)(auto ref R name)
-if (isConvertibleToString!R)
+// Read file with UTF-8 text but try to read it as UTF-16.
+@safe unittest
{
- return readText!(S, StringTypeOf!R)(name);
+ import std.exception : assertThrown;
+ import std.utf : UTFException;
+
+ write(deleteme, "abc");
+ scope(exit) remove(deleteme);
+ // Throws because the file is not valid UTF-16.
+ assertThrown!UTFException(readText!wstring(deleteme));
+}
+
+// Read file with UTF-16 text.
+@safe unittest
+{
+ import std.algorithm.searching : skipOver;
+
+ write(deleteme, "\uFEFFabc"w); // With BOM
+ scope(exit) remove(deleteme);
+ auto content = readText!wstring(deleteme);
+ assert(content == "\uFEFFabc"w);
+ // Strips BOM if present.
+ content.skipOver('\uFEFF');
+ assert(content == "abc"w);
}
@safe unittest
@@ -516,8 +629,103 @@ if (isConvertibleToString!R)
static assert(__traits(compiles, readText(TestAliasedString(null))));
}
+@safe unittest
+{
+ import std.array : appender;
+ import std.bitmanip : append, Endian;
+ import std.exception : assertThrown;
+ import std.path : buildPath;
+ import std.string : representation;
+ import std.utf : UTFException;
+
+ mkdir(deleteme);
+ scope(exit) rmdirRecurse(deleteme);
+
+ immutable none8 = buildPath(deleteme, "none8");
+ immutable none16 = buildPath(deleteme, "none16");
+ immutable utf8 = buildPath(deleteme, "utf8");
+ immutable utf16be = buildPath(deleteme, "utf16be");
+ immutable utf16le = buildPath(deleteme, "utf16le");
+ immutable utf32be = buildPath(deleteme, "utf32be");
+ immutable utf32le = buildPath(deleteme, "utf32le");
+ immutable utf7 = buildPath(deleteme, "utf7");
+
+ write(none8, "京都市");
+ write(none16, "京都市"w);
+ write(utf8, (cast(char[])[0xEF, 0xBB, 0xBF]) ~ "京都市");
+ {
+ auto str = "\uFEFF京都市"w;
+ auto arr = appender!(ubyte[])();
+ foreach (c; str)
+ arr.append(c);
+ write(utf16be, arr.data);
+ }
+ {
+ auto str = "\uFEFF京都市"w;
+ auto arr = appender!(ubyte[])();
+ foreach (c; str)
+ arr.append!(ushort, Endian.littleEndian)(c);
+ write(utf16le, arr.data);
+ }
+ {
+ auto str = "\U0000FEFF京都市"d;
+ auto arr = appender!(ubyte[])();
+ foreach (c; str)
+ arr.append(c);
+ write(utf32be, arr.data);
+ }
+ {
+ auto str = "\U0000FEFF京都市"d;
+ auto arr = appender!(ubyte[])();
+ foreach (c; str)
+ arr.append!(uint, Endian.littleEndian)(c);
+ write(utf32le, arr.data);
+ }
+ write(utf7, (cast(ubyte[])[0x2B, 0x2F, 0x76, 0x38, 0x2D]) ~ "foobar".representation);
+
+ assertThrown!UTFException(readText(none16));
+ assert(readText(utf8) == (cast(char[])[0xEF, 0xBB, 0xBF]) ~ "京都市");
+ assertThrown!UTFException(readText(utf16be));
+ assertThrown!UTFException(readText(utf16le));
+ assertThrown!UTFException(readText(utf32be));
+ assertThrown!UTFException(readText(utf32le));
+ assert(readText(utf7) == (cast(char[])[0x2B, 0x2F, 0x76, 0x38, 0x2D]) ~ "foobar");
+
+ assertThrown!UTFException(readText!wstring(none8));
+ assert(readText!wstring(none16) == "京都市"w);
+ assertThrown!UTFException(readText!wstring(utf8));
+ version (BigEndian)
+ {
+ assert(readText!wstring(utf16be) == "\uFEFF京都市"w);
+ assertThrown!UTFException(readText!wstring(utf16le));
+ }
+ else
+ {
+ assertThrown!UTFException(readText!wstring(utf16be));
+ assert(readText!wstring(utf16le) == "\uFEFF京都市"w);
+ }
+ assertThrown!UTFException(readText!wstring(utf32be));
+ assertThrown!UTFException(readText!wstring(utf32le));
+ assertThrown!UTFException(readText!wstring(utf7));
+
+ assertThrown!UTFException(readText!dstring(utf8));
+ assertThrown!UTFException(readText!dstring(utf16be));
+ assertThrown!UTFException(readText!dstring(utf16le));
+ version (BigEndian)
+ {
+ assert(readText!dstring(utf32be) == "\U0000FEFF京都市"d);
+ assertThrown!UTFException(readText!dstring(utf32le));
+ }
+ else
+ {
+ assertThrown!UTFException(readText!dstring(utf32be));
+ assert(readText!dstring(utf32le) == "\U0000FEFF京都市"d);
+ }
+ assertThrown!UTFException(readText!dstring(utf7));
+}
+
/*********************************************
-Write $(D buffer) to file $(D name).
+Write `buffer` to file `name`.
Creates the file if it does not already exist.
@@ -525,7 +733,7 @@ Params:
name = string or range of characters representing the file _name
buffer = data to be written to file
-Throws: $(D FileException) on error.
+Throws: $(LREF FileException) on error.
See_also: $(REF toFile, std,stdio)
*/
@@ -533,14 +741,14 @@ void write(R)(R name, const void[] buffer)
if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
!isConvertibleToString!R)
{
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
writeImpl(name, name.tempCString!FSChar(), buffer, false);
else
writeImpl(null, name.tempCString!FSChar(), buffer, false);
}
///
-@system unittest
+@safe unittest
{
scope(exit)
{
@@ -550,7 +758,9 @@ if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || is
int[] a = [ 0, 1, 1, 2, 3, 5, 8 ];
write(deleteme, a); // deleteme is the name of a temporary file
- assert(cast(int[]) read(deleteme) == a);
+ const bytes = read(deleteme);
+ const fileInts = () @trusted { return cast(int[]) bytes; }();
+ assert(fileInts == a);
}
/// ditto
@@ -566,7 +776,7 @@ if (isConvertibleToString!R)
}
/*********************************************
-Appends $(D buffer) to file $(D name).
+Appends `buffer` to file `name`.
Creates the file if it does not already exist.
@@ -574,20 +784,20 @@ Params:
name = string or range of characters representing the file _name
buffer = data to be appended to file
-Throws: $(D FileException) on error.
+Throws: $(LREF FileException) on error.
*/
void append(R)(R name, const void[] buffer)
if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) &&
!isConvertibleToString!R)
{
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
writeImpl(name, name.tempCString!FSChar(), buffer, true);
else
writeImpl(null, name.tempCString!FSChar(), buffer, true);
}
///
-@system unittest
+@safe unittest
{
scope(exit)
{
@@ -599,7 +809,9 @@ if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || is
write(deleteme, a); // deleteme is the name of a temporary file
int[] b = [ 13, 21 ];
append(deleteme, b);
- assert(cast(int[]) read(deleteme) == a ~ b);
+ const bytes = read(deleteme);
+ const fileInts = () @trusted { return cast(int[]) bytes; }();
+ assert(fileInts == a ~ b);
}
/// ditto
@@ -614,10 +826,10 @@ if (isConvertibleToString!R)
static assert(__traits(compiles, append(TestAliasedString("foo"), [0, 1, 2, 3])));
}
-// Posix implementation helper for write and append
+// POSIX implementation helper for write and append
-version (Posix) private void writeImpl(const(char)[] name, const(FSChar)* namez,
- in void[] buffer, bool append) @trusted
+version (Posix) private void writeImpl(scope const(char)[] name, scope const(FSChar)* namez,
+ scope const(void)[] buffer, bool append) @trusted
{
import std.conv : octal;
@@ -647,8 +859,8 @@ version (Posix) private void writeImpl(const(char)[] name, const(FSChar)* namez,
// Windows implementation helper for write and append
-version (Windows) private void writeImpl(const(char)[] name, const(FSChar)* namez,
- in void[] buffer, bool append) @trusted
+version (Windows) private void writeImpl(scope const(char)[] name, scope const(FSChar)* namez,
+ scope const(void)[] buffer, bool append) @trusted
{
HANDLE h;
if (append)
@@ -688,12 +900,21 @@ version (Windows) private void writeImpl(const(char)[] name, const(FSChar)* name
}
/***************************************************
- * Rename file $(D from) _to $(D to).
+ * Rename file `from` _to `to`, moving it between directories if required.
* If the target file exists, it is overwritten.
+ *
+ * It is not possible to rename a file across different mount points
+ * or drives. On POSIX, the operation is atomic. That means, if `to`
+ * already exists there will be no time period during the operation
+ * where `to` is missing. See
+ * $(HTTP man7.org/linux/man-pages/man2/rename.2.html, manpage for rename)
+ * for more details.
+ *
* Params:
* from = string or range of characters representing the existing file name
* to = string or range of characters representing the target file name
- * Throws: $(D FileException) on error.
+ *
+ * Throws: $(LREF FileException) on error.
*/
void rename(RF, RT)(RF from, RT to)
if ((isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) || isSomeString!RF)
@@ -705,12 +926,12 @@ if ((isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) ||
auto fromz = from.tempCString!FSChar();
auto toz = to.tempCString!FSChar();
- static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char))
+ static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char))
alias f = from;
else
enum string f = null;
- static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char))
+ static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable char))
alias t = to;
else
enum string t = null;
@@ -736,7 +957,23 @@ if (isConvertibleToString!RF || isConvertibleToString!RT)
static assert(__traits(compiles, rename(TestAliasedString(null), "".byChar)));
}
-private void renameImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz) @trusted
+///
+@safe unittest
+{
+ auto t1 = deleteme, t2 = deleteme~"2";
+ scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
+
+ t1.write("1");
+ t1.rename(t2);
+ assert(t2.readText == "1");
+
+ t1.write("2");
+ t1.rename(t2);
+ assert(t2.readText == "2");
+}
+
+private void renameImpl(scope const(char)[] f, scope const(char)[] t,
+ scope const(FSChar)* fromz, scope const(FSChar)* toz) @trusted
{
version (Windows)
{
@@ -773,28 +1010,29 @@ private void renameImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz,
auto t1 = deleteme, t2 = deleteme~"2";
scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
+
write(t1, "1");
rename(t1, t2);
assert(readText(t2) == "1");
+
write(t1, "2");
rename(t1, t2.byWchar);
assert(readText(t2) == "2");
}
-
/***************************************************
-Delete file $(D name).
+Delete file `name`.
Params:
name = string or range of characters representing the file _name
-Throws: $(D FileException) on error.
+Throws: $(LREF FileException) on error.
*/
void remove(R)(R name)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
!isConvertibleToString!R)
{
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
removeImpl(name, name.tempCString!FSChar());
else
removeImpl(null, name.tempCString!FSChar());
@@ -807,12 +1045,24 @@ if (isConvertibleToString!R)
remove!(StringTypeOf!R)(name);
}
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ deleteme.write("Hello");
+ assert(deleteme.readText == "Hello");
+
+ deleteme.remove;
+ assertThrown!FileException(deleteme.readText);
+}
+
@safe unittest
{
static assert(__traits(compiles, remove(TestAliasedString("foo"))));
}
-private void removeImpl(const(char)[] name, const(FSChar)* namez) @trusted
+private void removeImpl(scope const(char)[] name, scope const(FSChar)* namez) @trusted
{
version (Windows)
{
@@ -840,9 +1090,10 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R))
WIN32_FILE_ATTRIBUTE_DATA fad = void;
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
{
- static void getFA(const(char)[] name, const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
+ static void getFA(scope const(char)[] name, scope const(FSChar)* namez,
+ out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
{
import std.exception : enforce;
enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad),
@@ -852,7 +1103,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R))
}
else
{
- static void getFA(const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
+ static void getFA(scope const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted
{
import core.stdc.wchar_ : wcslen;
import std.conv : to;
@@ -874,13 +1125,15 @@ version (Windows) private ulong makeUlong(DWORD dwLow, DWORD dwHigh) @safe pure
return li.QuadPart;
}
-/***************************************************
-Get size of file $(D name) in bytes.
+/**
+Get size of file `name` in bytes.
Params:
name = string or range of characters representing the file _name
-
-Throws: $(D FileException) on error (e.g., file not found).
+Returns:
+ The size of file in bytes.
+Throws:
+ $(LREF FileException) on error (e.g., file not found).
*/
ulong getSize(R)(R name)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
@@ -899,7 +1152,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
{
return stat(namez, &buf);
}
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias names = name;
else
string names = null;
@@ -921,11 +1174,25 @@ if (isConvertibleToString!R)
static assert(__traits(compiles, getSize(TestAliasedString("foo"))));
}
+///
@safe unittest
{
+ scope(exit) deleteme.remove;
+
// create a file of size 1
write(deleteme, "a");
- scope(exit) { assert(exists(deleteme)); remove(deleteme); }
+ assert(getSize(deleteme) == 1);
+
+ // create a file of size 3
+ write(deleteme, "abc");
+ assert(getSize(deleteme) == 3);
+}
+
+@safe unittest
+{
+ // create a file of size 1
+ write(deleteme, "a");
+ scope(exit) deleteme.exists && deleteme.remove;
assert(getSize(deleteme) == 1);
// create a file of size 3
write(deleteme, "abc");
@@ -933,10 +1200,9 @@ if (isConvertibleToString!R)
assert(getSize(deleteme.byChar) == 3);
}
-
// Reads a time field from a stat_t with full precision.
version (Posix)
-private SysTime statTimeToStdTime(char which)(ref stat_t statbuf)
+private SysTime statTimeToStdTime(char which)(ref const stat_t statbuf)
{
auto unixTime = mixin(`statbuf.st_` ~ which ~ `time`);
long stdTime = unixTimeToStdTime(unixTime);
@@ -957,7 +1223,7 @@ private SysTime statTimeToStdTime(char which)(ref stat_t statbuf)
}
/++
- Get the access and modified times of file or folder $(D name).
+ Get the access and modified times of file or folder `name`.
Params:
name = File/Folder _name to get times for.
@@ -965,7 +1231,7 @@ private SysTime statTimeToStdTime(char which)(ref stat_t statbuf)
modificationTime = Time the file/folder was last modified.
Throws:
- $(D FileException) on error.
+ $(LREF FileException) on error.
+/
void getTimes(R)(R name,
out SysTime accessTime,
@@ -993,7 +1259,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
}
stat_t statbuf = void;
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias names = name;
else
string names = null;
@@ -1013,27 +1279,49 @@ if (isConvertibleToString!R)
return getTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
}
+///
+@safe unittest
+{
+ import std.datetime : abs, SysTime;
+
+ scope(exit) deleteme.remove;
+ write(deleteme, "a");
+
+ SysTime accessTime, modificationTime;
+
+ getTimes(deleteme, accessTime, modificationTime);
+
+ import std.datetime : Clock, seconds;
+ auto currTime = Clock.currTime();
+ enum leeway = 5.seconds;
+
+ auto diffAccess = accessTime - currTime;
+ auto diffModification = modificationTime - currTime;
+ assert(abs(diffAccess) <= leeway);
+ assert(abs(diffModification) <= leeway);
+}
+
@safe unittest
{
SysTime atime, mtime;
static assert(__traits(compiles, getTimes(TestAliasedString("foo"), atime, mtime)));
}
-@system unittest
+@safe unittest
{
import std.stdio : writefln;
auto currTime = Clock.currTime();
write(deleteme, "a");
- scope(exit) { assert(exists(deleteme)); remove(deleteme); }
+ scope(exit) assert(deleteme.exists), deleteme.remove;
- SysTime accessTime1 = void;
- SysTime modificationTime1 = void;
+ SysTime accessTime1;
+ SysTime modificationTime1;
getTimes(deleteme, accessTime1, modificationTime1);
- enum leeway = dur!"seconds"(5);
+ enum leeway = 5.seconds;
{
auto diffa = accessTime1 - currTime;
@@ -1079,10 +1367,10 @@ version (StdDdoc)
/++
$(BLUE This function is Windows-Only.)
- Get creation/access/modified times of file $(D name).
+ Get creation/access/modified times of file `name`.
- This is the same as $(D getTimes) except that it also gives you the file
- creation time - which isn't possible on Posix systems.
+ This is the same as `getTimes` except that it also gives you the file
+ creation time - which isn't possible on POSIX systems.
Params:
name = File _name to get times for.
@@ -1091,7 +1379,7 @@ version (StdDdoc)
fileModificationTime = Time the file was last modified.
Throws:
- $(D FileException) on error.
+ $(LREF FileException) on error.
+/
void getTimesWin(R)(R name,
out SysTime fileCreationTime,
@@ -1199,9 +1487,23 @@ version (Windows) @system unittest
}
}
+version (Darwin)
+private
+{
+ import core.stdc.config : c_ulong;
+ enum ATTR_CMN_MODTIME = 0x00000400, ATTR_CMN_ACCTIME = 0x00001000;
+ alias attrgroup_t = uint;
+ static struct attrlist
+ {
+ ushort bitmapcount, reserved;
+ attrgroup_t commonattr, volattr, dirattr, fileattr, forkattr;
+ }
+ extern(C) int setattrlist(in char* path, scope ref attrlist attrs,
+ scope void* attrbuf, size_t attrBufSize, c_ulong options) nothrow @nogc @system;
+}
/++
- Set access/modified times of file or folder $(D name).
+ Set access/modified times of file or folder `name`.
Params:
name = File/Folder _name to get times for.
@@ -1209,7 +1511,7 @@ version (Windows) @system unittest
modificationTime = Time the file/folder was last modified.
Throws:
- $(D FileException) on error.
+ $(LREF FileException) on error.
+/
void setTimes(R)(R name,
SysTime accessTime,
@@ -1217,30 +1519,48 @@ void setTimes(R)(R name,
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
!isConvertibleToString!R)
{
- version (Windows)
- {
- import std.datetime.systime : SysTimeToFILETIME;
+ auto namez = name.tempCString!FSChar();
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
+ alias names = name;
+ else
+ string names = null;
+ setTimesImpl(names, namez, accessTime, modificationTime);
+}
- auto namez = name.tempCString!FSChar();
- static auto trustedCreateFileW(const(FSChar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode,
- SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition,
- DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted
- {
- return CreateFileW(namez, dwDesiredAccess, dwShareMode,
- lpSecurityAttributes, dwCreationDisposition,
- dwFlagsAndAttributes, hTemplateFile);
+///
+@safe unittest
+{
+ import std.datetime : DateTime, hnsecs, SysTime;
- }
- static auto trustedCloseHandle(HANDLE hObject) @trusted
- {
- return CloseHandle(hObject);
- }
- static auto trustedSetFileTime(HANDLE hFile, in FILETIME *lpCreationTime,
- in ref FILETIME lpLastAccessTime, in ref FILETIME lpLastWriteTime) @trusted
- {
- return SetFileTime(hFile, lpCreationTime, &lpLastAccessTime, &lpLastWriteTime);
- }
+ scope(exit) deleteme.remove;
+ write(deleteme, "a");
+
+ SysTime accessTime = SysTime(DateTime(2010, 10, 4, 0, 0, 30));
+ SysTime modificationTime = SysTime(DateTime(2018, 10, 4, 0, 0, 30));
+ setTimes(deleteme, accessTime, modificationTime);
+
+ SysTime accessTimeResolved, modificationTimeResolved;
+ getTimes(deleteme, accessTimeResolved, modificationTimeResolved);
+
+ assert(accessTime == accessTimeResolved);
+ assert(modificationTime == modificationTimeResolved);
+}
+
+/// ditto
+void setTimes(R)(auto ref R name,
+ SysTime accessTime,
+ SysTime modificationTime)
+if (isConvertibleToString!R)
+{
+ setTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
+}
+private void setTimesImpl(scope const(char)[] names, scope const(FSChar)* namez,
+ SysTime accessTime, SysTime modificationTime) @trusted
+{
+ version (Windows)
+ {
+ import std.datetime.systime : SysTimeToFILETIME;
const ta = SysTimeToFILETIME(accessTime);
const tm = SysTimeToFILETIME(modificationTime);
alias defaults =
@@ -1252,75 +1572,55 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
FILE_ATTRIBUTE_DIRECTORY |
FILE_FLAG_BACKUP_SEMANTICS,
HANDLE.init);
- auto h = trustedCreateFileW(namez, defaults);
+ auto h = CreateFileW(namez, defaults);
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
- alias names = name;
- else
- string names = null;
cenforce(h != INVALID_HANDLE_VALUE, names, namez);
scope(exit)
- cenforce(trustedCloseHandle(h), names, namez);
+ cenforce(CloseHandle(h), names, namez);
- cenforce(trustedSetFileTime(h, null, ta, tm), names, namez);
+ cenforce(SetFileTime(h, null, &ta, &tm), names, namez);
}
- else version (Posix)
+ else
{
- auto namez = name.tempCString!FSChar();
static if (is(typeof(&utimensat)))
{
- static auto trustedUtimensat(int fd, const(FSChar)* namez, const ref timespec[2] times, int flags) @trusted
- {
- return utimensat(fd, namez, times, flags);
- }
timespec[2] t = void;
-
t[0] = accessTime.toTimeSpec();
t[1] = modificationTime.toTimeSpec();
-
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
- alias names = name;
- else
- string names = null;
- cenforce(trustedUtimensat(AT_FDCWD, namez, t, 0) == 0, names, namez);
+ cenforce(utimensat(AT_FDCWD, namez, t, 0) == 0, names, namez);
}
else
{
- static auto trustedUtimes(const(FSChar)* namez, const ref timeval[2] times) @trusted
+ version (Darwin)
{
- return utimes(namez, times);
+ // Set modification & access times with setattrlist to avoid precision loss.
+ attrlist attrs = { bitmapcount: 5, reserved: 0,
+ commonattr: ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME,
+ volattr: 0, dirattr: 0, fileattr: 0, forkattr: 0 };
+ timespec[2] attrbuf = [modificationTime.toTimeSpec(), accessTime.toTimeSpec()];
+ if (0 == setattrlist(namez, attrs, &attrbuf, attrbuf.sizeof, 0))
+ return;
+ if (.errno != ENOTSUP)
+ cenforce(false, names, namez);
+ // Not all volumes support setattrlist. In such cases
+ // fall through to the utimes implementation.
}
timeval[2] t = void;
-
t[0] = accessTime.toTimeVal();
t[1] = modificationTime.toTimeVal();
-
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
- alias names = name;
- else
- string names = null;
- cenforce(trustedUtimes(namez, t) == 0, names, namez);
+ cenforce(utimes(namez, t) == 0, names, namez);
}
}
}
-/// ditto
-void setTimes(R)(auto ref R name,
- SysTime accessTime,
- SysTime modificationTime)
-if (isConvertibleToString!R)
-{
- setTimes!(StringTypeOf!R)(name, accessTime, modificationTime);
-}
-
@safe unittest
{
if (false) // Test instatiation
setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init);
}
-@system unittest
+@safe unittest
{
import std.stdio : File;
string newdir = deleteme ~ r".dir";
@@ -1356,8 +1656,12 @@ if (isConvertibleToString!R)
/++
Returns the time that the given file was last modified.
+ Params:
+ name = the name of the file to check
+ Returns:
+ A $(REF SysTime,std,datetime,systime).
Throws:
- $(D FileException) if the given file does not exist.
+ $(LREF FileException) if the given file does not exist.
+/
SysTime timeLastModified(R)(R name)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
@@ -1381,7 +1685,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
}
stat_t statbuf = void;
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias names = name;
else
string names = null;
@@ -1398,6 +1702,19 @@ if (isConvertibleToString!R)
return timeLastModified!(StringTypeOf!R)(name);
}
+///
+@safe unittest
+{
+ import std.datetime : abs, DateTime, hnsecs, SysTime;
+ scope(exit) deleteme.remove;
+
+ import std.datetime : Clock, seconds;
+ auto currTime = Clock.currTime();
+ enum leeway = 5.seconds;
+ deleteme.write("bb");
+ assert(abs(deleteme.timeLastModified - currTime) <= leeway);
+}
+
@safe unittest
{
static assert(__traits(compiles, timeLastModified(TestAliasedString("foo"))));
@@ -1405,25 +1722,27 @@ if (isConvertibleToString!R)
/++
Returns the time that the given file was last modified. If the
- file does not exist, returns $(D returnIfMissing).
+ file does not exist, returns `returnIfMissing`.
A frequent usage pattern occurs in build automation tools such as
$(HTTP gnu.org/software/make, make) or $(HTTP
en.wikipedia.org/wiki/Apache_Ant, ant). To check whether file $(D
- target) must be rebuilt from file $(D source) (i.e., $(D target) is
- older than $(D source) or does not exist), use the comparison
- below. The code throws a $(D FileException) if $(D source) does not
- exist (as it should). On the other hand, the $(D SysTime.min) default
- makes a non-existing $(D target) seem infinitely old so the test
+ target) must be rebuilt from file `source` (i.e., `target` is
+ older than `source` or does not exist), use the comparison
+ below. The code throws a $(LREF FileException) if `source` does not
+ exist (as it should). On the other hand, the `SysTime.min` default
+ makes a non-existing `target` seem infinitely old so the test
correctly prompts building it.
Params:
- name = The _name of the file to get the modification time for.
+ name = The name of the file to get the modification time for.
returnIfMissing = The time to return if the given file does not exist.
+ Returns:
+ A $(REF SysTime,std,datetime,systime).
Example:
--------------------
-if (timeLastModified(source) >= timeLastModified(target, SysTime.min))
+if (source.timeLastModified >= target.timeLastModified(SysTime.min))
{
// must (re)build
}
@@ -1463,9 +1782,77 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R))
}
}
+///
@safe unittest
{
- //std.process.system("echo a > deleteme") == 0 || assert(false);
+ import std.datetime : SysTime;
+
+ assert("file.does.not.exist".timeLastModified(SysTime.min) == SysTime.min);
+
+ auto source = deleteme ~ "source";
+ auto target = deleteme ~ "target";
+ scope(exit) source.remove, target.remove;
+
+ source.write(".");
+ assert(target.timeLastModified(SysTime.min) < source.timeLastModified);
+ target.write(".");
+ assert(target.timeLastModified(SysTime.min) >= source.timeLastModified);
+}
+
+version (StdDdoc)
+{
+ /++
+ $(BLUE This function is POSIX-Only.)
+
+ Returns the time that the given file was last modified.
+ Params:
+ statbuf = stat_t retrieved from file.
+ +/
+ SysTime timeLastModified()(auto ref stat_t statbuf) pure nothrow {assert(false);}
+ /++
+ $(BLUE This function is POSIX-Only.)
+
+ Returns the time that the given file was last accessed.
+ Params:
+ statbuf = stat_t retrieved from file.
+ +/
+ SysTime timeLastAccessed()(auto ref stat_t statbuf) pure nothrow {assert(false);}
+ /++
+ $(BLUE This function is POSIX-Only.)
+
+ Returns the time that the given file was last changed.
+ Params:
+ statbuf = stat_t retrieved from file.
+ +/
+ SysTime timeStatusChanged()(auto ref stat_t statbuf) pure nothrow {assert(false);}
+}
+else version (Posix)
+{
+ SysTime timeLastModified()(auto ref stat_t statbuf) pure nothrow
+ {
+ return statTimeToStdTime!'m'(statbuf);
+ }
+ SysTime timeLastAccessed()(auto ref stat_t statbuf) pure nothrow
+ {
+ return statTimeToStdTime!'a'(statbuf);
+ }
+ SysTime timeStatusChanged()(auto ref stat_t statbuf) pure nothrow
+ {
+ return statTimeToStdTime!'c'(statbuf);
+ }
+
+ @safe unittest
+ {
+ stat_t statbuf;
+ // check that both lvalues and rvalues work
+ timeLastAccessed(statbuf);
+ cast(void) timeLastAccessed(stat_t.init);
+ }
+}
+
+@safe unittest
+{
+ //std.process.executeShell("echo a > deleteme");
if (exists(deleteme))
remove(deleteme);
@@ -1502,7 +1889,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R))
version (FreeBSD) {} else
version (DragonFlyBSD) {} else
version (OSX) {} else
-@system unittest
+@safe unittest
{
import core.thread;
@@ -1517,7 +1904,7 @@ version (OSX) {} else
remove(deleteme);
assert(time != lastTime);
lastTime = time;
- Thread.sleep(20.msecs);
+ () @trusted { Thread.sleep(20.msecs); }();
}
}
@@ -1543,6 +1930,19 @@ if (isConvertibleToString!R)
return exists!(StringTypeOf!R)(name);
}
+///
+@safe unittest
+{
+ auto f = deleteme ~ "does.not.exist";
+ assert(!f.exists);
+
+ f.write("hello");
+ assert(f.exists);
+
+ f.remove;
+ assert(!f.exists);
+}
+
private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc
{
version (Windows)
@@ -1580,16 +1980,18 @@ private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc
static assert(0);
}
+///
@safe unittest
{
- assert(exists("."));
- assert(!exists("this file does not exist"));
- write(deleteme, "a\n");
- scope(exit) { assert(exists(deleteme)); remove(deleteme); }
- assert(exists(deleteme));
+ assert(".".exists);
+ assert(!"this file does not exist".exists);
+ deleteme.write("a\n");
+ scope(exit) deleteme.remove;
+ assert(deleteme.exists);
}
-@safe unittest // Bugzilla 16573
+// https://issues.dlang.org/show_bug.cgi?id=16573
+@safe unittest
{
enum S : string { foo = "foo" }
assert(__traits(compiles, S.foo.exists));
@@ -1598,22 +2000,23 @@ private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc
/++
Returns the attributes of the given file.
- Note that the file attributes on Windows and Posix systems are
+ Note that the file attributes on Windows and POSIX systems are
completely different. On Windows, they're what is returned by
$(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx,
- GetFileAttributes), whereas on Posix systems, they're the $(LUCKY
- st_mode) value which is part of the $(D stat struct) gotten by
- calling the $(HTTP en.wikipedia.org/wiki/Stat_%28Unix%29, $(D stat))
+ GetFileAttributes), whereas on POSIX systems, they're the
+ `st_mode` value which is part of the $(D stat struct) gotten by
+ calling the $(HTTP en.wikipedia.org/wiki/Stat_%28Unix%29, `stat`)
function.
- On Posix systems, if the given file is a symbolic link, then
+ On POSIX systems, if the given file is a symbolic link, then
attributes are the attributes of the file pointed to by the symbolic
link.
Params:
- name = The file to get the attributes of.
-
- Throws: $(D FileException) on error.
+ name = The file to get the attributes of.
+ Returns:
+ The attributes of the file as a `uint`.
+ Throws: $(LREF FileException) on error.
+/
uint getAttributes(R)(R name)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
@@ -1628,7 +2031,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
}
immutable result = trustedGetFileAttributesW(namez);
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias names = name;
else
string names = null;
@@ -1645,7 +2048,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
}
stat_t statbuf = void;
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias names = name;
else
string names = null;
@@ -1662,6 +2065,40 @@ if (isConvertibleToString!R)
return getAttributes!(StringTypeOf!R)(name);
}
+/// getAttributes with a file
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto f = deleteme ~ "file";
+ scope(exit) f.remove;
+
+ assert(!f.exists);
+ assertThrown!FileException(f.getAttributes);
+
+ f.write(".");
+ auto attributes = f.getAttributes;
+ assert(!attributes.attrIsDir);
+ assert(attributes.attrIsFile);
+}
+
+/// getAttributes with a directory
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto dir = deleteme ~ "dir";
+ scope(exit) dir.rmdir;
+
+ assert(!dir.exists);
+ assertThrown!FileException(dir.getAttributes);
+
+ dir.mkdir;
+ auto attributes = dir.getAttributes;
+ assert(attributes.attrIsDir);
+ assert(!attributes.attrIsFile);
+}
+
@safe unittest
{
static assert(__traits(compiles, getAttributes(TestAliasedString(null))));
@@ -1684,7 +2121,7 @@ if (isConvertibleToString!R)
the attributes
Throws:
- $(D FileException) on error.
+ $(LREF FileException) on error.
+/
uint getLinkAttributes(R)(R name)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
@@ -1702,7 +2139,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
return lstat(namez, &buf);
}
stat_t lstatbuf = void;
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias names = name;
else
string names = null;
@@ -1718,6 +2155,64 @@ if (isConvertibleToString!R)
return getLinkAttributes!(StringTypeOf!R)(name);
}
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto source = deleteme ~ "source";
+ auto target = deleteme ~ "target";
+
+ assert(!source.exists);
+ assertThrown!FileException(source.getLinkAttributes);
+
+ // symlinking isn't available on Windows
+ version (Posix)
+ {
+ scope(exit) source.remove, target.remove;
+
+ target.write("target");
+ target.symlink(source);
+ assert(source.readText == "target");
+ assert(source.isSymlink);
+ assert(source.getLinkAttributes.attrIsSymlink);
+ }
+}
+
+/// if the file is no symlink, getLinkAttributes behaves like getAttributes
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto f = deleteme ~ "file";
+ scope(exit) f.remove;
+
+ assert(!f.exists);
+ assertThrown!FileException(f.getLinkAttributes);
+
+ f.write(".");
+ auto attributes = f.getLinkAttributes;
+ assert(!attributes.attrIsDir);
+ assert(attributes.attrIsFile);
+}
+
+/// if the file is no symlink, getLinkAttributes behaves like getAttributes
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto dir = deleteme ~ "dir";
+ scope(exit) dir.rmdir;
+
+ assert(!dir.exists);
+ assertThrown!FileException(dir.getLinkAttributes);
+
+ dir.mkdir;
+ auto attributes = dir.getLinkAttributes;
+ assert(attributes.attrIsDir);
+ assert(!attributes.attrIsFile);
+}
+
@safe unittest
{
static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null))));
@@ -1726,12 +2221,16 @@ if (isConvertibleToString!R)
/++
Set the _attributes of the given file.
+ For example, a programmatic equivalent of Unix's `chmod +x name`
+ to make a file executable is
+ `name.setAttributes(name.getAttributes | octal!700)`.
+
Params:
name = the file _name
attributes = the _attributes to set the file to
Throws:
- $(D FileException) if the given file does not exist.
+ $(LREF FileException) if the given file does not exist.
+/
void setAttributes(R)(R name, uint attributes)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
@@ -1744,7 +2243,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
{
return SetFileAttributesW(namez, dwFileAttributes);
}
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias names = name;
else
string names = null;
@@ -1758,7 +2257,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
return chmod(namez, mode);
}
assert(attributes <= mode_t.max);
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias names = name;
else
string names = null;
@@ -1778,6 +2277,58 @@ if (isConvertibleToString!R)
static assert(__traits(compiles, setAttributes(TestAliasedString(null), 0)));
}
+/// setAttributes with a file
+@safe unittest
+{
+ import std.exception : assertThrown;
+ import std.conv : octal;
+
+ auto f = deleteme ~ "file";
+ version (Posix)
+ {
+ scope(exit) f.remove;
+
+ assert(!f.exists);
+ assertThrown!FileException(f.setAttributes(octal!777));
+
+ f.write(".");
+ auto attributes = f.getAttributes;
+ assert(!attributes.attrIsDir);
+ assert(attributes.attrIsFile);
+
+ f.setAttributes(octal!777);
+ attributes = f.getAttributes;
+
+ assert((attributes & 1023) == octal!777);
+ }
+}
+
+/// setAttributes with a directory
+@safe unittest
+{
+ import std.exception : assertThrown;
+ import std.conv : octal;
+
+ auto dir = deleteme ~ "dir";
+ version (Posix)
+ {
+ scope(exit) dir.rmdir;
+
+ assert(!dir.exists);
+ assertThrown!FileException(dir.setAttributes(octal!777));
+
+ dir.mkdir;
+ auto attributes = dir.getAttributes;
+ assert(attributes.attrIsDir);
+ assert(!attributes.attrIsFile);
+
+ dir.setAttributes(octal!777);
+ attributes = dir.getAttributes;
+
+ assert((attributes & 1023) == octal!777);
+ }
+}
+
/++
Returns whether the given file is a directory.
@@ -1788,13 +2339,7 @@ if (isConvertibleToString!R)
true if name specifies a directory
Throws:
- $(D FileException) if the given file does not exist.
-
-Example:
---------------------
-assert(!"/etc/fonts/fonts.conf".isDir);
-assert("/usr/share/include".isDir);
---------------------
+ $(LREF FileException) if the given file does not exist.
+/
@property bool isDir(R)(R name)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
@@ -1817,6 +2362,26 @@ if (isConvertibleToString!R)
return name.isDir!(StringTypeOf!R);
}
+///
+
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto dir = deleteme ~ "dir";
+ auto f = deleteme ~ "f";
+ scope(exit) dir.rmdir, f.remove;
+
+ assert(!dir.exists);
+ assertThrown!FileException(dir.isDir);
+
+ dir.mkdir;
+ assert(dir.isDir);
+
+ f.write(".");
+ assert(!f.isDir);
+}
+
@safe unittest
{
static assert(__traits(compiles, TestAliasedString(null).isDir));
@@ -1842,7 +2407,7 @@ if (isConvertibleToString!R)
}
}
-@system unittest
+@safe unittest
{
version (Windows)
enum dir = "C:\\Program Files\\";
@@ -1865,13 +2430,7 @@ if (isConvertibleToString!R)
Returns:
true if attributes specifies a directory
-
-Example:
---------------------
-assert(!attrIsDir(getAttributes("/etc/fonts/fonts.conf")));
-assert(!attrIsDir(getLinkAttributes("/etc/fonts/fonts.conf")));
---------------------
- +/
++/
bool attrIsDir(uint attributes) @safe pure nothrow @nogc
{
version (Windows)
@@ -1884,6 +2443,27 @@ bool attrIsDir(uint attributes) @safe pure nothrow @nogc
}
}
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto dir = deleteme ~ "dir";
+ auto f = deleteme ~ "f";
+ scope(exit) dir.rmdir, f.remove;
+
+ assert(!dir.exists);
+ assertThrown!FileException(dir.getAttributes.attrIsDir);
+
+ dir.mkdir;
+ assert(dir.isDir);
+ assert(dir.getAttributes.attrIsDir);
+
+ f.write(".");
+ assert(!f.isDir);
+ assert(!f.getAttributes.attrIsDir);
+}
+
@safe unittest
{
version (Windows)
@@ -1921,15 +2501,15 @@ bool attrIsDir(uint attributes) @safe pure nothrow @nogc
Returns whether the given file (or directory) is a file.
On Windows, if a file is not a directory, then it's a file. So,
- either $(D isFile) or $(D isDir) will return true for any given file.
+ either `isFile` or `isDir` will return true for any given file.
- On Posix systems, if $(D isFile) is $(D true), that indicates that the file
- is a regular file (e.g. not a block not device). So, on Posix systems, it's
- possible for both $(D isFile) and $(D isDir) to be $(D false) for a
+ On POSIX systems, if `isFile` is `true`, that indicates that the file
+ is a regular file (e.g. not a block not device). So, on POSIX systems, it's
+ possible for both `isFile` and `isDir` to be `false` for a
particular file (in which case, it's a special file). You can use
- $(D getAttributes) to get the attributes to figure out what type of special
- it is, or you can use $(D DirEntry) to get at its $(D statBuf), which is the
- result from $(D stat). In either case, see the man page for $(D stat) for
+ `getAttributes` to get the attributes to figure out what type of special
+ it is, or you can use `DirEntry` to get at its `statBuf`, which is the
+ result from `stat`. In either case, see the man page for `stat` for
more information.
Params:
@@ -1939,14 +2519,8 @@ bool attrIsDir(uint attributes) @safe pure nothrow @nogc
true if name specifies a file
Throws:
- $(D FileException) if the given file does not exist.
-
-Example:
---------------------
-assert("/etc/fonts/fonts.conf".isFile);
-assert(!"/usr/share/include".isFile);
---------------------
- +/
+ $(LREF FileException) if the given file does not exist.
++/
@property bool isFile(R)(R name)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
!isConvertibleToString!R)
@@ -1964,7 +2538,27 @@ if (isConvertibleToString!R)
return isFile!(StringTypeOf!R)(name);
}
-@system unittest // bugzilla 15658
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto dir = deleteme ~ "dir";
+ auto f = deleteme ~ "f";
+ scope(exit) dir.rmdir, f.remove;
+
+ dir.mkdir;
+ assert(!dir.isFile);
+
+ assert(!f.exists);
+ assertThrown!FileException(f.isFile);
+
+ f.write(".");
+ assert(f.isFile);
+}
+
+// https://issues.dlang.org/show_bug.cgi?id=15658
+@safe unittest
{
DirEntry e = DirEntry(".");
static assert(is(typeof(isFile(e))));
@@ -2000,15 +2594,15 @@ if (isConvertibleToString!R)
Returns whether the given file _attributes are for a file.
On Windows, if a file is not a directory, it's a file. So, either
- $(D attrIsFile) or $(D attrIsDir) will return $(D true) for the
+ `attrIsFile` or `attrIsDir` will return `true` for the
_attributes of any given file.
- On Posix systems, if $(D attrIsFile) is $(D true), that indicates that the
- file is a regular file (e.g. not a block not device). So, on Posix systems,
- it's possible for both $(D attrIsFile) and $(D attrIsDir) to be $(D false)
+ On POSIX systems, if `attrIsFile` is `true`, that indicates that the
+ file is a regular file (e.g. not a block not device). So, on POSIX systems,
+ it's possible for both `attrIsFile` and `attrIsDir` to be `false`
for a particular file (in which case, it's a special file). If a file is a
special file, you can use the _attributes to check what type of special file
- it is (see the man page for $(D stat) for more information).
+ it is (see the man page for `stat` for more information).
Params:
attributes = The file _attributes.
@@ -2034,6 +2628,27 @@ bool attrIsFile(uint attributes) @safe pure nothrow @nogc
}
}
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto dir = deleteme ~ "dir";
+ auto f = deleteme ~ "f";
+ scope(exit) dir.rmdir, f.remove;
+
+ dir.mkdir;
+ assert(!dir.isFile);
+ assert(!dir.getAttributes.attrIsFile);
+
+ assert(!f.exists);
+ assertThrown!FileException(f.getAttributes.attrIsFile);
+
+ f.write(".");
+ assert(f.isFile);
+ assert(f.getAttributes.attrIsFile);
+}
+
@safe unittest
{
version (Windows)
@@ -2070,7 +2685,7 @@ bool attrIsFile(uint attributes) @safe pure nothrow @nogc
/++
Returns whether the given file is a symbolic link.
- On Windows, returns $(D true) when the file is either a symbolic link or a
+ On Windows, returns `true` when the file is either a symbolic link or a
junction point.
Params:
@@ -2080,7 +2695,7 @@ bool attrIsFile(uint attributes) @safe pure nothrow @nogc
true if name is a symbolic link
Throws:
- $(D FileException) if the given file does not exist.
+ $(LREF FileException) if the given file does not exist.
+/
@property bool isSymlink(R)(R name)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
@@ -2104,6 +2719,30 @@ if (isConvertibleToString!R)
static assert(__traits(compiles, TestAliasedString(null).isSymlink));
}
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto source = deleteme ~ "source";
+ auto target = deleteme ~ "target";
+
+ assert(!source.exists);
+ assertThrown!FileException(source.isSymlink);
+
+ // symlinking isn't available on Windows
+ version (Posix)
+ {
+ scope(exit) source.remove, target.remove;
+
+ target.write("target");
+ target.symlink(source);
+ assert(source.readText == "target");
+ assert(source.isSymlink);
+ assert(source.getLinkAttributes.attrIsSymlink);
+ }
+}
+
@system unittest
{
version (Windows)
@@ -2181,7 +2820,7 @@ if (isConvertibleToString!R)
/++
Returns whether the given file attributes are for a symbolic link.
- On Windows, return $(D true) when the file is either a symbolic link or a
+ On Windows, return `true` when the file is either a symbolic link or a
junction point.
Params:
@@ -2206,10 +2845,38 @@ bool attrIsSymlink(uint attributes) @safe pure nothrow @nogc
return (attributes & S_IFMT) == S_IFLNK;
}
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
-/****************************************************
- * Change directory to $(D pathname).
- * Throws: $(D FileException) on error.
+ auto source = deleteme ~ "source";
+ auto target = deleteme ~ "target";
+
+ assert(!source.exists);
+ assertThrown!FileException(source.getLinkAttributes.attrIsSymlink);
+
+ // symlinking isn't available on Windows
+ version (Posix)
+ {
+ scope(exit) source.remove, target.remove;
+
+ target.write("target");
+ target.symlink(source);
+ assert(source.readText == "target");
+ assert(source.isSymlink);
+ assert(source.getLinkAttributes.attrIsSymlink);
+ }
+}
+
+/**
+Change directory to `pathname`. Equivalent to `cd` on
+Windows and POSIX.
+
+Params:
+ pathname = the directory to step into
+
+Throws: $(LREF FileException) on error.
*/
void chdir(R)(R pathname)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
@@ -2232,7 +2899,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
return core.sys.posix.unistd.chdir(pathz) == 0;
}
}
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias pathStr = pathname;
else
string pathStr = null;
@@ -2246,16 +2913,41 @@ if (isConvertibleToString!R)
return chdir!(StringTypeOf!R)(pathname);
}
+///
+@system unittest
+{
+ import std.algorithm.comparison : equal;
+ import std.algorithm.sorting : sort;
+ import std.array : array;
+ import std.path : buildPath;
+
+ auto cwd = getcwd;
+ auto dir = deleteme ~ "dir";
+ dir.mkdir;
+ scope(exit) cwd.chdir, dir.rmdirRecurse;
+
+ dir.buildPath("a").write(".");
+ dir.chdir; // step into dir
+ "b".write(".");
+ assert(dirEntries(".", SpanMode.shallow).array.sort.equal(
+ [".".buildPath("a"), ".".buildPath("b")]
+ ));
+}
+
@safe unittest
{
static assert(__traits(compiles, chdir(TestAliasedString(null))));
}
-/****************************************************
-Make directory $(D pathname).
+/**
+Make a new directory `pathname`.
-Throws: $(D FileException) on Posix or $(D WindowsException) on Windows
- if an error occured.
+Params:
+ pathname = the path of the directory to make
+
+Throws:
+ $(LREF FileException) on POSIX or $(LREF WindowsException) on Windows
+ if an error occured.
*/
void mkdir(R)(R pathname)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
@@ -2270,7 +2962,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
{
return CreateDirectoryW(pathz, null);
}
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias pathStr = pathname;
else
string pathStr = null;
@@ -2284,7 +2976,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
{
return core.sys.posix.sys.stat.mkdir(pathz, mode);
}
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias pathStr = pathname;
else
string pathStr = null;
@@ -2305,10 +2997,29 @@ if (isConvertibleToString!R)
static assert(__traits(compiles, mkdir(TestAliasedString(null))));
}
+///
+@safe unittest
+{
+ import std.file : mkdir;
+
+ auto dir = deleteme ~ "dir";
+ scope(exit) dir.rmdir;
+
+ dir.mkdir;
+ assert(dir.exists);
+}
+
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
+ assertThrown("a/b/c/d/e".mkdir);
+}
+
// Same as mkdir but ignores "already exists" errors.
// Returns: "true" if the directory was created,
// "false" if it already existed.
-private bool ensureDirExists()(in char[] pathname)
+private bool ensureDirExists()(scope const(char)[] pathname)
{
import std.exception : enforce;
const pathz = pathname.tempCString!FSChar();
@@ -2331,16 +3042,18 @@ private bool ensureDirExists()(in char[] pathname)
return false;
}
-/****************************************************
- * Make directory and all parent directories as needed.
- *
- * Does nothing if the directory specified by
- * $(D pathname) already exists.
- *
- * Throws: $(D FileException) on error.
- */
+/**
+Make directory and all parent directories as needed.
+
+Does nothing if the directory specified by
+`pathname` already exists.
-void mkdirRecurse(in char[] pathname) @safe
+Params:
+ pathname = the full path of the directory to create
+
+Throws: $(LREF FileException) on error.
+ */
+void mkdirRecurse(scope const(char)[] pathname) @safe
{
import std.path : dirName, baseName;
@@ -2355,6 +3068,36 @@ void mkdirRecurse(in char[] pathname) @safe
}
}
+///
+@safe unittest
+{
+ import std.path : buildPath;
+
+ auto dir = deleteme ~ "dir";
+ scope(exit) dir.rmdirRecurse;
+
+ dir.mkdir;
+ assert(dir.exists);
+ dir.mkdirRecurse; // does nothing
+
+ // creates all parent directories as needed
+ auto nested = dir.buildPath("a", "b", "c");
+ nested.mkdirRecurse;
+ assert(nested.exists);
+}
+
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ scope(exit) deleteme.remove;
+ deleteme.write("a");
+
+ // cannot make directory as it's already a file
+ assertThrown!FileException(deleteme.mkdirRecurse);
+}
+
@safe unittest
{
import std.exception : assertThrown;
@@ -2383,7 +3126,7 @@ void mkdirRecurse(in char[] pathname) @safe
assertThrown!FileException(mkdirRecurse(`1:\foobar`));
}
- // bug3570
+ // https://issues.dlang.org/show_bug.cgi?id=3570
{
immutable basepath = deleteme ~ "_dir";
version (Windows)
@@ -2403,12 +3146,12 @@ void mkdirRecurse(in char[] pathname) @safe
}
/****************************************************
-Remove directory $(D pathname).
+Remove directory `pathname`.
Params:
pathname = Range or string specifying the directory name
-Throws: $(D FileException) on error.
+Throws: $(LREF FileException) on error.
*/
void rmdir(R)(R pathname)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
@@ -2431,7 +3174,7 @@ if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
return core.sys.posix.unistd.rmdir(pathz) == 0;
}
}
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias pathStr = pathname;
else
string pathStr = null;
@@ -2450,8 +3193,19 @@ if (isConvertibleToString!R)
static assert(__traits(compiles, rmdir(TestAliasedString(null))));
}
+///
+@safe unittest
+{
+ auto dir = deleteme ~ "dir";
+
+ dir.mkdir;
+ assert(dir.exists);
+ dir.rmdir;
+ assert(!dir.exists);
+}
+
/++
- $(BLUE This function is Posix-Only.)
+ $(BLUE This function is POSIX-Only.)
Creates a symbolic _link (_symlink).
@@ -2463,7 +3217,7 @@ if (isConvertibleToString!R)
current working directory.
Throws:
- $(D FileException) on error (which includes if the _symlink already
+ $(LREF FileException) on error (which includes if the _symlink already
exists).
+/
version (StdDdoc) void symlink(RO, RL)(RO original, RL link)
@@ -2545,7 +3299,7 @@ version (Posix) @safe unittest
/++
- $(BLUE This function is Posix-Only.)
+ $(BLUE This function is POSIX-Only.)
Returns the path to the file pointed to by a symlink. Note that the
path could be either relative or absolute depending on the symlink.
@@ -2553,7 +3307,7 @@ version (Posix) @safe unittest
working directory.
Throws:
- $(D FileException) on error.
+ $(LREF FileException) on error.
+/
version (StdDdoc) string readLink(R)(R link)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) ||
@@ -2642,7 +3396,7 @@ version (Posix) @system unittest // input range of dchars
import std.utf : byChar;
immutable string link = deleteme ~ "/l";
symlink("f", link);
- InputRange!dchar linkr = inputRangeObject(link);
+ InputRange!(ElementType!string) linkr = inputRangeObject(link);
alias R = typeof(linkr);
static assert(isInputRange!R);
static assert(!isForwardRange!R);
@@ -2652,11 +3406,12 @@ version (Posix) @system unittest // input range of dchars
/****************************************************
* Get the current working directory.
- * Throws: $(D FileException) on error.
+ * Throws: $(LREF FileException) on error.
*/
-version (Windows) string getcwd()
+version (Windows) string getcwd() @trusted
{
import std.conv : to;
+ import std.experimental.checkedint : checked;
/* GetCurrentDirectory's return value:
1. function succeeds: the number of characters that are written to
the buffer, not including the terminating null character.
@@ -2664,7 +3419,11 @@ version (Windows) string getcwd()
3. the buffer (lpBuffer) is not large enough: the required size of
the buffer, in characters, including the null-terminating character.
*/
- wchar[4096] buffW = void; //enough for most common case
+ version (StdUnittest)
+ enum BUF_SIZE = 10; // trigger reallocation code
+ else
+ enum BUF_SIZE = 4096; // enough for most common case
+ wchar[BUF_SIZE] buffW = void;
immutable n = cenforce(GetCurrentDirectoryW(to!DWORD(buffW.length), buffW.ptr),
"getcwd");
// we can do it because toUTFX always produces a fresh string
@@ -2674,14 +3433,15 @@ version (Windows) string getcwd()
}
else //staticBuff isn't enough
{
- auto ptr = cast(wchar*) malloc(wchar.sizeof * n);
+ auto cn = checked(n);
+ auto ptr = cast(wchar*) malloc((cn * wchar.sizeof).get);
scope(exit) free(ptr);
- immutable n2 = GetCurrentDirectoryW(n, ptr);
- cenforce(n2 && n2 < n, "getcwd");
+ immutable n2 = GetCurrentDirectoryW(cn.get, ptr);
+ cenforce(n2 && n2 < cn, "getcwd");
return ptr[0 .. n2].to!string;
}
}
-else version (Solaris) string getcwd()
+else version (Solaris) string getcwd() @trusted
{
/* BUF_SIZE >= PATH_MAX */
enum BUF_SIZE = 4096;
@@ -2691,7 +3451,7 @@ else version (Solaris) string getcwd()
scope(exit) core.stdc.stdlib.free(p);
return p[0 .. core.stdc.string.strlen(p)].idup;
}
-else version (Posix) string getcwd()
+else version (Posix) string getcwd() @trusted
{
auto p = cenforce(core.sys.posix.unistd.getcwd(null, 0),
"cannot get cwd");
@@ -2699,31 +3459,27 @@ else version (Posix) string getcwd()
return p[0 .. core.stdc.string.strlen(p)].idup;
}
-@system unittest
+///
+@safe unittest
{
auto s = getcwd();
assert(s.length);
}
-version (OSX)
- private extern (C) int _NSGetExecutablePath(char* buf, uint* bufsize);
-else version (FreeBSD)
- private extern (C) int sysctl (const int* name, uint namelen, void* oldp,
- size_t* oldlenp, const void* newp, size_t newlen);
-else version (NetBSD)
- private extern (C) int sysctl (const int* name, uint namelen, void* oldp,
- size_t* oldlenp, const void* newp, size_t newlen);
-
/**
* Returns the full path of the current executable.
*
+ * Returns:
+ * The path of the executable as a `string`.
+ *
* Throws:
* $(REF1 Exception, object)
*/
-@trusted string thisExePath ()
+@trusted string thisExePath()
{
- version (OSX)
+ version (Darwin)
{
+ import core.sys.darwin.mach.dyld : _NSGetExecutablePath;
import core.sys.posix.stdlib : realpath;
import std.conv : to;
import std.exception : errnoEnforce;
@@ -2867,6 +3623,7 @@ else version (NetBSD)
static assert(0, "thisExePath is not supported on this platform");
}
+///
@safe unittest
{
import std.path : isAbsolute;
@@ -2880,18 +3637,19 @@ else version (NetBSD)
version (StdDdoc)
{
/++
- Info on a file, similar to what you'd get from stat on a Posix system.
+ Info on a file, similar to what you'd get from stat on a POSIX system.
+/
struct DirEntry
{
+ @safe:
/++
- Constructs a $(D DirEntry) for the given file (or directory).
+ Constructs a `DirEntry` for the given file (or directory).
Params:
path = The file (or directory) to get a DirEntry for.
Throws:
- $(D FileException) if the file does not exist.
+ $(LREF FileException) if the file does not exist.
+/
this(string path);
@@ -2905,7 +3663,7 @@ version (StdDdoc)
}
/++
- Returns the path to the file represented by this $(D DirEntry).
+ Returns the path to the file represented by this `DirEntry`.
Example:
--------------------
@@ -2916,11 +3674,11 @@ auto de2 = DirEntry("/usr/share/include");
assert(de2.name == "/usr/share/include");
--------------------
+/
- @property string name() const;
+ @property string name() const return scope;
/++
- Returns whether the file represented by this $(D DirEntry) is a
+ Returns whether the file represented by this `DirEntry` is a
directory.
Example:
@@ -2932,20 +3690,20 @@ auto de2 = DirEntry("/usr/share/include");
assert(de2.isDir);
--------------------
+/
- @property bool isDir();
+ @property bool isDir() scope;
/++
- Returns whether the file represented by this $(D DirEntry) is a file.
+ Returns whether the file represented by this `DirEntry` is a file.
On Windows, if a file is not a directory, then it's a file. So,
- either $(D isFile) or $(D isDir) will return $(D true).
+ either `isFile` or `isDir` will return `true`.
- On Posix systems, if $(D isFile) is $(D true), that indicates that
+ On POSIX systems, if `isFile` is `true`, that indicates that
the file is a regular file (e.g. not a block not device). So, on
- Posix systems, it's possible for both $(D isFile) and $(D isDir) to
- be $(D false) for a particular file (in which case, it's a special
- file). You can use $(D attributes) or $(D statBuf) to get more
+ POSIX systems, it's possible for both `isFile` and `isDir` to
+ be `false` for a particular file (in which case, it's a special
+ file). You can use `attributes` or `statBuf` to get more
information about a special file (see the stat man page for more
details).
@@ -2958,91 +3716,100 @@ auto de2 = DirEntry("/usr/share/include");
assert(!de2.isFile);
--------------------
+/
- @property bool isFile();
+ @property bool isFile() scope;
/++
- Returns whether the file represented by this $(D DirEntry) is a
+ Returns whether the file represented by this `DirEntry` is a
symbolic link.
- On Windows, return $(D true) when the file is either a symbolic
+ On Windows, return `true` when the file is either a symbolic
link or a junction point.
+/
- @property bool isSymlink();
+ @property bool isSymlink() scope;
/++
- Returns the size of the the file represented by this $(D DirEntry)
+ Returns the size of the the file represented by this `DirEntry`
in bytes.
+/
- @property ulong size();
+ @property ulong size() scope;
/++
$(BLUE This function is Windows-Only.)
Returns the creation time of the file represented by this
- $(D DirEntry).
+ `DirEntry`.
+/
- @property SysTime timeCreated() const;
+ @property SysTime timeCreated() const scope;
/++
- Returns the time that the file represented by this $(D DirEntry) was
+ Returns the time that the file represented by this `DirEntry` was
last accessed.
Note that many file systems do not update the access time for files
(generally for performance reasons), so there's a good chance that
- $(D timeLastAccessed) will return the same value as
- $(D timeLastModified).
+ `timeLastAccessed` will return the same value as
+ `timeLastModified`.
+/
- @property SysTime timeLastAccessed();
+ @property SysTime timeLastAccessed() scope;
/++
- Returns the time that the file represented by this $(D DirEntry) was
+ Returns the time that the file represented by this `DirEntry` was
last modified.
+/
- @property SysTime timeLastModified();
+ @property SysTime timeLastModified() scope;
/++
- Returns the _attributes of the file represented by this $(D DirEntry).
+ $(BLUE This function is POSIX-Only.)
- Note that the file _attributes on Windows and Posix systems are
+ Returns the time that the file represented by this `DirEntry` was
+ last changed (not only in contents, but also in permissions or ownership).
+ +/
+ @property SysTime timeStatusChanged() const scope;
+
+ /++
+ Returns the _attributes of the file represented by this `DirEntry`.
+
+ Note that the file _attributes on Windows and POSIX systems are
completely different. On, Windows, they're what is returned by
- $(D GetFileAttributes)
+ `GetFileAttributes`
$(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, GetFileAttributes)
- Whereas, an Posix systems, they're the $(D st_mode) value which is
- part of the $(D stat) struct gotten by calling $(D stat).
+ Whereas, an POSIX systems, they're the `st_mode` value which is
+ part of the `stat` struct gotten by calling `stat`.
- On Posix systems, if the file represented by this $(D DirEntry) is a
+ On POSIX systems, if the file represented by this `DirEntry` is a
symbolic link, then _attributes are the _attributes of the file
pointed to by the symbolic link.
+/
- @property uint attributes();
+ @property uint attributes() scope;
/++
- On Posix systems, if the file represented by this $(D DirEntry) is a
- symbolic link, then $(D linkAttributes) are the attributes of the
- symbolic link itself. Otherwise, $(D linkAttributes) is identical to
- $(D attributes).
+ On POSIX systems, if the file represented by this `DirEntry` is a
+ symbolic link, then `linkAttributes` are the attributes of the
+ symbolic link itself. Otherwise, `linkAttributes` is identical to
+ `attributes`.
- On Windows, $(D linkAttributes) is identical to $(D attributes). It
+ On Windows, `linkAttributes` is identical to `attributes`. It
exists on Windows so that you don't have to special-case code for
Windows when dealing with symbolic links.
+/
- @property uint linkAttributes();
+ @property uint linkAttributes() scope;
version (Windows)
alias stat_t = void*;
/++
- $(BLUE This function is Posix-Only.)
+ $(BLUE This function is POSIX-Only.)
- The $(D stat) struct gotten from calling $(D stat).
+ The `stat` struct gotten from calling `stat`.
+/
- @property stat_t statBuf();
+ @property stat_t statBuf() scope;
}
}
else version (Windows)
{
struct DirEntry
{
+ @safe:
public:
alias name this;
@@ -3065,14 +3832,16 @@ else version (Windows)
}
}
- private this(string path, in WIN32_FIND_DATAW *fd)
+ private this(string path, WIN32_FIND_DATAW *fd) @trusted
{
import core.stdc.wchar_ : wcslen;
import std.conv : to;
import std.datetime.systime : FILETIMEToSysTime;
import std.path : buildPath;
- size_t clength = wcslen(fd.cFileName.ptr);
+ fd.cFileName[$ - 1] = 0;
+
+ size_t clength = wcslen(&fd.cFileName[0]);
_name = buildPath(path, fd.cFileName[0 .. clength].to!string);
_size = (cast(ulong) fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
_timeCreated = FILETIMEToSysTime(&fd.ftCreationTime);
@@ -3081,17 +3850,17 @@ else version (Windows)
_attributes = fd.dwFileAttributes;
}
- @property string name() const pure nothrow
+ @property string name() const pure nothrow return scope
{
return _name;
}
- @property bool isDir() const pure nothrow
+ @property bool isDir() const pure nothrow scope
{
return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
- @property bool isFile() const pure nothrow
+ @property bool isFile() const pure nothrow scope
{
//Are there no options in Windows other than directory and file?
//If there are, then this probably isn't the best way to determine
@@ -3099,37 +3868,37 @@ else version (Windows)
return !isDir;
}
- @property bool isSymlink() const pure nothrow
+ @property bool isSymlink() const pure nothrow scope
{
return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
}
- @property ulong size() const pure nothrow
+ @property ulong size() const pure nothrow scope
{
return _size;
}
- @property SysTime timeCreated() const pure nothrow
+ @property SysTime timeCreated() const pure nothrow scope
{
return cast(SysTime)_timeCreated;
}
- @property SysTime timeLastAccessed() const pure nothrow
+ @property SysTime timeLastAccessed() const pure nothrow scope
{
return cast(SysTime)_timeLastAccessed;
}
- @property SysTime timeLastModified() const pure nothrow
+ @property SysTime timeLastModified() const pure nothrow scope
{
return cast(SysTime)_timeLastModified;
}
- @property uint attributes() const pure nothrow
+ @property uint attributes() const pure nothrow scope
{
return _attributes;
}
- @property uint linkAttributes() const pure nothrow
+ @property uint linkAttributes() const pure nothrow scope
{
return _attributes;
}
@@ -3149,6 +3918,7 @@ else version (Posix)
{
struct DirEntry
{
+ @safe:
public:
alias name this;
@@ -3164,7 +3934,7 @@ else version (Posix)
_dTypeSet = false;
}
- private this(string path, core.sys.posix.dirent.dirent* fd)
+ private this(string path, core.sys.posix.dirent.dirent* fd) @safe
{
import std.path : buildPath;
@@ -3203,74 +3973,74 @@ else version (Posix)
}
}
- @property string name() const pure nothrow
+ @property string name() const pure nothrow return scope
{
return _name;
}
- @property bool isDir()
+ @property bool isDir() scope
{
_ensureStatOrLStatDone();
return (_statBuf.st_mode & S_IFMT) == S_IFDIR;
}
- @property bool isFile()
+ @property bool isFile() scope
{
_ensureStatOrLStatDone();
return (_statBuf.st_mode & S_IFMT) == S_IFREG;
}
- @property bool isSymlink()
+ @property bool isSymlink() scope
{
_ensureLStatDone();
return (_lstatMode & S_IFMT) == S_IFLNK;
}
- @property ulong size()
+ @property ulong size() scope
{
_ensureStatDone();
return _statBuf.st_size;
}
- @property SysTime timeStatusChanged()
+ @property SysTime timeStatusChanged() scope
{
_ensureStatDone();
return statTimeToStdTime!'c'(_statBuf);
}
- @property SysTime timeLastAccessed()
+ @property SysTime timeLastAccessed() scope
{
_ensureStatDone();
return statTimeToStdTime!'a'(_statBuf);
}
- @property SysTime timeLastModified()
+ @property SysTime timeLastModified() scope
{
_ensureStatDone();
return statTimeToStdTime!'m'(_statBuf);
}
- @property uint attributes()
+ @property uint attributes() scope
{
_ensureStatDone();
return _statBuf.st_mode;
}
- @property uint linkAttributes()
+ @property uint linkAttributes() scope
{
_ensureLStatDone();
return _lstatMode;
}
- @property stat_t statBuf()
+ @property stat_t statBuf() scope
{
_ensureStatDone();
@@ -3282,18 +4052,14 @@ else version (Posix)
This is to support lazy evaluation, because doing stat's is
expensive and not always needed.
+/
- void _ensureStatDone() @safe
+ void _ensureStatDone() @trusted scope
{
import std.exception : enforce;
- static auto trustedStat(in char[] path, stat_t* buf) @trusted
- {
- return stat(path.tempCString(), buf);
- }
if (_didStat)
return;
- enforce(trustedStat(_name, &_statBuf) == 0,
+ enforce(stat(_name.tempCString(), &_statBuf) == 0,
"Failed to stat file `" ~ _name ~ "'");
_didStat = true;
@@ -3306,12 +4072,12 @@ else version (Posix)
Try both stat and lstat for isFile and isDir
to detect broken symlinks.
+/
- void _ensureStatOrLStatDone()
+ void _ensureStatOrLStatDone() @trusted scope
{
if (_didStat)
return;
- if ( stat(_name.tempCString(), &_statBuf) != 0 )
+ if (stat(_name.tempCString(), &_statBuf) != 0)
{
_ensureLStatDone();
@@ -3328,7 +4094,7 @@ else version (Posix)
This is to support lazy evaluation, because doing stat's is
expensive and not always needed.
+/
- void _ensureLStatDone()
+ void _ensureLStatDone() @trusted scope
{
import std.exception : enforce;
@@ -3336,7 +4102,6 @@ else version (Posix)
return;
stat_t statbuf = void;
-
enforce(lstat(_name.tempCString(), &statbuf) == 0,
"Failed to stat file `" ~ _name ~ "'");
@@ -3348,9 +4113,9 @@ else version (Posix)
string _name; /// The file or directory represented by this DirEntry.
- stat_t _statBuf = void; /// The result of stat().
- uint _lstatMode; /// The stat mode from lstat().
- ubyte _dType; /// The type of the file.
+ stat_t _statBuf = void; /// The result of stat().
+ uint _lstatMode; /// The stat mode from lstat().
+ ubyte _dType; /// The type of the file.
bool _didLStat = false; /// Whether lstat() has been called for this DirEntry.
bool _didStat = false; /// Whether stat() has been called for this DirEntry.
@@ -3413,7 +4178,7 @@ else version (Posix)
core.sys.posix.unistd.symlink((deleteme ~ "_broken_symlink\0").ptr, symfile.ptr);
{
- //Issue 8298
+ // https://issues.dlang.org/show_bug.cgi?id=8298
DirEntry de = DirEntry(symfile);
assert(!de.isFile);
@@ -3444,7 +4209,7 @@ alias PreserveAttributes = Flag!"preserveAttributes";
version (StdDdoc)
{
- /// Defaults to $(D Yes.preserveAttributes) on Windows, and the opposite on all other platforms.
+ /// Defaults to `Yes.preserveAttributes` on Windows, and the opposite on all other platforms.
PreserveAttributes preserveAttributesDefault;
}
else version (Windows)
@@ -3457,9 +4222,9 @@ else
}
/***************************************************
-Copy file $(D from) _to file $(D to). File timestamps are preserved.
-File attributes are preserved, if $(D preserve) equals $(D Yes.preserveAttributes).
-On Windows only $(D Yes.preserveAttributes) (the default on Windows) is supported.
+Copy file `from` _to file `to`. File timestamps are preserved.
+File attributes are preserved, if `preserve` equals `Yes.preserveAttributes`.
+On Windows only `Yes.preserveAttributes` (the default on Windows) is supported.
If the target file exists, it is overwritten.
Params:
@@ -3467,7 +4232,7 @@ Params:
to = string or range of characters representing the target file name
preserve = whether to _preserve the file attributes
-Throws: $(D FileException) on error.
+Throws: $(LREF FileException) on error.
*/
void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault)
if (isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) && !isConvertibleToString!RF &&
@@ -3477,12 +4242,12 @@ if (isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) &&
auto fromz = from.tempCString!FSChar();
auto toz = to.tempCString!FSChar();
- static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char))
+ static if (isNarrowString!RF && is(immutable ElementEncodingType!RF == immutable char))
alias f = from;
else
enum string f = null;
- static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char))
+ static if (isNarrowString!RT && is(immutable ElementEncodingType!RT == immutable char))
alias t = to;
else
enum string t = null;
@@ -3499,13 +4264,36 @@ if (isConvertibleToString!RF || isConvertibleToString!RT)
copy!Types(from, to, preserve);
}
-@safe unittest // issue 15319
+///
+@safe unittest
+{
+ auto source = deleteme ~ "source";
+ auto target = deleteme ~ "target";
+ auto targetNonExistent = deleteme ~ "target2";
+
+ scope(exit) source.remove, target.remove, targetNonExistent.remove;
+
+ source.write("source");
+ target.write("target");
+
+ assert(target.readText == "target");
+
+ source.copy(target);
+ assert(target.readText == "source");
+
+ source.copy(targetNonExistent);
+ assert(targetNonExistent.readText == "source");
+}
+
+// https://issues.dlang.org/show_bug.cgi?id=15319
+@safe unittest
{
assert(__traits(compiles, copy("from.txt", "to.txt")));
}
-private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz,
- PreserveAttributes preserve) @trusted
+private void copyImpl(scope const(char)[] f, scope const(char)[] t,
+ scope const(FSChar)* fromz, scope const(FSChar)* toz,
+ PreserveAttributes preserve) @trusted
{
version (Windows)
{
@@ -3515,11 +4303,19 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co
{
import core.stdc.wchar_ : wcslen;
import std.conv : to;
+ import std.format : format;
+ /++
+ Reference resources: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew
+ Because OS copyfilew handles both source and destination paths,
+ the GetLastError does not accurately locate whether the error is for the source or destination.
+ +/
+ if (!f)
+ f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]);
if (!t)
t = to!(typeof(t))(toz[0 .. wcslen(toz)]);
- throw new FileException(t);
+ throw new FileException(format!"Copy from %s to %s"(f, t));
}
}
else version (Posix)
@@ -3582,17 +4378,14 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co
cenforce(core.sys.posix.unistd.close(fdw) != -1, f, fromz);
- utimbuf utim = void;
- utim.actime = cast(time_t) statbufr.st_atime;
- utim.modtime = cast(time_t) statbufr.st_mtime;
-
- cenforce(utime(toz, &utim) != -1, f, fromz);
+ setTimesImpl(t, toz, statbufr.statTimeToStdTime!'a', statbufr.statTimeToStdTime!'m');
}
}
+// https://issues.dlang.org/show_bug.cgi?id=14817
@safe unittest
{
- import std.algorithm, std.file; // issue 14817
+ import std.algorithm, std.file;
auto t1 = deleteme, t2 = deleteme~"2";
scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove();
write(t1, "11");
@@ -3605,9 +4398,18 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co
import std.utf : byChar;
copy(t1.byChar, t2.byChar);
assert(readText(t2.byChar) == "2");
+
+// https://issues.dlang.org/show_bug.cgi?id=20370
+ version (Windows)
+ assert(t1.timeLastModified == t2.timeLastModified);
+ else static if (is(typeof(&utimensat)) || is(typeof(&setattrlist)))
+ assert(t1.timeLastModified == t2.timeLastModified);
+ else
+ assert(abs(t1.timeLastModified - t2.timeLastModified) < dur!"usecs"(1));
}
-@safe version (Posix) @safe unittest //issue 11434
+// https://issues.dlang.org/show_bug.cgi?id=11434
+@safe version (Posix) @safe unittest
{
import std.conv : octal;
auto t1 = deleteme, t2 = deleteme~"2";
@@ -3619,7 +4421,8 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co
assert(getAttributes(t2) == octal!100767);
}
-@safe unittest // issue 15865
+// https://issues.dlang.org/show_bug.cgi?id=15865
+@safe unittest
{
import std.exception : assertThrown;
auto t = deleteme;
@@ -3629,30 +4432,40 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co
assert(readText(t) == "a");
}
+// https://issues.dlang.org/show_bug.cgi?id=19834
+version (Windows) @safe unittest
+{
+ import std.exception : collectException;
+ import std.algorithm.searching : startsWith;
+ import std.format : format;
+
+ auto f = deleteme;
+ auto t = f ~ "2";
+ auto ex = collectException(copy(f, t));
+ assert(ex.msg.startsWith(format!"Copy from %s to %s"(f, t)));
+}
+
/++
Remove directory and all of its content and subdirectories,
recursively.
+ Params:
+ pathname = the path of the directory to completely remove
+ de = The $(LREF DirEntry) to remove
+
Throws:
- $(D FileException) if there is an error (including if the given
+ $(LREF FileException) if there is an error (including if the given
file is not a directory).
+/
-void rmdirRecurse(in char[] pathname)
+void rmdirRecurse(scope const(char)[] pathname) @safe
{
//No references to pathname will be kept after rmdirRecurse,
//so the cast is safe
- rmdirRecurse(DirEntry(cast(string) pathname));
+ rmdirRecurse(DirEntry((() @trusted => cast(string) pathname)()));
}
-/++
- Remove directory and all of its content and subdirectories,
- recursively.
-
- Throws:
- $(D FileException) if there is an error (including if the given
- file is not a directory).
- +/
-void rmdirRecurse(ref DirEntry de)
+/// ditto
+void rmdirRecurse(ref DirEntry de) @safe
{
if (!de.isDir)
throw new FileException(de.name, "Not a directory");
@@ -3666,11 +4479,16 @@ void rmdirRecurse(ref DirEntry de)
}
else
{
- // all children, recursively depth-first
- foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false))
- {
- attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name);
- }
+ // dirEntries is @system because it uses a DirIterator with a
+ // RefCounted variable, but here, no references to the payload is
+ // escaped to the outside, so this should be @trusted
+ () @trusted {
+ // all children, recursively depth-first
+ foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false))
+ {
+ attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name);
+ }
+ }();
// the dir itself
rmdir(de.name);
@@ -3682,11 +4500,26 @@ void rmdirRecurse(ref DirEntry de)
//"rmdirRecurse(in char[] pathname)" implementation. That is needlessly
//expensive.
//A DirEntry is a bit big (72B), so keeping the "by ref" signature is desirable.
-void rmdirRecurse(DirEntry de)
+void rmdirRecurse(DirEntry de) @safe
{
rmdirRecurse(de);
}
+///
+@system unittest
+{
+ import std.path : buildPath;
+
+ auto dir = deleteme.buildPath("a", "b", "c");
+
+ dir.mkdirRecurse;
+ assert(dir.exists);
+
+ deleteme.rmdirRecurse;
+ assert(!dir.exists);
+ assert(!deleteme.exists);
+}
+
version (Windows) @system unittest
{
import std.exception : enforce;
@@ -3699,7 +4532,7 @@ version (Windows) @system unittest
version (Posix) @system unittest
{
import std.exception : enforce, collectException;
- import std.process : executeShell;
+
collectException(rmdirRecurse(deleteme));
auto d = deleteme~"/a/b/c/d/e/f/g";
enforce(collectException(mkdir(d)));
@@ -3713,9 +4546,8 @@ version (Posix) @system unittest
d = deleteme~"/a/b/c/d/e/f/g";
mkdirRecurse(d);
- version (Android) string link_cmd = "ln -s ";
- else string link_cmd = "ln -sf ";
- executeShell(link_cmd~deleteme~"/a/b/c "~deleteme~"/link");
+ const linkTarget = deleteme ~ "/link";
+ symlink(deleteme ~ "/a/b/c", linkTarget);
rmdirRecurse(deleteme);
enforce(!exists(deleteme));
}
@@ -3761,7 +4593,7 @@ enum SpanMode
$(B pre)-order), i.e. the content of any subdirectory is spanned
right after that subdirectory itself.
- Note that $(D SpanMode.breadth) will not result in all directory
+ Note that `SpanMode.breadth` will not result in all directory
members occurring before any subdirectory members, i.e. it is not
_true
$(HTTPS en.wikipedia.org/wiki/Tree_traversal#Breadth-first_search,
@@ -3770,9 +4602,39 @@ enum SpanMode
breadth,
}
+///
+@system unittest
+{
+ import std.algorithm.comparison : equal;
+ import std.algorithm.iteration : map;
+ import std.algorithm.sorting : sort;
+ import std.array : array;
+ import std.path : buildPath, relativePath;
+
+ auto root = deleteme ~ "root";
+ scope(exit) root.rmdirRecurse;
+ root.mkdir;
+
+ root.buildPath("animals").mkdir;
+ root.buildPath("animals", "cat").mkdir;
+
+ alias removeRoot = (return scope e) => e.relativePath(root);
+
+ assert(root.dirEntries(SpanMode.depth).map!removeRoot.equal(
+ [buildPath("animals", "cat"), "animals"]));
+
+ assert(root.dirEntries(SpanMode.breadth).map!removeRoot.equal(
+ ["animals", buildPath("animals", "cat")]));
+
+ root.buildPath("plants").mkdir;
+
+ assert(root.dirEntries(SpanMode.shallow).array.sort.map!removeRoot.equal(
+ ["animals", "plants"]));
+}
+
private struct DirIteratorImpl
{
- import std.array : Appender, appender;
+ @safe:
SpanMode _mode;
// Whether we should follow symlinked directories while iterating.
// It also indicates whether we should avoid functions which call
@@ -3780,82 +4642,95 @@ private struct DirIteratorImpl
// be more efficient to not call stat in addition to lstat).
bool _followSymlink;
DirEntry _cur;
- Appender!(DirHandle[]) _stack;
- Appender!(DirEntry[]) _stashed; //used in depth first mode
+ DirHandle[] _stack;
+ DirEntry[] _stashed; //used in depth first mode
+
//stack helpers
- void pushExtra(DirEntry de){ _stashed.put(de); }
+ void pushExtra(DirEntry de)
+ {
+ _stashed ~= de;
+ }
+
//ditto
- bool hasExtra(){ return !_stashed.data.empty; }
+ bool hasExtra()
+ {
+ return _stashed.length != 0;
+ }
+
//ditto
DirEntry popExtra()
{
DirEntry de;
- de = _stashed.data[$-1];
- _stashed.shrinkTo(_stashed.data.length - 1);
+ de = _stashed[$-1];
+ _stashed.popBack();
return de;
-
}
+
version (Windows)
{
+ WIN32_FIND_DATAW _findinfo;
struct DirHandle
{
string dirpath;
HANDLE h;
}
- bool stepIn(string directory)
+ bool stepIn(string directory) @safe
{
import std.path : chainPath;
+ auto searchPattern = chainPath(directory, "*.*");
- auto search_pattern = chainPath(directory, "*.*");
- WIN32_FIND_DATAW findinfo;
- HANDLE h = FindFirstFileW(search_pattern.tempCString!FSChar(), &findinfo);
+ static auto trustedFindFirstFileW(typeof(searchPattern) pattern, WIN32_FIND_DATAW* findinfo) @trusted
+ {
+ return FindFirstFileW(pattern.tempCString!FSChar(), findinfo);
+ }
+
+ HANDLE h = trustedFindFirstFileW(searchPattern, &_findinfo);
cenforce(h != INVALID_HANDLE_VALUE, directory);
- _stack.put(DirHandle(directory, h));
- return toNext(false, &findinfo);
+ _stack ~= DirHandle(directory, h);
+ return toNext(false, &_findinfo);
}
bool next()
{
- if (_stack.data.empty)
+ if (_stack.length == 0)
return false;
- WIN32_FIND_DATAW findinfo;
- return toNext(true, &findinfo);
+ return toNext(true, &_findinfo);
}
- bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo)
+ bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo) @trusted
{
import core.stdc.wchar_ : wcscmp;
if (fetch)
{
- if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE)
+ if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE)
{
popDirStack();
return false;
}
}
- while ( wcscmp(findinfo.cFileName.ptr, ".") == 0
- || wcscmp(findinfo.cFileName.ptr, "..") == 0)
- if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE)
+ while (wcscmp(&findinfo.cFileName[0], ".") == 0 ||
+ wcscmp(&findinfo.cFileName[0], "..") == 0)
+ if (FindNextFileW(_stack[$-1].h, findinfo) == FALSE)
{
popDirStack();
return false;
}
- _cur = DirEntry(_stack.data[$-1].dirpath, findinfo);
+ _cur = DirEntry(_stack[$-1].dirpath, findinfo);
return true;
}
- void popDirStack()
+ void popDirStack() @trusted
{
- assert(!_stack.data.empty);
- FindClose(_stack.data[$-1].h);
- _stack.shrinkTo(_stack.data.length-1);
+ assert(_stack.length != 0);
+ FindClose(_stack[$-1].h);
+ _stack.popBack();
}
- void releaseDirStack()
+ void releaseDirStack() @trusted
{
- foreach ( d; _stack.data)
+ foreach (d; _stack)
FindClose(d.h);
}
@@ -3874,40 +4749,47 @@ private struct DirIteratorImpl
bool stepIn(string directory)
{
- auto h = directory.length ? opendir(directory.tempCString()) : opendir(".");
+ static auto trustedOpendir(string dir) @trusted
+ {
+ return opendir(dir.tempCString());
+ }
+
+ auto h = directory.length ? trustedOpendir(directory) : trustedOpendir(".");
cenforce(h, directory);
- _stack.put(DirHandle(directory, h));
+ _stack ~= (DirHandle(directory, h));
return next();
}
- bool next()
+ bool next() @trusted
{
- if (_stack.data.empty)
+ if (_stack.length == 0)
return false;
- for (dirent* fdata; (fdata = readdir(_stack.data[$-1].h)) != null; )
+
+ for (dirent* fdata; (fdata = readdir(_stack[$-1].h)) != null; )
{
// Skip "." and ".."
- if (core.stdc.string.strcmp(fdata.d_name.ptr, ".") &&
- core.stdc.string.strcmp(fdata.d_name.ptr, "..") )
+ if (core.stdc.string.strcmp(&fdata.d_name[0], ".") &&
+ core.stdc.string.strcmp(&fdata.d_name[0], ".."))
{
- _cur = DirEntry(_stack.data[$-1].dirpath, fdata);
+ _cur = DirEntry(_stack[$-1].dirpath, fdata);
return true;
}
}
+
popDirStack();
return false;
}
- void popDirStack()
+ void popDirStack() @trusted
{
- assert(!_stack.data.empty);
- closedir(_stack.data[$-1].h);
- _stack.shrinkTo(_stack.data.length-1);
+ assert(_stack.length != 0);
+ closedir(_stack[$-1].h);
+ _stack.popBack();
}
- void releaseDirStack()
+ void releaseDirStack() @trusted
{
- foreach ( d; _stack.data)
+ foreach (d; _stack)
closedir(d.h);
}
@@ -3922,11 +4804,8 @@ private struct DirIteratorImpl
{
_mode = mode;
_followSymlink = followSymlink;
- _stack = appender(cast(DirHandle[])[]);
- if (_mode == SpanMode.depth)
- _stashed = appender(cast(DirEntry[])[]);
- static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
+ static if (isNarrowString!R && is(immutable ElementEncodingType!R == immutable char))
alias pathnameStr = pathname;
else
{
@@ -3948,8 +4827,17 @@ private struct DirIteratorImpl
}
}
}
- @property bool empty(){ return _stashed.data.empty && _stack.data.empty; }
- @property DirEntry front(){ return _cur; }
+
+ @property bool empty()
+ {
+ return _stashed.length == 0 && _stack.length == 0;
+ }
+
+ @property DirEntry front()
+ {
+ return _cur;
+ }
+
void popFront()
{
switch (_mode)
@@ -3993,25 +4881,29 @@ private struct DirIteratorImpl
struct DirIterator
{
+@safe:
private:
RefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl;
- this(string pathname, SpanMode mode, bool followSymlink)
+ this(string pathname, SpanMode mode, bool followSymlink) @trusted
{
impl = typeof(impl)(pathname, mode, followSymlink);
}
public:
- @property bool empty(){ return impl.empty; }
- @property DirEntry front(){ return impl.front; }
- void popFront(){ impl.popFront(); }
-
+ @property bool empty() { return impl.empty; }
+ @property DirEntry front() { return impl.front; }
+ void popFront() { impl.popFront(); }
}
/++
- Returns an input range of $(D DirEntry) that lazily iterates a given directory,
+ Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
+ of `DirEntry` that lazily iterates a given directory,
also provides two ways of foreach iteration. The iteration variable can be of
- type $(D string) if only the name is needed, or $(D DirEntry)
+ type `string` if only the name is needed, or `DirEntry`
if additional details are needed. The span _mode dictates how the
directory is traversed. The name of each iterated directory entry
- contains the absolute _path.
+ contains the absolute or relative _path (depending on _pathname).
+
+ Note: The order of returned directory entries is as it is provided by the
+ operating system / filesystem, and may not follow any particular sorting.
Params:
path = The directory to iterate over.
@@ -4024,7 +4916,7 @@ public:
std,_path).
mode = Whether the directory's sub-directories should be
- iterated in depth-first port-order ($(LREF depth)),
+ iterated in depth-first post-order ($(LREF depth)),
depth-first pre-order ($(LREF breadth)), or not at all
($(LREF shallow)).
@@ -4032,8 +4924,12 @@ public:
should be treated as directories and their contents
iterated over.
+ Returns:
+ An $(REF_ALTTEXT input range, isInputRange,std,range,primitives) of
+ $(LREF DirEntry).
+
Throws:
- $(D FileException) if the directory does not exist.
+ $(LREF FileException) if the directory does not exist.
Example:
--------------------
@@ -4065,7 +4961,7 @@ foreach (d; parallel(dFiles, 1)) //passes by 1 file to each thread
{
string cmd = "dmd -c " ~ d.name;
writeln(cmd);
- std.process.system(cmd);
+ std.process.executeShell(cmd);
}
// Iterate over all D source files in current directory and all its
@@ -4080,7 +4976,7 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
return DirIterator(path, mode, followSymlink);
}
-/// Duplicate functionality of D1's $(D std.file.listdir()):
+/// Duplicate functionality of D1's `std.file.listdir()`:
@safe unittest
{
string[] listdir(string pathname)
@@ -4092,7 +4988,7 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
return std.file.dirEntries(pathname, SpanMode.shallow)
.filter!(a => a.isFile)
- .map!(a => std.path.baseName(a.name))
+ .map!((return a) => std.path.baseName(a.name))
.array;
}
@@ -4122,7 +5018,7 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
// called from a shared library on Android,
// ie an apk
else
- string testdir = "deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID); // needs to be relative
+ string testdir = tempDir.buildPath("deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID));
mkdirRecurse(buildPath(testdir, "somedir"));
scope(exit) rmdirRecurse(testdir);
write(buildPath(testdir, "somefile"), null);
@@ -4135,7 +5031,7 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
auto len = enforce(walkLength(dirEntries(absolutePath(relpath), mode)));
assert(walkLength(dirEntries(relpath, mode)) == len);
assert(equal(
- map!(a => absolutePath(a.name))(dirEntries(relpath, mode)),
+ map!((return a) => absolutePath(a.name))(dirEntries(relpath, mode)),
map!(a => a.name)(dirEntries(absolutePath(relpath), mode))));
return len;
}
@@ -4156,7 +5052,7 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
assert(e.isFile || e.isDir, e.name);
}
- //issue 7264
+ // https://issues.dlang.org/show_bug.cgi?id=7264
foreach (string name; dirEntries(testdir, "*.d", SpanMode.breadth))
{
@@ -4165,14 +5061,14 @@ auto dirEntries(string path, SpanMode mode, bool followSymlink = true)
{
static assert(is(typeof(entry) == DirEntry));
}
- //issue 7138
+ // https://issues.dlang.org/show_bug.cgi?id=7138
auto a = array(dirEntries(testdir, SpanMode.shallow));
- // issue 11392
+ // https://issues.dlang.org/show_bug.cgi?id=11392
auto dFiles = dirEntries(testdir, SpanMode.shallow);
foreach (d; dFiles){}
- // issue 15146
+ // https://issues.dlang.org/show_bug.cgi?id=15146
dirEntries("", SpanMode.shallow).walkLength();
}
@@ -4263,6 +5159,40 @@ auto dirEntries(string path, string pattern, SpanMode mode,
}
}
+// Make sure that dirEntries does not butcher Unicode file names
+// https://issues.dlang.org/show_bug.cgi?id=17962
+@system unittest
+{
+ import std.algorithm.comparison : equal;
+ import std.algorithm.iteration : map;
+ import std.algorithm.sorting : sort;
+ import std.array : array;
+ import std.path : buildPath;
+ import std.uni : normalize;
+
+ // The Unicode normalization is required to make the tests pass on Mac OS X.
+ auto dir = deleteme ~ normalize("𐐷");
+ scope(exit) if (dir.exists) rmdirRecurse(dir);
+ mkdir(dir);
+ auto files = ["Hello World",
+ "Ma Chérie.jpeg",
+ "さいごの果実.txt"].map!(a => buildPath(dir, normalize(a)))().array();
+ sort(files);
+ foreach (file; files)
+ write(file, "nothing");
+
+ auto result = dirEntries(dir, SpanMode.shallow).map!((return a) => a.name.normalize()).array();
+ sort(result);
+
+ assert(equal(files, result));
+}
+
+// https://issues.dlang.org/show_bug.cgi?id=21250
+@system unittest
+{
+ import std.exception : assertThrown;
+ assertThrown!Exception(dirEntries("237f5babd6de21f40915826699582e36", "*.bin", SpanMode.depth));
+}
/**
* Reads a file line by line and parses the line into a single value or a
@@ -4287,13 +5217,14 @@ auto dirEntries(string path, string pattern, SpanMode mode,
* with extra characters are allowed.
*/
Select!(Types.length == 1, Types[0][], Tuple!(Types)[])
-slurp(Types...)(string filename, in char[] format)
+slurp(Types...)(string filename, scope const(char)[] format)
{
import std.array : appender;
import std.conv : text;
import std.exception : enforce;
- import std.format : formattedRead;
+ import std.format.read : formattedRead;
import std.stdio : File;
+ import std.string : stripRight;
auto app = appender!(typeof(return))();
ElementType!(typeof(return)) toAdd;
@@ -4302,7 +5233,7 @@ slurp(Types...)(string filename, in char[] format)
foreach (line; f.byLine())
{
formattedRead(line, format, &toAdd);
- enforce(line.empty,
+ enforce(line.stripRight("\r").empty,
text("Trailing characters at the end of line: `", line,
"'"));
app.put(toAdd);
@@ -4331,35 +5262,49 @@ slurp(Types...)(string filename, in char[] format)
assert(a[1] == tuple(345, 1.125));
}
+@system unittest
+{
+ import std.typecons : tuple;
-/**
-Returns the path to a directory for temporary files.
-
-On Windows, this function returns the result of calling the Windows API function
-$(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992.aspx, $(D GetTempPath)).
+ scope(exit)
+ {
+ assert(exists(deleteme));
+ remove(deleteme);
+ }
+ write(deleteme, "10\r\n20");
+ assert(slurp!(int)(deleteme, "%d") == [10, 20]);
+}
-On POSIX platforms, it searches through the following list of directories
-and returns the first one which is found to exist:
-$(OL
- $(LI The directory given by the $(D TMPDIR) environment variable.)
- $(LI The directory given by the $(D TEMP) environment variable.)
- $(LI The directory given by the $(D TMP) environment variable.)
- $(LI $(D /tmp))
- $(LI $(D /var/tmp))
- $(LI $(D /usr/tmp))
-)
-On all platforms, $(D tempDir) returns $(D ".") on failure, representing
-the current working directory.
+/**
+Returns the path to a directory for temporary files.
The return value of the function is cached, so the procedures described
-above will only be performed the first time the function is called. All
+below will only be performed the first time the function is called. All
subsequent runs will return the same string, regardless of whether
environment variables and directory structures have changed in the
meantime.
-The POSIX $(D tempDir) algorithm is inspired by Python's
-$(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, $(D tempfile.tempdir)).
+The POSIX `tempDir` algorithm is inspired by Python's
+$(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, `tempfile.tempdir`).
+
+Returns:
+ On Windows, this function returns the result of calling the Windows API function
+ $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992.aspx, `GetTempPath`).
+
+ On POSIX platforms, it searches through the following list of directories
+ and returns the first one which is found to exist:
+ $(OL
+ $(LI The directory given by the `TMPDIR` environment variable.)
+ $(LI The directory given by the `TEMP` environment variable.)
+ $(LI The directory given by the `TMP` environment variable.)
+ $(LI `/tmp`)
+ $(LI `/var/tmp`)
+ $(LI `/usr/tmp`)
+ )
+
+ On all platforms, `tempDir` returns `"."` on failure, representing
+ the current working directory.
*/
string tempDir() @trusted
{
@@ -4399,3 +5344,92 @@ string tempDir() @trusted
}
return cache;
}
+
+///
+@safe unittest
+{
+ import std.ascii : letters;
+ import std.conv : to;
+ import std.path : buildPath;
+ import std.random : randomSample;
+ import std.utf : byCodeUnit;
+
+ // random id with 20 letters
+ auto id = letters.byCodeUnit.randomSample(20).to!string;
+ auto myFile = tempDir.buildPath(id ~ "my_tmp_file");
+ scope(exit) myFile.remove;
+
+ myFile.write("hello");
+ assert(myFile.readText == "hello");
+}
+
+/**
+Returns the available disk space based on a given path.
+On Windows, `path` must be a directory; on POSIX systems, it can be a file or directory.
+
+Params:
+ path = on Windows, it must be a directory; on POSIX it can be a file or directory
+Returns:
+ Available space in bytes
+
+Throws:
+ $(LREF FileException) in case of failure
+*/
+ulong getAvailableDiskSpace(scope const(char)[] path) @safe
+{
+ version (Windows)
+ {
+ import core.sys.windows.winbase : GetDiskFreeSpaceExW;
+ import core.sys.windows.winnt : ULARGE_INTEGER;
+ import std.internal.cstring : tempCStringW;
+
+ ULARGE_INTEGER freeBytesAvailable;
+ auto err = () @trusted {
+ return GetDiskFreeSpaceExW(path.tempCStringW(), &freeBytesAvailable, null, null);
+ } ();
+ cenforce(err != 0, "Cannot get available disk space");
+
+ return freeBytesAvailable.QuadPart;
+ }
+ else version (Posix)
+ {
+ import std.internal.cstring : tempCString;
+
+ version (FreeBSD)
+ {
+ import core.sys.freebsd.sys.mount : statfs, statfs_t;
+
+ statfs_t stats;
+ auto err = () @trusted {
+ return statfs(path.tempCString(), &stats);
+ } ();
+ cenforce(err == 0, "Cannot get available disk space");
+
+ return stats.f_bavail * stats.f_bsize;
+ }
+ else
+ {
+ import core.sys.posix.sys.statvfs : statvfs, statvfs_t;
+
+ statvfs_t stats;
+ auto err = () @trusted {
+ return statvfs(path.tempCString(), &stats);
+ } ();
+ cenforce(err == 0, "Cannot get available disk space");
+
+ return stats.f_bavail * stats.f_frsize;
+ }
+ }
+ else static assert(0, "Unsupported platform");
+}
+
+///
+@safe unittest
+{
+ import std.exception : assertThrown;
+
+ auto space = getAvailableDiskSpace(".");
+ assert(space > 0);
+
+ assertThrown!FileException(getAvailableDiskSpace("ThisFileDoesNotExist123123"));
+}