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
diff --git a/.gitignore b/.gitignore
index 1ff8f33..d0f7d29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
1gettextpo.html1gettextpo.html
2gettextpo.so2gettextpo*.so
3/*.egg-info
3/build4/build
4/dist5/dist
5/MANIFEST6/MANIFEST
diff --git a/Makefile b/Makefile
index bcff939..c42eb05 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ check: all
10 $(PYTHON) test_gettextpo.py -v10 $(PYTHON) test_gettextpo.py -v
1111
12clean:12clean:
13 rm -rf build dist gettextpo.so gettextpo.html13 rm -rf build dist gettextpo*.so gettextpo.html
1414
15gettextpo.so: setup.py gettextpo.c15gettextpo.so: setup.py gettextpo.c
16 $(PYTHON) setup.py build_ext -i16 $(PYTHON) setup.py build_ext -i
diff --git a/README b/README
index 5fdef4e..f690fe3 100644
--- a/README
+++ b/README
@@ -6,11 +6,6 @@ GPL v3 or later as of October 2007 (before that it was GPL v2 or
6later) [1]. Therefore, any code using this module must be under a license6later) [1]. Therefore, any code using this module must be under a license
7compatible with the GPL v2, v3, or any later version.7compatible with the GPL v2, v3, or any later version.
88
9Note: when this code was first added to Launchpad, it made use of
10interfaces added in gettext version 0.14.2, which is not yet widely
11distributed (e.g. not in Hoary), and so a trimmed down copy of the
12code is included here. That code probably isn't needed here anymore.
13
14See also http://code.google.com/p/pygettextpo/, which is probably a9See also http://code.google.com/p/pygettextpo/, which is probably a
15duplicate project, though it has not had a public release yet (as of10duplicate project, though it has not had a public release yet (as of
1626 May 2009).1126 May 2009).
diff --git a/gettextpo.c b/gettextpo.c
index d125726..451e284 100644
--- a/gettextpo.c
+++ b/gettextpo.c
@@ -10,11 +10,26 @@
1010
11#include <gettext-po.h>11#include <gettext-po.h>
1212
13#define MIN_REQUIRED_GETTEXTPO_VERSION 0x000E0213#define MIN_REQUIRED_GETTEXTPO_VERSION 0x000F00
14#define MIN_MSGCTXT_GETTEXTPO_VERSION 0x000F00
1514
16#if LIBGETTEXTPO_VERSION < MIN_REQUIRED_GETTEXTPO_VERSION15#if LIBGETTEXTPO_VERSION < MIN_REQUIRED_GETTEXTPO_VERSION
17# error "this module requires gettext >= 0.14.2"16# error "this module requires gettext >= 0.15"
17#endif
18
19#if PY_MAJOR_VERSION >= 3
20# define PyNativeString_FromString(s) PyUnicode_FromString(s)
21# define PyNativeString_FromFormat(format, ...) \
22 PyUnicode_FromFormat(format, __VA_ARGS__)
23# if PY_VERSION_HEX >= 0x03030000
24# define PyNativeString_Size(s) PyUnicode_GetLength(s)
25# else
26# define PyNativeString_Size(s) PyUnicode_GetSize(s)
27# endif
28#else
29# define PyNativeString_FromString(s) PyBytes_FromString(s)
30# define PyNativeString_FromFormat(format, ...) \
31 PyString_FromFormat(format, __VA_ARGS__)
32# define PyNativeString_Size(s) PyBytes_Size(s)
18#endif33#endif
1934
20static PyObject *gettextpo_error = NULL;35static PyObject *gettextpo_error = NULL;
@@ -42,22 +57,22 @@ static PyTypeObject PyPoMessage_Type;
42/* ---------------------------- */57/* ---------------------------- */
4358
44/**59/**
45 * get_pystring_from_pyobject:60 * get_pybytes_from_pyobject:
46 * @object: a PyObject that represents a PyUnicode object.61 * @object: a PyObject that represents a PyUnicode object.
47 *62 *
48 * Gets a PyString that represents the @object as UTF-8. If the63 * Gets a PyBytes that represents the @object as UTF-8. If the
49 * object is a PyString, then assume it is in UTF-8 and return it.64 * object is a PyBytes, then assume it is in UTF-8 and return it.
50 *65 *
51 * Return value: a new #PyString object or NULL if there is any error66 * Return value: a new #PyBytes object or NULL if there is any error
52 * in which case, PyErr is set.67 * in which case, PyErr is set.
53 **/68 **/
54static PyObject *69static PyObject *
55get_pystring_from_pyobject(PyObject *object)70get_pybytes_from_pyobject(PyObject *object)
56{71{
57 PyObject *unicode;72 PyObject *unicode;
58 PyObject *string;73 PyObject *string;
5974
60 if (PyString_Check(object)) {75 if (PyBytes_Check(object)) {
61 Py_INCREF(object);76 Py_INCREF(object);
62 return object;77 return object;
63 }78 }
@@ -96,38 +111,6 @@ typedef struct {
96static ErrorClosure *error_closure = NULL;111static ErrorClosure *error_closure = NULL;
97static PyThread_type_lock error_closure_lock= NULL;112static PyThread_type_lock error_closure_lock= NULL;
98113
99/* The error handler API changed in gettext-0.15 ... */
100#if LIBGETTEXTPO_VERSION < 0x000F00
101
102static void
103error_handler_error(int status, int errnum, const char *format, ...)
104{
105 va_list args;
106 PyObject *str;
107
108 /* printf the string */
109 va_start(args, format);
110 str = PyString_FromFormatV(format, args);
111 va_end(args);
112
113 /* store the errors in a list, and as a string */
114 PyList_Append(error_closure->error_list,
115 Py_BuildValue("(sis)", "error", errnum, str));
116 PyString_ConcatAndDel(&(error_closure->error_string), str);
117
118 /* if status is nonzero, we are not meant to return */
119 if (status != 0) {
120 fprintf(stderr, "error_handler_error: status!=0, longjmp'ing out\n");
121 longjmp(error_closure->env, 1);
122 }
123}
124
125struct po_error_handler error_handler = {
126 .error = error_handler_error,
127};
128
129#else /* LIBGETTEXTPO_VERSION >= 0.15.0*/
130
131static void114static void
132error_handler_xerror(int severity, po_message_t message,115error_handler_xerror(int severity, po_message_t message,
133 const char *filename, size_t lineno, size_t column,116 const char *filename, size_t lineno, size_t column,
@@ -136,12 +119,38 @@ error_handler_xerror(int severity, po_message_t message,
136 PyObject *str;119 PyObject *str;
137120
138 /* printf the string */121 /* printf the string */
139 str = PyString_FromString(message_text);122#if PY_MAJOR_VERSION >= 3
123 {
124 size_t size = strlen(message_text);
125 if (size > PY_SSIZE_T_MAX)
126 /* We can't raise an exception here, so just truncate it. */
127 size = PY_SSIZE_T_MAX;
128 str = PyUnicode_DecodeUTF8(message_text, (Py_ssize_t)size, "replace");
129 }
130#else
131 str = PyBytes_FromString(message_text);
132#endif
140133
141 /* store the errors in a list, and as a string */134 /* store the errors in a list, and as a string */
142 PyList_Append(error_closure->error_list, Py_BuildValue("(sis)",135 PyList_Append(error_closure->error_list, Py_BuildValue("(siO)",
143 severity == PO_SEVERITY_WARNING ? "warning" : "error", 0, str));136 severity == PO_SEVERITY_WARNING ? "warning" : "error", 0, str));
144 PyString_ConcatAndDel(&(error_closure->error_string), str);137#if PY_MAJOR_VERSION >= 3
138 {
139 PyObject *old_error_string = error_closure->error_string;
140 if (PyUnicode_GET_LENGTH(error_closure->error_string)) {
141 error_closure->error_string = PyUnicode_FromFormat(
142 "%U\n%U", error_closure->error_string, str);
143 Py_XDECREF(str);
144 } else
145 error_closure->error_string = str;
146 Py_XDECREF(old_error_string);
147 }
148#else
149 if (PyBytes_GET_SIZE(error_closure->error_string))
150 PyBytes_ConcatAndDel(&(error_closure->error_string),
151 PyBytes_FromString("\n"));
152 PyBytes_ConcatAndDel(&(error_closure->error_string), str);
153#endif
145154
146 /* if it is a fatal error, we are not meant to return */155 /* if it is a fatal error, we are not meant to return */
147 if (severity == PO_SEVERITY_FATAL_ERROR) {156 if (severity == PO_SEVERITY_FATAL_ERROR) {
@@ -155,8 +164,6 @@ struct po_xerror_handler error_handler = {
155 .xerror = error_handler_xerror,164 .xerror = error_handler_xerror,
156};165};
157166
158#endif /* LIBGETTEXTPO_VERSION */
159
160/* ---------------------------- */167/* ---------------------------- */
161168
162static int169static int
@@ -174,7 +181,7 @@ pypo_file_init(PyPoFile *self, PyObject *args, PyObject *kwargs)
174 ErrorClosure closure;181 ErrorClosure closure;
175182
176 closure.error_list = PyList_New(0);183 closure.error_list = PyList_New(0);
177 closure.error_string = PyString_FromString("");184 closure.error_string = PyNativeString_FromString("");
178185
179 PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);186 PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);
180 error_closure = &closure;187 error_closure = &closure;
@@ -186,7 +193,7 @@ pypo_file_init(PyPoFile *self, PyObject *args, PyObject *kwargs)
186 error_closure = NULL;193 error_closure = NULL;
187 PyThread_release_lock(error_closure_lock);194 PyThread_release_lock(error_closure_lock);
188195
189 if (PyString_Size(closure.error_string) != 0) {196 if (PyNativeString_Size(closure.error_string) != 0) {
190 PyObject *exc;197 PyObject *exc;
191198
192 /* set up the exception */199 /* set up the exception */
@@ -262,7 +269,7 @@ pypo_file_write(PyPoFile *self, PyObject *args)
262 return NULL;269 return NULL;
263270
264 closure.error_list = PyList_New(0);271 closure.error_list = PyList_New(0);
265 closure.error_string = PyString_FromString("");272 closure.error_string = PyNativeString_FromString("");
266273
267 PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);274 PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);
268 error_closure = &closure;275 error_closure = &closure;
@@ -274,7 +281,7 @@ pypo_file_write(PyPoFile *self, PyObject *args)
274 error_closure = NULL;281 error_closure = NULL;
275 PyThread_release_lock(error_closure_lock);282 PyThread_release_lock(error_closure_lock);
276283
277 if (PyString_Size(closure.error_string) != 0) {284 if (PyNativeString_Size(closure.error_string) != 0) {
278 PyObject *exc;285 PyObject *exc;
279286
280 /* set up the exception */287 /* set up the exception */
@@ -309,7 +316,7 @@ pypo_file_domain_header(PyPoFile *self, PyObject *args)
309316
310 header = po_file_domain_header(self->pofile, domain);317 header = po_file_domain_header(self->pofile, domain);
311 if (header) {318 if (header) {
312 return PyString_FromString(header);319 return PyBytes_FromString(header);
313 } else {320 } else {
314 Py_RETURN_NONE;321 Py_RETURN_NONE;
315 }322 }
@@ -335,7 +342,7 @@ pypo_file_domains(PyPoFile *self, void *closure)
335 ret = PyList_New(0);342 ret = PyList_New(0);
336 domains = po_file_domains(self->pofile);343 domains = po_file_domains(self->pofile);
337 while (domains && *domains) {344 while (domains && *domains) {
338 PyObject *item = PyString_FromString(*domains);345 PyObject *item = PyBytes_FromString(*domains);
339346
340 PyList_Append(ret, item);347 PyList_Append(ret, item);
341 Py_DECREF(item);348 Py_DECREF(item);
@@ -356,8 +363,7 @@ PyDoc_STRVAR(doc_PyPoFile_Type,
356"filename");363"filename");
357364
358static PyTypeObject PyPoFile_Type = {365static PyTypeObject PyPoFile_Type = {
359 PyObject_HEAD_INIT(NULL)366 PyVarObject_HEAD_INIT(NULL, 0)
360 0, /* ob_size */
361 "gettextpo.PoFile", /* tp_name */367 "gettextpo.PoFile", /* tp_name */
362 sizeof(PyPoFile), /* tp_basicsize */368 sizeof(PyPoFile), /* tp_basicsize */
363 .tp_flags = Py_TPFLAGS_DEFAULT,369 .tp_flags = Py_TPFLAGS_DEFAULT,
@@ -449,8 +455,7 @@ PyDoc_STRVAR(doc_PyPoMessageIterator_Type,
449"Iterator type for PoFile. Iterates over the PoFile's messages.");455"Iterator type for PoFile. Iterates over the PoFile's messages.");
450456
451static PyTypeObject PyPoMessageIterator_Type = {457static PyTypeObject PyPoMessageIterator_Type = {
452 PyObject_HEAD_INIT(NULL)458 PyVarObject_HEAD_INIT(NULL, 0)
453 0,
454 "gettextpo.PoMessageIterator",459 "gettextpo.PoMessageIterator",
455 sizeof(PyPoMessageIterator),460 sizeof(PyPoMessageIterator),
456 .tp_flags = Py_TPFLAGS_DEFAULT,461 .tp_flags = Py_TPFLAGS_DEFAULT,
@@ -511,8 +516,8 @@ pypo_message_repr(PyPoMessage *self)
511 if (self->msg)516 if (self->msg)
512 msgid = po_message_msgid(self->msg);517 msgid = po_message_msgid(self->msg);
513518
514 return PyString_FromFormat("<PoMessage for '%s'>",519 return PyNativeString_FromFormat("<PoMessage for '%s'>",
515 msgid ? msgid : "(null)");520 msgid ? msgid : "(null)");
516}521}
517522
518static PyObject *523static PyObject *
@@ -550,13 +555,13 @@ _message_set_field(PyPoMessage *self, PyObject *args, const char *field,
550 if (object == Py_None) {555 if (object == Py_None) {
551 (*setter)(self->msg, "");556 (*setter)(self->msg, "");
552 } else {557 } else {
553 string = get_pystring_from_pyobject(object);558 string = get_pybytes_from_pyobject(object);
554559
555 if (string == NULL)560 if (string == NULL)
556 /* Got an exception */561 /* Got an exception */
557 return NULL;562 return NULL;
558 else {563 else {
559 value = PyString_AsString(string);564 value = PyBytes_AsString(string);
560 (*setter)(self->msg, value);565 (*setter)(self->msg, value);
561 Py_DECREF(string);566 Py_DECREF(string);
562 }567 }
@@ -565,9 +570,6 @@ _message_set_field(PyPoMessage *self, PyObject *args, const char *field,
565 Py_RETURN_NONE;570 Py_RETURN_NONE;
566}571}
567572
568/* msgctxt support was added in 0.15. */
569#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
570
571PyDoc_STRVAR(doc_pypo_message_set_msgctxt,573PyDoc_STRVAR(doc_pypo_message_set_msgctxt,
572"M.set_msgctxt(msgctxt) -> None. Set the msgctxt for this PoMessage");574"M.set_msgctxt(msgctxt) -> None. Set the msgctxt for this PoMessage");
573575
@@ -577,8 +579,6 @@ pypo_message_set_msgctxt(PyPoMessage *self, PyObject *args)
577 return _message_set_field(self, args, "O:set_msgctxt",579 return _message_set_field(self, args, "O:set_msgctxt",
578 &po_message_set_msgctxt);580 &po_message_set_msgctxt);
579}581}
580#endif
581
582582
583PyDoc_STRVAR(doc_pypo_message_set_msgid,583PyDoc_STRVAR(doc_pypo_message_set_msgid,
584"M.set_msgid(msgid) -> None. Set the msgid for this PoMessage");584"M.set_msgid(msgid) -> None. Set the msgid for this PoMessage");
@@ -637,13 +637,13 @@ pypo_message_set_msgstr_plural(PyPoMessage *self, PyObject *args)
637 if (object == Py_None) {637 if (object == Py_None) {
638 po_message_set_msgstr_plural(self->msg, index, "");638 po_message_set_msgstr_plural(self->msg, index, "");
639 } else {639 } else {
640 string = get_pystring_from_pyobject(object);640 string = get_pybytes_from_pyobject(object);
641641
642 if (string == NULL)642 if (string == NULL)
643 /* Got an exception */643 /* Got an exception */
644 return NULL;644 return NULL;
645 else {645 else {
646 msgstr = PyString_AsString(string);646 msgstr = PyBytes_AsString(string);
647 po_message_set_msgstr_plural(self->msg, index, msgstr);647 po_message_set_msgstr_plural(self->msg, index, msgstr);
648 Py_DECREF(string);648 Py_DECREF(string);
649 }649 }
@@ -711,7 +711,7 @@ pypo_message_check_format(PyPoMessage *self)
711 }711 }
712712
713 closure.error_list = PyList_New(0);713 closure.error_list = PyList_New(0);
714 closure.error_string = PyString_FromString("");714 closure.error_string = PyNativeString_FromString("");
715715
716 PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);716 PyThread_acquire_lock(error_closure_lock, WAIT_LOCK);
717 error_closure = &closure;717 error_closure = &closure;
@@ -723,7 +723,7 @@ pypo_message_check_format(PyPoMessage *self)
723 error_closure = NULL;723 error_closure = NULL;
724 PyThread_release_lock(error_closure_lock);724 PyThread_release_lock(error_closure_lock);
725725
726 if (PyString_Size(closure.error_string) != 0) {726 if (PyNativeString_Size(closure.error_string) != 0) {
727 PyObject *exc;727 PyObject *exc;
728728
729 /* set up the exception */729 /* set up the exception */
@@ -744,11 +744,8 @@ pypo_message_check_format(PyPoMessage *self)
744}744}
745745
746static PyMethodDef pypo_message_methods[] = {746static PyMethodDef pypo_message_methods[] = {
747/* msgctxt support was added in 0.15. */
748#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
749 { "set_msgctxt", (PyCFunction)pypo_message_set_msgctxt, METH_VARARGS,747 { "set_msgctxt", (PyCFunction)pypo_message_set_msgctxt, METH_VARARGS,
750 doc_pypo_message_set_msgctxt },748 doc_pypo_message_set_msgctxt },
751#endif
752 { "set_msgid", (PyCFunction)pypo_message_set_msgid, METH_VARARGS,749 { "set_msgid", (PyCFunction)pypo_message_set_msgid, METH_VARARGS,
753 doc_pypo_message_set_msgid },750 doc_pypo_message_set_msgid },
754 { "set_msgid_plural", (PyCFunction)pypo_message_set_msgid_plural, METH_VARARGS,751 { "set_msgid_plural", (PyCFunction)pypo_message_set_msgid_plural, METH_VARARGS,
@@ -766,8 +763,6 @@ static PyMethodDef pypo_message_methods[] = {
766 { NULL, 0, 0 }763 { NULL, 0, 0 }
767};764};
768765
769/* msgctxt support was added in 0.15. */
770#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
771PyDoc_STRVAR(doc_pypo_message_msgctxt,766PyDoc_STRVAR(doc_pypo_message_msgctxt,
772"M.msgctxt -> the msgctxt for this PoMessage.");767"M.msgctxt -> the msgctxt for this PoMessage.");
773768
@@ -778,10 +773,9 @@ pypo_message_get_msgctxt(PyPoMessage *self, void *closure)
778773
779 msgctxt = po_message_msgctxt(self->msg);774 msgctxt = po_message_msgctxt(self->msg);
780 if (msgctxt)775 if (msgctxt)
781 return PyString_FromString(msgctxt);776 return PyBytes_FromString(msgctxt);
782 Py_RETURN_NONE;777 Py_RETURN_NONE;
783}778}
784#endif
785779
786PyDoc_STRVAR(doc_pypo_message_msgid,780PyDoc_STRVAR(doc_pypo_message_msgid,
787"M.msgid -> the msgid for this PoMessage.");781"M.msgid -> the msgid for this PoMessage.");
@@ -793,7 +787,7 @@ pypo_message_get_msgid(PyPoMessage *self, void *closure)
793787
794 msgid = po_message_msgid(self->msg);788 msgid = po_message_msgid(self->msg);
795 if (msgid)789 if (msgid)
796 return PyString_FromString(msgid);790 return PyBytes_FromString(msgid);
797 Py_RETURN_NONE;791 Py_RETURN_NONE;
798}792}
799793
@@ -807,7 +801,7 @@ pypo_message_get_msgid_plural(PyPoMessage *self, void *closure)
807801
808 msgid_plural = po_message_msgid_plural(self->msg);802 msgid_plural = po_message_msgid_plural(self->msg);
809 if (msgid_plural)803 if (msgid_plural)
810 return PyString_FromString(msgid_plural);804 return PyBytes_FromString(msgid_plural);
811 Py_RETURN_NONE;805 Py_RETURN_NONE;
812}806}
813807
@@ -821,7 +815,7 @@ pypo_message_get_msgstr(PyPoMessage *self, void *closure)
821815
822 msgstr = po_message_msgstr(self->msg);816 msgstr = po_message_msgstr(self->msg);
823 if (msgstr)817 if (msgstr)
824 return PyString_FromString(msgstr);818 return PyBytes_FromString(msgstr);
825 Py_RETURN_NONE;819 Py_RETURN_NONE;
826}820}
827821
@@ -839,7 +833,7 @@ pypo_message_get_msgstr_plural(PyPoMessage *self, void *closure)
839 i = 0;833 i = 0;
840 msgstr = po_message_msgstr_plural(self->msg, i);834 msgstr = po_message_msgstr_plural(self->msg, i);
841 while (msgstr) {835 while (msgstr) {
842 item = PyString_FromString(msgstr);836 item = PyBytes_FromString(msgstr);
843 PyList_Append(ret, item);837 PyList_Append(ret, item);
844 Py_DECREF(item);838 Py_DECREF(item);
845839
@@ -859,17 +853,14 @@ pypo_message_get_comments(PyPoMessage *self, void *closure)
859853
860 comments = po_message_comments(self->msg);854 comments = po_message_comments(self->msg);
861 if (comments)855 if (comments)
862 return PyString_FromString(comments);856 return PyBytes_FromString(comments);
863 Py_RETURN_NONE;857 Py_RETURN_NONE;
864}858}
865859
866860
867static PyGetSetDef pypo_message_getsets[] = {861static PyGetSetDef pypo_message_getsets[] = {
868/* msgctxt support was added in 0.15. */
869#if LIBGETTEXTPO_VERSION >= MIN_MSGCTXT_GETTEXTPO_VERSION
870 { "msgctxt", (getter)pypo_message_get_msgctxt, (setter)0,862 { "msgctxt", (getter)pypo_message_get_msgctxt, (setter)0,
871 doc_pypo_message_msgctxt },863 doc_pypo_message_msgctxt },
872#endif
873 { "msgid", (getter)pypo_message_get_msgid, (setter)0,864 { "msgid", (getter)pypo_message_get_msgid, (setter)0,
874 doc_pypo_message_msgid },865 doc_pypo_message_msgid },
875 { "msgid_plural", (getter)pypo_message_get_msgid_plural, (setter)0,866 { "msgid_plural", (getter)pypo_message_get_msgid_plural, (setter)0,
@@ -887,8 +878,7 @@ PyDoc_STRVAR(doc_PyPoMessage_Type,
887"PyMessage() -> new empty PoMessage instance.");878"PyMessage() -> new empty PoMessage instance.");
888879
889static PyTypeObject PyPoMessage_Type = {880static PyTypeObject PyPoMessage_Type = {
890 PyObject_HEAD_INIT(NULL)881 PyVarObject_HEAD_INIT(NULL, 0)
891 0, /* ob_size */
892 "gettextpo.PoMessage", /* tp_name */882 "gettextpo.PoMessage", /* tp_name */
893 sizeof(PyPoMessage), /* tp_basicsize */883 sizeof(PyPoMessage), /* tp_basicsize */
894 .tp_flags = Py_TPFLAGS_DEFAULT,884 .tp_flags = Py_TPFLAGS_DEFAULT,
@@ -908,14 +898,27 @@ PyDoc_STRVAR(doc_gettextpo,
908"be of use to translation applications, or applications that need to\n"898"be of use to translation applications, or applications that need to\n"
909"manipulate or validate translations.");899"manipulate or validate translations.");
910900
911PyMODINIT_FUNC901#if PY_MAJOR_VERSION >= 3
912initgettextpo(void)902# define MOD_DEF(ob, name, doc, methods) \
903 do { \
904 static struct PyModuleDef moduledef = { \
905 PyModuleDef_HEAD_INIT, name, doc, -1, methods \
906 }; \
907 ob = PyModule_Create(&moduledef); \
908 } while (0)
909#else
910# define MOD_DEF(ob, name, doc, methods) \
911 ob = Py_InitModule3(name, methods, doc);
912#endif
913
914static PyObject *
915do_init(void)
913{916{
914 PyObject *mod;917 PyObject *mod;
915918
916 if (libgettextpo_version < MIN_REQUIRED_GETTEXTPO_VERSION) {919 if (libgettextpo_version < MIN_REQUIRED_GETTEXTPO_VERSION) {
917 PyErr_SetString(PyExc_RuntimeError, "version of libgettextpo too old");920 PyErr_SetString(PyExc_RuntimeError, "version of libgettextpo too old");
918 return;921 return NULL;
919 }922 }
920923
921 gettextpo_error = PyErr_NewException("gettextpo.error",924 gettextpo_error = PyErr_NewException("gettextpo.error",
@@ -925,25 +928,39 @@ initgettextpo(void)
925928
926 /* initialise PoMessage type */929 /* initialise PoMessage type */
927#define INIT_TYPE(type) \930#define INIT_TYPE(type) \
928 if (!type.ob_type) \
929 type.ob_type = &PyType_Type; \
930 if (!type.tp_alloc) \931 if (!type.tp_alloc) \
931 type.tp_alloc = PyType_GenericAlloc; \932 type.tp_alloc = PyType_GenericAlloc; \
932 if (!type.tp_new) \933 if (!type.tp_new) \
933 type.tp_new = PyType_GenericNew; \934 type.tp_new = PyType_GenericNew; \
934 if (PyType_Ready(&type) < 0) \935 if (PyType_Ready(&type) < 0) \
935 return936 return NULL
936937
937 INIT_TYPE(PyPoFile_Type);938 INIT_TYPE(PyPoFile_Type);
938 INIT_TYPE(PyPoMessageIterator_Type);939 INIT_TYPE(PyPoMessageIterator_Type);
939 INIT_TYPE(PyPoMessage_Type);940 INIT_TYPE(PyPoMessage_Type);
940941
941 mod = Py_InitModule3("gettextpo", NULL, doc_gettextpo);942 MOD_DEF(mod, "gettextpo", doc_gettextpo, NULL);
942 943
943 Py_INCREF(&PyPoFile_Type);944 Py_INCREF(&PyPoFile_Type);
944 PyModule_AddObject(mod, "PoFile", (PyObject *)&PyPoFile_Type);945 PyModule_AddObject(mod, "PoFile", (PyObject *)&PyPoFile_Type);
945 Py_INCREF(&PyPoMessage_Type);946 Py_INCREF(&PyPoMessage_Type);
946 PyModule_AddObject(mod, "PoMessage", (PyObject *)&PyPoMessage_Type);947 PyModule_AddObject(mod, "PoMessage", (PyObject *)&PyPoMessage_Type);
947 Py_INCREF(gettextpo_error);948 Py_INCREF(gettextpo_error);
948 PyModule_AddObject(mod, "error", gettextpo_error);949 PyModule_AddObject(mod, "error", gettextpo_error);
950
951 return mod;
952}
953
954#if PY_MAJOR_VERSION >= 3
955PyMODINIT_FUNC
956PyInit_gettextpo(void)
957{
958 return do_init();
949}959}
960#else
961PyMODINIT_FUNC
962initgettextpo(void)
963{
964 do_init();
965}
966#endif
diff --git a/setup.py b/setup.py
index 076e994..6f1a22c 100755
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
3# Copyright Canonical Ltd. This software is licensed under the GNU3# Copyright Canonical Ltd. This software is licensed under the GNU
4# Affero General Public License version 3 (see the file LICENSE).4# Affero General Public License version 3 (see the file LICENSE).
55
6from distutils.core import setup, Extension6from setuptools import setup, Extension
77
8gettextpo = Extension(8gettextpo = Extension(
9 'gettextpo', ['gettextpo.c'],9 'gettextpo', ['gettextpo.c'],
@@ -11,7 +11,15 @@ gettextpo = Extension(
1111
12setup(12setup(
13 name='pygettextpo',13 name='pygettextpo',
14 version='0.1',14 version='0.2',
15 author='Canonical Ltd',15 author='Canonical Ltd.',
16 author_email='lazr-developers@lists.launchpad.net',
16 description='A binding for the libgettext-po library',17 description='A binding for the libgettext-po library',
18 url='https://launchpad.net/pygettextpo',
19 classifiers=[
20 "License :: OSI Approved :: GNU Affero General Public License v3",
21 "Programming Language :: Python",
22 "Programming Language :: Python :: 2",
23 "Programming Language :: Python :: 3",
24 ],
17 ext_modules=[gettextpo])25 ext_modules=[gettextpo])
diff --git a/test_gettextpo.py b/test_gettextpo.py
index 41c4207..7dbee20 100644
--- a/test_gettextpo.py
+++ b/test_gettextpo.py
@@ -10,24 +10,24 @@ class PoFileTestCase(unittest.TestCase):
10 def testCreateEmpty(self):10 def testCreateEmpty(self):
11 # Test that we can create an empty pofile object11 # Test that we can create an empty pofile object
12 pofile = gettextpo.PoFile()12 pofile = gettextpo.PoFile()
13 self.assertEquals(list(iter(pofile)), [])13 self.assertEqual(list(iter(pofile)), [])
1414
15 def testAddMessage(self):15 def testAddMessage(self):
16 # Test that we can add messages to a new pofile object16 # Test that we can add messages to a new pofile object
17 pofile = gettextpo.PoFile()17 pofile = gettextpo.PoFile()
18 msg = gettextpo.PoMessage()18 msg = gettextpo.PoMessage()
19 msg.set_msgid('Hello')19 msg.set_msgid(b'Hello')
20 poiter = iter(pofile)20 poiter = iter(pofile)
21 poiter.insert(msg)21 poiter.insert(msg)
2222
23 self.assertEquals(list(iter(pofile)), [msg])23 self.assertEqual(list(iter(pofile)), [msg])
2424
25 def testAddMessageTwice(self):25 def testAddMessageTwice(self):
26 # A message object can only be added to one pofile object26 # A message object can only be added to one pofile object
27 pofile1 = gettextpo.PoFile()27 pofile1 = gettextpo.PoFile()
28 pofile2 = gettextpo.PoFile()28 pofile2 = gettextpo.PoFile()
29 msg = gettextpo.PoMessage()29 msg = gettextpo.PoMessage()
30 msg.set_msgid('Hello')30 msg.set_msgid(b'Hello')
3131
32 poiter = iter(pofile1)32 poiter = iter(pofile1)
33 poiter.insert(msg)33 poiter.insert(msg)
@@ -44,39 +44,47 @@ class PoMessageTestCase(unittest.TestCase):
4444
45 def testSetMsgId(self):45 def testSetMsgId(self):
46 msg = gettextpo.PoMessage()46 msg = gettextpo.PoMessage()
47 msg.set_msgid('Hello')47 msg.set_msgid(b'Hello')
48 self.assertEquals(msg.msgid, 'Hello')48 self.assertEqual(msg.msgid, b'Hello')
49 msg.set_msgid_plural('Hellos')49 msg.set_msgid_plural(b'Hellos')
50 self.assertEquals(msg.msgid_plural, 'Hellos')50 self.assertEqual(msg.msgid_plural, b'Hellos')
5151
52 def testSetMsgCtxt(self):52 def testSetMsgCtxt(self):
53 msg = gettextpo.PoMessage()53 msg = gettextpo.PoMessage()
54 msg.set_msgctxt('Hello')54 msg.set_msgctxt(b'Hello')
55 self.assertEquals(msg.msgctxt, 'Hello')55 self.assertEqual(msg.msgctxt, b'Hello')
5656
57 def testSetMsgStr(self):57 def testSetMsgStr(self):
58 msg = gettextpo.PoMessage()58 msg = gettextpo.PoMessage()
59 msg.set_msgstr('Hello World')59 msg.set_msgstr(b'Hello World')
60 self.assertEquals(msg.msgstr, 'Hello World')60 self.assertEqual(msg.msgstr, b'Hello World')
6161
62 def testSetMsgStrPlural(self):62 def testSetMsgStrPlural(self):
63 # Test handling of plural msgstrs. The PoMessage object can63 # Test handling of plural msgstrs. The PoMessage object can
64 # not hold plural msgstrs if the msgid does not have a plural.64 # not hold plural msgstrs if the msgid does not have a plural.
65 msg = gettextpo.PoMessage()65 msg = gettextpo.PoMessage()
66 msg.set_msgid('Something')66 msg.set_msgid(b'Something')
67 self.assertRaises(ValueError, msg.set_msgstr_plural, 0, 'Zero')67 self.assertRaises(ValueError, msg.set_msgstr_plural, 0, b'Zero')
68 self.assertEquals(msg.msgstr_plural, [])68 self.assertEqual(msg.msgstr_plural, [])
6969
70 # need to set the plural msgid first, then add the plural msgstrs70 # need to set the plural msgid first, then add the plural msgstrs
71 msg.set_msgid_plural('Somethings')71 msg.set_msgid_plural(b'Somethings')
72 msg.set_msgstr_plural(0, 'Zero')72 msg.set_msgstr_plural(0, b'Zero')
73 msg.set_msgstr_plural(1, 'One')73 msg.set_msgstr_plural(1, b'One')
74 msg.set_msgstr_plural(2, 'Two')74 msg.set_msgstr_plural(2, b'Two')
75 self.assertEquals(msg.msgstr_plural, ['Zero', 'One', 'Two'])75 self.assertEqual(msg.msgstr_plural, [b'Zero', b'One', b'Two'])
7676
7777
78class CheckFormatTestCase(unittest.TestCase):78class CheckFormatTestCase(unittest.TestCase):
7979
80 def assertGettextPoError(self, expected_errors, msg):
81 with self.assertRaises(gettextpo.error) as raised:
82 msg.check_format()
83 self.assertEqual(expected_errors, raised.exception.error_list)
84 self.assertEqual(
85 "\n".join(message for _, _, message in expected_errors),
86 str(raised.exception))
87
80 def testGoodFormat(self):88 def testGoodFormat(self):
81 # Check that no exception is raised on a good translation.89 # Check that no exception is raised on a good translation.
8290
@@ -85,36 +93,49 @@ class CheckFormatTestCase(unittest.TestCase):
85 # format a floating point value, so no error should be raised on93 # format a floating point value, so no error should be raised on
86 # that kind of change.94 # that kind of change.
87 msg = gettextpo.PoMessage()95 msg = gettextpo.PoMessage()
88 msg.set_msgid('Hello %s %d %g')96 msg.set_msgid(b'Hello %s %d %g')
89 msg.set_format('c-format', True)97 msg.set_format('c-format', True)
90 msg.set_msgstr('Bye %s %.2d %f')98 msg.set_msgstr(b'Bye %s %.2d %f')
9199
92 # this should run without an exception100 # this should run without an exception
93 msg.check_format()101 msg.check_format()
94102
95 def testAddFormatSpec(self):103 def testAddFormatSpec(self):
96 #Test that an exception is raised when a format string is added.104 # Test that an exception is raised when a format string is added.
97 msg = gettextpo.PoMessage()105 msg = gettextpo.PoMessage()
98 msg.set_msgid('No format specifiers')106 msg.set_msgid(b'No format specifiers')
99 msg.set_format('c-format', True)107 msg.set_format('c-format', True)
100 msg.set_msgstr('One format specifier: %20s')108 msg.set_msgstr(b'One format specifier: %20s')
101 self.assertRaises(gettextpo.error, msg.check_format)109 expected_errors = [
110 ("error", 0,
111 "number of format specifications in 'msgid' and 'msgstr' does "
112 "not match"),
113 ]
114 self.assertGettextPoError(expected_errors, msg)
102115
103 def testSwapFormatSpecs(self):116 def testSwapFormatSpecs(self):
104 # Test that an exception is raised when format strings are transposed.117 # Test that an exception is raised when format strings are transposed.
105 msg = gettextpo.PoMessage()118 msg = gettextpo.PoMessage()
106 msg.set_msgid('Spec 1: %s, Spec 2: %d')119 msg.set_msgid(b'Spec 1: %s, Spec 2: %d')
107 msg.set_format('c-format', True)120 msg.set_format('c-format', True)
108 msg.set_msgstr('Spec 1: %d, Spec 2: %s')121 msg.set_msgstr(b'Spec 1: %d, Spec 2: %s')
109 self.assertRaises(gettextpo.error, msg.check_format)122 expected_errors = [
123 ("error", 0,
124 "format specifications in 'msgid' and 'msgstr' for argument 1 "
125 "are not the same"),
126 ("error", 0,
127 "format specifications in 'msgid' and 'msgstr' for argument 2 "
128 "are not the same"),
129 ]
130 self.assertGettextPoError(expected_errors, msg)
110131
111 def testNonFormatString(self):132 def testNonFormatString(self):
112 # Test that no exception is raised if the message is not marked as133 # Test that no exception is raised if the message is not marked as
113 # a format string.134 # a format string.
114 msg = gettextpo.PoMessage()135 msg = gettextpo.PoMessage()
115 msg.set_msgid('Spec 1: %s, Spec 2: %d')136 msg.set_msgid(b'Spec 1: %s, Spec 2: %d')
116 msg.set_format('c-format', False)137 msg.set_format('c-format', False)
117 msg.set_msgstr('Spec 1: %d, Spec 2: %s')138 msg.set_msgstr(b'Spec 1: %d, Spec 2: %s')
118139
119 # this should run without an exception140 # this should run without an exception
120 msg.check_format()141 msg.check_format()
@@ -122,7 +143,7 @@ class CheckFormatTestCase(unittest.TestCase):
122 def testEmptyMsgStr(self):143 def testEmptyMsgStr(self):
123 # Test that empty translations do not trigger a failure.144 # Test that empty translations do not trigger a failure.
124 msg = gettextpo.PoMessage()145 msg = gettextpo.PoMessage()
125 msg.set_msgid('Hello %s')146 msg.set_msgid(b'Hello %s')
126 msg.set_format('c-format', True)147 msg.set_format('c-format', True)
127 msg.set_msgstr(None)148 msg.set_msgstr(None)
128149
@@ -132,12 +153,12 @@ class CheckFormatTestCase(unittest.TestCase):
132 def testGoodPlural(self):153 def testGoodPlural(self):
133 # Test that a good plural message passes without error.154 # Test that a good plural message passes without error.
134 msg = gettextpo.PoMessage()155 msg = gettextpo.PoMessage()
135 msg.set_msgid('%d apple')156 msg.set_msgid(b'%d apple')
136 msg.set_msgid_plural('%d apples')157 msg.set_msgid_plural(b'%d apples')
137 msg.set_format('c-format', True)158 msg.set_format('c-format', True)
138 msg.set_msgstr_plural(0, '%d orange')159 msg.set_msgstr_plural(0, b'%d orange')
139 msg.set_msgstr_plural(1, '%d oranges')160 msg.set_msgstr_plural(1, b'%d oranges')
140 msg.set_msgstr_plural(2, '%d oranges_')161 msg.set_msgstr_plural(2, b'%d oranges_')
141162
142 # this should run without an exception163 # this should run without an exception
143 msg.check_format()164 msg.check_format()
@@ -145,29 +166,34 @@ class CheckFormatTestCase(unittest.TestCase):
145 def testBadPlural(self):166 def testBadPlural(self):
146 # Test that bad plural translations raise an error error.167 # Test that bad plural translations raise an error error.
147 msg = gettextpo.PoMessage()168 msg = gettextpo.PoMessage()
148 msg.set_msgid('%d apple')169 msg.set_msgid(b'%d apple')
149 msg.set_msgid_plural('%d apples')170 msg.set_msgid_plural(b'%d apples')
150 msg.set_format('c-format', True)171 msg.set_format('c-format', True)
151 msg.set_msgstr_plural(0, '%d orange')172 msg.set_msgstr_plural(0, b'%d orange')
152 msg.set_msgstr_plural(1, '%d oranges')173 msg.set_msgstr_plural(1, b'%d oranges')
153 msg.set_msgstr_plural(2, '%g oranges_')174 msg.set_msgstr_plural(2, b'%g oranges_')
154 self.assertRaises(gettextpo.error, msg.check_format)175 expected_errors = [
176 ("error", 0,
177 "format specifications in 'msgid_plural' and 'msgstr[2]' for "
178 "argument 1 are not the same"),
179 ]
180 self.assertGettextPoError(expected_errors, msg)
155181
156 def testUnicodeString(self):182 def testUnicodeString(self):
157 # Test that a translation with unicode chars is working.183 # Test that a translation with unicode chars is working.
158 msg = gettextpo.PoMessage()184 msg = gettextpo.PoMessage()
159 msg.set_msgid(u'Carlos Perell\xf3 Mar\xedn')185 msg.set_msgid(u'Carlos Perell\xf3 Mar\xedn')
160 msg.set_msgstr(u'Carlos Perell\xf3 Mar\xedn')186 msg.set_msgstr(u'Carlos Perell\xf3 Mar\xedn')
161 self.assertEqual(msg.msgid, 'Carlos Perell\xc3\xb3 Mar\xc3\xadn')187 self.assertEqual(msg.msgid, b'Carlos Perell\xc3\xb3 Mar\xc3\xadn')
162 self.assertEqual(msg.msgstr, 'Carlos Perell\xc3\xb3 Mar\xc3\xadn')188 self.assertEqual(msg.msgstr, b'Carlos Perell\xc3\xb3 Mar\xc3\xadn')
163189
164## XXXX - gettext doesn't seem to check for this one190## XXXX - gettext doesn't seem to check for this one
165#191#
166# def testBadPluralMsgId(self):192# def testBadPluralMsgId(self):
167# # Test that conflicting plural msgids raise errors on their own.193# # Test that conflicting plural msgids raise errors on their own.
168# msg = gettextpo.PoMessage()194# msg = gettextpo.PoMessage()
169# msg.set_msgid('%d apple')195# msg.set_msgid(b'%d apple')
170# msg.set_msgid_plural('%g apples')196# msg.set_msgid_plural(b'%g apples')
171# msg.set_format('c-format', True)197# msg.set_format('c-format', True)
172# self.assertRaises(gettextpo.error, msg.check_format)198# self.assertRaises(gettextpo.error, msg.check_format)
173#199#

Subscribers

People subscribed via source and target branches