-
Notifications
You must be signed in to change notification settings - Fork 429
Expand file tree
/
Copy pathutils.ts
More file actions
111 lines (95 loc) · 2.81 KB
/
Copy pathutils.ts
File metadata and controls
111 lines (95 loc) · 2.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
function normalizeTemplateKey(type: string): string {
return type
.trim()
.toLowerCase()
.replace(/[_\s]+/g, '-')
.replace(/-+/g, '-');
}
function getLevenshteinDistance(source: string, target: string): number {
if (source === target) return 0;
if (!source.length) return target.length;
if (!target.length) return source.length;
const previous = Array.from(
{ length: target.length + 1 },
(_, index) => index,
);
const current = new Array<number>(target.length + 1);
for (let sourceIndex = 1; sourceIndex <= source.length; sourceIndex += 1) {
current[0] = sourceIndex;
const sourceCode = source.charCodeAt(sourceIndex - 1);
for (let targetIndex = 1; targetIndex <= target.length; targetIndex += 1) {
const replaceCost =
sourceCode === target.charCodeAt(targetIndex - 1) ? 0 : 1;
current[targetIndex] = Math.min(
previous[targetIndex] + 1,
current[targetIndex - 1] + 1,
previous[targetIndex - 1] + replaceCost,
);
}
for (let index = 0; index < current.length; index += 1) {
previous[index] = current[index];
}
}
return previous[target.length];
}
function getCommonPrefixLength(source: string, target: string): number {
const limit = Math.min(source.length, target.length);
let index = 0;
while (
index < limit &&
source.charCodeAt(index) === target.charCodeAt(index)
) {
index += 1;
}
return index;
}
function isBetterMatch(
bestMatch: string | undefined,
bestDistance: number,
bestPrefixLength: number,
candidateKey: string,
candidateDistance: number,
candidatePrefixLength: number,
): boolean {
return (
candidateDistance < bestDistance ||
(candidateDistance === bestDistance &&
candidatePrefixLength > bestPrefixLength) ||
(candidateDistance === bestDistance &&
candidatePrefixLength === bestPrefixLength &&
(!bestMatch || candidateKey < bestMatch))
);
}
export function findClosestTemplateKey(
type: string,
keys: Iterable<string>,
): string | undefined {
const normalizedType = normalizeTemplateKey(type);
if (!normalizedType) return undefined;
let bestMatch: string | undefined;
let bestDistance = Number.POSITIVE_INFINITY;
let bestPrefixLength = -1;
for (const key of keys) {
const normalizedKey = normalizeTemplateKey(key);
if (normalizedKey === normalizedType) {
return key;
}
const distance = getLevenshteinDistance(normalizedType, normalizedKey);
const prefixLength = getCommonPrefixLength(normalizedType, normalizedKey);
if (
isBetterMatch(
bestMatch,
bestDistance,
bestPrefixLength,
key,
distance,
prefixLength,
)
) {
bestMatch = key;
bestDistance = distance;
bestPrefixLength = prefixLength;
}
}
return bestMatch;
}