forked from thecodrr/fdir
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwalker.ts
More file actions
162 lines (151 loc) · 5.68 KB
/
walker.ts
File metadata and controls
162 lines (151 loc) · 5.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import { basename, dirname } from "path";
import { isRootDirectory, normalizePath } from "../utils";
import { ResultCallback, WalkerState, Options } from "../types";
import * as joinPath from "./functions/join-path";
import * as pushDirectory from "./functions/push-directory";
import * as pushFile from "./functions/push-file";
import * as getArray from "./functions/get-array";
import * as groupFiles from "./functions/group-files";
import * as resolveSymlink from "./functions/resolve-symlink";
import * as invokeCallback from "./functions/invoke-callback";
import * as walkDirectory from "./functions/walk-directory";
import { Queue } from "./queue";
import type { Dirent } from "fs";
import * as nativeFs from "fs";
import { Output } from "../types";
import { Counter } from "./counter";
import { Aborter } from "./aborter";
export class Walker<TOutput extends Output> {
private readonly root: string;
private readonly isSynchronous: boolean;
private readonly state: WalkerState;
private readonly joinPath: joinPath.JoinPathFunction;
private readonly pushDirectory: pushDirectory.PushDirectoryFunction;
private readonly pushFile: pushFile.PushFileFunction;
private readonly getArray: getArray.GetArrayFunction;
private readonly groupFiles: groupFiles.GroupFilesFunction;
private readonly resolveSymlink: resolveSymlink.ResolveSymlinkFunction | null;
private readonly walkDirectory: walkDirectory.WalkDirectoryFunction;
private readonly callbackInvoker: invokeCallback.InvokeCallbackFunction<TOutput>;
constructor(
root: string,
options: Options,
callback?: ResultCallback<TOutput>
) {
this.isSynchronous = !callback;
this.callbackInvoker = invokeCallback.build(options, this.isSynchronous);
this.root = normalizePath(root, options);
this.state = {
root: isRootDirectory(this.root) ? this.root : this.root.slice(0, -1),
// Perf: we explicitly tell the compiler to optimize for String arrays
paths: [""].slice(0, 0),
groups: [],
counts: new Counter(),
options,
queue: new Queue((error, state) =>
this.callbackInvoker(state, error, callback)
),
symlinks: new Map(),
visited: [""].slice(0, 0),
controller: new Aborter(),
fs: options.fs ?? nativeFs,
};
/*
* Perf: We conditionally change functions according to options. This gives a slight
* performance boost. Since these functions are so small, they are automatically inlined
* by the javascript engine so there's no function call overhead (in most cases).
*/
this.joinPath = joinPath.build(this.root, options);
this.pushDirectory = pushDirectory.build(this.root, options);
this.pushFile = pushFile.build(options);
this.getArray = getArray.build(options);
this.groupFiles = groupFiles.build(options);
this.resolveSymlink = resolveSymlink.build(options, this.isSynchronous);
this.walkDirectory = walkDirectory.build(this.isSynchronous);
}
start(): TOutput | null {
this.pushDirectory(this.root, this.state.paths, this.state.options.filters);
this.walkDirectory(
this.state,
this.root,
this.root,
this.state.options.maxDepth,
this.walk
);
return this.isSynchronous ? this.callbackInvoker(this.state, null) : null;
}
private walk = (entries: Dirent[], directoryPath: string, depth: number) => {
const {
paths,
options: {
filters,
resolveSymlinks,
excludeSymlinks,
exclude,
maxFiles,
signal,
useRealPaths,
pathSeparator,
},
controller,
} = this.state;
if (
controller.aborted ||
(signal && signal.aborted) ||
(maxFiles && paths.length > maxFiles)
)
return;
const files = this.getArray(this.state.paths);
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (
entry.isFile() ||
(entry.isSymbolicLink() && !resolveSymlinks && !excludeSymlinks)
) {
const filename = this.joinPath(entry.name, directoryPath);
this.pushFile(filename, files, this.state.counts, filters);
} else if (entry.isDirectory()) {
let path = joinPath.joinDirectoryPath(
entry.name,
directoryPath,
this.state.options.pathSeparator
);
if (exclude && exclude(entry.name, path)) continue;
this.pushDirectory(path, paths, filters);
this.walkDirectory(this.state, path, path, depth - 1, this.walk);
} else if (this.resolveSymlink && entry.isSymbolicLink()) {
let path = joinPath.joinPathWithBasePath(entry.name, directoryPath);
this.resolveSymlink(path, this.state, (stat, resolvedPath) => {
if (stat.isDirectory()) {
resolvedPath = normalizePath(resolvedPath, this.state.options);
if (
exclude &&
exclude(
entry.name,
useRealPaths ? resolvedPath : path + pathSeparator
)
)
return;
this.walkDirectory(
this.state,
resolvedPath,
useRealPaths ? resolvedPath : path + pathSeparator,
depth - 1,
this.walk
);
} else {
resolvedPath = useRealPaths ? resolvedPath : path;
const filename = basename(resolvedPath);
const directoryPath = normalizePath(
dirname(resolvedPath),
this.state.options
);
resolvedPath = this.joinPath(filename, directoryPath);
this.pushFile(resolvedPath, files, this.state.counts, filters);
}
});
}
}
this.groupFiles(this.state.groups, directoryPath, files);
};
}