-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
basename.ts
137 lines (120 loc) · 3.48 KB
/
basename.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
* @file basename
* @module pathe/lib/basename
*/
import ensurePosix from '#src/internal/ensure-posix'
import isDrivePath from '#src/internal/is-drive-path'
import isSep from '#src/internal/is-sep'
import validateString from '#src/internal/validate-string'
import { at, isUndefined } from '@flex-development/tutils'
/**
* Returns the last portion of a `path`, similar to the Unix `basename` command.
*
* Trailing [directory separators][1] are ignored.
*
* [1]: https://nodejs.org/api/path.html#pathsep
*
* @param {string} path - Path to evaluate
* @param {string} [suffix] - Suffix to remove from result
* @return {string} Last portion of `path`
* @throws {TypeError} If `path` is not a string or `suffix` is not a string
*/
const basename = (path: string, suffix?: string): string => {
validateString(path, 'path')
!isUndefined(suffix) && validateString(suffix, 'suffix')
// ensure path and suffix meet posix standards
path = ensurePosix(path)
!isUndefined(suffix) && (suffix = ensurePosix(suffix))
// return empty string if path and suffix are equal
if (path === suffix) return ''
/**
* Start index of basename.
*
* @var {number} start
*/
let start: number = 0
/**
* End index of basename.
*
* @var {number} end
*/
let end: number = -1
/**
* Path separator match check.
*
* @var {boolean} sep_match
*/
let sep_match: boolean = true
// check for drive path so as not to mistake the following path separator as
// an extra separator at the end of the path that can be disregarded
if (path.length >= 2 && isDrivePath(path)) start = 2
// get basename without attempting to remove suffix
if (!suffix || suffix.length > path.length) {
for (let i = path.length - 1; i >= start; --i) {
if (isSep(path.charAt(i))) {
// encountered separator that is not trailing separator
if (!sep_match) {
start = i + 1
break
}
} else if (end === -1) {
// encountered character that is not a separator => end path component
sep_match = false
end = i + 1
}
}
// basename can be safely extracted using slice when end is greater than -1
return end > -1 ? path.slice(start, end) : ''
}
/**
* Index of {@linkcode suffix} in {@linkcode path}.
*
* @var {number} sdx
*/
let sdx: number = suffix.length - 1
/**
* Index of character that is not a path separator.
*
* @var {number} nonsep
*/
let nonsep: number = -1
for (let i = path.length - 1; i >= 0; --i) {
/**
* Character at {@linkcode i} in {@linkcode path}.
*
* @const {string} char
*/
const char: string = at(path, i)
if (isSep(char)) {
// encountered separator that is not trailing separator
if (!sep_match) {
start = i + 1
break
}
} else {
if (nonsep === -1) {
// set index of character that is not a path separator in case suffix is
// not found in path
nonsep = i + 1
sep_match = false
}
if (sdx >= 0) {
// try matching suffix
if (char === at(suffix, sdx)) {
// end path component
if (--sdx === -1) end = i
} else {
// if suffix is not found, basename stops at last index of character
// that is not a path separator
end = nonsep
sdx = -1
}
}
}
}
return path.slice(
start,
start === end ? nonsep : end === -1 ? path.length : end
)
}
export default basename