Skip to main content
Go to Latest
File
import { matches } from "../dependencies.ts";
const starSub = /((\d+\.)*\d+)\.\*/;
function incrementLastNumber(list: number[]) { const newList = [...list]; newList[newList.length - 1]++; return newList;}/** * Will take in a range, like `>1.2` or `<1.2.3.4` or `=1.2` or `1.*` * and return a checker, that has the check function for checking that a version is in the valid * @param range * @returns */export function rangeOf(range: string | Checker): Checker { return Checker.parse(range);}
/** * Used to create a checker that will `and` all the ranges passed in * @param ranges * @returns */export function rangeAnd(...ranges: (string | Checker)[]): Checker { if (ranges.length === 0) { throw new Error("No ranges given"); } const [firstCheck, ...rest] = ranges; return Checker.parse(firstCheck).and(...rest);}
/** * Used to create a checker that will `or` all the ranges passed in * @param ranges * @returns */export function rangeOr(...ranges: (string | Checker)[]): Checker { if (ranges.length === 0) { throw new Error("No ranges given"); } const [firstCheck, ...rest] = ranges; return Checker.parse(firstCheck).or(...rest);}
/** * This will negate the checker, so given a checker that checks for >= 1.0.0, it will check for < 1.0.0 * @param range * @returns */export function notRange(range: string | Checker): Checker { return rangeOf(range).not();}
/** * EmVer is a set of versioning of any pattern like 1 or 1.2 or 1.2.3 or 1.2.3.4 or .. */export class EmVer { /** * Convert the range, should be 1.2.* or * into a emver * Or an already made emver * IsUnsafe */ static from(range: string | EmVer): EmVer { if (range instanceof EmVer) { return range; } return EmVer.parse(range); } /** * Convert the range, should be 1.2.* or * into a emver * IsUnsafe */ static parse(range: string): EmVer { const values = range.split(".").map((x) => parseInt(x)); for (const value of values) { if (isNaN(value)) { throw new Error(`Couldn't parse range: ${range}`); } } return new EmVer(values); } private constructor(public readonly values: number[]) {}
/** * Used when we need a new emver that has the last number incremented, used in the 1.* like things */ public withLastIncremented() { return new EmVer(incrementLastNumber(this.values)); }
public greaterThan(other: EmVer): boolean { for (const i in this.values) { if (other.values[i] == null) { return true; } if (this.values[i] > other.values[i]) { return true; }
if (this.values[i] < other.values[i]) { return false; } } return false; }
public equals(other: EmVer): boolean { if (other.values.length !== this.values.length) { return false; } for (const i in this.values) { if (this.values[i] !== other.values[i]) { return false; } } return true; } public greaterThanOrEqual(other: EmVer): boolean { return this.greaterThan(other) || this.equals(other); } public lessThanOrEqual(other: EmVer): boolean { return !this.greaterThan(other); } public lessThan(other: EmVer): boolean { return !this.greaterThanOrEqual(other); } /** * Return a enum string that describes (used for switching/iffs) * to know comparison * @param other * @returns */ public compare(other: EmVer) { if (this.equals(other)) { return "equal" as const; } else if (this.greaterThan(other)) { return "greater" as const; } else { return "less" as const; } } /** * Used when sorting emver's in a list using the sort method * @param other * @returns */ public compareForSort(other: EmVer) { return matches.matches(this.compare(other)) .when("equal", () => 0 as const) .when("greater", () => 1 as const) .when("less", () => -1 as const) .unwrap(); }}
/** * A checker is a function that takes a version and returns true if the version matches the checker. * Used when we are doing range checking, like saying ">=1.0.0".check("1.2.3") will be true */export class Checker { /** * Will take in a range, like `>1.2` or `<1.2.3.4` or `=1.2` or `1.*` * and return a checker, that has the check function for checking that a version is in the valid * @param range * @returns */ static parse(range: string | Checker): Checker { if (range instanceof Checker) { return range; } range = range.trim(); if (range.indexOf("||") !== -1) { return rangeOr(...range.split("||").map((x) => Checker.parse(x))); } if (range.indexOf("&&") !== -1) { return rangeAnd(...range.split("&&").map((x) => Checker.parse(x))); } if (range === "*") { return new Checker((version) => { EmVer.from(version); return true; }); } if (range.startsWith("!")) { return Checker.parse(range.substring(1)).not(); } const starSubMatches = starSub.exec(range); if (starSubMatches != null) { const emVarLower = EmVer.parse(starSubMatches[1]); const emVarUpper = emVarLower.withLastIncremented();
return new Checker((version) => { const v = EmVer.from(version); return (v.greaterThan(emVarLower) || v.equals(emVarLower)) && !v.greaterThan(emVarUpper) && !v.equals(emVarUpper); }); }
switch (range.substring(0, 2)) { case ">=": { const emVar = EmVer.parse(range.substring(2)); return new Checker((version) => { const v = EmVer.from(version); return v.greaterThanOrEqual(emVar); }); } case "<=": { const emVar = EmVer.parse(range.substring(2)); return new Checker((version) => { const v = EmVer.from(version); return v.lessThanOrEqual(emVar); }); } }
switch (range.substring(0, 1)) { case ">": { console.log("greaterThan"); const emVar = EmVer.parse(range.substring(1)); return new Checker((version) => { const v = EmVer.from(version); return v.greaterThan(emVar); }); } case "<": { const emVar = EmVer.parse(range.substring(1)); return new Checker((version) => { const v = EmVer.from(version); return v.lessThan(emVar); }); } case "=": { const emVar = EmVer.parse(range.substring(1)); return new Checker((version) => { const v = EmVer.from(version); return v.equals(emVar); }); } } throw new Error("Couldn't parse range: " + range); } constructor( /** * Check is the function that will be given a emver or unparsed emver and should give if it follows * a pattern */ public readonly check: (value: string | EmVer) => boolean, ) {}
/** * Used when we want the `and` condition with another checker */ public and(...others: (Checker | string)[]): Checker { return new Checker((value) => { if (!this.check(value)) { return false; } for (const other of others) { if (!Checker.parse(other).check(value)) { return false; } } return true; }); }
/** * Used when we want the `or` condition with another checker */ public or(...others: (Checker | string)[]): Checker { return new Checker((value) => { if (this.check(value)) { return true; } for (const other of others) { if (Checker.parse(other).check(value)) { return true; } } return false; }); }
/** * A useful example is making sure we don't match an exact version, like !=1.2.3 * @returns */ public not(): Checker { return new Checker((value) => !this.check(value)); }}