~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():
5447.3.9 by Vincent Ladeuil
Comment NewsParser.parse_bugs.
112
                continue # line may a be release
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
113
            try:
114
                if self.confirm_release():
5447.3.9 by Vincent Ladeuil
Comment NewsParser.parse_bugs.
115
                    continue # previous line was indeed a release
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
116
            finally:
117
                self.may_be_release = None
5447.3.8 by Vincent Ladeuil
Recognize release dates.
118
            if self.try_date():
5447.3.9 by Vincent Ladeuil
Comment NewsParser.parse_bugs.
119
                continue # The release date has been seen
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
120
            if self.add_line_to_entry():
5447.3.9 by Vincent Ladeuil
Comment NewsParser.parse_bugs.
121
                continue # accumulate in self.enrty
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
122
            for b in self.extract_bugs_from_entry():
5447.3.9 by Vincent Ladeuil
Comment NewsParser.parse_bugs.
123
                yield b # all bugs in the news entry
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(
5447.3.10 by Vincent Ladeuil
Add -m to search for a regexp in news entries instead of the bug number.
127
        usage="""Usage: %prog [options] BUG_NUMBER
5447.3.6 by Vincent Ladeuil
Make fixed-in really work now and some cleanup.
128
    """)
5447.3.10 by Vincent Ladeuil
Add -m to search for a regexp in news entries instead of the bug number.
129
    opt_parser.add_option(
130
        '-f', '--file', type='str', dest='news_file',
131
        help='NEWS file (defaults to ./NEWS)')
132
    opt_parser.add_option(
133
        '-m', '--message', type='str', dest='msg_re',
134
        help='A regexp to search for in the news entry '
135
        '(BUG_NUMBER should not be specified in this case)')
5447.3.6 by Vincent Ladeuil
Make fixed-in really work now and some cleanup.
136
    opt_parser.set_defaults(news_file='./NEWS')
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
137
    (opts, args) = opt_parser.parse_args(sys.argv[1:])
5447.3.10 by Vincent Ladeuil
Add -m to search for a regexp in news entries instead of the bug number.
138
    if opts.msg_re is not None:
139
        if len(args) != 0:
140
            opt_parser.error('BUG_NUMBER and -m are mutually exclusive')
141
        bug = None
142
        msg_re = re.compile(opts.msg_re)
143
    elif len(args) != 1:
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
144
        opt_parser.error('Expected a single bug number, got %r' % args)
5447.3.10 by Vincent Ladeuil
Add -m to search for a regexp in news entries instead of the bug number.
145
    else:
146
        bug = args[0]
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
147
148
    news = open(opts.news_file)
149
    parser = NewsParser(news)
150
    try:
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
151
        seen = 0
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
152
        for b in parser.parse_bugs():
5447.3.8 by Vincent Ladeuil
Recognize release dates.
153
            (number, authors, release, date, entry,) = b
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
154
            # indent entry
155
            entry = '\n'.join(['    ' + l for l in entry.splitlines()])
5447.3.10 by Vincent Ladeuil
Add -m to search for a regexp in news entries instead of the bug number.
156
            found = False
157
            if bug is not None:
158
                if number[1:] == bug: # Strip the leading '#'
159
                    found = True
160
            elif msg_re.search(entry) is not None:
161
                found = True
162
            if found:
5447.3.8 by Vincent Ladeuil
Recognize release dates.
163
                print 'Bug %s was fixed in bzr-%s/%s by %s:' % (
164
                    number, release, date, authors)
5447.3.6 by Vincent Ladeuil
Make fixed-in really work now and some cleanup.
165
                print entry
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
166
            seen += 1
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
167
    finally:
5447.3.2 by Vincent Ladeuil
Parse entries, extract bugs fron the entries (1105 bugs seen).
168
        print '%s bugs seen' % (seen,)
5447.3.1 by Vincent Ladeuil
NEWS parser recognizes releases.
169
        news.close()
170
171
172
main()