Skip to content

Commit 84fa21c

Browse files
committed
Fix iteration of nested directory trees
1 parent ef0be36 commit 84fa21c

File tree

1 file changed

+121
-41
lines changed

1 file changed

+121
-41
lines changed

std/file.d

Lines changed: 121 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4979,13 +4979,17 @@ alias DirIterator = _DirIterator!dip1000Enabled;
49794979
operating system / filesystem, and may not follow any particular sorting.
49804980
49814981
Params:
4982+
Path = Type of the directory path.
4983+
Can be either a `string` or a `DirEntry`.
4984+
49824985
useDIP1000 = used to instantiate this function separately for code with
49834986
and without -preview=dip1000 compiler switch, because it
49844987
affects the ABI of this function. Set automatically -
49854988
don't touch.
49864989
49874990
path = The directory to iterate over.
4988-
If empty, the current directory will be iterated.
4991+
If an empty string (or data that implicitly converts to one) is
4992+
provided, the current directory will be iterated.
49894993
49904994
pattern = Optional string with wildcards, such as $(RED
49914995
"*.d"). When present, it is used to filter the
@@ -5073,8 +5077,11 @@ scan("");
50735077

50745078
// For some reason, doing the same alias-to-a-template trick as with DirIterator
50755079
// does not work here.
5076-
auto dirEntries(bool useDIP1000 = dip1000Enabled)
5077-
(string path, SpanMode mode, bool followSymlink = true)
5080+
// The template constraint is necessary to prevent this overload from matching
5081+
// `DirEntry`. Said type has an `alias this` member of type `string`.
5082+
auto dirEntries(Path, bool useDIP1000 = dip1000Enabled)
5083+
(const Path path, SpanMode mode, bool followSymlink = true)
5084+
if (is(Path == string))
50785085
{
50795086
return _DirIterator!useDIP1000(path, mode, followSymlink);
50805087
}
@@ -5175,46 +5182,11 @@ auto dirEntries(bool useDIP1000 = dip1000Enabled)
51755182
dirEntries("", SpanMode.shallow).walkLength();
51765183
}
51775184

