Merge lp:~jml/launchpad/xxx-cleanup into lp:launchpad

Proposed by Jonathan Lange
Status: Merged
Approved by: Jonathan Lange
Approved revision: no longer in the source branch.
Merged at revision: 13296
Proposed branch: lp:~jml/launchpad/xxx-cleanup
Merge into: lp:launchpad
Diff against target: 580 lines (+128/-123)
24 files modified
lib/canonical/launchpad/daemons/tachandler.py (+2/-3)
lib/canonical/launchpad/doc/badges.txt (+1/-1)
lib/canonical/launchpad/doc/displaying-numbers.txt (+1/-1)
lib/canonical/launchpad/doc/menus.txt (+1/-1)
lib/canonical/launchpad/doc/presenting-lengths-of-time.txt (+1/-1)
lib/canonical/launchpad/ftests/__init__.py (+18/-4)
lib/canonical/lazr/doc/menus.txt (+1/-1)
lib/devscripts/autoland.py (+0/-4)
lib/lp/app/browser/stringformatter.py (+72/-1)
lib/lp/app/browser/tests/test_stringformatter.py (+10/-3)
lib/lp/app/doc/displaying-dates.txt (+1/-1)
lib/lp/app/doc/displaying-paragraphs-of-text.txt (+1/-1)
lib/lp/app/doc/tales-email-formatting.txt (+1/-1)
lib/lp/app/doc/tales-macro.txt (+1/-1)
lib/lp/app/doc/tales.txt (+1/-1)
lib/lp/bugs/doc/displaying-bugs-and-tasks.txt (+1/-1)
lib/lp/code/model/revision.py (+1/-1)
lib/lp/registry/browser/distributionsourcepackage.py (+4/-2)
lib/lp/registry/browser/tests/milestone-views.txt (+1/-1)
lib/lp/registry/doc/sourcepackage.txt (+2/-13)
lib/lp/registry/doc/team-nav-menus.txt (+1/-1)
lib/lp/scripts/utilities/warninghandler.py (+1/-3)
lib/lp/soyuz/browser/sourcepackagerelease.py (+5/-74)
lib/lp/testing/__init__.py (+0/-2)
To merge this branch: bzr merge lp:~jml/launchpad/xxx-cleanup
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Review via email: mp+65663@code.launchpad.net

Commit message

[r=jcsackett][no-qa] Many XXX cleanups.

Description of the change

This branch cleans up a few of my XXX comments from over the years.

 * lib/lp/soyuz/browser/sourcepackagerelease.py had a few generic Launchpad text parsing functions that are now in lp.app.browser.stringformatting
 * canonical.launchpad.ftests.test_tales is dead
 * Wrap a syscall in tachandler with until_no_eintr, so that it's robust
 * Put an __all__ in c.l.ftests to avoid pyflakes lint
 * Removed a XXX comment that was relevant only when the bug was open
 * Got rid of a duplicate call to get_authors()
 * Passed through 'line' in our warninghandler, now that we aren't on Python 2.5

