Merge ~cjwatson/pygettextpo:py3 into pygettextpo:main

Proposed by Colin Watson
Status: Merged
Merged at revision: 62673d4f42af7d8b14aa7c88e0f8be633d695a32
Proposed branch: ~cjwatson/pygettextpo:py3
Merge into: pygettextpo:main
Diff against target: 783 lines (+198/-151)
6 files modified
.gitignore (+2/-1)
Makefile (+1/-1)
README (+0/-5)
gettextpo.c (+111/-94)
setup.py (+11/-3)
test_gettextpo.py (+73/-47)
Reviewer Review Type Date Requested Status
Tom Wardill (community) Approve
Review via email: mp+391412@code.launchpad.net

Commit message

Port to Python 3

Description of the change

If this looks OK, then I plan to release this to PyPI, which will allow us to consume it from Launchpad in the normal way and fix https://bugs.launchpad.net/launchpad/+bug/1016333.

To post a comment you must log in.
Revision history for this message
Tom Wardill (twom) wrote :

I've built this and poked some strings at it, on py 3.5 and py3.8, seems to work :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2index 1ff8f33..d0f7d29 100644
3--- a/.gitignore
4+++ b/.gitignore
5@@ -1,5 +1,6 @@
6 gettextpo.html
7-gettextpo.so
8+gettextpo*.so
9+/*.egg-info
10 /build
11 /dist
12 /MANIFEST
13diff --git a/Makefile b/Makefile
14index bcff939..c42eb05 100644
15--- a/Makefile
16+++ b/Makefile
17@@ -10,7 +10,7 @@ check: all
18 $(PYTHON) test_gettextpo.py -v
19
20 clean:
21- rm -rf build dist gettextpo.so gettextpo.html
22+ rm -rf build dist gettextpo*.so gettextpo.html
23
24 gettextpo.so: setup.py gettextpo.c
25 $(PYTHON) setup.py build_ext -i
26diff --git a/README b/README
27index 5fdef4e..f690fe3 100644
28--- a/README
29+++ b/README
30@@ -6,11 +6,6 @@ GPL v3 or later as of October 2007 (before that it was GPL v2 or
31 later) [1]. Therefore, any code using this module must be under a license
32 compatible with the GPL v2, v3, or any later version.
33
34-Note: when this code was first added to Launchpad, it made use of
35-interfaces added in gettext version 0.14.2, which is not yet widely
36-distributed (e.g. not in Hoary), and so a trimmed down copy of the
37-code is included here. That code probably isn't needed here anymore.
38-
39 See also http://code.google.com/p/pygettextpo/, which is probably a
40 duplicate project, though it has not had a public release yet (as of
41 26 May 2009).
42diff --git a/gettextpo.c b/gettextpo.c
43index d125726..451e284 100644
44--- a/gettextpo.c
45+++ b/gettextpo.c
46@@ -10,11 +10,26 @@
47
48 #include <gettext-po.h>
49
50-#define MIN_REQUIRED_GETTEXTPO_VERSION 0x000E02
51-#define MIN_MSGCTXT_GETTEXTPO_VERSION 0x000F00
52+#define MIN_REQUIRED_GETTEXTPO_VERSION 0x000F00
53
54 #if LIBGETTEXTPO_VERSION < MIN_REQUIRED_GETTEXTPO_VERSION
55-# error "this module requires gettext >= 0.14.2"
56+# error "this module requires gettext >= 0.15"
57+#endif
58+
59+#if PY_MAJOR_VERSION >= 3
60+# define PyNativeString_FromString(s) PyUnicode_FromString(s)
61+# define PyNativeString_FromFormat(format, ...) \
62+ PyUnicode_FromFormat(format, __VA_ARGS__)
63+# if PY_VERSION_HEX >= 0x03030000
64+# define PyNativeString_Size(s) PyUnicode_GetLength(s)
65+# else
66+# define PyNativeString_Size(s) PyUnicode_GetSize(s)
67+# endif
68+#else
69+# define PyNativeString_FromString(s) PyBytes_FromString(s)
70+# define PyNativeString_FromFormat(format, ...) \
71+ PyString_FromFormat(format, __VA_ARGS__)
72+# define PyNativeString_Size(s) PyBytes_Size(s)
73 #endif
74
75 static PyObject *gettextpo_error = NULL;
76@@ -42,22 +57,22 @@ static PyTypeObject PyPoMessage_Type;
77 /* ---------------------------- */
78
79 /**
80- * get_pystring_from_pyobject:
81+ * get_pybytes_from_pyobject:
82 * @object: a PyObject that represents a PyUnicode object.
83 *
84- * Gets a PyString that represents the @object as UTF-8. If the
85- * object is a PyString, then assume it is in UTF-8 and return it.
86+ * Gets a PyBytes that represents the @object as UTF-8. If the
87+ * object is a PyBytes, then assume it is in UTF-8 and return it.
88 *
89- * Return value: a new #PyString object or NULL if there is any error
90+ * Return value: a new #PyBytes object or NULL if there is any error
91 * in which case, PyErr is set.
92 **/
93 static PyObject *
94-get_pystring_from_pyobject(PyObject *object)
95+get_pybytes_from_pyobject(PyObject *object)
96 {
97 PyObject *unicode;
98 PyObject *string;
99
100- if (PyString_Check(object)) {
101+ if (PyBytes_Check(object)) {
102 Py_INCREF(object);
103 return object;
104 }
105@@ -96,38 +111,6 @@ typedef struct {
106 static ErrorClosure *error_closure = NULL;
107 static PyThread_type_lock error_closure_lock= NULL;
108
109-/* The error handler API changed in gettext-0.15 ... */
110-#if LIBGETTEXTPO_VERSION < 0x000F00
111-
112-static void
113-error_handler_error(int status, int errnum, const char *format, ...)
114-{
115- va_list args;
116- PyObject *str;
117-
118- /* printf the string */
119- va_start(args, format);
120- str = PyString_FromFormatV(format, args);
121- va_end(args);
122-
123- /* store the errors in a list, and as a string */
124- PyList_Append(error_closure->error_list,
125- Py_BuildValue("(sis)", "error", errnum, str));
126- PyString_ConcatAndDel(&(error_closure->error_string), str);
127-
128- /* if status is nonzero, we are not meant to return */
129- if (status != 0) {
130- fprintf(stderr, "error_handler_error: status!=0, longjmp'ing out\n");
131- longjmp(error_closure->env, 1);
132- }
133-}
134-
135-struct po_error_handler error_handler = {
136- .error = error_handler_error,
137-};
138-
139-#else /* LIBGETTEXTPO_VERSION >= 0.15.0*/
140-
141 static void
142 error_handler_xerror(int severity, po_message_t message,
143 const char *filename, size_t lineno, size_t column,
144@@ -136,12 +119,38 @@ error_handler_xerror(int severity, po_message_t message,
145 PyObject *str;
146
147 /* printf the string */
148- str = PyString_FromString(message_text);
149+#if PY_MAJOR_VERSION >= 3
150+ {
151+ size_t size = strlen(message_text);
152+ if (size > PY_SSIZE_T_MAX)
153+ /* We can't raise an exception here, so just truncate it. */
154+ size = PY_SSIZE_T_MAX;
155+ str = PyUnicode_DecodeUTF8(message_text, (Py_ssize_t)size, "replace");
156+ }
157+#else
158+ str = PyBytes_FromString(message_text);
159+#endif
160
161 /* store the errors in a list, and as a string */
162- PyList_Append(error_closure->error_list, Py_BuildValue("(sis)",
163+ PyList_Append(error_closure->error_list, Py_BuildValue("(siO)",
164 severity == PO_SEVERITY_WARNING ? "warning" : "error", 0, str));
165- PyString_ConcatAndDel(&(error_closure->error_string), str);
166+#if PY_MAJOR_VERSION >= 3
167+ {
168+ PyObject *old_error_string = error_closure->error_string;
169+ if (PyUnicode_GET_LENGTH(error_closure->error_string)) {
170+ error_closure->error_string = PyUnicode_FromFormat(
171+ "%U\n%U", error_closure->error_string, str);
172+ Py_XDECREF(str);
173+ } else
174+ error_closure->error_string = str;
175+ Py_XDECREF(old_error_string);
176+ }
177+#else
178+ if (PyBytes_GET_SIZE(error_closure->error_string))
179+ PyBytes_ConcatAndDel(&(error_closure->error_string),
180+ PyBytes_FromString("\n"));
181+ PyBytes_ConcatAndDel(&(error_closure->error_string), str);
182+#endif
183
184 /* if it is a fatal error, we are not meant to return */
185 if (severity == PO_SEVERITY_FATAL_ERROR) {
186@@ -155,8 +164,6 @@ struct po_xerror_handler error_handler = {
187 .xerror = error_handler_xerror,
188 };
189
190-#endif /* LIBGETTEXTPO_VERSION */
191-
192 /* ---------------------------- */
193
194 static int
195@@ -174,7 +181,7 @@ pypo_file_init(PyPoFile *self, PyObject *args, PyObject *kwargs)
196 ErrorClosure closure;
197
198 closure.error_list = PyList_New(0);
199- closure.error_string = PyString_FromString("");
200+ closure.error_string = PyNativeString_FromString("");
201
202 PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);
203 error_closure = &closure;
204@@ -186,7 +193,7 @@ pypo_file_init(PyPoFile *self, PyObject *args, PyObject *kwargs)
205 error_closure = NULL;
206 PyThread_release_lock(error_closure_lock);
207
208- if (PyString_Size(closure.error_string) != 0) {
209+ if (PyNativeString_Size(closure.error_string) != 0) {
210 PyObject *exc;
211
212 /* set up the exception */
213@@ -262,7 +269,7 @@ pypo_file_write(PyPoFile *self, PyObject *args)
214 return NULL;
215
216 closure.error_list = PyList_New(0);
217- closure.error_string = PyString_FromString("");
218+ closure.error_string = PyNativeString_FromString("");
219
220 PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);
221 error_closure = &closure;
222@@ -274,7 +281,7 @@ pypo_file_write(PyPoFile *self, PyObject *args)
223 error_closure = NULL;
224 PyThread_release_lock(error_closure_lock);
225
226- if (PyString_Size(closure.error_string) != 0) {
227+ if (PyNativeString_Size(closure.error_string) != 0) {
228 PyObject *exc;
229
230 /* set up the exception */
231@@ -309,7 +316,7 @@ pypo_file_domain_header(PyPoFile *self, PyObject *args)
232
233 header = po_file_domain_header(self->pofile, domain);
234 if (header) {
235- return PyString_FromString(header);
236+ return PyBytes_FromString(header);
237 } else {
238 Py_RETURN_NONE;
239 }
240@@ -335,7 +342,7 @@ pypo_file_domains(PyPoFile *self, void *closure)
241 ret = PyList_New(0);
242 domains = po_file_domains(self->pofile);
243 while (domains && *domains) {
244- PyObject *item = PyString_FromString(*domains);
245+ PyObject *item = PyBytes_FromString(*domains);
246
247 PyList_Append(ret, item);
248 Py_DECREF(item);
249@@ -356,8 +363,7 @@ PyDoc_STRVAR(doc_PyPoFile_Type,
250 "filename");
251
252 static PyTypeObject PyPoFile_Type = {
253- PyObject_HEAD_INIT(NULL)
254- 0, /* ob_size */
255+ PyVarObject_HEAD_INIT(NULL, 0)
256 "gettextpo.PoFile", /* tp_name */
257 sizeof(PyPoFile), /* tp_basicsize */
258 .tp_flags = Py_TPFLAGS_DEFAULT,
259@@ -449,8 +455,7 @@ PyDoc_STRVAR(doc_PyPoMessageIterator_Type,
260 "Iterator type for PoFile. Iterates over the PoFile's messages.");
261
262 static PyTypeObject PyPoMessageIterator_Type = {
263- PyObject_HEAD_INIT(NULL)
264- 0,
265+ PyVarObject_HEAD_INIT(NULL, 0)
266 "gettextpo.PoMessageIterator",
267 sizeof(PyPoMessageIterator),
268 .tp_flags = Py_TPFLAGS_DEFAULT,
269@@ -511,8 +516,8 @@ pypo_message_repr(PyPoMessage *self)
270 if (self->msg)
271 msgid = po_message_msgid(self->msg);
272
273- return PyString_FromFormat("<PoMessage for '%s'>",
274- msgid ? msgid : "(null)");
275+ return PyNativeString_FromFormat("<PoMessage for '%s'>",
276+ msgid ? msgid : "(null)");
277 }
278
279 static PyObject *
280@@ -550,13 +555,13 @@ _message_set_field(PyPoMessage *self, PyObject *args, const char *field,
281 if (object == Py_None) {
282 (*setter)(self->msg, "");
283 } else {
284- string = get_pystring_from_pyobject(object);
285+ string = get_pybytes_from_pyobject(object);
286
287 if (string == NULL)
288 /* Got an exception */
289 return NULL;
290 else {
291- value = PyString_AsString(string);
292+ value = PyBytes_AsString(string);
293 (*setter)(self->msg, value);
294 Py_DECREF(string);
295 }
296@@ -565,9 +570,6 @@ _message_set_field(PyPoMessage *self, PyObject *args, const char *field,
297 Py_RETURN_NONE;
298 }
299
300-/* msgctxt support was added in 0.15. */
301-#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
302-
303 PyDoc_STRVAR(doc_pypo_message_set_msgctxt,
304 "M.set_msgctxt(msgctxt) -> None. Set the msgctxt for this PoMessage");
305
306@@ -577,8 +579,6 @@ pypo_message_set_msgctxt(PyPoMessage *self, PyObject *args)
307 return _message_set_field(self, args, "O:set_msgctxt",
308 &po_message_set_msgctxt);
309 }
310-#endif
311-
312
313 PyDoc_STRVAR(doc_pypo_message_set_msgid,
314 "M.set_msgid(msgid) -> None. Set the msgid for this PoMessage");
315@@ -637,13 +637,13 @@ pypo_message_set_msgstr_plural(PyPoMessage *self, PyObject *args)
316 if (object == Py_None) {
317 po_message_set_msgstr_plural(self->msg, index, "");
318 } else {
319- string = get_pystring_from_pyobject(object);
320+ string = get_pybytes_from_pyobject(object);
321
322 if (string == NULL)
323 /* Got an exception */
324 return NULL;
325 else {
326- msgstr = PyString_AsString(string);
327+ msgstr = PyBytes_AsString(string);
328 po_message_set_msgstr_plural(self->msg, index, msgstr);
329 Py_DECREF(string);
330 }
331@@ -711,7 +711,7 @@ pypo_message_check_format(PyPoMessage *self)
332 }
333
334 closure.error_list = PyList_New(0);
335- closure.error_string = PyString_FromString("");
336+ closure.error_string = PyNativeString_FromString("");
337
338 PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);
339 error_closure = &closure;
340@@ -723,7 +723,7 @@ pypo_message_check_format(PyPoMessage *self)
341 error_closure = NULL;
342 PyThread_release_lock(error_closure_lock);
343
344- if (PyString_Size(closure.error_string) != 0) {
345+ if (PyNativeString_Size(closure.error_string) != 0) {
346 PyObject *exc;
347
348 /* set up the exception */
349@@ -744,11 +744,8 @@ pypo_message_check_format(PyPoMessage *self)
350 }
351
352 static PyMethodDef pypo_message_methods[] = {
353-/* msgctxt support was added in 0.15. */
354-#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
355 { "set_msgctxt", (PyCFunction)pypo_message_set_msgctxt, METH_VARARGS,
356 doc_pypo_message_set_msgctxt },
357-#endif
358 { "set_msgid", (PyCFunction)pypo_message_set_msgid, METH_VARARGS,
359 doc_pypo_message_set_msgid },
360 { "set_msgid_plural", (PyCFunction)pypo_message_set_msgid_plural, METH_VARARGS,
361@@ -766,8 +763,6 @@ static PyMethodDef pypo_message_methods[] = {
362 { NULL, 0, 0 }
363 };
364
365-/* msgctxt support was added in 0.15. */
366-#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
367 PyDoc_STRVAR(doc_pypo_message_msgctxt,
368 "M.msgctxt -> the msgctxt for this PoMessage.");
369
370@@ -778,10 +773,9 @@ pypo_message_get_msgctxt(PyPoMessage *self, void *closure)
371
372 msgctxt = po_message_msgctxt(self->msg);
373 if (msgctxt)
374- return PyString_FromString(msgctxt);
375+ return PyBytes_FromString(msgctxt);
376 Py_RETURN_NONE;
377 }
378-#endif
379
380 PyDoc_STRVAR(doc_pypo_message_msgid,
381 "M.msgid -> the msgid for this PoMessage.");
382@@ -793,7 +787,7 @@ pypo_message_get_msgid(PyPoMessage *self, void *closure)
383
384 msgid = po_message_msgid(self->msg);
385 if (msgid)
386- return PyString_FromString(msgid);
387+ return PyBytes_FromString(msgid);
388 Py_RETURN_NONE;
389 }
390
391@@ -807,7 +801,7 @@ pypo_message_get_msgid_plural(PyPoMessage *self, void *closure)
392
393 msgid_plural = po_message_msgid_plural(self->msg);
394 if (msgid_plural)
395- return PyString_FromString(msgid_plural);
396+ return PyBytes_FromString(msgid_plural);
397 Py_RETURN_NONE;
398 }
399
400@@ -821,7 +815,7 @@ pypo_message_get_msgstr(PyPoMessage *self, void *closure)
401
402 msgstr = po_message_msgstr(self->msg);
403 if (msgstr)
404- return PyString_FromString(msgstr);
405+ return PyBytes_FromString(msgstr);
406 Py_RETURN_NONE;
407 }
408
409@@ -839,7 +833,7 @@ pypo_message_get_msgstr_plural(PyPoMessage *self, void *closure)
410 i = 0;
411 msgstr = po_message_msgstr_plural(self->msg, i);
412 while (msgstr) {
413- item = PyString_FromString(msgstr);
414+ item = PyBytes_FromString(msgstr);
415 PyList_Append(ret, item);
416 Py_DECREF(item);
417
418@@ -859,17 +853,14 @@ pypo_message_get_comments(PyPoMessage *self, void *closure)
419
420 comments = po_message_comments(self->msg);
421 if (comments)
422- return PyString_FromString(comments);
423+ return PyBytes_FromString(comments);
424 Py_RETURN_NONE;
425 }
426
427
428 static PyGetSetDef pypo_message_getsets[] = {
429-/* msgctxt support was added in 0.15. */
430-#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
431 { "msgctxt", (getter)pypo_message_get_msgctxt, (setter)0,
432 doc_pypo_message_msgctxt },
433-#endif
434 { "msgid", (getter)pypo_message_get_msgid, (setter)0,
435 doc_pypo_message_msgid },
436 { "msgid_plural", (getter)pypo_message_get_msgid_plural, (setter)0,
437@@ -887,8 +878,7 @@ PyDoc_STRVAR(doc_PyPoMessage_Type,
438 "PyMessage() -> new empty PoMessage instance.");
439
440 static PyTypeObject PyPoMessage_Type = {
441- PyObject_HEAD_INIT(NULL)
442- 0, /* ob_size */
443+ PyVarObject_HEAD_INIT(NULL, 0)
444 "gettextpo.PoMessage", /* tp_name */
445 sizeof(PyPoMessage), /* tp_basicsize */
446 .tp_flags = Py_TPFLAGS_DEFAULT,
447@@ -908,14 +898,27 @@ PyDoc_STRVAR(doc_gettextpo,
448 "be of use to translation applications, or applications that need to\n"
449 "manipulate or validate translations.");
450
451-PyMODINIT_FUNC
452-initgettextpo(void)
453+#if PY_MAJOR_VERSION >= 3
454+# define MOD_DEF(ob, name, doc, methods) \
455+ do { \
456+ static struct PyModuleDef moduledef = { \
457+ PyModuleDef_HEAD_INIT, name, doc, -1, methods \
458+ }; \
459+ ob = PyModule_Create(&moduledef); \
460+ } while (0)
461+#else
462+# define MOD_DEF(ob, name, doc, methods) \
463+ ob = Py_InitModule3(name, methods, doc);
464+#endif
465+
466+static PyObject *
467+do_init(void)
468 {
469 PyObject *mod;
470
471 if (libgettextpo_version < MIN_REQUIRED_GETTEXTPO_VERSION) {
472 PyErr_SetString(PyExc_RuntimeError, "version of libgettextpo too old");
473- return;
474+ return NULL;
475 }
476
477 gettextpo_error = PyErr_NewException("gettextpo.error",
478@@ -925,25 +928,39 @@ initgettextpo(void)
479
480 /* initialise PoMessage type */
481 #define INIT_TYPE(type) \
482- if (!type.ob_type) \
483- type.ob_type = &PyType_Type; \
484 if (!type.tp_alloc) \
485 type.tp_alloc = PyType_GenericAlloc; \
486 if (!type.tp_new) \
487 type.tp_new = PyType_GenericNew; \
488 if (PyType_Ready(&type) < 0) \
489- return
490+ return NULL
491
492 INIT_TYPE(PyPoFile_Type);
493 INIT_TYPE(PyPoMessageIterator_Type);
494 INIT_TYPE(PyPoMessage_Type);
495
496- mod = Py_InitModule3("gettextpo", NULL, doc_gettextpo);
497-
498+ MOD_DEF(mod, "gettextpo", doc_gettextpo, NULL);
499+
500 Py_INCREF(&PyPoFile_Type);
501 PyModule_AddObject(mod, "PoFile", (PyObject *)&PyPoFile_Type);
502 Py_INCREF(&PyPoMessage_Type);
503 PyModule_AddObject(mod, "PoMessage", (PyObject *)&PyPoMessage_Type);
504 Py_INCREF(gettextpo_error);
505 PyModule_AddObject(mod, "error", gettextpo_error);
506+
507+ return mod;
508+}
509+
510+#if PY_MAJOR_VERSION >= 3
511+PyMODINIT_FUNC
512+PyInit_gettextpo(void)
513+{
514+ return do_init();
515 }
516+#else
517+PyMODINIT_FUNC
518+initgettextpo(void)
519+{
520+ do_init();
521+}
522+#endif
523diff --git a/setup.py b/setup.py
524index 076e994..6f1a22c 100755
525--- a/setup.py
526+++ b/setup.py
527@@ -3,7 +3,7 @@
528 # Copyright Canonical Ltd. This software is licensed under the GNU
529 # Affero General Public License version 3 (see the file LICENSE).
530
531-from distutils.core import setup, Extension
532+from setuptools import setup, Extension
533
534 gettextpo = Extension(
535 'gettextpo', ['gettextpo.c'],
536@@ -11,7 +11,15 @@ gettextpo = Extension(
537
538 setup(
539 name='pygettextpo',
540- version='0.1',
541- author='Canonical Ltd',
542+ version='0.2',
543+ author='Canonical Ltd.',
544+ author_email='lazr-developers@lists.launchpad.net',
545 description='A binding for the libgettext-po library',
546+ url='https://launchpad.net/pygettextpo',
547+ classifiers=[
548+ "License :: OSI Approved :: GNU Affero General Public License v3",
549+ "Programming Language :: Python",
550+ "Programming Language :: Python :: 2",
551+ "Programming Language :: Python :: 3",
552+ ],
553 ext_modules=[gettextpo])
554diff --git a/test_gettextpo.py b/test_gettextpo.py
555index 41c4207..7dbee20 100644
556--- a/test_gettextpo.py
557+++ b/test_gettextpo.py
558@@ -10,24 +10,24 @@ class PoFileTestCase(unittest.TestCase):
559 def testCreateEmpty(self):
560 # Test that we can create an empty pofile object
561 pofile = gettextpo.PoFile()
562- self.assertEquals(list(iter(pofile)), [])
563+ self.assertEqual(list(iter(pofile)), [])
564
565 def testAddMessage(self):
566 # Test that we can add messages to a new pofile object
567 pofile = gettextpo.PoFile()
568 msg = gettextpo.PoMessage()
569- msg.set_msgid('Hello')
570+ msg.set_msgid(b'Hello')
571 poiter = iter(pofile)
572 poiter.insert(msg)
573
574- self.assertEquals(list(iter(pofile)), [msg])
575+ self.assertEqual(list(iter(pofile)), [msg])
576
577 def testAddMessageTwice(self):
578 # A message object can only be added to one pofile object
579 pofile1 = gettextpo.PoFile()
580 pofile2 = gettextpo.PoFile()
581 msg = gettextpo.PoMessage()
582- msg.set_msgid('Hello')
583+ msg.set_msgid(b'Hello')
584
585 poiter = iter(pofile1)
586 poiter.insert(msg)
587@@ -44,39 +44,47 @@ class PoMessageTestCase(unittest.TestCase):
588
589 def testSetMsgId(self):
590 msg = gettextpo.PoMessage()
591- msg.set_msgid('Hello')
592- self.assertEquals(msg.msgid, 'Hello')
593- msg.set_msgid_plural('Hellos')
594- self.assertEquals(msg.msgid_plural, 'Hellos')
595+ msg.set_msgid(b'Hello')
596+ self.assertEqual(msg.msgid, b'Hello')
597+ msg.set_msgid_plural(b'Hellos')
598+ self.assertEqual(msg.msgid_plural, b'Hellos')
599
600 def testSetMsgCtxt(self):
601 msg = gettextpo.PoMessage()
602- msg.set_msgctxt('Hello')
603- self.assertEquals(msg.msgctxt, 'Hello')
604+ msg.set_msgctxt(b'Hello')
605+ self.assertEqual(msg.msgctxt, b'Hello')
606
607 def testSetMsgStr(self):
608 msg = gettextpo.PoMessage()
609- msg.set_msgstr('Hello World')
610- self.assertEquals(msg.msgstr, 'Hello World')
611+ msg.set_msgstr(b'Hello World')
612+ self.assertEqual(msg.msgstr, b'Hello World')
613
614 def testSetMsgStrPlural(self):
615 # Test handling of plural msgstrs. The PoMessage object can
616 # not hold plural msgstrs if the msgid does not have a plural.
617 msg = gettextpo.PoMessage()
618- msg.set_msgid('Something')
619- self.assertRaises(ValueError, msg.set_msgstr_plural, 0, 'Zero')
620- self.assertEquals(msg.msgstr_plural, [])
621+ msg.set_msgid(b'Something')
622+ self.assertRaises(ValueError, msg.set_msgstr_plural, 0, b'Zero')
623+ self.assertEqual(msg.msgstr_plural, [])
624
625 # need to set the plural msgid first, then add the plural msgstrs
626- msg.set_msgid_plural('Somethings')
627- msg.set_msgstr_plural(0, 'Zero')
628- msg.set_msgstr_plural(1, 'One')
629- msg.set_msgstr_plural(2, 'Two')
630- self.assertEquals(msg.msgstr_plural, ['Zero', 'One', 'Two'])
631+ msg.set_msgid_plural(b'Somethings')
632+ msg.set_msgstr_plural(0, b'Zero')
633+ msg.set_msgstr_plural(1, b'One')
634+ msg.set_msgstr_plural(2, b'Two')
635+ self.assertEqual(msg.msgstr_plural, [b'Zero', b'One', b'Two'])
636
637
638 class CheckFormatTestCase(unittest.TestCase):
639
640+ def assertGettextPoError(self, expected_errors, msg):
641+ with self.assertRaises(gettextpo.error) as raised:
642+ msg.check_format()
643+ self.assertEqual(expected_errors, raised.exception.error_list)
644+ self.assertEqual(
645+ "\n".join(message for _, _, message in expected_errors),
646+ str(raised.exception))
647+
648 def testGoodFormat(self):
649 # Check that no exception is raised on a good translation.
650
651@@ -85,36 +93,49 @@ class CheckFormatTestCase(unittest.TestCase):
652 # format a floating point value, so no error should be raised on
653 # that kind of change.
654 msg = gettextpo.PoMessage()
655- msg.set_msgid('Hello %s %d %g')
656+ msg.set_msgid(b'Hello %s %d %g')
657 msg.set_format('c-format', True)
658- msg.set_msgstr('Bye %s %.2d %f')
659+ msg.set_msgstr(b'Bye %s %.2d %f')
660
661 # this should run without an exception
662 msg.check_format()
663
664 def testAddFormatSpec(self):
665- #Test that an exception is raised when a format string is added.
666+ # Test that an exception is raised when a format string is added.
667 msg = gettextpo.PoMessage()
668- msg.set_msgid('No format specifiers')
669+ msg.set_msgid(b'No format specifiers')
670 msg.set_format('c-format', True)
671- msg.set_msgstr('One format specifier: %20s')
672- self.assertRaises(gettextpo.error, msg.check_format)
673+ msg.set_msgstr(b'One format specifier: %20s')
674+ expected_errors = [
675+ ("error", 0,
676+ "number of format specifications in 'msgid' and 'msgstr' does "
677+ "not match"),
678+ ]
679+ self.assertGettextPoError(expected_errors, msg)
680
681 def testSwapFormatSpecs(self):
682 # Test that an exception is raised when format strings are transposed.
683 msg = gettextpo.PoMessage()
684- msg.set_msgid('Spec 1: %s, Spec 2: %d')
685+ msg.set_msgid(b'Spec 1: %s, Spec 2: %d')
686 msg.set_format('c-format', True)
687- msg.set_msgstr('Spec 1: %d, Spec 2: %s')
688- self.assertRaises(gettextpo.error, msg.check_format)
689+ msg.set_msgstr(b'Spec 1: %d, Spec 2: %s')
690+ expected_errors = [
691+ ("error", 0,
692+ "format specifications in 'msgid' and 'msgstr' for argument 1 "
693+ "are not the same"),
694+ ("error", 0,
695+ "format specifications in 'msgid' and 'msgstr' for argument 2 "
696+ "are not the same"),
697+ ]
698+ self.assertGettextPoError(expected_errors, msg)
699
700 def testNonFormatString(self):
701 # Test that no exception is raised if the message is not marked as
702 # a format string.
703 msg = gettextpo.PoMessage()
704- msg.set_msgid('Spec 1: %s, Spec 2: %d')
705+ msg.set_msgid(b'Spec 1: %s, Spec 2: %d')
706 msg.set_format('c-format', False)
707- msg.set_msgstr('Spec 1: %d, Spec 2: %s')
708+ msg.set_msgstr(b'Spec 1: %d, Spec 2: %s')
709
710 # this should run without an exception
711 msg.check_format()
712@@ -122,7 +143,7 @@ class CheckFormatTestCase(unittest.TestCase):
713 def testEmptyMsgStr(self):
714 # Test that empty translations do not trigger a failure.
715 msg = gettextpo.PoMessage()
716- msg.set_msgid('Hello %s')
717+ msg.set_msgid(b'Hello %s')
718 msg.set_format('c-format', True)
719 msg.set_msgstr(None)
720
721@@ -132,12 +153,12 @@ class CheckFormatTestCase(unittest.TestCase):
722 def testGoodPlural(self):
723 # Test that a good plural message passes without error.
724 msg = gettextpo.PoMessage()
725- msg.set_msgid('%d apple')
726- msg.set_msgid_plural('%d apples')
727+ msg.set_msgid(b'%d apple')
728+ msg.set_msgid_plural(b'%d apples')
729 msg.set_format('c-format', True)
730- msg.set_msgstr_plural(0, '%d orange')
731- msg.set_msgstr_plural(1, '%d oranges')
732- msg.set_msgstr_plural(2, '%d oranges_')
733+ msg.set_msgstr_plural(0, b'%d orange')
734+ msg.set_msgstr_plural(1, b'%d oranges')
735+ msg.set_msgstr_plural(2, b'%d oranges_')
736
737 # this should run without an exception
738 msg.check_format()
739@@ -145,29 +166,34 @@ class CheckFormatTestCase(unittest.TestCase):
740 def testBadPlural(self):
741 # Test that bad plural translations raise an error error.
742 msg = gettextpo.PoMessage()
743- msg.set_msgid('%d apple')
744- msg.set_msgid_plural('%d apples')
745+ msg.set_msgid(b'%d apple')
746+ msg.set_msgid_plural(b'%d apples')
747 msg.set_format('c-format', True)
748- msg.set_msgstr_plural(0, '%d orange')
749- msg.set_msgstr_plural(1, '%d oranges')
750- msg.set_msgstr_plural(2, '%g oranges_')
751- self.assertRaises(gettextpo.error, msg.check_format)
752+ msg.set_msgstr_plural(0, b'%d orange')
753+ msg.set_msgstr_plural(1, b'%d oranges')
754+ msg.set_msgstr_plural(2, b'%g oranges_')
755+ expected_errors = [
756+ ("error", 0,
757+ "format specifications in 'msgid_plural' and 'msgstr[2]' for "
758+ "argument 1 are not the same"),
759+ ]
760+ self.assertGettextPoError(expected_errors, msg)
761
762 def testUnicodeString(self):
763 # Test that a translation with unicode chars is working.
764 msg = gettextpo.PoMessage()
765 msg.set_msgid(u'Carlos Perell\xf3 Mar\xedn')
766 msg.set_msgstr(u'Carlos Perell\xf3 Mar\xedn')
767- self.assertEqual(msg.msgid, 'Carlos Perell\xc3\xb3 Mar\xc3\xadn')
768- self.assertEqual(msg.msgstr, 'Carlos Perell\xc3\xb3 Mar\xc3\xadn')
769+ self.assertEqual(msg.msgid, b'Carlos Perell\xc3\xb3 Mar\xc3\xadn')
770+ self.assertEqual(msg.msgstr, b'Carlos Perell\xc3\xb3 Mar\xc3\xadn')
771
772 ## XXXX - gettext doesn't seem to check for this one
773 #
774 # def testBadPluralMsgId(self):
775 # # Test that conflicting plural msgids raise errors on their own.
776 # msg = gettextpo.PoMessage()
777-# msg.set_msgid('%d apple')
778-# msg.set_msgid_plural('%g apples')
779+# msg.set_msgid(b'%d apple')
780+# msg.set_msgid_plural(b'%g apples')
781 # msg.set_format('c-format', True)
782 # self.assertRaises(gettextpo.error, msg.check_format)
783 #

Subscribers

People subscribed via source and target branches