5178-
// https://github.com/dlang/phobos/issues/9584
5179-
@safe unittest
5180-
{
5181-
import std.path : absolutePath, buildPath;
5182-
5183-
string root = deleteme();
5184-
mkdirRecurse(root);
5185-
scope (exit) rmdirRecurse(root);
5186-
5187-
mkdirRecurse(root.buildPath("1", "2"));
5188-
mkdirRecurse(root.buildPath("3", "4"));
5189-
mkdirRecurse(root.buildPath("3", "5", "6"));
5190-
5191-
const origWD = getcwd();
5192-
chdir(root);
5193-
scope(exit) chdir(origWD);
5194-
5195-
/*
5196-
This wouldn't work if `entry` were a `string` – for obvious reasons:
5197-
One cannot (reliably) iterate nested directory trees using relative path strings
5198-
while changing directories in between.
5199-
5200-
The expected error would be something along the lines of:
5201-
> Failed to stat file `./3/5': No such file or directory
5202-
5203-
See <https://github.com/dlang/phobos/issues/9584> for further details.
5204-
*/
5205-
foreach (DirEntry entry; ".".dirEntries(SpanMode.shallow))
5206-
{
5207-
if (entry.isDir)
5208-
foreach (DirEntry subEntry; entry.dirEntries(SpanMode.shallow))
5209-
if (subEntry.isDir)
5210-
chdir(subEntry.absolutePath); //
5211-
}
5212-
}
5213-
52145185
/// Ditto
5215-
auto dirEntries(bool useDIP1000 = dip1000Enabled)
5216-
(string path, string pattern, SpanMode mode,
5186+
auto dirEntries(Path, bool useDIP1000 = dip1000Enabled)
5187+
(const Path path, string pattern, SpanMode mode,
52175188
bool followSymlink = true)
5189+
if (is(Path == string)) // necessary, see comment on previous overload for details
52185190
{
52195191
import std.algorithm.iteration : filter;
52205192
import std.path : globMatch, baseName;
@@ -5334,6 +5306,114 @@ auto dirEntries(bool useDIP1000 = dip1000Enabled)
53345306
assertThrown!Exception(dirEntries("237f5babd6de21f40915826699582e36", "*.bin", SpanMode.depth));
53355307
}
53365308

5309+
@safe unittest
5310+
{
5311+
// This is why all the template constraints on `dirEntries` are necessary.
5312+
static assert(isImplicitlyConvertible!(DirEntry, string));
5313+
}
5314+
5315+
/// Ditto
5316+
auto dirEntries(Path, bool useDIP1000 = dip1000Enabled)
5317+
(Path path, SpanMode mode, bool followSymlink = true)
5318+
if (isImplicitlyConvertible!(Path, string) && !is(Path == string) && !is(Path == DirEntry))
5319+
{
5320+
return dirEntries!(string, useDIP1000)(path, mode, followSymlink);
5321+
}
5322+
5323+
/// Ditto
5324+
auto dirEntries(Path, bool useDIP1000 = dip1000Enabled)
5325+
(Path path, string pattern, SpanMode mode,
5326+
bool followSymlink = true)
5327+
if (isImplicitlyConvertible!(Path, string) && !is(Path == string) && !is(Path == DirEntry))
5328+
{
5329+
return dirEntries!(string, useDIP1000)(
5330+
path, pattern, mode,
5331+
followSymlink
5332+
);
5333+
}
5334+
5335+
@safe unittest
5336+
{
5337+
static struct Wrapper
5338+
{
5339+
string data;
5340+
alias data this;
5341+
}
5342+
5343+
string root = deleteme();
5344+
mkdirRecurse(root);
5345+
scope (exit) rmdirRecurse(root);
5346+
5347+
auto wrapped = Wrapper(root);
5348+
foreach(entry; dirEntries(wrapped, SpanMode.shallow)) {}
5349+
}
5350+
5351+
/// Ditto
5352+
auto dirEntries(Path, bool useDIP1000 = dip1000Enabled)
5353+
(Path path, SpanMode mode, bool followSymlink = true)
5354+
if (is(Path == DirEntry))
5355+
{
5356+
return dirEntries!(string, useDIP1000)(path.nameWithPrefix, mode, followSymlink);
5357+
}
5358+
5359+
/// Ditto
5360+
auto dirEntries(Path, bool useDIP1000 = dip1000Enabled)
5361+
(Path path, string pattern, SpanMode mode,
5362+
bool followSymlink = true)
5363+
if (is(Path == DirEntry))
5364+
{
5365+
return dirEntries!(string, useDIP1000)(
5366+
path.nameWithPrefix, pattern, mode,
5367+
followSymlink
5368+
);
5369+
}
5370+
5371+
// https://github.com/dlang/phobos/issues/9584
5372+
@safe unittest
5373+
{
5374+
import std.path : absolutePath, buildPath;
5375+
5376+
string root = deleteme();
5377+
mkdirRecurse(root);
5378+
scope (exit) rmdirRecurse(root);
5379+
5380+
mkdirRecurse(root.buildPath("1", "2"));
5381+
mkdirRecurse(root.buildPath("3", "4"));
5382+
mkdirRecurse(root.buildPath("3", "5", "6"));
5383+
5384+
const origWD = getcwd();
5385+
5386+
/*
5387+
This wouldn't work if `entry` were a `string` – for fair reasons:
5388+
One cannot (reliably) iterate nested directory trees using relative path strings
5389+
while changing directories in between.
5390+
5391+
The expected error would be something along the lines of:
5392+
> Failed to stat file `./3/5': No such file or directory
5393+
5394+
See <https://github.com/dlang/phobos/issues/9584> for further details.
5395+
*/
5396+
chdir(root);
5397+
scope(exit) chdir(origWD);
5398+
foreach (DirEntry entry; ".".dirEntries(SpanMode.shallow))
5399+
{
5400+
if (entry.isDir)
5401+
foreach (DirEntry subEntry; entry.dirEntries(SpanMode.shallow))
5402+
if (subEntry.isDir)
5403+
chdir(subEntry.absolutePath); //
5404+
}
5405+
5406+
chdir(root);
5407+
scope(exit) chdir(origWD);
5408+
foreach (DirEntry entry; ".".dirEntries("*", SpanMode.shallow))
5409+
{
5410+
if (entry.isDir)
5411+
foreach (DirEntry subEntry; entry.dirEntries("*", SpanMode.shallow))
5412+
if (subEntry.isDir)
5413+
chdir(subEntry.absolutePath); //
5414+
}
5415+
}
5416+
53375417
/**
53385418
* Reads a file line by line and parses the line into a single value or a
53395419
* $(REF Tuple, std,typecons) of values depending on the length of `Types`.

0 commit comments

Comments
 (0)