|
| 1 | +// .dirname, .basename, and .extname methods are extracted from Node.js v8.11.1, |
| 2 | +// backported and transplited with Babel, with backwards-compat fixes |
| 3 | + |
1 | 4 | // Copyright Joyent, Inc. and other Node contributors.
|
2 | 5 | //
|
3 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a
|
@@ -49,14 +52,6 @@ function normalizeArray(parts, allowAboveRoot) {
|
49 | 52 | return parts;
|
50 | 53 | }
|
51 | 54 |
|
52 |
| -// Split a filename into [root, dir, basename, ext], unix version |
53 |
| -// 'root' is just a slash, or nothing. |
54 |
| -var splitPathRe = |
55 |
| - /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; |
56 |
| -var splitPath = function(filename) { |
57 |
| - return splitPathRe.exec(filename).slice(1); |
58 |
| -}; |
59 |
| - |
60 | 55 | // path.resolve([from ...], to)
|
61 | 56 | // posix version
|
62 | 57 | exports.resolve = function() {
|
@@ -172,37 +167,120 @@ exports.relative = function(from, to) {
|
172 | 167 | exports.sep = '/';
|
173 | 168 | exports.delimiter = ':';
|
174 | 169 |
|
175 |
| -exports.dirname = function(path) { |
176 |
| - var result = splitPath(path), |
177 |
| - root = result[0], |
178 |
| - dir = result[1]; |
179 |
| - |
180 |
| - if (!root && !dir) { |
181 |
| - // No dirname whatsoever |
182 |
| - return '.'; |
| 170 | +exports.dirname = function (path) { |
| 171 | + if (typeof path !== 'string') path = path + ''; |
| 172 | + if (path.length === 0) return '.'; |
| 173 | + var code = path.charCodeAt(0); |
| 174 | + var hasRoot = code === 47 /*/*/; |
| 175 | + var end = -1; |
| 176 | + var matchedSlash = true; |
| 177 | + for (var i = path.length - 1; i >= 1; --i) { |
| 178 | + code = path.charCodeAt(i); |
| 179 | + if (code === 47 /*/*/) { |
| 180 | + if (!matchedSlash) { |
| 181 | + end = i; |
| 182 | + break; |
| 183 | + } |
| 184 | + } else { |
| 185 | + // We saw the first non-path separator |
| 186 | + matchedSlash = false; |
| 187 | + } |
183 | 188 | }
|
184 | 189 |
|
185 |
| - if (dir) { |
186 |
| - // It has a dirname, strip trailing slash |
187 |
| - dir = dir.substr(0, dir.length - 1); |
| 190 | + if (end === -1) return hasRoot ? '/' : '.'; |
| 191 | + if (hasRoot && end === 1) { |
| 192 | + // return '//'; |
| 193 | + // Backwards-compat fix: |
| 194 | + return '/'; |
188 | 195 | }
|
189 |
| - |
190 |
| - return root + dir; |
| 196 | + return path.slice(0, end); |
191 | 197 | };
|
192 | 198 |
|
| 199 | +function basename(path) { |
| 200 | + if (typeof path !== 'string') path = path + ''; |
| 201 | + |
| 202 | + var start = 0; |
| 203 | + var end = -1; |
| 204 | + var matchedSlash = true; |
| 205 | + var i; |
| 206 | + |
| 207 | + for (i = path.length - 1; i >= 0; --i) { |
| 208 | + if (path.charCodeAt(i) === 47 /*/*/) { |
| 209 | + // If we reached a path separator that was not part of a set of path |
| 210 | + // separators at the end of the string, stop now |
| 211 | + if (!matchedSlash) { |
| 212 | + start = i + 1; |
| 213 | + break; |
| 214 | + } |
| 215 | + } else if (end === -1) { |
| 216 | + // We saw the first non-path separator, mark this as the end of our |
| 217 | + // path component |
| 218 | + matchedSlash = false; |
| 219 | + end = i + 1; |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + if (end === -1) return ''; |
| 224 | + return path.slice(start, end); |
| 225 | +} |
193 | 226 |
|
194 |
| -exports.basename = function(path, ext) { |
195 |
| - var f = splitPath(path)[2]; |
196 |
| - // TODO: make this comparison case-insensitive on windows? |
| 227 | +// Uses a mixed approach for backwards-compatibility, as ext behavior changed |
| 228 | +// in new Node.js versions, so only basename() above is backported here |
| 229 | +exports.basename = function (path, ext) { |
| 230 | + var f = basename(path); |
197 | 231 | if (ext && f.substr(-1 * ext.length) === ext) {
|
198 | 232 | f = f.substr(0, f.length - ext.length);
|
199 | 233 | }
|
200 | 234 | return f;
|
201 | 235 | };
|
202 | 236 |
|
| 237 | +exports.extname = function (path) { |
| 238 | + if (typeof path !== 'string') path = path + ''; |
| 239 | + var startDot = -1; |
| 240 | + var startPart = 0; |
| 241 | + var end = -1; |
| 242 | + var matchedSlash = true; |
| 243 | + // Track the state of characters (if any) we see before our first dot and |
| 244 | + // after any path separator we find |
| 245 | + var preDotState = 0; |
| 246 | + for (var i = path.length - 1; i >= 0; --i) { |
| 247 | + var code = path.charCodeAt(i); |
| 248 | + if (code === 47 /*/*/) { |
| 249 | + // If we reached a path separator that was not part of a set of path |
| 250 | + // separators at the end of the string, stop now |
| 251 | + if (!matchedSlash) { |
| 252 | + startPart = i + 1; |
| 253 | + break; |
| 254 | + } |
| 255 | + continue; |
| 256 | + } |
| 257 | + if (end === -1) { |
| 258 | + // We saw the first non-path separator, mark this as the end of our |
| 259 | + // extension |
| 260 | + matchedSlash = false; |
| 261 | + end = i + 1; |
| 262 | + } |
| 263 | + if (code === 46 /*.*/) { |
| 264 | + // If this is our first dot, mark it as the start of our extension |
| 265 | + if (startDot === -1) |
| 266 | + startDot = i; |
| 267 | + else if (preDotState !== 1) |
| 268 | + preDotState = 1; |
| 269 | + } else if (startDot !== -1) { |
| 270 | + // We saw a non-dot and non-path separator before our dot, so we should |
| 271 | + // have a good chance at having a non-empty extension |
| 272 | + preDotState = -1; |
| 273 | + } |
| 274 | + } |
203 | 275 |
|
204 |
| -exports.extname = function(path) { |
205 |
| - return splitPath(path)[3]; |
| 276 | + if (startDot === -1 || end === -1 || |
| 277 | + // We saw a non-dot character immediately before the dot |
| 278 | + preDotState === 0 || |
| 279 | + // The (right-most) trimmed path component is exactly '..' |
| 280 | + preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { |
| 281 | + return ''; |
| 282 | + } |
| 283 | + return path.slice(startDot, end); |
206 | 284 | };
|
207 | 285 |
|
208 | 286 | function filter (xs, f) {
|
|
0 commit comments