423 lines
11 KiB
JavaScript
423 lines
11 KiB
JavaScript
|
// A derivative work based on:
|
|||
|
// <https://github.com/browserify/path-browserify>.
|
|||
|
// Which is licensed:
|
|||
|
//
|
|||
|
// MIT License
|
|||
|
//
|
|||
|
// Copyright (c) 2013 James Halliday
|
|||
|
//
|
|||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|||
|
// this software and associated documentation files (the "Software"), to deal in
|
|||
|
// the Software without restriction, including without limitation the rights to
|
|||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
|||
|
// subject to the following conditions:
|
|||
|
//
|
|||
|
// The above copyright notice and this permission notice shall be included in all
|
|||
|
// copies or substantial portions of the Software.
|
|||
|
//
|
|||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
|
// A derivative work based on:
|
|||
|
//
|
|||
|
// Parts of that are extracted from Node’s internal `path` module:
|
|||
|
// <https://github.com/nodejs/node/blob/master/lib/path.js>.
|
|||
|
// Which is licensed:
|
|||
|
//
|
|||
|
// Copyright Joyent, Inc. and other Node contributors.
|
|||
|
//
|
|||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|||
|
// copy of this software and associated documentation files (the
|
|||
|
// "Software"), to deal in the Software without restriction, including
|
|||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|||
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|||
|
// persons to whom the Software is furnished to do so, subject to the
|
|||
|
// following conditions:
|
|||
|
//
|
|||
|
// The above copyright notice and this permission notice shall be included
|
|||
|
// in all copies or substantial portions of the Software.
|
|||
|
//
|
|||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|||
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|||
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|||
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|||
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
|
|
|||
|
export const path = {basename, dirname, extname, join, sep: '/'}
|
|||
|
|
|||
|
/* eslint-disable max-depth, complexity */
|
|||
|
|
|||
|
/**
|
|||
|
* Get the basename from a path.
|
|||
|
*
|
|||
|
* @param {string} path
|
|||
|
* File path.
|
|||
|
* @param {string | null | undefined} [ext]
|
|||
|
* Extension to strip.
|
|||
|
* @returns {string}
|
|||
|
* Stem or basename.
|
|||
|
*/
|
|||
|
function basename(path, ext) {
|
|||
|
if (ext !== undefined && typeof ext !== 'string') {
|
|||
|
throw new TypeError('"ext" argument must be a string')
|
|||
|
}
|
|||
|
|
|||
|
assertPath(path)
|
|||
|
let start = 0
|
|||
|
let end = -1
|
|||
|
let index = path.length
|
|||
|
/** @type {boolean | undefined} */
|
|||
|
let seenNonSlash
|
|||
|
|
|||
|
if (ext === undefined || ext.length === 0 || ext.length > path.length) {
|
|||
|
while (index--) {
|
|||
|
if (path.codePointAt(index) === 47 /* `/` */) {
|
|||
|
// If we reached a path separator that was not part of a set of path
|
|||
|
// separators at the end of the string, stop now.
|
|||
|
if (seenNonSlash) {
|
|||
|
start = index + 1
|
|||
|
break
|
|||
|
}
|
|||
|
} else if (end < 0) {
|
|||
|
// We saw the first non-path separator, mark this as the end of our
|
|||
|
// path component.
|
|||
|
seenNonSlash = true
|
|||
|
end = index + 1
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return end < 0 ? '' : path.slice(start, end)
|
|||
|
}
|
|||
|
|
|||
|
if (ext === path) {
|
|||
|
return ''
|
|||
|
}
|
|||
|
|
|||
|
let firstNonSlashEnd = -1
|
|||
|
let extIndex = ext.length - 1
|
|||
|
|
|||
|
while (index--) {
|
|||
|
if (path.codePointAt(index) === 47 /* `/` */) {
|
|||
|
// If we reached a path separator that was not part of a set of path
|
|||
|
// separators at the end of the string, stop now.
|
|||
|
if (seenNonSlash) {
|
|||
|
start = index + 1
|
|||
|
break
|
|||
|
}
|
|||
|
} else {
|
|||
|
if (firstNonSlashEnd < 0) {
|
|||
|
// We saw the first non-path separator, remember this index in case
|
|||
|
// we need it if the extension ends up not matching.
|
|||
|
seenNonSlash = true
|
|||
|
firstNonSlashEnd = index + 1
|
|||
|
}
|
|||
|
|
|||
|
if (extIndex > -1) {
|
|||
|
// Try to match the explicit extension.
|
|||
|
if (path.codePointAt(index) === ext.codePointAt(extIndex--)) {
|
|||
|
if (extIndex < 0) {
|
|||
|
// We matched the extension, so mark this as the end of our path
|
|||
|
// component
|
|||
|
end = index
|
|||
|
}
|
|||
|
} else {
|
|||
|
// Extension does not match, so our result is the entire path
|
|||
|
// component
|
|||
|
extIndex = -1
|
|||
|
end = firstNonSlashEnd
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (start === end) {
|
|||
|
end = firstNonSlashEnd
|
|||
|
} else if (end < 0) {
|
|||
|
end = path.length
|
|||
|
}
|
|||
|
|
|||
|
return path.slice(start, end)
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the dirname from a path.
|
|||
|
*
|
|||
|
* @param {string} path
|
|||
|
* File path.
|
|||
|
* @returns {string}
|
|||
|
* File path.
|
|||
|
*/
|
|||
|
function dirname(path) {
|
|||
|
assertPath(path)
|
|||
|
|
|||
|
if (path.length === 0) {
|
|||
|
return '.'
|
|||
|
}
|
|||
|
|
|||
|
let end = -1
|
|||
|
let index = path.length
|
|||
|
/** @type {boolean | undefined} */
|
|||
|
let unmatchedSlash
|
|||
|
|
|||
|
// Prefix `--` is important to not run on `0`.
|
|||
|
while (--index) {
|
|||
|
if (path.codePointAt(index) === 47 /* `/` */) {
|
|||
|
if (unmatchedSlash) {
|
|||
|
end = index
|
|||
|
break
|
|||
|
}
|
|||
|
} else if (!unmatchedSlash) {
|
|||
|
// We saw the first non-path separator
|
|||
|
unmatchedSlash = true
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return end < 0
|
|||
|
? path.codePointAt(0) === 47 /* `/` */
|
|||
|
? '/'
|
|||
|
: '.'
|
|||
|
: end === 1 && path.codePointAt(0) === 47 /* `/` */
|
|||
|
? '//'
|
|||
|
: path.slice(0, end)
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get an extname from a path.
|
|||
|
*
|
|||
|
* @param {string} path
|
|||
|
* File path.
|
|||
|
* @returns {string}
|
|||
|
* Extname.
|
|||
|
*/
|
|||
|
function extname(path) {
|
|||
|
assertPath(path)
|
|||
|
|
|||
|
let index = path.length
|
|||
|
|
|||
|
let end = -1
|
|||
|
let startPart = 0
|
|||
|
let startDot = -1
|
|||
|
// Track the state of characters (if any) we see before our first dot and
|
|||
|
// after any path separator we find.
|
|||
|
let preDotState = 0
|
|||
|
/** @type {boolean | undefined} */
|
|||
|
let unmatchedSlash
|
|||
|
|
|||
|
while (index--) {
|
|||
|
const code = path.codePointAt(index)
|
|||
|
|
|||
|
if (code === 47 /* `/` */) {
|
|||
|
// If we reached a path separator that was not part of a set of path
|
|||
|
// separators at the end of the string, stop now.
|
|||
|
if (unmatchedSlash) {
|
|||
|
startPart = index + 1
|
|||
|
break
|
|||
|
}
|
|||
|
|
|||
|
continue
|
|||
|
}
|
|||
|
|
|||
|
if (end < 0) {
|
|||
|
// We saw the first non-path separator, mark this as the end of our
|
|||
|
// extension.
|
|||
|
unmatchedSlash = true
|
|||
|
end = index + 1
|
|||
|
}
|
|||
|
|
|||
|
if (code === 46 /* `.` */) {
|
|||
|
// If this is our first dot, mark it as the start of our extension.
|
|||
|
if (startDot < 0) {
|
|||
|
startDot = index
|
|||
|
} else if (preDotState !== 1) {
|
|||
|
preDotState = 1
|
|||
|
}
|
|||
|
} else if (startDot > -1) {
|
|||
|
// We saw a non-dot and non-path separator before our dot, so we should
|
|||
|
// have a good chance at having a non-empty extension.
|
|||
|
preDotState = -1
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (
|
|||
|
startDot < 0 ||
|
|||
|
end < 0 ||
|
|||
|
// We saw a non-dot character immediately before the dot.
|
|||
|
preDotState === 0 ||
|
|||
|
// The (right-most) trimmed path component is exactly `..`.
|
|||
|
(preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
|
|||
|
) {
|
|||
|
return ''
|
|||
|
}
|
|||
|
|
|||
|
return path.slice(startDot, end)
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Join segments from a path.
|
|||
|
*
|
|||
|
* @param {Array<string>} segments
|
|||
|
* Path segments.
|
|||
|
* @returns {string}
|
|||
|
* File path.
|
|||
|
*/
|
|||
|
function join(...segments) {
|
|||
|
let index = -1
|
|||
|
/** @type {string | undefined} */
|
|||
|
let joined
|
|||
|
|
|||
|
while (++index < segments.length) {
|
|||
|
assertPath(segments[index])
|
|||
|
|
|||
|
if (segments[index]) {
|
|||
|
joined =
|
|||
|
joined === undefined ? segments[index] : joined + '/' + segments[index]
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return joined === undefined ? '.' : normalize(joined)
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Normalize a basic file path.
|
|||
|
*
|
|||
|
* @param {string} path
|
|||
|
* File path.
|
|||
|
* @returns {string}
|
|||
|
* File path.
|
|||
|
*/
|
|||
|
// Note: `normalize` is not exposed as `path.normalize`, so some code is
|
|||
|
// manually removed from it.
|
|||
|
function normalize(path) {
|
|||
|
assertPath(path)
|
|||
|
|
|||
|
const absolute = path.codePointAt(0) === 47 /* `/` */
|
|||
|
|
|||
|
// Normalize the path according to POSIX rules.
|
|||
|
let value = normalizeString(path, !absolute)
|
|||
|
|
|||
|
if (value.length === 0 && !absolute) {
|
|||
|
value = '.'
|
|||
|
}
|
|||
|
|
|||
|
if (value.length > 0 && path.codePointAt(path.length - 1) === 47 /* / */) {
|
|||
|
value += '/'
|
|||
|
}
|
|||
|
|
|||
|
return absolute ? '/' + value : value
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Resolve `.` and `..` elements in a path with directory names.
|
|||
|
*
|
|||
|
* @param {string} path
|
|||
|
* File path.
|
|||
|
* @param {boolean} allowAboveRoot
|
|||
|
* Whether `..` can move above root.
|
|||
|
* @returns {string}
|
|||
|
* File path.
|
|||
|
*/
|
|||
|
function normalizeString(path, allowAboveRoot) {
|
|||
|
let result = ''
|
|||
|
let lastSegmentLength = 0
|
|||
|
let lastSlash = -1
|
|||
|
let dots = 0
|
|||
|
let index = -1
|
|||
|
/** @type {number | undefined} */
|
|||
|
let code
|
|||
|
/** @type {number} */
|
|||
|
let lastSlashIndex
|
|||
|
|
|||
|
while (++index <= path.length) {
|
|||
|
if (index < path.length) {
|
|||
|
code = path.codePointAt(index)
|
|||
|
} else if (code === 47 /* `/` */) {
|
|||
|
break
|
|||
|
} else {
|
|||
|
code = 47 /* `/` */
|
|||
|
}
|
|||
|
|
|||
|
if (code === 47 /* `/` */) {
|
|||
|
if (lastSlash === index - 1 || dots === 1) {
|
|||
|
// Empty.
|
|||
|
} else if (lastSlash !== index - 1 && dots === 2) {
|
|||
|
if (
|
|||
|
result.length < 2 ||
|
|||
|
lastSegmentLength !== 2 ||
|
|||
|
result.codePointAt(result.length - 1) !== 46 /* `.` */ ||
|
|||
|
result.codePointAt(result.length - 2) !== 46 /* `.` */
|
|||
|
) {
|
|||
|
if (result.length > 2) {
|
|||
|
lastSlashIndex = result.lastIndexOf('/')
|
|||
|
|
|||
|
if (lastSlashIndex !== result.length - 1) {
|
|||
|
if (lastSlashIndex < 0) {
|
|||
|
result = ''
|
|||
|
lastSegmentLength = 0
|
|||
|
} else {
|
|||
|
result = result.slice(0, lastSlashIndex)
|
|||
|
lastSegmentLength = result.length - 1 - result.lastIndexOf('/')
|
|||
|
}
|
|||
|
|
|||
|
lastSlash = index
|
|||
|
dots = 0
|
|||
|
continue
|
|||
|
}
|
|||
|
} else if (result.length > 0) {
|
|||
|
result = ''
|
|||
|
lastSegmentLength = 0
|
|||
|
lastSlash = index
|
|||
|
dots = 0
|
|||
|
continue
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (allowAboveRoot) {
|
|||
|
result = result.length > 0 ? result + '/..' : '..'
|
|||
|
lastSegmentLength = 2
|
|||
|
}
|
|||
|
} else {
|
|||
|
if (result.length > 0) {
|
|||
|
result += '/' + path.slice(lastSlash + 1, index)
|
|||
|
} else {
|
|||
|
result = path.slice(lastSlash + 1, index)
|
|||
|
}
|
|||
|
|
|||
|
lastSegmentLength = index - lastSlash - 1
|
|||
|
}
|
|||
|
|
|||
|
lastSlash = index
|
|||
|
dots = 0
|
|||
|
} else if (code === 46 /* `.` */ && dots > -1) {
|
|||
|
dots++
|
|||
|
} else {
|
|||
|
dots = -1
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return result
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Make sure `path` is a string.
|
|||
|
*
|
|||
|
* @param {string} path
|
|||
|
* File path.
|
|||
|
* @returns {asserts path is string}
|
|||
|
* Nothing.
|
|||
|
*/
|
|||
|
function assertPath(path) {
|
|||
|
if (typeof path !== 'string') {
|
|||
|
throw new TypeError(
|
|||
|
'Path must be a string. Received ' + JSON.stringify(path)
|
|||
|
)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* eslint-enable max-depth, complexity */
|