-
Notifications
You must be signed in to change notification settings - Fork 3k
Expand file tree
/
Copy pathindex.ts
More file actions
149 lines (138 loc) · 5.15 KB
/
index.ts
File metadata and controls
149 lines (138 loc) · 5.15 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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { regEx } from '../../../util/regex.ts';
import type { GenericVersion } from '../generic.ts';
import { GenericVersioningApi } from '../generic.ts';
import type { VersioningApi } from '../types.ts';
export const id = 'deb';
export const displayName = 'Deb version';
export const urls = [
'https://www.debian.org/doc/debian-policy/ch-controlfields.html#version',
'https://manpages.debian.org/unstable/dpkg-dev/deb-version.7.en.html',
];
export const supportsRanges = false;
const epochPattern = regEx(/^\d+$/);
const upstreamVersionPattern = regEx(/^[-+.:~A-Za-z\d]+$/);
const debianRevisionPattern = regEx(/^[+.~A-Za-z\d]*$/);
const numericPattern = regEx(/\d+/g);
const characterOrder =
'~ ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-.:';
const numericChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
export interface DebVersion extends GenericVersion {
/**
* epoch, defaults to 0 if not present, are used to leave version mistakes and previous
* versioning schemes behind.
*/
epoch: number;
/**
* upstreamVersion is the main version part: it defines the version of origin software
* that was packaged.
*/
upstreamVersion: string;
/**
* debianRevision is used to distinguish between different versions of packaging for the
* same upstream version.
*/
debianRevision: string;
}
class DebVersioningApi extends GenericVersioningApi {
protected _parse(version: string): DebVersion | null {
/* Splitting version into "[epoch:]upstream-version[-debian-revision]"
All found numbers are exported as release info */
// split of first element by `:` as epoch:
const epochSplit = version.split(':');
const epochStr = epochSplit.length > 1 ? epochSplit.shift()! : '0';
const remainingVersion = epochSplit.join(':');
// split of last element by `-`
if (remainingVersion.endsWith('-')) {
// Forbid debian revision (it would result in `2.4.0-` == `2.4.0`)
return null;
}
const debianSplit = remainingVersion.split('-');
const debianRevision = debianSplit.length > 1 ? debianSplit.pop()! : '';
const upstreamVersion = debianSplit.join('-');
if (
!epochPattern.test(epochStr) ||
!upstreamVersionPattern.test(upstreamVersion) ||
!debianRevisionPattern.test(debianRevision)
) {
return null;
}
const release = [...remainingVersion.matchAll(numericPattern)].map((m) =>
parseInt(m[0], 10),
);
return {
epoch: parseInt(epochStr, 10),
upstreamVersion,
debianRevision,
release,
suffix: debianRevision,
};
}
protected _compare_string(a: string, b: string): number {
/* Special string sorting based on official specification:
* https://manpages.debian.org/unstable/dpkg-dev/deb-version.7.en.html#Sorting_algorithm
* The string is compared by continuous blocks of a) non-digit and b) digit characters.
* Non-digit blocks are compared lexicographically with a custom character order.
* Digit blocks are compared numerically.
* We are alternating between both modes until a difference is found.
*/
let charPos = 0;
while (charPos < a.length || charPos < b.length) {
const aChar = a.charAt(charPos);
const bChar = b.charAt(charPos);
if (numericChars.includes(aChar) && numericChars.includes(bChar)) {
// numeric comparison of the whole block
let aNumericEnd = charPos + 1;
while (numericChars.includes(a.charAt(aNumericEnd))) {
aNumericEnd += 1;
}
let bNumericEnd = charPos + 1;
while (numericChars.includes(b.charAt(bNumericEnd))) {
bNumericEnd += 1;
}
const numericCmp = a
.substring(charPos, aNumericEnd)
.localeCompare(b.substring(charPos, bNumericEnd), undefined, {
numeric: true,
});
if (numericCmp !== 0) {
return numericCmp;
}
charPos = aNumericEnd; // the same as bNumericEnd as both are the same
continue;
}
if (aChar !== bChar) {
// Lexicographical comparison
// numeric character is treated like end of string (they are part of a new block)
const aPriority = characterOrder.indexOf(
numericChars.includes(aChar) || aChar === '' ? ' ' : aChar,
);
const bPriority = characterOrder.indexOf(
numericChars.includes(bChar) || bChar === '' ? ' ' : bChar,
);
return Math.sign(aPriority - bPriority);
}
charPos += 1;
}
return 0;
}
protected override _compare(version: string, other: string): number {
const parsed1 = this._parse(version);
const parsed2 = this._parse(other);
if (!(parsed1 && parsed2)) {
return 1;
}
if (parsed1.epoch !== parsed2.epoch) {
return Math.sign(parsed1.epoch - parsed2.epoch);
}
const upstreamVersionDifference = this._compare_string(
parsed1.upstreamVersion,
parsed2.upstreamVersion,
);
if (upstreamVersionDifference !== 0) {
return upstreamVersionDifference;
}
return this._compare_string(parsed1.debianRevision, parsed2.debianRevision);
}
}
export const api: VersioningApi = new DebVersioningApi();
export default api;