codekingpro/portable-devtools
114k
1import { Minimatch } from 'minimatch';
2import { fileURLToPath } from 'node:url';
3import { PathScurry, PathScurryDarwin, PathScurryPosix, PathScurryWin32, } from 'path-scurry';
4import { Pattern } from './pattern.js';
5import { GlobStream, GlobWalker } from './walker.js';
6// if no process global, just call it linux.
7// so we default to case-sensitive, / separators
8const defaultPlatform = (typeof process === 'object' &&
9 process &&
10 typeof process.platform === 'string') ?
11 process.platform
12 : 'linux';
13/**
14 * An object that can perform glob pattern traversals.
15 */
16export class Glob {
17 absolute;
18 cwd;
19 root;
20 dot;
21 dotRelative;
22 follow;
23 ignore;
24 magicalBraces;
25 mark;
26 matchBase;
27 maxDepth;
28 nobrace;
29 nocase;
30 nodir;
31 noext;
32 noglobstar;
33 pattern;
34 platform;
35 realpath;
36 scurry;
37 stat;
38 signal;
39 windowsPathsNoEscape;
40 withFileTypes;
41 includeChildMatches;
42 /**
43 * The options provided to the constructor.
44 */
45 opts;
46 /**
47 * An array of parsed immutable {@link Pattern} objects.
48 */
49 patterns;
50 /**
51 * All options are stored as properties on the `Glob` object.
52 *
53 * See {@link GlobOptions} for full options descriptions.
54 *
55 * Note that a previous `Glob` object can be passed as the
56 * `GlobOptions` to another `Glob` instantiation to re-use settings
57 * and caches with a new pattern.
58 *
59 * Traversal functions can be called multiple times to run the walk
60 * again.
61 */
62 constructor(pattern, opts) {
63 /* c8 ignore start */
64 if (!opts)
65 throw new TypeError('glob options required');
66 /* c8 ignore stop */
67 this.withFileTypes = !!opts.withFileTypes;
68 this.signal = opts.signal;
69 this.follow = !!opts.follow;
70 this.dot = !!opts.dot;
71 this.dotRelative = !!opts.dotRelative;
72 this.nodir = !!opts.nodir;
73 this.mark = !!opts.mark;
74 if (!opts.cwd) {
75 this.cwd = '';
76 }
77 else if (opts.cwd instanceof URL || opts.cwd.startsWith('file://')) {
78 opts.cwd = fileURLToPath(opts.cwd);
79 }
80 this.cwd = opts.cwd || '';
81 this.root = opts.root;
82 this.magicalBraces = !!opts.magicalBraces;
83 this.nobrace = !!opts.nobrace;
84 this.noext = !!opts.noext;
85 this.realpath = !!opts.realpath;
86 this.absolute = opts.absolute;
87 this.includeChildMatches = opts.includeChildMatches !== false;
88 this.noglobstar = !!opts.noglobstar;
89 this.matchBase = !!opts.matchBase;
90 this.maxDepth =
91 typeof opts.maxDepth === 'number' ? opts.maxDepth : Infinity;
92 this.stat = !!opts.stat;
93 this.ignore = opts.ignore;
94 if (this.withFileTypes && this.absolute !== undefined) {
95 throw new Error('cannot set absolute and withFileTypes:true');
96 }
97 if (typeof pattern === 'string') {
98 pattern = [pattern];
99 }
100 this.windowsPathsNoEscape =
101 !!opts.windowsPathsNoEscape ||
102 opts.allowWindowsEscape ===
103 false;
104 if (this.windowsPathsNoEscape) {
105 pattern = pattern.map(p => p.replace(/\\/g, '/'));
106 }
107 if (this.matchBase) {
108 if (opts.noglobstar) {
109 throw new TypeError('base matching requires globstar');
110 }
111 pattern = pattern.map(p => (p.includes('/') ? p : `./**/${p}`));
112 }
113 this.pattern = pattern;
114 this.platform = opts.platform || defaultPlatform;
115 this.opts = { ...opts, platform: this.platform };
116 if (opts.scurry) {
117 this.scurry = opts.scurry;
118 if (opts.nocase !== undefined &&
119 opts.nocase !== opts.scurry.nocase) {
120 throw new Error('nocase option contradicts provided scurry option');
121 }
122 }
123 else {
124 const Scurry = opts.platform === 'win32' ? PathScurryWin32
125 : opts.platform === 'darwin' ? PathScurryDarwin
126 : opts.platform ? PathScurryPosix
127 : PathScurry;
128 this.scurry = new Scurry(this.cwd, {
129 nocase: opts.nocase,
130 fs: opts.fs,
131 });
132 }
133 this.nocase = this.scurry.nocase;
134 // If you do nocase:true on a case-sensitive file system, then
135 // we need to use regexps instead of strings for non-magic
136 // path portions, because statting `aBc` won't return results
137 // for the file `AbC` for example.
138 const nocaseMagicOnly = this.platform === 'darwin' || this.platform === 'win32';
139 const mmo = {
140 braceExpandMax: 10_000,
141 ...opts,
142 dot: this.dot,
143 matchBase: this.matchBase,
144 nobrace: this.nobrace,
145 // default nocase based on platform
146 nocase: this.nocase,
147 nocaseMagicOnly,
148 nocomment: true,
149 noext: this.noext,
150 nonegate: true,
151 optimizationLevel: 2,
152 platform: this.platform,
153 windowsPathsNoEscape: this.windowsPathsNoEscape,
154 debug: !!this.opts.debug,
155 };
156 const mms = this.pattern.map(p => new Minimatch(p, mmo));
157 const [matchSet, globParts] = mms.reduce((set, m) => {
158 set[0].push(...m.set);
159 set[1].push(...m.globParts);
160 return set;
161 }, [[], []]);
162 this.patterns = matchSet.map((set, i) => {
163 const g = globParts[i];
164 /* c8 ignore start */
165 if (!g)
166 throw new Error('invalid pattern object');
167 /* c8 ignore stop */
168 return new Pattern(set, g, 0, this.platform);
169 });
170 }
171 async walk() {
172 // Walkers always return array of Path objects, so we just have to
173 // coerce them into the right shape. It will have already called
174 // realpath() if the option was set to do so, so we know that's cached.
175 // start out knowing the cwd, at least
176 return [
177 ...(await new GlobWalker(this.patterns, this.scurry.cwd, {
178 ...this.opts,
179 maxDepth: this.maxDepth !== Infinity ?
180 this.maxDepth + this.scurry.cwd.depth()
181 : Infinity,
182 platform: this.platform,
183 nocase: this.nocase,
184 includeChildMatches: this.includeChildMatches,
185 }).walk()),
186 ];
187 }
188 walkSync() {
189 return [
190 ...new GlobWalker(this.patterns, this.scurry.cwd, {
191 ...this.opts,
192 maxDepth: this.maxDepth !== Infinity ?
193 this.maxDepth + this.scurry.cwd.depth()
194 : Infinity,
195 platform: this.platform,
196 nocase: this.nocase,
197 includeChildMatches: this.includeChildMatches,
198 }).walkSync(),
199 ];
200 }
201 stream() {
202 return new GlobStream(this.patterns, this.scurry.cwd, {
203 ...this.opts,
204 maxDepth: this.maxDepth !== Infinity ?
205 this.maxDepth + this.scurry.cwd.depth()
206 : Infinity,
207 platform: this.platform,
208 nocase: this.nocase,
209 includeChildMatches: this.includeChildMatches,
210 }).stream();
211 }
212 streamSync() {
213 return new GlobStream(this.patterns, this.scurry.cwd, {
214 ...this.opts,
215 maxDepth: this.maxDepth !== Infinity ?
216 this.maxDepth + this.scurry.cwd.depth()
217 : Infinity,
218 platform: this.platform,
219 nocase: this.nocase,
220 includeChildMatches: this.includeChildMatches,
221 }).streamSync();
222 }
223 /**
224 * Default sync iteration function. Returns a Generator that
225 * iterates over the results.
226 */
227 iterateSync() {
228 return this.streamSync()[Symbol.iterator]();
229 }
230 [Symbol.iterator]() {
231 return this.iterateSync();
232 }
233 /**
234 * Default async iteration function. Returns an AsyncGenerator that
235 * iterates over the results.
236 */
237 iterate() {
238 return this.stream()[Symbol.asyncIterator]();
239 }
240 [Symbol.asyncIterator]() {
241 return this.iterate();
242 }
243}
244//# sourceMappingURL=glob.js.map