Merge lp:~jelmer/brz/bundle-keywords into lp:brz
- bundle-keywords
- Merge into trunk
Proposed by
Jelmer Vernooij
Status: | Work in progress |
---|---|
Proposed branch: | lp:~jelmer/brz/bundle-keywords |
Merge into: | lp:brz |
Diff against target: |
862 lines (+810/-0) 9 files modified
breezy/filters/__init__.py (+3/-0) breezy/plugins/keywords/NEWS (+18/-0) breezy/plugins/keywords/README.txt (+43/-0) breezy/plugins/keywords/TODO (+13/-0) breezy/plugins/keywords/__init__.py (+240/-0) breezy/plugins/keywords/keywords.py (+269/-0) breezy/plugins/keywords/tests/__init__.py (+19/-0) breezy/plugins/keywords/tests/test_conversion.py (+82/-0) breezy/plugins/keywords/tests/test_keywords_in_trees.py (+123/-0) |
To merge this branch: | bzr merge lp:~jelmer/brz/bundle-keywords |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Breezy developers | Pending | ||
Review via email: mp+358357@code.launchpad.net |
Commit message
Description of the change
Bundle the keywords plugin.
To post a comment you must log in.
lp:~jelmer/brz/bundle-keywords
updated
- 6694. By Jelmer Vernooij
-
Use absolute imports.
Unmerged revisions
- 6694. By Jelmer Vernooij
-
Use absolute imports.
- 6693. By Jelmer Vernooij
-
Fix tests with breezy.
- 6692. By Jelmer Vernooij
-
merge trunk.
- 6691. By Jelmer Vernooij
-
Bundle keywords plugin.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'breezy/filters/__init__.py' | |||
2 | --- breezy/filters/__init__.py 2018-07-17 22:59:51 +0000 | |||
3 | +++ breezy/filters/__init__.py 2018-11-12 21:28:40 +0000 | |||
4 | @@ -91,6 +91,9 @@ | |||
5 | 91 | """Relative path of file to tree-root.""" | 91 | """Relative path of file to tree-root.""" |
6 | 92 | return self._relpath | 92 | return self._relpath |
7 | 93 | 93 | ||
8 | 94 | def file_id(self): | ||
9 | 95 | return self.source_tree().path2id(self.relpath()) | ||
10 | 96 | |||
11 | 94 | def source_tree(self): | 97 | def source_tree(self): |
12 | 95 | """Source Tree object.""" | 98 | """Source Tree object.""" |
13 | 96 | return self._tree | 99 | return self._tree |
14 | 97 | 100 | ||
15 | === added directory 'breezy/plugins/keywords' | |||
16 | === added file 'breezy/plugins/keywords/NEWS' | |||
17 | --- breezy/plugins/keywords/NEWS 1970-01-01 00:00:00 +0000 | |||
18 | +++ breezy/plugins/keywords/NEWS 2018-11-12 21:28:40 +0000 | |||
19 | @@ -0,0 +1,18 @@ | |||
20 | 1 | ########################## | ||
21 | 2 | bzr-keywords Release Notes | ||
22 | 3 | ########################## | ||
23 | 4 | |||
24 | 5 | .. contents:: | ||
25 | 6 | |||
26 | 7 | In Development | ||
27 | 8 | ############## | ||
28 | 9 | |||
29 | 10 | This version is suitable for use with Bazaar 1.14 or later using | ||
30 | 11 | trees in the 1.14 format. | ||
31 | 12 | |||
32 | 13 | |||
33 | 14 | 0.1 28-Jul-2008 | ||
34 | 15 | ############### | ||
35 | 16 | |||
36 | 17 | This version is suitable for testing with the development branch, | ||
37 | 18 | ~ian-clatworthy/bzr/bzr.content-filters. | ||
38 | 0 | 19 | ||
39 | === added file 'breezy/plugins/keywords/README.txt' | |||
40 | --- breezy/plugins/keywords/README.txt 1970-01-01 00:00:00 +0000 | |||
41 | +++ breezy/plugins/keywords/README.txt 2018-11-12 21:28:40 +0000 | |||
42 | @@ -0,0 +1,43 @@ | |||
43 | 1 | bzr-keywords: RCS-like keyword templates | ||
44 | 2 | ======================================== | ||
45 | 3 | |||
46 | 4 | Overview | ||
47 | 5 | -------- | ||
48 | 6 | |||
49 | 7 | This plugin adds keyword filtering to selected files. This allows | ||
50 | 8 | you to do things like include the current user and date in a web page. | ||
51 | 9 | |||
52 | 10 | |||
53 | 11 | Installation | ||
54 | 12 | ------------ | ||
55 | 13 | |||
56 | 14 | The easiest way to install this plugin is to either copy or symlink the | ||
57 | 15 | directory into your ~/.bazaar/plugins directory. Be sure to rename the | ||
58 | 16 | directory to keywords (instead of bzr-keywords). | ||
59 | 17 | |||
60 | 18 | See http://bazaar-vcs.org/UsingPlugins for other options such as | ||
61 | 19 | using the BZR_PLUGIN_PATH environment variable. | ||
62 | 20 | |||
63 | 21 | |||
64 | 22 | Testing | ||
65 | 23 | ------- | ||
66 | 24 | |||
67 | 25 | To test the plugin after installation: | ||
68 | 26 | |||
69 | 27 | bzr selftest keywords.tests | ||
70 | 28 | |||
71 | 29 | |||
72 | 30 | Documentation | ||
73 | 31 | ------------- | ||
74 | 32 | |||
75 | 33 | To see the documentation after installation: | ||
76 | 34 | |||
77 | 35 | bzr help keywords | ||
78 | 36 | |||
79 | 37 | |||
80 | 38 | Licensing | ||
81 | 39 | --------- | ||
82 | 40 | |||
83 | 41 | This plugin is (C) Copyright Canonical Limited 2008 under the | ||
84 | 42 | GPL Version 2 or later. Please see the file COPYING.txt for the licence | ||
85 | 43 | details. | ||
86 | 0 | 44 | ||
87 | === added file 'breezy/plugins/keywords/TODO' | |||
88 | --- breezy/plugins/keywords/TODO 1970-01-01 00:00:00 +0000 | |||
89 | +++ breezy/plugins/keywords/TODO 2018-11-12 21:28:40 +0000 | |||
90 | @@ -0,0 +1,13 @@ | |||
91 | 1 | Things to consider: | ||
92 | 2 | * python_escape (maybe called string_escape) ala xml_escape? | ||
93 | 3 | * some sort of block construct so easier to include in ReST and properties | ||
94 | 4 | files, e.g. | ||
95 | 5 | .. $begin-keywords$ | ||
96 | 6 | :name1: value1 | ||
97 | 7 | :name2: value2 | ||
98 | 8 | .. $end-keywords$ | ||
99 | 9 | |||
100 | 10 | * Add tests for: | ||
101 | 11 | * untested keyword values (including date formatting) | ||
102 | 12 | * escaping | ||
103 | 13 | * style formatting | ||
104 | 0 | 14 | ||
105 | === added file 'breezy/plugins/keywords/__init__.py' | |||
106 | --- breezy/plugins/keywords/__init__.py 1970-01-01 00:00:00 +0000 | |||
107 | +++ breezy/plugins/keywords/__init__.py 2018-11-12 21:28:40 +0000 | |||
108 | @@ -0,0 +1,240 @@ | |||
109 | 1 | # Copyright (C) 2008 Canonical Ltd | ||
110 | 2 | # | ||
111 | 3 | # This program is free software; you can redistribute it and/or modify | ||
112 | 4 | # it under the terms of the GNU General Public License as published by | ||
113 | 5 | # the Free Software Foundation; either version 2 of the License, or | ||
114 | 6 | # (at your option) any later version. | ||
115 | 7 | # | ||
116 | 8 | # This program is distributed in the hope that it will be useful, | ||
117 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
118 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
119 | 11 | # GNU General Public License for more details. | ||
120 | 12 | # | ||
121 | 13 | # You should have received a copy of the GNU General Public License | ||
122 | 14 | # along with this program; if not, write to the Free Software | ||
123 | 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
124 | 16 | |||
125 | 17 | r'''Keyword Templating | ||
126 | 18 | ================== | ||
127 | 19 | |||
128 | 20 | Keyword templating is provided as a content filter where Bazaar internally | ||
129 | 21 | stores a canonical format but outputs a convenience format. See | ||
130 | 22 | ``bzr help content-filters`` for general information about using these. | ||
131 | 23 | |||
132 | 24 | Note: Content filtering is only supported in recently added formats, | ||
133 | 25 | e.g. 1.14. | ||
134 | 26 | |||
135 | 27 | Keyword templates are specified using the following patterns: | ||
136 | 28 | |||
137 | 29 | * in canonical/compressed format: $Keyword$ | ||
138 | 30 | * in convenience/expanded format: $Keyword: value $ | ||
139 | 31 | |||
140 | 32 | When expanding, the existing text is retained if an unknown keyword is | ||
141 | 33 | found. If the keyword is already expanded but known, the value is replaced. | ||
142 | 34 | When compressing, the values of known keywords are removed. | ||
143 | 35 | |||
144 | 36 | Keyword filtering needs to be enabled for selected branches and files via | ||
145 | 37 | rules. See ``bzr help rules`` for general information on defining rules. | ||
146 | 38 | For example, to enable keywords for all ``txt`` files on your system, add | ||
147 | 39 | these lines to your ``BZR_HOME/rules`` file:: | ||
148 | 40 | |||
149 | 41 | [name *.txt] | ||
150 | 42 | keywords = on | ||
151 | 43 | |||
152 | 44 | To disable keywords for ``txt`` files but enable them for ``html`` files:: | ||
153 | 45 | |||
154 | 46 | [name *.txt] | ||
155 | 47 | keywords = off | ||
156 | 48 | |||
157 | 49 | [name *.html] | ||
158 | 50 | keywords = xml_escape | ||
159 | 51 | |||
160 | 52 | ``xml_escape`` enables keyword expansion but it escapes special characters | ||
161 | 53 | in keyword values so they can be safely included in HTML or XML files. | ||
162 | 54 | |||
163 | 55 | The currently supported keywords are given below. | ||
164 | 56 | |||
165 | 57 | ============= ========================================================= | ||
166 | 58 | Keyword Description | ||
167 | 59 | ============= ========================================================= | ||
168 | 60 | Date the date and time the file was last modified | ||
169 | 61 | Committer the committer (name and email) of the last change | ||
170 | 62 | Authors the authors (names and emails) of the last change | ||
171 | 63 | Revision-Id the unique id of the revision that last changed the file | ||
172 | 64 | Path the relative path of the file in the tree | ||
173 | 65 | Filename just the name part of the relative path | ||
174 | 66 | Directory just the directory part of the relative path | ||
175 | 67 | File-Id the unique id assigned to this file | ||
176 | 68 | Now the current date and time | ||
177 | 69 | User the current user (name and email) | ||
178 | 70 | ============= ========================================================= | ||
179 | 71 | |||
180 | 72 | If you want finer control over the formatting of names and email | ||
181 | 73 | addresses, you can use the following keywords. | ||
182 | 74 | |||
183 | 75 | ============= ======================================================= | ||
184 | 76 | Keyword Description | ||
185 | 77 | ============= ======================================================= | ||
186 | 78 | Committer-Name just the name of the current committer | ||
187 | 79 | Committer-Email just the email address of the current committer | ||
188 | 80 | Author1-Name just the name of the first author | ||
189 | 81 | Author1-Email just the email address of the first author | ||
190 | 82 | Author2-Name just the name of the second author | ||
191 | 83 | Author2-Email just the email address of the second author | ||
192 | 84 | Author3-Name just the name of the third author | ||
193 | 85 | Author3-Email just the email address of the third author | ||
194 | 86 | User-Name just the name of the current user | ||
195 | 87 | User-Email just the email address of the current user | ||
196 | 88 | ============= ======================================================= | ||
197 | 89 | |||
198 | 90 | Note: If you have more than 3 authors for a given revision, please | ||
199 | 91 | ask on the Bazaar mailing list for an enhancement to support the | ||
200 | 92 | number you need. | ||
201 | 93 | |||
202 | 94 | By default, dates/times are output using this format:: | ||
203 | 95 | |||
204 | 96 | YYYY-MM-DD HH:MM:SS+HH:MM | ||
205 | 97 | |||
206 | 98 | To specify a custom format, add a configuration setting to | ||
207 | 99 | ``BZR_HOME/bazaar.conf`` like this:: | ||
208 | 100 | |||
209 | 101 | keywords.format.Now = %A, %B %d, %Y | ||
210 | 102 | |||
211 | 103 | The last part of the key needs to match the keyword name. The value must be | ||
212 | 104 | a legal strftime (http://docs.python.org/lib/module-time.html) format. | ||
213 | 105 | ''' | ||
214 | 106 | |||
215 | 107 | from __future__ import absolute_import | ||
216 | 108 | |||
217 | 109 | |||
218 | 110 | from ... import ( | ||
219 | 111 | builtins, | ||
220 | 112 | commands, | ||
221 | 113 | filters, | ||
222 | 114 | option, | ||
223 | 115 | ) | ||
224 | 116 | |||
225 | 117 | |||
226 | 118 | def test_suite(): | ||
227 | 119 | """Called by breezy to fetch tests for this plugin""" | ||
228 | 120 | from unittest import TestSuite, TestLoader | ||
229 | 121 | from .tests import ( | ||
230 | 122 | test_conversion, | ||
231 | 123 | test_keywords_in_trees, | ||
232 | 124 | ) | ||
233 | 125 | loader = TestLoader() | ||
234 | 126 | suite = TestSuite() | ||
235 | 127 | for module in [ | ||
236 | 128 | test_conversion, | ||
237 | 129 | test_keywords_in_trees, | ||
238 | 130 | ]: | ||
239 | 131 | suite.addTests(loader.loadTestsFromModule(module)) | ||
240 | 132 | return suite | ||
241 | 133 | |||
242 | 134 | |||
243 | 135 | # Define and register the filter stack map | ||
244 | 136 | def _keywords_filter_stack_lookup(k): | ||
245 | 137 | from .keywords import ( | ||
246 | 138 | _kw_compressor, | ||
247 | 139 | _normal_kw_expander, | ||
248 | 140 | _xml_escape_kw_expander, | ||
249 | 141 | ) | ||
250 | 142 | filter_stack_map = { | ||
251 | 143 | 'off': [], | ||
252 | 144 | 'on': | ||
253 | 145 | [filters.ContentFilter(_kw_compressor, _normal_kw_expander)], | ||
254 | 146 | 'xml_escape': | ||
255 | 147 | [filters.ContentFilter(_kw_compressor, _xml_escape_kw_expander)], | ||
256 | 148 | } | ||
257 | 149 | return filter_stack_map.get(k) | ||
258 | 150 | |||
259 | 151 | try: | ||
260 | 152 | register_filter = filters.filter_stacks_registry.register | ||
261 | 153 | except AttributeError: | ||
262 | 154 | register_filter = filters.register_filter_stack_map | ||
263 | 155 | |||
264 | 156 | register_filter('keywords', _keywords_filter_stack_lookup) | ||
265 | 157 | |||
266 | 158 | |||
267 | 159 | class cmd_cat(builtins.cmd_cat): | ||
268 | 160 | """ | ||
269 | 161 | The ``--keywords`` option specifies the keywords expansion | ||
270 | 162 | style. By default (``raw`` style), no expansion is done. | ||
271 | 163 | Other styles enable expansion in a ``cooked`` mode where both | ||
272 | 164 | the keyword and its value are displayed inside $ markers, or in | ||
273 | 165 | numerous publishing styles - ``publish``, ``publish-values`` and | ||
274 | 166 | ``publish-names`` - where the $ markers are completely removed. | ||
275 | 167 | The publishing styles do not support round-tripping back to the | ||
276 | 168 | raw content but are useful for improving the readability of | ||
277 | 169 | published web pages for example. | ||
278 | 170 | |||
279 | 171 | Note: Files must have the ``keywords`` preference defined for them | ||
280 | 172 | in order for the ``--keywords`` option to take effect. In particular, | ||
281 | 173 | the preference specifies how keyword values are encoded for different | ||
282 | 174 | filename patterns. See ``bzr help keywords`` for more information on | ||
283 | 175 | how to specify the required preference using rules. | ||
284 | 176 | """ | ||
285 | 177 | |||
286 | 178 | # Add a new option to the builtin command and | ||
287 | 179 | # override the inherited run() and help() methods | ||
288 | 180 | |||
289 | 181 | takes_options = builtins.cmd_cat.takes_options + [ | ||
290 | 182 | option.RegistryOption('keywords', | ||
291 | 183 | lazy_registry=(__name__ + ".keywords", | ||
292 | 184 | "_keyword_style_registry"), | ||
293 | 185 | converter=lambda s: s, | ||
294 | 186 | help='Keyword expansion style.')] | ||
295 | 187 | |||
296 | 188 | def run(self, *args, **kwargs): | ||
297 | 189 | """Process special options and delegate to superclass.""" | ||
298 | 190 | if 'keywords' in kwargs: | ||
299 | 191 | from .keywords import ( | ||
300 | 192 | _keyword_style_registry, | ||
301 | 193 | ) | ||
302 | 194 | # Implicitly set the filters option | ||
303 | 195 | kwargs['filters'] = True | ||
304 | 196 | style = kwargs['keywords'] | ||
305 | 197 | _keyword_style_registry.default_key = style | ||
306 | 198 | del kwargs['keywords'] | ||
307 | 199 | return super(cmd_cat, self).run(*args, **kwargs) | ||
308 | 200 | |||
309 | 201 | def help(self): | ||
310 | 202 | """Return help message including text from superclass.""" | ||
311 | 203 | from inspect import getdoc | ||
312 | 204 | return getdoc(super(cmd_cat, self)) + '\n\n' + getdoc(self) | ||
313 | 205 | |||
314 | 206 | |||
315 | 207 | class cmd_export(builtins.cmd_export): | ||
316 | 208 | # Add a new option to the builtin command and | ||
317 | 209 | # override the inherited run() and help() methods | ||
318 | 210 | |||
319 | 211 | takes_options = builtins.cmd_export.takes_options + [ | ||
320 | 212 | option.RegistryOption('keywords', | ||
321 | 213 | lazy_registry=(__name__ + ".keywords", | ||
322 | 214 | "_keyword_style_registry"), | ||
323 | 215 | converter=lambda s: s, | ||
324 | 216 | help='Keyword expansion style.')] | ||
325 | 217 | |||
326 | 218 | def run(self, *args, **kwargs): | ||
327 | 219 | """Process special options and delegate to superclass.""" | ||
328 | 220 | if 'keywords' in kwargs: | ||
329 | 221 | from .keywords import ( | ||
330 | 222 | _keyword_style_registry, | ||
331 | 223 | ) | ||
332 | 224 | # Implicitly set the filters option | ||
333 | 225 | kwargs['filters'] = True | ||
334 | 226 | style = kwargs['keywords'] | ||
335 | 227 | _keyword_style_registry.default_key = style | ||
336 | 228 | del kwargs['keywords'] | ||
337 | 229 | return super(cmd_export, self).run(*args, **kwargs) | ||
338 | 230 | |||
339 | 231 | def help(self): | ||
340 | 232 | """Return help message including text from superclass.""" | ||
341 | 233 | from inspect import getdoc | ||
342 | 234 | # NOTE: Reuse of cmd_cat help below is deliberate, not a bug | ||
343 | 235 | return getdoc(super(cmd_export, self)) + '\n\n' + getdoc(cmd_cat) | ||
344 | 236 | |||
345 | 237 | |||
346 | 238 | # Register the command wrappers | ||
347 | 239 | commands.register_command(cmd_cat, decorate=False) | ||
348 | 240 | commands.register_command(cmd_export, decorate=False) | ||
349 | 0 | 241 | ||
350 | === added file 'breezy/plugins/keywords/keywords.py' | |||
351 | --- breezy/plugins/keywords/keywords.py 1970-01-01 00:00:00 +0000 | |||
352 | +++ breezy/plugins/keywords/keywords.py 2018-11-12 21:28:40 +0000 | |||
353 | @@ -0,0 +1,269 @@ | |||
354 | 1 | # Copyright (C) 2008 Canonical Ltd | ||
355 | 2 | # | ||
356 | 3 | # This program is free software; you can redistribute it and/or modify | ||
357 | 4 | # it under the terms of the GNU General Public License as published by | ||
358 | 5 | # the Free Software Foundation; either version 2 of the License, or | ||
359 | 6 | # (at your option) any later version. | ||
360 | 7 | # | ||
361 | 8 | # This program is distributed in the hope that it will be useful, | ||
362 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
363 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
364 | 11 | # GNU General Public License for more details. | ||
365 | 12 | # | ||
366 | 13 | # You should have received a copy of the GNU General Public License | ||
367 | 14 | # along with this program; if not, write to the Free Software | ||
368 | 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
369 | 16 | |||
370 | 17 | from __future__ import absolute_import | ||
371 | 18 | |||
372 | 19 | import re, time | ||
373 | 20 | from ... import ( | ||
374 | 21 | debug, | ||
375 | 22 | osutils, | ||
376 | 23 | registry, | ||
377 | 24 | trace, | ||
378 | 25 | ) | ||
379 | 26 | from ...sixish import text_type | ||
380 | 27 | |||
381 | 28 | # Expansion styles | ||
382 | 29 | # Note: Round-tripping is only required between the raw and cooked styles | ||
383 | 30 | _keyword_style_registry = registry.Registry() | ||
384 | 31 | _keyword_style_registry.register('raw', b'$%(name)s$') | ||
385 | 32 | _keyword_style_registry.register('cooked', b'$%(name)s: %(value)s $') | ||
386 | 33 | _keyword_style_registry.register('publish', b'%(name)s: %(value)s') | ||
387 | 34 | _keyword_style_registry.register('publish-values', b'%(value)s') | ||
388 | 35 | _keyword_style_registry.register('publish-names', b'%(name)s') | ||
389 | 36 | _keyword_style_registry.default_key = 'cooked' | ||
390 | 37 | |||
391 | 38 | |||
392 | 39 | # Regular expressions for matching the raw and cooked patterns | ||
393 | 40 | _KW_RAW_RE = re.compile(b'\\$([\\w\\-]+)(:[^$]*)?\\$') | ||
394 | 41 | _KW_COOKED_RE = re.compile(b'\\$([\\w\\-]+):([^$]+)\\$') | ||
395 | 42 | |||
396 | 43 | |||
397 | 44 | # The registry of keywords. Other plugins may wish to add entries to this. | ||
398 | 45 | keyword_registry = registry.Registry() | ||
399 | 46 | |||
400 | 47 | # Revision-related keywords | ||
401 | 48 | keyword_registry.register('Date', | ||
402 | 49 | lambda c: format_date(c.revision().timestamp, c.revision().timezone, | ||
403 | 50 | c.config(), 'Date')) | ||
404 | 51 | keyword_registry.register('Committer', | ||
405 | 52 | lambda c: c.revision().committer) | ||
406 | 53 | keyword_registry.register('Authors', | ||
407 | 54 | lambda c: ", ".join(c.revision().get_apparent_authors())) | ||
408 | 55 | keyword_registry.register('Revision-Id', | ||
409 | 56 | lambda c: c.revision_id()) | ||
410 | 57 | keyword_registry.register('Path', | ||
411 | 58 | lambda c: c.relpath()) | ||
412 | 59 | keyword_registry.register('Directory', | ||
413 | 60 | lambda c: osutils.split(c.relpath())[0]) | ||
414 | 61 | keyword_registry.register('Filename', | ||
415 | 62 | lambda c: osutils.split(c.relpath())[1]) | ||
416 | 63 | keyword_registry.register('File-Id', | ||
417 | 64 | lambda c: c.file_id()) | ||
418 | 65 | |||
419 | 66 | # Environment-related keywords | ||
420 | 67 | keyword_registry.register('Now', | ||
421 | 68 | lambda c: format_date(time.time(), time.timezone, c.config(), 'Now')) | ||
422 | 69 | keyword_registry.register('User', | ||
423 | 70 | lambda c: c.config().username()) | ||
424 | 71 | |||
425 | 72 | # Keywords for finer control over name & address formatting | ||
426 | 73 | keyword_registry.register('Committer-Name', | ||
427 | 74 | lambda c: extract_name(c.revision().committer)) | ||
428 | 75 | keyword_registry.register('Committer-Email', | ||
429 | 76 | lambda c: extract_email(c.revision().committer)) | ||
430 | 77 | keyword_registry.register('Author1-Name', | ||
431 | 78 | lambda c: extract_name_item(c.revision().get_apparent_authors(), 0)) | ||
432 | 79 | keyword_registry.register('Author1-Email', | ||
433 | 80 | lambda c: extract_email_item(c.revision().get_apparent_authors(), 0)) | ||
434 | 81 | keyword_registry.register('Author2-Name', | ||
435 | 82 | lambda c: extract_name_item(c.revision().get_apparent_authors(), 1)) | ||
436 | 83 | keyword_registry.register('Author2-Email', | ||
437 | 84 | lambda c: extract_email_item(c.revision().get_apparent_authors(), 1)) | ||
438 | 85 | keyword_registry.register('Author3-Name', | ||
439 | 86 | lambda c: extract_name_item(c.revision().get_apparent_authors(), 2)) | ||
440 | 87 | keyword_registry.register('Author3-Email', | ||
441 | 88 | lambda c: extract_email_item(c.revision().get_apparent_authors(), 2)) | ||
442 | 89 | keyword_registry.register('User-Name', | ||
443 | 90 | lambda c: extract_name(c.config().username())) | ||
444 | 91 | keyword_registry.register('User-Email', | ||
445 | 92 | lambda c: extract_email(c.config().username())) | ||
446 | 93 | |||
447 | 94 | |||
448 | 95 | def format_date(timestamp, offset=0, cfg=None, name=None): | ||
449 | 96 | """Return a formatted date string. | ||
450 | 97 | |||
451 | 98 | :param timestamp: Seconds since the epoch. | ||
452 | 99 | :param offset: Timezone offset in seconds east of utc. | ||
453 | 100 | """ | ||
454 | 101 | if cfg is not None and name is not None: | ||
455 | 102 | cfg_key = 'keywords.format.%s' % (name,) | ||
456 | 103 | format = cfg.get_user_option(cfg_key) | ||
457 | 104 | else: | ||
458 | 105 | format = None | ||
459 | 106 | return osutils.format_date(timestamp, offset, date_fmt=format) | ||
460 | 107 | |||
461 | 108 | |||
462 | 109 | def extract_name(userid): | ||
463 | 110 | """Extract the name out of a user-id string. | ||
464 | 111 | |||
465 | 112 | user-id strings have the format 'name <email>'. | ||
466 | 113 | """ | ||
467 | 114 | if userid and userid[-1] == '>': | ||
468 | 115 | return userid[:-1].rsplit('<', 1)[0].rstrip() | ||
469 | 116 | else: | ||
470 | 117 | return userid | ||
471 | 118 | |||
472 | 119 | |||
473 | 120 | def extract_email(userid): | ||
474 | 121 | """Extract the email address out of a user-id string. | ||
475 | 122 | |||
476 | 123 | user-id strings have the format 'name <email>'. | ||
477 | 124 | """ | ||
478 | 125 | if userid and userid[-1] == '>': | ||
479 | 126 | return userid[:-1].rsplit('<', 1)[1] | ||
480 | 127 | else: | ||
481 | 128 | return userid | ||
482 | 129 | |||
483 | 130 | def extract_name_item(seq, n): | ||
484 | 131 | """Extract the name out of the nth item in a sequence of user-ids. | ||
485 | 132 | |||
486 | 133 | :return: the user-name or an empty string | ||
487 | 134 | """ | ||
488 | 135 | try: | ||
489 | 136 | return extract_name(seq[n]) | ||
490 | 137 | except IndexError: | ||
491 | 138 | return "" | ||
492 | 139 | |||
493 | 140 | |||
494 | 141 | def extract_email_item(seq, n): | ||
495 | 142 | """Extract the email out of the nth item in a sequence of user-ids. | ||
496 | 143 | |||
497 | 144 | :return: the email address or an empty string | ||
498 | 145 | """ | ||
499 | 146 | try: | ||
500 | 147 | return extract_email(seq[n]) | ||
501 | 148 | except IndexError: | ||
502 | 149 | return "" | ||
503 | 150 | |||
504 | 151 | |||
505 | 152 | def compress_keywords(s, keyword_dicts): | ||
506 | 153 | """Replace cooked style keywords with raw style in a string. | ||
507 | 154 | |||
508 | 155 | Note: If the keyword is not known, the text is not modified. | ||
509 | 156 | |||
510 | 157 | :param s: the string | ||
511 | 158 | :param keyword_dicts: an iterable of keyword dictionaries. | ||
512 | 159 | :return: the string with keywords compressed | ||
513 | 160 | """ | ||
514 | 161 | _raw_style = _keyword_style_registry.get('raw') | ||
515 | 162 | result = b'' | ||
516 | 163 | rest = s | ||
517 | 164 | while True: | ||
518 | 165 | match = _KW_COOKED_RE.search(rest) | ||
519 | 166 | if not match: | ||
520 | 167 | break | ||
521 | 168 | result += rest[:match.start()] | ||
522 | 169 | keyword = match.group(1) | ||
523 | 170 | expansion = _get_from_dicts(keyword_dicts, keyword.decode('ascii')) | ||
524 | 171 | if expansion is None: | ||
525 | 172 | # Unknown expansion - leave as is | ||
526 | 173 | result += match.group(0) | ||
527 | 174 | else: | ||
528 | 175 | result += _raw_style % {b'name': keyword} | ||
529 | 176 | rest = rest[match.end():] | ||
530 | 177 | return result + rest | ||
531 | 178 | |||
532 | 179 | |||
533 | 180 | def expand_keywords(s, keyword_dicts, context=None, encoder=None, style=None): | ||
534 | 181 | """Replace raw style keywords with another style in a string. | ||
535 | 182 | |||
536 | 183 | Note: If the keyword is already in the expanded style, the value is | ||
537 | 184 | not replaced. | ||
538 | 185 | |||
539 | 186 | :param s: the string | ||
540 | 187 | :param keyword_dicts: an iterable of keyword dictionaries. If values | ||
541 | 188 | are callables, they are executed to find the real value. | ||
542 | 189 | :param context: the parameter to pass to callable values | ||
543 | 190 | :param style: the style of expansion to use of None for the default | ||
544 | 191 | :return: the string with keywords expanded | ||
545 | 192 | """ | ||
546 | 193 | _expanded_style = _keyword_style_registry.get(style) | ||
547 | 194 | result = b'' | ||
548 | 195 | rest = s | ||
549 | 196 | while True: | ||
550 | 197 | match = _KW_RAW_RE.search(rest) | ||
551 | 198 | if not match: | ||
552 | 199 | break | ||
553 | 200 | result += rest[:match.start()] | ||
554 | 201 | keyword = match.group(1) | ||
555 | 202 | expansion = _get_from_dicts(keyword_dicts, keyword.decode('ascii')) | ||
556 | 203 | if callable(expansion): | ||
557 | 204 | try: | ||
558 | 205 | expansion = expansion(context) | ||
559 | 206 | except AttributeError as err: | ||
560 | 207 | if 'error' in debug.debug_flags: | ||
561 | 208 | trace.note("error evaluating %s for keyword %s: %s", | ||
562 | 209 | expansion, keyword, err) | ||
563 | 210 | expansion = b"(evaluation error)" | ||
564 | 211 | if isinstance(expansion, text_type): | ||
565 | 212 | expansion = expansion.encode('utf-8') | ||
566 | 213 | if expansion is None: | ||
567 | 214 | # Unknown expansion - leave as is | ||
568 | 215 | result += match.group(0) | ||
569 | 216 | rest = rest[match.end():] | ||
570 | 217 | continue | ||
571 | 218 | if b'$' in expansion: | ||
572 | 219 | # Expansion is not safe to be collapsed later | ||
573 | 220 | expansion = b"(value unsafe to expand)" | ||
574 | 221 | if encoder is not None: | ||
575 | 222 | expansion = encoder(expansion) | ||
576 | 223 | params = {b'name': keyword, b'value': expansion} | ||
577 | 224 | result += _expanded_style % params | ||
578 | 225 | rest = rest[match.end():] | ||
579 | 226 | return result + rest | ||
580 | 227 | |||
581 | 228 | |||
582 | 229 | def _get_from_dicts(dicts, key, default=None): | ||
583 | 230 | """Search a sequence of dictionaries or registries for a key. | ||
584 | 231 | |||
585 | 232 | :return: the value, or default if not found | ||
586 | 233 | """ | ||
587 | 234 | for dict in dicts: | ||
588 | 235 | if key in dict: | ||
589 | 236 | return dict.get(key) | ||
590 | 237 | return default | ||
591 | 238 | |||
592 | 239 | |||
593 | 240 | def _xml_escape(s): | ||
594 | 241 | """Escape a string so it can be included safely in XML/HTML.""" | ||
595 | 242 | # Compile the regular expressions if not already done | ||
596 | 243 | from ... import xml8 | ||
597 | 244 | xml8._ensure_utf8_re() | ||
598 | 245 | # Convert and strip the trailing quote | ||
599 | 246 | return xml8._encode_and_escape(s)[:-1] | ||
600 | 247 | |||
601 | 248 | |||
602 | 249 | def _kw_compressor(chunks, context=None): | ||
603 | 250 | """Filter that replaces keywords with their compressed form.""" | ||
604 | 251 | text = b''.join(chunks) | ||
605 | 252 | return [compress_keywords(text, [keyword_registry])] | ||
606 | 253 | |||
607 | 254 | |||
608 | 255 | def _kw_expander(chunks, context, encoder=None): | ||
609 | 256 | """Keyword expander.""" | ||
610 | 257 | text = b''.join(chunks) | ||
611 | 258 | return [expand_keywords(text, [keyword_registry], context=context, | ||
612 | 259 | encoder=encoder)] | ||
613 | 260 | |||
614 | 261 | |||
615 | 262 | def _normal_kw_expander(chunks, context=None): | ||
616 | 263 | """Filter that replaces keywords with their expanded form.""" | ||
617 | 264 | return _kw_expander(chunks, context) | ||
618 | 265 | |||
619 | 266 | |||
620 | 267 | def _xml_escape_kw_expander(chunks, context=None): | ||
621 | 268 | """Filter that replaces keywords with a form suitable for use in XML.""" | ||
622 | 269 | return _kw_expander(chunks, context, encoder=_xml_escape) | ||
623 | 0 | 270 | ||
624 | === added directory 'breezy/plugins/keywords/tests' | |||
625 | === added file 'breezy/plugins/keywords/tests/__init__.py' | |||
626 | --- breezy/plugins/keywords/tests/__init__.py 1970-01-01 00:00:00 +0000 | |||
627 | +++ breezy/plugins/keywords/tests/__init__.py 2018-11-12 21:28:40 +0000 | |||
628 | @@ -0,0 +1,19 @@ | |||
629 | 1 | # Copyright (C) 2008 Canonical Limited. | ||
630 | 2 | # | ||
631 | 3 | # This program is free software; you can redistribute it and/or modify | ||
632 | 4 | # it under the terms of the GNU General Public License as published by | ||
633 | 5 | # the Free Software Foundation; version 2 of the License. | ||
634 | 6 | # | ||
635 | 7 | # This program is distributed in the hope that it will be useful, | ||
636 | 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
637 | 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
638 | 10 | # GNU General Public License for more details. | ||
639 | 11 | # | ||
640 | 12 | # You should have received a copy of the GNU General Public License | ||
641 | 13 | # along with this program; if not, write to the Free Software | ||
642 | 14 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
643 | 15 | # | ||
644 | 16 | |||
645 | 17 | """Tests for bzr-keywords.""" | ||
646 | 18 | |||
647 | 19 | from __future__ import absolute_import | ||
648 | 0 | 20 | ||
649 | === added file 'breezy/plugins/keywords/tests/test_conversion.py' | |||
650 | --- breezy/plugins/keywords/tests/test_conversion.py 1970-01-01 00:00:00 +0000 | |||
651 | +++ breezy/plugins/keywords/tests/test_conversion.py 2018-11-12 21:28:40 +0000 | |||
652 | @@ -0,0 +1,82 @@ | |||
653 | 1 | # Copyright (C) 2008 Canonical Limited. | ||
654 | 2 | # | ||
655 | 3 | # This program is free software; you can redistribute it and/or modify | ||
656 | 4 | # it under the terms of the GNU General Public License as published by | ||
657 | 5 | # the Free Software Foundation; version 2 of the License. | ||
658 | 6 | # | ||
659 | 7 | # This program is distributed in the hope that it will be useful, | ||
660 | 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
661 | 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
662 | 10 | # GNU General Public License for more details. | ||
663 | 11 | # | ||
664 | 12 | # You should have received a copy of the GNU General Public License | ||
665 | 13 | # along with this program; if not, write to the Free Software | ||
666 | 14 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
667 | 15 | # | ||
668 | 16 | |||
669 | 17 | from __future__ import absolute_import | ||
670 | 18 | |||
671 | 19 | """Tests for keyword expansion/contraction.""" | ||
672 | 20 | |||
673 | 21 | |||
674 | 22 | from .... import tests | ||
675 | 23 | from ..keywords import ( | ||
676 | 24 | compress_keywords, | ||
677 | 25 | expand_keywords, | ||
678 | 26 | ) | ||
679 | 27 | |||
680 | 28 | |||
681 | 29 | # Sample unexpanded and expanded pairs for a keyword dictionary | ||
682 | 30 | _keywords = {'Foo': 'FOO!', 'Bar': 'bar', 'CallMe': lambda c: "now!"} | ||
683 | 31 | _keywords_dicts = [{'Foo': 'FOO!'}, {'Bar': 'bar', 'CallMe': lambda c: "now!"}] | ||
684 | 32 | _samples = [ | ||
685 | 33 | (b'$Foo$', b'$Foo: FOO! $'), | ||
686 | 34 | (b'$Foo', b'$Foo'), | ||
687 | 35 | (b'Foo$', b'Foo$'), | ||
688 | 36 | (b'$Foo$ xyz', b'$Foo: FOO! $ xyz'), | ||
689 | 37 | (b'abc $Foo$', b'abc $Foo: FOO! $'), | ||
690 | 38 | (b'abc $Foo$ xyz', b'abc $Foo: FOO! $ xyz'), | ||
691 | 39 | (b'$Foo$$Bar$', b'$Foo: FOO! $$Bar: bar $'), | ||
692 | 40 | (b'abc $Foo$ xyz $Bar$ qwe', b'abc $Foo: FOO! $ xyz $Bar: bar $ qwe'), | ||
693 | 41 | (b'$Unknown$$Bar$', b'$Unknown$$Bar: bar $'), | ||
694 | 42 | (b'$Unknown: unkn $$Bar$', b'$Unknown: unkn $$Bar: bar $'), | ||
695 | 43 | (b'$Foo$$Unknown$', b'$Foo: FOO! $$Unknown$'), | ||
696 | 44 | (b'$CallMe$', b'$CallMe: now! $'), | ||
697 | 45 | ] | ||
698 | 46 | |||
699 | 47 | |||
700 | 48 | class TestKeywordsConversion(tests.TestCase): | ||
701 | 49 | |||
702 | 50 | def test_compression(self): | ||
703 | 51 | # Test keyword expansion | ||
704 | 52 | for raw, cooked in _samples: | ||
705 | 53 | self.assertEqual(raw, compress_keywords(cooked, [_keywords])) | ||
706 | 54 | |||
707 | 55 | def test_expansion(self): | ||
708 | 56 | # Test keyword expansion | ||
709 | 57 | for raw, cooked in _samples: | ||
710 | 58 | self.assertEqual(cooked, expand_keywords(raw, [_keywords])) | ||
711 | 59 | |||
712 | 60 | def test_expansion_across_multiple_dictionaries(self): | ||
713 | 61 | # Check all still works when keywords in different dictionaries | ||
714 | 62 | for raw, cooked in _samples: | ||
715 | 63 | self.assertEqual(cooked, expand_keywords(raw, _keywords_dicts)) | ||
716 | 64 | |||
717 | 65 | def test_expansion_feedback_when_unsafe(self): | ||
718 | 66 | kw_dict = {'Xxx': 'y$z'} | ||
719 | 67 | self.assertEqual(b'$Xxx: (value unsafe to expand) $', | ||
720 | 68 | expand_keywords(b'$Xxx$', [kw_dict])) | ||
721 | 69 | |||
722 | 70 | def test_expansion_feedback_when_error(self): | ||
723 | 71 | kw_dict = {'Xxx': lambda ctx: ctx.unknownMethod} | ||
724 | 72 | self.assertEqual(b'$Xxx: (evaluation error) $', | ||
725 | 73 | expand_keywords(b'$Xxx$', [kw_dict])) | ||
726 | 74 | |||
727 | 75 | def test_expansion_replaced_if_already_expanded(self): | ||
728 | 76 | s = b'$Xxx: old value $' | ||
729 | 77 | kw_dict = {'Xxx': 'new value'} | ||
730 | 78 | self.assertEqual(b'$Xxx: new value $', expand_keywords(s, [kw_dict])) | ||
731 | 79 | |||
732 | 80 | def test_expansion_ignored_if_already_expanded_but_unknown(self): | ||
733 | 81 | s = b'$Xxx: old value $' | ||
734 | 82 | self.assertEqual(b'$Xxx: old value $', expand_keywords(s, [{}])) | ||
735 | 0 | 83 | ||
736 | === added file 'breezy/plugins/keywords/tests/test_keywords_in_trees.py' | |||
737 | --- breezy/plugins/keywords/tests/test_keywords_in_trees.py 1970-01-01 00:00:00 +0000 | |||
738 | +++ breezy/plugins/keywords/tests/test_keywords_in_trees.py 2018-11-12 21:28:40 +0000 | |||
739 | @@ -0,0 +1,123 @@ | |||
740 | 1 | # Copyright (C) 2009 Canonical Limited. | ||
741 | 2 | # | ||
742 | 3 | # This program is free software; you can redistribute it and/or modify | ||
743 | 4 | # it under the terms of the GNU General Public License as published by | ||
744 | 5 | # the Free Software Foundation; version 2 of the License. | ||
745 | 6 | # | ||
746 | 7 | # This program is distributed in the hope that it will be useful, | ||
747 | 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
748 | 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
749 | 10 | # GNU General Public License for more details. | ||
750 | 11 | # | ||
751 | 12 | # You should have received a copy of the GNU General Public License | ||
752 | 13 | # along with this program; if not, write to the Free Software | ||
753 | 14 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
754 | 15 | # | ||
755 | 16 | |||
756 | 17 | from __future__ import absolute_import | ||
757 | 18 | |||
758 | 19 | """Tests for keyword expansion/contraction in trees.""" | ||
759 | 20 | |||
760 | 21 | ## TODO: add tests for xml_escaped | ||
761 | 22 | |||
762 | 23 | from .... import rules | ||
763 | 24 | from ....tests import TestCaseWithTransport | ||
764 | 25 | from ....workingtree import WorkingTree | ||
765 | 26 | |||
766 | 27 | |||
767 | 28 | # Sample files. We exclude keywords that change from one run to another, | ||
768 | 29 | # TODO: Test Date, Path, Now, User, User-Email | ||
769 | 30 | _sample_text_raw = b""" | ||
770 | 31 | Committer: $Committer$ | ||
771 | 32 | Committer-Name: $Committer-Name$ | ||
772 | 33 | Authors: $Authors$ | ||
773 | 34 | Author1-Email: $Author1-Email$ | ||
774 | 35 | Revision-Id: $Revision-Id$ | ||
775 | 36 | Filename: $Filename$ | ||
776 | 37 | Directory: $Directory$ | ||
777 | 38 | File-Id: $File-Id$ | ||
778 | 39 | """ | ||
779 | 40 | #User: $User$ | ||
780 | 41 | #User-Email: $User-Email$ | ||
781 | 42 | _sample_text_cooked = b""" | ||
782 | 43 | Committer: $Committer: Jane Smith <jane@example.com> $ | ||
783 | 44 | Committer-Name: $Committer-Name: Jane Smith $ | ||
784 | 45 | Authors: $Authors: Sue Smith <sue@example.com> $ | ||
785 | 46 | Author1-Email: $Author1-Email: sue@example.com $ | ||
786 | 47 | Revision-Id: $Revision-Id: rev1-id $ | ||
787 | 48 | Filename: $Filename: file1 $ | ||
788 | 49 | Directory: $Directory: $ | ||
789 | 50 | File-Id: $File-Id: file1-id $ | ||
790 | 51 | """ | ||
791 | 52 | #User: $User: Dave Smith <dave@example.com>$ | ||
792 | 53 | #User-Email: $User-Email: dave@example.com $ | ||
793 | 54 | _sample_binary = _sample_text_raw + b"""\x00""" | ||
794 | 55 | |||
795 | 56 | |||
796 | 57 | class TestKeywordsInTrees(TestCaseWithTransport): | ||
797 | 58 | |||
798 | 59 | def patch_rules_searcher(self, keywords): | ||
799 | 60 | """Patch in a custom rules searcher with a given keywords setting.""" | ||
800 | 61 | if keywords is None: | ||
801 | 62 | WorkingTree._get_rules_searcher = self.real_rules_searcher | ||
802 | 63 | else: | ||
803 | 64 | def custom__rules_searcher(tree, default_searcher): | ||
804 | 65 | return rules._IniBasedRulesSearcher([ | ||
805 | 66 | '[name *]\n', | ||
806 | 67 | 'keywords=%s\n' % keywords, | ||
807 | 68 | ]) | ||
808 | 69 | WorkingTree._get_rules_searcher = custom__rules_searcher | ||
809 | 70 | |||
810 | 71 | def prepare_tree(self, content, keywords=None): | ||
811 | 72 | """Prepare a working tree and commit some content.""" | ||
812 | 73 | def restore_real_rules_searcher(): | ||
813 | 74 | WorkingTree._get_rules_searcher = self.real_rules_searcher | ||
814 | 75 | self.real_rules_searcher = WorkingTree._get_rules_searcher | ||
815 | 76 | self.addCleanup(restore_real_rules_searcher) | ||
816 | 77 | self.patch_rules_searcher(keywords) | ||
817 | 78 | t = self.make_branch_and_tree('tree1') | ||
818 | 79 | # Patch is a custom username | ||
819 | 80 | #def custom_global_config(): | ||
820 | 81 | # config_file = StringIO( | ||
821 | 82 | # "[DEFAULT]\nemail=Dave Smith <dave@example.com>\n") | ||
822 | 83 | # my_config = config.GlobalConfig() | ||
823 | 84 | # my_config._parser = my_config._get_parser(file=config_file) | ||
824 | 85 | # return my_config | ||
825 | 86 | #t.branch.get_config()._get_global_config = custom_global_config | ||
826 | 87 | self.build_tree_contents([('tree1/file1', content)]) | ||
827 | 88 | t.add(['file1'], [b'file1-id']) | ||
828 | 89 | t.commit("add file1", rev_id=b"rev1-id", | ||
829 | 90 | committer="Jane Smith <jane@example.com>", | ||
830 | 91 | authors=["Sue Smith <sue@example.com>"]) | ||
831 | 92 | basis = t.basis_tree() | ||
832 | 93 | basis.lock_read() | ||
833 | 94 | self.addCleanup(basis.unlock) | ||
834 | 95 | return t, basis | ||
835 | 96 | |||
836 | 97 | def assertNewContentForSetting(self, wt, keywords, expected): | ||
837 | 98 | """Clone a working tree and check the convenience content.""" | ||
838 | 99 | self.patch_rules_searcher(keywords) | ||
839 | 100 | wt2 = wt.controldir.sprout('tree-%s' % keywords).open_workingtree() | ||
840 | 101 | # To see exactly what got written to disk, we need an unfiltered read | ||
841 | 102 | content = wt2.get_file_text('file1', filtered=False) | ||
842 | 103 | self.assertEqual(expected, content) | ||
843 | 104 | |||
844 | 105 | def assertContent(self, wt, basis, expected_raw, expected_cooked): | ||
845 | 106 | """Check the committed content and content in cloned trees.""" | ||
846 | 107 | basis_content = basis.get_file_text('file1') | ||
847 | 108 | self.assertEqualDiff(expected_raw, basis_content) | ||
848 | 109 | self.assertNewContentForSetting(wt, None, expected_raw) | ||
849 | 110 | self.assertNewContentForSetting(wt, 'on', expected_cooked) | ||
850 | 111 | self.assertNewContentForSetting(wt, 'off', expected_raw) | ||
851 | 112 | |||
852 | 113 | def test_keywords_no_rules(self): | ||
853 | 114 | wt, basis = self.prepare_tree(_sample_text_raw) | ||
854 | 115 | self.assertContent(wt, basis, _sample_text_raw, _sample_text_cooked) | ||
855 | 116 | |||
856 | 117 | def test_keywords_on(self): | ||
857 | 118 | wt, basis = self.prepare_tree(_sample_text_raw, keywords='on') | ||
858 | 119 | self.assertContent(wt, basis, _sample_text_raw, _sample_text_cooked) | ||
859 | 120 | |||
860 | 121 | def test_keywords_off(self): | ||
861 | 122 | wt, basis = self.prepare_tree(_sample_text_raw, keywords='off') | ||
862 | 123 | self.assertContent(wt, basis, _sample_text_raw, _sample_text_cooked) |