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() |