Brak opisu

glob.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  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. module.exports = glob
  35. var fs = require("graceful-fs")
  36. , minimatch = require("minimatch")
  37. , Minimatch = minimatch.Minimatch
  38. , inherits = require("inherits")
  39. , EE = require("events").EventEmitter
  40. , path = require("path")
  41. , isDir = {}
  42. , assert = require("assert").ok
  43. function glob (pattern, options, cb) {
  44. if (typeof options === "function") cb = options, options = {}
  45. if (!options) options = {}
  46. if (typeof options === "number") {
  47. deprecated()
  48. return
  49. }
  50. var g = new Glob(pattern, options, cb)
  51. return g.sync ? g.found : g
  52. }
  53. glob.fnmatch = deprecated
  54. function deprecated () {
  55. throw new Error("glob's interface has changed. Please see the docs.")
  56. }
  57. glob.sync = globSync
  58. function globSync (pattern, options) {
  59. if (typeof options === "number") {
  60. deprecated()
  61. return
  62. }
  63. options = options || {}
  64. options.sync = true
  65. return glob(pattern, options)
  66. }
  67. glob.Glob = Glob
  68. inherits(Glob, EE)
  69. function Glob (pattern, options, cb) {
  70. if (!(this instanceof Glob)) {
  71. return new Glob(pattern, options, cb)
  72. }
  73. if (typeof cb === "function") {
  74. this.on("error", cb)
  75. this.on("end", function (matches) {
  76. cb(null, matches)
  77. })
  78. }
  79. options = options || {}
  80. this.EOF = {}
  81. this._emitQueue = []
  82. this.maxDepth = options.maxDepth || 1000
  83. this.maxLength = options.maxLength || Infinity
  84. this.statCache = options.statCache || {}
  85. this.changedCwd = false
  86. var cwd = process.cwd()
  87. if (!options.hasOwnProperty("cwd")) this.cwd = cwd
  88. else {
  89. this.cwd = options.cwd
  90. this.changedCwd = path.resolve(options.cwd) !== cwd
  91. }
  92. this.root = options.root || path.resolve(this.cwd, "/")
  93. this.root = path.resolve(this.root)
  94. if (process.platform === "win32")
  95. this.root = this.root.replace(/\\/g, "/")
  96. this.nomount = !!options.nomount
  97. if (!pattern) {
  98. throw new Error("must provide pattern")
  99. }
  100. // base-matching: just use globstar for that.
  101. if (options.matchBase && -1 === pattern.indexOf("/")) {
  102. if (options.noglobstar) {
  103. throw new Error("base matching requires globstar")
  104. }
  105. pattern = "**/" + pattern
  106. }
  107. this.strict = options.strict !== false
  108. this.dot = !!options.dot
  109. this.mark = !!options.mark
  110. this.sync = !!options.sync
  111. this.nounique = !!options.nounique
  112. this.nonull = !!options.nonull
  113. this.nosort = !!options.nosort
  114. this.nocase = !!options.nocase
  115. this.stat = !!options.stat
  116. this.debug = !!options.debug || !!options.globDebug
  117. if (this.debug)
  118. this.log = console.error
  119. this.silent = !!options.silent
  120. var mm = this.minimatch = new Minimatch(pattern, options)
  121. this.options = mm.options
  122. pattern = this.pattern = mm.pattern
  123. this.error = null
  124. this.aborted = false
  125. EE.call(this)
  126. // process each pattern in the minimatch set
  127. var n = this.minimatch.set.length
  128. // The matches are stored as {<filename>: true,...} so that
  129. // duplicates are automagically pruned.
  130. // Later, we do an Object.keys() on these.
  131. // Keep them as a list so we can fill in when nonull is set.
  132. this.matches = new Array(n)
  133. this.minimatch.set.forEach(iterator.bind(this))
  134. function iterator (pattern, i, set) {
  135. this._process(pattern, 0, i, function (er) {
  136. if (er) this.emit("error", er)
  137. if (-- n <= 0) this._finish()
  138. })
  139. }
  140. }
  141. Glob.prototype.log = function () {}
  142. Glob.prototype._finish = function () {
  143. assert(this instanceof Glob)
  144. var nou = this.nounique
  145. , all = nou ? [] : {}
  146. for (var i = 0, l = this.matches.length; i < l; i ++) {
  147. var matches = this.matches[i]
  148. this.log("matches[%d] =", i, matches)
  149. // do like the shell, and spit out the literal glob
  150. if (!matches) {
  151. if (this.nonull) {
  152. var literal = this.minimatch.globSet[i]
  153. if (nou) all.push(literal)
  154. else all[literal] = true
  155. }
  156. } else {
  157. // had matches
  158. var m = Object.keys(matches)
  159. if (nou) all.push.apply(all, m)
  160. else m.forEach(function (m) {
  161. all[m] = true
  162. })
  163. }
  164. }
  165. if (!nou) all = Object.keys(all)
  166. if (!this.nosort) {
  167. all = all.sort(this.nocase ? alphasorti : alphasort)
  168. }
  169. if (this.mark) {
  170. // at *some* point we statted all of these
  171. all = all.map(function (m) {
  172. var sc = this.statCache[m]
  173. if (!sc)
  174. return m
  175. var isDir = (Array.isArray(sc) || sc === 2)
  176. if (isDir && m.slice(-1) !== "/") {
  177. return m + "/"
  178. }
  179. if (!isDir && m.slice(-1) === "/") {
  180. return m.replace(/\/+$/, "")
  181. }
  182. return m
  183. }, this)
  184. }
  185. this.log("emitting end", all)
  186. this.EOF = this.found = all
  187. this.emitMatch(this.EOF)
  188. }
  189. function alphasorti (a, b) {
  190. a = a.toLowerCase()
  191. b = b.toLowerCase()
  192. return alphasort(a, b)
  193. }
  194. function alphasort (a, b) {
  195. return a > b ? 1 : a < b ? -1 : 0
  196. }
  197. Glob.prototype.abort = function () {
  198. this.aborted = true
  199. this.emit("abort")
  200. }
  201. Glob.prototype.pause = function () {
  202. if (this.paused) return
  203. if (this.sync)
  204. this.emit("error", new Error("Can't pause/resume sync glob"))
  205. this.paused = true
  206. this.emit("pause")
  207. }
  208. Glob.prototype.resume = function () {
  209. if (!this.paused) return
  210. if (this.sync)
  211. this.emit("error", new Error("Can't pause/resume sync glob"))
  212. this.paused = false
  213. this.emit("resume")
  214. this._processEmitQueue()
  215. //process.nextTick(this.emit.bind(this, "resume"))
  216. }
  217. Glob.prototype.emitMatch = function (m) {
  218. this._emitQueue.push(m)
  219. this._processEmitQueue()
  220. }
  221. Glob.prototype._processEmitQueue = function (m) {
  222. while (!this._processingEmitQueue &&
  223. !this.paused) {
  224. this._processingEmitQueue = true
  225. var m = this._emitQueue.shift()
  226. if (!m) {
  227. this._processingEmitQueue = false
  228. break
  229. }
  230. this.log('emit!', m === this.EOF ? "end" : "match")
  231. this.emit(m === this.EOF ? "end" : "match", m)
  232. this._processingEmitQueue = false
  233. }
  234. }
  235. Glob.prototype._process = function (pattern, depth, index, cb_) {
  236. assert(this instanceof Glob)
  237. var cb = function cb (er, res) {
  238. assert(this instanceof Glob)
  239. if (this.paused) {
  240. if (!this._processQueue) {
  241. this._processQueue = []
  242. this.once("resume", function () {
  243. var q = this._processQueue
  244. this._processQueue = null
  245. q.forEach(function (cb) { cb() })
  246. })
  247. }
  248. this._processQueue.push(cb_.bind(this, er, res))
  249. } else {
  250. cb_.call(this, er, res)
  251. }
  252. }.bind(this)
  253. if (this.aborted) return cb()
  254. if (depth > this.maxDepth) return cb()
  255. // Get the first [n] parts of pattern that are all strings.
  256. var n = 0
  257. while (typeof pattern[n] === "string") {
  258. n ++
  259. }
  260. // now n is the index of the first one that is *not* a string.
  261. // see if there's anything else
  262. var prefix
  263. switch (n) {
  264. // if not, then this is rather simple
  265. case pattern.length:
  266. prefix = pattern.join("/")
  267. this._stat(prefix, function (exists, isDir) {
  268. // either it's there, or it isn't.
  269. // nothing more to do, either way.
  270. if (exists) {
  271. if (prefix && isAbsolute(prefix) && !this.nomount) {
  272. if (prefix.charAt(0) === "/") {
  273. prefix = path.join(this.root, prefix)
  274. } else {
  275. prefix = path.resolve(this.root, prefix)
  276. }
  277. }
  278. if (process.platform === "win32")
  279. prefix = prefix.replace(/\\/g, "/")
  280. this.matches[index] = this.matches[index] || {}
  281. this.matches[index][prefix] = true
  282. this.emitMatch(prefix)
  283. }
  284. return cb()
  285. })
  286. return
  287. case 0:
  288. // pattern *starts* with some non-trivial item.
  289. // going to readdir(cwd), but not include the prefix in matches.
  290. prefix = null
  291. break
  292. default:
  293. // pattern has some string bits in the front.
  294. // whatever it starts with, whether that's "absolute" like /foo/bar,
  295. // or "relative" like "../baz"
  296. prefix = pattern.slice(0, n)
  297. prefix = prefix.join("/")
  298. break
  299. }
  300. // get the list of entries.
  301. var read
  302. if (prefix === null) read = "."
  303. else if (isAbsolute(prefix) || isAbsolute(pattern.join("/"))) {
  304. if (!prefix || !isAbsolute(prefix)) {
  305. prefix = path.join("/", prefix)
  306. }
  307. read = prefix = path.resolve(prefix)
  308. // if (process.platform === "win32")
  309. // read = prefix = prefix.replace(/^[a-zA-Z]:|\\/g, "/")
  310. this.log('absolute: ', prefix, this.root, pattern, read)
  311. } else {
  312. read = prefix
  313. }
  314. this.log('readdir(%j)', read, this.cwd, this.root)
  315. return this._readdir(read, function (er, entries) {
  316. if (er) {
  317. // not a directory!
  318. // this means that, whatever else comes after this, it can never match
  319. return cb()
  320. }
  321. // globstar is special
  322. if (pattern[n] === minimatch.GLOBSTAR) {
  323. // test without the globstar, and with every child both below
  324. // and replacing the globstar.
  325. var s = [ pattern.slice(0, n).concat(pattern.slice(n + 1)) ]
  326. entries.forEach(function (e) {
  327. if (e.charAt(0) === "." && !this.dot) return
  328. // instead of the globstar
  329. s.push(pattern.slice(0, n).concat(e).concat(pattern.slice(n + 1)))
  330. // below the globstar
  331. s.push(pattern.slice(0, n).concat(e).concat(pattern.slice(n)))
  332. }, this)
  333. // now asyncForEach over this
  334. var l = s.length
  335. , errState = null
  336. s.forEach(function (gsPattern) {
  337. this._process(gsPattern, depth + 1, index, function (er) {
  338. if (errState) return
  339. if (er) return cb(errState = er)
  340. if (--l <= 0) return cb()
  341. })
  342. }, this)
  343. return
  344. }
  345. // not a globstar
  346. // It will only match dot entries if it starts with a dot, or if
  347. // dot is set. Stuff like @(.foo|.bar) isn't allowed.
  348. var pn = pattern[n]
  349. if (typeof pn === "string") {
  350. var found = entries.indexOf(pn) !== -1
  351. entries = found ? entries[pn] : []
  352. } else {
  353. var rawGlob = pattern[n]._glob
  354. , dotOk = this.dot || rawGlob.charAt(0) === "."
  355. entries = entries.filter(function (e) {
  356. return (e.charAt(0) !== "." || dotOk) &&
  357. (typeof pattern[n] === "string" && e === pattern[n] ||
  358. e.match(pattern[n]))
  359. })
  360. }
  361. // If n === pattern.length - 1, then there's no need for the extra stat
  362. // *unless* the user has specified "mark" or "stat" explicitly.
  363. // We know that they exist, since the readdir returned them.
  364. if (n === pattern.length - 1 &&
  365. !this.mark &&
  366. !this.stat) {
  367. entries.forEach(function (e) {
  368. if (prefix) {
  369. if (prefix !== "/") e = prefix + "/" + e
  370. else e = prefix + e
  371. }
  372. if (e.charAt(0) === "/" && !this.nomount) {
  373. e = path.join(this.root, e)
  374. }
  375. if (process.platform === "win32")
  376. e = e.replace(/\\/g, "/")
  377. this.matches[index] = this.matches[index] || {}
  378. this.matches[index][e] = true
  379. this.emitMatch(e)
  380. }, this)
  381. return cb.call(this)
  382. }
  383. // now test all the remaining entries as stand-ins for that part
  384. // of the pattern.
  385. var l = entries.length
  386. , errState = null
  387. if (l === 0) return cb() // no matches possible
  388. entries.forEach(function (e) {
  389. var p = pattern.slice(0, n).concat(e).concat(pattern.slice(n + 1))
  390. this._process(p, depth + 1, index, function (er) {
  391. if (errState) return
  392. if (er) return cb(errState = er)
  393. if (--l === 0) return cb.call(this)
  394. })
  395. }, this)
  396. })
  397. }
  398. Glob.prototype._stat = function (f, cb) {
  399. assert(this instanceof Glob)
  400. var abs = f
  401. if (f.charAt(0) === "/") {
  402. abs = path.join(this.root, f)
  403. } else if (this.changedCwd) {
  404. abs = path.resolve(this.cwd, f)
  405. }
  406. this.log('stat', [this.cwd, f, '=', abs])
  407. if (f.length > this.maxLength) {
  408. var er = new Error("Path name too long")
  409. er.code = "ENAMETOOLONG"
  410. er.path = f
  411. return this._afterStat(f, abs, cb, er)
  412. }
  413. if (this.statCache.hasOwnProperty(f)) {
  414. var exists = this.statCache[f]
  415. , isDir = exists && (Array.isArray(exists) || exists === 2)
  416. if (this.sync) return cb.call(this, !!exists, isDir)
  417. return process.nextTick(cb.bind(this, !!exists, isDir))
  418. }
  419. if (this.sync) {
  420. var er, stat
  421. try {
  422. stat = fs.statSync(abs)
  423. } catch (e) {
  424. er = e
  425. }
  426. this._afterStat(f, abs, cb, er, stat)
  427. } else {
  428. fs.stat(abs, this._afterStat.bind(this, f, abs, cb))
  429. }
  430. }
  431. Glob.prototype._afterStat = function (f, abs, cb, er, stat) {
  432. var exists
  433. assert(this instanceof Glob)
  434. if (abs.slice(-1) === "/" && stat && !stat.isDirectory()) {
  435. this.log("should be ENOTDIR, fake it")
  436. er = new Error("ENOTDIR, not a directory '" + abs + "'")
  437. er.path = abs
  438. er.code = "ENOTDIR"
  439. stat = null
  440. }
  441. if (er || !stat) {
  442. exists = false
  443. } else {
  444. exists = stat.isDirectory() ? 2 : 1
  445. }
  446. this.statCache[f] = this.statCache[f] || exists
  447. cb.call(this, !!exists, exists === 2)
  448. }
  449. Glob.prototype._readdir = function (f, cb) {
  450. assert(this instanceof Glob)
  451. var abs = f
  452. if (f.charAt(0) === "/") {
  453. abs = path.join(this.root, f)
  454. } else if (isAbsolute(f)) {
  455. abs = f
  456. } else if (this.changedCwd) {
  457. abs = path.resolve(this.cwd, f)
  458. }
  459. this.log('readdir', [this.cwd, f, abs])
  460. if (f.length > this.maxLength) {
  461. var er = new Error("Path name too long")
  462. er.code = "ENAMETOOLONG"
  463. er.path = f
  464. return this._afterReaddir(f, abs, cb, er)
  465. }
  466. if (this.statCache.hasOwnProperty(f)) {
  467. var c = this.statCache[f]
  468. if (Array.isArray(c)) {
  469. if (this.sync) return cb.call(this, null, c)
  470. return process.nextTick(cb.bind(this, null, c))
  471. }
  472. if (!c || c === 1) {
  473. // either ENOENT or ENOTDIR
  474. var code = c ? "ENOTDIR" : "ENOENT"
  475. , er = new Error((c ? "Not a directory" : "Not found") + ": " + f)
  476. er.path = f
  477. er.code = code
  478. this.log(f, er)
  479. if (this.sync) return cb.call(this, er)
  480. return process.nextTick(cb.bind(this, er))
  481. }
  482. // at this point, c === 2, meaning it's a dir, but we haven't
  483. // had to read it yet, or c === true, meaning it's *something*
  484. // but we don't have any idea what. Need to read it, either way.
  485. }
  486. if (this.sync) {
  487. var er, entries
  488. try {
  489. entries = fs.readdirSync(abs)
  490. } catch (e) {
  491. er = e
  492. }
  493. return this._afterReaddir(f, abs, cb, er, entries)
  494. }
  495. fs.readdir(abs, this._afterReaddir.bind(this, f, abs, cb))
  496. }
  497. Glob.prototype._afterReaddir = function (f, abs, cb, er, entries) {
  498. assert(this instanceof Glob)
  499. if (entries && !er) {
  500. this.statCache[f] = entries
  501. // if we haven't asked to stat everything for suresies, then just
  502. // assume that everything in there exists, so we can avoid
  503. // having to stat it a second time. This also gets us one step
  504. // further into ELOOP territory.
  505. if (!this.mark && !this.stat) {
  506. entries.forEach(function (e) {
  507. if (f === "/") e = f + e
  508. else e = f + "/" + e
  509. this.statCache[e] = true
  510. }, this)
  511. }
  512. return cb.call(this, er, entries)
  513. }
  514. // now handle errors, and cache the information
  515. if (er) switch (er.code) {
  516. case "ENOTDIR": // totally normal. means it *does* exist.
  517. this.statCache[f] = 1
  518. return cb.call(this, er)
  519. case "ENOENT": // not terribly unusual
  520. case "ELOOP":
  521. case "ENAMETOOLONG":
  522. case "UNKNOWN":
  523. this.statCache[f] = false
  524. return cb.call(this, er)
  525. default: // some unusual error. Treat as failure.
  526. this.statCache[f] = false
  527. if (this.strict) this.emit("error", er)
  528. if (!this.silent) console.error("glob error", er)
  529. return cb.call(this, er)
  530. }
  531. }
  532. var isAbsolute = process.platform === "win32" ? absWin : absUnix
  533. function absWin (p) {
  534. if (absUnix(p)) return true
  535. // pull off the device/UNC bit from a windows path.
  536. // from node's lib/path.js
  537. var splitDeviceRe =
  538. /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/
  539. , result = splitDeviceRe.exec(p)
  540. , device = result[1] || ''
  541. , isUnc = device && device.charAt(1) !== ':'
  542. , isAbsolute = !!result[2] || isUnc // UNC paths are always absolute
  543. return isAbsolute
  544. }
  545. function absUnix (p) {
  546. return p.charAt(0) === "/" || p === ""
  547. }