{ pkgs ? import <nixpkgs> {}, lib ? pkgs.lib }: let inherit (builtins) elemAt match; # Replace a list entry at defined index with set value ireplace = idx: value: list: let inherit (builtins) genList length; in genList (i: if i == idx then value else (elemAt list i)) (length list); orBlank = x: if x != null then x else ""; operators = let mkComparison = ret: version: v: let x = 1; in builtins.compareVersions version v == ret; mkIdxComparison = idx: version: v: let ver = builtins.splitVersion v; minor = builtins.toString (lib.toInt (elemAt ver idx) + 1); upper = builtins.concatStringsSep "." (ireplace idx minor ver); in operators.">=" version v && operators."<" version upper; dropWildcardPrecision = f: version: constraint: let wildcardMatch = ( match "([^0-9x*]*)((0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)|(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)|(0|[1-9][0-9]*)){0,1}([.x*]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)){0,1}(\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)){0,1}" constraint ); matchPart = (elemAt wildcardMatch 1); shortConstraint = if matchPart != null then matchPart else ""; shortVersion = builtins.substring 0 (builtins.stringLength shortConstraint) version; in f shortVersion shortConstraint; in { # Prefix operators "==" = dropWildcardPrecision (mkComparison 0); ">" = dropWildcardPrecision (mkComparison 1); "<" = dropWildcardPrecision (mkComparison (-1)); "!=" = v: c: !operators."==" v c; ">=" = v: c: operators."==" v c || operators.">" v c; "<=" = v: c: operators."==" v c || operators."<" v c; # Semver specific operators "~" = mkIdxComparison 1; "^" = mkIdxComparison 0; }; re = { operators = "([=><!~^]+)"; version = "((0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)|(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)|(0|[1-9][0-9]*)){0,1}([.x*]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)){0,1}(\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)){0,1}"; }; reLengths = { operators = 1; version = 16; }; parseConstraint = constraintStr: let # The common prefix operators mPre = match "${re.operators} *${re.version}" constraintStr; # There is an upper bound to the operator (this implementation is a bit hacky) mUpperBound = match "${re.operators} *${re.version} *< *${re.version}" constraintStr; # There is also an infix operator to match ranges mIn = match "${re.version} - *${re.version}" constraintStr; # There is no operators mNone = match "${re.version}" constraintStr; in ( if mPre != null then { ops.t = elemAt mPre 0; v = orBlank (elemAt mPre reLengths.operators); } # Infix operators are range matches else if mIn != null then { ops = { t = "-"; l = ">="; u = "<="; }; v = { vl = orBlank (elemAt mIn 0); vu = orBlank (elemAt mIn reLengths.version); }; } else if mUpperBound != null then { ops = { t = "-"; l = (elemAt mUpperBound 0); u = "<"; }; v = { vl = orBlank (elemAt mUpperBound reLengths.operators); vu = orBlank (elemAt mUpperBound (reLengths.operators + reLengths.version)); }; } else if mNone != null then { ops.t = "=="; v = orBlank (elemAt mNone 0); } else throw ''Constraint "${constraintStr}" could not be parsed'' ); satisfiesSingle = version: constraint: let inherit (parseConstraint constraint) ops v; in if ops.t == "-" then (operators."${ops.l}" version v.vl && operators."${ops.u}" version v.vu) else operators."${ops.t}" version v; satisfies = version: constraint: # TODO: use a regex for the split builtins.length ( builtins.filter (c: satisfiesSingle version c) (lib.splitString " || " constraint) ) > 0; in { inherit satisfies; }