Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81164 views
1
// Approach:
2
//
3
// 1. Get the minimatch set
4
// 2. For each pattern in the set, PROCESS(pattern, false)
5
// 3. Store matches per-set, then uniq them
6
//
7
// PROCESS(pattern, inGlobStar)
8
// Get the first [n] items from pattern that are all strings
9
// Join these together. This is PREFIX.
10
// If there is no more remaining, then stat(PREFIX) and
11
// add to matches if it succeeds. END.
12
//
13
// If inGlobStar and PREFIX is symlink and points to dir
14
// set ENTRIES = []
15
// else readdir(PREFIX) as ENTRIES
16
// If fail, END
17
//
18
// with ENTRIES
19
// If pattern[n] is GLOBSTAR
20
// // handle the case where the globstar match is empty
21
// // by pruning it out, and testing the resulting pattern
22
// PROCESS(pattern[0..n] + pattern[n+1 .. $], false)
23
// // handle other cases.
24
// for ENTRY in ENTRIES (not dotfiles)
25
// // attach globstar + tail onto the entry
26
// // Mark that this entry is a globstar match
27
// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true)
28
//
29
// else // not globstar
30
// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot)
31
// Test ENTRY against pattern[n]
32
// If fails, continue
33
// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $])
34
//
35
// Caveat:
36
// Cache all stats and readdirs results to minimize syscall. Since all
37
// we ever care about is existence and directory-ness, we can just keep
38
// `true` for files, and [children,...] for directories, or `false` for
39
// things that don't exist.
40
41
module.exports = glob
42
43
var fs = require("fs")
44
var minimatch = require("minimatch")
45
var Minimatch = minimatch.Minimatch
46
var inherits = require("inherits")
47
var EE = require("events").EventEmitter
48
var path = require("path")
49
var assert = require("assert")
50
var globSync = require("./sync.js")
51
var common = require("./common.js")
52
var alphasort = common.alphasort
53
var isAbsolute = common.isAbsolute
54
var setopts = common.setopts
55
var ownProp = common.ownProp
56
var inflight = require("inflight")
57
var util = require("util")
58
59
var once = require("once")
60
61
function glob (pattern, options, cb) {
62
if (typeof options === "function") cb = options, options = {}
63
if (!options) options = {}
64
65
if (options.sync) {
66
if (cb)
67
throw new TypeError('callback provided to sync glob')
68
return globSync(pattern, options)
69
}
70
71
return new Glob(pattern, options, cb)
72
}
73
74
glob.sync = globSync
75
var GlobSync = glob.GlobSync = globSync.GlobSync
76
77
// old api surface
78
glob.glob = glob
79
80
glob.hasMagic = function (pattern, options_) {
81
var options = util._extend({}, options_)
82
options.noprocess = true
83
84
var g = new Glob(pattern, options)
85
var set = g.minimatch.set
86
if (set.length > 1)
87
return true
88
89
for (var j = 0; j < set[0].length; j++) {
90
if (typeof set[0][j] !== 'string')
91
return true
92
}
93
94
return false
95
}
96
97
glob.Glob = Glob
98
inherits(Glob, EE)
99
function Glob (pattern, options, cb) {
100
if (typeof options === "function") {
101
cb = options
102
options = null
103
}
104
105
if (options && options.sync) {
106
if (cb)
107
throw new TypeError('callback provided to sync glob')
108
return new GlobSync(pattern, options)
109
}
110
111
if (!(this instanceof Glob))
112
return new Glob(pattern, options, cb)
113
114
setopts(this, pattern, options)
115
116
// process each pattern in the minimatch set
117
var n = this.minimatch.set.length
118
119
// The matches are stored as {<filename>: true,...} so that
120
// duplicates are automagically pruned.
121
// Later, we do an Object.keys() on these.
122
// Keep them as a list so we can fill in when nonull is set.
123
this.matches = new Array(n)
124
125
if (typeof cb === "function") {
126
cb = once(cb)
127
this.on("error", cb)
128
this.on("end", function (matches) {
129
cb(null, matches)
130
})
131
}
132
133
var self = this
134
var n = this.minimatch.set.length
135
this._processing = 0
136
this.matches = new Array(n)
137
138
this._emitQueue = []
139
this._processQueue = []
140
this.paused = false
141
142
if (this.noprocess)
143
return this
144
145
if (n === 0)
146
return done()
147
148
for (var i = 0; i < n; i ++) {
149
this._process(this.minimatch.set[i], i, false, done)
150
}
151
152
function done () {
153
--self._processing
154
if (self._processing <= 0)
155
self._finish()
156
}
157
}
158
159
Glob.prototype._finish = function () {
160
assert(this instanceof Glob)
161
if (this.aborted)
162
return
163
164
//console.error('FINISH', this.matches)
165
common.finish(this)
166
this.emit("end", this.found)
167
}
168
169
Glob.prototype._mark = function (p) {
170
return common.mark(this, p)
171
}
172
173
Glob.prototype._makeAbs = function (f) {
174
return common.makeAbs(this, f)
175
}
176
177
Glob.prototype.abort = function () {
178
this.aborted = true
179
this.emit("abort")
180
}
181
182
Glob.prototype.pause = function () {
183
if (!this.paused) {
184
this.paused = true
185
this.emit("pause")
186
}
187
}
188
189
Glob.prototype.resume = function () {
190
if (this.paused) {
191
this.emit("resume")
192
this.paused = false
193
if (this._emitQueue.length) {
194
var eq = this._emitQueue.slice(0)
195
this._emitQueue.length = 0
196
for (var i = 0; i < eq.length; i ++) {
197
var e = eq[i]
198
this._emitMatch(e[0], e[1])
199
}
200
}
201
if (this._processQueue.length) {
202
var pq = this._processQueue.slice(0)
203
this._processQueue.length = 0
204
for (var i = 0; i < pq.length; i ++) {
205
var p = pq[i]
206
this._processing--
207
this._process(p[0], p[1], p[2], p[3])
208
}
209
}
210
}
211
}
212
213
Glob.prototype._process = function (pattern, index, inGlobStar, cb) {
214
assert(this instanceof Glob)
215
assert(typeof cb === 'function')
216
217
if (this.aborted)
218
return
219
220
this._processing++
221
if (this.paused) {
222
this._processQueue.push([pattern, index, inGlobStar, cb])
223
return
224
}
225
226
//console.error("PROCESS %d", this._processing, pattern)
227
228
// Get the first [n] parts of pattern that are all strings.
229
var n = 0
230
while (typeof pattern[n] === "string") {
231
n ++
232
}
233
// now n is the index of the first one that is *not* a string.
234
235
// see if there's anything else
236
var prefix
237
switch (n) {
238
// if not, then this is rather simple
239
case pattern.length:
240
this._processSimple(pattern.join('/'), index, cb)
241
return
242
243
case 0:
244
// pattern *starts* with some non-trivial item.
245
// going to readdir(cwd), but not include the prefix in matches.
246
prefix = null
247
break
248
249
default:
250
// pattern has some string bits in the front.
251
// whatever it starts with, whether that's "absolute" like /foo/bar,
252
// or "relative" like "../baz"
253
prefix = pattern.slice(0, n).join("/")
254
break
255
}
256
257
var remain = pattern.slice(n)
258
259
// get the list of entries.
260
var read
261
if (prefix === null)
262
read = "."
263
else if (isAbsolute(prefix) || isAbsolute(pattern.join("/"))) {
264
if (!prefix || !isAbsolute(prefix))
265
prefix = "/" + prefix
266
read = prefix
267
} else
268
read = prefix
269
270
var abs = this._makeAbs(read)
271
272
var isGlobStar = remain[0] === minimatch.GLOBSTAR
273
if (isGlobStar)
274
this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb)
275
else
276
this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb)
277
}
278
279
280
Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) {
281
var self = this
282
this._readdir(abs, inGlobStar, function (er, entries) {
283
return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb)
284
})
285
}
286
287
Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) {
288
289
// if the abs isn't a dir, then nothing can match!
290
if (!entries)
291
return cb()
292
293
// It will only match dot entries if it starts with a dot, or if
294
// dot is set. Stuff like @(.foo|.bar) isn't allowed.
295
var pn = remain[0]
296
var negate = !!this.minimatch.negate
297
var rawGlob = pn._glob
298
var dotOk = this.dot || rawGlob.charAt(0) === "."
299
300
var matchedEntries = []
301
for (var i = 0; i < entries.length; i++) {
302
var e = entries[i]
303
if (e.charAt(0) !== "." || dotOk) {
304
var m
305
if (negate && !prefix) {
306
m = !e.match(pn)
307
} else {
308
m = e.match(pn)
309
}
310
if (m)
311
matchedEntries.push(e)
312
}
313
}
314
315
//console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries)
316
317
var len = matchedEntries.length
318
// If there are no matched entries, then nothing matches.
319
if (len === 0)
320
return cb()
321
322
// if this is the last remaining pattern bit, then no need for
323
// an additional stat *unless* the user has specified mark or
324
// stat explicitly. We know they exist, since readdir returned
325
// them.
326
327
if (remain.length === 1 && !this.mark && !this.stat) {
328
if (!this.matches[index])
329
this.matches[index] = Object.create(null)
330
331
for (var i = 0; i < len; i ++) {
332
var e = matchedEntries[i]
333
if (prefix) {
334
if (prefix !== "/")
335
e = prefix + "/" + e
336
else
337
e = prefix + e
338
}
339
340
if (e.charAt(0) === "/" && !this.nomount) {
341
e = path.join(this.root, e)
342
}
343
this._emitMatch(index, e)
344
}
345
// This was the last one, and no stats were needed
346
return cb()
347
}
348
349
// now test all matched entries as stand-ins for that part
350
// of the pattern.
351
remain.shift()
352
for (var i = 0; i < len; i ++) {
353
var e = matchedEntries[i]
354
var newPattern
355
if (prefix) {
356
if (prefix !== "/")
357
e = prefix + "/" + e
358
else
359
e = prefix + e
360
}
361
this._process([e].concat(remain), index, inGlobStar, cb)
362
}
363
cb()
364
}
365
366
Glob.prototype._emitMatch = function (index, e) {
367
if (this.aborted)
368
return
369
370
if (!this.matches[index][e]) {
371
if (this.paused) {
372
this._emitQueue.push([index, e])
373
return
374
}
375
376
if (this.nodir) {
377
var c = this.cache[this._makeAbs(e)]
378
if (c === 'DIR' || Array.isArray(c))
379
return
380
}
381
382
this.matches[index][e] = true
383
if (!this.stat && !this.mark)
384
return this.emit("match", e)
385
386
var self = this
387
this._stat(this._makeAbs(e), function (er, c, st) {
388
self.emit("stat", e, st)
389
self.emit("match", e)
390
})
391
}
392
}
393
394
Glob.prototype._readdirInGlobStar = function (abs, cb) {
395
if (this.aborted)
396
return
397
398
var lstatkey = "lstat\0" + abs
399
var self = this
400
var lstatcb = inflight(lstatkey, lstatcb_)
401
402
if (lstatcb)
403
fs.lstat(abs, lstatcb)
404
405
function lstatcb_ (er, lstat) {
406
if (er)
407
return cb()
408
409
var isSym = lstat.isSymbolicLink()
410
self.symlinks[abs] = isSym
411
412
// If it's not a symlink or a dir, then it's definitely a regular file.
413
// don't bother doing a readdir in that case.
414
if (!isSym && !lstat.isDirectory()) {
415
self.cache[abs] = 'FILE'
416
cb()
417
} else
418
self._readdir(abs, false, cb)
419
}
420
}
421
422
Glob.prototype._readdir = function (abs, inGlobStar, cb) {
423
if (this.aborted)
424
return
425
426
cb = inflight("readdir\0"+abs+"\0"+inGlobStar, cb)
427
if (!cb)
428
return
429
430
//console.error("RD %j %j", +inGlobStar, abs)
431
if (inGlobStar && !ownProp(this.symlinks, abs))
432
return this._readdirInGlobStar(abs, cb)
433
434
if (ownProp(this.cache, abs)) {
435
var c = this.cache[abs]
436
if (!c || c === 'FILE')
437
return cb()
438
439
if (Array.isArray(c))
440
return cb(null, c)
441
}
442
443
var self = this
444
fs.readdir(abs, readdirCb(this, abs, cb))
445
}
446
447
function readdirCb (self, abs, cb) {
448
return function (er, entries) {
449
if (er)
450
self._readdirError(abs, er, cb)
451
else
452
self._readdirEntries(abs, entries.sort(alphasort), cb)
453
}
454
}
455
456
Glob.prototype._readdirEntries = function (abs, entries, cb) {
457
if (this.aborted)
458
return
459
460
// if we haven't asked to stat everything, then just
461
// assume that everything in there exists, so we can avoid
462
// having to stat it a second time.
463
if (!this.mark && !this.stat) {
464
for (var i = 0; i < entries.length; i ++) {
465
var e = entries[i]
466
if (abs === "/")
467
e = abs + e
468
else
469
e = abs + "/" + e
470
this.cache[e] = true
471
}
472
}
473
474
this.cache[abs] = entries
475
return cb(null, entries)
476
}
477
478
Glob.prototype._readdirError = function (f, er, cb) {
479
if (this.aborted)
480
return
481
482
// handle errors, and cache the information
483
switch (er.code) {
484
case "ENOTDIR": // totally normal. means it *does* exist.
485
this.cache[f] = 'FILE'
486
break
487
488
case "ENOENT": // not terribly unusual
489
case "ELOOP":
490
case "ENAMETOOLONG":
491
case "UNKNOWN":
492
this.cache[f] = false
493
break
494
495
default: // some unusual error. Treat as failure.
496
this.cache[f] = false
497
if (this.strict) return this.emit("error", er)
498
if (!this.silent) console.error("glob error", er)
499
break
500
}
501
return cb()
502
}
503
504
Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) {
505
var self = this
506
this._readdir(abs, inGlobStar, function (er, entries) {
507
self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb)
508
})
509
}
510
511
512
Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) {
513
//console.error("pgs2", prefix, remain[0], entries)
514
515
// no entries means not a dir, so it can never have matches
516
// foo.txt/** doesn't match foo.txt
517
if (!entries)
518
return cb()
519
520
// test without the globstar, and with every child both below
521
// and replacing the globstar.
522
var remainWithoutGlobStar = remain.slice(1)
523
var gspref = prefix ? [ prefix ] : []
524
var noGlobStar = gspref.concat(remainWithoutGlobStar)
525
526
// the noGlobStar pattern exits the inGlobStar state
527
this._process(noGlobStar, index, false, cb)
528
529
var isSym = this.symlinks[abs]
530
var len = entries.length
531
532
// If it's a symlink, and we're in a globstar, then stop
533
if (isSym && inGlobStar)
534
return cb()
535
536
for (var i = 0; i < len; i++) {
537
var e = entries[i]
538
if (e.charAt(0) === "." && !this.dot)
539
continue
540
541
// these two cases enter the inGlobStar state
542
var instead = gspref.concat(entries[i], remainWithoutGlobStar)
543
this._process(instead, index, true, cb)
544
545
var below = gspref.concat(entries[i], remain)
546
this._process(below, index, true, cb)
547
}
548
549
cb()
550
}
551
552
Glob.prototype._processSimple = function (prefix, index, cb) {
553
// XXX review this. Shouldn't it be doing the mounting etc
554
// before doing stat? kinda weird?
555
var self = this
556
this._stat(prefix, function (er, exists) {
557
self._processSimple2(prefix, index, er, exists, cb)
558
})
559
}
560
Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) {
561
562
//console.error("ps2", prefix, exists)
563
564
if (!this.matches[index])
565
this.matches[index] = Object.create(null)
566
567
// If it doesn't exist, then just mark the lack of results
568
if (!exists)
569
return cb()
570
571
if (prefix && isAbsolute(prefix) && !this.nomount) {
572
if (prefix.charAt(0) === "/") {
573
prefix = path.join(this.root, prefix)
574
} else {
575
prefix = path.resolve(this.root, prefix)
576
}
577
}
578
579
if (process.platform === "win32")
580
prefix = prefix.replace(/\\/g, "/")
581
582
// Mark this as a match
583
this._emitMatch(index, prefix)
584
cb()
585
}
586
587
// Returns either 'DIR', 'FILE', or false
588
Glob.prototype._stat = function (f, cb) {
589
var abs = f
590
if (f.charAt(0) === "/")
591
abs = path.join(this.root, f)
592
else if (this.changedCwd)
593
abs = path.resolve(this.cwd, f)
594
595
596
if (f.length > this.maxLength)
597
return cb()
598
599
if (!this.stat && ownProp(this.cache, f)) {
600
var c = this.cache[f]
601
602
if (Array.isArray(c))
603
c = 'DIR'
604
605
// It exists, but not how we need it
606
if (abs.slice(-1) === "/" && c !== 'DIR')
607
return cb()
608
609
return cb(null, c)
610
}
611
612
var exists
613
var stat = this.statCache[abs]
614
if (stat !== undefined) {
615
if (stat === false)
616
return cb(null, stat)
617
else
618
return cb(null, stat.isDirectory() ? 'DIR' : 'FILE', stat)
619
}
620
621
var self = this
622
var statcb = inflight("stat\0" + abs, statcb_)
623
if (statcb)
624
fs.stat(abs, statcb)
625
626
function statcb_ (er, stat) {
627
self._stat2(f, abs, er, stat, cb)
628
}
629
}
630
631
Glob.prototype._stat2 = function (f, abs, er, stat, cb) {
632
if (er) {
633
this.statCache[abs] = false
634
return cb()
635
}
636
637
this.statCache[abs] = stat
638
639
if (abs.slice(-1) === "/" && !stat.isDirectory())
640
return cb(null, false, stat)
641
642
var c = stat.isDirectory() ? 'DIR' : 'FILE'
643
this.cache[f] = this.cache[f] || c
644
return cb(null, c, stat)
645
}
646
647