Happy to answer questions. Sorry for the grab bag approach, but it seemed easier for all concerned. Mostly me though.

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/daemons/tachandler.py'
2--- lib/canonical/launchpad/daemons/tachandler.py 2011-06-21 16:47:51 +0000
3+++ lib/canonical/launchpad/daemons/tachandler.py 2011-06-24 10:53:25 +0000
4@@ -25,6 +25,7 @@
5 kill_by_pidfile,
6 remove_if_exists,
7 two_stage_kill,
8+ until_no_eintr,
9 )
10
11
12@@ -85,9 +86,7 @@
13 proc = subprocess.Popen(args, stdout=subprocess.PIPE,
14 stderr=subprocess.STDOUT)
15 self.addCleanup(self.killTac)
16- # XXX: JonathanLange 2008-03-19: This can raise EINTR. We should
17- # really catch it and try again if that happens.
18- stdout = proc.stdout.read()
19+ stdout = until_no_eintr(10, proc.stdout.read)
20 if stdout:
21 raise TacException('Error running %s: unclean stdout/err: %s'
22 % (args, stdout))
23
24=== modified file 'lib/canonical/launchpad/doc/badges.txt'
25--- lib/canonical/launchpad/doc/badges.txt 2010-10-31 20:18:45 +0000
26+++ lib/canonical/launchpad/doc/badges.txt 2011-06-24 10:53:25 +0000
27@@ -272,7 +272,7 @@
28 through the printed attribute accessors, uses the attributes of the
29 content class.
30
31- >>> from canonical.launchpad.ftests import test_tales
32+ >>> from lp.testing import test_tales
33 >>> print test_tales('context/badges:small', context=foo)
34 Foo.bugs
35 Foo.blueprints
36
37=== modified file 'lib/canonical/launchpad/doc/displaying-numbers.txt'
38--- lib/canonical/launchpad/doc/displaying-numbers.txt 2009-10-21 17:41:20 +0000
39+++ lib/canonical/launchpad/doc/displaying-numbers.txt 2011-06-24 10:53:25 +0000
40@@ -1,6 +1,6 @@
41 = Displaying Numbers with ZPT =
42
43- >>> from canonical.launchpad.ftests import test_tales
44+ >>> from lp.testing import test_tales
45
46 == bytes: Byte contractions ==
47
48
49=== modified file 'lib/canonical/launchpad/doc/menus.txt'
50--- lib/canonical/launchpad/doc/menus.txt 2011-03-23 16:28:51 +0000
51+++ lib/canonical/launchpad/doc/menus.txt 2011-06-24 10:53:25 +0000
52@@ -700,7 +700,7 @@
53
54 >>> from zope.publisher.interfaces.browser import IBrowserRequest
55 >>> from zope.publisher.interfaces.http import IHTTPApplicationRequest
56- >>> from canonical.launchpad.ftests import test_tales
57+ >>> from lp.testing import test_tales
58 >>> from canonical.launchpad.webapp import LaunchpadView
59 >>> from canonical.launchpad.webapp.vhosts import allvhosts
60 >>> class FakeRequest:
61
62=== modified file 'lib/canonical/launchpad/doc/presenting-lengths-of-time.txt'
63--- lib/canonical/launchpad/doc/presenting-lengths-of-time.txt 2010-10-18 22:24:59 +0000
64+++ lib/canonical/launchpad/doc/presenting-lengths-of-time.txt 2011-06-24 10:53:25 +0000
65@@ -3,7 +3,7 @@
66
67 First, let's bring in some dependencies:
68
69- >>> from canonical.launchpad.ftests import test_tales
70+ >>> from lp.testing import test_tales
71 >>> from datetime import timedelta
72
73 Exact Duration
74
75=== modified file 'lib/canonical/launchpad/ftests/__init__.py'
76--- lib/canonical/launchpad/ftests/__init__.py 2010-12-02 16:13:51 +0000
77+++ lib/canonical/launchpad/ftests/__init__.py 2011-06-24 10:53:25 +0000
78@@ -1,8 +1,25 @@
79-# Copyright 2009 Canonical Ltd. This software is licensed under the
80+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
81 # GNU Affero General Public License version 3 (see the file LICENSE).
82
83 # pylint: disable-msg=W0401
84
85+__all__ = [
86+ 'ANONYMOUS',
87+ 'decrypt_content',
88+ 'import_public_key',
89+ 'import_public_test_keys',
90+ 'import_secret_test_key',
91+ 'is_logged_in',
92+ 'LaunchpadFormHarness',
93+ 'login',
94+ 'login_person',
95+ 'logout',
96+ 'print_date_attribute',
97+ 'set_so_attr',
98+ 'sync',
99+ 'syncUpdate',
100+ ]
101+
102 from canonical.launchpad.ftests._launchpadformharness import (
103 LaunchpadFormHarness,
104 )
105@@ -24,7 +41,4 @@
106 login,
107 login_person,
108 logout,
109- test_tales,
110 )
111-
112-
113
114=== modified file 'lib/canonical/lazr/doc/menus.txt'
115--- lib/canonical/lazr/doc/menus.txt 2010-10-18 22:24:59 +0000
116+++ lib/canonical/lazr/doc/menus.txt 2011-06-24 10:53:25 +0000
117@@ -931,9 +931,9 @@
118 >>> from zope.interface import classImplements
119 >>> from zope.traversing.adapters import DefaultTraversable
120 >>> from zope.traversing.interfaces import IPathAdapter, ITraversable
121- >>> from canonical.launchpad.ftests import test_tales
122 >>> from canonical.lazr.testing.menus import summarise_tal_links
123 >>> from lp.app.browser.tales import MenuAPI
124+ >>> from lp.testing import test_tales
125
126 # MenuAPI is normally registered as an IPathAdapter in ZCML. This
127 # approximates what is done by the code:
128
129=== modified file 'lib/devscripts/autoland.py'
130--- lib/devscripts/autoland.py 2011-03-31 15:02:47 +0000
131+++ lib/devscripts/autoland.py 2011-06-24 10:53:25 +0000
132@@ -239,10 +239,6 @@
133 def get_email(person):
134 """Get the preferred email address for 'person'."""
135 email_object = person.preferred_email_address
136- # XXX: JonathanLange 2009-09-24 bug=319432: This raises a very obscure
137- # error when the email address isn't set. e.g. with name12 in the sample
138- # data. e.g. "httplib2.RelativeURIError: Only absolute URIs are allowed.
139- # uri = tag:launchpad.net:2008:redacted".
140 return email_object.email
141
142
143
144=== modified file 'lib/lp/app/browser/stringformatter.py'
145--- lib/lp/app/browser/stringformatter.py 2011-05-29 23:34:26 +0000
146+++ lib/lp/app/browser/stringformatter.py 2011-06-24 10:53:25 +0000
147@@ -1,4 +1,4 @@
148-# Copyright 2010 Canonical Ltd. This software is licensed under the
149+# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
150 # GNU Affero General Public License version 3 (see the file LICENSE).
151
152 """TALES formatter for strings."""
153@@ -9,7 +9,10 @@
154 'add_word_breaks',
155 'break_long_words',
156 'escape',
157+ 'extract_bug_numbers',
158+ 'extract_email_addresses',
159 'FormattersAPI',
160+ 'linkify_bug_numbers',
161 're_substitute',
162 'split_paragraphs',
163 ]
164@@ -170,6 +173,74 @@
165 return break_text_pat.sub(replace, text)
166
167
168+def extract_bug_numbers(text):
169+ '''Unique bug numbers matching the "LP: #n(, #n)*" pattern in the text.'''
170+ # FormattersAPI._linkify_substitution requires a match object
171+ # that has named groups "bug" and "bugnum". The matching text for
172+ # the "bug" group is used as the link text and "bugnum" forms part
173+ # of the URL for the link to the bug. Example:
174+ # >>> bm.groupdict( )
175+ # {'bugnum': '400686', 'bug': '#400686'}
176+
177+ # We need to match bug numbers of the form:
178+ # LP: #1, #2, #3
179+ # #4, #5
180+ # over multiple lines.
181+ #
182+ # Writing a single catch-all regex for this has proved rather hard
183+ # so I am taking the strategy of matching LP:(group) first, and
184+ # feeding the result into another regex to pull out the bug and
185+ # bugnum groups.
186+ unique_bug_matches = dict()
187+
188+ line_matches = re.finditer(
189+ 'LP:\s*(?P<buglist>(.+?[^,]))($|\n)', text,
190+ re.DOTALL | re.IGNORECASE)
191+
192+ for line_match in line_matches:
193+ bug_matches = re.finditer(
194+ '\s*((?P<bug>#(?P<bugnum>\d+)),?\s*)',
195+ line_match.group('buglist'))
196+
197+ for bug_match in bug_matches:
198+ bugnum = bug_match.group('bugnum')
199+ if bugnum in unique_bug_matches:
200+ # We got this bug already, ignore it.
201+ continue
202+ unique_bug_matches[bugnum] = bug_match
203+
204+ return unique_bug_matches
205+
206+
207+def linkify_bug_numbers(text):
208+ """Linkify to a bug if LP: #number appears in the (changelog) text."""
209+ unique_bug_matches = extract_bug_numbers(text)
210+ for bug_match in unique_bug_matches.values():
211+ replace_text = bug_match.group('bug')
212+ if replace_text is not None:
213+ # XXX julian 2008-01-10
214+ # Note that re.sub would be far more efficient to use
215+ # instead of string.replace() but this requires a regex
216+ # that matches everything in one go. We're also at danger
217+ # of replacing the wrong thing if string.replace() finds
218+ # other matching substrings. So for example in the
219+ # string:
220+ # "LP: #9, #999"
221+ # replacing #9 with some HTML would also interfere with
222+ # #999. The liklihood of this happening is very, very
223+ # small, however.
224+ text = text.replace(
225+ replace_text,
226+ FormattersAPI._linkify_substitution(bug_match))
227+ return text
228+
229+
230+def extract_email_addresses(text):
231+ '''Unique email addresses in the text.'''
232+ matches = re.finditer(FormattersAPI._re_email, text)
233+ return list(set([match.group() for match in matches]))
234+
235+
236 class FormattersAPI:
237 """Adapter from strings to HTML formatted text."""
238
239
240=== modified file 'lib/lp/app/browser/tests/test_stringformatter.py'
241--- lib/lp/app/browser/tests/test_stringformatter.py 2011-05-29 23:34:26 +0000
242+++ lib/lp/app/browser/tests/test_stringformatter.py 2011-06-24 10:53:25 +0000
243@@ -15,7 +15,10 @@
244 from canonical.launchpad.testing.pages import find_tags_by_class
245 from canonical.launchpad.webapp.interfaces import ILaunchBag
246 from canonical.testing.layers import DatabaseFunctionalLayer
247-from lp.app.browser.stringformatter import FormattersAPI
248+from lp.app.browser.stringformatter import (
249+ FormattersAPI,
250+ linkify_bug_numbers,
251+ )
252 from lp.testing import TestCase
253
254
255@@ -141,6 +144,11 @@
256 expected_html,
257 [FormattersAPI(text).text_to_html() for text in test_strings])
258
259+ def test_explicit_bug_linkification(self):
260+ text = 'LP: #10'
261+ self.assertEqual(
262+ 'LP: <a href="/bugs/10">#10</a>', linkify_bug_numbers(text))
263+
264
265 class TestLinkifyingProtocols(TestCase):
266
267@@ -194,7 +202,7 @@
268 ('<p><a rel="nofollow" '
269 'href="http://example.com/path_(with_parens">'
270 'http://<wbr></wbr>example.<wbr></wbr>com'
271- '/path_<wbr></wbr>(with_parens</a></p>'),
272+ '/path_<wbr></wbr>(with_parens</a></p>'),
273 ]
274
275 self.assertEqual(
276@@ -275,7 +283,6 @@
277
278 class TestDiffFormatter(TestCase):
279 """Test the string formatter fmt:diff."""
280- layer = DatabaseFunctionalLayer
281
282 def test_emptyString(self):
283 # An empty string gives an empty string.
284
285=== modified file 'lib/lp/app/doc/displaying-dates.txt'
286--- lib/lp/app/doc/displaying-dates.txt 2010-09-25 14:48:49 +0000
287+++ lib/lp/app/doc/displaying-dates.txt 2011-06-24 10:53:25 +0000
288@@ -27,7 +27,7 @@
289 First, let's bring in some dependencies:
290
291 >>> from datetime import datetime, timedelta
292- >>> from canonical.launchpad.ftests import test_tales
293+ >>> from lp.testing import test_tales
294 >>> import pytz
295 >>> UTC = pytz.timezone('UTC')
296
297
298=== modified file 'lib/lp/app/doc/displaying-paragraphs-of-text.txt'
299--- lib/lp/app/doc/displaying-paragraphs-of-text.txt 2010-12-21 16:13:15 +0000
300+++ lib/lp/app/doc/displaying-paragraphs-of-text.txt 2011-06-24 10:53:25 +0000
301@@ -6,7 +6,7 @@
302
303 == Basics ==
304
305- >>> from canonical.launchpad.ftests import test_tales
306+ >>> from lp.testing import test_tales
307
308 >>> text = ('This is a paragraph.\n'
309 ... '\n'
310
311=== modified file 'lib/lp/app/doc/tales-email-formatting.txt'
312--- lib/lp/app/doc/tales-email-formatting.txt 2010-09-25 14:29:32 +0000
313+++ lib/lp/app/doc/tales-email-formatting.txt 2011-06-24 10:53:25 +0000
314@@ -10,7 +10,7 @@
315
316 First, let's bring in a small helper function:
317
318- >>> from canonical.launchpad.ftests import test_tales
319+ >>> from lp.testing import test_tales
320
321 == Quoting styles ==
322
323
324=== modified file 'lib/lp/app/doc/tales-macro.txt'
325--- lib/lp/app/doc/tales-macro.txt 2010-09-25 14:29:32 +0000
326+++ lib/lp/app/doc/tales-macro.txt 2011-06-24 10:53:25 +0000
327@@ -16,7 +16,7 @@
328 <html metal:use-macro="view/macro:page/main_side" />
329
330
331- >>> from canonical.launchpad.ftests import test_tales
332+ >>> from lp.testing import test_tales
333 >>> view = FakeView()
334
335 # Return value is the compiled macro expression.
336
337=== modified file 'lib/lp/app/doc/tales.txt'
338--- lib/lp/app/doc/tales.txt 2011-05-31 15:46:32 +0000
339+++ lib/lp/app/doc/tales.txt 2011-06-24 10:53:25 +0000
340@@ -7,7 +7,7 @@
341
342 First, let's bring in a small helper function:
343
344- >>> from canonical.launchpad.ftests import test_tales
345+ >>> from lp.testing import test_tales
346
347
348 The count: namespace to get numbers
349
350=== modified file 'lib/lp/bugs/doc/displaying-bugs-and-tasks.txt'
351--- lib/lp/bugs/doc/displaying-bugs-and-tasks.txt 2011-03-23 16:28:51 +0000
352+++ lib/lp/bugs/doc/displaying-bugs-and-tasks.txt 2011-06-24 10:53:25 +0000
353@@ -16,7 +16,7 @@
354 >>> from zope.component import getUtility
355 >>> from canonical.launchpad.webapp.interfaces import ILaunchBag
356 >>> from lp.bugs.interfaces.bugtask import BugTaskImportance, IBugTaskSet
357- >>> from canonical.launchpad.ftests import (
358+ >>> from lp.testing import (
359 ... login,
360 ... test_tales,
361 ... )
362
363=== modified file 'lib/lp/code/model/revision.py'
364--- lib/lp/code/model/revision.py 2011-02-11 10:35:23 +0000
365+++ lib/lp/code/model/revision.py 2011-06-24 10:53:25 +0000
366@@ -345,7 +345,7 @@
367 # author per revision, so we use the first on the assumption that
368 # this is the primary author.
369 try:
370- author = bzr_revision.get_apparent_authors()[0]
371+ author = authors[0]
372 except IndexError:
373 author = None
374 return self.new(
375
376=== modified file 'lib/lp/registry/browser/distributionsourcepackage.py'
377--- lib/lp/registry/browser/distributionsourcepackage.py 2011-05-12 14:55:54 +0000
378+++ lib/lp/registry/browser/distributionsourcepackage.py 2011-06-24 10:53:25 +0000
379@@ -60,6 +60,10 @@
380 )
381 from lp.answers.enums import QuestionStatus
382 from lp.app.browser.tales import CustomizableFormatter
383+from lp.app.browser.stringformatter import (
384+ extract_bug_numbers,
385+ extract_email_addresses,
386+ )
387 from lp.app.enums import ServiceUsage
388 from lp.app.interfaces.launchpad import IServiceUsage
389 from lp.bugs.browser.bugtask import BugTargetTraversalMixin
390@@ -78,8 +82,6 @@
391 from lp.registry.interfaces.series import SeriesStatus
392 from lp.services.propertycache import cachedproperty
393 from lp.soyuz.browser.sourcepackagerelease import (
394- extract_bug_numbers,
395- extract_email_addresses,
396 linkify_changelog,
397 )
398 from lp.soyuz.interfaces.archive import IArchiveSet
399
400=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
401--- lib/lp/registry/browser/tests/milestone-views.txt 2011-03-04 00:55:49 +0000
402+++ lib/lp/registry/browser/tests/milestone-views.txt 2011-06-24 10:53:25 +0000
403@@ -10,7 +10,7 @@
404
405 The default url for a milestone is to the main site.
406
407- >>> from canonical.launchpad.ftests import test_tales
408+ >>> from lp.testing import test_tales
409 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
410
411 >>> request = LaunchpadTestRequest(SERVER_URL='http://bugs.launchpad.net')
412
413=== modified file 'lib/lp/registry/doc/sourcepackage.txt'
414--- lib/lp/registry/doc/sourcepackage.txt 2011-04-11 16:00:53 +0000
415+++ lib/lp/registry/doc/sourcepackage.txt 2011-06-24 10:53:25 +0000
416@@ -383,19 +383,8 @@
417 <BLANKLINE>
418
419 The view will linkify bug numbers of the format "LP: #number" in the
420-changelog if number is a valid bug ID (also see Soyuz page tests 24
421--sourcepackage-changelog.txt). The changelog() method calls a method
422-_linkify_bug_numbers() to do this.
423-
424-Zero or more spaces may appear between the ":" and the "#".
425-
426- >>> from lp.soyuz.browser.sourcepackagerelease import (
427- ... linkify_bug_numbers)
428- >>> linkify_bug_numbers("LP: #10")
429- 'LP: <a href="/bugs/10">#10</a>'
430-
431-More complete examples of the bug linkification can be found in the doctest
432-displaying-paragraphs-of-text.
433+changelog if number is a valid bug ID (see Soyuz page tests 24
434+-sourcepackage-changelog.txt).
435
436
437 Comparing Sourcepackages
438
439=== modified file 'lib/lp/registry/doc/team-nav-menus.txt'
440--- lib/lp/registry/doc/team-nav-menus.txt 2011-01-04 16:08:57 +0000
441+++ lib/lp/registry/doc/team-nav-menus.txt 2011-06-24 10:53:25 +0000
442@@ -3,7 +3,7 @@
443 The team pages have their own navigation menu.
444
445 >>> from zope.component import queryAdapter
446- >>> from canonical.launchpad.ftests import test_tales
447+ >>> from lp.testing import test_tales
448 >>> from canonical.launchpad.webapp.interfaces import INavigationMenu
449 >>> from canonical.lazr.testing.menus import summarise_tal_links
450 >>> from lp.registry.interfaces.person import IPersonSet
451
452=== modified file 'lib/lp/scripts/utilities/warninghandler.py'
453--- lib/lp/scripts/utilities/warninghandler.py 2010-08-20 20:31:18 +0000
454+++ lib/lp/scripts/utilities/warninghandler.py 2011-06-24 10:53:25 +0000
455@@ -158,9 +158,7 @@
456 if file is None:
457 file = sys.stderr
458 stream = StringIO.StringIO()
459- # XXX: JonathanLange 2010-07-27: When Launchpad ceases supporting Python
460- # 2.5, pass on the optional 'line' parameter.
461- old_show_warning(message, category, filename, lineno, stream)
462+ old_show_warning(message, category, filename, lineno, stream, line=line)
463 warning_message = stream.getvalue()
464 important_info = find_important_info()
465
466
467=== modified file 'lib/lp/services/osutils.py'
468=== modified file 'lib/lp/soyuz/browser/sourcepackagerelease.py'
469--- lib/lp/soyuz/browser/sourcepackagerelease.py 2010-12-17 19:16:54 +0000
470+++ lib/lp/soyuz/browser/sourcepackagerelease.py 2011-06-24 10:53:25 +0000
471@@ -1,15 +1,11 @@
472-# Copyright 2009 Canonical Ltd. This software is licensed under the
473+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
474 # GNU Affero General Public License version 3 (see the file LICENSE).
475
476 """Browser view for a sourcepackagerelease"""
477
478 __metaclass__ = type
479
480-# XXX: JonathanLange 2010-01-06: Many of these functions should be moved to a
481-# generic lp.services.text module.
482 __all__ = [
483- 'extract_bug_numbers',
484- 'extract_email_addresses',
485 'linkify_changelog',
486 'SourcePackageReleaseView',
487 ]
488@@ -18,75 +14,10 @@
489 import re
490
491 from canonical.launchpad.webapp import LaunchpadView
492-from lp.app.browser.stringformatter import FormattersAPI
493-
494-
495-def extract_bug_numbers(text):
496- '''Unique bug numbers matching the "LP: #n(, #n)*" pattern in the text.'''
497- # FormattersAPI._linkify_substitution requires a match object
498- # that has named groups "bug" and "bugnum". The matching text for
499- # the "bug" group is used as the link text and "bugnum" forms part
500- # of the URL for the link to the bug. Example:
501- # >>> bm.groupdict( )
502- # {'bugnum': '400686', 'bug': '#400686'}
503-
504- # We need to match bug numbers of the form:
505- # LP: #1, #2, #3
506- # #4, #5
507- # over multiple lines.
508- #
509- # Writing a single catch-all regex for this has proved rather hard
510- # so I am taking the strategy of matching LP:(group) first, and
511- # feeding the result into another regex to pull out the bug and
512- # bugnum groups.
513- unique_bug_matches = dict()
514-
515- line_matches = re.finditer(
516- 'LP:\s*(?P<buglist>(.+?[^,]))($|\n)', text,
517- re.DOTALL | re.IGNORECASE)
518-
519- for line_match in line_matches:
520- bug_matches = re.finditer(
521- '\s*((?P<bug>#(?P<bugnum>\d+)),?\s*)',
522- line_match.group('buglist'))
523-
524- for bug_match in bug_matches:
525- bugnum = bug_match.group('bugnum')
526- if bugnum in unique_bug_matches:
527- # We got this bug already, ignore it.
528- continue
529- unique_bug_matches[bugnum] = bug_match
530-
531- return unique_bug_matches
532-
533-
534-def linkify_bug_numbers(text):
535- """Linkify to a bug if LP: #number appears in the (changelog) text."""
536- unique_bug_matches = extract_bug_numbers(text)
537- for bug_match in unique_bug_matches.values():
538- replace_text = bug_match.group('bug')
539- if replace_text is not None:
540- # XXX julian 2008-01-10
541- # Note that re.sub would be far more efficient to use
542- # instead of string.replace() but this requires a regex
543- # that matches everything in one go. We're also at danger
544- # of replacing the wrong thing if string.replace() finds
545- # other matching substrings. So for example in the
546- # string:
547- # "LP: #9, #999"
548- # replacing #9 with some HTML would also interfere with
549- # #999. The liklihood of this happening is very, very
550- # small, however.
551- text = text.replace(
552- replace_text,
553- FormattersAPI._linkify_substitution(bug_match))
554- return text
555-
556-
557-def extract_email_addresses(text):
558- '''Unique email addresses in the text.'''
559- matches = re.finditer(FormattersAPI._re_email, text)
560- return list(set([match.group() for match in matches]))
561+from lp.app.browser.stringformatter import (
562+ FormattersAPI,
563+ linkify_bug_numbers,
564+ )
565
566
567 def obfuscate_email(user, text):
568
569=== modified file 'lib/lp/testing/__init__.py'
570--- lib/lp/testing/__init__.py 2011-06-23 00:19:05 +0000
571+++ lib/lp/testing/__init__.py 2011-06-24 10:53:25 +0000
572@@ -154,8 +154,6 @@
573 with_celebrity_logged_in,
574 with_person_logged_in,
575 )
576-# canonical.launchpad.ftests expects test_tales to be imported from here.
577-# XXX: JonathanLange 2010-01-01: Why?!
578 from lp.testing._tales import test_tales
579 from lp.testing._webservice import (
580 api_url,