Status: | Merged |
---|---|
Approved by: | Martin Packman |
Approved revision: | no longer in the source branch. |
Merge reported by: | The Breezy Bot |
Merged at revision: | not available |
Proposed branch: | lp:~gz/brz/py3_rio |
Merge into: | lp:brz |
Prerequisite: | lp:~gz/brz/py3_bootstrap2 |
Diff against target: |
435 lines (+86/-82) 3 files modified
breezy/rio.py (+31/-29) breezy/tests/test__rio.py (+24/-15) breezy/tests/test_rio.py (+31/-38) |
To merge this branch: | bzr merge lp:~gz/brz/py3_rio |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij | Approve | ||
Review via email: mp+325459@code.launchpad.net |
Commit message
Make rio work with Python 3
Description of the change
Change code and tests for the rio module to work with Python 3.
For now, the _rio_pyx helper is still 2.7 only.
To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'breezy/rio.py' |
2 | --- breezy/rio.py 2017-06-10 01:57:00 +0000 |
3 | +++ breezy/rio.py 2017-06-11 17:04:24 +0000 |
4 | @@ -174,17 +174,19 @@ |
5 | # max() complains if sequence is empty |
6 | return [] |
7 | result = [] |
8 | - for tag, value in self.items: |
9 | - if value == '': |
10 | - result.append(tag.encode('ascii') + b': \n') |
11 | - elif '\n' in value: |
12 | + for text_tag, text_value in self.items: |
13 | + tag = text_tag.encode('ascii') |
14 | + value = text_value.encode('utf-8') |
15 | + if value == b'': |
16 | + result.append(tag + b': \n') |
17 | + elif b'\n' in value: |
18 | # don't want splitlines behaviour on empty lines |
19 | - val_lines = value.split('\n') |
20 | - result.append(tag + b': ' + val_lines[0].encode('utf-8') + b'\n') |
21 | + val_lines = value.split(b'\n') |
22 | + result.append(tag + b': ' + val_lines[0] + b'\n') |
23 | for line in val_lines[1:]: |
24 | - result.append(b'\t' + line.encode('utf-8') + b'\n') |
25 | + result.append(b'\t' + line + b'\n') |
26 | else: |
27 | - result.append(tag.encode('ascii') + b': ' + value.encode('utf-8') + b'\n') |
28 | + result.append(tag + b': ' + value + b'\n') |
29 | return result |
30 | |
31 | def to_string(self): |
32 | @@ -302,61 +304,61 @@ |
33 | max_rio_width = max_width - 4 |
34 | lines = [] |
35 | for pline in stanza.to_lines(): |
36 | - for line in pline.split('\n')[:-1]: |
37 | - line = re.sub('\\\\', '\\\\\\\\', line) |
38 | + for line in pline.split(b'\n')[:-1]: |
39 | + line = re.sub(b'\\\\', b'\\\\\\\\', line) |
40 | while len(line) > 0: |
41 | partline = line[:max_rio_width] |
42 | line = line[max_rio_width:] |
43 | - if len(line) > 0 and line[0] != [' ']: |
44 | + if len(line) > 0 and line[:1] != [b' ']: |
45 | break_index = -1 |
46 | - break_index = partline.rfind(' ', -20) |
47 | + break_index = partline.rfind(b' ', -20) |
48 | if break_index < 3: |
49 | - break_index = partline.rfind('-', -20) |
50 | + break_index = partline.rfind(b'-', -20) |
51 | break_index += 1 |
52 | if break_index < 3: |
53 | - break_index = partline.rfind('/', -20) |
54 | + break_index = partline.rfind(b'/', -20) |
55 | if break_index >= 3: |
56 | line = partline[break_index:] + line |
57 | partline = partline[:break_index] |
58 | if len(line) > 0: |
59 | - line = ' ' + line |
60 | - partline = re.sub('\r', '\\\\r', partline) |
61 | + line = b' ' + line |
62 | + partline = re.sub(b'\r', b'\\\\r', partline) |
63 | blank_line = False |
64 | if len(line) > 0: |
65 | - partline += '\\' |
66 | - elif re.search(' $', partline): |
67 | - partline += '\\' |
68 | + partline += b'\\' |
69 | + elif re.search(b' $', partline): |
70 | + partline += b'\\' |
71 | blank_line = True |
72 | - lines.append('# ' + partline + '\n') |
73 | + lines.append(b'# ' + partline + b'\n') |
74 | if blank_line: |
75 | - lines.append('# \n') |
76 | + lines.append(b'# \n') |
77 | return lines |
78 | |
79 | |
80 | def _patch_stanza_iter(line_iter): |
81 | - map = {'\\\\': '\\', |
82 | - '\\r' : '\r', |
83 | - '\\\n': ''} |
84 | + map = {b'\\\\': b'\\', |
85 | + b'\\r' : b'\r', |
86 | + b'\\\n': b''} |
87 | def mapget(match): |
88 | return map[match.group(0)] |
89 | |
90 | last_line = None |
91 | for line in line_iter: |
92 | - if line.startswith('# '): |
93 | + if line.startswith(b'# '): |
94 | line = line[2:] |
95 | - elif line.startswith('#'): |
96 | + elif line.startswith(b'#'): |
97 | line = line[1:] |
98 | else: |
99 | raise ValueError("bad line %r" % (line,)) |
100 | if last_line is not None and len(line) > 2: |
101 | line = line[2:] |
102 | - line = re.sub('\r', '', line) |
103 | - line = re.sub('\\\\(.|\n)', mapget, line) |
104 | + line = re.sub(b'\r', b'', line) |
105 | + line = re.sub(b'\\\\(.|\n)', mapget, line) |
106 | if last_line is None: |
107 | last_line = line |
108 | else: |
109 | last_line += line |
110 | - if last_line[-1] == '\n': |
111 | + if last_line[-1:] == b'\n': |
112 | yield last_line |
113 | last_line = None |
114 | if last_line is not None: |
115 | |
116 | === modified file 'breezy/tests/test__rio.py' |
117 | --- breezy/tests/test__rio.py 2017-05-23 14:08:03 +0000 |
118 | +++ breezy/tests/test__rio.py 2017-06-11 17:04:24 +0000 |
119 | @@ -16,10 +16,15 @@ |
120 | |
121 | """Tests for _rio_*.""" |
122 | |
123 | -from breezy import ( |
124 | +from __future__ import absolute_import |
125 | + |
126 | +from .. import ( |
127 | rio, |
128 | tests, |
129 | ) |
130 | +from ..sixish import ( |
131 | + text_type, |
132 | + ) |
133 | |
134 | |
135 | def load_tests(loader, standard_tests, pattern): |
136 | @@ -43,7 +48,7 @@ |
137 | |
138 | def test_no_colon(self): |
139 | self.assertFalse(self.module._valid_tag("foo:bla")) |
140 | - |
141 | + |
142 | def test_type_error(self): |
143 | self.assertRaises(TypeError, self.module._valid_tag, 423) |
144 | |
145 | @@ -51,7 +56,11 @@ |
146 | self.assertFalse(self.module._valid_tag("")) |
147 | |
148 | def test_unicode(self): |
149 | - self.assertRaises(TypeError, self.module._valid_tag, u"foo") |
150 | + if text_type is str: |
151 | + # When str is a unicode type, it is valid for a tag |
152 | + self.assertTrue(self.module._valid_tag(u"foo")) |
153 | + else: |
154 | + self.assertRaises(TypeError, self.module._valid_tag, u"foo") |
155 | |
156 | def test_non_ascii_char(self): |
157 | self.assertFalse(self.module._valid_tag("\xb5")) |
158 | @@ -67,7 +76,7 @@ |
159 | if s is not None: |
160 | for tag, value in s.iter_pairs(): |
161 | self.assertIsInstance(tag, str) |
162 | - self.assertIsInstance(value, unicode) |
163 | + self.assertIsInstance(value, text_type) |
164 | |
165 | def assertReadStanzaRaises(self, exception, line_iter): |
166 | self.assertRaises(exception, self.module._read_stanza_utf8, line_iter) |
167 | @@ -79,34 +88,34 @@ |
168 | self.assertReadStanza(None, []) |
169 | |
170 | def test_none(self): |
171 | - self.assertReadStanza(None, [""]) |
172 | + self.assertReadStanza(None, [b""]) |
173 | |
174 | def test_simple(self): |
175 | - self.assertReadStanza(rio.Stanza(foo="bar"), ["foo: bar\n", ""]) |
176 | + self.assertReadStanza(rio.Stanza(foo="bar"), [b"foo: bar\n", b""]) |
177 | |
178 | def test_multi_line(self): |
179 | - self.assertReadStanza(rio.Stanza(foo="bar\nbla"), |
180 | - ["foo: bar\n", "\tbla\n"]) |
181 | + self.assertReadStanza( |
182 | + rio.Stanza(foo="bar\nbla"), [b"foo: bar\n", b"\tbla\n"]) |
183 | |
184 | def test_repeated(self): |
185 | s = rio.Stanza() |
186 | s.add("foo", "bar") |
187 | s.add("foo", "foo") |
188 | - self.assertReadStanza(s, ["foo: bar\n", "foo: foo\n"]) |
189 | + self.assertReadStanza(s, [b"foo: bar\n", b"foo: foo\n"]) |
190 | |
191 | def test_invalid_early_colon(self): |
192 | - self.assertReadStanzaRaises(ValueError, ["f:oo: bar\n"]) |
193 | + self.assertReadStanzaRaises(ValueError, [b"f:oo: bar\n"]) |
194 | |
195 | def test_invalid_tag(self): |
196 | - self.assertReadStanzaRaises(ValueError, ["f%oo: bar\n"]) |
197 | + self.assertReadStanzaRaises(ValueError, [b"f%oo: bar\n"]) |
198 | |
199 | def test_continuation_too_early(self): |
200 | - self.assertReadStanzaRaises(ValueError, ["\tbar\n"]) |
201 | + self.assertReadStanzaRaises(ValueError, [b"\tbar\n"]) |
202 | |
203 | def test_large(self): |
204 | - value = "bla" * 9000 |
205 | + value = b"bla" * 9000 |
206 | self.assertReadStanza(rio.Stanza(foo=value), |
207 | - ["foo: %s\n" % value]) |
208 | + [b"foo: %s\n" % value]) |
209 | |
210 | def test_non_ascii_char(self): |
211 | self.assertReadStanza(rio.Stanza(foo=u"n\xe5me"), |
212 | @@ -123,7 +132,7 @@ |
213 | if s is not None: |
214 | for tag, value in s.iter_pairs(): |
215 | self.assertIsInstance(tag, str) |
216 | - self.assertIsInstance(value, unicode) |
217 | + self.assertIsInstance(value, text_type) |
218 | |
219 | def assertReadStanzaRaises(self, exception, line_iter): |
220 | self.assertRaises(exception, self.module._read_stanza_unicode, |
221 | |
222 | === modified file 'breezy/tests/test_rio.py' |
223 | --- breezy/tests/test_rio.py 2017-05-24 19:44:00 +0000 |
224 | +++ breezy/tests/test_rio.py 2017-06-11 17:04:24 +0000 |
225 | @@ -51,26 +51,18 @@ |
226 | self.assertEqual(s.get('number'), '42') |
227 | self.assertEqual(s.get('name'), 'fred') |
228 | |
229 | - def test_value_checks(self): |
230 | - """rio checks types on construction""" |
231 | - # these aren't enforced at construction time |
232 | - ## self.assertRaises(ValueError, |
233 | - ## Stanza, complex=42 + 3j) |
234 | - ## self.assertRaises(ValueError, |
235 | - ## Stanza, several=range(10)) |
236 | - |
237 | def test_empty_value(self): |
238 | """Serialize stanza with empty field""" |
239 | s = Stanza(empty='') |
240 | - self.assertEqualDiff(s.to_string(), |
241 | - "empty: \n") |
242 | + self.assertEquals(s.to_string(), |
243 | + b"empty: \n") |
244 | |
245 | def test_to_lines(self): |
246 | """Write simple rio stanza to string""" |
247 | s = Stanza(number='42', name='fred') |
248 | self.assertEqual(list(s.to_lines()), |
249 | - ['name: fred\n', |
250 | - 'number: 42\n']) |
251 | + [b'name: fred\n', |
252 | + b'number: 42\n']) |
253 | |
254 | def test_as_dict(self): |
255 | """Convert rio Stanza to dictionary""" |
256 | @@ -84,18 +76,18 @@ |
257 | s = Stanza(a_thing='something with "quotes like \\"this\\""', number='42', name='fred') |
258 | s.write(tmpf) |
259 | tmpf.seek(0) |
260 | - self.assertEqualDiff(tmpf.read(), r''' |
261 | -a_thing: something with "quotes like \"this\"" |
262 | + self.assertEqual(tmpf.read(), b'''\ |
263 | +a_thing: something with "quotes like \\"this\\"" |
264 | name: fred |
265 | number: 42 |
266 | -'''[1:]) |
267 | +''') |
268 | |
269 | def test_multiline_string(self): |
270 | tmpf = TemporaryFile() |
271 | s = Stanza(motto="war is peace\nfreedom is slavery\nignorance is strength") |
272 | s.write(tmpf) |
273 | tmpf.seek(0) |
274 | - self.assertEqualDiff(tmpf.read(), '''\ |
275 | + self.assertEqual(tmpf.read(), b'''\ |
276 | motto: war is peace |
277 | \tfreedom is slavery |
278 | \tignorance is strength |
279 | @@ -106,7 +98,7 @@ |
280 | |
281 | def test_read_stanza(self): |
282 | """Load stanza from string""" |
283 | - lines = """\ |
284 | + lines = b"""\ |
285 | revision: mbp@sourcefrog.net-123-abc |
286 | timestamp: 1130653962 |
287 | timezone: 36000 |
288 | @@ -114,7 +106,7 @@ |
289 | """.splitlines(True) |
290 | s = read_stanza(lines) |
291 | self.assertTrue('revision' in s) |
292 | - self.assertEqualDiff(s.get('revision'), 'mbp@sourcefrog.net-123-abc') |
293 | + self.assertEqual(s.get('revision'), 'mbp@sourcefrog.net-123-abc') |
294 | self.assertEqual(list(s.iter_pairs()), |
295 | [('revision', 'mbp@sourcefrog.net-123-abc'), |
296 | ('timestamp', '1130653962'), |
297 | @@ -136,13 +128,13 @@ |
298 | def test_backslash(self): |
299 | s = Stanza(q='\\') |
300 | t = s.to_string() |
301 | - self.assertEqualDiff(t, 'q: \\\n') |
302 | + self.assertEqual(t, b'q: \\\n') |
303 | s2 = read_stanza(s.to_lines()) |
304 | self.assertEqual(s, s2) |
305 | |
306 | def test_blank_line(self): |
307 | s = Stanza(none='', one='\n', two='\n\n') |
308 | - self.assertEqualDiff(s.to_string(), """\ |
309 | + self.assertEqual(s.to_string(), b"""\ |
310 | none:\x20 |
311 | one:\x20 |
312 | \t |
313 | @@ -155,7 +147,7 @@ |
314 | |
315 | def test_whitespace_value(self): |
316 | s = Stanza(space=' ', tabs='\t\t\t', combo='\n\t\t\n') |
317 | - self.assertEqualDiff(s.to_string(), """\ |
318 | + self.assertEqual(s.to_string(), b"""\ |
319 | combo:\x20 |
320 | \t\t\t |
321 | \t |
322 | @@ -192,16 +184,16 @@ |
323 | |
324 | def test_read_nul_byte(self): |
325 | """File consisting of a nul byte causes an error.""" |
326 | - self.assertRaises(ValueError, read_stanza, ['\0']) |
327 | + self.assertRaises(ValueError, read_stanza, [b'\0']) |
328 | |
329 | def test_read_nul_bytes(self): |
330 | """File consisting of many nul bytes causes an error.""" |
331 | - self.assertRaises(ValueError, read_stanza, ['\0' * 100]) |
332 | + self.assertRaises(ValueError, read_stanza, [b'\0' * 100]) |
333 | |
334 | def test_read_iter(self): |
335 | """Read several stanzas from file""" |
336 | tmpf = TemporaryFile() |
337 | - tmpf.write("""\ |
338 | + tmpf.write(b"""\ |
339 | version_header: 1 |
340 | |
341 | name: foo |
342 | @@ -222,7 +214,7 @@ |
343 | def test_read_several(self): |
344 | """Read several stanzas from file""" |
345 | tmpf = TemporaryFile() |
346 | - tmpf.write("""\ |
347 | + tmpf.write(b"""\ |
348 | version_header: 1 |
349 | |
350 | name: foo |
351 | @@ -242,8 +234,9 @@ |
352 | s = read_stanza(tmpf) |
353 | self.assertEqual(s, Stanza(name="foo", val='123')) |
354 | s = read_stanza(tmpf) |
355 | - self.assertEqualDiff(s.get('name'), 'quoted') |
356 | - self.assertEqualDiff(s.get('address'), ' "Willowglen"\n 42 Wallaby Way\n Sydney') |
357 | + self.assertEqual(s.get('name'), 'quoted') |
358 | + self.assertEqual( |
359 | + s.get('address'), ' "Willowglen"\n 42 Wallaby Way\n Sydney') |
360 | s = read_stanza(tmpf) |
361 | self.assertEqual(s, Stanza(name="bar", val='129319')) |
362 | s = read_stanza(tmpf) |
363 | @@ -266,7 +259,7 @@ |
364 | |
365 | def test_tricky_quoted(self): |
366 | tmpf = TemporaryFile() |
367 | - tmpf.write('''\ |
368 | + tmpf.write(b'''\ |
369 | s: "one" |
370 | |
371 | s:\x20 |
372 | @@ -316,7 +309,7 @@ |
373 | stanza = read_stanza(tmpf) |
374 | self.rio_file_stanzas([stanza]) |
375 | self.assertEqual(len(stanza), 1) |
376 | - self.assertEqualDiff(stanza.get('s'), expected) |
377 | + self.assertEqual(stanza.get('s'), expected) |
378 | |
379 | def test_write_empty_stanza(self): |
380 | """Write empty stanza""" |
381 | @@ -339,7 +332,7 @@ |
382 | self.assertEqual(s.get('foo'), uni_data) |
383 | raw_lines = s.to_lines() |
384 | self.assertEqual(raw_lines, |
385 | - ['foo: ' + uni_data.encode('utf-8') + '\n']) |
386 | + [b'foo: ' + uni_data.encode('utf-8') + b'\n']) |
387 | new_s = read_stanza(raw_lines) |
388 | self.assertEqual(new_s.get('foo'), uni_data) |
389 | |
390 | @@ -356,8 +349,8 @@ |
391 | s = Stanza(foo=uni_data) |
392 | parent_stanza = Stanza(child=s.to_unicode()) |
393 | raw_lines = parent_stanza.to_lines() |
394 | - self.assertEqual(['child: foo: ' + uni_data.encode('utf-8') + '\n', |
395 | - '\t\n', |
396 | + self.assertEqual([b'child: foo: ' + uni_data.encode('utf-8') + b'\n', |
397 | + b'\t\n', |
398 | ], raw_lines) |
399 | new_parent = read_stanza(raw_lines) |
400 | child_text = new_parent.get('child') |
401 | @@ -368,9 +361,9 @@ |
402 | def mail_munge(self, lines, dos_nl=True): |
403 | new_lines = [] |
404 | for line in lines: |
405 | - line = re.sub(' *\n', '\n', line) |
406 | + line = re.sub(b' *\n', b'\n', line) |
407 | if dos_nl: |
408 | - line = re.sub('([^\r])\n', '\\1\r\n', line) |
409 | + line = re.sub(b'([^\r])\n', b'\\1\r\n', line) |
410 | new_lines.append(line) |
411 | return new_lines |
412 | |
413 | @@ -378,7 +371,7 @@ |
414 | stanza = Stanza(data='#\n\r\\r ', space=' ' * 255, hash='#' * 255) |
415 | lines = rio.to_patch_lines(stanza) |
416 | for line in lines: |
417 | - self.assertContainsRe(line, '^# ') |
418 | + self.assertContainsRe(line, b'^# ') |
419 | self.assertTrue(72 >= len(line)) |
420 | for line in rio.to_patch_lines(stanza, max_width=12): |
421 | self.assertTrue(12 >= len(line)) |
422 | @@ -393,10 +386,10 @@ |
423 | def test_patch_rio_linebreaks(self): |
424 | stanza = Stanza(breaktest='linebreak -/'*30) |
425 | self.assertContainsRe(rio.to_patch_lines(stanza, 71)[0], |
426 | - 'linebreak\\\\\n') |
427 | + b'linebreak\\\\\n') |
428 | stanza = Stanza(breaktest='linebreak-/'*30) |
429 | self.assertContainsRe(rio.to_patch_lines(stanza, 70)[0], |
430 | - 'linebreak-\\\\\n') |
431 | + b'linebreak-\\\\\n') |
432 | stanza = Stanza(breaktest='linebreak/'*30) |
433 | self.assertContainsRe(rio.to_patch_lines(stanza, 70)[0], |
434 | - 'linebreak\\\\\n') |
435 | + b'linebreak\\\\\n') |