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() )
 |