From 5b21ee08b4ea3c42ad4522cc37cc8cfd11563661 Mon Sep 17 00:00:00 2001 From: BluJ Date: Fri, 15 Jul 2022 12:46:06 -0600 Subject: [PATCH] feat: emvar lite --- emvar-lite/mod.ts | 151 +++++++++++++++++++++++++++++++++++++++ emvar-lite/range.test.ts | 140 ++++++++++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 emvar-lite/mod.ts create mode 100644 emvar-lite/range.test.ts diff --git a/emvar-lite/mod.ts b/emvar-lite/mod.ts new file mode 100644 index 0000000..2963484 --- /dev/null +++ b/emvar-lite/mod.ts @@ -0,0 +1,151 @@ + +const starSub = /((\d+\.)*\d+)\.\*/ + +function incrementLastNumber(list: number[]) { + const newList = [...list] + newList[newList.length - 1]++ + return newList +} +export function rangeOf(range: string | Checker): Checker { + if (range instanceof Checker) { + return range + } + if (range === '*') return new Checker((version) => { + EmVar.from(version) + return true + }); + const starSubMatches = starSub.exec(range) + if (starSubMatches != null) { + const emVarLower = EmVar.parse(starSubMatches[1]) + const emVarUpper = emVarLower.withLastIncremented() + + return new Checker((version) => { + const v = EmVar.from(version); + return (v.greaterThan(emVarLower) || v.equals(emVarLower)) && !v.greaterThan(emVarUpper) && !v.equals(emVarUpper); + }) + } + + switch (range.substring(0, 2)) { + case '>=': { + const emVar = EmVar.parse(range.substring(2)); + return new Checker((version) => { + const v = EmVar.from(version); + return v.greaterThan(emVar) || v.equals(emVar); + }) + } + case '<=': { + const emVar = EmVar.parse(range.substring(2)); + return new Checker((version) => { + const v = EmVar.from(version); + return !v.greaterThan(emVar); + }) + } + + } + + switch (range.substring(0, 1)) { + case '>': { + console.log('greaterThan') + const emVar = EmVar.parse(range.substring(1)); + return new Checker((version) => { + const v = EmVar.from(version); + return v.greaterThan(emVar); + }) + } + case '<': { + const emVar = EmVar.parse(range.substring(1)); + return new Checker((version) => { + const v = EmVar.from(version); + return !v.greaterThan(emVar) && !v.equals(emVar); + }) + } + case '=': { + const emVar = EmVar.parse(range.substring(1)); + return new Checker((version) => { + const v = EmVar.from(version); + return v.equals(emVar); + }) + } + + } + throw new Error("Couldn't parse range: " + range); +} + +export function rangeAnd(...ranges: (string | Checker)[]): Checker { + let [firstCheck, ...rest] = ranges.map(rangeOf); + for (const checker of rest) { + firstCheck = firstCheck.and(checker); + } + return firstCheck; +} + +export function rangeOr(...ranges: (string | Checker)[]): Checker { + let [firstCheck, ...rest] = ranges.map(rangeOf); + for (const checker of rest) { + firstCheck = firstCheck.or(checker); + } + return firstCheck; +} + + +export class EmVar { + static from(range: string | EmVar): EmVar { + if (range instanceof EmVar) { + return range + } + return EmVar.parse(range) + } + static parse(range: string): EmVar { + 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 EmVar(values); + } + private constructor(public readonly values: number[]) { } + + public withLastIncremented() { + return new EmVar(incrementLastNumber(this.values)) + } + + public greaterThan(other: EmVar): 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: EmVar): 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; + } +} + +export class Checker { + constructor(public readonly check: (value: string | EmVar) => boolean) { } + + public and(other: Checker): Checker { + return new Checker((value) => this.check(value) && other.check(value)); + } + public or(other: Checker): Checker { + return new Checker((value) => this.check(value) || other.check(value)); + } +} \ No newline at end of file diff --git a/emvar-lite/range.test.ts b/emvar-lite/range.test.ts new file mode 100644 index 0000000..62a1f9d --- /dev/null +++ b/emvar-lite/range.test.ts @@ -0,0 +1,140 @@ + +import { expect } from "https://deno.land/x/expect@v0.2.9/mod.ts"; +import { rangeAnd, rangeOf, rangeOr } from "./mod.ts"; +const { test } = Deno; + + +{ + const checker = rangeOf("*"); + test("rangeOf('*')", () => { + expect(checker.check("1")).toBe(true); + expect(checker.check("1.2")).toBe(true); + expect(checker.check("1.2.3.4")).toBe(true); + }) + test("rangeOf('*') invalid", () => { + expect(() => checker.check("a")).toThrow(); + expect(() => checker.check("")).toThrow(); + expect(() => checker.check("1..3")).toThrow(); + }) +} + +{ + const checker = rangeOf(">1.2.3.4"); + test(`rangeOf(">1.2.3.4") valid`, () => { + expect(checker.check("2")).toBe(true); + expect(checker.check("1.2.3.5")).toBe(true); + expect(checker.check("1.2.3.4.1")).toBe(true); + }) + + test(`rangeOf(">1.2.3.4") invalid`, () => { + expect(checker.check("1.2.3.4")).toBe(false); + expect(checker.check("1.2.3")).toBe(false); + expect(checker.check("1")).toBe(false); + }) +} +{ + const checker = rangeOf("=1.2.3"); + test(`rangeOf("=1.2.3") valid`, () => { + expect(checker.check("1.2.3")).toBe(true); + }) + + test(`rangeOf("=1.2.3") invalid`, () => { + expect(checker.check("2")).toBe(false); + expect(checker.check("1.2.3.1")).toBe(false); + expect(checker.check("1.2")).toBe(false); + }) +} +{ + const checker = rangeOf(">=1.2.3.4"); + test(`rangeOf(">=1.2.3.4") valid`, () => { + expect(checker.check("2")).toBe(true); + expect(checker.check("1.2.3.5")).toBe(true); + expect(checker.check("1.2.3.4.1")).toBe(true); + expect(checker.check("1.2.3.4")).toBe(true); + }) + + test(`rangeOf(">=1.2.3.4") invalid`, () => { + expect(checker.check("1.2.3")).toBe(false); + expect(checker.check("1")).toBe(false); + }) +} +{ + const checker = rangeOf("<1.2.3.4"); + test(`rangeOf("<1.2.3.4") invalid`, () => { + expect(checker.check("2")).toBe(false); + expect(checker.check("1.2.3.5")).toBe(false); + expect(checker.check("1.2.3.4.1")).toBe(false); + expect(checker.check("1.2.3.4")).toBe(false); + }) + + test(`rangeOf("<1.2.3.4") valid`, () => { + expect(checker.check("1.2.3")).toBe(true); + expect(checker.check("1")).toBe(true); + }) +} +{ + const checker = rangeOf("<=1.2.3.4"); + test(`rangeOf("<=1.2.3.4") invalid`, () => { + expect(checker.check("2")).toBe(false); + expect(checker.check("1.2.3.5")).toBe(false); + expect(checker.check("1.2.3.4.1")).toBe(false); + }) + + test(`rangeOf("<=1.2.3.4") valid`, () => { + expect(checker.check("1.2.3")).toBe(true); + expect(checker.check("1")).toBe(true); + expect(checker.check("1.2.3.4")).toBe(true); + }) +} + +{ + + const checkA = rangeOf(">1"); + const checkB = rangeOf("<=2"); + + const checker = rangeAnd(checkA, checkB); + test(`simple and(checkers) valid`, () => { + expect(checker.check("2")).toBe(true); + + expect(checker.check("1.1")).toBe(true); + }) + test(`simple and(checkers) invalid`, () => { + expect(checker.check("2.1")).toBe(false); + expect(checker.check("1")).toBe(false); + expect(checker.check("0")).toBe(false); + }) +} +{ + + const checkA = rangeOf("<1"); + const checkB = rangeOf("=2"); + + const checker = rangeOr(checkA, checkB); + test(`simple or(checkers) valid`, () => { + expect(checker.check("2")).toBe(true); + expect(checker.check("0.1")).toBe(true); + }) + test(`simple or(checkers) invalid`, () => { + expect(checker.check("2.1")).toBe(false); + expect(checker.check("1")).toBe(false); + expect(checker.check("1.1")).toBe(false); + }) +} +{ + const checker = rangeOf('1.2.*'); + test(`rangeOf(1.2.*) valid`, () => { + expect(checker.check("1.2")).toBe(true); + expect(checker.check("1.2.1")).toBe(true); + }) + test(`rangeOf(1.2.*) invalid`, () => { + expect(checker.check("1.3")).toBe(false); + expect(checker.check("1.3.1")).toBe(false); + + expect(checker.check("1.1.1")).toBe(false); + expect(checker.check("1.1")).toBe(false); + expect(checker.check("1")).toBe(false); + + + expect(checker.check("2")).toBe(false); + }) +} \ No newline at end of file