glob.js 19 KB

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