summaryrefslogtreecommitdiffstats
path: root/scripts/issue.py
blob: ee8a1c5939cf41a58511ba35e1db943510ce0363 (plain) (blame)
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from debian import deb822
import os.path
import re
import sys


class issue(deb822.Deb822):
    nonReleaseFields = ['Candidate',
                        'References',
                        'Description',
                        'Notes',
                        'Bugs']
    reservedPrefixes = ['Ubuntu-']

    def __init__(self, path):
        self.file = open(path, 'r')
        self.d = deb822.Deb822(self.file)
        self.name = os.path.basename(path)

    def status(self, release):
        if release in self.d:
            return self.d[release]
        else:
            return ""

    def fieldIsRelease(self, field):
        if field in self.nonReleaseFields:
            return False
        for p in self.reservedPrefixes:
            if field[:len(p)] == p:
                return False
        return True

    def get_releases(self):
        releases = set()
        for field in self.d.keys():
            if self.fieldIsRelease(field):
                releases.add(field)
        return releases


_ignore_patterns = [re.compile('.*~$'),
                    re.compile('^#.*'),
                    re.compile('^00.*')]


def _ignore_issue_name(issue):
    for p in _ignore_patterns:
        if p.match(issue):
            return True
    return False


# Pad last part of CVE ID to 8 digits so string comparison keeps working
def _pad_cve_id(cve_id):
    return re.sub(r'-(\d+)$', lambda m: '-%08d' % int(m.group(1)), cve_id)


def get_issues(dir):
    issues = []
    L = [f for f in os.listdir(dir)
         if not os.path.isdir(os.path.join(dir, f))
         and not _ignore_issue_name(f)]
    L.sort(key=_pad_cve_id)

    return [ issue(os.path.join(dir, f)) for f in L ]


_status_re = re.compile(
    r'\s*(\S*)'
    r'(?:\s*\([^\s)]+\))?'  # optional version
    r'(?:\s*\[[^]]+\])?'    # optional changerefs
    r'(?:\s*"[^"]+")?'      # optional comment
    )


_comma_re = re.compile(r'\s*,\s*')


def _coalesce_state(old_state, new_state):
    if old_state is None:
        return new_state
    # "needed" overrides everything else
    if 'needed' in [old_state, new_state]:
        return 'needed'
    # "pendng" overrides everything but "needed"
    if 'pending' in [old_state, new_state]:
        return 'pending'
    # We don't expect to use more than one of "released", "ignored",
    # or "N/A" and it's not clear which should override which
    if old_state != new_state:
        print(f'W: Not sure how to coalesce status {old_state} and {new_state}',
              file=sys.stderr)
    return old_state


def parse_status(s):
    state = None
    start = 0

    while True:
        m = _status_re.match(s[start:])
        if not m:
            raise SyntaxError(f'bad status {s[start:]}')
        start += m.end()

        # Coalesce with last status
        state = _coalesce_state(state, m.group(1))

        # End of field?
        if start == len(s) or s[start:].isspace():
            return {'state': state}

        # No, must be followed by a comma separator
        m = _comma_re.match(s[start:])
        if not m:
            raise SyntaxError(f'missing separator in{s[start:]}')
        start += m.end()

© 2014-2024 Faster IT GmbH | imprint | privacy policy