|
| 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