Merge ~cjwatson/pygettextpo:py3 into pygettextpo:main
- Git
- lp:~cjwatson/pygettextpo
- py3
- Merge into 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) |
Related bugs: |
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:/
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/.gitignore b/.gitignore |
2 | index 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 |
13 | diff --git a/Makefile b/Makefile |
14 | index 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 |
26 | diff --git a/README b/README |
27 | index 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). |
42 | diff --git a/gettextpo.c b/gettextpo.c |
43 | index 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 |
523 | diff --git a/setup.py b/setup.py |
524 | index 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]) |
554 | diff --git a/test_gettextpo.py b/test_gettextpo.py |
555 | index 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 | # |
I've built this and poked some strings at it, on py 3.5 and py3.8, seems to work :)