File size: 2,286 Bytes
4951e76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""Minimal Cargo-style semver parsing/matching (enough for RustSec version ranges)."""
import re

_V = re.compile(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$")


def parse(v):
    m = _V.match(v.strip())
    if not m:
        raise ValueError(v)
    a, b, c, pre = m.groups()
    return (int(a), int(b or 0), int(c or 0)), pre


def key(v):
    t, pre = parse(v)
    # prerelease sorts before release
    return (t, 0 if pre else 1, pre or "")


def is_pre(v):
    return parse(v)[1] is not None


def _parts(s):
    m = _V.match(s.strip())
    a, b, c, pre = m.groups()
    return int(a), (None if b is None else int(b)), (None if c is None else int(c)), pre


def _cmp_one(v, op, rhs):
    kv = key(v)
    kr = key(rhs)
    if op == "=":
        a, b, c, _ = _parts(rhs)
        t = kv[0]
        if b is None:
            return t[0] == a
        if c is None:
            return t[0] == a and t[1] == b
        return kv == kr
    if op == "<":
        return kv < kr
    if op == "<=":
        a, b, c, _ = _parts(rhs)
        if c is None:  # "<= 1.2" means < 1.3 style; be permissive
            hi = (a, (b if b is not None else 0), 10**9)
            return kv[0] <= hi
        return kv <= kr
    if op == ">":
        return kv > kr
    if op == ">=":
        return kv >= kr
    if op == "^" or op == "":
        a, b, c, _ = _parts(rhs)
        lo = kr
        if a > 0 or b is None:
            hi = (a + 1, 0, 0)
        elif b > 0 or c is None:
            hi = (0, b + 1, 0)
        else:
            hi = (0, 0, c + 1)
        return kv >= lo and kv[0] < hi
    if op == "~":
        a, b, c, _ = _parts(rhs)
        hi = (a + 1, 0, 0) if b is None else (a, b + 1, 0)
        return kv >= kr and kv[0] < hi
    raise ValueError(op)


_C = re.compile(r"^\s*(<=|>=|=|<|>|\^|~)?\s*(\S+)\s*$")


def matches_req(v, req):
    """req: comma-separated comparator list; all must hold."""
    for part in req.split(","):
        m = _C.match(part)
        if not m:
            raise ValueError(req)
        op, rhs = m.groups()
        if rhs.endswith(".*"):
            rhs, op = rhs[:-2], "="
        if not _cmp_one(v, op or "", rhs):
            return False
    return True


def matches_any(v, reqs):
    return any(matches_req(v, r) for r in reqs)