in lib/src/ignore.dart [220:300]
static List<String> listFiles({
String beneath = '',
required Iterable<String> Function(String) listDir,
required Ignore? Function(String) ignoreForDir,
required bool Function(String) isDir,
bool includeDirs = false,
}) {
if (beneath.startsWith('/') ||
beneath.startsWith('./') ||
beneath.startsWith('../')) {
throw ArgumentError.value(
'must be relative and normalized', 'beneath', beneath);
}
if (beneath.endsWith('/')) {
throw ArgumentError.value('must not end with /', beneath);
}
// To streamline the algorithm we represent all paths as starting with '/'
// and the empty path as just '/'.
if (beneath == '.') beneath = '';
beneath = '/$beneath';
// Will contain all the files that are not ignored.
final result = <String>[];
// At any given point in the search, this will contain the Ignores from
// directories leading up to the current entity.
// The single `null` aligns popping and pushing in this stack with [toVisit]
// below.
final ignoreStack = <_IgnorePrefixPair?>[null];
// Find all ignores between './' and [beneath] (not inclusive).
// [index] points at the next '/' in the path.
var index = -1;
while ((index = beneath.indexOf('/', index + 1)) != -1) {
final partial = beneath.substring(0, index + 1);
if (_matchesStack(ignoreStack, partial)) {
// A directory on the way towards [beneath] was ignored. Empty result.
return <String>[];
}
final ignore = ignoreForDir(
partial == '/' ? '.' : partial.substring(1, partial.length - 1));
ignoreStack
.add(ignore == null ? null : _IgnorePrefixPair(ignore, partial));
}
// Do a depth first tree-search starting at [beneath].
// toVisit is a stack containing all items that are waiting to be processed.
final toVisit = [
[beneath]
];
while (toVisit.isNotEmpty) {
final topOfStack = toVisit.last;
if (topOfStack.isEmpty) {
toVisit.removeLast();
ignoreStack.removeLast();
continue;
}
final current = topOfStack.removeLast();
// This is the version of current we present to the callbacks and in
// [result].
//
// The empty path is represented as '.' and there is no leading '/'.
final normalizedCurrent = current == '/' ? '.' : current.substring(1);
final currentIsDir = isDir(normalizedCurrent);
if (_matchesStack(ignoreStack, currentIsDir ? '$current/' : current)) {
// current was ignored. Continue with the next item.
continue;
}
if (currentIsDir) {
final ignore = ignoreForDir(normalizedCurrent);
ignoreStack
.add(ignore == null ? null : _IgnorePrefixPair(ignore, current));
// Put all entities in current on the stack to be processed.
toVisit.add(listDir(normalizedCurrent).map((x) => '/$x').toList());
if (includeDirs) {
result.add(normalizedCurrent);
}
} else {
result.add(normalizedCurrent);
}
}
return result;
}