5 changed files with 142 additions and 12 deletions
@ -0,0 +1,92 @@ |
|||
function defaultComparator(a, b) { |
|||
return a.localeCompare(b, undefined, { |
|||
numeric: true, |
|||
sensitivity: "base", |
|||
}); |
|||
} |
|||
|
|||
function compareSafe(a, b, comparator) { |
|||
if (a == null && b == null) return 0; |
|||
if (a != null && b == null) return 1; |
|||
if (b != null && a == null) return -1; |
|||
return comparator(a, b); |
|||
} |
|||
|
|||
type Comparator<T> = (a: T, b: T) => number; |
|||
type Entity = { |
|||
name: string; |
|||
isDirectory: boolean; |
|||
}; |
|||
type Opts = { |
|||
comparator?: Comparator<string>; |
|||
kinds?: boolean; |
|||
}; |
|||
|
|||
function createBetterDirectorySort(opts: Opts = {}): Comparator<Entity> { |
|||
let comparator = opts.comparator || defaultComparator; |
|||
let kinds = opts.kinds || false; |
|||
|
|||
return function betterDirectorySort(a, b) { |
|||
// directories should be placed before files
|
|||
if (a.isDirectory && !b.isDirectory) return -1; |
|||
if (b.isDirectory && !a.isDirectory) return 1; |
|||
|
|||
// dotsfiles should be placed before non-dotfiles
|
|||
let aDot = a.name[0] === "."; |
|||
let bDot = b.name[0] === "."; |
|||
if (aDot && !bDot) return -1; |
|||
if (bDot && !aDot) return 1; |
|||
|
|||
// names will be sorted in period-delimitted parts
|
|||
let aParts = a.name.split("."); |
|||
let bParts = b.name.split("."); |
|||
|
|||
// the first part of the name is the most important part
|
|||
let aName = aParts.shift(); |
|||
let bName = bParts.shift(); |
|||
|
|||
// final extension is only used when all else is the same
|
|||
let aExt = aParts.pop(); |
|||
let bExt = bParts.pop(); |
|||
|
|||
let comparedName = compareSafe(aName, bName, comparator); |
|||
if (comparedName !== 0) return comparedName; |
|||
|
|||
// "kind" extension is used above other parts
|
|||
if (kinds) { |
|||
let aKind = aParts.pop(); |
|||
let bKind = bParts.pop(); |
|||
|
|||
let comparedKind = compareSafe(aKind, bKind, comparator); |
|||
if (comparedKind !== 0) return comparedKind; |
|||
} |
|||
|
|||
// loop with an decrementing index
|
|||
let i = 0; |
|||
while (true) { |
|||
let aPart = aParts[i]; |
|||
let bPart = bParts[i]; |
|||
|
|||
// if there's no more parts on either name, break
|
|||
if (aPart == null && bPart == null) break; |
|||
|
|||
// compare the current part, if its the same, continue comparing parts
|
|||
let comparedPart = compareSafe(aPart, bPart, comparator); |
|||
if (comparedPart !== 0) return comparedPart; |
|||
|
|||
i++; |
|||
} |
|||
|
|||
// compare the last extension as a final tie-breaker
|
|||
return compareSafe(aExt, bExt, comparator); |
|||
}; |
|||
} |
|||
|
|||
const betterDirectorySort = createBetterDirectorySort(); |
|||
|
|||
// @ts-ignore
|
|||
betterDirectorySort.custom = createBetterDirectorySort; |
|||
// @ts-ignore
|
|||
betterDirectorySort.defaultComparator = defaultComparator; |
|||
|
|||
export { betterDirectorySort }; |
Loading…
Reference in new issue