417 lines
8.3 KiB
TypeScript
417 lines
8.3 KiB
TypeScript
import { compiler } from "melodyc";
|
|
|
|
/*
|
|
VERSION_PATTERN = r"""
|
|
v?
|
|
(?:
|
|
(?:(?P<epoch>[0-9]+)!)? # epoch
|
|
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
|
|
(?P<pre> # pre-release
|
|
[-_\.]?
|
|
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
|
[-_\.]?
|
|
(?P<pre_n>[0-9]+)?
|
|
)?
|
|
(?P<post> # post release
|
|
(?:-(?P<post_n1>[0-9]+))
|
|
|
|
|
(?:
|
|
[-_\.]?
|
|
(?P<post_l>post|rev|r)
|
|
[-_\.]?
|
|
(?P<post_n2>[0-9]+)?
|
|
)
|
|
)?
|
|
(?P<dev> # dev release
|
|
[-_\.]?
|
|
(?P<dev_l>dev)
|
|
[-_\.]?
|
|
(?P<dev_n>[0-9]+)?
|
|
)?
|
|
)
|
|
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
|
"""
|
|
*/
|
|
|
|
const VERSION_PATTERN = `
|
|
<start>;
|
|
|
|
option of "v";
|
|
|
|
match {
|
|
option of match {
|
|
capture epoch {
|
|
some of <digit>;
|
|
}
|
|
"!";
|
|
}
|
|
|
|
capture release {
|
|
some of <digit>;
|
|
any of match {
|
|
".";
|
|
some of <digit>;
|
|
}
|
|
}
|
|
|
|
option of capture pre {
|
|
option of either {
|
|
".";
|
|
"_";
|
|
"-";
|
|
}
|
|
capture pre_l {
|
|
either {
|
|
"a";
|
|
"b";
|
|
"c";
|
|
"rc";
|
|
"alpha";
|
|
"beta";
|
|
"pre";
|
|
"preview";
|
|
}
|
|
}
|
|
option of either {
|
|
".";
|
|
"_";
|
|
"-";
|
|
}
|
|
capture pre_n {
|
|
some of <digit>;
|
|
}
|
|
}
|
|
|
|
option of capture post {
|
|
either {
|
|
match {
|
|
"-";
|
|
capture post_n {
|
|
some of <digit>;
|
|
}
|
|
}
|
|
|
|
match {
|
|
option of either {
|
|
".";
|
|
"_";
|
|
"-";
|
|
}
|
|
capture post_l {
|
|
either {
|
|
"post";
|
|
"rev";
|
|
"r";
|
|
}
|
|
}
|
|
option of either {
|
|
".";
|
|
"_";
|
|
"-";
|
|
}
|
|
option of capture post_n {
|
|
some of <digit>;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
option of capture dev {
|
|
option of either {
|
|
".";
|
|
"_";
|
|
"-";
|
|
}
|
|
capture dev_l {
|
|
"dev";
|
|
}
|
|
option of either {
|
|
".";
|
|
"_";
|
|
"-";
|
|
}
|
|
option of capture dev_n {
|
|
some of <digit>;
|
|
}
|
|
}
|
|
|
|
option of match {
|
|
"+";
|
|
capture local {
|
|
some of either {
|
|
a to z;
|
|
0 to 9;
|
|
}
|
|
|
|
any of match {
|
|
option of either {
|
|
".";
|
|
"_";
|
|
"-";
|
|
}
|
|
some of either {
|
|
a to z;
|
|
0 to 9;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// get rid of random bullshit at the end
|
|
capture random_bullshit {
|
|
any of <char>;
|
|
}
|
|
|
|
<end>;
|
|
`;
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
|
|
const output = compiler(VERSION_PATTERN);
|
|
console.log(output);
|
|
const VERSION_REGEX = new RegExp(output);
|
|
|
|
|
|
export default class Pep440Version {
|
|
epoch?: number;
|
|
release: {
|
|
major: number;
|
|
minor: number;
|
|
patch: number;
|
|
};
|
|
preRelease?: {
|
|
name: string;
|
|
version?: number;
|
|
};
|
|
postRelease?: {
|
|
name?: string;
|
|
version?: number;
|
|
};
|
|
dev?: {
|
|
name: string;
|
|
version?: number;
|
|
};
|
|
|
|
random_bullshit_at_the_end?: string;
|
|
|
|
|
|
|
|
/**
|
|
*
|
|
* @param {string} versionString
|
|
*/
|
|
constructor(versionString: string) {
|
|
console.log(`Constructing version from ${versionString}`);
|
|
|
|
const match = versionString.match(VERSION_REGEX);
|
|
|
|
if( match === null || match.groups === undefined ) {
|
|
throw new Error("Couldn't parse version: " + versionString)
|
|
}
|
|
|
|
const g = match.groups;
|
|
|
|
if( g['release'] === undefined ) {
|
|
throw new Error("Couldn't parse version (because of the absence of a release): " + versionString)
|
|
}
|
|
|
|
const [ major, minor, patch ] = g['release'].split(".").map( e => Number(e) );
|
|
|
|
this.release = {
|
|
major: major ?? 0,
|
|
minor: minor ?? 0,
|
|
patch: patch ?? 0,
|
|
}
|
|
|
|
this.epoch = Number(g['epoch']);
|
|
|
|
if( g['pre'] ) {
|
|
this.preRelease = {
|
|
name: g['pre_l'],
|
|
version: g['pre_n'] ? Number(g['pre_n']) : undefined,
|
|
}
|
|
}
|
|
|
|
if( g['post'] ) {
|
|
this.postRelease = {
|
|
name: g['post_l'],
|
|
version: g['post_n'] ? Number(g['post_n']) : undefined,
|
|
}
|
|
}
|
|
|
|
if( g['dev'] ) {
|
|
this.dev = {
|
|
name: g['dev_l'],
|
|
version: g['dev_n'] ? Number(g['dev_n']) : undefined,
|
|
}
|
|
}
|
|
|
|
this.random_bullshit_at_the_end = g['random_bullshit'];
|
|
}
|
|
|
|
/**
|
|
* @returns the version (as as tring) in a format that can be correctly compared
|
|
* even if it looks goofy
|
|
*/
|
|
toComparableString() {
|
|
// comparing strings is annoying
|
|
// for example: "1.2" > "1.12", even though 12 > 2
|
|
//
|
|
// To fix that issue, we're going to have fixed-length numbers,
|
|
// so that comparing 1.2 and 1.12 would mean comparing
|
|
// "000012" > "000002", this one being correct because 1 > 0.
|
|
|
|
// pad to 8 because who in their right mind would use 8 digits for the major version
|
|
// (noting that some people use 4 because they use years)
|
|
const PAD_LENGTH = 8;
|
|
|
|
const epoch = (this.epoch?.toString() ?? '0').padStart(PAD_LENGTH, '0');
|
|
|
|
const release_major = (this.release.major.toString() ?? '0').padStart(PAD_LENGTH, '0');
|
|
const release_minor = (this.release.minor.toString() ?? '0').padStart(PAD_LENGTH, '0');
|
|
const release_patch = (this.release.patch.toString() ?? '0').padStart(PAD_LENGTH, '0');
|
|
const release = `${release_major}.${release_minor}.${release_patch}`;
|
|
|
|
const prename = (this.preRelease?.name || '').padStart(PAD_LENGTH, '0');
|
|
const postname = (this.postRelease?.name || '').padStart(PAD_LENGTH, '0');
|
|
const devname = (this.dev?.name || '').padStart(PAD_LENGTH, '0');
|
|
|
|
|
|
const prever = (this.preRelease?.version?.toString() || '0').padStart(PAD_LENGTH, '0');
|
|
const postver = (this.postRelease?.version?.toString() || '0').padStart(PAD_LENGTH, '0');
|
|
const devver = (this.dev?.version?.toString() || '0').padStart(PAD_LENGTH, '0');
|
|
|
|
return `${epoch}!${release}.${prename}${prever}.${postname}${postver}.${devname}${devver}`;
|
|
}
|
|
|
|
isAbove(other: Pep440Version) {
|
|
return this.toComparableString() > other.toComparableString();
|
|
}
|
|
|
|
isBelow(other: Pep440Version) {
|
|
return this.toComparableString() < other.toComparableString();
|
|
}
|
|
|
|
isEqual(other: Pep440Version) {
|
|
return this.toComparableString() === other.toComparableString();
|
|
}
|
|
|
|
compare(other: Pep440Version) {
|
|
if( this.isAbove(other) ) {
|
|
return -1;
|
|
}
|
|
else if( this.isBelow(other) ) {
|
|
return 1;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @returns the version as a Pep440-compliant, human-readable string (e.g. "2.1.3")
|
|
*/
|
|
releaseString() {
|
|
let str = "";
|
|
|
|
if( this.epoch ) {
|
|
str += `${this.epoch}!`;
|
|
}
|
|
str += this.release.major ?? 0;
|
|
str += '.';
|
|
str += this.release.minor ?? 0;
|
|
str += '.';
|
|
str += this.release.patch ?? 0;
|
|
|
|
if(this.preRelease) {
|
|
str += '.';
|
|
if( this.preRelease.name ) {
|
|
str += this.preRelease.name;
|
|
}
|
|
if( this.preRelease.version ) {
|
|
str += this.preRelease.version;
|
|
}
|
|
}
|
|
|
|
if(this.postRelease) {
|
|
str += '.';
|
|
if( this.postRelease.name ) {
|
|
str += this.postRelease.name;
|
|
}
|
|
if( this.postRelease.version ) {
|
|
str += this.postRelease.version;
|
|
}
|
|
}
|
|
|
|
if(this.dev) {
|
|
str += '.';
|
|
if( this.dev.name ) {
|
|
str += this.dev.name;
|
|
}
|
|
if( this.dev.version ) {
|
|
str += this.dev.version;
|
|
}
|
|
}
|
|
|
|
if( this.random_bullshit_at_the_end ) {
|
|
str += this.random_bullshit_at_the_end;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
//static fromString(...versionStrings: string[]) {
|
|
// return versionStrings.map( v => new Pep440Version(v) );
|
|
//}
|
|
}
|
|
|
|
type OPERATOR_FUNCTION = (source: Pep440Version, target: Pep440Version) => boolean
|
|
|
|
type OPERATORS_FN_type = {
|
|
[key: string]: OPERATOR_FUNCTION;
|
|
};
|
|
|
|
const OPERATORS_FUNCTIONS: OPERATORS_FN_type = {
|
|
">=": (source, target) => target.isAbove(source) || target.isEqual(source),
|
|
">": (source, target) => target.isAbove(source),
|
|
"==": (source, target) => target.isEqual(source),
|
|
"": (source, target) => true,
|
|
};
|
|
|
|
export class Pep440VersionRule {
|
|
version: Pep440Version;
|
|
operator: OPERATOR_FUNCTION;
|
|
|
|
constructor(version: Pep440Version, operator: string = "") {
|
|
this.version = version;
|
|
this.operator = OPERATORS_FUNCTIONS[operator];
|
|
}
|
|
|
|
/**
|
|
*/
|
|
getSortedMatchingVersions(versions: Pep440Version[]) {
|
|
const sorted = versions.sort( (a, b) => a.compare(b) );
|
|
|
|
let filtered;
|
|
if( typeof this.operator === "function" ) {
|
|
filtered = sorted.filter( v => this.operator( this.version, v ) );
|
|
}
|
|
else {
|
|
filtered = sorted;
|
|
}
|
|
|
|
|
|
return filtered;
|
|
}
|
|
|
|
static getRuleMatchingAnyVersion() {
|
|
return new Pep440VersionRule(new Pep440Version("0.0.0"))
|
|
}
|
|
}
|
|
|
|
//console.log( new Pep440Version("4!1.2.3dev2").toComparableString() )
|