Merge lp:~jelmer/bzr-keywords/lazy into lp:bzr-keywords
- lazy
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | John A Meinel |
Approved revision: | no longer in the source branch. |
Merged at revision: | 18 |
Proposed branch: | lp:~jelmer/bzr-keywords/lazy |
Merge into: | lp:bzr-keywords |
Diff against target: |
652 lines (+294/-266) 4 files modified
__init__.py (+23/-258) keywords.py (+264/-0) tests/test_conversion.py (+4/-1) tests/test_keywords_in_trees.py (+3/-7) |
To merge this branch: | bzr merge lp:~jelmer/bzr-keywords/lazy |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John A Meinel | Needs Information | ||
Martin Pool (community) | Approve | ||
Review via email: mp+51444@code.launchpad.net |
Commit message
Description of the change
Lazily load the keywords plugin.
Martin Pool (mbp) : | # |
Jelmer Vernooij (jelmer) wrote : | # |
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> On 2/27/2011 4:57 AM, Jelmer Vernooij wrote:
> > Jelmer Vernooij has proposed merging lp:~jelmer/bzr-keywords/lazy into lp
> :bzr-keywords.
> >
> > Requested reviews:
> > Bazaar Developers (bzr)
> >
> > For more details, see:
> > https:/
> >
> > Lazily load the keywords plugin.
>
> I'm a bit surprised at how much code is added here, versus how much is
> removed.
> All the format_date, extract_name, etc don't seem to come from somewhere
> else.
>
> Is this just a large rewrite of the internals?
>
> The changes seem fine to me, but I didn't go over them in detail, with
> the change being surprisingly large.
>
> Care to explain a bit more what you changed?
There was a conflict with some earlier changes, which caused the code I moved around to stay in __init__.py in the conflicts. Should be fixed now.
- 18. By Jelmer Vernooij
-
Merge lazy loading support.
Preview Diff
1 | === modified file '__init__.py' |
2 | --- __init__.py 2010-10-01 19:44:29 +0000 |
3 | +++ __init__.py 2011-03-02 12:34:10 +0000 |
4 | @@ -105,18 +105,11 @@ |
5 | ''' |
6 | |
7 | |
8 | -import re, time |
9 | from bzrlib import ( |
10 | builtins, |
11 | commands, |
12 | - config, |
13 | - debug, |
14 | filters, |
15 | option, |
16 | - osutils, |
17 | - registry, |
18 | - trace, |
19 | - xml8, |
20 | ) |
21 | |
22 | |
23 | @@ -137,249 +130,13 @@ |
24 | return suite |
25 | |
26 | |
27 | -# Expansion styles |
28 | -# Note: Round-tripping is only required between the raw and cooked styles |
29 | -_keyword_style_registry = registry.Registry() |
30 | -_keyword_style_registry.register('raw', '$%(name)s$') |
31 | -_keyword_style_registry.register('cooked', '$%(name)s: %(value)s $') |
32 | -_keyword_style_registry.register('publish', '%(name)s: %(value)s') |
33 | -_keyword_style_registry.register('publish-values', '%(value)s') |
34 | -_keyword_style_registry.register('publish-names', '%(name)s') |
35 | -_keyword_style_registry.default_key = 'cooked' |
36 | - |
37 | - |
38 | -# Regular expressions for matching the raw and cooked patterns |
39 | -_KW_RAW_RE = re.compile(r'\$([\w\-]+)(:[^$]*)?\$') |
40 | -_KW_COOKED_RE = re.compile(r'\$([\w\-]+):([^$]+)\$') |
41 | - |
42 | - |
43 | -# The registry of keywords. Other plugins may wish to add entries to this. |
44 | -keyword_registry = registry.Registry() |
45 | - |
46 | -# Revision-related keywords |
47 | -keyword_registry.register('Date', |
48 | - lambda c: format_date(c.revision().timestamp, c.revision().timezone, |
49 | - c.config(), 'Date')) |
50 | -keyword_registry.register('Committer', |
51 | - lambda c: c.revision().committer) |
52 | -keyword_registry.register('Authors', |
53 | - lambda c: ", ".join(c.revision().get_apparent_authors())) |
54 | -keyword_registry.register('Revision-Id', |
55 | - lambda c: c.revision_id()) |
56 | -keyword_registry.register('Path', |
57 | - lambda c: c.relpath()) |
58 | -keyword_registry.register('Directory', |
59 | - lambda c: osutils.split(c.relpath())[0]) |
60 | -keyword_registry.register('Filename', |
61 | - lambda c: osutils.split(c.relpath())[1]) |
62 | -keyword_registry.register('File-Id', |
63 | - lambda c: c.file_id()) |
64 | - |
65 | -# Environment-related keywords |
66 | -keyword_registry.register('Now', |
67 | - lambda c: format_date(time.time(), time.timezone, c.config(), 'Now')) |
68 | -keyword_registry.register('User', |
69 | - lambda c: c.config().username()) |
70 | - |
71 | -# Keywords for finer control over name & address formatting |
72 | -keyword_registry.register('Committer-Name', |
73 | - lambda c: extract_name(c.revision().committer)) |
74 | -keyword_registry.register('Committer-Email', |
75 | - lambda c: extract_email(c.revision().committer)) |
76 | -keyword_registry.register('Author1-Name', |
77 | - lambda c: extract_name_item(c.revision().get_apparent_authors(), 0)) |
78 | -keyword_registry.register('Author1-Email', |
79 | - lambda c: extract_email_item(c.revision().get_apparent_authors(), 0)) |
80 | -keyword_registry.register('Author2-Name', |
81 | - lambda c: extract_name_item(c.revision().get_apparent_authors(), 1)) |
82 | -keyword_registry.register('Author2-Email', |
83 | - lambda c: extract_email_item(c.revision().get_apparent_authors(), 1)) |
84 | -keyword_registry.register('Author3-Name', |
85 | - lambda c: extract_name_item(c.revision().get_apparent_authors(), 2)) |
86 | -keyword_registry.register('Author3-Email', |
87 | - lambda c: extract_email_item(c.revision().get_apparent_authors(), 2)) |
88 | -keyword_registry.register('User-Name', |
89 | - lambda c: extract_name(c.config().username())) |
90 | -keyword_registry.register('User-Email', |
91 | - lambda c: extract_email(c.config().username())) |
92 | - |
93 | - |
94 | -def format_date(timestamp, offset=0, cfg=None, name=None): |
95 | - """Return a formatted date string. |
96 | - |
97 | - :param timestamp: Seconds since the epoch. |
98 | - :param offset: Timezone offset in seconds east of utc. |
99 | - """ |
100 | - if cfg is not None and name is not None: |
101 | - cfg_key = 'keywords.format.%s' % (name,) |
102 | - format = cfg.get_user_option(cfg_key) |
103 | - else: |
104 | - format = None |
105 | - return osutils.format_date(timestamp, offset, date_fmt=format) |
106 | - |
107 | - |
108 | -def extract_name(userid): |
109 | - """Extract the name out of a user-id string. |
110 | - |
111 | - user-id strings have the format 'name <email>'. |
112 | - """ |
113 | - if userid and userid[-1] == '>': |
114 | - return userid[:-1].rsplit('<', 1)[0].rstrip() |
115 | - else: |
116 | - return userid |
117 | - |
118 | - |
119 | -def extract_email(userid): |
120 | - """Extract the email address out of a user-id string. |
121 | - |
122 | - user-id strings have the format 'name <email>'. |
123 | - """ |
124 | - if userid and userid[-1] == '>': |
125 | - return userid[:-1].rsplit('<', 1)[1] |
126 | - else: |
127 | - return userid |
128 | - |
129 | -def extract_name_item(seq, n): |
130 | - """Extract the name out of the nth item in a sequence of user-ids. |
131 | - |
132 | - :return: the user-name or an empty string |
133 | - """ |
134 | - try: |
135 | - return extract_name(seq[n]) |
136 | - except IndexError: |
137 | - return "" |
138 | - |
139 | - |
140 | -def extract_email_item(seq, n): |
141 | - """Extract the email out of the nth item in a sequence of user-ids. |
142 | - |
143 | - :return: the email address or an empty string |
144 | - """ |
145 | - try: |
146 | - return extract_email(seq[n]) |
147 | - except IndexError: |
148 | - return "" |
149 | - |
150 | - |
151 | -def compress_keywords(s, keyword_dicts): |
152 | - """Replace cooked style keywords with raw style in a string. |
153 | - |
154 | - Note: If the keyword is not known, the text is not modified. |
155 | - |
156 | - :param s: the string |
157 | - :param keyword_dicts: an iterable of keyword dictionaries. |
158 | - :return: the string with keywords compressed |
159 | - """ |
160 | - _raw_style = _keyword_style_registry.get('raw') |
161 | - result = '' |
162 | - rest = s |
163 | - while (True): |
164 | - match = _KW_COOKED_RE.search(rest) |
165 | - if not match: |
166 | - break |
167 | - result += rest[:match.start()] |
168 | - keyword = match.group(1) |
169 | - expansion = _get_from_dicts(keyword_dicts, keyword) |
170 | - if expansion is None: |
171 | - # Unknown expansion - leave as is |
172 | - result += match.group(0) |
173 | - else: |
174 | - result += _raw_style % {'name': keyword} |
175 | - rest = rest[match.end():] |
176 | - return result + rest |
177 | - |
178 | - |
179 | -def expand_keywords(s, keyword_dicts, context=None, encoder=None, style=None): |
180 | - """Replace raw style keywords with another style in a string. |
181 | - |
182 | - Note: If the keyword is already in the expanded style, the value is |
183 | - not replaced. |
184 | - |
185 | - :param s: the string |
186 | - :param keyword_dicts: an iterable of keyword dictionaries. If values |
187 | - are callables, they are executed to find the real value. |
188 | - :param context: the parameter to pass to callable values |
189 | - :param style: the style of expansion to use of None for the default |
190 | - :return: the string with keywords expanded |
191 | - """ |
192 | - _expanded_style = _keyword_style_registry.get(style) |
193 | - result = '' |
194 | - rest = s |
195 | - while (True): |
196 | - match = _KW_RAW_RE.search(rest) |
197 | - if not match: |
198 | - break |
199 | - result += rest[:match.start()] |
200 | - keyword = match.group(1) |
201 | - expansion = _get_from_dicts(keyword_dicts, keyword) |
202 | - if callable(expansion): |
203 | - try: |
204 | - expansion = expansion(context) |
205 | - except AttributeError, err: |
206 | - if 'error' in debug.debug_flags: |
207 | - trace.note("error evaluating %s for keyword %s: %s", |
208 | - expansion, keyword, err) |
209 | - expansion = "(evaluation error)" |
210 | - if expansion is None: |
211 | - # Unknown expansion - leave as is |
212 | - result += match.group(0) |
213 | - rest = rest[match.end():] |
214 | - continue |
215 | - if '$' in expansion: |
216 | - # Expansion is not safe to be collapsed later |
217 | - expansion = "(value unsafe to expand)" |
218 | - if encoder is not None: |
219 | - expansion = encoder(expansion) |
220 | - params = {'name': keyword, 'value': expansion} |
221 | - result += _expanded_style % params |
222 | - rest = rest[match.end():] |
223 | - return result + rest |
224 | - |
225 | - |
226 | -def _get_from_dicts(dicts, key, default=None): |
227 | - """Search a sequence of dictionaries or registries for a key. |
228 | - |
229 | - :return: the value, or default if not found |
230 | - """ |
231 | - for dict in dicts: |
232 | - if key in dict: |
233 | - return dict.get(key) |
234 | - return default |
235 | - |
236 | - |
237 | -def _xml_escape(s): |
238 | - """Escape a string so it can be included safely in XML/HTML.""" |
239 | - # Complie the regular expressions if not already done |
240 | - xml8._ensure_utf8_re() |
241 | - # Convert and strip the trailing quote |
242 | - return xml8._encode_and_escape(s)[:-1] |
243 | - |
244 | - |
245 | -def _kw_compressor(chunks, context=None): |
246 | - """Filter that replaces keywords with their compressed form.""" |
247 | - text = ''.join(chunks) |
248 | - return [compress_keywords(text, [keyword_registry])] |
249 | - |
250 | - |
251 | -def _kw_expander(chunks, context, encoder=None): |
252 | - """Keyword expander.""" |
253 | - text = ''.join(chunks) |
254 | - return [expand_keywords(text, [keyword_registry], context=context, |
255 | - encoder=encoder)] |
256 | - |
257 | - |
258 | -def _normal_kw_expander(chunks, context=None): |
259 | - """Filter that replaces keywords with their expanded form.""" |
260 | - return _kw_expander(chunks, context) |
261 | - |
262 | - |
263 | -def _xml_escape_kw_expander(chunks, context=None): |
264 | - """Filter that replaces keywords with a form suitable for use in XML.""" |
265 | - return _kw_expander(chunks, context, encoder=_xml_escape) |
266 | - |
267 | - |
268 | # Define and register the filter stack map |
269 | def _keywords_filter_stack_lookup(k): |
270 | + from bzrlib.plugins.keywords.keywords import ( |
271 | + _kw_compressor, |
272 | + _normal_kw_expander, |
273 | + _xml_escape_kw_expander, |
274 | + ) |
275 | filter_stack_map = { |
276 | 'off': [], |
277 | 'on': |
278 | @@ -416,21 +173,25 @@ |
279 | # override the inherited run() and help() methods |
280 | |
281 | takes_options = builtins.cmd_cat.takes_options + [ |
282 | - option.RegistryOption('keywords', |
283 | - registry=_keyword_style_registry, |
284 | - converter=lambda s: s, |
285 | - help='Keyword expansion style.')] |
286 | - |
287 | + option.RegistryOption('keywords', |
288 | + lazy_registry=("bzrlib.plugins.keywords.keywords", |
289 | + "_keyword_style_registry"), |
290 | + converter=lambda s: s, |
291 | + help='Keyword expansion style.')] |
292 | + |
293 | def run(self, *args, **kwargs): |
294 | """Process special options and delegate to superclass.""" |
295 | if 'keywords' in kwargs: |
296 | + from bzrlib.plugins.keywords.keywords import ( |
297 | + _keyword_style_registry, |
298 | + ) |
299 | # Implicitly set the filters option |
300 | kwargs['filters'] = True |
301 | style = kwargs['keywords'] |
302 | _keyword_style_registry.default_key = style |
303 | del kwargs['keywords'] |
304 | return super(cmd_cat, self).run(*args, **kwargs) |
305 | - |
306 | + |
307 | def help(self): |
308 | """Return help message including text from superclass.""" |
309 | from inspect import getdoc |
310 | @@ -442,21 +203,25 @@ |
311 | # override the inherited run() and help() methods |
312 | |
313 | takes_options = builtins.cmd_export.takes_options + [ |
314 | - option.RegistryOption('keywords', |
315 | - registry=_keyword_style_registry, |
316 | + option.RegistryOption('keywords', |
317 | + lazy_registry=("bzrlib.plugins.keywords.keywords", |
318 | + "_keyword_style_registry"), |
319 | converter=lambda s: s, |
320 | help='Keyword expansion style.')] |
321 | - |
322 | + |
323 | def run(self, *args, **kwargs): |
324 | """Process special options and delegate to superclass.""" |
325 | if 'keywords' in kwargs: |
326 | + from bzrlib.plugins.keywords.keywords import ( |
327 | + _keyword_style_registry, |
328 | + ) |
329 | # Implicitly set the filters option |
330 | kwargs['filters'] = True |
331 | style = kwargs['keywords'] |
332 | _keyword_style_registry.default_key = style |
333 | del kwargs['keywords'] |
334 | return super(cmd_export, self).run(*args, **kwargs) |
335 | - |
336 | + |
337 | def help(self): |
338 | """Return help message including text from superclass.""" |
339 | from inspect import getdoc |
340 | |
341 | === added file 'keywords.py' |
342 | --- keywords.py 1970-01-01 00:00:00 +0000 |
343 | +++ keywords.py 2011-03-02 12:34:10 +0000 |
344 | @@ -0,0 +1,264 @@ |
345 | +# Copyright (C) 2008 Canonical Ltd |
346 | +# |
347 | +# This program is free software; you can redistribute it and/or modify |
348 | +# it under the terms of the GNU General Public License as published by |
349 | +# the Free Software Foundation; either version 2 of the License, or |
350 | +# (at your option) any later version. |
351 | +# |
352 | +# This program is distributed in the hope that it will be useful, |
353 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
354 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
355 | +# GNU General Public License for more details. |
356 | +# |
357 | +# You should have received a copy of the GNU General Public License |
358 | +# along with this program; if not, write to the Free Software |
359 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
360 | + |
361 | +import re, time |
362 | +from bzrlib import ( |
363 | + debug, |
364 | + osutils, |
365 | + registry, |
366 | + trace, |
367 | + ) |
368 | + |
369 | +# Expansion styles |
370 | +# Note: Round-tripping is only required between the raw and cooked styles |
371 | +_keyword_style_registry = registry.Registry() |
372 | +_keyword_style_registry.register('raw', '$%(name)s$') |
373 | +_keyword_style_registry.register('cooked', '$%(name)s: %(value)s $') |
374 | +_keyword_style_registry.register('publish', '%(name)s: %(value)s') |
375 | +_keyword_style_registry.register('publish-values', '%(value)s') |
376 | +_keyword_style_registry.register('publish-names', '%(name)s') |
377 | +_keyword_style_registry.default_key = 'cooked' |
378 | + |
379 | + |
380 | +# Regular expressions for matching the raw and cooked patterns |
381 | +_KW_RAW_RE = re.compile(r'\$([\w\-]+)(:[^$]*)?\$') |
382 | +_KW_COOKED_RE = re.compile(r'\$([\w\-]+):([^$]+)\$') |
383 | + |
384 | + |
385 | +# The registry of keywords. Other plugins may wish to add entries to this. |
386 | +keyword_registry = registry.Registry() |
387 | + |
388 | +# Revision-related keywords |
389 | +keyword_registry.register('Date', |
390 | + lambda c: format_date(c.revision().timestamp, c.revision().timezone, |
391 | + c.config(), 'Date')) |
392 | +keyword_registry.register('Committer', |
393 | + lambda c: c.revision().committer) |
394 | +keyword_registry.register('Authors', |
395 | + lambda c: ", ".join(c.revision().get_apparent_authors())) |
396 | +keyword_registry.register('Revision-Id', |
397 | + lambda c: c.revision_id()) |
398 | +keyword_registry.register('Path', |
399 | + lambda c: c.relpath()) |
400 | +keyword_registry.register('Directory', |
401 | + lambda c: osutils.split(c.relpath())[0]) |
402 | +keyword_registry.register('Filename', |
403 | + lambda c: osutils.split(c.relpath())[1]) |
404 | +keyword_registry.register('File-Id', |
405 | + lambda c: c.file_id()) |
406 | + |
407 | +# Environment-related keywords |
408 | +keyword_registry.register('Now', |
409 | + lambda c: format_date(time.time(), time.timezone, c.config(), 'Now')) |
410 | +keyword_registry.register('User', |
411 | + lambda c: c.config().username()) |
412 | + |
413 | +# Keywords for finer control over name & address formatting |
414 | +keyword_registry.register('Committer-Name', |
415 | + lambda c: extract_name(c.revision().committer)) |
416 | +keyword_registry.register('Committer-Email', |
417 | + lambda c: extract_email(c.revision().committer)) |
418 | +keyword_registry.register('Author1-Name', |
419 | + lambda c: extract_name_item(c.revision().get_apparent_authors(), 0)) |
420 | +keyword_registry.register('Author1-Email', |
421 | + lambda c: extract_email_item(c.revision().get_apparent_authors(), 0)) |
422 | +keyword_registry.register('Author2-Name', |
423 | + lambda c: extract_name_item(c.revision().get_apparent_authors(), 1)) |
424 | +keyword_registry.register('Author2-Email', |
425 | + lambda c: extract_email_item(c.revision().get_apparent_authors(), 1)) |
426 | +keyword_registry.register('Author3-Name', |
427 | + lambda c: extract_name_item(c.revision().get_apparent_authors(), 2)) |
428 | +keyword_registry.register('Author3-Email', |
429 | + lambda c: extract_email_item(c.revision().get_apparent_authors(), 2)) |
430 | +keyword_registry.register('User-Name', |
431 | + lambda c: extract_name(c.config().username())) |
432 | +keyword_registry.register('User-Email', |
433 | + lambda c: extract_email(c.config().username())) |
434 | + |
435 | + |
436 | +def format_date(timestamp, offset=0, cfg=None, name=None): |
437 | + """Return a formatted date string. |
438 | + |
439 | + :param timestamp: Seconds since the epoch. |
440 | + :param offset: Timezone offset in seconds east of utc. |
441 | + """ |
442 | + if cfg is not None and name is not None: |
443 | + cfg_key = 'keywords.format.%s' % (name,) |
444 | + format = cfg.get_user_option(cfg_key) |
445 | + else: |
446 | + format = None |
447 | + return osutils.format_date(timestamp, offset, date_fmt=format) |
448 | + |
449 | + |
450 | +def extract_name(userid): |
451 | + """Extract the name out of a user-id string. |
452 | + |
453 | + user-id strings have the format 'name <email>'. |
454 | + """ |
455 | + if userid and userid[-1] == '>': |
456 | + return userid[:-1].rsplit('<', 1)[0].rstrip() |
457 | + else: |
458 | + return userid |
459 | + |
460 | + |
461 | +def extract_email(userid): |
462 | + """Extract the email address out of a user-id string. |
463 | + |
464 | + user-id strings have the format 'name <email>'. |
465 | + """ |
466 | + if userid and userid[-1] == '>': |
467 | + return userid[:-1].rsplit('<', 1)[1] |
468 | + else: |
469 | + return userid |
470 | + |
471 | +def extract_name_item(seq, n): |
472 | + """Extract the name out of the nth item in a sequence of user-ids. |
473 | + |
474 | + :return: the user-name or an empty string |
475 | + """ |
476 | + try: |
477 | + return extract_name(seq[n]) |
478 | + except IndexError: |
479 | + return "" |
480 | + |
481 | + |
482 | +def extract_email_item(seq, n): |
483 | + """Extract the email out of the nth item in a sequence of user-ids. |
484 | + |
485 | + :return: the email address or an empty string |
486 | + """ |
487 | + try: |
488 | + return extract_email(seq[n]) |
489 | + except IndexError: |
490 | + return "" |
491 | + |
492 | + |
493 | +def compress_keywords(s, keyword_dicts): |
494 | + """Replace cooked style keywords with raw style in a string. |
495 | + |
496 | + Note: If the keyword is not known, the text is not modified. |
497 | + |
498 | + :param s: the string |
499 | + :param keyword_dicts: an iterable of keyword dictionaries. |
500 | + :return: the string with keywords compressed |
501 | + """ |
502 | + _raw_style = _keyword_style_registry.get('raw') |
503 | + result = '' |
504 | + rest = s |
505 | + while (True): |
506 | + match = _KW_COOKED_RE.search(rest) |
507 | + if not match: |
508 | + break |
509 | + result += rest[:match.start()] |
510 | + keyword = match.group(1) |
511 | + expansion = _get_from_dicts(keyword_dicts, keyword) |
512 | + if expansion is None: |
513 | + # Unknown expansion - leave as is |
514 | + result += match.group(0) |
515 | + else: |
516 | + result += _raw_style % {'name': keyword} |
517 | + rest = rest[match.end():] |
518 | + return result + rest |
519 | + |
520 | + |
521 | +def expand_keywords(s, keyword_dicts, context=None, encoder=None, style=None): |
522 | + """Replace raw style keywords with another style in a string. |
523 | + |
524 | + Note: If the keyword is already in the expanded style, the value is |
525 | + not replaced. |
526 | + |
527 | + :param s: the string |
528 | + :param keyword_dicts: an iterable of keyword dictionaries. If values |
529 | + are callables, they are executed to find the real value. |
530 | + :param context: the parameter to pass to callable values |
531 | + :param style: the style of expansion to use of None for the default |
532 | + :return: the string with keywords expanded |
533 | + """ |
534 | + _expanded_style = _keyword_style_registry.get(style) |
535 | + result = '' |
536 | + rest = s |
537 | + while (True): |
538 | + match = _KW_RAW_RE.search(rest) |
539 | + if not match: |
540 | + break |
541 | + result += rest[:match.start()] |
542 | + keyword = match.group(1) |
543 | + expansion = _get_from_dicts(keyword_dicts, keyword) |
544 | + if callable(expansion): |
545 | + try: |
546 | + expansion = expansion(context) |
547 | + except AttributeError, err: |
548 | + if 'error' in debug.debug_flags: |
549 | + trace.note("error evaluating %s for keyword %s: %s", |
550 | + expansion, keyword, err) |
551 | + expansion = "(evaluation error)" |
552 | + if expansion is None: |
553 | + # Unknown expansion - leave as is |
554 | + result += match.group(0) |
555 | + rest = rest[match.end():] |
556 | + continue |
557 | + if '$' in expansion: |
558 | + # Expansion is not safe to be collapsed later |
559 | + expansion = "(value unsafe to expand)" |
560 | + if encoder is not None: |
561 | + expansion = encoder(expansion) |
562 | + params = {'name': keyword, 'value': expansion} |
563 | + result += _expanded_style % params |
564 | + rest = rest[match.end():] |
565 | + return result + rest |
566 | + |
567 | + |
568 | +def _get_from_dicts(dicts, key, default=None): |
569 | + """Search a sequence of dictionaries or registries for a key. |
570 | + |
571 | + :return: the value, or default if not found |
572 | + """ |
573 | + for dict in dicts: |
574 | + if key in dict: |
575 | + return dict.get(key) |
576 | + return default |
577 | + |
578 | + |
579 | +def _xml_escape(s): |
580 | + """Escape a string so it can be included safely in XML/HTML.""" |
581 | + # Compile the regular expressions if not already done |
582 | + from bzrlib import xml8 |
583 | + xml8._ensure_utf8_re() |
584 | + # Convert and strip the trailing quote |
585 | + return xml8._encode_and_escape(s)[:-1] |
586 | + |
587 | + |
588 | +def _kw_compressor(chunks, context=None): |
589 | + """Filter that replaces keywords with their compressed form.""" |
590 | + text = ''.join(chunks) |
591 | + return [compress_keywords(text, [keyword_registry])] |
592 | + |
593 | + |
594 | +def _kw_expander(chunks, context, encoder=None): |
595 | + """Keyword expander.""" |
596 | + text = ''.join(chunks) |
597 | + return [expand_keywords(text, [keyword_registry], context=context, |
598 | + encoder=encoder)] |
599 | + |
600 | + |
601 | +def _normal_kw_expander(chunks, context=None): |
602 | + """Filter that replaces keywords with their expanded form.""" |
603 | + return _kw_expander(chunks, context) |
604 | + |
605 | + |
606 | +def _xml_escape_kw_expander(chunks, context=None): |
607 | + """Filter that replaces keywords with a form suitable for use in XML.""" |
608 | + return _kw_expander(chunks, context, encoder=_xml_escape) |
609 | |
610 | === modified file 'tests/test_conversion.py' |
611 | --- tests/test_conversion.py 2010-10-01 17:31:51 +0000 |
612 | +++ tests/test_conversion.py 2011-03-02 12:34:10 +0000 |
613 | @@ -18,7 +18,10 @@ |
614 | |
615 | |
616 | from bzrlib import tests |
617 | -from bzrlib.plugins.keywords import compress_keywords, expand_keywords |
618 | +from bzrlib.plugins.keywords.keywords import ( |
619 | + compress_keywords, |
620 | + expand_keywords, |
621 | + ) |
622 | |
623 | |
624 | # Sample unexpanded and expanded pairs for a keyword dictionary |
625 | |
626 | === modified file 'tests/test_keywords_in_trees.py' |
627 | --- tests/test_keywords_in_trees.py 2009-08-11 05:14:31 +0000 |
628 | +++ tests/test_keywords_in_trees.py 2011-03-02 12:34:10 +0000 |
629 | @@ -18,12 +18,8 @@ |
630 | |
631 | ## TODO: add tests for xml_escaped |
632 | |
633 | -from cStringIO import StringIO |
634 | -import sys |
635 | - |
636 | -from bzrlib import config, rules |
637 | -from bzrlib.tests import TestCaseWithTransport, TestSkipped |
638 | -from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree |
639 | +from bzrlib import rules |
640 | +from bzrlib.tests import TestCaseWithTransport |
641 | from bzrlib.workingtree import WorkingTree |
642 | |
643 | |
644 | @@ -90,7 +86,7 @@ |
645 | t.add('file1', 'file1-id') |
646 | t.commit("add file1", rev_id="rev1-id", |
647 | committer="Jane Smith <jane@example.com>", |
648 | - author="Sue Smith <sue@example.com>") |
649 | + authors=["Sue Smith <sue@example.com>"]) |
650 | basis = t.basis_tree() |
651 | basis.lock_read() |
652 | self.addCleanup(basis.unlock) |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
On 2/27/2011 4:57 AM, Jelmer Vernooij wrote: /code.launchpad .net/~jelmer/ bzr-keywords/ lazy/+merge/ 51444
> Jelmer Vernooij has proposed merging lp:~jelmer/bzr-keywords/lazy into lp:bzr-keywords.
>
> Requested reviews:
> Bazaar Developers (bzr)
>
> For more details, see:
> https:/
>
> Lazily load the keywords plugin.
I'm a bit surprised at how much code is added here, versus how much is
removed.
All the format_date, extract_name, etc don't seem to come from somewhere
else.
Is this just a large rewrite of the internals?
The changes seem fine to me, but I didn't go over them in detail, with
the change being surprisingly large.
Care to explain a bit more what you changed?
review: needsinfo
John
=:->
-----BEGIN PGP SIGNATURE----- enigmail. mozdev. org/
uFfAACgkQJdeBCY SNAAPKpQCcD0Epu FviTVfxSMY5ikxI wPrW gGztSHhpYnyzoNk 5l
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAk1
KV8AoIGp7aUs4DD
=3j0u
-----END PGP SIGNATURE-----