import { regEx } from '../../../util/regex.ts'; import { GenericVersioningApi } from '../generic.ts'; import type { VersioningApi } from '../types.ts'; import type { DebVersion } from './types.ts'; export const id = 'deb'; export const displayName = 'Deb version'; export const urls = [ '[Debian policy - Version](https://www.debian.org/doc/debian-policy/ch-controlfields.html#version)', '[deb-version man page](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']; 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;