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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
|
Displaying Paragraphs of Text with ZPT
======================================
To display paragraphs of text in HTML, use fmt:text-to-html. For details,
see <https://launchpad.canonical.com/DisplayingParagraphsOfText>.
Basics
------
>>> from lp.testing import test_tales
>>> text = ('This is a paragraph.\n'
... '\n'
... 'This is another paragraph.')
>>> test_tales('foo/fmt:text-to-html', foo=text)
'<p>This is a paragraph.</p>\n<p>This is another paragraph.</p>'
>>> text = ('This is a line.\n'
... 'This is another line.')
>>> test_tales('foo/fmt:text-to-html', foo=text)
'<p>This is a line.<br />\nThis is another line.</p>'
>>> text = (
... 'This is a paragraph that has been hard-wrapped by an e-mail'
... ' application.\n'
... 'We used to handle this specially, but we no longer do because it'
... ' was disturbing\n'
... 'the display of backtraces. Expected results:\n'
... '* joy\n'
... '* elation'
... )
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>This is a paragraph that has been hard-wrapped by an e-mail
application.<br />
We used to handle this specially, but we no longer do because it was
disturbing<br />
the display of backtraces. Expected results:<br />
* joy<br />
* elation</p>
>>> text = (
... " 1. Here's an example\n"
... " 2. where a list is followed by a paragraph.\n"
... " Leading spaces in a line or paragraph are presented, which "
... "means converting them to . Trailing spaces are passed "
... "through as-is, which means browsers will ignore them, but "
... "that's fine, they're not important anyway.\n"
... )
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p> 1. Here's an example<br />
2. where a list is followed by a paragraph.<br />
Leading spaces in a line or paragraph are presented, which means converting them to &nbsp;. Trailing spaces are passed through as-is, which means browsers will ignore them, but that's fine, they're not important anyway.</p>
>>> text = (
... 'This is a little paragraph all by itself. How cute!'
... )
>>> test_tales('foo/fmt:text-to-html', foo=text)
'<p>This is a little paragraph all by itself. How cute!</p>'
>>> text = (
... 'Here are two paragraphs with lots of whitespace between them.\n'
... '\n'
... '\n'
... '\n'
... '\n'
... 'But they\'re still just two paragraphs.')
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>Here are two paragraphs with lots of whitespace between them.</p>
<p>But they're still just two paragraphs.</p>
If a line begins with whitespace, it will not be merged with the
previous line. This aids in the display of code samples:
>>> text = (
... 'This is a code sample written in Python.\n'
... ' def messageCount(self):\n'
... ' """See IRosettaStats."""\n'
... ' return self.potemplate.messageCount()\n'
... '\n'
... ' def currentCount(self, language=None):\n'
... ' """See IRosettaStats."""\n'
... ' return self.currentCount\n')
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>This is a code sample written in Python.<br />
def messageCount(self):<br />
"""See IRosettaStats."""<br />
return self.potemplate<wbr></wbr>.messageCount(<wbr></wbr>)</p>
<p> def currentCount(self, language=None):<br />
"""See IRosettaStats."""<br />
return self.currentCount</p>
Testing a bunch of URL links.
>>> text = (
... 'https://launchpad.net/ is the new Launchpad site\n'
... 'http://example.com/something?foo=bar&hum=baz\n'
... 'You can check the PPC md5sums at '
... 'ftp://ftp.ubuntu.com/ubuntu/dists/breezy/main/installer-powerpc'
... '/current/images/MD5SUMS\n'
... 'irc://irc.freenode.net/#launchpad\n'
... '\n'
... 'I have a Jabber account (jabber:foo@jabber.example.com)\n'
... 'Foo Bar <mailto:foo.bar@example.net>')
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a rel="nofollow" href="https://launchpad.net/">https:/<wbr></wbr>/launchpad.<wbr></wbr>net/</a> is the new Launchpad site<br />
<a rel="nofollow" href="http://example.com/something?foo=bar&hum=baz">http://<wbr></wbr>example.<wbr></wbr>com/something?<wbr></wbr>foo=bar&<wbr></wbr>hum=baz</a><br />
You can check the PPC md5sums at <a rel="nofollow" href="ftp://ftp.ubuntu.com/ubuntu/dists/breezy/main/installer-powerpc/current/images/MD5SUMS">ftp://ftp.<wbr></wbr>ubuntu.<wbr></wbr>com/ubuntu/<wbr></wbr>dists/breezy/<wbr></wbr>main/installer-<wbr></wbr>powerpc/<wbr></wbr>current/<wbr></wbr>images/<wbr></wbr>MD5SUMS</a><br />
<a rel="nofollow" href="irc://irc.freenode.net/#launchpad">irc://irc.<wbr></wbr>freenode.<wbr></wbr>net/#launchpad</a></p>
<p>I have a Jabber account (<a rel="nofollow" href="jabber:foo@jabber.example.com">jabber:<wbr></wbr>foo@jabber.<wbr></wbr>example.<wbr></wbr>com</a>)<br />
Foo Bar <<a rel="nofollow" href="mailto:foo.bar@example.net">mailto:<wbr></wbr>foo.bar@<wbr></wbr>example.<wbr></wbr>net</a>></p>
URL linkification
-----------------
fmt:text-to-html knows how to linkify URLs:
>>> text = (
... 'http://localhost:8086/bar/baz/foo.html\n'
... 'ftp://localhost:8086/bar/baz/foo.bar.html\n'
... 'sftp://localhost:8086/bar/baz/foo.bar.html.\n'
... 'http://localhost:8086/bar/baz/foo.bar.html;\n'
... 'news://localhost:8086/bar/baz/foo.bar.html:\n'
... 'http://localhost:8086/bar/baz/foo.bar.html?\n'
... 'http://localhost:8086/bar/baz/foo.bar.html,\n'
... '<http://localhost:8086/bar/baz/foo.bar.html>\n'
... '<http://localhost:8086/bar/baz/foo.bar.html>,\n'
... '<http://localhost:8086/bar/baz/foo.bar.html>.\n'
... '<http://localhost:8086/bar/baz/foo.bar.html>;\n'
... '<http://localhost:8086/bar/baz/foo.bar.html>:\n'
... '<http://localhost:8086/bar/baz/foo.bar.html>?\n'
... '(http://localhost:8086/bar/baz/foo.bar.html)\n'
... '(http://localhost:8086/bar/baz/foo.bar.html),\n'
... '(http://localhost:8086/bar/baz/foo.bar.html).\n'
... '(http://localhost:8086/bar/baz/foo.bar.html);\n'
... '(http://localhost:8086/bar/baz/foo.bar.html):\n'
... 'http://localhost/bar/baz/foo.bar.html?a=b&b=a\n'
... 'http://localhost/bar/baz/foo.bar.html?a=b&b=a.\n'
... 'http://localhost/bar/baz/foo.bar.html?a=b&b=a,\n'
... 'http://localhost/bar/baz/foo.bar.html?a=b&b=a;\n'
... 'http://localhost/bar/baz/foo.bar.html?a=b&b=a:\n'
... 'http://localhost/bar/baz/foo.bar.html?'
... 'a=b&b=a:b;c@d_e%f~g#h,j!k-l+m$n*o\'p\n'
... 'http://www.searchtools.com/test/urls/(parens).html\n'
... 'http://www.searchtools.com/test/urls/-dash.html\n'
... 'http://www.searchtools.com/test/urls/_underscore.html\n'
... 'http://www.searchtools.com/test/urls/period.x.html\n'
... 'http://www.searchtools.com/test/urls/!exclamation.html\n'
... 'http://www.searchtools.com/test/urls/~tilde.html\n'
... 'http://www.searchtools.com/test/urls/*asterisk.html\n'
... 'irc://irc.freenode.net/launchpad\n'
... 'irc://irc.freenode.net/%23launchpad,isserver\n'
... 'mailto:noreply@launchpad.net\n'
... 'jabber:noreply@launchpad.net\n'
... 'http://localhost/foo?xxx&\n'
... 'http://localhost?testing=[square-brackets-in-query]\n'
... )
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a rel="nofollow" href="http://localhost:8086/bar/baz/foo.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>html</a><br />
<a rel="nofollow" href="ftp://localhost:8086/bar/baz/foo.bar.html">ftp://localhost<wbr></wbr>:8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a><br />
<a rel="nofollow" href="sftp://localhost:8086/bar/baz/foo.bar.html">sftp://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>.<br />
<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>;<br />
<a rel="nofollow" href="news://localhost:8086/bar/baz/foo.bar.html">news://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>:<br />
<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>?<br />
<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>,<br />
<<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>><br />
<<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>>,<br />
<<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>>.<br />
<<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>>;<br />
<<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>>:<br />
<<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>>?<br />
(<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>)<br />
(<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>),<br />
(<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>).<br />
(<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>);<br />
(<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>):<br />
<a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&b=a">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&b=a</a><br />
<a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&b=a">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&b=a</a>.<br />
<a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&b=a">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&b=a</a>,<br />
<a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&b=a">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&b=a</a>;<br />
<a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&b=a">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&b=a</a>:<br />
<a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&b=a:b;c@d_e%f~g#h,j!k-l+m$n*o'p">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&b=a:b;<wbr></wbr>c@d_e%f~<wbr></wbr>g#h,j!k-<wbr></wbr>l+m$n*o'<wbr></wbr>p</a><br />
<a rel="nofollow" href="http://www.searchtools.com/test/urls/(parens).html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/(parens)<wbr></wbr>.html</a><br />
<a rel="nofollow" href="http://www.searchtools.com/test/urls/-dash.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/-dash.<wbr></wbr>html</a><br />
<a rel="nofollow" href="http://www.searchtools.com/test/urls/_underscore.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/_underscor<wbr></wbr>e.html</a><br />
<a rel="nofollow" href="http://www.searchtools.com/test/urls/period.x.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/period.<wbr></wbr>x.html</a><br />
<a rel="nofollow" href="http://www.searchtools.com/test/urls/!exclamation.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/!exclamati<wbr></wbr>on.html</a><br />
<a rel="nofollow" href="http://www.searchtools.com/test/urls/~tilde.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/~tilde.<wbr></wbr>html</a><br />
<a rel="nofollow" href="http://www.searchtools.com/test/urls/*asterisk.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/*asterisk.<wbr></wbr>html</a><br />
<a rel="nofollow" href="irc://irc.freenode.net/launchpad">irc://irc.<wbr></wbr>freenode.<wbr></wbr>net/launchpad</a><br />
<a rel="nofollow" href="irc://irc.freenode.net/%23launchpad,isserver">irc://irc.<wbr></wbr>freenode.<wbr></wbr>net/%23launchpa<wbr></wbr>d,isserver</a><br />
<a rel="nofollow" href="mailto:noreply@launchpad.net">mailto:<wbr></wbr>noreply@<wbr></wbr>launchpad.<wbr></wbr>net</a><br />
<a rel="nofollow" href="jabber:noreply@launchpad.net">jabber:<wbr></wbr>noreply@<wbr></wbr>launchpad.<wbr></wbr>net</a><br />
<a rel="nofollow" href="http://localhost/foo?xxx&">http://<wbr></wbr>localhost/<wbr></wbr>foo?xxx&</a><br />
<a rel="nofollow" href="http://localhost?testing=[square-brackets-in-query]">http://<wbr></wbr>localhost?<wbr></wbr>testing=<wbr></wbr>[square-<wbr></wbr>brackets-<wbr></wbr>in-query]</a></p>
The fmt:text-to-html formatter leaves a number of non-URIs unlinked:
>>> text = (
... 'nothttp://launchpad.net/\n'
... 'http::No-cache=True\n')
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>nothttp:<wbr></wbr>//launchpad.<wbr></wbr>net/<br />
http::No-cache=True</p>
Bug references
--------------
fmt:text-to-html is also smart enough to convert bug references into
links:
>>> text = (
... 'bug 123\n'
... 'bug 123\n'
... 'bug #123\n'
... 'bug number 123\n'
... 'bug number. 123\n'
... 'bug num 123\n'
... 'bug num. 123\n'
... 'bug no 123\n'
... 'bug report 123\n'
... 'bug no. 123\n'
... 'bug#123\n'
... 'bug-123\n'
... 'bug-report-123\n'
... 'bug=123\n'
... 'bug\n'
... '#123\n'
... 'debug #52\n')
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="/bugs/123" class="bug-link">bug 123</a><br />
<a href="/bugs/123" class="bug-link">bug 123</a><br />
<a href="/bugs/123" class="bug-link">bug #123</a><br />
<a href="/bugs/123" class="bug-link">bug number 123</a><br />
bug number. 123<br />
<a href="/bugs/123" class="bug-link">bug num 123</a><br />
<a href="/bugs/123" class="bug-link">bug num. 123</a><br />
<a href="/bugs/123" class="bug-link">bug no 123</a><br />
<a href="/bugs/123" class="bug-link">bug report 123</a><br />
<a href="/bugs/123" class="bug-link">bug no. 123</a><br />
bug#123<br />
<a href="/bugs/123" class="bug-link">bug-123</a><br />
<a href="/bugs/123" class="bug-link">bug-report-123</a><br />
<a href="/bugs/123" class="bug-link">bug=123</a><br />
<a href="/bugs/123" class="bug-link">bug<br /> #123</a><br />
debug #52</p>
>>> text = (
... 'bug 123\n'
... 'bug 123\n')
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="/bugs/123" class="bug-link">bug 123</a><br />
<a href="/bugs/123" class="bug-link">bug 123</a></p>
>>> text = (
... 'bug 1234\n'
... 'bug 123\n')
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="/bugs/1234" class="bug-link">bug 1234</a><br />
<a href="/bugs/123" class="bug-link">bug 123</a></p>
>>> text = 'bug 0123\n'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="/bugs/123" class="bug-link">bug 0123</a></p>
We linkify bugs that are in the Ubuntu convention for referring to bugs in
Debian changelogs.
>>> text = 'LP: #123.\n'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>LP: <a href="/bugs/123" class="bug-link">#123</a>.</p>
Works with multiple bugs:
>>> text = 'LP: #123, #2.\n'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>LP: <a href="/bugs/123" class="bug-link">#123</a>, <a href="/bugs/2" class="bug-link">#2</a>.</p>
And with lower case 'lp' too:
>>> text = 'lp: #123, #2.\n'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>lp: <a href="/bugs/123" class="bug-link">#123</a>, <a href="/bugs/2" class="bug-link">#2</a>.</p>
Even line breaks cannot stop the power of bug linking:
>>> text = 'LP: #123,\n#2.\n'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>LP: <a href="/bugs/123" class="bug-link">#123</a>,<br />
<a href="/bugs/2" class="bug-link">#2</a>.</p>
To check a private bug, we need to log in and set a bug to be private.
>>> from zope.component import getUtility
>>> from lp.bugs.interfaces.bug import IBugSet
>>> bugset = getUtility(IBugSet)
>>> firefox_crashes = bugset.get(6)
>>> login("test@canonical.com")
>>> current_user = getUtility(ILaunchBag).user
>>> firefox_crashes.setPrivate(True, current_user)
True
Bug.setPrivate adds all indirect subscribers to the bug as direct
subscribers, but we want to see what the bug looks like if we're not a
subscriber.
>>> firefox_crashes.unsubscribe(current_user, current_user)
A private bug is still linked as no check is made on the actual bug.
>>> text = 'bug 6\n'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="/bugs/6" class="bug-link">bug 6</a></p>
FAQ references
--------------
FAQ references are global, and also linkified:
>>> text = (
... 'faq 1\n'
... 'faq #2\n'
... 'faq-2\n'
... 'faq=2\n'
... 'faq item 1\n'
... 'faq number 2\n'
... )
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="http://answers.launchpad.dev/ubuntu/+faq/1">faq 1</a><br />
<a href="http://answers.launchpad.dev/ubuntu/+faq/2">faq #2</a><br />
<a href="http://answers.launchpad.dev/ubuntu/+faq/2">faq-2</a><br />
<a href="http://answers.launchpad.dev/ubuntu/+faq/2">faq=2</a><br />
<a href="http://answers.launchpad.dev/ubuntu/+faq/1">faq item 1</a><br />
<a href="http://answers.launchpad.dev/ubuntu/+faq/2">faq number 2</a></p>
Except, that is, when the FAQ doesn't exist:
>>> text = (
... 'faq 999\n'
... )
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>faq 999</p>
Branch references
-----------------
Branch references are linkified:
>>> text = (
... 'lp:~foo/bar/baz\n'
... 'lp:~foo/bar/bug-123\n'
... 'lp:~foo/+junk/baz\n'
... 'lp:~foo/ubuntu/jaunty/evolution/baz\n'
... 'lp:foo/bar\n'
... 'lp:foo\n'
... 'lp:foo,\n'
... 'lp:foo/bar.\n'
... 'lp:foo/bar/baz\n'
... 'lp:///foo\n'
... 'lp:/foo\n')
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="/+branch/~foo/bar/baz" class="...">lp:~foo/bar/baz</a><br />
<a href="/+branch/~foo/bar/bug-123" class="...">lp:~foo/bar/bug-123</a><br />
<a href="/+branch/~foo/+junk/baz" class="...">lp:~foo/+junk/baz</a><br />
<a href="/+branch/~foo/ubuntu/jaunty/evolution/baz" class="...">lp:~foo/ubuntu/jaunty/evolution/baz</a><br />
<a href="/+branch/foo/bar" class="...">lp:foo/bar</a><br />
<a href="/+branch/foo" class="...">lp:foo</a><br />
<a href="/+branch/foo" class="...">lp:foo</a>,<br />
<a href="/+branch/foo/bar" class="...">lp:foo/bar</a>.<br />
<a href="/+branch/foo/bar/baz" class="...">lp:foo/bar/baz</a><br />
<a href="/+branch/foo" class="...">lp:///foo</a><br />
<a href="/+branch/foo" class="...">lp:/foo</a></p>
Text that looks like a branch reference, but is followed only by digits is
treated as a link to a bug.
>>> text = 'lp:1234'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="/bugs/1234" class="bug-link">lp:1234</a></p>
We are even smart enough to notice the trailing punctuation gunk and separate
that from the link.
>>> text = 'lp:1234,'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="/bugs/1234" class="bug-link">lp:1234</a>,</p>
OOPS references
---------------
fmt:text-to-html is also smart enough to convert OOPS references into
links. However, it only does this if the logged in person is a member of the
Launchpad Developers team.
XXX 2006-08-23 jamesh
We explicitly cal set_developer_in_launchbag_before_traversal() here.
If this event handler is not called, then the "developer" attribute in
the launchbag is not updated. Normally it would be called during the
request before traversal, but we aren't doing publication traversal in
this test.
https://launchpad.net/bugs/30746
When not logged in as a privileged user, no link:
>>> from lp.services.webapp.launchbag import (
... set_developer_in_launchbag_before_traversal)
>>> login('test@canonical.com')
>>> set_developer_in_launchbag_before_traversal(None)
>>> getUtility(ILaunchBag).developer
False
>>> text = 'OOPS-38C23'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>OOPS-38C23</p>
After login, a link:
>>> login('foo.bar@canonical.com')
>>> set_developer_in_launchbag_before_traversal(None)
>>> getUtility(ILaunchBag).developer
True
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="https://lp-oops.canonical.com/oops.py/?oopsid=OOPS-38C23">OOPS-38C23</a></p>
OOPS references can take a number of forms:
>>> text = 'OOPS-38C23'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="https://lp-oops.canonical.com/oops.py/?oopsid=OOPS-38C23">OOPS-38C23</a></p>
>>> text = 'OOPS-123abcdef'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="https://lp-oops.canonical.com/oops.py/?oopsid=OOPS-123abcdef">OOPS-123abcdef</a></p>
>>> text = 'OOPS-abcdef123'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="https://lp-oops.canonical.com/oops.py/?oopsid=OOPS-abcdef123">OOPS-abcdef123</a></p>
If the configuration value doesn't end with a slash, we won't add one. This
lets us configure the URL to use query parameters.
>>> from lp.services.config import config
>>> oops_root_url = """
... [launchpad]
... oops_root_url: http://foo/bar
... """
>>> config.push('oops_root_url', oops_root_url)
>>> text = 'OOPS-38C23'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p><a href="http://foo/barOOPS-38C23">OOPS-38C23</a></p>
>>> config_data = config.pop('oops_root_url')
Check against false positives:
>>> text = 'OOPS code'
>>> print test_tales('foo/fmt:text-to-html', foo=text)
<p>OOPS code</p>
Reset login information.
>>> login('test@canonical.com')
>>> set_developer_in_launchbag_before_traversal(None)
>>> getUtility(ILaunchBag).developer
False
Regex helper functions
----------------------
The _substitute_matchgroup_for_spaces() static method is part of the
fmt:text-to-html code. It is a helper for writing regular expressions where
we want to replace a variable number of spaces with the same number of
entities.
>>> from lp.app.browser.stringformatter import FormattersAPI
>>> import re
>>> matchobj = re.match('foo(.*)bar', 'fooX Ybar')
>>> matchobj.groups()
('X Y',)
>>> FormattersAPI._substitute_matchgroup_for_spaces(matchobj)
' '
The _linkify_substitution() static method is used for converting bug
references or URLs into links. It uses the named matchgroups 'bug' and
'bugnum' when it is dealing with bugs, and 'url' when it is dealing with URLs.
First, let's try a match of nothing it understands. This is a bug, so we get
an AssertionError.
>>> matchobj = re.match(
... ('(?P<bug>xxx)?(?P<faq>www)?(?P<url>yyy)?(?P<oops>zzz)?'
... '(?P<lpbranchurl>www)?(?P<clbug>vvv)?'),
... 'fish')
>>> sorted(matchobj.groupdict().items())
[('bug', None),
('clbug', None),
('faq', None),
('lpbranchurl', None),
('oops', None),
('url', None)]
>>> FormattersAPI._linkify_substitution(matchobj)
Traceback (most recent call last):
...
AssertionError: Unknown pattern matched.
When we have a URL, the URL is made into a link. A quote is added to the
url to demonstrate quoting in the HTML attribute.
>>> matchobj = re.match('(?P<bug>xxx)?(?P<url>y"y)?', 'y"y')
>>> sorted(matchobj.groupdict().items())
[('bug', None), ('url', 'y"y')]
>>> FormattersAPI._linkify_substitution(matchobj)
'<a rel="nofollow" href="y"y">y"y</a>'
When we have a bug reference, the 'bug' group is used as the text of the link,
and the 'bugnum' is used to look up the bug.
>>> matchobj = re.match(
... '(?P<bug>xxxx)?(?P<bugnum>2)?(?P<url>yyy)?', 'xxxx2')
>>> sorted(matchobj.groupdict().items())
[('bug', 'xxxx'), ('bugnum', '2'), ('url', None)]
>>> FormattersAPI._linkify_substitution(matchobj)
'<a href="/bugs/2" class="bug-link">xxxx</a>'
When the bugnum doesn't match any bug, we still get a link, but get a message
in the link's title.
>>> matchobj = re.match(
... '(?P<bug>xxxx)?(?P<bugnum>2000)?(?P<url>yyy)?', 'xxxx2000')
>>> sorted(matchobj.groupdict().items())
[('bug', 'xxxx'), ('bugnum', '2000'), ('url', None)]
>>> FormattersAPI._linkify_substitution(matchobj)
'<a href="/bugs/2000" class="bug-link">xxxx</a>'
|