Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80711 views
1
// Approach:
2
//
3
// 1. Get the minimatch set
4
// 2. For each pattern in the set, PROCESS(pattern)
5
// 3. Store matches per-set, then uniq them
6
//
7
// PROCESS(pattern)
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
// readdir(PREFIX) as ENTRIES
13
// If fails, END
14
// If pattern[n] is GLOBSTAR
15
// // handle the case where the globstar match is empty
16
// // by pruning it out, and testing the resulting pattern
17
// PROCESS(pattern[0..n] + pattern[n+1 .. $])
18
// // handle other cases.
19
// for ENTRY in ENTRIES (not dotfiles)
20
// // attach globstar + tail onto the entry
21
// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $])
22
//
23
// else // not globstar
24
// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot)
25
// Test ENTRY against pattern[n]
26
// If fails, continue
27
// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $])
28
//
29
// Caveat:
30
// Cache all stats and readdirs results to minimize syscall. Since all
31
// we ever care about is existence and directory-ness, we can just keep
32
// `true` for files, and [children,...] for directories, or `false` for
33
// things that don't exist.
34
35
36
37
module.exports = glob
38
39
var fs = require("fs")
40
, minimatch = require("minimatch")
41
, Minimatch = minimatch.Minimatch
42
, inherits = require("inherits")
43
, EE = require("events").EventEmitter
44
, path = require("path")
45
, isDir = {}
46
, assert = require("assert").ok
47
48
function glob (pattern, options, cb) {
49
if (typeof options === "function") cb = options, options = {}
50
if (!options) options = {}
51
52
if (typeof options === "number") {
53
deprecated()
54
return
55
}
56
57
var g = new Glob(pattern, options, cb)
58
return g.sync ? g.found : g
59
}
60
61
glob.fnmatch = deprecated
62
63
function deprecated () {
64
throw new Error("glob's interface has changed. Please see the docs.")
65
}
66
67
glob.sync = globSync
68
function globSync (pattern, options) {
69
if (typeof options === "number") {
70
deprecated()
71
return
72
}
73
74
options = options || {}
75
options.sync = true
76
return glob(pattern, options)
77
}
78
79
this._processingEmitQueue = false
80
81
glob.Glob = Glob
82
inherits(Glob, EE)
83
function Glob (pattern, options, cb) {
84
if (!(this instanceof Glob)) {
85
return new Glob(pattern, options, cb)
86
}
87
88
if (typeof options === "function") {
89
cb = options
90
options = null
91
}
92
93
if (typeof cb === "function") {
94
this.on("error", cb)
95
this.on("end", function (matches) {
96
cb(null, matches)
97
})
98
}
99
100
options = options || {}
101
102
this._endEmitted = false
103
this.EOF = {}
104
this._emitQueue = []
105
106
this.paused = false
107
this._processingEmitQueue = false
108
109
this.maxDepth = options.maxDepth || 1000
110
this.maxLength = options.maxLength || Infinity
111
this.cache = options.cache || {}
112
this.statCache = options.statCache || {}
113
114
this.changedCwd = false
115
var cwd = process.cwd()
116
if (!options.hasOwnProperty("cwd")) this.cwd = cwd
117
else {
118
this.cwd = options.cwd
119
this.changedCwd = path.resolve(options.cwd) !== cwd
120
}
121
122
this.root = options.root || path.resolve(this.cwd, "/")
123
this.root = path.resolve(this.root)
124
if (process.platform === "win32")
125
this.root = this.root.replace(/\\/g, "/")
126
127
this.nomount = !!options.nomount
128
129
if (!pattern) {
130
throw new Error("must provide pattern")
131
}
132
133
// base-matching: just use globstar for that.
134
if (options.matchBase && -1 === pattern.indexOf("/")) {
135
if (options.noglobstar) {
136
throw new Error("base matching requires globstar")
137
}
138
pattern = "**/" + pattern
139
}
140
141
this.strict = options.strict !== false
142
this.dot = !!options.dot
143
this.mark = !!options.mark
144
this.sync = !!options.sync
145
this.nounique = !!options.nounique
146
this.nonull = !!options.nonull
147
this.nosort = !!options.nosort
148
this.nocase = !!options.nocase
149
this.stat = !!options.stat
150
151
this.debug = !!options.debug || !!options.globDebug
152
if (this.debug)
153
this.log = console.error
154
155
this.silent = !!options.silent
156
157
var mm = this.minimatch = new Minimatch(pattern, options)
158
this.options = mm.options
159
pattern = this.pattern = mm.pattern
160
161
this.error = null
162
this.aborted = false
163
164
// list of all the patterns that ** has resolved do, so
165
// we can avoid visiting multiple times.
166
this._globstars = {}
167
168
EE.call(this)
169
170
// process each pattern in the minimatch set
171
var n = this.minimatch.set.length
172
173
// The matches are stored as {<filename>: true,...} so that
174
// duplicates are automagically pruned.
175
// Later, we do an Object.keys() on these.
176
// Keep them as a list so we can fill in when nonull is set.
177
this.matches = new Array(n)
178
179
this.minimatch.set.forEach(iterator.bind(this))
180
function iterator (pattern, i, set) {
181
this._process(pattern, 0, i, function (er) {
182
if (er) this.emit("error", er)
183
if (-- n <= 0) this._finish()
184
})
185
}
186
}
187
188
Glob.prototype.log = function () {}
189
190
Glob.prototype._finish = function () {
191
assert(this instanceof Glob)
192
193
var nou = this.nounique
194
, all = nou ? [] : {}
195
196
for (var i = 0, l = this.matches.length; i < l; i ++) {
197
var matches = this.matches[i]
198
this.log("matches[%d] =", i, matches)
199
// do like the shell, and spit out the literal glob
200
if (!matches) {
201
if (this.nonull) {
202
var literal = this.minimatch.globSet[i]
203
if (nou) all.push(literal)
204
else all[literal] = true
205
}
206
} else {
207
// had matches
208
var m = Object.keys(matches)
209
if (nou) all.push.apply(all, m)
210
else m.forEach(function (m) {
211
all[m] = true
212
})
213
}
214
}
215
216
if (!nou) all = Object.keys(all)
217
218
if (!this.nosort) {
219
all = all.sort(this.nocase ? alphasorti : alphasort)
220
}
221
222
if (this.mark) {
223
// at *some* point we statted all of these
224
all = all.map(this._mark, this)
225
}
226
227
this.log("emitting end", all)
228
229
this.EOF = this.found = all
230
this.emitMatch(this.EOF)
231
}
232
233
function alphasorti (a, b) {
234
a = a.toLowerCase()
235
b = b.toLowerCase()
236
return alphasort(a, b)
237
}
238
239
function alphasort (a, b) {
240
return a > b ? 1 : a < b ? -1 : 0
241
}
242
243
Glob.prototype._mark = function (p) {
244
var c = this.cache[p]
245
var m = p
246
if (c) {
247
var isDir = c === 2 || Array.isArray(c)
248
var slash = p.slice(-1) === '/'
249
250
if (isDir && !slash)
251
m += '/'
252
else if (!isDir && slash)
253
m = m.slice(0, -1)
254
255
if (m !== p) {
256
this.statCache[m] = this.statCache[p]
257
this.cache[m] = this.cache[p]
258
}
259
}
260
261
return m
262
}
263
264
Glob.prototype.abort = function () {
265
this.aborted = true
266
this.emit("abort")
267
}
268
269
Glob.prototype.pause = function () {
270
if (this.paused) return
271
if (this.sync)
272
this.emit("error", new Error("Can't pause/resume sync glob"))
273
this.paused = true
274
this.emit("pause")
275
}
276
277
Glob.prototype.resume = function () {
278
if (!this.paused) return
279
if (this.sync)
280
this.emit("error", new Error("Can't pause/resume sync glob"))
281
this.paused = false
282
this.emit("resume")
283
this._processEmitQueue()
284
//process.nextTick(this.emit.bind(this, "resume"))
285
}
286
287
Glob.prototype.emitMatch = function (m) {
288
this.log('emitMatch', m)
289
this._emitQueue.push(m)
290
this._processEmitQueue()
291
}
292
293
Glob.prototype._processEmitQueue = function (m) {
294
this.log("pEQ paused=%j processing=%j m=%j", this.paused,
295
this._processingEmitQueue, m)
296
var done = false
297
while (!this._processingEmitQueue &&
298
!this.paused) {
299
this._processingEmitQueue = true
300
var m = this._emitQueue.shift()
301
this.log(">processEmitQueue", m === this.EOF ? ":EOF:" : m)
302
if (!m) {
303
this.log(">processEmitQueue, falsey m")
304
this._processingEmitQueue = false
305
break
306
}
307
308
if (m === this.EOF || !(this.mark && !this.stat)) {
309
this.log("peq: unmarked, or eof")
310
next.call(this, 0, false)
311
} else if (this.statCache[m]) {
312
var sc = this.statCache[m]
313
var exists
314
if (sc)
315
exists = sc.isDirectory() ? 2 : 1
316
this.log("peq: stat cached")
317
next.call(this, exists, exists === 2)
318
} else {
319
this.log("peq: _stat, then next")
320
this._stat(m, next)
321
}
322
323
function next(exists, isDir) {
324
this.log("next", m, exists, isDir)
325
var ev = m === this.EOF ? "end" : "match"
326
327
// "end" can only happen once.
328
assert(!this._endEmitted)
329
if (ev === "end")
330
this._endEmitted = true
331
332
if (exists) {
333
// Doesn't mean it necessarily doesn't exist, it's possible
334
// we just didn't check because we don't care that much, or
335
// this is EOF anyway.
336
if (isDir && !m.match(/\/$/)) {
337
m = m + "/"
338
} else if (!isDir && m.match(/\/$/)) {
339
m = m.replace(/\/+$/, "")
340
}
341
}
342
this.log("emit", ev, m)
343
this.emit(ev, m)
344
this._processingEmitQueue = false
345
if (done && m !== this.EOF && !this.paused)
346
this._processEmitQueue()
347
}
348
}
349
done = true
350
}
351
352
Glob.prototype._process = function (pattern, depth, index, cb_) {
353
assert(this instanceof Glob)
354
355
var cb = function cb (er, res) {
356
assert(this instanceof Glob)
357
if (this.paused) {
358
if (!this._processQueue) {
359
this._processQueue = []
360
this.once("resume", function () {
361
var q = this._processQueue
362
this._processQueue = null
363
q.forEach(function (cb) { cb() })
364
})
365
}
366
this._processQueue.push(cb_.bind(this, er, res))
367
} else {
368
cb_.call(this, er, res)
369
}
370
}.bind(this)
371
372
if (this.aborted) return cb()
373
374
if (depth > this.maxDepth) return cb()
375
376
// Get the first [n] parts of pattern that are all strings.
377
var n = 0
378
while (typeof pattern[n] === "string") {
379
n ++
380
}
381
// now n is the index of the first one that is *not* a string.
382
383
// see if there's anything else
384
var prefix
385
switch (n) {
386
// if not, then this is rather simple
387
case pattern.length:
388
prefix = pattern.join("/")
389
this._stat(prefix, function (exists, isDir) {
390
// either it's there, or it isn't.
391
// nothing more to do, either way.
392
if (exists) {
393
if (prefix && isAbsolute(prefix) && !this.nomount) {
394
if (prefix.charAt(0) === "/") {
395
prefix = path.join(this.root, prefix)
396
} else {
397
prefix = path.resolve(this.root, prefix)
398
}
399
}
400
401
if (process.platform === "win32")
402
prefix = prefix.replace(/\\/g, "/")
403
404
this.matches[index] = this.matches[index] || {}
405
this.matches[index][prefix] = true
406
this.emitMatch(prefix)
407
}
408
return cb()
409
})
410
return
411
412
case 0:
413
// pattern *starts* with some non-trivial item.
414
// going to readdir(cwd), but not include the prefix in matches.
415
prefix = null
416
break
417
418
default:
419
// pattern has some string bits in the front.
420
// whatever it starts with, whether that's "absolute" like /foo/bar,
421
// or "relative" like "../baz"
422
prefix = pattern.slice(0, n)
423
prefix = prefix.join("/")
424
break
425
}
426
427
// get the list of entries.
428
var read
429
if (prefix === null) read = "."
430
else if (isAbsolute(prefix) || isAbsolute(pattern.join("/"))) {
431
if (!prefix || !isAbsolute(prefix)) {
432
prefix = path.join("/", prefix)
433
}
434
read = prefix = path.resolve(prefix)
435
436
// if (process.platform === "win32")
437
// read = prefix = prefix.replace(/^[a-zA-Z]:|\\/g, "/")
438
439
this.log('absolute: ', prefix, this.root, pattern, read)
440
} else {
441
read = prefix
442
}
443
444
this.log('readdir(%j)', read, this.cwd, this.root)
445
446
return this._readdir(read, function (er, entries) {
447
if (er) {
448
// not a directory!
449
// this means that, whatever else comes after this, it can never match
450
return cb()
451
}
452
453
// globstar is special
454
if (pattern[n] === minimatch.GLOBSTAR) {
455
// test without the globstar, and with every child both below
456
// and replacing the globstar.
457
var s = [ pattern.slice(0, n).concat(pattern.slice(n + 1)) ]
458
entries.forEach(function (e) {
459
if (e.charAt(0) === "." && !this.dot) return
460
// instead of the globstar
461
s.push(pattern.slice(0, n).concat(e).concat(pattern.slice(n + 1)))
462
// below the globstar
463
s.push(pattern.slice(0, n).concat(e).concat(pattern.slice(n)))
464
}, this)
465
466
s = s.filter(function (pattern) {
467
var key = gsKey(pattern)
468
var seen = !this._globstars[key]
469
this._globstars[key] = true
470
return seen
471
}, this)
472
473
if (!s.length)
474
return cb()
475
476
// now asyncForEach over this
477
var l = s.length
478
, errState = null
479
s.forEach(function (gsPattern) {
480
this._process(gsPattern, depth + 1, index, function (er) {
481
if (errState) return
482
if (er) return cb(errState = er)
483
if (--l <= 0) return cb()
484
})
485
}, this)
486
487
return
488
}
489
490
// not a globstar
491
// It will only match dot entries if it starts with a dot, or if
492
// dot is set. Stuff like @(.foo|.bar) isn't allowed.
493
var pn = pattern[n]
494
var rawGlob = pattern[n]._glob
495
, dotOk = this.dot || rawGlob.charAt(0) === "."
496
497
entries = entries.filter(function (e) {
498
return (e.charAt(0) !== "." || dotOk) &&
499
e.match(pattern[n])
500
})
501
502
// If n === pattern.length - 1, then there's no need for the extra stat
503
// *unless* the user has specified "mark" or "stat" explicitly.
504
// We know that they exist, since the readdir returned them.
505
if (n === pattern.length - 1 &&
506
!this.mark &&
507
!this.stat) {
508
entries.forEach(function (e) {
509
if (prefix) {
510
if (prefix !== "/") e = prefix + "/" + e
511
else e = prefix + e
512
}
513
if (e.charAt(0) === "/" && !this.nomount) {
514
e = path.join(this.root, e)
515
}
516
517
if (process.platform === "win32")
518
e = e.replace(/\\/g, "/")
519
520
this.matches[index] = this.matches[index] || {}
521
this.matches[index][e] = true
522
this.emitMatch(e)
523
}, this)
524
return cb.call(this)
525
}
526
527
528
// now test all the remaining entries as stand-ins for that part
529
// of the pattern.
530
var l = entries.length
531
, errState = null
532
if (l === 0) return cb() // no matches possible
533
entries.forEach(function (e) {
534
var p = pattern.slice(0, n).concat(e).concat(pattern.slice(n + 1))
535
this._process(p, depth + 1, index, function (er) {
536
if (errState) return
537
if (er) return cb(errState = er)
538
if (--l === 0) return cb.call(this)
539
})
540
}, this)
541
})
542
543
}
544
545
function gsKey (pattern) {
546
return '**' + pattern.map(function (p) {
547
return (p === minimatch.GLOBSTAR) ? '**' : (''+p)
548
}).join('/')
549
}
550
551
Glob.prototype._stat = function (f, cb) {
552
assert(this instanceof Glob)
553
var abs = f
554
if (f.charAt(0) === "/") {
555
abs = path.join(this.root, f)
556
} else if (this.changedCwd) {
557
abs = path.resolve(this.cwd, f)
558
}
559
560
if (f.length > this.maxLength) {
561
var er = new Error("Path name too long")
562
er.code = "ENAMETOOLONG"
563
er.path = f
564
return this._afterStat(f, abs, cb, er)
565
}
566
567
this.log('stat', [this.cwd, f, '=', abs])
568
569
if (!this.stat && this.cache.hasOwnProperty(f)) {
570
var exists = this.cache[f]
571
, isDir = exists && (Array.isArray(exists) || exists === 2)
572
if (this.sync) return cb.call(this, !!exists, isDir)
573
return process.nextTick(cb.bind(this, !!exists, isDir))
574
}
575
576
var stat = this.statCache[abs]
577
if (this.sync || stat) {
578
var er
579
try {
580
stat = fs.statSync(abs)
581
} catch (e) {
582
er = e
583
}
584
this._afterStat(f, abs, cb, er, stat)
585
} else {
586
fs.stat(abs, this._afterStat.bind(this, f, abs, cb))
587
}
588
}
589
590
Glob.prototype._afterStat = function (f, abs, cb, er, stat) {
591
var exists
592
assert(this instanceof Glob)
593
594
if (abs.slice(-1) === "/" && stat && !stat.isDirectory()) {
595
this.log("should be ENOTDIR, fake it")
596
597
er = new Error("ENOTDIR, not a directory '" + abs + "'")
598
er.path = abs
599
er.code = "ENOTDIR"
600
stat = null
601
}
602
603
var emit = !this.statCache[abs]
604
this.statCache[abs] = stat
605
606
if (er || !stat) {
607
exists = false
608
} else {
609
exists = stat.isDirectory() ? 2 : 1
610
if (emit)
611
this.emit('stat', f, stat)
612
}
613
this.cache[f] = this.cache[f] || exists
614
cb.call(this, !!exists, exists === 2)
615
}
616
617
Glob.prototype._readdir = function (f, cb) {
618
assert(this instanceof Glob)
619
var abs = f
620
if (f.charAt(0) === "/") {
621
abs = path.join(this.root, f)
622
} else if (isAbsolute(f)) {
623
abs = f
624
} else if (this.changedCwd) {
625
abs = path.resolve(this.cwd, f)
626
}
627
628
if (f.length > this.maxLength) {
629
var er = new Error("Path name too long")
630
er.code = "ENAMETOOLONG"
631
er.path = f
632
return this._afterReaddir(f, abs, cb, er)
633
}
634
635
this.log('readdir', [this.cwd, f, abs])
636
if (this.cache.hasOwnProperty(f)) {
637
var c = this.cache[f]
638
if (Array.isArray(c)) {
639
if (this.sync) return cb.call(this, null, c)
640
return process.nextTick(cb.bind(this, null, c))
641
}
642
643
if (!c || c === 1) {
644
// either ENOENT or ENOTDIR
645
var code = c ? "ENOTDIR" : "ENOENT"
646
, er = new Error((c ? "Not a directory" : "Not found") + ": " + f)
647
er.path = f
648
er.code = code
649
this.log(f, er)
650
if (this.sync) return cb.call(this, er)
651
return process.nextTick(cb.bind(this, er))
652
}
653
654
// at this point, c === 2, meaning it's a dir, but we haven't
655
// had to read it yet, or c === true, meaning it's *something*
656
// but we don't have any idea what. Need to read it, either way.
657
}
658
659
if (this.sync) {
660
var er, entries
661
try {
662
entries = fs.readdirSync(abs)
663
} catch (e) {
664
er = e
665
}
666
return this._afterReaddir(f, abs, cb, er, entries)
667
}
668
669
fs.readdir(abs, this._afterReaddir.bind(this, f, abs, cb))
670
}
671
672
Glob.prototype._afterReaddir = function (f, abs, cb, er, entries) {
673
assert(this instanceof Glob)
674
if (entries && !er) {
675
this.cache[f] = entries
676
// if we haven't asked to stat everything for suresies, then just
677
// assume that everything in there exists, so we can avoid
678
// having to stat it a second time. This also gets us one step
679
// further into ELOOP territory.
680
if (!this.mark && !this.stat) {
681
entries.forEach(function (e) {
682
if (f === "/") e = f + e
683
else e = f + "/" + e
684
this.cache[e] = true
685
}, this)
686
}
687
688
return cb.call(this, er, entries)
689
}
690
691
// now handle errors, and cache the information
692
if (er) switch (er.code) {
693
case "ENOTDIR": // totally normal. means it *does* exist.
694
this.cache[f] = 1
695
return cb.call(this, er)
696
case "ENOENT": // not terribly unusual
697
case "ELOOP":
698
case "ENAMETOOLONG":
699
case "UNKNOWN":
700
this.cache[f] = false
701
return cb.call(this, er)
702
default: // some unusual error. Treat as failure.
703
this.cache[f] = false
704
if (this.strict) this.emit("error", er)
705
if (!this.silent) console.error("glob error", er)
706
return cb.call(this, er)
707
}
708
}
709
710
var isAbsolute = process.platform === "win32" ? absWin : absUnix
711
712
function absWin (p) {
713
if (absUnix(p)) return true
714
// pull off the device/UNC bit from a windows path.
715
// from node's lib/path.js
716
var splitDeviceRe =
717
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/
718
, result = splitDeviceRe.exec(p)
719
, device = result[1] || ''
720
, isUnc = device && device.charAt(1) !== ':'
721
, isAbsolute = !!result[2] || isUnc // UNC paths are always absolute
722
723
return isAbsolute
724
}
725
726
function absUnix (p) {
727
return p.charAt(0) === "/" || p === ""
728
}
729
730