~bzr-pqm/bzr/bzr.dev

5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
1
#!/usr/bin/python
2
3
# Simple script that will output the release where a given bug was fixed
4
# searching the NEWS file
5
6
import optparse
7
import re
8
import sys
9
10
11
class NewsParser(object):
12
5447.3.4 by Vincent Ladeuil
Parse authors too (1116 bugs seen, we miss 24, the others are false positives).
13
    paren_exp_re = re.compile('\(([^)]+)\)')
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
14
    release_re = re.compile("bzr[ -]")
15
    release_prefix_length = len('bzr ')
5447.3.4 by Vincent Ladeuil
Parse authors too (1116 bugs seen, we miss 24, the others are false positives).
16
    bugs_re = re.compile('#([0-9]+)')
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
17
18
    def __init__(self, news):
19
        self.news = news
20
        # Temporary attributes used by the parser
5447.3.8 by Vincent Ladeuil
Recognize release dates.
21
        self.release = None
22
        self.date = None
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
23
        self.may_be_release = None
24
        self.release_markup = None
25
        self.entry = ''
26
        self.line = None
27
        self.lrs = None
28
29
    def set_line(self, line):
30
        self.line = line
31
        self.lrs = line.rstrip()
32
33
    def try_release(self):
34
        if self.release_re.match(self.lrs) is not None:
35
            # May be a new release
36
            self.may_be_release = self.lrs
37
            # We know the markup will have the same length as the release
38
            self.release_markup = '#' * len(self.may_be_release)
39
            return True
40
        return False
41
42
    def confirm_release(self):
43
        if self.may_be_release is not None and self.lrs == self.release_markup:
44
            # The release is followed by the right markup
45
            self.release = self.may_be_release[self.release_prefix_length:]
5447.3.8 by Vincent Ladeuil
Recognize release dates.
46
            # Wait for the associated date
47
            self.date = None
48
            return True
49
        return False
50
51
    def try_date(self):
52
        if self.release is None:
53
            return False
54
        date_re = re.compile(':%s: (NOT RELEASED YET|\d{4}-\d{2}-\d{2})'
55
                             % (self.release,))
56
        match = date_re.match(self.lrs)
57
        if  match is not None:
58
            self.date = match.group(1)
59
            return True
60
        # The old fashion way
61
        released_re = re.compile(':Released:\s+(\d{4}-\d{2}-\d{2})')
62
        match = released_re.match(self.lrs)
63
        if  match is not None:
64
            self.date = match.group(1)
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
65
            return True
66
        return False
67
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
68
    def add_line_to_entry(self):
69
        if self.lrs == '':
70
            return False
71
        self.entry += self.line
72
        return True
73
74
    def extract_bugs_from_entry(self):
5447.3.7 by Vincent Ladeuil
Explain extract_bugs_from_entry assumptions.
75
        """Possibly extract bugs from a NEWS entry and yield them.
76
77
        Not all entries will contain bugs and some entries are even garbage and
78
        we don't try to parse them (yet). The trigger is a '#' and what looks
79
        like a bug number inside parens to start with. From that we extract
80
        authors (when present) and multiple bugs if needed.
81
        """
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
82
        # FIXME: Malone entries are different
5447.3.5 by Vincent Ladeuil
Catch the two forms (bugs/authors) and (authors/bugs). We got the 1142 bugs.
83
        # Join all entry lines to simplify multiple line matching
5447.3.4 by Vincent Ladeuil
Parse authors too (1116 bugs seen, we miss 24, the others are false positives).
84
        flat_entry = ' '.join(self.entry.splitlines())
85
        # Fixed bugs are always inside parens
86
        for par in self.paren_exp_re.findall(flat_entry):
87
            sharp = par.find('#')
5447.3.5 by Vincent Ladeuil
Catch the two forms (bugs/authors) and (authors/bugs). We got the 1142 bugs.
88
            if sharp is not None:
5447.3.7 by Vincent Ladeuil
Explain extract_bugs_from_entry assumptions.
89
                # We have at least one bug inside parens.
5447.3.5 by Vincent Ladeuil
Catch the two forms (bugs/authors) and (authors/bugs). We got the 1142 bugs.
90
                bugs = list(self.bugs_re.finditer(par))
91
                if bugs:
5447.3.7 by Vincent Ladeuil
Explain extract_bugs_from_entry assumptions.
92
                    # See where the first bug is mentioned
5447.3.5 by Vincent Ladeuil
Catch the two forms (bugs/authors) and (authors/bugs). We got the 1142 bugs.
93
                    start = bugs[0].start()
94
                    end = bugs[-1].end()
95
                    if start == 0:
96
                        # (bugs/authors)
97
                        authors = par[end:]
98
                    else:
99
                        # (authors/bugs)
100
                         authors = par[:start]
101
                    for bug_match in bugs:
102
                        bug_number = bug_match.group(0)
5447.3.8 by Vincent Ladeuil
Recognize release dates.
103
                        yield (bug_number, authors,
104
                               self.release, self.date, self.entry)
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
105
        # We've consumed the entry
106
        self.entry = ''
107
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
108
    def parse_bugs(self):
109
        for line in self.news:
110
            self.set_line(line)
111
            if self.try_release():
112
                continue
113
            try:
114
                if self.confirm_release():
115
                    continue
116
            finally:
117
                self.may_be_release = None
5447.3.8 by Vincent Ladeuil
Recognize release dates.
118
            if self.try_date():
119
                continue
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
120
            if self.add_line_to_entry():
121
                continue
122
            for b in self.extract_bugs_from_entry():
123
                yield b
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
124
125
def main():
5447.3.6 by Vincent Ladeuil
Make fixed-in really work now and some cleanup.
126
    opt_parser = optparse.OptionParser(
127
        usage="""Usage: %prog [options] <bug_number>
128
    """)
129
    opt_parser.add_option('-f', '--file', type='str', dest='news_file',
130
                          help='NEWS file (defaults to ./NEWS)')
131
    opt_parser.set_defaults(news_file='./NEWS')
132
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
133
    (opts, args) = opt_parser.parse_args(sys.argv[1:])
134
    if len(args) != 1:
135
        opt_parser.error('Expected a single bug number, got %r' % args)
136
137
    bug = args[0]
138
139
    news = open(opts.news_file)
140
    parser = NewsParser(news)
141
    try:
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
142
        seen = 0
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
143
        for b in parser.parse_bugs():
5447.3.8 by Vincent Ladeuil
Recognize release dates.
144
            (number, authors, release, date, entry,) = b
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
145
            # indent entry
146
            entry = '\n'.join(['    ' + l for l in entry.splitlines()])
5447.3.6 by Vincent Ladeuil
Make fixed-in really work now and some cleanup.
147
            if number[1:] == bug: # Strip the leading '#'
5447.3.8 by Vincent Ladeuil
Recognize release dates.
148
                print 'Bug %s was fixed in bzr-%s/%s by %s:' % (
149
                    number, release, date, authors)
5447.3.6 by Vincent Ladeuil
Make fixed-in really work now and some cleanup.
150
                print entry
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
151
            seen += 1
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
152
    finally:
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
153
        print '%s bugs seen' % (seen,)
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
154
        news.close()
155
156
157
main()