diff --git a/polyfills/path.js b/polyfills/path.js index 57acd64..d7a33c3 100644 --- a/polyfills/path.js +++ b/polyfills/path.js @@ -23,6 +23,13 @@ // must be no slashes, empty elements, or device names (c:\) in the array // (so also no leading and trailing slashes - it does not distinguish // relative and absolute paths) + +function assertPath(path) { + if (typeof path !== 'string') { + throw new TypeError('Path must be a string. Received ' + JSON.stringify(path)); + } +} + function normalizeArray(parts, allowAboveRoot) { // if the path tries to go above the root, `up` ends up > 0 var up = 0; @@ -203,6 +210,83 @@ export function basename(path, ext) { export function extname(path) { return splitPath(path)[3]; } + +export function parse(path) { + assertPath(path); + + var ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) return ret; + var code = path.charCodeAt(0); + var isAbsolute = code === 47 /*/*/; + var start; + if (isAbsolute) { + ret.root = '/'; + start = 1; + } else { + start = 0; + } + var startDot = -1; + var startPart = 0; + var end = -1; + var matchedSlash = true; + var i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + var preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + code = path.charCodeAt(i); + 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 (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === 46 /*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i;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 === -1 || end === -1 || + // 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) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) ret.base = ret.name = path.slice(1, end);else ret.base = ret.name = path.slice(startPart, end); + } + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path.slice(1, startDot); + ret.base = path.slice(1, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + } + ret.ext = path.slice(startDot, end); + } + + if (startPart > 0) ret.dir = path.slice(0, startPart - 1);else if (isAbsolute) ret.dir = '/'; + + return ret; +} + export default { extname: extname, basename: basename, @@ -213,7 +297,8 @@ export default { join: join, isAbsolute: isAbsolute, normalize: normalize, - resolve: resolve + resolve: resolve, + parse: parse, }; function filter (xs, f) { if (xs.filter) return xs.filter(f); diff --git a/test/examples/path-parse.js b/test/examples/path-parse.js new file mode 100644 index 0000000..95f7297 --- /dev/null +++ b/test/examples/path-parse.js @@ -0,0 +1,18 @@ +import {parse} from 'path'; + +// test path.parse +var parseResult = parse('/home/user/dir/file.txt'); +var expectedResult = { + root: '/', + dir: '/home/user/dir', + base: 'file.txt', + ext: '.txt', + name: 'file' +}; +if (Object.keys(expectedResult).every( + key => expectedResult[key] === parseResult[key] + )) { + done(); +} else { + done(new Error(`parse error, expected ${JSON.stringify(expectedResult)} but got '${JSON.stringify(parseResult)}'`)); +} diff --git a/test/index.js b/test/index.js index 106bf7e..d25fe46 100644 --- a/test/index.js +++ b/test/index.js @@ -13,6 +13,7 @@ const files = [ 'constants.js', 'os.js', 'path.js', + 'path-parse.js', 'string-decoder.js', 'zlib.js', 'domain.js',