UNPKG

rucken

Version:

Console tools and scripts for nx and not only that I (EndyKaufman) use to automate the workflow and speed up the development process

318 lines (317 loc) 13.5 kB
"use strict"; /* eslint-disable @typescript-eslint/no-explicit-any */ /** * @fileoverview Cross-platform file path sorting library for Node.js * @author Milos Djermanovic <milos.djermanovic@gmail.com> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.windowsSort = exports.posixSort = exports.sort = void 0; const tslib_1 = require("tslib"); const path_1 = tslib_1.__importDefault(require("path")); // Path types. Order of keys defines default order of path types in the sorted array. const posixPathType = { rel: 1, home: 2, abs: 3, // /... }; const windowsPathType = { rel: 1, home: 2, abs: 3, drel: 4, dabs: 5, unc: 6, nms: 7, // \\?\... \\.\... }; const otherPlatformPathType = { nonroot: 1, }; const otherRoot = 0; /** * Sort options object. * * @typedef {Object} SortOptions * @property {string} [pathKey] Key of a property whose value is path string. * @property {boolean} [shallowFirst] If true, content of a directory will come before the content of its subdirectories. * @property {boolean} [deepFirst] If true, content of a directory will come after the content of its subdirectories. * @property {boolean} [homePathsSupported] If true, paths starting with '~' will be treated as 'home' paths. * @property {string[]} [posixOrder] POSIX path types order. Permutation of ['rel', 'home', 'abs']. * @property {string[]} [windowsOrder] Windows path types order. Permutation of ['rel', 'home', 'abs', 'drel', 'dabs', 'unc', 'nms']. * @property {Function} [segmentCompareFn] Function used to compare path segments. */ /** * Internal cross-platform file path sorting function. */ function _sort(Path, paths, options) { const { pathKey, shallowFirst, deepFirst, homePathsSupported, posixOrder, windowsOrder, segmentCompareFn, } = Object.assign({ shallowFirst: false, deepFirst: false, homePathsSupported: false, segmentCompareFn: (a, b) => a.localeCompare(b) }, options); validateOptions(); // Never throw an error caused by the paths argument. It can be anything, and it can contain anything. if (!Array.isArray(paths)) return paths; const pathOrder = preparePathOrder(); // Never call prototype methods on entry arrays and objects directly. const parsedPaths = [].map.call(paths, parse); // Both map and sort will preserve holes. Sort will put holes at the end. return parsedPaths .sort(compare) .map((parsedPath) => parsedPath.original); function validateOptions() { if (pathKey !== undefined && typeof pathKey !== 'string') { throw new Error('Invalid arguments: pathKey must be a string or undefined.'); } if (deepFirst && shallowFirst) { throw new Error('Invalid arguments: Only one of shallowFirst and deepFirst can have a truthy value.'); } validateOrder(posixPathType, posixOrder, 'posixOrder'); validateOrder(windowsPathType, windowsOrder, 'windowsOrder'); if (typeof segmentCompareFn !== 'function') { throw new Error('Invalid argument: segmentCompareFunction must be a function or undefined.'); } } function validateOrder(type, order, paramName) { if (order !== undefined) { if (!Array.isArray(order)) { throw new Error(`Invalid arguments: ${paramName} must be an array or undefined.`); } const keys = Object.keys(type); if (keys.length !== order.length || !keys.every((key) => [].indexOf.call(order, key) >= 0)) { throw new Error(`Invalid arguments: ${paramName} must be a permutation of ${JSON.stringify(keys)} array or undefined.`); } } } function preparePathOrder() { let order; // Prepare sort order by path type. Map over keys can be replaced with Object.values from ES2017 if (Path.sep === '/') { // Posix if (posixOrder) { // Custom order = [].map.call(posixOrder, (key) => posixPathType[key]); } else { // Predefined order = Object.keys(posixPathType).map((key) => posixPathType[key]); } } else if (Path.sep === '\\') { // Windows if (windowsOrder) { // Custom order = [].map.call(windowsOrder, (key) => windowsPathType[key]); } else { // Predefined order = Object.keys(windowsPathType).map((key) => windowsPathType[key]); } } else { // Other platform order = Object.keys(otherPlatformPathType).map((key) => otherPlatformPathType[key]); } // Paths with unrecognized parsed root will be at the end. It should never happen, though. order.push(otherRoot); return order; } function parse(path) { const original = path; // Find the path string in the given element let pathString = undefined; if (typeof path === 'string') { pathString = path; } else if (pathKey !== undefined && path !== null && typeof path === 'object' && typeof path[pathKey] === 'string') { pathString = path[pathKey]; } if (pathString === undefined) { // Unreadable element return { original, pathString }; } // Parse and normalize never throw if you send a string, and always return strings const normalizedPathString = Path.normalize(pathString); const normalized = Path.parse(normalizedPathString); const { root, base } = normalized; let { dir } = normalized; let pathType = undefined; if (Path.sep === '/') { // Posix if (root) { if (root === '/') { pathType = posixPathType.abs; // Optimization. Also, without this line files from the root dir would be // wrongly treated as files from /""/ (empty name dir in the root dir) dir = dir.slice(1); } else { pathType = otherRoot; } } else { if (homePathsSupported && pathString.startsWith('~')) { pathType = posixPathType.home; } else { pathType = posixPathType.rel; } } } else if (Path.sep === '\\') { // Windows if (root) { if (root.startsWith('\\\\')) { if (root.length > 2 && (root[2] === '?' || root[2] === '.')) { // There is no need to check root[3], it must be '\\' // Otherwise, normalize() and parse() would produce a completely different root pathType = windowsPathType.nms; } else { pathType = windowsPathType.unc; } if (dir.endsWith('\\')) { // Optimization. Also, remove separator from the end to avoid the empty name subdir problem dir = dir.slice(2, -1); } else { // Optimization dir = dir.slice(2); } } else if (root.startsWith('\\')) { pathType = windowsPathType.abs; // Optimization. Also, without this line files from the root dir would be // wrongly treated as files from \""\ (empty name dir in the root dir) dir = dir.slice(1); } else if (root.endsWith(':\\')) { pathType = windowsPathType.dabs; if (dir.endsWith('\\')) { // This can be only the root dir (e.g. C:\), that's how parse() works. // Remove ':' and also remove separator from the end to avoid the empty name subdir problem dir = `${dir.slice(0, root.length - 2)}${dir.slice(root.length - 1, -1)}`; } else { // Remove ':' dir = `${dir.slice(0, root.length - 2)}${dir.slice(root.length - 1)}`; } } else if (root.endsWith(':')) { pathType = windowsPathType.drel; if (dir.length === root.length) { // Remove ':' dir = dir.slice(0, -1); } else { // Remove ':' and add `\\` between drive and subdir as they should be individually compared dir = `${dir.slice(0, root.length - 1)}\\${dir.slice(root.length)}`; } } else { pathType = otherRoot; } } else { if (homePathsSupported && pathString.startsWith('~')) { pathType = windowsPathType.home; } else { pathType = windowsPathType.rel; } } } else { // Other platform if (root) { pathType = otherRoot; } else { pathType = otherPlatformPathType.nonroot; } } // Root is always included in dir const dirs = dir ? dir.split(Path.sep) : []; return { original, pathString, normalizedPathString, pathType, dirs, base }; } function compare({ pathString: leftPathString, normalizedPathString: leftNormalizedPathString, pathType: leftPathType, dirs: leftDirs, base: leftBase, }, { pathString: rightPathString, normalizedPathString: rightNormalizedPathString, pathType: rightPathType, dirs: rightDirs, base: rightBase, }) { // Unreadable elements will be at the end, before holes const leftUnreadable = leftPathString === undefined ? 1 : 0; const rightUnreadable = rightPathString === undefined ? -1 : 0; if (leftUnreadable || rightUnreadable) { return leftUnreadable + rightUnreadable; } // Different types of paths are never directly compared if (leftPathType !== rightPathType) { // Find first in the list, which should always contain both. for (const pathType of pathOrder) { if (pathType === leftPathType) return -1; if (pathType === rightPathType) return 1; } } // Same types of paths. Compare dirs first. for (let i = 0; i < leftDirs.length && i < rightDirs.length; i++) { const result = segmentCompareFn(leftDirs[i], rightDirs[i]); if (result !== 0) return result; } // Dirs match, but one path might be deeper. if (leftDirs.length === rightDirs.length) { // Dirs completely match. const result = segmentCompareFn(leftBase, rightBase); if (result !== 0) return result; // Two normalized paths are equal, compare full unnormalized versions. return segmentCompareFn(leftPathString, rightPathString); } else if (!(deepFirst || shallowFirst)) { // Base vs directory const result = leftDirs.length < rightDirs.length ? segmentCompareFn(leftBase, rightDirs[leftDirs.length]) : segmentCompareFn(leftDirs[rightDirs.length], rightBase); if (result !== 0) return result; // Directory vs its content, compare full normalized paths return segmentCompareFn(leftNormalizedPathString, rightNormalizedPathString); } else { // Deep vs shallow. Exactly one of deepFirst and shallowFirst is true, no need to test both. return ((leftDirs.length < rightDirs.length ? -1 : 1) * (deepFirst ? -1 : 1)); } } } /** * Cross-platform file path sorting function. * * @param {(string[]|any[])} paths Array of path strings or objects containing path strings. * @param {SortOptions} [options] Sort options. * @returns {(string[]|any[])} Sorted array. */ function sort(paths, options) { return _sort(path_1.default, paths, options); } exports.sort = sort; /** * POSIX-specific file path sorting function. * * @param {(string[]|any[])} paths Array of path strings or objects containing path strings. * @param {SortOptions} [options] Sort options. * @returns {(string[]|any[])} Sorted array. */ function posixSort(paths, options) { return _sort(path_1.default.posix, paths, options); } exports.posixSort = posixSort; /** * Windows-specific file path sorting function. * * @param {(string[]|any[])} paths Array of path strings or objects containing path strings. * @param {SortOptions} [options] Sort options. * @returns {(string[]|any[])} Sorted array. */ function windowsSort(paths, options) { return _sort(path_1.default.win32, paths, options); } exports.windowsSort = windowsSort;