Merge lp:~jelmer/bzr-keywords/lazy into lp:bzr-keywords

Proposed by Jelmer Vernooij
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
Reviewer Review Type Date Requested Status
John A Meinel Needs Information
Martin Pool (community) Approve
Review via email: mp+51444@code.launchpad.net

Description of the change

Lazily load the keywords plugin.

To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) :
review: Approve
Revision history for this message
John A Meinel (jameinel) 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://code.launchpad.net/~jelmer/bzr-keywords/lazy/+merge/51444
>
> 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-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk1uFfAACgkQJdeBCYSNAAPKpQCcD0EpuFviTVfxSMY5ikxIwPrW
KV8AoIGp7aUs4DDgGztSHhpYnyzoNk5l
=3j0u
-----END PGP SIGNATURE-----

review: Needs Information
Revision history for this message
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://code.launchpad.net/~jelmer/bzr-keywords/lazy/+merge/51444
> >
> > 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.

lp:~jelmer/bzr-keywords/lazy updated
18. By Jelmer Vernooij

Merge lazy loading support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '__init__.py'
--- __init__.py 2010-10-01 19:44:29 +0000
+++ __init__.py 2011-03-02 12:34:10 +0000
@@ -105,18 +105,11 @@
105'''105'''
106106
107107
108import re, time
109from bzrlib import (108from bzrlib import (
110 builtins,109 builtins,
111 commands,110 commands,
112 config,
113 debug,
114 filters,111 filters,
115 option,112 option,
116 osutils,
117 registry,
118 trace,
119 xml8,
120 )113 )
121114
122115
@@ -137,249 +130,13 @@
137 return suite130 return suite
138131
139132
140# Expansion styles
141# Note: Round-tripping is only required between the raw and cooked styles
142_keyword_style_registry = registry.Registry()
143_keyword_style_registry.register('raw', '$%(name)s$')
144_keyword_style_registry.register('cooked', '$%(name)s: %(value)s $')
145_keyword_style_registry.register('publish', '%(name)s: %(value)s')
146_keyword_style_registry.register('publish-values', '%(value)s')
147_keyword_style_registry.register('publish-names', '%(name)s')
148_keyword_style_registry.default_key = 'cooked'
149
150
151# Regular expressions for matching the raw and cooked patterns
152_KW_RAW_RE = re.compile(r'\$([\w\-]+)(:[^$]*)?\$')
153_KW_COOKED_RE = re.compile(r'\$([\w\-]+):([^$]+)\$')
154
155
156# The registry of keywords. Other plugins may wish to add entries to this.
157keyword_registry = registry.Registry()
158
159# Revision-related keywords
160keyword_registry.register('Date',
161 lambda c: format_date(c.revision().timestamp, c.revision().timezone,
162 c.config(), 'Date'))
163keyword_registry.register('Committer',
164 lambda c: c.revision().committer)
165keyword_registry.register('Authors',
166 lambda c: ", ".join(c.revision().get_apparent_authors()))
167keyword_registry.register('Revision-Id',
168 lambda c: c.revision_id())
169keyword_registry.register('Path',
170 lambda c: c.relpath())
171keyword_registry.register('Directory',
172 lambda c: osutils.split(c.relpath())[0])
173keyword_registry.register('Filename',
174 lambda c: osutils.split(c.relpath())[1])
175keyword_registry.register('File-Id',
176 lambda c: c.file_id())
177
178# Environment-related keywords
179keyword_registry.register('Now',
180 lambda c: format_date(time.time(), time.timezone, c.config(), 'Now'))
181keyword_registry.register('User',
182 lambda c: c.config().username())
183
184# Keywords for finer control over name & address formatting
185keyword_registry.register('Committer-Name',
186 lambda c: extract_name(c.revision().committer))
187keyword_registry.register('Committer-Email',
188 lambda c: extract_email(c.revision().committer))
189keyword_registry.register('Author1-Name',
190 lambda c: extract_name_item(c.revision().get_apparent_authors(), 0))
191keyword_registry.register('Author1-Email',
192 lambda c: extract_email_item(c.revision().get_apparent_authors(), 0))
193keyword_registry.register('Author2-Name',
194 lambda c: extract_name_item(c.revision().get_apparent_authors(), 1))
195keyword_registry.register('Author2-Email',
196 lambda c: extract_email_item(c.revision().get_apparent_authors(), 1))
197keyword_registry.register('Author3-Name',
198 lambda c: extract_name_item(c.revision().get_apparent_authors(), 2))
199keyword_registry.register('Author3-Email',
200 lambda c: extract_email_item(c.revision().get_apparent_authors(), 2))
201keyword_registry.register('User-Name',
202 lambda c: extract_name(c.config().username()))
203keyword_registry.register('User-Email',
204 lambda c: extract_email(c.config().username()))
205
206
207def format_date(timestamp, offset=0, cfg=None, name=None):
208 """Return a formatted date string.
209
210 :param timestamp: Seconds since the epoch.
211 :param offset: Timezone offset in seconds east of utc.
212 """
213 if cfg is not None and name is not None:
214 cfg_key = 'keywords.format.%s' % (name,)
215 format = cfg.get_user_option(cfg_key)
216 else:
217 format = None
218 return osutils.format_date(timestamp, offset, date_fmt=format)
219
220
221def extract_name(userid):
222 """Extract the name out of a user-id string.
223
224 user-id strings have the format 'name <email>'.
225 """
226 if userid and userid[-1] == '>':
227 return userid[:-1].rsplit('<', 1)[0].rstrip()
228 else:
229 return userid
230
231
232def extract_email(userid):
233 """Extract the email address out of a user-id string.
234
235 user-id strings have the format 'name <email>'.
236 """
237 if userid and userid[-1] == '>':
238 return userid[:-1].rsplit('<', 1)[1]
239 else:
240 return userid
241
242def extract_name_item(seq, n):
243 """Extract the name out of the nth item in a sequence of user-ids.
244
245 :return: the user-name or an empty string
246 """
247 try:
248 return extract_name(seq[n])
249 except IndexError:
250 return ""
251
252
253def extract_email_item(seq, n):
254 """Extract the email out of the nth item in a sequence of user-ids.
255
256 :return: the email address or an empty string
257 """
258 try:
259 return extract_email(seq[n])
260 except IndexError:
261 return ""
262
263
264def compress_keywords(s, keyword_dicts):
265 """Replace cooked style keywords with raw style in a string.
266
267 Note: If the keyword is not known, the text is not modified.
268
269 :param s: the string
270 :param keyword_dicts: an iterable of keyword dictionaries.
271 :return: the string with keywords compressed
272 """
273 _raw_style = _keyword_style_registry.get('raw')
274 result = ''
275 rest = s
276 while (True):
277 match = _KW_COOKED_RE.search(rest)
278 if not match:
279 break
280 result += rest[:match.start()]
281 keyword = match.group(1)
282 expansion = _get_from_dicts(keyword_dicts, keyword)
283 if expansion is None:
284 # Unknown expansion - leave as is
285 result += match.group(0)
286 else:
287 result += _raw_style % {'name': keyword}
288 rest = rest[match.end():]
289 return result + rest
290
291
292def expand_keywords(s, keyword_dicts, context=None, encoder=None, style=None):
293 """Replace raw style keywords with another style in a string.
294
295 Note: If the keyword is already in the expanded style, the value is
296 not replaced.
297
298 :param s: the string
299 :param keyword_dicts: an iterable of keyword dictionaries. If values
300 are callables, they are executed to find the real value.
301 :param context: the parameter to pass to callable values
302 :param style: the style of expansion to use of None for the default
303 :return: the string with keywords expanded
304 """
305 _expanded_style = _keyword_style_registry.get(style)
306 result = ''
307 rest = s
308 while (True):
309 match = _KW_RAW_RE.search(rest)
310 if not match:
311 break
312 result += rest[:match.start()]
313 keyword = match.group(1)
314 expansion = _get_from_dicts(keyword_dicts, keyword)
315 if callable(expansion):
316 try:
317 expansion = expansion(context)
318 except AttributeError, err:
319 if 'error' in debug.debug_flags:
320 trace.note("error evaluating %s for keyword %s: %s",
321 expansion, keyword, err)
322 expansion = "(evaluation error)"
323 if expansion is None:
324 # Unknown expansion - leave as is
325 result += match.group(0)
326 rest = rest[match.end():]
327 continue
328 if '$' in expansion:
329 # Expansion is not safe to be collapsed later
330 expansion = "(value unsafe to expand)"
331 if encoder is not None:
332 expansion = encoder(expansion)
333 params = {'name': keyword, 'value': expansion}
334 result += _expanded_style % params
335 rest = rest[match.end():]
336 return result + rest
337
338
339def _get_from_dicts(dicts, key, default=None):
340 """Search a sequence of dictionaries or registries for a key.
341
342 :return: the value, or default if not found
343 """
344 for dict in dicts:
345 if key in dict:
346 return dict.get(key)
347 return default
348
349
350def _xml_escape(s):
351 """Escape a string so it can be included safely in XML/HTML."""
352 # Complie the regular expressions if not already done
353 xml8._ensure_utf8_re()
354 # Convert and strip the trailing quote
355 return xml8._encode_and_escape(s)[:-1]
356
357
358def _kw_compressor(chunks, context=None):
359 """Filter that replaces keywords with their compressed form."""
360 text = ''.join(chunks)
361 return [compress_keywords(text, [keyword_registry])]
362
363
364def _kw_expander(chunks, context, encoder=None):
365 """Keyword expander."""
366 text = ''.join(chunks)
367 return [expand_keywords(text, [keyword_registry], context=context,
368 encoder=encoder)]
369
370
371def _normal_kw_expander(chunks, context=None):
372 """Filter that replaces keywords with their expanded form."""
373 return _kw_expander(chunks, context)
374
375
376def _xml_escape_kw_expander(chunks, context=None):
377 """Filter that replaces keywords with a form suitable for use in XML."""
378 return _kw_expander(chunks, context, encoder=_xml_escape)
379
380
381# Define and register the filter stack map133# Define and register the filter stack map
382def _keywords_filter_stack_lookup(k):134def _keywords_filter_stack_lookup(k):
135 from bzrlib.plugins.keywords.keywords import (
136 _kw_compressor,
137 _normal_kw_expander,
138 _xml_escape_kw_expander,
139 )
383 filter_stack_map = {140 filter_stack_map = {
384 'off': [],141 'off': [],
385 'on':142 'on':
@@ -416,21 +173,25 @@
416 # override the inherited run() and help() methods173 # override the inherited run() and help() methods
417174
418 takes_options = builtins.cmd_cat.takes_options + [175 takes_options = builtins.cmd_cat.takes_options + [
419 option.RegistryOption('keywords',176 option.RegistryOption('keywords',
420 registry=_keyword_style_registry,177 lazy_registry=("bzrlib.plugins.keywords.keywords",
421 converter=lambda s: s,178 "_keyword_style_registry"),
422 help='Keyword expansion style.')]179 converter=lambda s: s,
423 180 help='Keyword expansion style.')]
181
424 def run(self, *args, **kwargs):182 def run(self, *args, **kwargs):
425 """Process special options and delegate to superclass."""183 """Process special options and delegate to superclass."""
426 if 'keywords' in kwargs:184 if 'keywords' in kwargs:
185 from bzrlib.plugins.keywords.keywords import (
186 _keyword_style_registry,
187 )
427 # Implicitly set the filters option188 # Implicitly set the filters option
428 kwargs['filters'] = True189 kwargs['filters'] = True
429 style = kwargs['keywords']190 style = kwargs['keywords']
430 _keyword_style_registry.default_key = style191 _keyword_style_registry.default_key = style
431 del kwargs['keywords']192 del kwargs['keywords']
432 return super(cmd_cat, self).run(*args, **kwargs)193 return super(cmd_cat, self).run(*args, **kwargs)
433 194
434 def help(self):195 def help(self):
435 """Return help message including text from superclass."""196 """Return help message including text from superclass."""
436 from inspect import getdoc197 from inspect import getdoc
@@ -442,21 +203,25 @@
442 # override the inherited run() and help() methods203 # override the inherited run() and help() methods
443204
444 takes_options = builtins.cmd_export.takes_options + [205 takes_options = builtins.cmd_export.takes_options + [
445 option.RegistryOption('keywords',206 option.RegistryOption('keywords',
446 registry=_keyword_style_registry,207 lazy_registry=("bzrlib.plugins.keywords.keywords",
208 "_keyword_style_registry"),
447 converter=lambda s: s,209 converter=lambda s: s,
448 help='Keyword expansion style.')]210 help='Keyword expansion style.')]
449 211
450 def run(self, *args, **kwargs):212 def run(self, *args, **kwargs):
451 """Process special options and delegate to superclass."""213 """Process special options and delegate to superclass."""
452 if 'keywords' in kwargs:214 if 'keywords' in kwargs:
215 from bzrlib.plugins.keywords.keywords import (
216 _keyword_style_registry,
217 )
453 # Implicitly set the filters option218 # Implicitly set the filters option
454 kwargs['filters'] = True219 kwargs['filters'] = True
455 style = kwargs['keywords']220 style = kwargs['keywords']
456 _keyword_style_registry.default_key = style221 _keyword_style_registry.default_key = style
457 del kwargs['keywords']222 del kwargs['keywords']
458 return super(cmd_export, self).run(*args, **kwargs)223 return super(cmd_export, self).run(*args, **kwargs)
459 224
460 def help(self):225 def help(self):
461 """Return help message including text from superclass."""226 """Return help message including text from superclass."""
462 from inspect import getdoc227 from inspect import getdoc
463228
=== added file 'keywords.py'
--- keywords.py 1970-01-01 00:00:00 +0000
+++ keywords.py 2011-03-02 12:34:10 +0000
@@ -0,0 +1,264 @@
1# Copyright (C) 2008 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17import re, time
18from bzrlib import (
19 debug,
20 osutils,
21 registry,
22 trace,
23 )
24
25# Expansion styles
26# Note: Round-tripping is only required between the raw and cooked styles
27_keyword_style_registry = registry.Registry()
28_keyword_style_registry.register('raw', '$%(name)s$')
29_keyword_style_registry.register('cooked', '$%(name)s: %(value)s $')
30_keyword_style_registry.register('publish', '%(name)s: %(value)s')
31_keyword_style_registry.register('publish-values', '%(value)s')
32_keyword_style_registry.register('publish-names', '%(name)s')
33_keyword_style_registry.default_key = 'cooked'
34
35
36# Regular expressions for matching the raw and cooked patterns
37_KW_RAW_RE = re.compile(r'\$([\w\-]+)(:[^$]*)?\$')
38_KW_COOKED_RE = re.compile(r'\$([\w\-]+):([^$]+)\$')
39
40
41# The registry of keywords. Other plugins may wish to add entries to this.
42keyword_registry = registry.Registry()
43
44# Revision-related keywords
45keyword_registry.register('Date',
46 lambda c: format_date(c.revision().timestamp, c.revision().timezone,
47 c.config(), 'Date'))
48keyword_registry.register('Committer',
49 lambda c: c.revision().committer)
50keyword_registry.register('Authors',
51 lambda c: ", ".join(c.revision().get_apparent_authors()))
52keyword_registry.register('Revision-Id',
53 lambda c: c.revision_id())
54keyword_registry.register('Path',
55 lambda c: c.relpath())
56keyword_registry.register('Directory',
57 lambda c: osutils.split(c.relpath())[0])
58keyword_registry.register('Filename',
59 lambda c: osutils.split(c.relpath())[1])
60keyword_registry.register('File-Id',
61 lambda c: c.file_id())
62
63# Environment-related keywords
64keyword_registry.register('Now',
65 lambda c: format_date(time.time(), time.timezone, c.config(), 'Now'))
66keyword_registry.register('User',
67 lambda c: c.config().username())
68
69# Keywords for finer control over name & address formatting
70keyword_registry.register('Committer-Name',
71 lambda c: extract_name(c.revision().committer))
72keyword_registry.register('Committer-Email',
73 lambda c: extract_email(c.revision().committer))
74keyword_registry.register('Author1-Name',
75 lambda c: extract_name_item(c.revision().get_apparent_authors(), 0))
76keyword_registry.register('Author1-Email',
77 lambda c: extract_email_item(c.revision().get_apparent_authors(), 0))
78keyword_registry.register('Author2-Name',
79 lambda c: extract_name_item(c.revision().get_apparent_authors(), 1))
80keyword_registry.register('Author2-Email',
81 lambda c: extract_email_item(c.revision().get_apparent_authors(), 1))
82keyword_registry.register('Author3-Name',
83 lambda c: extract_name_item(c.revision().get_apparent_authors(), 2))
84keyword_registry.register('Author3-Email',
85 lambda c: extract_email_item(c.revision().get_apparent_authors(), 2))
86keyword_registry.register('User-Name',
87 lambda c: extract_name(c.config().username()))
88keyword_registry.register('User-Email',
89 lambda c: extract_email(c.config().username()))
90
91
92def format_date(timestamp, offset=0, cfg=None, name=None):
93 """Return a formatted date string.
94
95 :param timestamp: Seconds since the epoch.
96 :param offset: Timezone offset in seconds east of utc.
97 """
98 if cfg is not None and name is not None:
99 cfg_key = 'keywords.format.%s' % (name,)
100 format = cfg.get_user_option(cfg_key)
101 else:
102 format = None
103 return osutils.format_date(timestamp, offset, date_fmt=format)
104
105
106def extract_name(userid):
107 """Extract the name out of a user-id string.
108
109 user-id strings have the format 'name <email>'.
110 """
111 if userid and userid[-1] == '>':
112 return userid[:-1].rsplit('<', 1)[0].rstrip()
113 else:
114 return userid
115
116
117def extract_email(userid):
118 """Extract the email address out of a user-id string.
119
120 user-id strings have the format 'name <email>'.
121 """
122 if userid and userid[-1] == '>':
123 return userid[:-1].rsplit('<', 1)[1]
124 else:
125 return userid
126
127def extract_name_item(seq, n):
128 """Extract the name out of the nth item in a sequence of user-ids.
129
130 :return: the user-name or an empty string
131 """
132 try:
133 return extract_name(seq[n])
134 except IndexError:
135 return ""
136
137
138def extract_email_item(seq, n):
139 """Extract the email out of the nth item in a sequence of user-ids.
140
141 :return: the email address or an empty string
142 """
143 try:
144 return extract_email(seq[n])
145 except IndexError:
146 return ""
147
148
149def compress_keywords(s, keyword_dicts):
150 """Replace cooked style keywords with raw style in a string.
151
152 Note: If the keyword is not known, the text is not modified.
153
154 :param s: the string
155 :param keyword_dicts: an iterable of keyword dictionaries.
156 :return: the string with keywords compressed
157 """
158 _raw_style = _keyword_style_registry.get('raw')
159 result = ''
160 rest = s
161 while (True):
162 match = _KW_COOKED_RE.search(rest)
163 if not match:
164 break
165 result += rest[:match.start()]
166 keyword = match.group(1)
167 expansion = _get_from_dicts(keyword_dicts, keyword)
168 if expansion is None:
169 # Unknown expansion - leave as is
170 result += match.group(0)
171 else:
172 result += _raw_style % {'name': keyword}
173 rest = rest[match.end():]
174 return result + rest
175
176
177def expand_keywords(s, keyword_dicts, context=None, encoder=None, style=None):
178 """Replace raw style keywords with another style in a string.
179
180 Note: If the keyword is already in the expanded style, the value is
181 not replaced.
182
183 :param s: the string
184 :param keyword_dicts: an iterable of keyword dictionaries. If values
185 are callables, they are executed to find the real value.
186 :param context: the parameter to pass to callable values
187 :param style: the style of expansion to use of None for the default
188 :return: the string with keywords expanded
189 """
190 _expanded_style = _keyword_style_registry.get(style)
191 result = ''
192 rest = s
193 while (True):
194 match = _KW_RAW_RE.search(rest)
195 if not match:
196 break
197 result += rest[:match.start()]
198 keyword = match.group(1)
199 expansion = _get_from_dicts(keyword_dicts, keyword)
200 if callable(expansion):
201 try:
202 expansion = expansion(context)
203 except AttributeError, err:
204 if 'error' in debug.debug_flags:
205 trace.note("error evaluating %s for keyword %s: %s",
206 expansion, keyword, err)
207 expansion = "(evaluation error)"
208 if expansion is None:
209 # Unknown expansion - leave as is
210 result += match.group(0)
211 rest = rest[match.end():]
212 continue
213 if '$' in expansion:
214 # Expansion is not safe to be collapsed later
215 expansion = "(value unsafe to expand)"
216 if encoder is not None:
217 expansion = encoder(expansion)
218 params = {'name': keyword, 'value': expansion}
219 result += _expanded_style % params
220 rest = rest[match.end():]
221 return result + rest
222
223
224def _get_from_dicts(dicts, key, default=None):
225 """Search a sequence of dictionaries or registries for a key.
226
227 :return: the value, or default if not found
228 """
229 for dict in dicts:
230 if key in dict:
231 return dict.get(key)
232 return default
233
234
235def _xml_escape(s):
236 """Escape a string so it can be included safely in XML/HTML."""
237 # Compile the regular expressions if not already done
238 from bzrlib import xml8
239 xml8._ensure_utf8_re()
240 # Convert and strip the trailing quote
241 return xml8._encode_and_escape(s)[:-1]
242
243
244def _kw_compressor(chunks, context=None):
245 """Filter that replaces keywords with their compressed form."""
246 text = ''.join(chunks)
247 return [compress_keywords(text, [keyword_registry])]
248
249
250def _kw_expander(chunks, context, encoder=None):
251 """Keyword expander."""
252 text = ''.join(chunks)
253 return [expand_keywords(text, [keyword_registry], context=context,
254 encoder=encoder)]
255
256
257def _normal_kw_expander(chunks, context=None):
258 """Filter that replaces keywords with their expanded form."""
259 return _kw_expander(chunks, context)
260
261
262def _xml_escape_kw_expander(chunks, context=None):
263 """Filter that replaces keywords with a form suitable for use in XML."""
264 return _kw_expander(chunks, context, encoder=_xml_escape)
0265
=== modified file 'tests/test_conversion.py'
--- tests/test_conversion.py 2010-10-01 17:31:51 +0000
+++ tests/test_conversion.py 2011-03-02 12:34:10 +0000
@@ -18,7 +18,10 @@
1818
1919
20from bzrlib import tests20from bzrlib import tests
21from bzrlib.plugins.keywords import compress_keywords, expand_keywords21from bzrlib.plugins.keywords.keywords import (
22 compress_keywords,
23 expand_keywords,
24 )
2225
2326
24# Sample unexpanded and expanded pairs for a keyword dictionary27# Sample unexpanded and expanded pairs for a keyword dictionary
2528
=== modified file 'tests/test_keywords_in_trees.py'
--- tests/test_keywords_in_trees.py 2009-08-11 05:14:31 +0000
+++ tests/test_keywords_in_trees.py 2011-03-02 12:34:10 +0000
@@ -18,12 +18,8 @@
1818
19## TODO: add tests for xml_escaped19## TODO: add tests for xml_escaped
2020
21from cStringIO import StringIO21from bzrlib import rules
22import sys22from bzrlib.tests import TestCaseWithTransport
23
24from bzrlib import config, rules
25from bzrlib.tests import TestCaseWithTransport, TestSkipped
26from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree
27from bzrlib.workingtree import WorkingTree23from bzrlib.workingtree import WorkingTree
2824
2925
@@ -90,7 +86,7 @@
90 t.add('file1', 'file1-id')86 t.add('file1', 'file1-id')
91 t.commit("add file1", rev_id="rev1-id",87 t.commit("add file1", rev_id="rev1-id",
92 committer="Jane Smith <jane@example.com>",88 committer="Jane Smith <jane@example.com>",
93 author="Sue Smith <sue@example.com>")89 authors=["Sue Smith <sue@example.com>"])
94 basis = t.basis_tree()90 basis = t.basis_tree()
95 basis.lock_read()91 basis.lock_read()
96 self.addCleanup(basis.unlock)92 self.addCleanup(basis.unlock)

Subscribers

People subscribed via source and target branches