-
Notifications
You must be signed in to change notification settings - Fork 58
/
walker.ts
136 lines (119 loc) · 5.04 KB
/
walker.ts
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
import { resolve as pathResolve } from "path";
import { cleanPath, convertSlashes } 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 { Dirent } from "fs";
import { Output } from "../types";
import { Counter } from "./counter";
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.state = {
// 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)
),
};
this.root = this.normalizePath(root);
/*
* 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.walkDirectory(
this.state,
this.root,
this.state.options.maxDepth,
this.walk
);
return this.isSynchronous ? this.callbackInvoker(this.state, null) : null;
}
private normalizePath(path: string) {
const { resolvePaths, normalizePath, pathSeparator } = this.state.options;
const pathNeedsCleaning =
(process.platform === "win32" && path.includes("/")) ||
path.startsWith(".");
if (resolvePaths) path = pathResolve(path);
if (normalizePath || pathNeedsCleaning) path = cleanPath(path);
if (path === ".") return "";
const needsSeperator = path[path.length - 1] !== pathSeparator;
return convertSlashes(
needsSeperator ? path + pathSeparator : path,
pathSeparator
);
}
private walk = (entries: Dirent[], directoryPath: string, depth: number) => {
const {
paths,
options: { filters, resolveSymlinks, exclude, maxFiles, signal },
} = this.state;
if ((signal && signal.aborted) || (maxFiles && paths.length > maxFiles))
return;
this.pushDirectory(directoryPath, paths, filters);
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)) {
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.walkDirectory(this.state, path, depth - 1, this.walk);
} else if (entry.isSymbolicLink() && resolveSymlinks) {
let path = this.joinPath(entry.name, directoryPath);
this.resolveSymlink!(path, this.state, (stat, resolvedPath) => {
if (stat.isDirectory()) {
resolvedPath = this.normalizePath(resolvedPath);
if (exclude && exclude(entry.name, resolvedPath)) return;
this.walkDirectory(this.state, resolvedPath, depth - 1, this.walk);
} else {
this.pushFile(resolvedPath, files, this.state.counts, filters);
}
});
}
}
this.groupFiles(this.state.groups, directoryPath, files);
};
}