minpath.browser.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. // A derivative work based on:
  2. // <https://github.com/browserify/path-browserify>.
  3. // Which is licensed:
  4. //
  5. // MIT License
  6. //
  7. // Copyright (c) 2013 James Halliday
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy of
  10. // this software and associated documentation files (the "Software"), to deal in
  11. // the Software without restriction, including without limitation the rights to
  12. // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  13. // the Software, and to permit persons to whom the Software is furnished to do so,
  14. // subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in all
  17. // copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  21. // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  22. // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  23. // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  24. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  25. // A derivative work based on:
  26. //
  27. // Parts of that are extracted from Node’s internal `path` module:
  28. // <https://github.com/nodejs/node/blob/master/lib/path.js>.
  29. // Which is licensed:
  30. //
  31. // Copyright Joyent, Inc. and other Node contributors.
  32. //
  33. // Permission is hereby granted, free of charge, to any person obtaining a
  34. // copy of this software and associated documentation files (the
  35. // "Software"), to deal in the Software without restriction, including
  36. // without limitation the rights to use, copy, modify, merge, publish,
  37. // distribute, sublicense, and/or sell copies of the Software, and to permit
  38. // persons to whom the Software is furnished to do so, subject to the
  39. // following conditions:
  40. //
  41. // The above copyright notice and this permission notice shall be included
  42. // in all copies or substantial portions of the Software.
  43. //
  44. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  45. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  46. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  47. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  48. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  49. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  50. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  51. export const minpath = {basename, dirname, extname, join, sep: '/'}
  52. /* eslint-disable max-depth, complexity */
  53. /**
  54. * Get the basename from a path.
  55. *
  56. * @param {string} path
  57. * File path.
  58. * @param {string | null | undefined} [extname]
  59. * Extension to strip.
  60. * @returns {string}
  61. * Stem or basename.
  62. */
  63. function basename(path, extname) {
  64. if (extname !== undefined && typeof extname !== 'string') {
  65. throw new TypeError('"ext" argument must be a string')
  66. }
  67. assertPath(path)
  68. let start = 0
  69. let end = -1
  70. let index = path.length
  71. /** @type {boolean | undefined} */
  72. let seenNonSlash
  73. if (
  74. extname === undefined ||
  75. extname.length === 0 ||
  76. extname.length > path.length
  77. ) {
  78. while (index--) {
  79. if (path.codePointAt(index) === 47 /* `/` */) {
  80. // If we reached a path separator that was not part of a set of path
  81. // separators at the end of the string, stop now.
  82. if (seenNonSlash) {
  83. start = index + 1
  84. break
  85. }
  86. } else if (end < 0) {
  87. // We saw the first non-path separator, mark this as the end of our
  88. // path component.
  89. seenNonSlash = true
  90. end = index + 1
  91. }
  92. }
  93. return end < 0 ? '' : path.slice(start, end)
  94. }
  95. if (extname === path) {
  96. return ''
  97. }
  98. let firstNonSlashEnd = -1
  99. let extnameIndex = extname.length - 1
  100. while (index--) {
  101. if (path.codePointAt(index) === 47 /* `/` */) {
  102. // If we reached a path separator that was not part of a set of path
  103. // separators at the end of the string, stop now.
  104. if (seenNonSlash) {
  105. start = index + 1
  106. break
  107. }
  108. } else {
  109. if (firstNonSlashEnd < 0) {
  110. // We saw the first non-path separator, remember this index in case
  111. // we need it if the extension ends up not matching.
  112. seenNonSlash = true
  113. firstNonSlashEnd = index + 1
  114. }
  115. if (extnameIndex > -1) {
  116. // Try to match the explicit extension.
  117. if (path.codePointAt(index) === extname.codePointAt(extnameIndex--)) {
  118. if (extnameIndex < 0) {
  119. // We matched the extension, so mark this as the end of our path
  120. // component
  121. end = index
  122. }
  123. } else {
  124. // Extension does not match, so our result is the entire path
  125. // component
  126. extnameIndex = -1
  127. end = firstNonSlashEnd
  128. }
  129. }
  130. }
  131. }
  132. if (start === end) {
  133. end = firstNonSlashEnd
  134. } else if (end < 0) {
  135. end = path.length
  136. }
  137. return path.slice(start, end)
  138. }
  139. /**
  140. * Get the dirname from a path.
  141. *
  142. * @param {string} path
  143. * File path.
  144. * @returns {string}
  145. * File path.
  146. */
  147. function dirname(path) {
  148. assertPath(path)
  149. if (path.length === 0) {
  150. return '.'
  151. }
  152. let end = -1
  153. let index = path.length
  154. /** @type {boolean | undefined} */
  155. let unmatchedSlash
  156. // Prefix `--` is important to not run on `0`.
  157. while (--index) {
  158. if (path.codePointAt(index) === 47 /* `/` */) {
  159. if (unmatchedSlash) {
  160. end = index
  161. break
  162. }
  163. } else if (!unmatchedSlash) {
  164. // We saw the first non-path separator
  165. unmatchedSlash = true
  166. }
  167. }
  168. return end < 0
  169. ? path.codePointAt(0) === 47 /* `/` */
  170. ? '/'
  171. : '.'
  172. : end === 1 && path.codePointAt(0) === 47 /* `/` */
  173. ? '//'
  174. : path.slice(0, end)
  175. }
  176. /**
  177. * Get an extname from a path.
  178. *
  179. * @param {string} path
  180. * File path.
  181. * @returns {string}
  182. * Extname.
  183. */
  184. function extname(path) {
  185. assertPath(path)
  186. let index = path.length
  187. let end = -1
  188. let startPart = 0
  189. let startDot = -1
  190. // Track the state of characters (if any) we see before our first dot and
  191. // after any path separator we find.
  192. let preDotState = 0
  193. /** @type {boolean | undefined} */
  194. let unmatchedSlash
  195. while (index--) {
  196. const code = path.codePointAt(index)
  197. if (code === 47 /* `/` */) {
  198. // If we reached a path separator that was not part of a set of path
  199. // separators at the end of the string, stop now.
  200. if (unmatchedSlash) {
  201. startPart = index + 1
  202. break
  203. }
  204. continue
  205. }
  206. if (end < 0) {
  207. // We saw the first non-path separator, mark this as the end of our
  208. // extension.
  209. unmatchedSlash = true
  210. end = index + 1
  211. }
  212. if (code === 46 /* `.` */) {
  213. // If this is our first dot, mark it as the start of our extension.
  214. if (startDot < 0) {
  215. startDot = index
  216. } else if (preDotState !== 1) {
  217. preDotState = 1
  218. }
  219. } else if (startDot > -1) {
  220. // We saw a non-dot and non-path separator before our dot, so we should
  221. // have a good chance at having a non-empty extension.
  222. preDotState = -1
  223. }
  224. }
  225. if (
  226. startDot < 0 ||
  227. end < 0 ||
  228. // We saw a non-dot character immediately before the dot.
  229. preDotState === 0 ||
  230. // The (right-most) trimmed path component is exactly `..`.
  231. (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  232. ) {
  233. return ''
  234. }
  235. return path.slice(startDot, end)
  236. }
  237. /**
  238. * Join segments from a path.
  239. *
  240. * @param {Array<string>} segments
  241. * Path segments.
  242. * @returns {string}
  243. * File path.
  244. */
  245. function join(...segments) {
  246. let index = -1
  247. /** @type {string | undefined} */
  248. let joined
  249. while (++index < segments.length) {
  250. assertPath(segments[index])
  251. if (segments[index]) {
  252. joined =
  253. joined === undefined ? segments[index] : joined + '/' + segments[index]
  254. }
  255. }
  256. return joined === undefined ? '.' : normalize(joined)
  257. }
  258. /**
  259. * Normalize a basic file path.
  260. *
  261. * @param {string} path
  262. * File path.
  263. * @returns {string}
  264. * File path.
  265. */
  266. // Note: `normalize` is not exposed as `path.normalize`, so some code is
  267. // manually removed from it.
  268. function normalize(path) {
  269. assertPath(path)
  270. const absolute = path.codePointAt(0) === 47 /* `/` */
  271. // Normalize the path according to POSIX rules.
  272. let value = normalizeString(path, !absolute)
  273. if (value.length === 0 && !absolute) {
  274. value = '.'
  275. }
  276. if (value.length > 0 && path.codePointAt(path.length - 1) === 47 /* / */) {
  277. value += '/'
  278. }
  279. return absolute ? '/' + value : value
  280. }
  281. /**
  282. * Resolve `.` and `..` elements in a path with directory names.
  283. *
  284. * @param {string} path
  285. * File path.
  286. * @param {boolean} allowAboveRoot
  287. * Whether `..` can move above root.
  288. * @returns {string}
  289. * File path.
  290. */
  291. function normalizeString(path, allowAboveRoot) {
  292. let result = ''
  293. let lastSegmentLength = 0
  294. let lastSlash = -1
  295. let dots = 0
  296. let index = -1
  297. /** @type {number | undefined} */
  298. let code
  299. /** @type {number} */
  300. let lastSlashIndex
  301. while (++index <= path.length) {
  302. if (index < path.length) {
  303. code = path.codePointAt(index)
  304. } else if (code === 47 /* `/` */) {
  305. break
  306. } else {
  307. code = 47 /* `/` */
  308. }
  309. if (code === 47 /* `/` */) {
  310. if (lastSlash === index - 1 || dots === 1) {
  311. // Empty.
  312. } else if (lastSlash !== index - 1 && dots === 2) {
  313. if (
  314. result.length < 2 ||
  315. lastSegmentLength !== 2 ||
  316. result.codePointAt(result.length - 1) !== 46 /* `.` */ ||
  317. result.codePointAt(result.length - 2) !== 46 /* `.` */
  318. ) {
  319. if (result.length > 2) {
  320. lastSlashIndex = result.lastIndexOf('/')
  321. if (lastSlashIndex !== result.length - 1) {
  322. if (lastSlashIndex < 0) {
  323. result = ''
  324. lastSegmentLength = 0
  325. } else {
  326. result = result.slice(0, lastSlashIndex)
  327. lastSegmentLength = result.length - 1 - result.lastIndexOf('/')
  328. }
  329. lastSlash = index
  330. dots = 0
  331. continue
  332. }
  333. } else if (result.length > 0) {
  334. result = ''
  335. lastSegmentLength = 0
  336. lastSlash = index
  337. dots = 0
  338. continue
  339. }
  340. }
  341. if (allowAboveRoot) {
  342. result = result.length > 0 ? result + '/..' : '..'
  343. lastSegmentLength = 2
  344. }
  345. } else {
  346. if (result.length > 0) {
  347. result += '/' + path.slice(lastSlash + 1, index)
  348. } else {
  349. result = path.slice(lastSlash + 1, index)
  350. }
  351. lastSegmentLength = index - lastSlash - 1
  352. }
  353. lastSlash = index
  354. dots = 0
  355. } else if (code === 46 /* `.` */ && dots > -1) {
  356. dots++
  357. } else {
  358. dots = -1
  359. }
  360. }
  361. return result
  362. }
  363. /**
  364. * Make sure `path` is a string.
  365. *
  366. * @param {string} path
  367. * File path.
  368. * @returns {asserts path is string}
  369. * Nothing.
  370. */
  371. function assertPath(path) {
  372. if (typeof path !== 'string') {
  373. throw new TypeError(
  374. 'Path must be a string. Received ' + JSON.stringify(path)
  375. )
  376. }
  377. }
  378. /* eslint-enable max-depth, complexity */