Merge lp:~gagern/bzr/bug513322-authors into lp:bzr
- bug513322-authors
- Merge into bzr.dev
Status: | Merged |
---|---|
Approved by: | Vincent Ladeuil |
Approved revision: | no longer in the source branch. |
Merged at revision: | 5211 |
Proposed branch: | lp:~gagern/bzr/bug513322-authors |
Merge into: | lp:bzr |
Diff against target: |
333 lines (+228/-9) 4 files modified
NEWS (+4/-0) bzrlib/builtins.py (+9/-2) bzrlib/log.py (+70/-7) bzrlib/tests/test_log.py (+145/-0) |
To merge this branch: | bzr merge lp:~gagern/bzr/bug513322-authors |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gary van der Merwe | Approve | ||
Robert Collins (community) | Abstain | ||
Vincent Ladeuil | Approve | ||
Review via email: mp+23122@code.launchpad.net |
This proposal supersedes a proposal from 2010-04-01.
Commit message
Description of the change
This patch introduces a --authors switch to bzr log, allowing users to override the choice of authors for all built-in formats. It comes with a NEWS item and several test cases.
I'm not perfectly happy with the naming, so if you prefer different names for some method or attribute, let me know. I also haven't included a blackbox test ensuring that the --authors command line switch actually gets passed to the log formatter. Do you consider this necessary?
Robert Collins (lifeless) wrote : Posted in a previous version of this proposal | # |
Martin von Gagern (gagern) wrote : | # |
The registry is here, and now I've even signed the canonical contributor agreement. Care to review and hopefully merge this?
Vincent Ladeuil (vila) wrote : | # |
It's easier to review changes if you just push to your original branch and put the mp status back to 'Needs review', your additional revisions then appear after the review comments.
There are still a few details though:
91 + def authors(self, rev, who, short=False, sep=None):
92 + if self._author_
93 + author_list_handler = self._author_
94 + else:
95 + author_list_handler = author_
If 'who' changes, your cached value for the handler is wrong, I don't think it
worth taking the risk, get rid of the cached value instead.
PEP8 nits:
133 +def author_
134 + return rev.get_
135 +
136 +def author_
137 + lst = rev.get_
138 + try:
139 + return [lst[0]]
140 + except IndexError:
141 + return []
142 +
143 +def author_
Two blank lines between functions.
This could be fixed by the second reviewer before merging I think.
Vincent Ladeuil (vila) wrote : | # |
Clicked send too fast.
The overall change looks good to me apart from the details above so it's a bb:tweak (i.e. doesn't need another review to be merged, the required changes being trivial)
Martin von Gagern (gagern) wrote : | # |
> It's easier to review changes if you just push to your original branch and put the mp status back to 'Needs review', your additional revisions then appear after the review comments.
I thought so at first as well. But with the predecessor of this branch
here, I had waited in the "Needs review" state (which the merge requests
maintained anyway, so there was no need to reset anything) for five days
without another vote, then I resubmitted and received an approval within
the day and got it merged within two days.
See https:/
So from that experience, I derived the mental note "resubmit if you want
to get things moving". Nevertheless, I'll give the simple push another try.
> There are still a few details though:
>
> 91 + def authors(self, rev, who, short=False, sep=None):
> 92 + if self._author_
> 93 + author_list_handler = self._author_
> 94 + else:
> 95 + author_list_handler = author_
>
> If 'who' changes, your cached value for the handler is wrong, I don't think it
> worth taking the risk, get rid of the cached value instead.
self._author_
override. The idea is that the log formatter has a preference of whom to
list as author(s), and expresses this preference in the 'who' argument.
The user, on the other hand, can force a different choice, which
overrides whatever the formatter uses by default.
self._author_
Or were you talking about some other cache? If so, I don't see which.
> PEP8 nits:
> Two blank lines between functions.
Fixed and pushed.
Robert Collins (lifeless) : | # |
Gary van der Merwe (garyvdm) wrote : | # |
Small nit pick: Please put your name, and the bug number in NEWS. I've fixed this for you, and submitted to pqm.
Preview Diff
1 | === modified file 'NEWS' | |||
2 | --- NEWS 2010-04-16 13:39:50 +0000 | |||
3 | +++ NEWS 2010-04-19 15:06:32 +0000 | |||
4 | @@ -194,6 +194,10 @@ | |||
5 | 194 | * Merges can be proposed on Launchpad with the new lp-propose-merge command. | 194 | * Merges can be proposed on Launchpad with the new lp-propose-merge command. |
6 | 195 | (Aaron Bentley, Jonathan Lange) | 195 | (Aaron Bentley, Jonathan Lange) |
7 | 196 | 196 | ||
8 | 197 | * New command line option ``--authors`` to ``bzr log`` allows users to | ||
9 | 198 | select which of the apparent authors and committer should be | ||
10 | 199 | included in the log. Defaults depend on format. | ||
11 | 200 | |||
12 | 197 | Bug Fixes | 201 | Bug Fixes |
13 | 198 | ********* | 202 | ********* |
14 | 199 | 203 | ||
15 | 200 | 204 | ||
16 | === modified file 'bzrlib/builtins.py' | |||
17 | --- bzrlib/builtins.py 2010-04-15 15:03:15 +0000 | |||
18 | +++ bzrlib/builtins.py 2010-04-19 15:06:32 +0000 | |||
19 | @@ -2279,6 +2279,11 @@ | |||
20 | 2279 | help='Show just the specified revision.' | 2279 | help='Show just the specified revision.' |
21 | 2280 | ' See also "help revisionspec".'), | 2280 | ' See also "help revisionspec".'), |
22 | 2281 | 'log-format', | 2281 | 'log-format', |
23 | 2282 | RegistryOption('authors', | ||
24 | 2283 | 'What names to list as authors - first, all or committer.', | ||
25 | 2284 | title='Authors', | ||
26 | 2285 | lazy_registry=('bzrlib.log', 'author_list_registry'), | ||
27 | 2286 | ), | ||
28 | 2282 | Option('levels', | 2287 | Option('levels', |
29 | 2283 | short_name='n', | 2288 | short_name='n', |
30 | 2284 | help='Number of levels to display - 0 for all, 1 for flat.', | 2289 | help='Number of levels to display - 0 for all, 1 for flat.', |
31 | @@ -2314,7 +2319,8 @@ | |||
32 | 2314 | message=None, | 2319 | message=None, |
33 | 2315 | limit=None, | 2320 | limit=None, |
34 | 2316 | show_diff=False, | 2321 | show_diff=False, |
36 | 2317 | include_merges=False): | 2322 | include_merges=False, |
37 | 2323 | authors=None): | ||
38 | 2318 | from bzrlib.log import ( | 2324 | from bzrlib.log import ( |
39 | 2319 | Logger, | 2325 | Logger, |
40 | 2320 | make_log_request_dict, | 2326 | make_log_request_dict, |
41 | @@ -2394,7 +2400,8 @@ | |||
42 | 2394 | show_timezone=timezone, | 2400 | show_timezone=timezone, |
43 | 2395 | delta_format=get_verbosity_level(), | 2401 | delta_format=get_verbosity_level(), |
44 | 2396 | levels=levels, | 2402 | levels=levels, |
46 | 2397 | show_advice=levels is None) | 2403 | show_advice=levels is None, |
47 | 2404 | author_list_handler=authors) | ||
48 | 2398 | 2405 | ||
49 | 2399 | # Choose the algorithm for doing the logging. It's annoying | 2406 | # Choose the algorithm for doing the logging. It's annoying |
50 | 2400 | # having multiple code paths like this but necessary until | 2407 | # having multiple code paths like this but necessary until |
51 | 2401 | 2408 | ||
52 | === modified file 'bzrlib/log.py' | |||
53 | --- bzrlib/log.py 2010-04-14 10:38:57 +0000 | |||
54 | +++ bzrlib/log.py 2010-04-19 15:06:32 +0000 | |||
55 | @@ -1317,7 +1317,7 @@ | |||
56 | 1317 | 1317 | ||
57 | 1318 | def __init__(self, to_file, show_ids=False, show_timezone='original', | 1318 | def __init__(self, to_file, show_ids=False, show_timezone='original', |
58 | 1319 | delta_format=None, levels=None, show_advice=False, | 1319 | delta_format=None, levels=None, show_advice=False, |
60 | 1320 | to_exact_file=None): | 1320 | to_exact_file=None, author_list_handler=None): |
61 | 1321 | """Create a LogFormatter. | 1321 | """Create a LogFormatter. |
62 | 1322 | 1322 | ||
63 | 1323 | :param to_file: the file to output to | 1323 | :param to_file: the file to output to |
64 | @@ -1331,6 +1331,8 @@ | |||
65 | 1331 | let the log formatter decide. | 1331 | let the log formatter decide. |
66 | 1332 | :param show_advice: whether to show advice at the end of the | 1332 | :param show_advice: whether to show advice at the end of the |
67 | 1333 | log or not | 1333 | log or not |
68 | 1334 | :param author_list_handler: callable generating a list of | ||
69 | 1335 | authors to display for a given revision | ||
70 | 1334 | """ | 1336 | """ |
71 | 1335 | self.to_file = to_file | 1337 | self.to_file = to_file |
72 | 1336 | # 'exact' stream used to show diff, it should print content 'as is' | 1338 | # 'exact' stream used to show diff, it should print content 'as is' |
73 | @@ -1351,6 +1353,7 @@ | |||
74 | 1351 | self.levels = levels | 1353 | self.levels = levels |
75 | 1352 | self._show_advice = show_advice | 1354 | self._show_advice = show_advice |
76 | 1353 | self._merge_count = 0 | 1355 | self._merge_count = 0 |
77 | 1356 | self._author_list_handler = author_list_handler | ||
78 | 1354 | 1357 | ||
79 | 1355 | def get_levels(self): | 1358 | def get_levels(self): |
80 | 1356 | """Get the number of levels to display or 0 for all.""" | 1359 | """Get the number of levels to display or 0 for all.""" |
81 | @@ -1388,10 +1391,41 @@ | |||
82 | 1388 | return address | 1391 | return address |
83 | 1389 | 1392 | ||
84 | 1390 | def short_author(self, rev): | 1393 | def short_author(self, rev): |
89 | 1391 | name, address = config.parse_username(rev.get_apparent_authors()[0]) | 1394 | return self.authors(rev, 'first', short=True, sep=', ') |
90 | 1392 | if name: | 1395 | |
91 | 1393 | return name | 1396 | def authors(self, rev, who, short=False, sep=None): |
92 | 1394 | return address | 1397 | """Generate list of authors, taking --authors option into account. |
93 | 1398 | |||
94 | 1399 | The caller has to specify the name of a author list handler, | ||
95 | 1400 | as provided by the author list registry, using the ``who`` | ||
96 | 1401 | argument. That name only sets a default, though: when the | ||
97 | 1402 | user selected a different author list generation using the | ||
98 | 1403 | ``--authors`` command line switch, as represented by the | ||
99 | 1404 | ``author_list_handler`` constructor argument, that value takes | ||
100 | 1405 | precedence. | ||
101 | 1406 | |||
102 | 1407 | :param rev: The revision for which to generate the list of authors. | ||
103 | 1408 | :param who: Name of the default handler. | ||
104 | 1409 | :param short: Whether to shorten names to either name or address. | ||
105 | 1410 | :param sep: What separator to use for automatic concatenation. | ||
106 | 1411 | """ | ||
107 | 1412 | if self._author_list_handler is not None: | ||
108 | 1413 | # The user did specify --authors, which overrides the default | ||
109 | 1414 | author_list_handler = self._author_list_handler | ||
110 | 1415 | else: | ||
111 | 1416 | # The user didn't specify --authors, so we use the caller's default | ||
112 | 1417 | author_list_handler = author_list_registry.get(who) | ||
113 | 1418 | names = author_list_handler(rev) | ||
114 | 1419 | if short: | ||
115 | 1420 | for i in range(len(names)): | ||
116 | 1421 | name, address = config.parse_username(names[i]) | ||
117 | 1422 | if name: | ||
118 | 1423 | names[i] = name | ||
119 | 1424 | else: | ||
120 | 1425 | names[i] = address | ||
121 | 1426 | if sep is not None: | ||
122 | 1427 | names = sep.join(names) | ||
123 | 1428 | return names | ||
124 | 1395 | 1429 | ||
125 | 1396 | def merge_marker(self, revision): | 1430 | def merge_marker(self, revision): |
126 | 1397 | """Get the merge marker to include in the output or '' if none.""" | 1431 | """Get the merge marker to include in the output or '' if none.""" |
127 | @@ -1499,7 +1533,7 @@ | |||
128 | 1499 | lines.extend(self.custom_properties(revision.rev)) | 1533 | lines.extend(self.custom_properties(revision.rev)) |
129 | 1500 | 1534 | ||
130 | 1501 | committer = revision.rev.committer | 1535 | committer = revision.rev.committer |
132 | 1502 | authors = revision.rev.get_apparent_authors() | 1536 | authors = self.authors(revision.rev, 'all') |
133 | 1503 | if authors != [committer]: | 1537 | if authors != [committer]: |
134 | 1504 | lines.append('author: %s' % (", ".join(authors),)) | 1538 | lines.append('author: %s' % (", ".join(authors),)) |
135 | 1505 | lines.append('committer: %s' % (committer,)) | 1539 | lines.append('committer: %s' % (committer,)) |
136 | @@ -1679,7 +1713,8 @@ | |||
137 | 1679 | self.show_timezone, | 1713 | self.show_timezone, |
138 | 1680 | date_fmt='%Y-%m-%d', | 1714 | date_fmt='%Y-%m-%d', |
139 | 1681 | show_offset=False) | 1715 | show_offset=False) |
141 | 1682 | committer_str = revision.rev.get_apparent_authors()[0].replace (' <', ' <') | 1716 | committer_str = self.authors(revision.rev, 'first', sep=', ') |
142 | 1717 | committer_str = committer_str.replace(' <', ' <') | ||
143 | 1683 | to_file.write('%s %s\n\n' % (date_str,committer_str)) | 1718 | to_file.write('%s %s\n\n' % (date_str,committer_str)) |
144 | 1684 | 1719 | ||
145 | 1685 | if revision.delta is not None and revision.delta.has_changed(): | 1720 | if revision.delta is not None and revision.delta.has_changed(): |
146 | @@ -1750,6 +1785,34 @@ | |||
147 | 1750 | raise errors.BzrCommandError("unknown log formatter: %r" % name) | 1785 | raise errors.BzrCommandError("unknown log formatter: %r" % name) |
148 | 1751 | 1786 | ||
149 | 1752 | 1787 | ||
150 | 1788 | def author_list_all(rev): | ||
151 | 1789 | return rev.get_apparent_authors()[:] | ||
152 | 1790 | |||
153 | 1791 | |||
154 | 1792 | def author_list_first(rev): | ||
155 | 1793 | lst = rev.get_apparent_authors() | ||
156 | 1794 | try: | ||
157 | 1795 | return [lst[0]] | ||
158 | 1796 | except IndexError: | ||
159 | 1797 | return [] | ||
160 | 1798 | |||
161 | 1799 | |||
162 | 1800 | def author_list_committer(rev): | ||
163 | 1801 | return [rev.committer] | ||
164 | 1802 | |||
165 | 1803 | |||
166 | 1804 | author_list_registry = registry.Registry() | ||
167 | 1805 | |||
168 | 1806 | author_list_registry.register('all', author_list_all, | ||
169 | 1807 | 'All authors') | ||
170 | 1808 | |||
171 | 1809 | author_list_registry.register('first', author_list_first, | ||
172 | 1810 | 'The first author') | ||
173 | 1811 | |||
174 | 1812 | author_list_registry.register('committer', author_list_committer, | ||
175 | 1813 | 'The committer') | ||
176 | 1814 | |||
177 | 1815 | |||
178 | 1753 | def show_one_log(revno, rev, delta, verbose, to_file, show_timezone): | 1816 | def show_one_log(revno, rev, delta, verbose, to_file, show_timezone): |
179 | 1754 | # deprecated; for compatibility | 1817 | # deprecated; for compatibility |
180 | 1755 | lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone) | 1818 | lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone) |
181 | 1756 | 1819 | ||
182 | === modified file 'bzrlib/tests/test_log.py' | |||
183 | --- bzrlib/tests/test_log.py 2010-03-25 08:14:04 +0000 | |||
184 | +++ bzrlib/tests/test_log.py 2010-04-19 15:06:32 +0000 | |||
185 | @@ -1541,3 +1541,148 @@ | |||
186 | 1541 | 1541 | ||
187 | 1542 | def test_bugs_handler_present(self): | 1542 | def test_bugs_handler_present(self): |
188 | 1543 | self.properties_handler_registry.get('bugs_properties_handler') | 1543 | self.properties_handler_registry.get('bugs_properties_handler') |
189 | 1544 | |||
190 | 1545 | |||
191 | 1546 | class TestLogForAuthors(TestCaseForLogFormatter): | ||
192 | 1547 | |||
193 | 1548 | def setUp(self): | ||
194 | 1549 | TestCaseForLogFormatter.setUp(self) | ||
195 | 1550 | self.wt = self.make_standard_commit('nicky', | ||
196 | 1551 | authors=['John Doe <jdoe@example.com>', | ||
197 | 1552 | 'Jane Rey <jrey@example.com>']) | ||
198 | 1553 | |||
199 | 1554 | def assertFormatterResult(self, formatter, who, result): | ||
200 | 1555 | formatter_kwargs = dict() | ||
201 | 1556 | if who is not None: | ||
202 | 1557 | author_list_handler = log.author_list_registry.get(who) | ||
203 | 1558 | formatter_kwargs['author_list_handler'] = author_list_handler | ||
204 | 1559 | TestCaseForLogFormatter.assertFormatterResult(self, result, | ||
205 | 1560 | self.wt.branch, formatter, formatter_kwargs=formatter_kwargs) | ||
206 | 1561 | |||
207 | 1562 | def test_line_default(self): | ||
208 | 1563 | self.assertFormatterResult(log.LineLogFormatter, None, """\ | ||
209 | 1564 | 1: John Doe 2005-11-22 add a | ||
210 | 1565 | """) | ||
211 | 1566 | |||
212 | 1567 | def test_line_committer(self): | ||
213 | 1568 | self.assertFormatterResult(log.LineLogFormatter, 'committer', """\ | ||
214 | 1569 | 1: Lorem Ipsum 2005-11-22 add a | ||
215 | 1570 | """) | ||
216 | 1571 | |||
217 | 1572 | def test_line_first(self): | ||
218 | 1573 | self.assertFormatterResult(log.LineLogFormatter, 'first', """\ | ||
219 | 1574 | 1: John Doe 2005-11-22 add a | ||
220 | 1575 | """) | ||
221 | 1576 | |||
222 | 1577 | def test_line_all(self): | ||
223 | 1578 | self.assertFormatterResult(log.LineLogFormatter, 'all', """\ | ||
224 | 1579 | 1: John Doe, Jane Rey 2005-11-22 add a | ||
225 | 1580 | """) | ||
226 | 1581 | |||
227 | 1582 | |||
228 | 1583 | def test_short_default(self): | ||
229 | 1584 | self.assertFormatterResult(log.ShortLogFormatter, None, """\ | ||
230 | 1585 | 1 John Doe\t2005-11-22 | ||
231 | 1586 | add a | ||
232 | 1587 | |||
233 | 1588 | """) | ||
234 | 1589 | |||
235 | 1590 | def test_short_committer(self): | ||
236 | 1591 | self.assertFormatterResult(log.ShortLogFormatter, 'committer', """\ | ||
237 | 1592 | 1 Lorem Ipsum\t2005-11-22 | ||
238 | 1593 | add a | ||
239 | 1594 | |||
240 | 1595 | """) | ||
241 | 1596 | |||
242 | 1597 | def test_short_first(self): | ||
243 | 1598 | self.assertFormatterResult(log.ShortLogFormatter, 'first', """\ | ||
244 | 1599 | 1 John Doe\t2005-11-22 | ||
245 | 1600 | add a | ||
246 | 1601 | |||
247 | 1602 | """) | ||
248 | 1603 | |||
249 | 1604 | def test_short_all(self): | ||
250 | 1605 | self.assertFormatterResult(log.ShortLogFormatter, 'all', """\ | ||
251 | 1606 | 1 John Doe, Jane Rey\t2005-11-22 | ||
252 | 1607 | add a | ||
253 | 1608 | |||
254 | 1609 | """) | ||
255 | 1610 | |||
256 | 1611 | def test_long_default(self): | ||
257 | 1612 | self.assertFormatterResult(log.LongLogFormatter, None, """\ | ||
258 | 1613 | ------------------------------------------------------------ | ||
259 | 1614 | revno: 1 | ||
260 | 1615 | author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com> | ||
261 | 1616 | committer: Lorem Ipsum <test@example.com> | ||
262 | 1617 | branch nick: nicky | ||
263 | 1618 | timestamp: Tue 2005-11-22 00:00:00 +0000 | ||
264 | 1619 | message: | ||
265 | 1620 | add a | ||
266 | 1621 | """) | ||
267 | 1622 | |||
268 | 1623 | def test_long_committer(self): | ||
269 | 1624 | self.assertFormatterResult(log.LongLogFormatter, 'committer', """\ | ||
270 | 1625 | ------------------------------------------------------------ | ||
271 | 1626 | revno: 1 | ||
272 | 1627 | committer: Lorem Ipsum <test@example.com> | ||
273 | 1628 | branch nick: nicky | ||
274 | 1629 | timestamp: Tue 2005-11-22 00:00:00 +0000 | ||
275 | 1630 | message: | ||
276 | 1631 | add a | ||
277 | 1632 | """) | ||
278 | 1633 | |||
279 | 1634 | def test_long_first(self): | ||
280 | 1635 | self.assertFormatterResult(log.LongLogFormatter, 'first', """\ | ||
281 | 1636 | ------------------------------------------------------------ | ||
282 | 1637 | revno: 1 | ||
283 | 1638 | author: John Doe <jdoe@example.com> | ||
284 | 1639 | committer: Lorem Ipsum <test@example.com> | ||
285 | 1640 | branch nick: nicky | ||
286 | 1641 | timestamp: Tue 2005-11-22 00:00:00 +0000 | ||
287 | 1642 | message: | ||
288 | 1643 | add a | ||
289 | 1644 | """) | ||
290 | 1645 | |||
291 | 1646 | def test_long_all(self): | ||
292 | 1647 | self.assertFormatterResult(log.LongLogFormatter, 'all', """\ | ||
293 | 1648 | ------------------------------------------------------------ | ||
294 | 1649 | revno: 1 | ||
295 | 1650 | author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com> | ||
296 | 1651 | committer: Lorem Ipsum <test@example.com> | ||
297 | 1652 | branch nick: nicky | ||
298 | 1653 | timestamp: Tue 2005-11-22 00:00:00 +0000 | ||
299 | 1654 | message: | ||
300 | 1655 | add a | ||
301 | 1656 | """) | ||
302 | 1657 | |||
303 | 1658 | def test_gnu_changelog_default(self): | ||
304 | 1659 | self.assertFormatterResult(log.GnuChangelogLogFormatter, None, """\ | ||
305 | 1660 | 2005-11-22 John Doe <jdoe@example.com> | ||
306 | 1661 | |||
307 | 1662 | \tadd a | ||
308 | 1663 | |||
309 | 1664 | """) | ||
310 | 1665 | |||
311 | 1666 | def test_gnu_changelog_committer(self): | ||
312 | 1667 | self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', """\ | ||
313 | 1668 | 2005-11-22 Lorem Ipsum <test@example.com> | ||
314 | 1669 | |||
315 | 1670 | \tadd a | ||
316 | 1671 | |||
317 | 1672 | """) | ||
318 | 1673 | |||
319 | 1674 | def test_gnu_changelog_first(self): | ||
320 | 1675 | self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', """\ | ||
321 | 1676 | 2005-11-22 John Doe <jdoe@example.com> | ||
322 | 1677 | |||
323 | 1678 | \tadd a | ||
324 | 1679 | |||
325 | 1680 | """) | ||
326 | 1681 | |||
327 | 1682 | def test_gnu_changelog_all(self): | ||
328 | 1683 | self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', """\ | ||
329 | 1684 | 2005-11-22 John Doe <jdoe@example.com>, Jane Rey <jrey@example.com> | ||
330 | 1685 | |||
331 | 1686 | \tadd a | ||
332 | 1687 | |||
333 | 1688 | """) |
The manual parsing and the structure of the if block says to me that this would be better handled by a registry of author handlers.